Merge branch 'main' into fix/android-ios-fragmentation-compatibility

This commit is contained in:
callebtc
2025-07-13 21:25:11 +02:00
committed by GitHub
68 changed files with 2102 additions and 969 deletions
@@ -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
@@ -54,6 +56,7 @@ class BluetoothConnectionManager(
// Simplified connection tracking - reduced memory footprint
private val connectedDevices = ConcurrentHashMap<String, DeviceConnection>()
private val subscribedDevices = CopyOnWriteArrayList<BluetoothDevice>()
public val addressPeerMap = ConcurrentHashMap<String, String>()
// Connection attempt tracking with automatic cleanup
private val pendingConnections = ConcurrentHashMap<String, ConnectionAttempt>()
@@ -129,15 +132,17 @@ class BluetoothConnectionManager(
try {
isActive = true
// Setup GATT server first
setupGattServer()
// Start power manager and services
connectionScope.launch {
powerManager.start()
delay(500) // Ensure GATT server is ready
delay(300) // Brief delay to ensure GATT server is ready
startAdvertising()
delay(200)
delay(100)
if (powerManager.shouldUseDutyCycle()) {
Log.i(TAG, "Using power-aware duty cycling")
@@ -147,13 +152,14 @@ class BluetoothConnectionManager(
startPeriodicCleanup()
Log.i(TAG, "Power-optimized Bluetooth services started successfully (CLIENT ONLY)")
Log.i(TAG, "Bluetooth services started successfully")
}
return true
} catch (e: Exception) {
Log.e(TAG, "Failed to start Bluetooth services: ${e.message}")
isActive = false
return false
}
}
@@ -197,11 +203,50 @@ class BluetoothConnectionManager(
powerManager.setAppBackgroundState(inBackground)
}
// Function to send data to a single device (server side)
private fun notifyDevice(device: BluetoothDevice, data: ByteArray): Boolean {
return try {
characteristic?.let { char ->
char.value = data
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.address)
}
false
}
}
// Function to send data to a single device (client side)
private fun writeToDeviceConn(deviceConn: DeviceConnection, data: ByteArray): Boolean {
return try {
deviceConn.characteristic?.let { char ->
char.value = data
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
}
}
/**
* Broadcast packet to connected devices with connection limit enforcement
* Automatically fragments large packets to fit within BLE MTU limits
*/
fun broadcastPacket(packet: BitchatPacket) {
fun broadcastPacket(routed: RoutedPacket) {
val packet = routed.packet
if (!isActive) return
// Check if we need to fragment
@@ -229,40 +274,61 @@ class BluetoothConnectionManager(
*/
private fun sendSinglePacket(packet: BitchatPacket) {
val data = packet.toBinaryData() ?: return
Log.d(TAG, "Sending packet type ${packet.type} (${data.size} bytes) to ${subscribedDevices.size} server + ${connectedDevices.size} client connections")
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}")
if (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}")
if (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)
}
}
}
@@ -277,7 +343,8 @@ class BluetoothConnectionManager(
*/
fun getDebugInfo(): String {
return buildString {
appendLine("=== Power-Optimized Bluetooth Connection Manager ===")
appendLine("=== Bluetooth Connection Manager ===")
appendLine("Bluetooth MAC Address: ${bluetoothAdapter?.address}")
appendLine("Active: $isActive")
appendLine("Bluetooth Enabled: ${bluetoothAdapter?.isEnabled}")
appendLine("Has Permissions: ${hasBluetoothPermissions()}")
@@ -381,6 +448,12 @@ class BluetoothConnectionManager(
val serverCallback = object : BluetoothGattServerCallback() {
override fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
// Guard against callbacks after service shutdown
if (!isActive) {
Log.d(TAG, "Server: Ignoring connection state change after shutdown")
return
}
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
Log.d(TAG, "Server: Device connected ${device.address}")
@@ -397,6 +470,20 @@ class BluetoothConnectionManager(
}
}
override fun onServiceAdded(status: Int, service: BluetoothGattService) {
// Guard against callbacks after service shutdown
if (!isActive) {
Log.d(TAG, "Server: Ignoring service added callback after shutdown")
return
}
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "Server: Service added successfully: ${service.uuid}")
} else {
Log.e(TAG, "Server: Failed to add service: ${service.uuid}, status: $status")
}
}
override fun onCharacteristicWriteRequest(
device: BluetoothDevice,
requestId: Int,
@@ -406,11 +493,22 @@ class BluetoothConnectionManager(
offset: Int,
value: ByteArray
) {
// Guard against callbacks after service shutdown
if (!isActive) {
Log.d(TAG, "Server: Ignoring characteristic write after shutdown")
return
}
if (characteristic.uuid == CHARACTERISTIC_UUID) {
Log.d(TAG, "Server: Received packet from ${device.address}, size: ${value.size} bytes")
val packet = BitchatPacket.fromBinaryData(value)
if (packet != null) {
val peerID = String(packet.senderID).replace("\u0000", "")
Log.d(TAG, "Server: Parsed packet type ${packet.type} from $peerID")
delegate?.onPacketReceived(packet, peerID, device)
} else {
Log.w(TAG, "Server: Failed to parse packet from ${device.address}, size: ${value.size} bytes")
Log.w(TAG, "Server: Packet data: ${value.joinToString(" ") { "%02x".format(it) }}")
}
if (responseNeeded) {
@@ -428,13 +526,21 @@ class BluetoothConnectionManager(
offset: Int,
value: ByteArray
) {
// Guard against callbacks after service shutdown
if (!isActive) {
Log.d(TAG, "Server: Ignoring descriptor write after shutdown")
return
}
if (BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE.contentEquals(value)) {
Log.d(TAG, "Device ${device.address} subscribed to notifications")
subscribedDevices.add(device)
connectionScope.launch {
delay(100)
delegate?.onDeviceConnected(device)
if (isActive) { // Check if still active
delegate?.onDeviceConnected(device)
}
}
}
@@ -444,9 +550,25 @@ class BluetoothConnectionManager(
}
}
// Clean up existing server
gattServer?.close()
// Proper cleanup sequencing to prevent race conditions
gattServer?.let { server ->
Log.d(TAG, "Cleaning up existing GATT server")
try {
server.close()
} catch (e: Exception) {
Log.w(TAG, "Error closing existing GATT server: ${e.message}")
}
}
// Small delay to ensure cleanup is complete
Thread.sleep(100)
if (!isActive) {
Log.d(TAG, "Service inactive, skipping GATT server creation")
return
}
// Create new server
gattServer = bluetoothManager.openGattServer(context, serverCallback)
// Create characteristic with notification support
@@ -554,8 +676,7 @@ class BluetoothConnectionManager(
// DEBUG: Log ALL scan results first
val device = result.device
val rssi = result.rssi
val scanRecord = result.scanRecord
Log.d(TAG, "Scan result: device: ${device.address}, Name: '${device.name}', RSSI: $rssi")
// Log.d(TAG, "Scan result: device: ${device.address}, Name: '${device.name}', RSSI: $rssi")
handleScanResult(result)
}
@@ -642,9 +763,7 @@ class BluetoothConnectionManager(
} else {
null
}
Log.d(TAG, "Processing bitchat device: $deviceAddress, name: '$deviceName', peerID: $extractedPeerID, RSSI: $rssi")
// Power-aware RSSI filtering
if (rssi < powerManager.getRSSIThreshold()) {
Log.d(TAG, "Skipping device $deviceAddress due to weak signal: $rssi < ${powerManager.getRSSIThreshold()}")
@@ -654,7 +773,7 @@ class BluetoothConnectionManager(
// CRITICAL FIX: Prevent multiple simultaneous connections to same device
// Check if already connected OR already attempting to connect
if (connectedDevices.containsKey(deviceAddress)) {
Log.d(TAG, "Device $deviceAddress already connected, skipping")
// Log.d(TAG, "Device $deviceAddress already connected, skipping")
return
}
@@ -685,12 +804,6 @@ class BluetoothConnectionManager(
val attempts = (currentAttempt?.attempts ?: 0) + 1
pendingConnections[deviceAddress] = ConnectionAttempt(attempts)
if (extractedPeerID != null) {
Log.i(TAG, "Initiating connection to peer $extractedPeerID at $deviceAddress (RSSI: $rssi, attempt: $attempts)")
} else {
Log.i(TAG, "Initiating connection to device with bitchat service at $deviceAddress (RSSI: $rssi, attempt: $attempts)")
}
// Start connection immediately while holding lock
connectToDevice(device, rssi)
}
@@ -701,6 +814,7 @@ class BluetoothConnectionManager(
if (!hasBluetoothPermissions()) return
val deviceAddress = device.address
Log.d(TAG, "Connecting to bitchat device: $deviceAddress")
val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
@@ -763,9 +877,7 @@ class BluetoothConnectionManager(
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
Log.d(TAG, "Client: Service discovery completed for $deviceAddress with status: $status")
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
val service = gatt.getService(SERVICE_UUID)
if (service != null) {
@@ -808,25 +920,30 @@ class BluetoothConnectionManager(
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
val value = characteristic.value
Log.d(TAG, "Client: Received packet from ${gatt.device.address}, size: ${value.size} bytes")
val packet = BitchatPacket.fromBinaryData(value)
if (packet != null) {
val peerID = String(packet.senderID).replace("\u0000", "")
Log.d(TAG, "Client: Parsed packet type ${packet.type} from $peerID")
delegate?.onPacketReceived(packet, peerID, gatt.device)
} else {
Log.w(TAG, "Client: Failed to parse packet from ${gatt.device.address}, size: ${value.size} bytes")
Log.w(TAG, "Client: Packet data: ${value.joinToString(" ") { "%02x".format(it) }}")
}
}
}
try {
Log.d(TAG, "Attempting GATT connection to $deviceAddress with autoConnect=false")
Log.d(TAG, "Client: Attempting GATT connection to $deviceAddress with autoConnect=false")
val gatt = device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
if (gatt == null) {
Log.e(TAG, "connectGatt returned null for $deviceAddress")
pendingConnections.remove(deviceAddress)
} else {
Log.d(TAG, "GATT connection initiated successfully for $deviceAddress")
Log.d(TAG, "Client: GATT connection initiated successfully for $deviceAddress")
}
} catch (e: Exception) {
Log.e(TAG, "Exception connecting to $deviceAddress: ${e.message}")
Log.e(TAG, "Client: Exception connecting to $deviceAddress: ${e.message}")
pendingConnections.remove(deviceAddress)
}
}
@@ -879,6 +996,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
@@ -907,6 +1025,7 @@ class BluetoothConnectionManager(
private fun clearAllConnections() {
connectedDevices.clear()
subscribedDevices.clear()
addressPeerMap.clear()
pendingConnections.clear()
}
}
@@ -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
@@ -47,6 +48,9 @@ class BluetoothMeshService(private val context: Context) {
internal val connectionManager = BluetoothConnectionManager(context, myPeerID, fragmentManager) // Made internal for access
private val packetProcessor = PacketProcessor(myPeerID)
// Service state management
private var isActive = false
// Delegate for message callbacks (maintains same interface)
var delegate: BluetoothMeshDelegate? = null
@@ -66,10 +70,10 @@ class BluetoothMeshService(private val context: Context) {
while (isActive) {
try {
delay(10000) // 10 seconds
val debugInfo = getDebugStatus()
Log.d(TAG, "=== PERIODIC DEBUG STATUS ===")
Log.d(TAG, debugInfo)
Log.d(TAG, "=== END DEBUG STATUS ===")
if (isActive) { // Double-check before logging
val debugInfo = getDebugStatus()
Log.d(TAG, "=== PERIODIC DEBUG STATUS ===\n$debugInfo\n=== END DEBUG STATUS ===")
}
} catch (e: Exception) {
Log.e(TAG, "Error in periodic debug logging: ${e.message}")
}
@@ -98,7 +102,14 @@ class BluetoothMeshService(private val context: Context) {
// SecurityManager delegate for key exchange notifications
securityManager.delegate = object : SecurityManagerDelegate {
override fun onKeyExchangeCompleted(peerID: String) {
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)
@@ -121,7 +132,7 @@ class BluetoothMeshService(private val context: Context) {
}
override fun sendPacket(packet: BitchatPacket) {
connectionManager.broadcastPacket(packet)
connectionManager.broadcastPacket(RoutedPacket(packet))
}
}
@@ -154,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 {
@@ -215,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) {
@@ -251,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) {
@@ -276,11 +287,16 @@ class BluetoothMeshService(private val context: Context) {
* Start the mesh service
*/
fun startServices() {
// Prevent double starts (defensive programming)
if (isActive) {
Log.w(TAG, "Mesh service already active, ignoring duplicate start request")
return
}
Log.i(TAG, "Starting Bluetooth mesh service with peer ID: $myPeerID")
if (connectionManager.startServices()) {
Log.i(TAG, "Bluetooth services started successfully")
isActive = true
// Send initial announcements after services are ready
serviceScope.launch {
delay(1000)
@@ -295,7 +311,13 @@ class BluetoothMeshService(private val context: Context) {
* Stop all mesh services
*/
fun stopServices() {
if (!isActive) {
Log.w(TAG, "Mesh service not active, ignoring stop request")
return
}
Log.i(TAG, "Stopping Bluetooth mesh service")
isActive = false
// Send leave announcement
sendLeaveAnnouncement()
@@ -351,7 +373,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))
}
}
}
@@ -404,7 +426,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) {
@@ -430,13 +452,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))
}
}
@@ -454,7 +476,7 @@ class BluetoothMeshService(private val context: Context) {
payload = nickname.toByteArray()
)
connectionManager.broadcastPacket(packet)
connectionManager.broadcastPacket(RoutedPacket(packet))
peerManager.markPeerAsAnnouncedTo(peerID)
}
@@ -470,7 +492,7 @@ class BluetoothMeshService(private val context: Context) {
payload = publicKeyData
)
connectionManager.broadcastPacket(packet)
connectionManager.broadcastPacket(RoutedPacket(packet))
Log.d(TAG, "Sent key exchange")
}
@@ -486,7 +508,7 @@ class BluetoothMeshService(private val context: Context) {
payload = nickname.toByteArray()
)
connectionManager.broadcastPacket(packet)
connectionManager.broadcastPacket(RoutedPacket(packet))
}
/**
@@ -547,4 +569,5 @@ interface BluetoothMeshDelegate {
fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String?
fun getNickname(): String?
fun isFavorite(peerID: String): Boolean
fun registerPeerPublicKey(peerID: String, publicKeyData: ByteArray)
}
@@ -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
@@ -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)
}
@@ -26,12 +26,12 @@ class PowerManager(private val context: Context) {
private const val MEDIUM_BATTERY = 50
// Scan duty cycle periods (ms)
private const val SCAN_ON_DURATION_NORMAL = 2000L // 2 seconds on
private const val SCAN_OFF_DURATION_NORMAL = 8000L // 8 seconds off
private const val SCAN_ON_DURATION_POWER_SAVE = 1000L // 1 second on
private const val SCAN_OFF_DURATION_POWER_SAVE = 15000L // 15 seconds off
private const val SCAN_ON_DURATION_ULTRA_LOW = 500L // 0.5 seconds on
private const val SCAN_OFF_DURATION_ULTRA_LOW = 30000L // 30 seconds off
private const val SCAN_ON_DURATION_NORMAL = 8000L // 8 seconds on
private const val SCAN_OFF_DURATION_NORMAL = 2000L // 2 seconds off
private const val SCAN_ON_DURATION_POWER_SAVE = 2000L // 2 seconds on
private const val SCAN_OFF_DURATION_POWER_SAVE = 8000L // 8 seconds off
private const val SCAN_ON_DURATION_ULTRA_LOW = 1000L // 1 second on
private const val SCAN_OFF_DURATION_ULTRA_LOW = 10000L // 10 seconds off
// Connection limits
private const val MAX_CONNECTIONS_NORMAL = 8
@@ -112,38 +112,38 @@ class PowerManager(private val context: Context) {
/**
* Get scan settings optimized for current power mode
*/
fun getScanSettings(): ScanSettings {
// CRITICAL FIX: Set reportDelay to 0 for all modes.
// When using a custom duty cycle, we want scan results delivered immediately,
// not batched. A non-zero report delay can conflict with the scan window,
// causing missed results if the scan stops before the delay is met.
val builder = ScanSettings.Builder()
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
fun getScanSettings(): ScanSettings {
// CRITICAL FIX: Set reportDelay to 0 for all modes.
// When using a custom duty cycle, we want scan results delivered immediately,
// not batched. A non-zero report delay can conflict with the scan window,
// causing missed results if the scan stops before the delay is met.
val builder = ScanSettings.Builder()
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
when (currentMode) {
PowerMode.PERFORMANCE -> builder
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT)
when (currentMode) {
PowerMode.PERFORMANCE -> builder
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT)
PowerMode.BALANCED -> builder
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
PowerMode.BALANCED -> builder
.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
PowerMode.POWER_SAVER -> builder
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
PowerMode.POWER_SAVER -> builder
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
PowerMode.ULTRA_LOW_POWER -> builder
.setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
}
return builder.setReportDelay(0).build()
PowerMode.ULTRA_LOW_POWER -> builder
.setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
}
return builder.setReportDelay(0).build()
}
/**
* Get advertising settings optimized for current power mode
@@ -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)
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)
fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray, receivedAddress: String?)
}