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 1494b45b..9261a555 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -64,7 +64,7 @@ class BluetoothMeshService(private val context: Context) { // Wire up PacketProcessor reference for recursive handling in MessageHandler messageHandler.packetProcessor = packetProcessor -// startPeriodicDebugLogging() + startPeriodicDebugLogging() } /** @@ -491,7 +491,7 @@ class BluetoothMeshService(private val context: Context) { if (encryptedPayload != null) { // Sign - val signature = securityManager.signPacket(encryptedPayload) + // val signature = securityManager.signPacket(encryptedPayload) val packet = BitchatPacket( version = 1u, @@ -500,7 +500,7 @@ class BluetoothMeshService(private val context: Context) { recipientID = hexStringToByteArray(recipientPeerID), timestamp = System.currentTimeMillis().toULong(), payload = encryptedPayload, - signature = signature, + signature = null, ttl = MAX_TTL ) diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt index 504a342f..272fbdf2 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt @@ -8,11 +8,20 @@ import android.util.Log import com.bitchat.android.protocol.SpecialRecipients import com.bitchat.android.model.RoutedPacket import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.actor /** - * Handles packet broadcasting to connected devices + * Handles packet broadcasting to connected devices using actor pattern for serialization + * + * SERIALIZATION FIX: Uses Kotlin coroutine actor to serialize all packet broadcasting + * This prevents race conditions when multiple threads try to broadcast simultaneously */ class BluetoothPacketBroadcaster( private val connectionScope: CoroutineScope, @@ -25,6 +34,33 @@ class BluetoothPacketBroadcaster( private const val CLEANUP_DELAY = 500L } + // Data class to hold broadcast request information + private data class BroadcastRequest( + val routed: RoutedPacket, + val gattServer: BluetoothGattServer?, + val characteristic: BluetoothGattCharacteristic? + ) + + // Actor scope for the broadcaster + private val broadcasterScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + // SERIALIZATION: Actor to serialize all broadcast operations + @OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class) + private val broadcasterActor = broadcasterScope.actor( + capacity = Channel.UNLIMITED + ) { + Log.d(TAG, "🎭 Created packet broadcaster actor") + try { + for (request in channel) { + Log.d(TAG, "Processing broadcast for packet type ${request.routed.packet.type} (serialized)") + broadcastSinglePacketInternal(request.routed, request.gattServer, request.characteristic) + Log.d(TAG, "Completed broadcast for packet type ${request.routed.packet.type}") + } + } finally { + Log.d(TAG, "🎭 Packet broadcaster actor terminated") + } + } + fun broadcastPacket( routed: RoutedPacket, gattServer: BluetoothGattServer?, @@ -54,12 +90,32 @@ class BluetoothPacketBroadcaster( /** - * Broadcast single packet to connected devices with connection limit enforcement + * Public entry point for broadcasting - submits request to actor for serialization */ fun broadcastSinglePacket( routed: RoutedPacket, gattServer: BluetoothGattServer?, characteristic: BluetoothGattCharacteristic? + ) { + // Submit broadcast request to actor for serialized processing + broadcasterScope.launch { + try { + broadcasterActor.send(BroadcastRequest(routed, gattServer, characteristic)) + } catch (e: Exception) { + Log.w(TAG, "Failed to send broadcast request to actor: ${e.message}") + // Fallback to direct processing if actor fails + broadcastSinglePacketInternal(routed, gattServer, characteristic) + } + } + } + + /** + * Internal broadcast implementation - runs in serialized actor context + */ + private suspend fun broadcastSinglePacketInternal( + routed: RoutedPacket, + gattServer: BluetoothGattServer?, + characteristic: BluetoothGattCharacteristic? ) { val packet = routed.packet val data = packet.toBinaryData() ?: return @@ -177,4 +233,31 @@ class BluetoothPacketBroadcaster( false } } + + /** + * Get debug information + */ + fun getDebugInfo(): String { + return buildString { + appendLine("=== Packet Broadcaster Debug Info ===") + appendLine("Broadcaster Scope Active: ${broadcasterScope.isActive}") + appendLine("Actor Channel Closed: ${broadcasterActor.isClosedForSend}") + appendLine("Connection Scope Active: ${connectionScope.isActive}") + } + } + + /** + * Shutdown the broadcaster actor gracefully + */ + fun shutdown() { + Log.d(TAG, "Shutting down BluetoothPacketBroadcaster actor") + + // Close the actor gracefully + broadcasterActor.close() + + // Cancel the broadcaster scope + broadcasterScope.cancel() + + Log.d(TAG, "BluetoothPacketBroadcaster shutdown complete") + } } \ No newline at end of file diff --git a/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt b/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt index 92eadffe..90817dfb 100644 --- a/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt @@ -14,6 +14,7 @@ class FragmentManager { companion object { private const val TAG = "FragmentManager" + private const val FRAGMENT_SIZE_THRESHOLD = 512 // 512 bytes private const val MAX_FRAGMENT_SIZE = 500 // Match iOS/Rust for BLE compatibility (185 byte MTU limit) private const val FRAGMENT_TIMEOUT = 30000L // 30 seconds private const val CLEANUP_INTERVAL = 10000L // 10 seconds @@ -39,7 +40,7 @@ class FragmentManager { fun createFragments(packet: BitchatPacket): List { val data = packet.toBinaryData() ?: return emptyList() - if (data.size <= MAX_FRAGMENT_SIZE) { + if (data.size <= FRAGMENT_SIZE_THRESHOLD) { return listOf(packet) // No fragmentation needed }