From 9a49201d73bc0e56718cfa2d7fc5e714618ba0dd Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 9 Jan 2026 20:13:57 +0700 Subject: [PATCH] fix connection limits --- .../mesh/BluetoothConnectionManager.kt | 27 +++++++-- .../mesh/BluetoothConnectionTracker.kt | 57 ++++++++++++++++--- .../mesh/BluetoothGattServerManager.kt | 30 +++++++--- 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt index 6defd010..12da366a 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt @@ -109,11 +109,19 @@ class BluetoothConnectionManager( } // Connection caps: enforce on change connectionScope.launch { - dbg.maxConnectionsOverall.collect { + dbg.maxConnectionsOverall.collect { maxOverall -> if (!isActive) return@collect + // 1. Enforce client limits (handled by tracker) connectionTracker.enforceConnectionLimits() - // Also enforce server side best-effort - serverManager.enforceServerLimit(dbg.maxServerConnections.value) + + // 2. Enforce overall limit on server connections if needed + // (Tracker knows about all connections but can't disconnect servers directly) + val maxServer = dbg.maxServerConnections.value + val excessServers = connectionTracker.getExcessServerConnections(maxServer, maxOverall) + excessServers.forEach { device -> + Log.d(TAG, "Disconnecting server ${device.address} due to overall cap") + serverManager.disconnectDevice(device) + } } } connectionScope.launch { @@ -123,9 +131,18 @@ class BluetoothConnectionManager( } } connectionScope.launch { - dbg.maxServerConnections.collect { + dbg.maxServerConnections.collect { maxServer -> if (!isActive) return@collect - serverManager.enforceServerLimit(dbg.maxServerConnections.value) + // Enforce server specific limit + serverManager.enforceServerLimit(maxServer) + + // Also check if this change puts us over the overall limit + val maxOverall = dbg.maxConnectionsOverall.value + val excessServers = connectionTracker.getExcessServerConnections(maxServer, maxOverall) + excessServers.forEach { device -> + Log.d(TAG, "Disconnecting server ${device.address} due to overall cap") + serverManager.disconnectDevice(device) + } } } } catch (_: Exception) { } diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionTracker.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionTracker.kt index 7029185f..85e5f452 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionTracker.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionTracker.kt @@ -229,11 +229,17 @@ class BluetoothConnectionTracker( */ fun getConnectedDeviceCount(): Int = connectedDevices.size + /** + * Check if connection limit is reached + */ /** * Check if connection limit is reached */ fun isConnectionLimitReached(): Boolean { - return connectedDevices.size >= powerManager.getMaxConnections() + // Respect debug override if set + val dbg = try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance() } catch (_: Exception) { null } + val maxConnections = dbg?.maxConnectionsOverall?.value ?: powerManager.getMaxConnections() + return connectedDevices.size >= maxConnections } /** @@ -244,10 +250,9 @@ class BluetoothConnectionTracker( val dbg = try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance() } catch (_: Exception) { null } val maxOverall = dbg?.maxConnectionsOverall?.value ?: powerManager.getMaxConnections() val maxClient = dbg?.maxClientConnections?.value ?: maxOverall - val maxServer = dbg?.maxServerConnections?.value ?: maxOverall + // Note: maxServer is handled by GattServerManager, but we need to respect overall limit here too val clients = connectedDevices.values.filter { it.isClient } - val servers = connectedDevices.values.filter { !it.isClient } // Enforce client cap first (we can actively disconnect) if (clients.size > maxClient) { @@ -259,22 +264,56 @@ class BluetoothConnectionTracker( } } - // Note: server cap enforced in GattServerManager (we don't have server handle here) - - // Enforce overall cap by disconnecting oldest client connections + // Re-check overall cap after client cleanup if (connectedDevices.size > maxOverall) { Log.i(TAG, "Enforcing overall cap: ${connectedDevices.size} > $maxOverall") val excess = connectedDevices.size - maxOverall - val toDisconnect = connectedDevices.values - .filter { it.isClient } // only clients from here + + // Prefer disconnecting clients first to satisfy overall cap + val clientsToDisconnect = connectedDevices.values + .filter { it.isClient } .sortedBy { it.connectedAt } .take(excess) - toDisconnect.forEach { dc -> + + clientsToDisconnect.forEach { dc -> Log.d(TAG, "Disconnecting client ${dc.device.address} due to overall cap") dc.gatt?.disconnect() } } } + + /** + * Get excess server connections that should be disconnected to satisfy limits. + * This allows the Manager to coordinate server disconnects since Tracker doesn't control the server. + */ + fun getExcessServerConnections(maxServer: Int, maxOverall: Int): List { + val servers = connectedDevices.values.filter { !it.isClient } + val excessList = mutableListOf() + + // 1. Check server specific limit + if (servers.size > maxServer) { + val excessCount = servers.size - maxServer + val toRemove = servers.sortedBy { it.connectedAt }.take(excessCount) + excessList.addAll(toRemove.map { it.device }) + } + + // 2. Check overall limit (considering we might have already removed some above) + // We need to count how many connections we will have after the above removals + val currentTotal = connectedDevices.size + val plannedRemovals = excessList.size + val projectedTotal = currentTotal - plannedRemovals + + if (projectedTotal > maxOverall) { + val furtherExcess = projectedTotal - maxOverall + // We can only remove servers here. Clients are handled in enforceConnectionLimits. + // Filter out devices we already planned to remove + val remainingServers = servers.filter { s -> excessList.none { it.address == s.device.address } } + val toRemove = remainingServers.sortedBy { it.connectedAt }.take(furtherExcess) + excessList.addAll(toRemove.map { it.device }) + } + + return excessList + } /** * Clean up a specific device connection diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt index 6a1c6fbf..e06fd008 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt @@ -49,15 +49,28 @@ class BluetoothGattServerManager( fun enforceServerLimit(maxServer: Int) { if (maxServer <= 0) return try { - val subs = connectionTracker.getSubscribedDevices() - if (subs.size > maxServer) { - val excess = subs.size - maxServer - subs.take(excess).forEach { d -> - try { gattServer?.cancelConnection(d) } catch (_: Exception) { } + // Use connection tracker to get actual connected server devices + val servers = connectionTracker.getConnectedDevices().values.filter { !it.isClient } + if (servers.size > maxServer) { + val excess = servers.size - maxServer + // Disconnect oldest + servers.sortedBy { it.connectedAt }.take(excess).forEach { d -> + try { gattServer?.cancelConnection(d.device) } catch (_: Exception) { } } } } catch (_: Exception) { } } + + /** + * Disconnect a specific device (used by ConnectionManager to enforce overall limits) + */ + fun disconnectDevice(device: BluetoothDevice) { + try { + gattServer?.cancelConnection(device) + } catch (e: Exception) { + Log.w(TAG, "Error disconnecting device ${device.address}: ${e.message}") + } + } /** * Start GATT server @@ -122,9 +135,10 @@ class BluetoothGattServerManager( // Try to cancel any active connections explicitly before closing try { - val devices = connectionTracker.getSubscribedDevices() - devices.forEach { d -> - try { gattServer?.cancelConnection(d) } catch (_: Exception) { } + // Disconnect ALL server connections + val servers = connectionTracker.getConnectedDevices().values.filter { !it.isClient } + servers.forEach { d -> + try { gattServer?.cancelConnection(d.device) } catch (_: Exception) { } } } catch (_: Exception) { }