bdo-toolkit 1.0.1__tar.gz → 1.0.3__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 (90) hide show
  1. {bdo_toolkit-1.0.1/src/bdo_toolkit.egg-info → bdo_toolkit-1.0.3}/PKG-INFO +16 -39
  2. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/README.md +15 -38
  3. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_capture_backend.py +121 -4
  4. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_deposit_origin.py +579 -155
  5. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_engine.py +9 -0
  6. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_framing.py +28 -6
  7. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_reassembly.py +32 -14
  8. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_version.py +1 -1
  9. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/calibration.py +50 -2
  10. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/capture.py +6 -0
  11. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/character_state.py +16 -1
  12. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_live_tracker.py +3 -3
  13. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_replay_capture.py +2 -0
  14. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/session.py +7 -14
  15. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3/src/bdo_toolkit.egg-info}/PKG-INFO +16 -39
  16. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit.egg-info/SOURCES.txt +1 -0
  17. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_calibration.py +28 -9
  18. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_calibration_lifecycle.py +13 -1
  19. bdo_toolkit-1.0.3/tests/test_capture_backend.py +446 -0
  20. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_character_state.py +182 -6
  21. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_deposit_origin.py +834 -7
  22. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_inventory_snapshots.py +43 -1
  23. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_reassembly.py +196 -0
  24. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_replay.py +4 -1
  25. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_session.py +11 -23
  26. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/LICENSE +0 -0
  27. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/pyproject.toml +0 -0
  28. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/setup.cfg +0 -0
  29. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/__init__.py +0 -0
  30. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_async_sessions.py +0 -0
  31. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_capture_options.py +0 -0
  32. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_capture_runtime.py +0 -0
  33. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_profile_runtime.py +0 -0
  34. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_protocol.py +0 -0
  35. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_specs.py +0 -0
  36. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_storage_destination_validation.py +0 -0
  37. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_storage_hydration.py +0 -0
  38. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/cli.py +0 -0
  39. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/diagnostics.py +0 -0
  40. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/events.py +0 -0
  41. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/filters.py +0 -0
  42. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/item_state.py +0 -0
  43. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/origin_learning.py +0 -0
  44. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/profiles.py +0 -0
  45. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/py.typed +0 -0
  46. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/remote_profiles.py +0 -0
  47. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/__init__.py +0 -0
  48. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_constants.py +0 -0
  49. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_detail_learning.py +0 -0
  50. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_details.py +0 -0
  51. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_discovery.py +0 -0
  52. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_result.py +0 -0
  53. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_scanner.py +0 -0
  54. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_validation.py +0 -0
  55. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/async_session.py +0 -0
  56. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/models.py +0 -0
  57. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/replay.py +0 -0
  58. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/writers.py +0 -0
  59. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit.egg-info/dependency_links.txt +0 -0
  60. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit.egg-info/entry_points.txt +0 -0
  61. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit.egg-info/requires.txt +0 -0
  62. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit.egg-info/top_level.txt +0 -0
  63. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_async_sessions.py +0 -0
  64. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_calibration_conveniences.py +0 -0
  65. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_calibration_retention.py +0 -0
  66. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_capture_runtime.py +0 -0
  67. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_cli.py +0 -0
  68. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_decoder_diagnostics.py +0 -0
  69. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_direction.py +0 -0
  70. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_dynamic_storage_calibration_matrix.py +0 -0
  71. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_engine.py +0 -0
  72. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_examples_and_writers.py +0 -0
  73. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_explicit_profile.py +0 -0
  74. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_fixture_regressions.py +0 -0
  75. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_hardening.py +0 -0
  76. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_initial_load_storage.py +0 -0
  77. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_item_state.py +0 -0
  78. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_live_capture_session.py +0 -0
  79. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_origin_learning.py +0 -0
  80. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_remote_profiles.py +0 -0
  81. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_api.py +0 -0
  82. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_details.py +0 -0
  83. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_learning.py +0 -0
  84. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_learning_adversarial.py +0 -0
  85. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_real_captures.py +0 -0
  86. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_storage_destination_validation.py +0 -0
  87. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_storage_events.py +0 -0
  88. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_storage_hydration_tracker.py +0 -0
  89. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_storage_unknown_destination_calibration.py +0 -0
  90. {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_toolkit_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bdo-toolkit
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: Passive, read-only BDO packet parsing toolkit for app developers.
5
5
  Author: bdo-toolkit contributors
6
6
  License-Expression: MIT
@@ -28,6 +28,7 @@ Dynamic: license-file
28
28
  # bdo-toolkit
29
29
 
30
30
  [![CI](https://img.shields.io/github/actions/workflow/status/ychwu/bdo-toolkit/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/ychwu/bdo-toolkit/actions/workflows/ci.yml)
31
+ [![PyPI version](https://img.shields.io/pypi/v/bdo-toolkit?style=flat-square&label=version)](https://pypi.org/project/bdo-toolkit/)
31
32
  [![Python 3.14+](https://img.shields.io/badge/python-3.14%2B-3776AB?style=flat-square&logo=python&logoColor=white)](https://github.com/ychwu/bdo-toolkit/blob/main/pyproject.toml)
32
33
  ![Tested on NA/EU](https://img.shields.io/badge/tested-NA%2FEU-5b61a8?style=flat-square)
33
34
  [![Package: Stable](https://img.shields.io/badge/package-stable-2f855a?style=flat-square)](https://ychwu.github.io/bdo-toolkit/#stability)
@@ -48,11 +49,21 @@ traffic into structured, application-ready data.
48
49
 
49
50
  ## Capabilities
50
51
 
51
- | In-game task | Python result | Status | Guide |
52
+ bdo-toolkit exposes three passive workflows. Each can observe live traffic or
53
+ replay a saved PCAP or PCAPNG file.
54
+
55
+ | Capability | What it provides | Guide | Status |
52
56
  | --- | --- | --- | --- |
53
- | Watch supported item changes such as loot, gathering, and storage activity | A continuing stream of typed `BDOEvent` objects | Stable | [Item events](https://ychwu.github.io/bdo-toolkit/#item-overview) |
54
- | Log in or switch characters | One observational `ItemStateSnapshot` of inventory, known balances, and town storage | Beta | [Inventory & town storage](https://ychwu.github.io/bdo-toolkit/#item-state-overview) |
55
- | Load the Arena of Solare Leaderboard | One `SolareCaptureResult`; a complete result contains a leaderboard snapshot | Beta | [Arena of Solare](https://ychwu.github.io/bdo-toolkit/#solare-overview) |
57
+ | Item activity | A continuing stream of typed `BDOEvent` objects for supported loot, gathering, inventory, and storage changes | [Item events](https://ychwu.github.io/bdo-toolkit/#item-overview) | Stable |
58
+ | Inventory and town storage | A finite `ItemStateSnapshot` assembled from character-load traffic, with inventory, known balances, and observed town storage | [Inventory & town storage](https://ychwu.github.io/bdo-toolkit/#item-state-overview) | Beta |
59
+ | Arena of Solare leaderboards | A finite `SolareCaptureResult` containing overall rankings, class tables, and player statistics when the capture is complete | [Arena of Solare](https://ychwu.github.io/bdo-toolkit/#solare-overview) | Beta |
60
+
61
+ These workflows include synchronous and
62
+ [asyncio](https://ychwu.github.io/bdo-toolkit/#asyncio) sessions, capture and
63
+ decoder health diagnostics, console and JSONL event writers, and a
64
+ [command-line interface](https://ychwu.github.io/bdo-toolkit/#cli). Exact
65
+ signatures, fields, lifecycle behavior, and failure contracts are in the
66
+ [API index](https://ychwu.github.io/bdo-toolkit/#api-index).
56
67
 
57
68
  Testing and validation cover **NA/EU only**. Compatibility with other regional
58
69
  services is unknown.
@@ -98,46 +109,12 @@ installed with the Python wheel.
98
109
  See the [Examples index](https://ychwu.github.io/bdo-toolkit/#item-examples)
99
110
  for every script, its prerequisites, and the guide that explains it.
100
111
 
101
- ## Documentation
102
-
103
- The documentation separates task-focused guides from the symbol-first API
104
- reference. Exact signatures, fields, lifecycle behavior, and failure contracts
105
- live there rather than in this README.
106
-
107
- | Goal | Start here |
108
- | --- | --- |
109
- | Understand the package | [Overview](https://ychwu.github.io/bdo-toolkit/#overview) |
110
- | Build with live item events | [Item events](https://ychwu.github.io/bdo-toolkit/#item-overview) |
111
- | Query character inventory and town storage | [Inventory & town storage](https://ychwu.github.io/bdo-toolkit/#item-state-overview) |
112
- | Rebuild item decoding after a patch | [Calibration](https://ychwu.github.io/bdo-toolkit/#calibration-workflow) |
113
- | Capture and query an Arena of Solare leaderboard | [Arena of Solare](https://ychwu.github.io/bdo-toolkit/#solare-overview) |
114
- | Integrate with an asyncio application | [Asyncio integration](https://ychwu.github.io/bdo-toolkit/#asyncio) |
115
- | Look up a class, function, or model | [API index](https://ychwu.github.io/bdo-toolkit/#api-index) |
116
- | Use the terminal interface | [Command line](https://ychwu.github.io/bdo-toolkit/#cli) |
117
- | Diagnose a problem | [Troubleshooting](https://ychwu.github.io/bdo-toolkit/#errors) |
118
- | Review data handling and project boundaries | [Safety & privacy](https://ychwu.github.io/bdo-toolkit/#stability) |
119
-
120
112
  ## Support
121
113
 
122
114
  For questions, contact me on Discord: `._.__.__._._.__._____.__._.___.`
123
115
 
124
116
  For bugs and feature requests, [open a GitHub issue](https://github.com/ychwu/bdo-toolkit/issues).
125
117
 
126
- ## Development
127
-
128
- ```powershell
129
- git clone https://github.com/ychwu/bdo-toolkit.git
130
- cd bdo-toolkit
131
- python -m pip install -e ".[dev]"
132
- python -m pytest -q -W error
133
- python -m mypy src/bdo_toolkit
134
- python -m pip wheel . --no-deps --wheel-dir dist
135
- ```
136
-
137
- CI runs tests, type checking, wheel construction, and a CLI smoke test on
138
- Ubuntu and Windows with Python 3.14. Tests that require private
139
- game-session captures skip when those local fixtures are absent.
140
-
141
118
  ## License
142
119
 
143
120
  bdo-toolkit is available under the
@@ -1,6 +1,7 @@
1
1
  # bdo-toolkit
2
2
 
3
3
  [![CI](https://img.shields.io/github/actions/workflow/status/ychwu/bdo-toolkit/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/ychwu/bdo-toolkit/actions/workflows/ci.yml)
4
+ [![PyPI version](https://img.shields.io/pypi/v/bdo-toolkit?style=flat-square&label=version)](https://pypi.org/project/bdo-toolkit/)
4
5
  [![Python 3.14+](https://img.shields.io/badge/python-3.14%2B-3776AB?style=flat-square&logo=python&logoColor=white)](https://github.com/ychwu/bdo-toolkit/blob/main/pyproject.toml)
5
6
  ![Tested on NA/EU](https://img.shields.io/badge/tested-NA%2FEU-5b61a8?style=flat-square)
6
7
  [![Package: Stable](https://img.shields.io/badge/package-stable-2f855a?style=flat-square)](https://ychwu.github.io/bdo-toolkit/#stability)
@@ -21,11 +22,21 @@ traffic into structured, application-ready data.
21
22
 
22
23
  ## Capabilities
23
24
 
24
- | In-game task | Python result | Status | Guide |
25
+ bdo-toolkit exposes three passive workflows. Each can observe live traffic or
26
+ replay a saved PCAP or PCAPNG file.
27
+
28
+ | Capability | What it provides | Guide | Status |
25
29
  | --- | --- | --- | --- |
26
- | Watch supported item changes such as loot, gathering, and storage activity | A continuing stream of typed `BDOEvent` objects | Stable | [Item events](https://ychwu.github.io/bdo-toolkit/#item-overview) |
27
- | Log in or switch characters | One observational `ItemStateSnapshot` of inventory, known balances, and town storage | Beta | [Inventory & town storage](https://ychwu.github.io/bdo-toolkit/#item-state-overview) |
28
- | Load the Arena of Solare Leaderboard | One `SolareCaptureResult`; a complete result contains a leaderboard snapshot | Beta | [Arena of Solare](https://ychwu.github.io/bdo-toolkit/#solare-overview) |
30
+ | Item activity | A continuing stream of typed `BDOEvent` objects for supported loot, gathering, inventory, and storage changes | [Item events](https://ychwu.github.io/bdo-toolkit/#item-overview) | Stable |
31
+ | Inventory and town storage | A finite `ItemStateSnapshot` assembled from character-load traffic, with inventory, known balances, and observed town storage | [Inventory & town storage](https://ychwu.github.io/bdo-toolkit/#item-state-overview) | Beta |
32
+ | Arena of Solare leaderboards | A finite `SolareCaptureResult` containing overall rankings, class tables, and player statistics when the capture is complete | [Arena of Solare](https://ychwu.github.io/bdo-toolkit/#solare-overview) | Beta |
33
+
34
+ These workflows include synchronous and
35
+ [asyncio](https://ychwu.github.io/bdo-toolkit/#asyncio) sessions, capture and
36
+ decoder health diagnostics, console and JSONL event writers, and a
37
+ [command-line interface](https://ychwu.github.io/bdo-toolkit/#cli). Exact
38
+ signatures, fields, lifecycle behavior, and failure contracts are in the
39
+ [API index](https://ychwu.github.io/bdo-toolkit/#api-index).
29
40
 
30
41
  Testing and validation cover **NA/EU only**. Compatibility with other regional
31
42
  services is unknown.
@@ -71,46 +82,12 @@ installed with the Python wheel.
71
82
  See the [Examples index](https://ychwu.github.io/bdo-toolkit/#item-examples)
72
83
  for every script, its prerequisites, and the guide that explains it.
73
84
 
74
- ## Documentation
75
-
76
- The documentation separates task-focused guides from the symbol-first API
77
- reference. Exact signatures, fields, lifecycle behavior, and failure contracts
78
- live there rather than in this README.
79
-
80
- | Goal | Start here |
81
- | --- | --- |
82
- | Understand the package | [Overview](https://ychwu.github.io/bdo-toolkit/#overview) |
83
- | Build with live item events | [Item events](https://ychwu.github.io/bdo-toolkit/#item-overview) |
84
- | Query character inventory and town storage | [Inventory & town storage](https://ychwu.github.io/bdo-toolkit/#item-state-overview) |
85
- | Rebuild item decoding after a patch | [Calibration](https://ychwu.github.io/bdo-toolkit/#calibration-workflow) |
86
- | Capture and query an Arena of Solare leaderboard | [Arena of Solare](https://ychwu.github.io/bdo-toolkit/#solare-overview) |
87
- | Integrate with an asyncio application | [Asyncio integration](https://ychwu.github.io/bdo-toolkit/#asyncio) |
88
- | Look up a class, function, or model | [API index](https://ychwu.github.io/bdo-toolkit/#api-index) |
89
- | Use the terminal interface | [Command line](https://ychwu.github.io/bdo-toolkit/#cli) |
90
- | Diagnose a problem | [Troubleshooting](https://ychwu.github.io/bdo-toolkit/#errors) |
91
- | Review data handling and project boundaries | [Safety & privacy](https://ychwu.github.io/bdo-toolkit/#stability) |
92
-
93
85
  ## Support
94
86
 
95
87
  For questions, contact me on Discord: `._.__.__._._.__._____.__._.___.`
96
88
 
97
89
  For bugs and feature requests, [open a GitHub issue](https://github.com/ychwu/bdo-toolkit/issues).
98
90
 
99
- ## Development
100
-
101
- ```powershell
102
- git clone https://github.com/ychwu/bdo-toolkit.git
103
- cd bdo-toolkit
104
- python -m pip install -e ".[dev]"
105
- python -m pytest -q -W error
106
- python -m mypy src/bdo_toolkit
107
- python -m pip wheel . --no-deps --wheel-dir dist
108
- ```
109
-
110
- CI runs tests, type checking, wheel construction, and a CLI smoke test on
111
- Ubuntu and Windows with Python 3.14. Tests that require private
112
- game-session captures skip when those local fixtures are absent.
113
-
114
91
  ## License
115
92
 
116
93
  bdo-toolkit is available under the
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from dataclasses import dataclass
6
6
  from pathlib import Path
7
- from typing import Iterable, Iterator, Optional, Protocol
7
+ from typing import Any, Iterable, Iterator, Optional, Protocol
8
8
 
9
9
 
10
10
  class SegmentConsumer(Protocol):
@@ -93,21 +93,138 @@ def validate_server_ports(ports: Iterable[int]) -> tuple[int, ...]:
93
93
  return tuple(normalized)
94
94
 
95
95
 
96
+ def _required_header_integer(value: Any, field_name: str) -> int:
97
+ """Return one decoded header field or reject an incomplete dissection."""
98
+ if value is None:
99
+ raise ValueError(f"IPv4/TCP {field_name} is unavailable")
100
+ try:
101
+ return int(value)
102
+ except (TypeError, ValueError, OverflowError) as exc:
103
+ raise ValueError(f"IPv4/TCP {field_name} is invalid: {value!r}") from exc
104
+
105
+
106
+ def _validate_unfragmented_ipv4_tcp(ip: Any) -> None:
107
+ """Reject IP states that cannot be handed to TCP reassembly safely."""
108
+ version = _required_header_integer(getattr(ip, "version", None), "version")
109
+ if version != 4:
110
+ raise ValueError(f"expected IPv4 version 4, got {version}")
111
+
112
+ fragment_offset = _required_header_integer(
113
+ getattr(ip, "frag", None),
114
+ "fragment offset",
115
+ )
116
+ flags = _required_header_integer(getattr(ip, "flags", None), "flags")
117
+ if fragment_offset != 0 or flags & 0x01:
118
+ raise ValueError(
119
+ "fragmented IPv4/TCP packets are unsupported; "
120
+ "TCP reassembly requires a complete IP datagram"
121
+ )
122
+
123
+
124
+ def _captured_ipv4_length(ip: Any) -> int:
125
+ """Return captured bytes without reserializing ordinary Scapy packets."""
126
+ original = getattr(ip, "original", None)
127
+ if isinstance(original, (bytes, bytearray, memoryview)) and original:
128
+ return len(original)
129
+ return len(bytes(ip))
130
+
131
+
132
+ def _extract_ipv4_tcp_payload(ip: Any, tcp: Any) -> bytes:
133
+ """Return only application bytes declared by complete IPv4/TCP headers.
134
+
135
+ Scapy exposes link-layer padding through ``tcp.payload`` even though those
136
+ bytes lie beyond the IPv4 total length. The wire header lengths are the
137
+ authority at this boundary so padding can never advance TCP sequence state.
138
+ """
139
+ total_length = _required_header_integer(
140
+ getattr(ip, "len", None),
141
+ "total length",
142
+ )
143
+ ip_header_words = _required_header_integer(
144
+ getattr(ip, "ihl", None),
145
+ "header length",
146
+ )
147
+ tcp_header_words = _required_header_integer(
148
+ getattr(tcp, "dataofs", None),
149
+ "TCP header length",
150
+ )
151
+
152
+ if not 5 <= ip_header_words <= 15:
153
+ raise ValueError(
154
+ f"invalid IPv4 header length: {ip_header_words} 32-bit words"
155
+ )
156
+ if not 5 <= tcp_header_words <= 15:
157
+ raise ValueError(
158
+ f"invalid TCP header length: {tcp_header_words} 32-bit words"
159
+ )
160
+ if not 0 <= total_length <= 0xFFFF:
161
+ raise ValueError(f"invalid IPv4 total length: {total_length}")
162
+
163
+ captured_length = _captured_ipv4_length(ip)
164
+ if captured_length < total_length:
165
+ raise ValueError(
166
+ "truncated IPv4 packet: "
167
+ f"header declares {total_length} bytes, capture has {captured_length}"
168
+ )
169
+
170
+ header_length = (ip_header_words + tcp_header_words) * 4
171
+ if total_length < header_length:
172
+ raise ValueError(
173
+ "invalid IPv4/TCP header lengths: "
174
+ f"total length {total_length} is smaller than {header_length}"
175
+ )
176
+
177
+ declared_length = total_length - header_length
178
+ available = bytes(getattr(tcp, "payload", b""))
179
+ if len(available) < declared_length:
180
+ raise ValueError(
181
+ "truncated IPv4/TCP payload: "
182
+ f"header declares {declared_length} bytes, capture has {len(available)}"
183
+ )
184
+ return available[:declared_length]
185
+
186
+
187
+ def _consumer_server_ports(engine: SegmentConsumer) -> Optional[frozenset[int]]:
188
+ """Read the validated port set exposed by each built-in consumer."""
189
+ ports = getattr(engine, "server_ports", None)
190
+ if ports is None:
191
+ # Solare's internal collector predates the shared ``server_ports`` name.
192
+ ports = getattr(engine, "ports", None)
193
+ if ports is None:
194
+ return None
195
+ return frozenset(ports)
196
+
197
+
96
198
  def make_packet_handler(engine: SegmentConsumer):
97
199
  IP, TCP, _, _, _ = import_scapy()
200
+ server_ports = _consumer_server_ports(engine)
98
201
 
99
202
  def handle(packet) -> None:
100
- if IP not in packet or TCP not in packet:
203
+ if IP not in packet:
101
204
  return
102
205
 
103
206
  ip = packet[IP]
207
+ protocol = _required_header_integer(getattr(ip, "proto", None), "protocol")
208
+ if protocol != 6:
209
+ return
210
+
211
+ if TCP not in packet:
212
+ # Non-initial fragments and severely truncated headers have no
213
+ # source port, so they cannot be attributed to a selected flow.
214
+ return
215
+
104
216
  tcp = packet[TCP]
105
- payload = bytes(tcp.payload)
217
+ source_port = int(tcp.sport)
218
+ if server_ports is not None and source_port not in server_ports:
219
+ return
220
+
221
+ _validate_unfragmented_ipv4_tcp(ip)
222
+ payload = _extract_ipv4_tcp_payload(ip, tcp)
106
223
  flags = int(tcp.flags)
107
224
 
108
225
  engine.process_tcp_segment(
109
226
  source_ip=str(ip.src),
110
- source_port=int(tcp.sport),
227
+ source_port=source_port,
111
228
  destination_ip=str(ip.dst),
112
229
  destination_port=int(tcp.dport),
113
230
  sequence=int(tcp.seq),