132 lines
4.8 KiB
Markdown
132 lines
4.8 KiB
Markdown
|
|
# Gate 2D: BLEPeerSessionManager Python Equivalence
|
||
|
|
|
||
|
|
Date: 2026-05-18 16:11 America/Los_Angeles
|
||
|
|
|
||
|
|
Scope: Python equivalence harness only. No live BLE integration.
|
||
|
|
|
||
|
|
## Summary
|
||
|
|
|
||
|
|
Gate 2D added a Python test harness that compares current Python
|
||
|
|
`BLEInterface._handle_identity_handshake` behavior with the C++
|
||
|
|
`BLEPeerSessionManager` pybind decision/result model.
|
||
|
|
|
||
|
|
Added:
|
||
|
|
|
||
|
|
- `migration/tests/test_ble_peer_session_manager_python_equivalence.py`
|
||
|
|
- `migration/phase2/Gate2D_BLEPeerSessionManager_python_equivalence_20260518_1611.md`
|
||
|
|
- `migration/sql/mark_gate2d_protocol_session_python_equivalence_20260518_1611.sql`
|
||
|
|
|
||
|
|
Unchanged:
|
||
|
|
|
||
|
|
- `src/ble_reticulum/BLEInterface.py`
|
||
|
|
- live Python BLE behavior
|
||
|
|
- driver/platform code
|
||
|
|
- BlueZ/Bleak/DBus integration
|
||
|
|
- `RNS.Transport` integration
|
||
|
|
- ESP32 BLE integration
|
||
|
|
|
||
|
|
## Harness Design
|
||
|
|
|
||
|
|
The harness imports the real `BLEInterface` class, then binds these real Python methods onto a minimal fake object:
|
||
|
|
|
||
|
|
- `_handle_identity_handshake`
|
||
|
|
- `_check_duplicate_identity`
|
||
|
|
- `_compute_identity_hash`
|
||
|
|
- `_get_fragmenter_key`
|
||
|
|
|
||
|
|
The fake object supplies only the state those methods require:
|
||
|
|
|
||
|
|
- `address_to_identity`
|
||
|
|
- `identity_to_address`
|
||
|
|
- `address_to_interface`
|
||
|
|
- `spawned_interfaces`
|
||
|
|
- `peers`
|
||
|
|
- `fragmenters`
|
||
|
|
- `reassemblers`
|
||
|
|
- `_pending_identity_connections`
|
||
|
|
- `_pending_detach`
|
||
|
|
- `_last_real_data`
|
||
|
|
- `_zombie_timeout`
|
||
|
|
- fake driver
|
||
|
|
- fake fragmenter/reassembler constructors
|
||
|
|
- fake stale-address cleanup and spawn hooks
|
||
|
|
|
||
|
|
This avoids real RNS runtime, BlueZ, Bleak, DBus, and live BLE dependencies while still exercising the actual Python reference methods.
|
||
|
|
|
||
|
|
## Equivalence Cases Covered
|
||
|
|
|
||
|
|
| Case | Python reference behavior checked | C++ result checked |
|
||
|
|
|---|---|---|
|
||
|
|
| non-16-byte payload | returns `False` | `PassToReassembler`, `consumed=false` |
|
||
|
|
| new 16-byte identity | maps updated, fragmenter/reassembler created, pending removed, last-real-data updated, spawn requested | `AcceptedNewIdentity`, `CreateFragmentationState`, `MarkPeerReady`, `RemovePendingIdentity`, `MarkRealData` |
|
||
|
|
| known identity duplicate same | returns `True`, no normal data processing | `ConsumedDuplicateSameIdentity`, `consumed=true` |
|
||
|
|
| known identity duplicate mismatch | returns `True`, warning recorded | `ConsumedDuplicateMismatchedIdentity`, `Warn`, `consumed=true` |
|
||
|
|
| duplicate identity active elsewhere | disconnects current address | `RejectedDuplicateIdentity`, `DisconnectCurrentPeer` |
|
||
|
|
| duplicate identity with pending detach / stale old address | accepts new identity and invokes stale cleanup | `AcceptedNewIdentity`, `CleanupOldAddress`, `UpdatePeerAddress` |
|
||
|
|
| duplicate identity with zombie old connection | accepts new identity and disconnects old address | `AcceptedNewIdentity`, `DisconnectOldPeer`, `CleanupOldAddress` |
|
||
|
|
| MTU provided | fragmenter uses driver MTU | result MTU equals provided MTU |
|
||
|
|
| MTU missing | fragmenter falls back to 23 | result MTU equals 23 |
|
||
|
|
| pending identity removal | successful handshake removes pending address | `RemovePendingIdentity` |
|
||
|
|
| existing spawned interface path | existing peer address and `address_to_interface` update | `UpdatePeerAddress`, `MarkPeerReady` |
|
||
|
|
| exception compatibility | Python consumes packet after synthetic MTU exception | skipped for C++; manager has no platform exception surface |
|
||
|
|
|
||
|
|
The skipped exception case is intentional documentation: current Python consumes after adapter-side exceptions, but the C++ session manager does not call platform dependencies such as `driver.get_peer_mtu`, so `ErrorConsumed` is not exercised cleanly at Gate 2D.
|
||
|
|
|
||
|
|
## Verification
|
||
|
|
|
||
|
|
New Gate 2D test:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
PYTHONPATH=migration/protocol_core pytest -q migration/tests/test_ble_peer_session_manager_python_equivalence.py
|
||
|
|
```
|
||
|
|
|
||
|
|
Result:
|
||
|
|
|
||
|
|
```text
|
||
|
|
11 passed, 1 skipped, 2 warnings in 0.24s
|
||
|
|
```
|
||
|
|
|
||
|
|
Gate 2C / Gate 2D regression:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
PYTHONPATH=migration/protocol_core pytest -q migration/tests/test_ble_peer_session_manager_pybind.py migration/tests/test_ble_peer_session_manager_python_equivalence.py
|
||
|
|
```
|
||
|
|
|
||
|
|
Result:
|
||
|
|
|
||
|
|
```text
|
||
|
|
23 passed, 1 skipped, 2 warnings in 0.18s
|
||
|
|
```
|
||
|
|
|
||
|
|
Backend shim regression:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
pytest -q migration/tests/test_fragmentation_backend_shim.py
|
||
|
|
```
|
||
|
|
|
||
|
|
Result:
|
||
|
|
|
||
|
|
```text
|
||
|
|
9 passed, 2 warnings in 0.64s
|
||
|
|
```
|
||
|
|
|
||
|
|
Migration regression set:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
PYTHONPATH=migration/protocol_core pytest -q migration/tests/test_fragmentation_cpp_equivalence.py migration/tests/test_identity_helpers_cpp_equivalence.py migration/tests/test_ble_peer_session_manager_pybind.py migration/tests/test_ble_peer_session_manager_python_equivalence.py
|
||
|
|
```
|
||
|
|
|
||
|
|
Result:
|
||
|
|
|
||
|
|
```text
|
||
|
|
71 passed, 2 skipped, 2 warnings in 0.47s
|
||
|
|
```
|
||
|
|
|
||
|
|
## SQL
|
||
|
|
|
||
|
|
Companion SQL:
|
||
|
|
|
||
|
|
`migration/sql/mark_gate2d_protocol_session_python_equivalence_20260518_1611.sql`
|
||
|
|
|
||
|
|
The SQL marks `_handle_identity_handshake` as `PYTHON_EQUIVALENT` for phase
|
||
|
|
`2_ble_protocol_session_manager`, keeps tag `GLUE`, keeps `cpp_candidate=1`, and does not mark any field-accepted status.
|