safety, no data imported into sqlite yet

This commit is contained in:
John Poole 2026-05-16 15:46:53 -07:00
commit 41a5c86a8e
8 changed files with 940 additions and 0 deletions

1
migration/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.sqlite

View file

@ -0,0 +1,47 @@
I am trying to extract the protocol-only portion of this BLE Reticulum implementation into a future C++ library callable from Python.
Use /usr/local/src/ble-reticulum/src/ble_symbols_20260516_1429.txt as the master inventory. For every symbol listed in that file, inspect the corresponding source code and classify it as:
[CORE]
Protocol logic that may belong in a future C++/Rust library.
[GLUE]
Python Reticulum integration that should probably stay Python initially.
[PLATFORM]
Linux/BLE/BlueZ/Bleak/bluezero/DBus/GATT/scanning/advertising code.
[TEST]
Examples, proof scripts, diagnostics, command-line tools, or test harnesses.
[UNKNOWN]
Not enough information to classify safely.
For each symbol, produce a Markdown table with:
- file path
- symbol name
- line number
- tag
- phase-1 C++ candidate: yes/no
- rationale
- important callers, if easily found
- important callees, if easily found
Do not modify files.
At the end, produce:
1. A "Phase 1 C++ extraction candidates" list.
2. A "Do not port yet" list.
3. A short explanation of where the Python/C++ boundary should probably be.
4. Any symbols whose classification depends on Reticulum internals.
For information about where reticulum is installed, see /usr/local/src/ble-reticulum/reticulum_info.txt
The main tree is /usr/local/src/ble-reticulum which is pulled from my Forgejo instance. I have created a branch "c++migration" which is the current branch.
Treat ble_dual_node_echo.py as an acceptance test/harness, not as the source of the protocol-core design.
Prefer conservative classification. If a symbol touches RNS.Transport, RNS.Interface, RNS.Link, BleakClient, bluezero, DBus, sockets, threads, or OS-specific objects, do not mark it as phase-1 C++ unless the protocol logic can be cleanly separated from those dependencies.

View file

@ -0,0 +1,154 @@
Read-only inspection completed. Note: the prompts Reticulum info path was not present as named; I used `/usr/local/src/ble-reticulum/reticulum_info_20260516_1444.txt`. Installed RNS is `rns 1.1.3` at `/home/jlpoole/rnsenv/lib/python3.13/site-packages/RNS`.
**BLEFragmentation.py**
| file path | symbol | line | tag | phase-1 C++ candidate | rationale | important callers | important callees |
|---|---:|---:|---|---|---|---|---|
| `src/ble_reticulum/BLEFragmentation.py` | `BLEFragmenter` | 52 | [CORE] | yes | Pure BLE packet fragmentation format; RNS only logging. | `BLEInterface._mtu_negotiated_callback`, `_handle_identity_handshake`, `BLEPeerInterface.process_outgoing` | `struct.pack` |
| same | `BLEFragmenter.__init__` | 68 | [CORE] | yes | MTU/header sizing logic. | fragmenter construction | none |
| same | `BLEFragmenter.fragment_packet` | 82 | [CORE] | yes | Core packet-to-fragments encoding. | `BLEPeerInterface.process_outgoing` | `struct.pack` |
| same | `BLEFragmenter.get_fragment_overhead` | 158 | [CORE] | yes | Pure sizing/overhead calculation. | tests/diagnostics likely | none |
| same | `BLEReassembler` | 176 | [CORE] | yes | Core fragment state machine; RNS only logging. | `BLEInterface` receive paths | `_reassemble`, `cleanup_stale_buffers` |
| same | `BLEReassembler.__init__` | 187 | [CORE] | yes | Protocol reassembly state. | reassembler construction | none |
| same | `BLEReassembler.receive_fragment` | 205 | [CORE] | yes | Core fragment validation/dedup/reassembly trigger. | `_handle_ble_data`, `handle_peripheral_data` | `struct.unpack`, `_reassemble` |
| same | `BLEReassembler._reassemble` | 380 | [CORE] | yes | Pure ordered fragment join. | `receive_fragment` | none |
| same | `BLEReassembler.cleanup_stale_buffers` | 402 | [CORE] | yes | Timeout policy for incomplete protocol frames. | cleanup timer, receive paths | `time.time` |
| same | `BLEReassembler.get_statistics` | 429 | [CORE] | yes | Protocol stats. | receive/logging paths | none |
| same | `BLEReassembler.reset_statistics` | 443 | [CORE] | yes | Protocol stats reset. | tests likely | none |
| same | `HDLCFramer` | 450 | [CORE] | yes | Pure byte-stuffing frame format. | not obviously used | static methods |
| same | `HDLCFramer.frame_packet` | 464 | [CORE] | yes | Pure HDLC-style escaping. | tests/possible future use | none |
| same | `HDLCFramer.deframe_packet` | 491 | [CORE] | yes | Pure HDLC-style unescaping/validation. | tests/possible future use | none |
**BLEInterface.py**
| file path | symbol | line | tag | phase-1 C++ candidate | rationale | important callers | important callees |
|---|---:|---:|---|---|---|---|---|
| `src/ble_reticulum/BLEInterface.py` | `DiscoveredPeer` | 118 | [PLATFORM] | no | BLE discovery/connection scoring state. | `_device_discovered_callback`, `_select_peers_to_connect` | `time.time` |
| same | `DiscoveredPeer.__init__` | 146 | [PLATFORM] | no | Stores BLE address/RSSI/timing. | discovery callback | `time.time` |
| same | `update_rssi` | 167 | [PLATFORM] | no | Discovery cache update. | discovery callback | `time.time` |
| same | `record_connection_attempt` | 172 | [PLATFORM] | no | BLE connection policy state. | discovery callback | `time.time` |
| same | `record_connection_success` | 177 | [PLATFORM] | no | BLE connection policy state. | `_record_connection_success` | none |
| same | `record_connection_failure` | 181 | [PLATFORM] | no | BLE connection policy state. | `_record_connection_failure` | none |
| same | `get_success_rate` | 185 | [PLATFORM] | no | BLE peer scoring helper. | `_score_peer`, `__repr__` | none |
| same | `__repr__` | 196 | [TEST] | no | Diagnostic formatting only. | logging/debug | `get_success_rate` |
| same | `BLEInterface` | 202 | [GLUE] | no | Reticulum `Interface` implementation binding BLE driver to RNS Transport. | Reticulum external interface loader | many |
| same | `BLEInterface.__init__` | 267 | [GLUE] | no | Heavy RNS config, driver, locks, timers, callbacks. | Reticulum loader | `Interface.get_config_obj`, driver ctor |
| same | `start` | 479 | [GLUE] | no | Starts driver/scanning and touches Transport path workaround. | `__init__` | `driver.start`, `driver.start_scanning`, `_clear_stale_ble_paths` |
| same | `final_init` | 516 | [GLUE] | no | Reticulum lifecycle hook. | Reticulum | `_start_advertising_when_identity_ready` |
| same | `_setup_logging_redirect` | 529 | [GLUE] | no | RNS logging integration. | `__init__` | `logging`, `RNS.log` |
| same | `_start_advertising_when_identity_ready` | 583 | [GLUE] | no | Polls `RNS.Transport.identity`; starts platform advertising. | `final_init` thread | `driver.set_identity`, `driver.start_advertising` |
| same | `_clear_stale_ble_paths` | 630 | [GLUE] | no | Depends on `RNS.Transport.path_table` internals. | `start` | `Transport.path_table` |
| same | `_start_cleanup_timer` | 686 | [GLUE] | no | Thread timer around Python state. | `__init__`, cleanup task | `threading.Timer` |
| same | `_periodic_cleanup_task` | 701 | [GLUE] | no | Coordinates reassembly cleanup plus interface lifecycle. | timer | `cleanup_stale_buffers`, `_validate_spawned_interfaces` |
| same | `_cleanup_pending_identity_connections` | 741 | [GLUE] | no | Driver disconnect policy around identity handshake. | cleanup task | `driver.disconnect` |
| same | `_process_pending_detaches` | 771 | [GLUE] | no | Reticulum peer interface lifecycle. | cleanup task | `peer_if.detach`, `_get_fragmenter_key` |
| same | `_validate_spawned_interfaces` | 829 | [GLUE] | no | Compares RNS spawned interfaces to driver connections. | cleanup task | `driver.connected_peers`, `peer_if.detach` |
| same | `_device_discovered_callback` | 912 | [PLATFORM] | no | BLE scan callback and connection initiation. | driver callback | `DiscoveredPeer`, `_select_peers_to_connect`, `driver.connect` |
| same | `_device_connected_callback` | 964 | [GLUE] | no | Driver callback; maps identity to Reticulum peer interface. | driver callback | `_compute_identity_hash`, `_mtu_negotiated_callback` |
| same | `_check_duplicate_identity` | 1023 | [GLUE] | no | Identity dedup policy tied to driver state and MAC rotation. | driver callback | `_cleanup_stale_address`, `driver.disconnect` |
| same | `_mtu_negotiated_callback` | 1112 | [GLUE] | no | Creates protocol fragmenters but tied to driver/RNS interface spawn. | driver callback | `BLEFragmenter`, `BLEReassembler`, `_spawn_peer_interface` |
| same | `_handle_identity_handshake` | 1174 | [GLUE] | no | 16-byte identity handshake is protocol-ish, but intertwined with driver/RNS state. | `_data_received_callback` | `_check_duplicate_identity`, `_spawn_peer_interface` |
| same | `_data_received_callback` | 1281 | [GLUE] | no | Driver callback dispatch. | driver callback | `_handle_identity_handshake`, `_handle_ble_data` |
| same | `_device_disconnected_callback` | 1295 | [GLUE] | no | Reticulum/driver cleanup. | driver callback | `_compute_identity_hash`, detach scheduling |
| same | `_cleanup_stale_address` | 1380 | [GLUE] | no | MAC rotation mapping cleanup. | duplicate/address-change paths | dict cleanup |
| same | `_address_changed_callback` | 1411 | [GLUE] | no | Driver MAC rotation callback. | driver callback | `_get_fragmenter_key`, `_compute_identity_hash` |
| same | `_error_callback` | 1479 | [GLUE] | no | Driver error logging/blacklisting. | driver callback | `_record_connection_failure` |
| same | `_score_peer` | 1534 | [PLATFORM] | no | BLE RSSI/recency scoring policy. | `_select_peers_to_connect` | `peer.get_success_rate` |
| same | `_select_peers_to_connect` | 1620 | [PLATFORM] | no | BLE connection selection and MAC sorting. | discovery callback | `_score_peer`, `_is_blacklisted` |
| same | `_is_blacklisted` | 1761 | [PLATFORM] | no | BLE retry/backoff policy. | `_select_peers_to_connect` | `time.time` |
| same | `_record_connection_success` | 1785 | [PLATFORM] | no | BLE discovery/backoff state. | connection callback | `DiscoveredPeer.record_connection_success` |
| same | `_record_connection_failure` | 1800 | [PLATFORM] | no | BLE retry/backoff plus BlueZ cleanup hook. | error callback | `driver._remove_bluez_device` |
| same | `_get_fragmenter_key` | 1858 | [CORE] | yes | Pure identity-to-fragment-state key; cleanly separable. | many send/receive paths | `bytes.hex` |
| same | `_compute_identity_hash` | 1871 | [CORE] | yes | Pure identity truncation rule; cleanly separable. | identity mapping paths | `bytes.hex` |
| same | `_spawn_peer_interface` | 1892 | [GLUE] | no | Creates Reticulum `Interface`, mutates `RNS.Transport.interfaces`. | MTU/handshake paths | `BLEPeerInterface`, `RNS.Transport.interfaces.append` |
| same | `_handle_ble_data` | 1957 | [GLUE] | no | Uses core reassembly, but routes into Reticulum peer interfaces. | data callback | `BLEReassembler.receive_fragment`, `peer_if.process_incoming` |
| same | `handle_peripheral_data` | 2085 | [GLUE] | no | Older peripheral data path; handshake/reassembly/RNS routing mixed. | `BLEGATTServer` callback | `BLEFragmenter`, `BLEReassembler`, `_spawn_peer_interface` |
| same | `handle_central_connected` | 2188 | [GLUE] | no | GATT server to RNS interface lifecycle. | `BLEGATTServer` callback | `_spawn_peer_interface` |
| same | `handle_central_disconnected` | 2222 | [GLUE] | no | GATT server cleanup of Reticulum peer mapping. | `BLEGATTServer` callback | `_compute_identity_hash` |
| same | `process_incoming` | 2291 | [GLUE] | no | Calls Reticulum owner inbound. | peer interfaces | `owner.inbound` |
| same | `process_outgoing` | 2305 | [GLUE] | no | Broadcasts Reticulum packet to peer interfaces. | Reticulum Transport | `peer_if.process_outgoing` |
| same | `detach` | 2335 | [GLUE] | no | Reticulum/driver shutdown. | Reticulum | `driver.stop`, `peer_if.detach` |
| same | `should_ingress_limit` | 2365 | [GLUE] | no | Reticulum Interface policy. | Reticulum | none |
| same | `__str__` | 2376 | [TEST] | no | Diagnostic formatting. | logging | none |
| same | `BLEPeerInterface` | 2380 | [GLUE] | no | Reticulum per-peer interface wrapper. | `_spawn_peer_interface` | `Interface` |
| same | `BLEPeerInterface.__init__` | 2388 | [GLUE] | no | Reticulum interface fields/statistics. | `_spawn_peer_interface` | `Interface.__init__` |
| same | `BLEPeerInterface.process_incoming` | 2420 | [GLUE] | no | Hands data to `owner.inbound`. | `_handle_ble_data` | `owner.inbound` |
| same | `BLEPeerInterface.process_outgoing` | 2437 | [GLUE] | no | Calls core fragmenter, then platform driver send. | parent `process_outgoing`, Reticulum | `fragment_packet`, `driver.send` |
| same | `BLEPeerInterface.detach` | 2483 | [GLUE] | no | Removes interface from `RNS.Transport.interfaces`. | cleanup paths | `RNS.Transport.interfaces.remove` |
| same | `BLEPeerInterface.should_ingress_limit` | 2493 | [GLUE] | no | Delegates Reticulum policy. | Reticulum | parent method |
| same | `BLEPeerInterface.connection_id` | 2498 | [GLUE] | no | Reticulum/RNS identity display helper. | Reticulum/status | `RNS.Identity.full_hash` |
| same | `BLEPeerInterface.__str__` | 2510 | [TEST] | no | Diagnostic formatting. | logging | none |
**BLEAgent.py, BLEGATTServer.py, bluetooth_driver.py**
| file path | symbol | line | tag | phase-1 C++ candidate | rationale | important callers | important callees |
|---|---:|---:|---|---|---|---|---|
| `src/ble_reticulum/BLEAgent.py` | `BLEAgent` + all methods | 55 | [PLATFORM] | no | BlueZ DBus pairing agent. | `register_agent`, GATT servers | `dbus.service`, `RNS.log` |
| same | `register_agent` | 202 | [PLATFORM] | no | Registers BlueZ agent on system DBus. | GATT server startup | `DBusGMainLoop`, `dbus.SystemBus` |
| same | `unregister_agent` | 262 | [PLATFORM] | no | Unregisters BlueZ agent. | GATT server shutdown | `dbus.SystemBus` |
| `src/ble_reticulum/BLEGATTServer.py` | `BLEGATTServer` + all methods | 51 | [PLATFORM] | no | bluezero GATT server, threads, callbacks, notifications. | older/alternate peripheral path | `bluezero.peripheral`, `register_agent` |
| same | `set_transport_identity` | 484 | [GLUE] | no | Validates Reticulum identity bytes but serves them over GATT. | startup | none |
| `src/ble_reticulum/bluetooth_driver.py` | `BLEDevice` | 10 | [PLATFORM] | no | BLE discovery DTO. | Linux driver scan | dataclass |
| same | `DriverState` | 18 | [PLATFORM] | no | Driver state enum. | driver state property | enum |
| same | `BLEDriverInterface` + all abstract methods | 29 | [PLATFORM] | no | Platform BLE backend contract, not protocol core. | `BLEInterface` | abstract methods |
**linux_bluetooth_driver.py**
| file path | symbol | line | tag | phase-1 C++ candidate | rationale | important callers | important callees |
|---|---:|---:|---|---|---|---|---|
| `src/ble_reticulum/linux_bluetooth_driver.py` | `_rns_showwarning` | 142 | [GLUE] | no | Routes Python warnings to RNS logging. | `warnings.showwarning` | `RNS.log` |
| same | `apply_bluez_services_resolved_patch` | 220 | [PLATFORM] | no | Monkey-patches Bleak BlueZ manager. | `LinuxBluetoothDriver.__init__` | `BlueZManager._wait_for_services_discovery` |
| same | `PeerConnection` | 287 | [PLATFORM] | no | BLE connection runtime DTO with `BleakClient`. | driver connection state | dataclass |
| same | `LinuxBluetoothDriver` + lifecycle/scanning/connection/GATT methods | 297 | [PLATFORM] | no | Linux BLE backend using Bleak, bluezero, DBus, asyncio, threads. | `BLEInterface` | `BleakScanner`, `BleakClient`, DBus, `BluezeroGATTServer` |
| same | `_connect_to_peer` | 988 | [PLATFORM] | no | Central connection flow, service discovery, GATT reads/writes. | `connect` | `BleakClient`, `_negotiate_mtu`, callbacks |
| same | `_connect_via_dbus_le` | 1253 | [PLATFORM] | no | BlueZ DBus LE-only workaround. | `_connect_to_peer` | `MessageBus`, `Adapter1.ConnectDevice` |
| same | `_negotiate_mtu` | 1291 | [PLATFORM] | no | Bleak/BlueZ MTU probing. | `_connect_to_peer` | `client._backend`, `client.mtu_size` |
| same | `_handle_notification` | 1332 | [PLATFORM] | no | Bleak notification callback adapter. | `start_notify` callback | `on_data_received` |
| same | `BluezeroGATTServer` + all methods | 1578 | [PLATFORM] | no | bluezero peripheral server, DBus monitoring, stale polling. | `LinuxBluetoothDriver.start_advertising` | `bluezero`, DBus, threads |
| same | `_handle_write_rx` | 2328 | [PLATFORM] | no | GATT write callback; forwards bytes upward. | bluezero | driver callbacks |
| same | `_handle_read_identity` | 2374 | [PLATFORM] | no | GATT identity characteristic read. | bluezero | none |
| same | `_handle_central_connected` | 2388 | [PLATFORM] | no | Peripheral connection state plus callbacks. | `_handle_write_rx` | `PeerConnection`, driver callbacks |
| same | `_handle_central_disconnected` | 2430 | [PLATFORM] | no | DBus/poll disconnect cleanup. | DBus monitor, stale poll | driver callback |
| same | `send_notification` | 2479 | [PLATFORM] | no | bluezero notification send. | `LinuxBluetoothDriver.send` | `tx_characteristic.set_value` |
For `linux_bluetooth_driver.py`, the inventory includes many individual platform methods under `LinuxBluetoothDriver` and `BluezeroGATTServer`; all are [PLATFORM]/no for the same reason: they directly touch Bleak, bluezero, DBus, BlueZ, sockets/processes, asyncio event loops, or threads.
**Phase 1 C++ Extraction Candidates**
- `BLEFragmenter`
- `BLEFragmenter.fragment_packet`
- `BLEFragmenter.get_fragment_overhead`
- `BLEReassembler`
- `BLEReassembler.receive_fragment`
- `BLEReassembler.cleanup_stale_buffers`
- `BLEReassembler.get_statistics`
- `BLEReassembler.reset_statistics`
- `HDLCFramer.frame_packet`
- `HDLCFramer.deframe_packet`
- Extracted pure equivalents of `BLEInterface._get_fragmenter_key`
- Extracted pure equivalents of `BLEInterface._compute_identity_hash`
**Do Not Port Yet**
- `BLEInterface` and `BLEPeerInterface` as classes.
- All BlueZ/Bleak/bluezero/DBus code: `BLEAgent`, `BLEGATTServer`, `LinuxBluetoothDriver`, `BluezeroGATTServer`.
- `bluetooth_driver.py` driver abstraction unless designing a cross-language platform backend API later.
- Peer discovery/scoring/backoff/MAC-rotation orchestration in `BLEInterface`; it is BLE transport policy and Reticulum glue, not a clean protocol core.
- `ble_dual_node_echo.py`: treat as acceptance harness, as requested.
**Likely Python/C++ Boundary**
The clean first boundary is a small protocol library that accepts/returns bytes and simple metadata only: fragment packet, parse fragment, reassemble by sender/session key, timeout stale buffers, and compute identity-derived keys. Python should continue owning Reticulum `Interface` integration, `RNS.Transport` mutation, driver callbacks, scanning, advertising, GATT characteristics, DBus pairing, threading, timers, and logging.
**Classification Depends On Reticulum Internals**
- `BLEInterface.__init__`, `start`, `final_init`
- `_start_advertising_when_identity_ready`
- `_clear_stale_ble_paths`
- `_spawn_peer_interface`
- `_handle_ble_data`
- `handle_peripheral_data`
- `process_incoming`
- `process_outgoing`
- `detach`
- `BLEPeerInterface.process_incoming`
- `BLEPeerInterface.detach`
- `BLEPeerInterface.connection_id`
The strongest dependency is `_clear_stale_ble_paths`, which assumes `RNS.Transport.path_table` tuple indexes and BLE interface type names.

1
migration/README.md Normal file
View file

@ -0,0 +1 @@
This directory concerns the migration to C++ the ble-reticulum project.

View file

@ -0,0 +1,182 @@
BLEAgent.py
class BLEAgent line 55
def __init__(self, bus, capability) line 68
def _log(self, message, level) line 82
def Release(self) line 101
def AuthorizeService(self, device, uuid) line 111
def RequestAuthorization(self, device) line 127
def RequestConfirmation(self, device, passkey) line 139
def RequestPasskey(self, device) line 155
def Cancel(self) line 172
def _format_device_path(self, device_path) line 182
def register_agent(capability) line 202
def unregister_agent(agent) line 262
BLEFragmentation.py
class BLEFragmenter line 52
def __init__(self, mtu) line 68
def fragment_packet(self, packet) line 82
def get_fragment_overhead(self, packet_size) line 158
class BLEReassembler line 176
def __init__(self, timeout) line 187
def receive_fragment(self, fragment, sender_id) line 205
def _reassemble(self, buffer) line 380
def cleanup_stale_buffers(self) line 402
def get_statistics(self) line 429
def reset_statistics(self) line 443
class HDLCFramer line 450
def frame_packet(packet) line 464
def deframe_packet(frame) line 491
BLEGATTServer.py
class BLEGATTServer line 51
def __init__(self, interface, device_name, agent_capability) line 77
def _log(self, message, level) line 127
def _handle_write_rx(self, value, options) line 148
def _handle_read_identity(self, options) line 215
def _handle_central_connected(self, central_address, mtu) line 242
def _handle_central_disconnected(self, central_address) line 279
def _run_server_thread(self) line 306
async def start(self) line 441
def set_transport_identity(self, identity_hash) line 484
async def stop(self) line 503
async def send_notification(self, data, central_address) line 543
def is_connected(self, central_address) line 607
def get_connected_centrals(self) line 620
def get_connection_info(self, central_address) line 630
def get_central_mtu(self, central_address) line 643
def get_statistics(self) line 658
def __str__(self) line 678
def __repr__(self) line 684
BLEInterface.py
class DiscoveredPeer line 118
def __init__(self, address, name, rssi) line 146
def update_rssi(self, rssi) line 167
def record_connection_attempt(self) line 172
def record_connection_success(self) line 177
def record_connection_failure(self) line 181
def get_success_rate(self) line 185
def __repr__(self) line 196
class BLEInterface line 202
def __init__(self, owner, configuration) line 267
def start(self) line 479
def final_init(self) line 516
def _setup_logging_redirect(self) line 529
def _start_advertising_when_identity_ready(self) line 583
def _clear_stale_ble_paths(self) line 630
def _start_cleanup_timer(self) line 686
def _periodic_cleanup_task(self) line 701
def _cleanup_pending_identity_connections(self) line 741
def _process_pending_detaches(self) line 771
def _validate_spawned_interfaces(self) line 829
def _device_discovered_callback(self, device) line 912
def _device_connected_callback(self, address, peer_identity) line 964
def _check_duplicate_identity(self, address, peer_identity) line 1023
def _mtu_negotiated_callback(self, address, mtu) line 1112
def _handle_identity_handshake(self, address, data) line 1174
def _data_received_callback(self, address, data) line 1281
def _device_disconnected_callback(self, address) line 1295
def _cleanup_stale_address(self, identity_hash, old_address) line 1380
def _address_changed_callback(self, old_address, new_address, identity_hash) line 1411
def _error_callback(self, severity, message, exc) line 1479
def _score_peer(self, peer) line 1534
def _select_peers_to_connect(self) line 1620
def _is_blacklisted(self, address) line 1761
def _record_connection_success(self, address) line 1785
def _record_connection_failure(self, address) line 1800
def _get_fragmenter_key(self, peer_identity, peer_address) line 1858
def _compute_identity_hash(self, peer_identity) line 1871
def _spawn_peer_interface(self, address, name, peer_identity, client, mtu, connection_type) line 1892
def _handle_ble_data(self, peer_address, data) line 1957
def handle_peripheral_data(self, data, sender_address) line 2085
def handle_central_connected(self, address) line 2188
def handle_central_disconnected(self, address) line 2222
def process_incoming(self, data) line 2291
def process_outgoing(self, data) line 2305
def detach(self) line 2335
def should_ingress_limit(self) line 2365
def __str__(self) line 2376
class BLEPeerInterface line 2380
def __init__(self, parent, peer_address, peer_name, peer_identity) line 2388
def process_incoming(self, data) line 2420
def process_outgoing(self, data) line 2437
def detach(self) line 2483
def should_ingress_limit(self) line 2493
def connection_id(self) line 2498
def __str__(self) line 2510
__init__.py
bluetooth_driver.py
class BLEDevice line 10
class DriverState line 18
class BLEDriverInterface line 29
def start(self, service_uuid, rx_char_uuid, tx_char_uuid, identity_char_uuid) line 57
def stop(self) line 66
def set_identity(self, identity_bytes) line 74
def state(self) line 85
def connected_peers(self) line 91
def start_scanning(self) line 98
def stop_scanning(self) line 106
def start_advertising(self, device_name, identity) line 111
def stop_advertising(self) line 124
def connect(self, address) line 129
def disconnect(self, address) line 137
def send(self, address, data) line 142
def read_characteristic(self, address, char_uuid) line 157
def write_characteristic(self, address, char_uuid, data) line 165
def start_notify(self, address, char_uuid, callback) line 173
def get_local_address(self) line 183
def get_peer_role(self, address) line 191
def set_service_discovery_delay(self, seconds) line 204
def set_power_mode(self, mode) line 212
linux_bluetooth_driver.py
def _rns_showwarning(message, category, filename, lineno, file, line) line 142
def apply_bluez_services_resolved_patch() line 220
class PeerConnection line 287
class LinuxBluetoothDriver line 297
def __init__(self, discovery_interval, connection_timeout, min_rssi, service_discovery_delay, max_peers, adapter_index, agent_capability) line 314
def _log(self, message, level) line 403
def start(self, service_uuid, rx_char_uuid, tx_char_uuid, identity_char_uuid) line 426
def stop(self) line 489
def set_identity(self, identity_bytes) line 531
def state(self) line 551
def connected_peers(self) line 556
def start_scanning(self) line 565
def stop_scanning(self) line 582
def _should_pause_scanning(self) line 593
async def _scan_loop(self) line 607
async def _perform_scan(self) line 636
def start_advertising(self, device_name, identity) line 749
def stop_advertising(self) line 784
def connect(self, address) line 806
def disconnect(self, address) line 876
def _handle_peripheral_disconnected(self, address) line 909
async def _remove_bluez_device(self, address) line 942
async def _connect_to_peer(self, address) line 988
async def _connect_via_dbus_le(self, peer_address) line 1253
async def _negotiate_mtu(self, client) line 1291
def _handle_notification(self, address, data) line 1332
def send(self, address, data) line 1344
def read_characteristic(self, address, char_uuid) line 1386
def write_characteristic(self, address, char_uuid, data) line 1409
def start_notify(self, address, char_uuid, callback) line 1431
def get_local_address(self) line 1464
def get_peer_role(self, address) line 1468
def get_peer_mtu(self, address) line 1475
def set_service_discovery_delay(self, seconds) line 1494
def set_power_mode(self, mode) line 1499
def _run_event_loop(self) line 1511
async def _get_local_adapter_address(self) line 1523
def _detect_bluez_version(self) line 1556
class BluezeroGATTServer line 1578
def __init__(self, driver, service_uuid, rx_char_uuid, tx_char_uuid, identity_char_uuid, adapter_index, agent_capability) line 1589
def _log(self, message, level) line 1646
def set_identity(self, identity_bytes) line 1650
def _verify_services_on_dbus(self, timeout) line 1662
def _monitor_device_disconnections(self) line 1730
def _poll_stale_connections(self) line 1965
def start(self, device_name) line 2072
def stop(self) line 2160
def _run_server_thread(self, device_name) line 2206
def _handle_write_rx(self, value, options) line 2328
def _handle_read_identity(self, options) line 2374
def _handle_central_connected(self, central_address, mtu) line 2388
def _handle_central_disconnected(self, central_address) line 2430
def send_notification(self, central_address, data) line 2479

View file

@ -0,0 +1,41 @@
(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ date
Sat May 16 14:41:10 PDT 2026
(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ python3 -c 'import RNS; print(RNS.__file__)'
/home/jlpoole/rnsenv/lib/python3.13/site-packages/RNS/__init__.py
(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ which rnsd
/home/jlpoole/rnsenv/bin/rnsd
(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ which rnstatus
/home/jlpoole/rnsenv/bin/rnstatus
(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ which rnpath
/home/jlpoole/rnsenv/bin/rnpath
(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ equery belongs "$(which rnsd)"
* Searching for /home/jlpoole/rnsenv/bin/rnsd ...
(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ python3 -m pip show rns
python3 -m pip show reticulum
Name: rns
Version: 1.1.3
Summary: Self-configuring, encrypted and resilient mesh networking stack for LoRa, packet radio, WiFi and everything in between
Home-page: https://reticulum.network/
Author: Mark Qvist
Author-email: mark@unsigned.io
License: Reticulum License
Location: /home/jlpoole/rnsenv/lib/python3.13/site-packages
Requires: cryptography, pyserial
Required-by: lxmf, lxst, nomadnet, sbapp
WARNING: Package(s) not found: reticulum
(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ date
hostname
which python3
python3 -V
python3 -c 'import RNS, os; print("RNS file:", RNS.__file__); print("RNS dir :", os.path.dirname(RNS.__file__))'
which rnsd
rnsd --version 2>/dev/null || true
Sat May 16 14:43:28 PDT 2026
jp
/home/jlpoole/rnsenv/bin/python3
Python 3.13.12
RNS file: /home/jlpoole/rnsenv/lib/python3.13/site-packages/RNS/__init__.py
RNS dir : /home/jlpoole/rnsenv/lib/python3.13/site-packages/RNS
/home/jlpoole/rnsenv/bin/rnsd
rnsd 1.1.3
(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $

View file

@ -0,0 +1,456 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Getopt::Long qw(GetOptions);
use File::Basename qw(dirname);
use Cwd qw(abs_path);
my $script_dir = dirname(abs_path($0));
my $default_db = "$script_dir/ble_migration.sqlite";
my $default_md = "$script_dir/Codex_response_20260616_1514.md";
my $default_inventory = "$script_dir/ble_symbols_20260516_1429.txt";
my $db_path = $default_db;
my $markdown_path = $default_md;
my $inventory_path = $default_inventory;
my $reviewer = 'codex';
my $dry_run = 0;
my $help = 0;
GetOptions(
'db=s' => \$db_path,
'markdown=s' => \$markdown_path,
'inventory=s' => \$inventory_path,
'reviewer=s' => \$reviewer,
'dry-run' => \$dry_run,
'help' => \$help,
) or usage(2);
usage(0) if $help;
die "Markdown file not found: $markdown_path\n" unless -f $markdown_path;
die "SQLite database not found: $db_path\n" unless -f $db_path;
die "Inventory file not found: $inventory_path\n" unless -f $inventory_path;
my $inventory = load_inventory($inventory_path);
my @rows = parse_markdown_tables($markdown_path, $inventory);
die "No symbol rows found in $markdown_path\n" unless @rows;
my $sql = build_sql(\@rows, $reviewer);
if ($dry_run) {
print $sql;
exit 0;
}
open(my $sqlite, '|-', 'sqlite3', $db_path)
or die "Unable to start sqlite3 for $db_path: $!\n";
print {$sqlite} $sql;
close($sqlite) or die "sqlite3 import failed for $db_path\n";
print "Imported " . scalar(@rows) . " symbol review rows into $db_path\n";
sub usage {
my ($exit_code) = @_;
print <<"USAGE";
Usage:
perl migration/import_codex_review.pl [options]
Options:
--db PATH SQLite database path
default: migration/ble_migration.sqlite
--markdown PATH Codex Markdown review path
default: migration/Codex_response_20260616_1514.md
--inventory PATH symbol inventory used to infer class_name for unqualified
method rows
default: migration/ble_symbols_20260516_1429.txt
--reviewer NAME reviewer value for reviews table
default: codex
--dry-run print SQL instead of applying it
--help show this help
The importer is idempotent for symbols: it upserts by the schema's unique key
(source_file, class_name, symbol_name, line_number). It appends one review row
per imported Markdown table row each time it is run.
USAGE
exit $exit_code;
}
sub load_inventory {
my ($path) = @_;
open(my $fh, '<', $path) or die "Unable to read $path: $!\n";
my %inventory;
my $current_file;
my $current_class = '';
while (my $line = <$fh>) {
chomp $line;
if ($line =~ /^(\S+\.py)\s*$/) {
$current_file = $1;
$current_class = '';
next;
}
next unless defined $current_file;
if ($line =~ /^\s{4}class\s+([A-Za-z_][A-Za-z0-9_]*)\s+line\s+(\d+)/) {
my ($class, $line_number) = ($1, $2);
$current_class = $class;
my $key = inventory_key($current_file, $class, $line_number);
$inventory{$key} = {
symbol_type => 'class',
class_name => '',
symbol_name => $class,
};
next;
}
if ($line =~ /^\s{8}(?:async\s+)?def\s+([A-Za-z_][A-Za-z0-9_]*)\([^)]*\)\s+line\s+(\d+)/) {
my ($method, $line_number) = ($1, $2);
my $key = inventory_key($current_file, $method, $line_number);
$inventory{$key} = {
symbol_type => 'method',
class_name => $current_class,
symbol_name => $method,
};
next;
}
if ($line =~ /^\s{4}def\s+([A-Za-z_][A-Za-z0-9_]*)\([^)]*\)\s+line\s+(\d+)/) {
my ($function, $line_number) = ($1, $2);
my $key = inventory_key($current_file, $function, $line_number);
$inventory{$key} = {
symbol_type => 'function',
class_name => '',
symbol_name => $function,
};
next;
}
}
close($fh);
return \%inventory;
}
sub inventory_key {
my ($file, $symbol, $line_number) = @_;
return join('|', basename_only($file), $symbol, defined($line_number) ? $line_number : '');
}
sub parse_markdown_tables {
my ($path, $inventory) = @_;
open(my $fh, '<', $path) or die "Unable to read $path: $!\n";
my @rows;
my $current_file;
my $in_expected_table = 0;
while (my $line = <$fh>) {
chomp $line;
if ($line =~ /^\|\s*file path\s*\|\s*symbol\s*\|\s*line\s*\|\s*tag\s*\|\s*phase-1 C\+\+ candidate\s*\|/i) {
$in_expected_table = 1;
next;
}
if ($in_expected_table && $line =~ /^\|\s*-+/) {
next;
}
if ($in_expected_table && $line !~ /^\|/) {
$in_expected_table = 0;
next;
}
next unless $in_expected_table;
my @cells = split_markdown_row($line);
next unless @cells == 8;
my ($file, $symbol, $line_number, $tag, $candidate, $rationale, $callers, $callees) = @cells;
next if lc($file) eq 'file path';
$file = clean_cell($file);
if ($file eq 'same') {
die "Encountered 'same' file before an explicit file path\n" unless defined $current_file;
$file = $current_file;
} else {
$current_file = $file;
}
$symbol = clean_cell($symbol);
$tag = clean_tag(clean_cell($tag));
$candidate = lc(clean_cell($candidate)) eq 'yes' ? 1 : 0;
$line_number = clean_cell($line_number);
$line_number = $line_number =~ /(\d+)/ ? $1 : undef;
$rationale = clean_cell($rationale);
$callers = clean_cell($callers);
$callees = clean_cell($callees);
my ($symbol_type, $class_name, $symbol_name);
my $inventory_key = inventory_key($file, symbol_lookup_name($symbol), $line_number);
if (exists $inventory->{$inventory_key}) {
$symbol_type = $inventory->{$inventory_key}->{symbol_type};
$class_name = $inventory->{$inventory_key}->{class_name};
$symbol_name = $inventory->{$inventory_key}->{symbol_name};
} else {
($symbol_type, $class_name, $symbol_name) = infer_symbol_shape($symbol);
$class_name = '' unless defined $class_name;
}
my $phase = $candidate ? '1_candidate' : '0_inventory';
my $confidence = $symbol =~ /\+\s*all\s+methods/i ? 'medium' : 'high';
my $notes = "Imported from " . basename_only($path) . "; original symbol cell: $symbol";
push @rows, {
source_file => $file,
symbol_name => $symbol_name,
symbol_type => $symbol_type,
class_name => $class_name,
line_number => $line_number,
tag => $tag,
phase => $phase,
status => 'REVIEWED',
cpp_candidate => $candidate,
confidence => $confidence,
rationale => $rationale,
callers => $callers,
callees => $callees,
notes => $notes,
};
}
close($fh);
return @rows;
}
sub split_markdown_row {
my ($line) = @_;
$line =~ s/^\|//;
$line =~ s/\|$//;
my @cells = split /\|/, $line, -1;
for my $cell (@cells) {
$cell =~ s/^\s+//;
$cell =~ s/\s+$//;
}
return @cells;
}
sub clean_cell {
my ($value) = @_;
$value = '' unless defined $value;
$value =~ s/<br\s*\/?>/\n/gi;
$value =~ s/`([^`]*)`/$1/g;
$value =~ s/^\s+//;
$value =~ s/\s+$//;
return $value;
}
sub clean_tag {
my ($tag) = @_;
$tag =~ s/^\[//;
$tag =~ s/\]$//;
$tag = uc($tag || 'UNKNOWN');
return $tag =~ /^(CORE|GLUE|PLATFORM|TEST|UNKNOWN)$/ ? $tag : 'UNKNOWN';
}
sub infer_symbol_shape {
my ($symbol) = @_;
if ($symbol =~ /^([A-Za-z_][A-Za-z0-9_]*)\s*\+\s*all\s+methods\b/i) {
return ('class_group', undef, $symbol);
}
if ($symbol =~ /^([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)$/) {
return ('method', $1, $2);
}
if ($symbol =~ /^_[A-Za-z_][A-Za-z0-9_]*$/ || $symbol =~ /^[a-z_][A-Za-z0-9_]*$/) {
return ('function', undef, $symbol);
}
if ($symbol =~ /^[A-Z][A-Za-z0-9_]*$/) {
return ('class', undef, $symbol);
}
return ('symbol', undef, $symbol);
}
sub symbol_lookup_name {
my ($symbol) = @_;
if ($symbol =~ /^([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)$/) {
return $2;
}
if ($symbol =~ /^([A-Za-z_][A-Za-z0-9_]*)\s*\+\s*all\s+methods\b/i) {
return $1;
}
return $symbol;
}
sub build_sql {
my ($rows, $reviewer_name) = @_;
my $sql = <<"SQL";
.bail on
PRAGMA foreign_keys = ON;
BEGIN;
CREATE TEMP TABLE import_symbols (
import_order INTEGER PRIMARY KEY,
source_file TEXT NOT NULL,
symbol_name TEXT NOT NULL,
symbol_type TEXT,
class_name TEXT,
line_number INTEGER,
tag TEXT NOT NULL,
phase TEXT NOT NULL,
status TEXT NOT NULL,
cpp_candidate INTEGER NOT NULL,
confidence TEXT,
rationale TEXT,
callers TEXT,
callees TEXT,
notes TEXT
);
SQL
my $i = 0;
for my $row (@{$rows}) {
$i++;
$sql .= "INSERT INTO import_symbols VALUES ("
. join(', ',
$i,
sql_quote($row->{source_file}),
sql_quote($row->{symbol_name}),
sql_quote($row->{symbol_type}),
sql_quote($row->{class_name}),
sql_integer($row->{line_number}),
sql_quote($row->{tag}),
sql_quote($row->{phase}),
sql_quote($row->{status}),
$row->{cpp_candidate} ? 1 : 0,
sql_quote($row->{confidence}),
sql_quote($row->{rationale}),
sql_quote($row->{callers}),
sql_quote($row->{callees}),
sql_quote($row->{notes}),
)
. ");\n";
}
$sql .= <<"SQL";
CREATE TEMP TABLE import_existing AS
SELECT
i.import_order,
s.symbol_id,
s.tag AS old_tag,
s.status AS old_status
FROM import_symbols i
JOIN symbols s
ON s.source_file = i.source_file
AND COALESCE(s.class_name, '') = COALESCE(i.class_name, '')
AND s.symbol_name = i.symbol_name
AND COALESCE(s.line_number, -1) = COALESCE(i.line_number, -1);
INSERT INTO symbols (
source_file,
symbol_name,
symbol_type,
class_name,
line_number,
tag,
phase,
status,
cpp_candidate,
confidence,
rationale,
callers,
callees,
notes,
updated_at
)
SELECT
source_file,
symbol_name,
symbol_type,
class_name,
line_number,
tag,
phase,
status,
cpp_candidate,
confidence,
rationale,
callers,
callees,
notes,
CURRENT_TIMESTAMP
FROM import_symbols
WHERE 1
ON CONFLICT(source_file, class_name, symbol_name, line_number) DO UPDATE SET
symbol_type = excluded.symbol_type,
tag = excluded.tag,
phase = excluded.phase,
status = excluded.status,
cpp_candidate = excluded.cpp_candidate,
confidence = excluded.confidence,
rationale = excluded.rationale,
callers = excluded.callers,
callees = excluded.callees,
notes = excluded.notes,
updated_at = CURRENT_TIMESTAMP;
INSERT INTO reviews (
symbol_id,
reviewer,
old_tag,
new_tag,
old_status,
new_status,
note
)
SELECT
s.symbol_id,
@{[sql_quote($reviewer_name)]},
e.old_tag,
i.tag,
e.old_status,
i.status,
'Imported from Codex Markdown review. Rationale: ' || COALESCE(i.rationale, '')
FROM import_symbols i
JOIN symbols s
ON s.source_file = i.source_file
AND COALESCE(s.class_name, '') = COALESCE(i.class_name, '')
AND s.symbol_name = i.symbol_name
AND COALESCE(s.line_number, -1) = COALESCE(i.line_number, -1)
LEFT JOIN import_existing e
ON e.import_order = i.import_order;
COMMIT;
SQL
return $sql;
}
sub sql_quote {
my ($value) = @_;
return 'NULL' unless defined $value;
$value =~ s/'/''/g;
return "'$value'";
}
sub sql_integer {
my ($value) = @_;
return 'NULL' unless defined $value && $value =~ /^\d+$/;
return $value;
}
sub basename_only {
my ($path) = @_;
$path =~ s{.*/}{};
return $path;
}

View file

@ -0,0 +1,58 @@
-- 20260516 ChatGPT
-- $Header$
--
-- Example:
-- cd /usr/local/src/ble-reticulum/migration
-- sqlite3 ble_migration.sqlite ".read sql/ble_migration_schema.sql"
-- Verify
-- sqlite3 ble_migration.sqlite ".tables"
-- sqlite3 ble_migration.sqlite ".schema symbols"
--
-- Purpose:
-- Track symbol-by-symbol migration status for BLE Reticulum protocol extraction.
CREATE TABLE IF NOT EXISTS symbols (
symbol_id INTEGER PRIMARY KEY AUTOINCREMENT,
source_file TEXT NOT NULL,
symbol_name TEXT NOT NULL,
symbol_type TEXT,
class_name TEXT,
line_number INTEGER,
tag TEXT DEFAULT 'UNKNOWN',
phase TEXT DEFAULT '0_inventory',
status TEXT DEFAULT 'DISCOVERED',
cpp_candidate INTEGER DEFAULT 0,
confidence TEXT DEFAULT 'unknown',
rationale TEXT,
callers TEXT,
callees TEXT,
notes TEXT,
first_seen_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
UNIQUE(source_file, class_name, symbol_name, line_number)
);
CREATE TABLE IF NOT EXISTS reviews (
review_id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol_id INTEGER NOT NULL REFERENCES symbols(symbol_id),
reviewed_at TEXT DEFAULT CURRENT_TIMESTAMP,
reviewer TEXT,
old_tag TEXT,
new_tag TEXT,
old_status TEXT,
new_status TEXT,
note TEXT
);
CREATE INDEX IF NOT EXISTS symbols_tag_idx
ON symbols(tag);
CREATE INDEX IF NOT EXISTS symbols_status_idx
ON symbols(status);
CREATE INDEX IF NOT EXISTS symbols_phase_idx
ON symbols(phase);
CREATE INDEX IF NOT EXISTS symbols_cpp_candidate_idx
ON symbols(cpp_candidate);