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
This commit is contained in:
GUVWAF
2025-07-12 13:18:09 +02:00
parent 4545afb0c4
commit d1085fde0b
6 changed files with 216 additions and 114 deletions
@@ -9,6 +9,8 @@ import android.os.ParcelUuid
import android.util.Log import android.util.Log
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.BitchatPacket
import com.bitchat.android.protocol.SpecialRecipients
import com.bitchat.android.model.RoutedPacket
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@@ -53,6 +55,7 @@ class BluetoothConnectionManager(
// Simplified connection tracking - reduced memory footprint // Simplified connection tracking - reduced memory footprint
private val connectedDevices = ConcurrentHashMap<String, DeviceConnection>() private val connectedDevices = ConcurrentHashMap<String, DeviceConnection>()
private val subscribedDevices = CopyOnWriteArrayList<BluetoothDevice>() private val subscribedDevices = CopyOnWriteArrayList<BluetoothDevice>()
public val addressPeerMap = ConcurrentHashMap<String, String>()
// Connection attempt tracking with automatic cleanup // Connection attempt tracking with automatic cleanup
private val pendingConnections = ConcurrentHashMap<String, ConnectionAttempt>() private val pendingConnections = ConcurrentHashMap<String, ConnectionAttempt>()
@@ -199,47 +202,102 @@ class BluetoothConnectionManager(
powerManager.setAppBackgroundState(inBackground) 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 * Broadcast packet to connected devices with connection limit enforcement
*/ */
fun broadcastPacket(packet: BitchatPacket) { fun broadcastPacket(routed: RoutedPacket) {
val packet = routed.packet
if (!isActive) return if (!isActive) return
val data = packet.toBinaryData() ?: 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") 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) // Send to server connections (devices connected to our GATT server)
subscribedDevices.forEach { device -> subscribedDevices.forEach { device ->
try { if (device.address == routed.relayAddress) {
characteristic?.let { char -> Log.d(TAG, "Skipping broadcast back to relayer: ${device.address}")
char.value = data return@forEach
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 (addressPeerMap[device.address] == senderID) {
Log.d(TAG, "Skipping broadcast back to sender: ${device.address}")
return@forEach
}
notifyDevice(device, data)
} }
// Send to client connections // Send to client connections
connectedDevices.values.forEach { deviceConn -> connectedDevices.values.forEach { deviceConn ->
if (deviceConn.isClient && deviceConn.gatt != null && deviceConn.characteristic != null) { if (deviceConn.isClient && deviceConn.gatt != null && deviceConn.characteristic != null) {
try { if (deviceConn.device.address == routed.relayAddress) {
deviceConn.characteristic.value = data Log.d(TAG, "Skipping broadcast back to relayer: ${deviceConn.device.address}")
deviceConn.gatt.writeCharacteristic(deviceConn.characteristic) return@forEach
} 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 (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) { private fun cleanupDeviceConnection(deviceAddress: String) {
connectedDevices.remove(deviceAddress)?.let { deviceConn -> connectedDevices.remove(deviceAddress)?.let { deviceConn ->
subscribedDevices.removeAll { it.address == deviceAddress } subscribedDevices.removeAll { it.address == deviceAddress }
addressPeerMap.remove(deviceAddress)
} }
// CRITICAL FIX: Always remove from pending connections when cleaning up // CRITICAL FIX: Always remove from pending connections when cleaning up
// This prevents failed connections from blocking future attempts // This prevents failed connections from blocking future attempts
@@ -935,6 +994,7 @@ class BluetoothConnectionManager(
private fun clearAllConnections() { private fun clearAllConnections() {
connectedDevices.clear() connectedDevices.clear()
subscribedDevices.clear() subscribedDevices.clear()
addressPeerMap.clear()
pendingConnections.clear() pendingConnections.clear()
} }
} }
@@ -5,6 +5,7 @@ import android.util.Log
import com.bitchat.android.crypto.EncryptionService import com.bitchat.android.crypto.EncryptionService
import com.bitchat.android.crypto.MessagePadding import com.bitchat.android.crypto.MessagePadding
import com.bitchat.android.model.BitchatMessage import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.model.RoutedPacket
import com.bitchat.android.model.DeliveryAck import com.bitchat.android.model.DeliveryAck
import com.bitchat.android.model.ReadReceipt import com.bitchat.android.model.ReadReceipt
import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.BitchatPacket
@@ -101,10 +102,14 @@ class BluetoothMeshService(private val context: Context) {
// SecurityManager delegate for key exchange notifications // SecurityManager delegate for key exchange notifications
securityManager.delegate = object : SecurityManagerDelegate { 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 // Notify delegate about key exchange completion so it can register peer fingerprint
delegate?.registerPeerPublicKey(peerID, peerPublicKeyData) delegate?.registerPeerPublicKey(peerID, peerPublicKeyData)
receivedAddress?.let { address ->
connectionManager.addressPeerMap[address] = peerID
}
// Send announcement and cached messages after key exchange // Send announcement and cached messages after key exchange
serviceScope.launch { serviceScope.launch {
delay(100) delay(100)
@@ -127,7 +132,7 @@ class BluetoothMeshService(private val context: Context) {
} }
override fun sendPacket(packet: BitchatPacket) { override fun sendPacket(packet: BitchatPacket) {
connectionManager.broadcastPacket(packet) connectionManager.broadcastPacket(RoutedPacket(packet))
} }
} }
@@ -160,11 +165,11 @@ class BluetoothMeshService(private val context: Context) {
// Packet operations // Packet operations
override fun sendPacket(packet: BitchatPacket) { override fun sendPacket(packet: BitchatPacket) {
connectionManager.broadcastPacket(packet) connectionManager.broadcastPacket(RoutedPacket(packet))
} }
override fun relayPacket(packet: BitchatPacket) { override fun relayPacket(routed: RoutedPacket) {
connectionManager.broadcastPacket(packet) connectionManager.broadcastPacket(routed)
} }
override fun getBroadcastRecipient(): ByteArray { override fun getBroadcastRecipient(): ByteArray {
@@ -221,32 +226,32 @@ class BluetoothMeshService(private val context: Context) {
peerManager.updatePeerLastSeen(peerID) peerManager.updatePeerLastSeen(peerID)
} }
override fun handleKeyExchange(packet: BitchatPacket, peerID: String): Boolean { override fun handleKeyExchange(routed: RoutedPacket): Boolean {
return runBlocking { securityManager.handleKeyExchange(packet, peerID) } return runBlocking { securityManager.handleKeyExchange(routed) }
} }
override fun handleAnnounce(packet: BitchatPacket, peerID: String) { override fun handleAnnounce(routed: RoutedPacket) {
serviceScope.launch { messageHandler.handleAnnounce(packet, peerID) } serviceScope.launch { messageHandler.handleAnnounce(routed) }
} }
override fun handleMessage(packet: BitchatPacket, peerID: String) { override fun handleMessage(routed: RoutedPacket) {
serviceScope.launch { messageHandler.handleMessage(packet, peerID) } serviceScope.launch { messageHandler.handleMessage(routed) }
} }
override fun handleLeave(packet: BitchatPacket, peerID: String) { override fun handleLeave(routed: RoutedPacket) {
serviceScope.launch { messageHandler.handleLeave(packet, peerID) } serviceScope.launch { messageHandler.handleLeave(routed) }
} }
override fun handleFragment(packet: BitchatPacket): BitchatPacket? { override fun handleFragment(packet: BitchatPacket): BitchatPacket? {
return fragmentManager.handleFragment(packet) return fragmentManager.handleFragment(packet)
} }
override fun handleDeliveryAck(packet: BitchatPacket, peerID: String) { override fun handleDeliveryAck(routed: RoutedPacket) {
serviceScope.launch { messageHandler.handleDeliveryAck(packet, peerID) } serviceScope.launch { messageHandler.handleDeliveryAck(routed) }
} }
override fun handleReadReceipt(packet: BitchatPacket, peerID: String) { override fun handleReadReceipt(routed: RoutedPacket) {
serviceScope.launch { messageHandler.handleReadReceipt(packet, peerID) } serviceScope.launch { messageHandler.handleReadReceipt(routed) }
} }
override fun sendAnnouncementToPeer(peerID: String) { override fun sendAnnouncementToPeer(peerID: String) {
@@ -257,15 +262,15 @@ class BluetoothMeshService(private val context: Context) {
storeForwardManager.sendCachedMessages(peerID) storeForwardManager.sendCachedMessages(peerID)
} }
override fun relayPacket(packet: BitchatPacket) { override fun relayPacket(routed: RoutedPacket) {
connectionManager.broadcastPacket(packet) connectionManager.broadcastPacket(routed)
} }
} }
// BluetoothConnectionManager delegates // BluetoothConnectionManager delegates
connectionManager.delegate = object : BluetoothConnectionManagerDelegate { connectionManager.delegate = object : BluetoothConnectionManagerDelegate {
override fun onPacketReceived(packet: BitchatPacket, peerID: String, device: android.bluetooth.BluetoothDevice?) { 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) { 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 // Send with random delay and retry for reliability
// delay(Random.nextLong(50, 500)) // 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 // Send with delay
delay(Random.nextLong(50, 500)) delay(Random.nextLong(50, 500))
connectionManager.broadcastPacket(packet) connectionManager.broadcastPacket(RoutedPacket(packet))
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -451,13 +456,13 @@ class BluetoothMeshService(private val context: Context) {
// Send multiple times for reliability // Send multiple times for reliability
delay(Random.nextLong(0, 500)) delay(Random.nextLong(0, 500))
connectionManager.broadcastPacket(announcePacket) connectionManager.broadcastPacket(RoutedPacket(announcePacket))
delay(500 + Random.nextLong(0, 500)) delay(500 + Random.nextLong(0, 500))
connectionManager.broadcastPacket(announcePacket) connectionManager.broadcastPacket(RoutedPacket(announcePacket))
delay(1000 + Random.nextLong(0, 500)) 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() payload = nickname.toByteArray()
) )
connectionManager.broadcastPacket(packet) connectionManager.broadcastPacket(RoutedPacket(packet))
peerManager.markPeerAsAnnouncedTo(peerID) peerManager.markPeerAsAnnouncedTo(peerID)
} }
@@ -491,7 +496,7 @@ class BluetoothMeshService(private val context: Context) {
payload = publicKeyData payload = publicKeyData
) )
connectionManager.broadcastPacket(packet) connectionManager.broadcastPacket(RoutedPacket(packet))
Log.d(TAG, "Sent key exchange") Log.d(TAG, "Sent key exchange")
} }
@@ -507,7 +512,7 @@ class BluetoothMeshService(private val context: Context) {
payload = nickname.toByteArray() payload = nickname.toByteArray()
) )
connectionManager.broadcastPacket(packet) connectionManager.broadcastPacket(RoutedPacket(packet))
} }
/** /**
@@ -5,6 +5,7 @@ import com.bitchat.android.crypto.MessagePadding
import com.bitchat.android.model.BitchatMessage import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.model.DeliveryAck import com.bitchat.android.model.DeliveryAck
import com.bitchat.android.model.ReadReceipt import com.bitchat.android.model.ReadReceipt
import com.bitchat.android.model.RoutedPacket
import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.BitchatPacket
import com.bitchat.android.protocol.MessageType import com.bitchat.android.protocol.MessageType
import kotlinx.coroutines.* import kotlinx.coroutines.*
@@ -30,7 +31,10 @@ class MessageHandler(private val myPeerID: String) {
/** /**
* Handle announce message * 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 if (peerID == myPeerID) return false
val nickname = String(packet.payload, Charsets.UTF_8) val nickname = String(packet.payload, Charsets.UTF_8)
@@ -43,7 +47,7 @@ class MessageHandler(private val myPeerID: String) {
if (packet.ttl > 1u) { if (packet.ttl > 1u) {
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
delay(Random.nextLong(100, 300)) delay(Random.nextLong(100, 300))
delegate?.relayPacket(relayPacket) delegate?.relayPacket(RoutedPacket(relayPacket, peerID, routed.relayAddress))
} }
return isFirstAnnounce return isFirstAnnounce
@@ -52,27 +56,31 @@ class MessageHandler(private val myPeerID: String) {
/** /**
* Handle broadcast or private message * 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 if (peerID == myPeerID) return
val recipientID = packet.recipientID?.takeIf { !it.contentEquals(delegate?.getBroadcastRecipient()) } val recipientID = packet.recipientID?.takeIf { !it.contentEquals(delegate?.getBroadcastRecipient()) }
if (recipientID == null) { if (recipientID == null) {
// BROADCAST MESSAGE // BROADCAST MESSAGE
handleBroadcastMessage(packet, peerID) handleBroadcastMessage(routed)
} else if (String(recipientID).replace("\u0000", "") == myPeerID) { } else if (String(recipientID).replace("\u0000", "") == myPeerID) {
// PRIVATE MESSAGE FOR US // PRIVATE MESSAGE FOR US
handlePrivateMessage(packet, peerID) handlePrivateMessage(packet, peerID)
} else if (packet.ttl > 0u) { } else if (packet.ttl > 0u) {
// RELAY MESSAGE // RELAY MESSAGE
relayMessage(packet) relayMessage(routed)
} }
} }
/** /**
* Handle broadcast message * 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 { try {
// Parse message // Parse message
val message = BitchatMessage.fromBinaryPayload(packet.payload) val message = BitchatMessage.fromBinaryPayload(packet.payload)
@@ -104,7 +112,7 @@ class MessageHandler(private val myPeerID: String) {
} }
// Relay broadcast messages // Relay broadcast messages
relayMessage(packet) relayMessage(routed)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Failed to process broadcast message: ${e.message}") Log.e(TAG, "Failed to process broadcast message: ${e.message}")
@@ -162,7 +170,9 @@ class MessageHandler(private val myPeerID: String) {
/** /**
* Handle leave message * 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) val content = String(packet.payload, Charsets.UTF_8)
if (content.startsWith("#")) { if (content.startsWith("#")) {
@@ -180,14 +190,16 @@ class MessageHandler(private val myPeerID: String) {
// Relay if TTL > 0 // Relay if TTL > 0
if (packet.ttl > 1u) { if (packet.ttl > 1u) {
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
delegate?.relayPacket(relayPacket) delegate?.relayPacket(routed.copy(packet = relayPacket))
} }
} }
/** /**
* Handle delivery acknowledgment * 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) { if (packet.recipientID != null && String(packet.recipientID).replace("\u0000", "") == myPeerID) {
try { try {
val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID) val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID)
@@ -200,17 +212,19 @@ class MessageHandler(private val myPeerID: String) {
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Failed to decrypt delivery ACK: ${e.message}") 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 // Relay
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
delegate?.relayPacket(relayPacket) delegate?.relayPacket(routed.copy(packet = relayPacket))
} }
} }
/** /**
* Handle read receipt * 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) { if (packet.recipientID != null && String(packet.recipientID).replace("\u0000", "") == myPeerID) {
try { try {
val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID) val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID)
@@ -223,17 +237,18 @@ class MessageHandler(private val myPeerID: String) {
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Failed to decrypt read receipt: ${e.message}") 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 // Relay
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) 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) * 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 if (packet.ttl == 0u.toUByte()) return
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
@@ -253,7 +268,7 @@ class MessageHandler(private val myPeerID: String) {
if (shouldRelay) { if (shouldRelay) {
val delay = Random.nextLong(50, 500) // Random delay like iOS val delay = Random.nextLong(50, 500) // Random delay like iOS
delay(delay) delay(delay)
delegate?.relayPacket(relayPacket) delegate?.relayPacket(routed.copy(packet = relayPacket))
} }
} }
@@ -326,7 +341,7 @@ interface MessageHandlerDelegate {
// Packet operations // Packet operations
fun sendPacket(packet: BitchatPacket) fun sendPacket(packet: BitchatPacket)
fun relayPacket(packet: BitchatPacket) fun relayPacket(routed: RoutedPacket)
fun getBroadcastRecipient(): ByteArray fun getBroadcastRecipient(): ByteArray
// Cryptographic operations // Cryptographic operations
@@ -3,6 +3,7 @@ package com.bitchat.android.mesh
import android.util.Log import android.util.Log
import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.BitchatPacket
import com.bitchat.android.protocol.MessageType import com.bitchat.android.protocol.MessageType
import com.bitchat.android.model.RoutedPacket
import kotlinx.coroutines.* import kotlinx.coroutines.*
/** /**
@@ -24,16 +25,19 @@ class PacketProcessor(private val myPeerID: String) {
/** /**
* Process received packet - main entry point for all incoming packets * Process received packet - main entry point for all incoming packets
*/ */
fun processPacket(packet: BitchatPacket, peerID: String) { fun processPacket(routed: RoutedPacket) {
processorScope.launch { processorScope.launch {
handleReceivedPacket(packet, peerID) handleReceivedPacket(routed)
} }
} }
/** /**
* Handle received packet - core protocol logic (exact same as iOS) * 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 // Basic validation and security checks
if (!delegate?.validatePacketSecurity(packet, peerID)!!) { if (!delegate?.validatePacketSecurity(packet, peerID)!!) {
Log.d(TAG, "Packet failed security validation from $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) // Process based on message type (exact same logic as iOS)
when (MessageType.fromValue(packet.type)) { when (MessageType.fromValue(packet.type)) {
MessageType.KEY_EXCHANGE -> handleKeyExchange(packet, peerID) MessageType.KEY_EXCHANGE -> handleKeyExchange(routed)
MessageType.ANNOUNCE -> handleAnnounce(packet, peerID) MessageType.ANNOUNCE -> handleAnnounce(routed)
MessageType.MESSAGE -> handleMessage(packet, peerID) MessageType.MESSAGE -> handleMessage(routed)
MessageType.LEAVE -> handleLeave(packet, peerID) MessageType.LEAVE -> handleLeave(routed)
MessageType.FRAGMENT_START, MessageType.FRAGMENT_START,
MessageType.FRAGMENT_CONTINUE, MessageType.FRAGMENT_CONTINUE,
MessageType.FRAGMENT_END -> handleFragment(packet, peerID) MessageType.FRAGMENT_END -> handleFragment(routed)
MessageType.DELIVERY_ACK -> handleDeliveryAck(packet, peerID) MessageType.DELIVERY_ACK -> handleDeliveryAck(routed)
MessageType.READ_RECEIPT -> handleReadReceipt(packet, peerID) MessageType.READ_RECEIPT -> handleReadReceipt(routed)
else -> { else -> {
Log.w(TAG, "Unknown message type: ${packet.type}") Log.w(TAG, "Unknown message type: ${packet.type}")
} }
@@ -65,10 +69,11 @@ class PacketProcessor(private val myPeerID: String) {
/** /**
* Handle key exchange message * 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") Log.d(TAG, "Processing key exchange from $peerID")
val success = delegate?.handleKeyExchange(packet, peerID) ?: false val success = delegate?.handleKeyExchange(routed) ?: false
if (success) { if (success) {
// Key exchange successful, send announce and cached messages // Key exchange successful, send announce and cached messages
@@ -83,60 +88,60 @@ class PacketProcessor(private val myPeerID: String) {
/** /**
* Handle announce message * Handle announce message
*/ */
private suspend fun handleAnnounce(packet: BitchatPacket, peerID: String) { private suspend fun handleAnnounce(routed: RoutedPacket) {
Log.d(TAG, "Processing announce from $peerID") Log.d(TAG, "Processing announce from ${routed.peerID}")
delegate?.handleAnnounce(packet, peerID) delegate?.handleAnnounce(routed)
} }
/** /**
* Handle regular message * Handle regular message
*/ */
private suspend fun handleMessage(packet: BitchatPacket, peerID: String) { private suspend fun handleMessage(routed: RoutedPacket) {
Log.d(TAG, "Processing message from $peerID") Log.d(TAG, "Processing message from ${routed.peerID}")
delegate?.handleMessage(packet, peerID) delegate?.handleMessage(routed)
} }
/** /**
* Handle leave message * Handle leave message
*/ */
private suspend fun handleLeave(packet: BitchatPacket, peerID: String) { private suspend fun handleLeave(routed: RoutedPacket) {
Log.d(TAG, "Processing leave from $peerID") Log.d(TAG, "Processing leave from ${routed.peerID}")
delegate?.handleLeave(packet, peerID) delegate?.handleLeave(routed)
} }
/** /**
* Handle message fragments * Handle message fragments
*/ */
private suspend fun handleFragment(packet: BitchatPacket, peerID: String) { private suspend fun handleFragment(routed: RoutedPacket) {
Log.d(TAG, "Processing fragment from $peerID") Log.d(TAG, "Processing fragment from ${routed.peerID}")
val reassembledPacket = delegate?.handleFragment(packet) val reassembledPacket = delegate?.handleFragment(routed.packet)
if (reassembledPacket != null) { if (reassembledPacket != null) {
Log.d(TAG, "Fragment reassembled, processing complete message") Log.d(TAG, "Fragment reassembled, processing complete message")
handleReceivedPacket(reassembledPacket, peerID) handleReceivedPacket(RoutedPacket(reassembledPacket, routed.peerID, routed.relayAddress))
} }
// Relay fragment regardless of reassembly // Relay fragment regardless of reassembly
if (packet.ttl > 0u) { if (routed.packet.ttl > 0u) {
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) val relayPacket = routed.packet.copy(ttl = (routed.packet.ttl - 1u).toUByte())
delegate?.relayPacket(relayPacket) delegate?.relayPacket(RoutedPacket(relayPacket, routed.peerID, routed.relayAddress))
} }
} }
/** /**
* Handle delivery acknowledgment * Handle delivery acknowledgment
*/ */
private suspend fun handleDeliveryAck(packet: BitchatPacket, peerID: String) { private suspend fun handleDeliveryAck(routed: RoutedPacket) {
Log.d(TAG, "Processing delivery ACK from $peerID") Log.d(TAG, "Processing delivery ACK from ${routed.peerID}")
delegate?.handleDeliveryAck(packet, peerID) delegate?.handleDeliveryAck(routed)
} }
/** /**
* Handle read receipt * Handle read receipt
*/ */
private suspend fun handleReadReceipt(packet: BitchatPacket, peerID: String) { private suspend fun handleReadReceipt(routed: RoutedPacket) {
Log.d(TAG, "Processing read receipt from $peerID") Log.d(TAG, "Processing read receipt from ${routed.peerID}")
delegate?.handleReadReceipt(packet, peerID) delegate?.handleReadReceipt(routed)
} }
/** /**
@@ -169,16 +174,16 @@ interface PacketProcessorDelegate {
fun updatePeerLastSeen(peerID: String) fun updatePeerLastSeen(peerID: String)
// Message type handlers // Message type handlers
fun handleKeyExchange(packet: BitchatPacket, peerID: String): Boolean fun handleKeyExchange(routed: RoutedPacket): Boolean
fun handleAnnounce(packet: BitchatPacket, peerID: String) fun handleAnnounce(routed: RoutedPacket)
fun handleMessage(packet: BitchatPacket, peerID: String) fun handleMessage(routed: RoutedPacket)
fun handleLeave(packet: BitchatPacket, peerID: String) fun handleLeave(routed: RoutedPacket)
fun handleFragment(packet: BitchatPacket): BitchatPacket? fun handleFragment(packet: BitchatPacket): BitchatPacket?
fun handleDeliveryAck(packet: BitchatPacket, peerID: String) fun handleDeliveryAck(routed: RoutedPacket)
fun handleReadReceipt(packet: BitchatPacket, peerID: String) fun handleReadReceipt(routed: RoutedPacket)
// Communication // Communication
fun sendAnnouncementToPeer(peerID: String) fun sendAnnouncementToPeer(peerID: String)
fun sendCachedMessages(peerID: String) fun sendCachedMessages(peerID: String)
fun relayPacket(packet: BitchatPacket) fun relayPacket(routed: RoutedPacket)
} }
@@ -4,6 +4,7 @@ import android.util.Log
import com.bitchat.android.crypto.EncryptionService import com.bitchat.android.crypto.EncryptionService
import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.BitchatPacket
import com.bitchat.android.protocol.MessageType import com.bitchat.android.protocol.MessageType
import com.bitchat.android.model.RoutedPacket
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.* import java.util.*
import kotlin.collections.mutableSetOf import kotlin.collections.mutableSetOf
@@ -88,7 +89,10 @@ class SecurityManager(private val encryptionService: EncryptionService, private
/** /**
* Handle key exchange packet * 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 (peerID == myPeerID) return false
if (packet.payload.isEmpty()) { if (packet.payload.isEmpty()) {
@@ -113,7 +117,7 @@ class SecurityManager(private val encryptionService: EncryptionService, private
Log.d(TAG, "Successfully processed key exchange from $peerID") Log.d(TAG, "Successfully processed key exchange from $peerID")
// Notify delegate // Notify delegate
delegate?.onKeyExchangeCompleted(peerID, packet.payload) delegate?.onKeyExchangeCompleted(peerID, packet.payload, routed.relayAddress)
return true return true
@@ -315,5 +319,5 @@ class SecurityManager(private val encryptionService: EncryptionService, private
* Delegate interface for security manager callbacks * Delegate interface for security manager callbacks
*/ */
interface SecurityManagerDelegate { interface SecurityManagerDelegate {
fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray) fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray, receivedAddress: String?)
} }
@@ -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)
)