From f3cafedb60955e68c8bc590944a46bf249af77c8 Mon Sep 17 00:00:00 2001 From: torlando-tech Date: Mon, 3 Nov 2025 23:43:30 -0500 Subject: [PATCH] fix(ble): Resolve connection role and startup errors This commit addresses two critical issues that prevented the BLE interface from functioning correctly after the driver abstraction refactor. 1. **Fix `exec()` Startup Error:** The interface failed to load via `rnsd` due to a `KeyError: '__name__'` caused by using relative imports (`from . import ...`). The `exec()` environment used by Reticulum does not preserve package context, breaking these imports. This is fixed by reverting to absolute imports (`from bluetooth_driver import ...`) which work correctly with the existing `sys.path` manipulation logic. 2. **Fix Connection Role Logic:** Connections were failing because the interface would always attempt to read the peer's identity, even when acting as the peripheral. This caused a `Can only read characteristics in central mode` error. The fix introduces role-aware logic into the connection callback: - A `get_peer_role()` method was added to the driver interface. - `BLEInterface` now checks the role on connection. - If central, it reads the identity characteristic. - If peripheral, it waits for the identity handshake packet, preventing the invalid operation. --- src/RNS/Interfaces/BLEInterface.py | 55 ++++++++++++-------- src/RNS/Interfaces/bluetooth_driver.py | 13 +++++ src/RNS/Interfaces/linux_bluetooth_driver.py | 7 +++ 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/RNS/Interfaces/BLEInterface.py b/src/RNS/Interfaces/BLEInterface.py index 720cc8e..58395d0 100644 --- a/src/RNS/Interfaces/BLEInterface.py +++ b/src/RNS/Interfaces/BLEInterface.py @@ -646,37 +646,48 @@ class BLEInterface(Interface): Called when driver has established a connection. We read the identity characteristic and prepare to receive data. """ - RNS.log(f"{self} connected to {address}, reading identity...", RNS.LOG_INFO) + # Check connection role to determine identity exchange method + role = self.driver.get_peer_role(address) - # Read identity characteristic - try: - identity_bytes = self.driver.read_characteristic( - address, - BLEInterface.CHARACTERISTIC_IDENTITY_UUID - ) + if role == "central": + # We are the central, we must read the peer's identity + RNS.log(f"{self} connected to {address} as CENTRAL, reading identity...", RNS.LOG_INFO) + try: + identity_bytes = self.driver.read_characteristic( + address, + BLEInterface.CHARACTERISTIC_IDENTITY_UUID + ) - if identity_bytes and len(identity_bytes) == 16: - peer_identity = bytes(identity_bytes) - identity_hash = self._compute_identity_hash(peer_identity) + if identity_bytes and len(identity_bytes) == 16: + peer_identity = bytes(identity_bytes) + identity_hash = self._compute_identity_hash(peer_identity) - # Store identity mappings - self.address_to_identity[address] = peer_identity - self.identity_to_address[identity_hash] = address + # Store identity mappings + self.address_to_identity[address] = peer_identity + self.identity_to_address[identity_hash] = address - RNS.log(f"{self} received peer identity from {address}: {identity_hash}", RNS.LOG_INFO) + RNS.log(f"{self} received peer identity from {address}: {identity_hash}", RNS.LOG_INFO) + self._record_connection_success(address) + else: + RNS.log(f"{self} invalid identity from {address}, disconnecting", RNS.LOG_WARNING) + self.driver.disconnect(address) + self._record_connection_failure(address) - # Record successful connection - self._record_connection_success(address) - - else: - RNS.log(f"{self} invalid identity from {address}, disconnecting", RNS.LOG_WARNING) + except Exception as e: + RNS.log(f"{self} failed to read identity from {address}: {e}", RNS.LOG_ERROR) self.driver.disconnect(address) self._record_connection_failure(address) - except Exception as e: - RNS.log(f"{self} failed to read identity from {address}: {e}", RNS.LOG_ERROR) + elif role == "peripheral": + # We are the peripheral, we must wait for the central to send its identity + RNS.log(f"{self} connected to {address} as PERIPHERAL, waiting for identity handshake...", RNS.LOG_INFO) + # The identity will be received in `handle_peripheral_data` or `_data_received_callback` + # No action is needed here. + pass + + else: + RNS.log(f"{self} connected to {address}, but role is unknown. Disconnecting.", RNS.LOG_WARNING) self.driver.disconnect(address) - self._record_connection_failure(address) def _mtu_negotiated_callback(self, address: str, mtu: int): """ diff --git a/src/RNS/Interfaces/bluetooth_driver.py b/src/RNS/Interfaces/bluetooth_driver.py index 4cb888f..b39a8ba 100644 --- a/src/RNS/Interfaces/bluetooth_driver.py +++ b/src/RNS/Interfaces/bluetooth_driver.py @@ -181,6 +181,19 @@ class BLEDriverInterface(ABC): """ pass + @abstractmethod + def get_peer_role(self, address: str) -> Optional[str]: + """ + Returns the connection role for a connected peer. + + Args: + address: The MAC address of the peer. + + Returns: + A string ('central' or 'peripheral') or None if not connected. + """ + pass + @abstractmethod def set_service_discovery_delay(self, seconds: float): """ diff --git a/src/RNS/Interfaces/linux_bluetooth_driver.py b/src/RNS/Interfaces/linux_bluetooth_driver.py index 390fcaf..9121d6b 100644 --- a/src/RNS/Interfaces/linux_bluetooth_driver.py +++ b/src/RNS/Interfaces/linux_bluetooth_driver.py @@ -1079,6 +1079,13 @@ class LinuxBluetoothDriver(BLEDriverInterface): """Return local Bluetooth adapter MAC address.""" return self.local_address or "00:00:00:00:00:00" + def get_peer_role(self, address: str) -> Optional[str]: + """Return the connection role ('central' or 'peripheral') for a peer.""" + with self._peers_lock: + if address in self._peers: + return self._peers[address].connection_type + return None + def set_service_discovery_delay(self, seconds: float): """Set delay between connection and service discovery.""" self.service_discovery_delay = seconds