From 9bdfa31d5579b510da27207637603323914d00be Mon Sep 17 00:00:00 2001 From: prudhvir3ddy Date: Fri, 11 Jul 2025 17:15:54 +0530 Subject: [PATCH 1/7] Upgrade compileSDK, kotlin, AGP, compose, core libs --- app/build.gradle.kts | 4 +-- .../java/com/bitchat/android/MainActivity.kt | 4 +-- build.gradle.kts | 1 + gradle/libs.versions.toml | 32 +++++++++---------- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8aad3797..4220116d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.parcelize) + alias(libs.plugins.kotlin.compose) } android { @@ -40,9 +41,6 @@ android { buildFeatures { compose = true } - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() - } packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" diff --git a/app/src/main/java/com/bitchat/android/MainActivity.kt b/app/src/main/java/com/bitchat/android/MainActivity.kt index a1f5390c..5ac59bb5 100644 --- a/app/src/main/java/com/bitchat/android/MainActivity.kt +++ b/app/src/main/java/com/bitchat/android/MainActivity.kt @@ -318,11 +318,11 @@ class MainActivity : ComponentActivity() { } } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) // Handle notification intents when app is already running if (onboardingState == OnboardingState.COMPLETE) { - intent?.let { handleNotificationIntent(it) } + handleNotificationIntent(intent) } } diff --git a/build.gradle.kts b/build.gradle.kts index 38d6e783..daf86da9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,4 +3,5 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.android.library) apply false + alias(libs.plugins.kotlin.compose) apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 282370a4..eb1422bc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,36 +1,35 @@ [versions] # Android and Kotlin -agp = "8.2.0" -kotlin = "1.9.20" -compileSdk = "34" +agp = "8.10.1" +kotlin = "2.2.0" +compileSdk = "35" minSdk = "26" # API 26 for proper BLE support targetSdk = "34" # AndroidX Core -core-ktx = "1.12.0" -lifecycle-runtime = "2.7.0" -activity-compose = "1.8.2" -appcompat = "1.6.1" +core-ktx = "1.16.0" +lifecycle-runtime = "2.9.1" +activity-compose = "1.10.1" +appcompat = "1.7.1" # Compose -compose-bom = "2023.10.01" -compose-compiler = "1.5.5" +compose-bom = "2025.06.01" # Navigation -navigation-compose = "2.7.6" +navigation-compose = "2.9.1" # Accompanist -accompanist-permissions = "0.32.0" +accompanist-permissions = "0.37.3" # Cryptography bouncycastle = "1.70" tink-android = "1.10.0" # JSON -gson = "2.10.1" +gson = "2.13.1" # Coroutines -kotlinx-coroutines = "1.7.3" +kotlinx-coroutines = "1.10.2" # Bluetooth nordic-ble = "2.6.1" @@ -39,12 +38,12 @@ nordic-ble = "2.6.1" lz4-java = "1.8.0" # Security -security-crypto = "1.1.0-alpha06" +security-crypto = "1.1.0-beta01" # Testing junit = "4.13.2" -androidx-test-ext = "1.1.5" -espresso = "3.5.1" +androidx-test-ext = "1.2.1" +espresso = "3.6.1" [libraries] # AndroidX Core @@ -104,6 +103,7 @@ android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-parcelize = { id = "kotlin-parcelize" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } [bundles] compose = [ From d1085fde0b9afcaf6f84c5dd10cbc35b9cd5ddef Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Fri, 11 Jul 2025 12:44:25 +0200 Subject: [PATCH 2/7] Cheap routing optimizations - Don't relay back to sender or relayer - Only send to connected device if recipientID matches one - Don't relay our own ACK/read receipt --- .../mesh/BluetoothConnectionManager.kt | 108 ++++++++++++++---- .../android/mesh/BluetoothMeshService.kt | 61 +++++----- .../bitchat/android/mesh/MessageHandler.kt | 51 ++++++--- .../bitchat/android/mesh/PacketProcessor.kt | 87 +++++++------- .../bitchat/android/mesh/SecurityManager.kt | 10 +- .../com/bitchat/android/model/RoutedPacket.kt | 13 +++ 6 files changed, 216 insertions(+), 114 deletions(-) create mode 100644 app/src/main/java/com/bitchat/android/model/RoutedPacket.kt 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 83673d1f..e729496a 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt @@ -9,6 +9,8 @@ import android.os.ParcelUuid import android.util.Log import androidx.core.app.ActivityCompat import com.bitchat.android.protocol.BitchatPacket +import com.bitchat.android.protocol.SpecialRecipients +import com.bitchat.android.model.RoutedPacket import kotlinx.coroutines.* import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -53,6 +55,7 @@ class BluetoothConnectionManager( // Simplified connection tracking - reduced memory footprint private val connectedDevices = ConcurrentHashMap() private val subscribedDevices = CopyOnWriteArrayList() + public val addressPeerMap = ConcurrentHashMap() // Connection attempt tracking with automatic cleanup private val pendingConnections = ConcurrentHashMap() @@ -199,47 +202,102 @@ class BluetoothConnectionManager( powerManager.setAppBackgroundState(inBackground) } + // Function to send data to a single device (server side) + private fun notifyDevice(device: BluetoothDevice, data: ByteArray) { + try { + characteristic?.let { char -> + char.value = data + gattServer?.notifyCharacteristicChanged(device, char, false) + } + } catch (e: Exception) { + Log.w(TAG, "Error sending to server connection ${device.address}: ${e.message}") + connectionScope.launch { + delay(CLEANUP_DELAY) + subscribedDevices.remove(device) + addressPeerMap.remove(device.getAddress()) + } + } + } + + // Function to send data to a single device (client side) + private fun writeToDeviceConn(deviceConn: DeviceConnection, data: ByteArray) { + try { + deviceConn.characteristic?.let { char -> + char.value = data + deviceConn.gatt?.writeCharacteristic(char) + } + } catch (e: Exception) { + Log.w(TAG, "Error sending to client connection ${deviceConn.device.address}: ${e.message}") + connectionScope.launch { + delay(CLEANUP_DELAY) + cleanupDeviceConnection(deviceConn.device.address) + } + } + } + /** * Broadcast packet to connected devices with connection limit enforcement */ - fun broadcastPacket(packet: BitchatPacket) { + fun broadcastPacket(routed: RoutedPacket) { + val packet = routed.packet + if (!isActive) return val data = packet.toBinaryData() ?: return + if (packet.recipientID != SpecialRecipients.BROADCAST) { + val recipientID = packet.recipientID?.let { + String(it).replace("\u0000", "").trim() + } ?: "" + + // Try to find the recipient in server connections (subscribedDevices) + val targetDevice = subscribedDevices.firstOrNull { addressPeerMap[it.address] == recipientID } + // If found, send directly + if (targetDevice != null) { + Log.d(TAG, "Send packet type ${packet.type} directly to target device for recipient $recipientID: ${targetDevice.address}") + notifyDevice(targetDevice, data) + return // Sent, no need to continue + } + + // Try to find the recipient in client connections (connectedDevices) + val targetDeviceConn = connectedDevices.values.firstOrNull { addressPeerMap[it.device.address] == recipientID } + // If found, send directly + if (targetDeviceConn != null) { + Log.d(TAG, "Send packet type ${packet.type} directly to target client connection for recipient $recipientID: ${targetDeviceConn.device.address}") + writeToDeviceConn(targetDeviceConn, data) + return // Sent, no need to continue + } + } + + // Else, continue with broadcasting to all devices Log.d(TAG, "Broadcasting packet type ${packet.type} to ${subscribedDevices.size} server + ${connectedDevices.size} client connections") - + + val senderID = String(packet.senderID).replace("\u0000", "") // Send to server connections (devices connected to our GATT server) subscribedDevices.forEach { device -> - try { - characteristic?.let { char -> - char.value = data - gattServer?.notifyCharacteristicChanged(device, char, false) - } - } catch (e: Exception) { - Log.w(TAG, "Error sending to server connection ${device.address}: ${e.message}") - // Clean up failed connection - connectionScope.launch { - delay(CLEANUP_DELAY) - subscribedDevices.remove(device) - } + if (device.address == routed.relayAddress) { + Log.d(TAG, "Skipping broadcast back to relayer: ${device.address}") + return@forEach } + if (addressPeerMap[device.address] == senderID) { + Log.d(TAG, "Skipping broadcast back to sender: ${device.address}") + return@forEach + } + notifyDevice(device, data) } // Send to client connections connectedDevices.values.forEach { deviceConn -> if (deviceConn.isClient && deviceConn.gatt != null && deviceConn.characteristic != null) { - try { - deviceConn.characteristic.value = data - deviceConn.gatt.writeCharacteristic(deviceConn.characteristic) - } catch (e: Exception) { - Log.w(TAG, "Error sending to client connection ${deviceConn.device.address}: ${e.message}") - // Clean up failed connection - connectionScope.launch { - delay(CLEANUP_DELAY) - cleanupDeviceConnection(deviceConn.device.address) - } + if (deviceConn.device.address == routed.relayAddress) { + Log.d(TAG, "Skipping broadcast back to relayer: ${deviceConn.device.address}") + return@forEach } + if (addressPeerMap[deviceConn.device.address] == senderID) { + Log.d(TAG, "Skipping broadcast back to sender: ${deviceConn.device.address}") + return@forEach + } + writeToDeviceConn(deviceConn, data) } } } @@ -907,6 +965,7 @@ class BluetoothConnectionManager( private fun cleanupDeviceConnection(deviceAddress: String) { connectedDevices.remove(deviceAddress)?.let { deviceConn -> subscribedDevices.removeAll { it.address == deviceAddress } + addressPeerMap.remove(deviceAddress) } // CRITICAL FIX: Always remove from pending connections when cleaning up // This prevents failed connections from blocking future attempts @@ -935,6 +994,7 @@ class BluetoothConnectionManager( private fun clearAllConnections() { connectedDevices.clear() subscribedDevices.clear() + addressPeerMap.clear() pendingConnections.clear() } } 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 e92a6dfc..5d4634f0 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -5,6 +5,7 @@ import android.util.Log import com.bitchat.android.crypto.EncryptionService import com.bitchat.android.crypto.MessagePadding import com.bitchat.android.model.BitchatMessage +import com.bitchat.android.model.RoutedPacket import com.bitchat.android.model.DeliveryAck import com.bitchat.android.model.ReadReceipt import com.bitchat.android.protocol.BitchatPacket @@ -101,10 +102,14 @@ class BluetoothMeshService(private val context: Context) { // SecurityManager delegate for key exchange notifications securityManager.delegate = object : SecurityManagerDelegate { - override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray) { + override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray, receivedAddress: String?) { // Notify delegate about key exchange completion so it can register peer fingerprint delegate?.registerPeerPublicKey(peerID, peerPublicKeyData) + receivedAddress?.let { address -> + connectionManager.addressPeerMap[address] = peerID + } + // Send announcement and cached messages after key exchange serviceScope.launch { delay(100) @@ -127,7 +132,7 @@ class BluetoothMeshService(private val context: Context) { } override fun sendPacket(packet: BitchatPacket) { - connectionManager.broadcastPacket(packet) + connectionManager.broadcastPacket(RoutedPacket(packet)) } } @@ -160,11 +165,11 @@ class BluetoothMeshService(private val context: Context) { // Packet operations override fun sendPacket(packet: BitchatPacket) { - connectionManager.broadcastPacket(packet) + connectionManager.broadcastPacket(RoutedPacket(packet)) } - override fun relayPacket(packet: BitchatPacket) { - connectionManager.broadcastPacket(packet) + override fun relayPacket(routed: RoutedPacket) { + connectionManager.broadcastPacket(routed) } override fun getBroadcastRecipient(): ByteArray { @@ -221,32 +226,32 @@ class BluetoothMeshService(private val context: Context) { peerManager.updatePeerLastSeen(peerID) } - override fun handleKeyExchange(packet: BitchatPacket, peerID: String): Boolean { - return runBlocking { securityManager.handleKeyExchange(packet, peerID) } + override fun handleKeyExchange(routed: RoutedPacket): Boolean { + return runBlocking { securityManager.handleKeyExchange(routed) } } - override fun handleAnnounce(packet: BitchatPacket, peerID: String) { - serviceScope.launch { messageHandler.handleAnnounce(packet, peerID) } + override fun handleAnnounce(routed: RoutedPacket) { + serviceScope.launch { messageHandler.handleAnnounce(routed) } } - override fun handleMessage(packet: BitchatPacket, peerID: String) { - serviceScope.launch { messageHandler.handleMessage(packet, peerID) } + override fun handleMessage(routed: RoutedPacket) { + serviceScope.launch { messageHandler.handleMessage(routed) } } - override fun handleLeave(packet: BitchatPacket, peerID: String) { - serviceScope.launch { messageHandler.handleLeave(packet, peerID) } + override fun handleLeave(routed: RoutedPacket) { + serviceScope.launch { messageHandler.handleLeave(routed) } } override fun handleFragment(packet: BitchatPacket): BitchatPacket? { return fragmentManager.handleFragment(packet) } - override fun handleDeliveryAck(packet: BitchatPacket, peerID: String) { - serviceScope.launch { messageHandler.handleDeliveryAck(packet, peerID) } + override fun handleDeliveryAck(routed: RoutedPacket) { + serviceScope.launch { messageHandler.handleDeliveryAck(routed) } } - override fun handleReadReceipt(packet: BitchatPacket, peerID: String) { - serviceScope.launch { messageHandler.handleReadReceipt(packet, peerID) } + override fun handleReadReceipt(routed: RoutedPacket) { + serviceScope.launch { messageHandler.handleReadReceipt(routed) } } override fun sendAnnouncementToPeer(peerID: String) { @@ -257,15 +262,15 @@ class BluetoothMeshService(private val context: Context) { storeForwardManager.sendCachedMessages(peerID) } - override fun relayPacket(packet: BitchatPacket) { - connectionManager.broadcastPacket(packet) + override fun relayPacket(routed: RoutedPacket) { + connectionManager.broadcastPacket(routed) } } // BluetoothConnectionManager delegates connectionManager.delegate = object : BluetoothConnectionManagerDelegate { override fun onPacketReceived(packet: BitchatPacket, peerID: String, device: android.bluetooth.BluetoothDevice?) { - packetProcessor.processPacket(packet, peerID) + packetProcessor.processPacket(RoutedPacket(packet, peerID, device?.address)) } override fun onDeviceConnected(device: android.bluetooth.BluetoothDevice) { @@ -372,7 +377,7 @@ class BluetoothMeshService(private val context: Context) { // Send with random delay and retry for reliability // delay(Random.nextLong(50, 500)) - connectionManager.broadcastPacket(packet) + connectionManager.broadcastPacket(RoutedPacket(packet)) } } } @@ -425,7 +430,7 @@ class BluetoothMeshService(private val context: Context) { // Send with delay delay(Random.nextLong(50, 500)) - connectionManager.broadcastPacket(packet) + connectionManager.broadcastPacket(RoutedPacket(packet)) } } catch (e: Exception) { @@ -451,13 +456,13 @@ class BluetoothMeshService(private val context: Context) { // Send multiple times for reliability delay(Random.nextLong(0, 500)) - connectionManager.broadcastPacket(announcePacket) + connectionManager.broadcastPacket(RoutedPacket(announcePacket)) delay(500 + Random.nextLong(0, 500)) - connectionManager.broadcastPacket(announcePacket) + connectionManager.broadcastPacket(RoutedPacket(announcePacket)) delay(1000 + Random.nextLong(0, 500)) - connectionManager.broadcastPacket(announcePacket) + connectionManager.broadcastPacket(RoutedPacket(announcePacket)) } } @@ -475,7 +480,7 @@ class BluetoothMeshService(private val context: Context) { payload = nickname.toByteArray() ) - connectionManager.broadcastPacket(packet) + connectionManager.broadcastPacket(RoutedPacket(packet)) peerManager.markPeerAsAnnouncedTo(peerID) } @@ -491,7 +496,7 @@ class BluetoothMeshService(private val context: Context) { payload = publicKeyData ) - connectionManager.broadcastPacket(packet) + connectionManager.broadcastPacket(RoutedPacket(packet)) Log.d(TAG, "Sent key exchange") } @@ -507,7 +512,7 @@ class BluetoothMeshService(private val context: Context) { payload = nickname.toByteArray() ) - connectionManager.broadcastPacket(packet) + connectionManager.broadcastPacket(RoutedPacket(packet)) } /** diff --git a/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt b/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt index d25caf1c..2cff0e6c 100644 --- a/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt +++ b/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt @@ -5,6 +5,7 @@ import com.bitchat.android.crypto.MessagePadding import com.bitchat.android.model.BitchatMessage import com.bitchat.android.model.DeliveryAck import com.bitchat.android.model.ReadReceipt +import com.bitchat.android.model.RoutedPacket import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.MessageType import kotlinx.coroutines.* @@ -30,7 +31,10 @@ class MessageHandler(private val myPeerID: String) { /** * Handle announce message */ - suspend fun handleAnnounce(packet: BitchatPacket, peerID: String): Boolean { + suspend fun handleAnnounce(routed: RoutedPacket): Boolean { + val packet = routed.packet + val peerID = routed.peerID ?: "unknown" + if (peerID == myPeerID) return false val nickname = String(packet.payload, Charsets.UTF_8) @@ -43,7 +47,7 @@ class MessageHandler(private val myPeerID: String) { if (packet.ttl > 1u) { val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) delay(Random.nextLong(100, 300)) - delegate?.relayPacket(relayPacket) + delegate?.relayPacket(RoutedPacket(relayPacket, peerID, routed.relayAddress)) } return isFirstAnnounce @@ -52,27 +56,31 @@ class MessageHandler(private val myPeerID: String) { /** * Handle broadcast or private message */ - suspend fun handleMessage(packet: BitchatPacket, peerID: String) { + suspend fun handleMessage(routed: RoutedPacket) { + val packet = routed.packet + val peerID = routed.peerID ?: "unknown" if (peerID == myPeerID) return val recipientID = packet.recipientID?.takeIf { !it.contentEquals(delegate?.getBroadcastRecipient()) } if (recipientID == null) { // BROADCAST MESSAGE - handleBroadcastMessage(packet, peerID) + handleBroadcastMessage(routed) } else if (String(recipientID).replace("\u0000", "") == myPeerID) { // PRIVATE MESSAGE FOR US handlePrivateMessage(packet, peerID) } else if (packet.ttl > 0u) { // RELAY MESSAGE - relayMessage(packet) + relayMessage(routed) } } /** * Handle broadcast message */ - private suspend fun handleBroadcastMessage(packet: BitchatPacket, peerID: String) { + private suspend fun handleBroadcastMessage(routed: RoutedPacket) { + val packet = routed.packet + val peerID = routed.peerID ?: "unknown" try { // Parse message val message = BitchatMessage.fromBinaryPayload(packet.payload) @@ -104,7 +112,7 @@ class MessageHandler(private val myPeerID: String) { } // Relay broadcast messages - relayMessage(packet) + relayMessage(routed) } catch (e: Exception) { Log.e(TAG, "Failed to process broadcast message: ${e.message}") @@ -162,7 +170,9 @@ class MessageHandler(private val myPeerID: String) { /** * Handle leave message */ - suspend fun handleLeave(packet: BitchatPacket, peerID: String) { + suspend fun handleLeave(routed: RoutedPacket) { + val packet = routed.packet + val peerID = routed.peerID ?: "unknown" val content = String(packet.payload, Charsets.UTF_8) if (content.startsWith("#")) { @@ -180,14 +190,16 @@ class MessageHandler(private val myPeerID: String) { // Relay if TTL > 0 if (packet.ttl > 1u) { val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) - delegate?.relayPacket(relayPacket) + delegate?.relayPacket(routed.copy(packet = relayPacket)) } } /** * Handle delivery acknowledgment */ - suspend fun handleDeliveryAck(packet: BitchatPacket, peerID: String) { + suspend fun handleDeliveryAck(routed: RoutedPacket) { + val packet = routed.packet + val peerID = routed.peerID ?: "unknown" if (packet.recipientID != null && String(packet.recipientID).replace("\u0000", "") == myPeerID) { try { val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID) @@ -200,17 +212,19 @@ class MessageHandler(private val myPeerID: String) { } catch (e: Exception) { Log.e(TAG, "Failed to decrypt delivery ACK: ${e.message}") } - } else if (packet.ttl > 0u) { + } else if (packet.ttl > 0u && String(packet.senderID).replace("\u0000", "") != myPeerID) { // Relay val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) - delegate?.relayPacket(relayPacket) + delegate?.relayPacket(routed.copy(packet = relayPacket)) } } /** * Handle read receipt */ - suspend fun handleReadReceipt(packet: BitchatPacket, peerID: String) { + suspend fun handleReadReceipt(routed: RoutedPacket) { + val packet = routed.packet + val peerID = routed.peerID ?: "unknown" if (packet.recipientID != null && String(packet.recipientID).replace("\u0000", "") == myPeerID) { try { val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID) @@ -223,17 +237,18 @@ class MessageHandler(private val myPeerID: String) { } catch (e: Exception) { Log.e(TAG, "Failed to decrypt read receipt: ${e.message}") } - } else if (packet.ttl > 0u) { + } else if (packet.ttl > 0u && String(packet.senderID).replace("\u0000", "") != myPeerID) { // Relay val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) - delegate?.relayPacket(relayPacket) + delegate?.relayPacket(routed.copy(packet = relayPacket)) } } /** * Relay message with adaptive probability (same as iOS) */ - private suspend fun relayMessage(packet: BitchatPacket) { + private suspend fun relayMessage(routed: RoutedPacket) { + val packet = routed.packet if (packet.ttl == 0u.toUByte()) return val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) @@ -253,7 +268,7 @@ class MessageHandler(private val myPeerID: String) { if (shouldRelay) { val delay = Random.nextLong(50, 500) // Random delay like iOS delay(delay) - delegate?.relayPacket(relayPacket) + delegate?.relayPacket(routed.copy(packet = relayPacket)) } } @@ -326,7 +341,7 @@ interface MessageHandlerDelegate { // Packet operations fun sendPacket(packet: BitchatPacket) - fun relayPacket(packet: BitchatPacket) + fun relayPacket(routed: RoutedPacket) fun getBroadcastRecipient(): ByteArray // Cryptographic operations diff --git a/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt b/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt index 35084d27..dc43ed7a 100644 --- a/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt +++ b/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt @@ -3,6 +3,7 @@ package com.bitchat.android.mesh import android.util.Log import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.MessageType +import com.bitchat.android.model.RoutedPacket import kotlinx.coroutines.* /** @@ -24,16 +25,19 @@ class PacketProcessor(private val myPeerID: String) { /** * Process received packet - main entry point for all incoming packets */ - fun processPacket(packet: BitchatPacket, peerID: String) { + fun processPacket(routed: RoutedPacket) { processorScope.launch { - handleReceivedPacket(packet, peerID) + handleReceivedPacket(routed) } } /** * Handle received packet - core protocol logic (exact same as iOS) */ - private suspend fun handleReceivedPacket(packet: BitchatPacket, peerID: String) { + private suspend fun handleReceivedPacket(routed: RoutedPacket) { + val packet = routed.packet + val peerID = routed.peerID ?: "unknown" + // Basic validation and security checks if (!delegate?.validatePacketSecurity(packet, peerID)!!) { Log.d(TAG, "Packet failed security validation from $peerID") @@ -47,15 +51,15 @@ class PacketProcessor(private val myPeerID: String) { // Process based on message type (exact same logic as iOS) when (MessageType.fromValue(packet.type)) { - MessageType.KEY_EXCHANGE -> handleKeyExchange(packet, peerID) - MessageType.ANNOUNCE -> handleAnnounce(packet, peerID) - MessageType.MESSAGE -> handleMessage(packet, peerID) - MessageType.LEAVE -> handleLeave(packet, peerID) + MessageType.KEY_EXCHANGE -> handleKeyExchange(routed) + MessageType.ANNOUNCE -> handleAnnounce(routed) + MessageType.MESSAGE -> handleMessage(routed) + MessageType.LEAVE -> handleLeave(routed) MessageType.FRAGMENT_START, MessageType.FRAGMENT_CONTINUE, - MessageType.FRAGMENT_END -> handleFragment(packet, peerID) - MessageType.DELIVERY_ACK -> handleDeliveryAck(packet, peerID) - MessageType.READ_RECEIPT -> handleReadReceipt(packet, peerID) + MessageType.FRAGMENT_END -> handleFragment(routed) + MessageType.DELIVERY_ACK -> handleDeliveryAck(routed) + MessageType.READ_RECEIPT -> handleReadReceipt(routed) else -> { Log.w(TAG, "Unknown message type: ${packet.type}") } @@ -65,10 +69,11 @@ class PacketProcessor(private val myPeerID: String) { /** * Handle key exchange message */ - private suspend fun handleKeyExchange(packet: BitchatPacket, peerID: String) { + private suspend fun handleKeyExchange(routed: RoutedPacket) { + val peerID = routed.peerID ?: "unknown" Log.d(TAG, "Processing key exchange from $peerID") - val success = delegate?.handleKeyExchange(packet, peerID) ?: false + val success = delegate?.handleKeyExchange(routed) ?: false if (success) { // Key exchange successful, send announce and cached messages @@ -83,60 +88,60 @@ class PacketProcessor(private val myPeerID: String) { /** * Handle announce message */ - private suspend fun handleAnnounce(packet: BitchatPacket, peerID: String) { - Log.d(TAG, "Processing announce from $peerID") - delegate?.handleAnnounce(packet, peerID) + private suspend fun handleAnnounce(routed: RoutedPacket) { + Log.d(TAG, "Processing announce from ${routed.peerID}") + delegate?.handleAnnounce(routed) } /** * Handle regular message */ - private suspend fun handleMessage(packet: BitchatPacket, peerID: String) { - Log.d(TAG, "Processing message from $peerID") - delegate?.handleMessage(packet, peerID) + private suspend fun handleMessage(routed: RoutedPacket) { + Log.d(TAG, "Processing message from ${routed.peerID}") + delegate?.handleMessage(routed) } /** * Handle leave message */ - private suspend fun handleLeave(packet: BitchatPacket, peerID: String) { - Log.d(TAG, "Processing leave from $peerID") - delegate?.handleLeave(packet, peerID) + private suspend fun handleLeave(routed: RoutedPacket) { + Log.d(TAG, "Processing leave from ${routed.peerID}") + delegate?.handleLeave(routed) } /** * Handle message fragments */ - private suspend fun handleFragment(packet: BitchatPacket, peerID: String) { - Log.d(TAG, "Processing fragment from $peerID") + private suspend fun handleFragment(routed: RoutedPacket) { + Log.d(TAG, "Processing fragment from ${routed.peerID}") - val reassembledPacket = delegate?.handleFragment(packet) + val reassembledPacket = delegate?.handleFragment(routed.packet) if (reassembledPacket != null) { Log.d(TAG, "Fragment reassembled, processing complete message") - handleReceivedPacket(reassembledPacket, peerID) + handleReceivedPacket(RoutedPacket(reassembledPacket, routed.peerID, routed.relayAddress)) } // Relay fragment regardless of reassembly - if (packet.ttl > 0u) { - val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) - delegate?.relayPacket(relayPacket) + if (routed.packet.ttl > 0u) { + val relayPacket = routed.packet.copy(ttl = (routed.packet.ttl - 1u).toUByte()) + delegate?.relayPacket(RoutedPacket(relayPacket, routed.peerID, routed.relayAddress)) } } /** * Handle delivery acknowledgment */ - private suspend fun handleDeliveryAck(packet: BitchatPacket, peerID: String) { - Log.d(TAG, "Processing delivery ACK from $peerID") - delegate?.handleDeliveryAck(packet, peerID) + private suspend fun handleDeliveryAck(routed: RoutedPacket) { + Log.d(TAG, "Processing delivery ACK from ${routed.peerID}") + delegate?.handleDeliveryAck(routed) } /** * Handle read receipt */ - private suspend fun handleReadReceipt(packet: BitchatPacket, peerID: String) { - Log.d(TAG, "Processing read receipt from $peerID") - delegate?.handleReadReceipt(packet, peerID) + private suspend fun handleReadReceipt(routed: RoutedPacket) { + Log.d(TAG, "Processing read receipt from ${routed.peerID}") + delegate?.handleReadReceipt(routed) } /** @@ -169,16 +174,16 @@ interface PacketProcessorDelegate { fun updatePeerLastSeen(peerID: String) // Message type handlers - fun handleKeyExchange(packet: BitchatPacket, peerID: String): Boolean - fun handleAnnounce(packet: BitchatPacket, peerID: String) - fun handleMessage(packet: BitchatPacket, peerID: String) - fun handleLeave(packet: BitchatPacket, peerID: String) + fun handleKeyExchange(routed: RoutedPacket): Boolean + fun handleAnnounce(routed: RoutedPacket) + fun handleMessage(routed: RoutedPacket) + fun handleLeave(routed: RoutedPacket) fun handleFragment(packet: BitchatPacket): BitchatPacket? - fun handleDeliveryAck(packet: BitchatPacket, peerID: String) - fun handleReadReceipt(packet: BitchatPacket, peerID: String) + fun handleDeliveryAck(routed: RoutedPacket) + fun handleReadReceipt(routed: RoutedPacket) // Communication fun sendAnnouncementToPeer(peerID: String) fun sendCachedMessages(peerID: String) - fun relayPacket(packet: BitchatPacket) + fun relayPacket(routed: RoutedPacket) } diff --git a/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt b/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt index 65b74e42..50c0ce92 100644 --- a/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt @@ -4,6 +4,7 @@ import android.util.Log import com.bitchat.android.crypto.EncryptionService import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.MessageType +import com.bitchat.android.model.RoutedPacket import kotlinx.coroutines.* import java.util.* import kotlin.collections.mutableSetOf @@ -88,7 +89,10 @@ class SecurityManager(private val encryptionService: EncryptionService, private /** * Handle key exchange packet */ - suspend fun handleKeyExchange(packet: BitchatPacket, peerID: String): Boolean { + suspend fun handleKeyExchange(routed: RoutedPacket): Boolean { + val packet = routed.packet + val peerID = routed.peerID ?: "unknown" + if (peerID == myPeerID) return false if (packet.payload.isEmpty()) { @@ -113,7 +117,7 @@ class SecurityManager(private val encryptionService: EncryptionService, private Log.d(TAG, "Successfully processed key exchange from $peerID") // Notify delegate - delegate?.onKeyExchangeCompleted(peerID, packet.payload) + delegate?.onKeyExchangeCompleted(peerID, packet.payload, routed.relayAddress) return true @@ -315,5 +319,5 @@ class SecurityManager(private val encryptionService: EncryptionService, private * Delegate interface for security manager callbacks */ interface SecurityManagerDelegate { - fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray) + fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray, receivedAddress: String?) } diff --git a/app/src/main/java/com/bitchat/android/model/RoutedPacket.kt b/app/src/main/java/com/bitchat/android/model/RoutedPacket.kt new file mode 100644 index 00000000..59697360 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/model/RoutedPacket.kt @@ -0,0 +1,13 @@ +package com.bitchat.android.model + +import com.bitchat.android.protocol.BitchatPacket + +/** + * Represents a routed packet with additional metadata + * Used for processing and routing packets in the mesh network + */ +data class RoutedPacket( + val packet: BitchatPacket, + val peerID: String? = null, // Who sent it (parsed from packet.senderID) + val relayAddress: String? = null // Address it came from (for avoiding loopback) +) \ No newline at end of file From 1b37f46d1e647e5ebddc2153ddee3993cea7383a Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Fri, 11 Jul 2025 16:39:47 +0200 Subject: [PATCH 3/7] Fallback to broadcast if direct send fails --- .../mesh/BluetoothConnectionManager.kt | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 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 e729496a..87655ef8 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt @@ -203,35 +203,39 @@ class BluetoothConnectionManager( } // Function to send data to a single device (server side) - private fun notifyDevice(device: BluetoothDevice, data: ByteArray) { - try { + private fun notifyDevice(device: BluetoothDevice, data: ByteArray): Boolean { + return try { characteristic?.let { char -> char.value = data - gattServer?.notifyCharacteristicChanged(device, char, false) - } + val result = gattServer?.notifyCharacteristicChanged(device, char, false) ?: false + result + } ?: false } catch (e: Exception) { Log.w(TAG, "Error sending to server connection ${device.address}: ${e.message}") connectionScope.launch { delay(CLEANUP_DELAY) subscribedDevices.remove(device) - addressPeerMap.remove(device.getAddress()) + addressPeerMap.remove(device.address) } + false } } // Function to send data to a single device (client side) - private fun writeToDeviceConn(deviceConn: DeviceConnection, data: ByteArray) { - try { + private fun writeToDeviceConn(deviceConn: DeviceConnection, data: ByteArray): Boolean { + return try { deviceConn.characteristic?.let { char -> char.value = data - deviceConn.gatt?.writeCharacteristic(char) - } + val result = deviceConn.gatt?.writeCharacteristic(char) ?: false + result + } ?: false } catch (e: Exception) { Log.w(TAG, "Error sending to client connection ${deviceConn.device.address}: ${e.message}") connectionScope.launch { delay(CLEANUP_DELAY) cleanupDeviceConnection(deviceConn.device.address) } + false } } @@ -255,8 +259,8 @@ class BluetoothConnectionManager( // If found, send directly if (targetDevice != null) { Log.d(TAG, "Send packet type ${packet.type} directly to target device for recipient $recipientID: ${targetDevice.address}") - notifyDevice(targetDevice, data) - return // Sent, no need to continue + if (notifyDevice(targetDevice, data)) + return // Sent, no need to continue } // Try to find the recipient in client connections (connectedDevices) @@ -264,8 +268,8 @@ class BluetoothConnectionManager( // If found, send directly if (targetDeviceConn != null) { Log.d(TAG, "Send packet type ${packet.type} directly to target client connection for recipient $recipientID: ${targetDeviceConn.device.address}") - writeToDeviceConn(targetDeviceConn, data) - return // Sent, no need to continue + if (writeToDeviceConn(targetDeviceConn, data)) + return // Sent, no need to continue } } From 0c3c52b7c0b1b6f8bf6dee1c0d1f8ed6e095ad02 Mon Sep 17 00:00:00 2001 From: Comodore125 Date: Sat, 12 Jul 2025 23:06:16 +0200 Subject: [PATCH 4/7] known issues added to 0.6 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e8bd3aa..87c5b026 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved README documentation with updated clone URLs - Enhanced logging throughout the application for better debugging +### Known Issues +- reinstallation via apk does not work in some or all cases, it is advised to uninstall previous version first ## [0.5.1] - 2025-07-10 ### Added From c8c1f21d3c110e4b31948baae3bf05f98676c879 Mon Sep 17 00:00:00 2001 From: Comodore125 Date: Sat, 12 Jul 2025 23:12:56 +0200 Subject: [PATCH 5/7] Update CHANGELOG.md more precise text --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87c5b026..fdd0a20b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Enhanced logging throughout the application for better debugging ### Known Issues -- reinstallation via apk does not work in some or all cases, it is advised to uninstall previous version first +- reinstallation via apk does not work in all cases, it is advised to uninstall previous version first. root cause: https://github.com/permissionlesstech/bitchat-android/issues/92#issuecomment-3066035548 + ## [0.5.1] - 2025-07-10 ### Added From 3a01de655fa300cb810db00bb6596d6aa14b10db Mon Sep 17 00:00:00 2001 From: Comodore125 Date: Sat, 12 Jul 2025 23:19:10 +0200 Subject: [PATCH 6/7] Update CHANGELOG.md even more precise and more elegant update --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdd0a20b..02357fe1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,8 +46,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Enhanced logging throughout the application for better debugging ### Known Issues -- reinstallation via apk does not work in all cases, it is advised to uninstall previous version first. root cause: https://github.com/permissionlesstech/bitchat-android/issues/92#issuecomment-3066035548 - +- Reinstallation via APK does not work in all cases; it is recommended to uninstall the previous version first. + - Root cause: [See issue #92, comment](https://github.com/permissionlesstech/bitchat-android/issues/92#issuecomment-3066035548) + ## [0.5.1] - 2025-07-10 ### Added From 78dc9dc026c697106bef6d3313ef7c375a74063f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 13 Jul 2025 17:09:53 +0200 Subject: [PATCH 7/7] Revert "known issues added to 0.6" --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02357fe1..6e8bd3aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,10 +45,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved README documentation with updated clone URLs - Enhanced logging throughout the application for better debugging -### Known Issues -- Reinstallation via APK does not work in all cases; it is recommended to uninstall the previous version first. - - Root cause: [See issue #92, comment](https://github.com/permissionlesstech/bitchat-android/issues/92#issuecomment-3066035548) - ## [0.5.1] - 2025-07-10 ### Added