From 4566d5d2282cf851e58211ab2666b79ed22ec2de Mon Sep 17 00:00:00 2001 From: CC Date: Wed, 10 Jun 2026 12:16:00 +0200 Subject: [PATCH] fix review --- .../android/mesh/BluetoothMeshService.kt | 31 +++------- .../android/service/MeshServiceHolder.kt | 59 ++++++++++++++++++- .../android/service/TransportBridgeService.kt | 34 +++++++++++ .../wifi-aware/WifiAwareMeshService.kt | 11 ++-- .../android/ui/CommandProcessorTest.kt | 4 +- 5 files changed, 107 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt index 14bc6913..c6c10473 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -101,20 +101,9 @@ class BluetoothMeshService(private val context: Context) : TransportBridgeServic } ) - // Wire sync manager delegate - gossipSyncManager.delegate = object : GossipSyncManager.Delegate { - override fun sendPacket(packet: BitchatPacket) { - broadcastRoutedPacket(RoutedPacket(packet)) - } - override fun sendPacketToPeer(peerID: String, packet: BitchatPacket) { - sendPacketToPeerAcrossTransports(peerID, packet) - } - override fun signPacketForBroadcast(packet: BitchatPacket): BitchatPacket { - return signPacketBeforeBroadcast(packet) - } + com.bitchat.android.service.MeshServiceHolder.setGossipManager(gossipSyncManager) { packet -> + signPacketBeforeBroadcast(packet) } - - com.bitchat.android.service.MeshServiceHolder.setGossipManager(gossipSyncManager) if (isBleTransportEnabled()) { TransportBridgeService.register("BLE", this) } @@ -144,13 +133,6 @@ class BluetoothMeshService(private val context: Context) : TransportBridgeServic TransportBridgeService.broadcast("BLE", routed) } - private fun sendPacketToPeerAcrossTransports(peerID: String, packet: BitchatPacket): Boolean { - if (!isBleTransportEnabled()) return false - val sentOverBle = connectionManager.sendPacketToPeer(peerID, packet) - TransportBridgeService.sendToPeer("BLE", peerID, packet) - return sentOverBle - } - private fun isBleTransportEnabled(): Boolean { return try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().bleEnabled.value @@ -670,8 +652,9 @@ class BluetoothMeshService(private val context: Context) : TransportBridgeServic Log.i(TAG, "BLE transport disabled by debug settings; not starting mesh service") connectionManager.disableTransport() TransportBridgeService.unregister("BLE") + com.bitchat.android.service.MeshServiceHolder.stopSharedGossip("BLE") try { com.bitchat.android.services.AppStateStore.clearTransportPeers("BLE") } catch (_: Exception) { } - try { com.bitchat.android.services.AppStateStore.clearTransportDirectPeers("BLE") } catch (_: Exception) { } + try { com.bitchat.android.services.AppStateStore.clearTransportDirectPeers("BLE") } catch (_: Exception) { } return } if (terminated) { @@ -690,7 +673,7 @@ class BluetoothMeshService(private val context: Context) : TransportBridgeServic sendPeriodicBroadcastAnnounce() Log.d(TAG, "Started periodic broadcast announcements (every 30 seconds)") // Start periodic syncs - gossipSyncManager.start() + com.bitchat.android.service.MeshServiceHolder.startSharedGossip("BLE") Log.d(TAG, "GossipSyncManager started") } else { Log.e(TAG, "Failed to start Bluetooth services") @@ -713,7 +696,7 @@ class BluetoothMeshService(private val context: Context) : TransportBridgeServic isActive = false announceJob?.cancel() announceJob = null - try { gossipSyncManager.stop() } catch (_: Exception) { } + com.bitchat.android.service.MeshServiceHolder.stopSharedGossip("BLE") TransportBridgeService.unregister("BLE") try { com.bitchat.android.services.AppStateStore.clearTransportPeers("BLE") } catch (_: Exception) { } try { com.bitchat.android.services.AppStateStore.clearTransportDirectPeers("BLE") } catch (_: Exception) { } @@ -746,7 +729,7 @@ class BluetoothMeshService(private val context: Context) : TransportBridgeServic delay(200) // Give leave message time to send // Stop all components - gossipSyncManager.stop() + com.bitchat.android.service.MeshServiceHolder.stopSharedGossip("BLE") Log.d(TAG, "GossipSyncManager stopped") connectionManager.stopServices() Log.d(TAG, "BluetoothConnectionManager stop requested") diff --git a/app/src/main/java/com/bitchat/android/service/MeshServiceHolder.kt b/app/src/main/java/com/bitchat/android/service/MeshServiceHolder.kt index 7d8b3014..1ff4ec29 100644 --- a/app/src/main/java/com/bitchat/android/service/MeshServiceHolder.kt +++ b/app/src/main/java/com/bitchat/android/service/MeshServiceHolder.kt @@ -3,6 +3,9 @@ package com.bitchat.android.service import android.content.Context import com.bitchat.android.mesh.BluetoothMeshService import com.bitchat.android.mesh.UnifiedMeshService +import com.bitchat.android.model.RoutedPacket +import com.bitchat.android.protocol.BitchatPacket +import com.bitchat.android.sync.GossipSyncManager /** * Process-wide holder to share a single BluetoothMeshService instance @@ -11,10 +14,59 @@ import com.bitchat.android.mesh.UnifiedMeshService object MeshServiceHolder { private const val TAG = "MeshServiceHolder" @Volatile - var sharedGossipSyncManager: com.bitchat.android.sync.GossipSyncManager? = null + var sharedGossipSyncManager: GossipSyncManager? = null private set - fun setGossipManager(mgr: com.bitchat.android.sync.GossipSyncManager) { sharedGossipSyncManager = mgr } + private val activeGossipOwners = mutableSetOf() + + @Synchronized + fun setGossipManager( + mgr: GossipSyncManager, + signer: (BitchatPacket) -> BitchatPacket + ) { + val previous = sharedGossipSyncManager + if (previous !== mgr) { + try { previous?.stop() } catch (_: Exception) { } + } + sharedGossipSyncManager = mgr + mgr.delegate = TransportGossipDelegate(signer) + if (activeGossipOwners.isNotEmpty()) { + mgr.start() + } + } + + @Synchronized + fun startSharedGossip(owner: String) { + val wasIdle = activeGossipOwners.isEmpty() + activeGossipOwners.add(owner) + if (wasIdle) { + sharedGossipSyncManager?.start() + } + } + + @Synchronized + fun stopSharedGossip(owner: String) { + activeGossipOwners.remove(owner) + if (activeGossipOwners.isEmpty()) { + sharedGossipSyncManager?.stop() + } + } + + private class TransportGossipDelegate( + private val signer: (BitchatPacket) -> BitchatPacket + ) : GossipSyncManager.Delegate { + override fun sendPacket(packet: BitchatPacket) { + TransportBridgeService.broadcastFromLocal(RoutedPacket(packet)) + } + + override fun sendPacketToPeer(peerID: String, packet: BitchatPacket) { + TransportBridgeService.sendToPeerFromLocal(peerID, packet) + } + + override fun signPacketForBroadcast(packet: BitchatPacket): BitchatPacket { + return signer(packet) + } + } @Volatile var meshService: BluetoothMeshService? = null @@ -84,6 +136,9 @@ object MeshServiceHolder { @Synchronized fun clear() { android.util.Log.d(TAG, "Clearing BluetoothMeshService from holder") + try { sharedGossipSyncManager?.stop() } catch (_: Exception) { } + sharedGossipSyncManager = null + activeGossipOwners.clear() meshService = null unifiedMeshService = null } diff --git a/app/src/main/java/com/bitchat/android/service/TransportBridgeService.kt b/app/src/main/java/com/bitchat/android/service/TransportBridgeService.kt index c6a83fd0..9422be0e 100644 --- a/app/src/main/java/com/bitchat/android/service/TransportBridgeService.kt +++ b/app/src/main/java/com/bitchat/android/service/TransportBridgeService.kt @@ -105,6 +105,40 @@ object TransportBridgeService { } } + /** + * Send a locally originated packet to every active transport without applying relay TTL + * handling. This is used for neighbor-only packets such as REQUEST_SYNC whose TTL is + * intentionally zero on the first radio hop. + */ + fun broadcastFromLocal(packet: RoutedPacket) { + val targets = transports.toMap() + if (targets.isEmpty()) return + + targets.forEach { (id, layer) -> + try { + layer.send(packet) + } catch (e: Exception) { + Log.e(TAG, "Failed to send local packet to $id: ${e.message}") + } + } + } + + /** + * Send a locally originated packet directly to a peer on every active transport. + */ + fun sendToPeerFromLocal(peerID: String, packet: BitchatPacket) { + val targets = transports.toMap() + if (targets.isEmpty()) return + + targets.forEach { (id, layer) -> + try { + layer.sendToPeer(peerID, packet) + } catch (e: Exception) { + Log.e(TAG, "Failed to send local peer packet to $id: ${e.message}") + } + } + } + private fun prepareForwardedPacket(kind: String, packet: BitchatPacket): BitchatPacket? { if (packet.ttl == 0u.toUByte()) { Log.d(TAG, "Dropping bridged packet type ${packet.type}: TTL expired") diff --git a/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshService.kt b/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshService.kt index 67dccdf2..6a8c67b8 100644 --- a/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshService.kt +++ b/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshService.kt @@ -492,12 +492,13 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor } }, Handler(Looper.getMainLooper())) - meshCore.startCore() - startPeriodicConnectionMaintenance() - connectionTracker.start() - // Register with cross-layer transport bridge TransportBridgeService.register("WIFI", this) + + meshCore.startCore() + com.bitchat.android.service.MeshServiceHolder.startSharedGossip("WIFI") + startPeriodicConnectionMaintenance() + connectionTracker.start() } /** @@ -511,6 +512,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor // Unregister from bridge TransportBridgeService.unregister("WIFI") + com.bitchat.android.service.MeshServiceHolder.stopSharedGossip("WIFI") try { com.bitchat.android.services.AppStateStore.clearTransportPeers("WIFI") } catch (_: Exception) { } try { com.bitchat.android.services.AppStateStore.clearTransportDirectPeers("WIFI") } catch (_: Exception) { } @@ -554,6 +556,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor recoveryInProgress = true isActive = false TransportBridgeService.unregister("WIFI") + com.bitchat.android.service.MeshServiceHolder.stopSharedGossip("WIFI") try { com.bitchat.android.services.AppStateStore.clearTransportPeers("WIFI") } catch (_: Exception) { } try { com.bitchat.android.services.AppStateStore.clearTransportDirectPeers("WIFI") } catch (_: Exception) { } val oldPublishSession = publishSession diff --git a/app/src/test/java/com/bitchat/android/ui/CommandProcessorTest.kt b/app/src/test/java/com/bitchat/android/ui/CommandProcessorTest.kt index f1b33d32..431d8500 100644 --- a/app/src/test/java/com/bitchat/android/ui/CommandProcessorTest.kt +++ b/app/src/test/java/com/bitchat/android/ui/CommandProcessorTest.kt @@ -2,7 +2,7 @@ package com.bitchat.android.ui import android.content.Context import androidx.test.core.app.ApplicationProvider -import com.bitchat.android.mesh.BluetoothMeshService +import com.bitchat.android.mesh.MeshService import com.bitchat.android.model.BitchatMessage import junit.framework.TestCase.assertEquals import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -34,7 +34,7 @@ class CommandProcessorTest() { coroutineScope = testScope ) - private val meshService: BluetoothMeshService = mock() + private val meshService: MeshService = mock() @Before fun setup() {