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.
- {bdo_toolkit-1.0.1/src/bdo_toolkit.egg-info → bdo_toolkit-1.0.3}/PKG-INFO +16 -39
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/README.md +15 -38
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_capture_backend.py +121 -4
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_deposit_origin.py +579 -155
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_engine.py +9 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_framing.py +28 -6
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_reassembly.py +32 -14
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_version.py +1 -1
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/calibration.py +50 -2
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/capture.py +6 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/character_state.py +16 -1
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_live_tracker.py +3 -3
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_replay_capture.py +2 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/session.py +7 -14
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3/src/bdo_toolkit.egg-info}/PKG-INFO +16 -39
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit.egg-info/SOURCES.txt +1 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_calibration.py +28 -9
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_calibration_lifecycle.py +13 -1
- bdo_toolkit-1.0.3/tests/test_capture_backend.py +446 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_character_state.py +182 -6
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_deposit_origin.py +834 -7
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_inventory_snapshots.py +43 -1
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_reassembly.py +196 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_replay.py +4 -1
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_session.py +11 -23
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/LICENSE +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/pyproject.toml +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/setup.cfg +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/__init__.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_async_sessions.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_capture_options.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_capture_runtime.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_profile_runtime.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_protocol.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_specs.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_storage_destination_validation.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/_storage_hydration.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/cli.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/diagnostics.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/events.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/filters.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/item_state.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/origin_learning.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/profiles.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/py.typed +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/remote_profiles.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/__init__.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_constants.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_detail_learning.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_details.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_discovery.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_result.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_scanner.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/_validation.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/async_session.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/models.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/solare/replay.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit/writers.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit.egg-info/dependency_links.txt +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit.egg-info/entry_points.txt +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit.egg-info/requires.txt +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/src/bdo_toolkit.egg-info/top_level.txt +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_async_sessions.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_calibration_conveniences.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_calibration_retention.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_capture_runtime.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_cli.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_decoder_diagnostics.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_direction.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_dynamic_storage_calibration_matrix.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_engine.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_examples_and_writers.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_explicit_profile.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_fixture_regressions.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_hardening.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_initial_load_storage.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_item_state.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_live_capture_session.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_origin_learning.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_remote_profiles.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_api.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_details.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_learning.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_learning_adversarial.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_solare_real_captures.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_storage_destination_validation.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_storage_events.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_storage_hydration_tracker.py +0 -0
- {bdo_toolkit-1.0.1 → bdo_toolkit-1.0.3}/tests/test_storage_unknown_destination_calibration.py +0 -0
- {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.
|
|
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
|
[](https://github.com/ychwu/bdo-toolkit/actions/workflows/ci.yml)
|
|
31
|
+
[](https://pypi.org/project/bdo-toolkit/)
|
|
31
32
|
[](https://github.com/ychwu/bdo-toolkit/blob/main/pyproject.toml)
|
|
32
33
|

|
|
33
34
|
[](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
|
-
|
|
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
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
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
|
[](https://github.com/ychwu/bdo-toolkit/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/bdo-toolkit/)
|
|
4
5
|
[](https://github.com/ychwu/bdo-toolkit/blob/main/pyproject.toml)
|
|
5
6
|

|
|
6
7
|
[](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
|
-
|
|
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
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
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
|
|
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
|
-
|
|
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=
|
|
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),
|