Implement Noise XX Handshake Protocol for Direct Messages (#180)

* noise

* works?

* noise

* temporary

* better

* wip: use subnet

* better

* barely working

* werk

* subnet

* fix peer ID

* 8 byte peer ID

* wip noise

* wip fixes for noise

* std lib for noise

* noise handshake one step further

* buffers

* use fork

* fix imports

* simplify counter

* remove trash

* hashing

* no prologue

* nice

* wip, use noise encryption

* peer ID hex

* simplify session manager

* heavy logging

* use singleton

* Fix Noise session race condition with elegant per-peer actor serialization

- Use Kotlin coroutine actors for per-peer packet processing
- Each peer gets dedicated actor that processes packets sequentially
- Eliminates race conditions in session management without complex locking
- Single surgical change in PacketProcessor - minimal, maintainable
- Leverages Kotlin's native concurrency primitives

* decrypt correctly

* iniator works now

* clean code and fix signature to null

* better

* no signature in private message

* small fixes

* refactor ack

* refactor but untested

* messages working

* wip ack

* wip fix ack

* more logging

* pending tracker

* keep pending connections on errors

* less logging

* refactor model

* refactor frombinarydata

* idendityannouncement refactor and update to new binary protocol

* fix keys

* refix keys

* dms work

* revert to mainnet

* do not change bluetooth adapter name

* keep code but uncomment

* clean up comments

* cleanup comments
This commit is contained in:
callebtc
2025-07-24 12:01:46 +02:00
committed by GitHub
parent 9e9231353b
commit c3c395832c
58 changed files with 15209 additions and 517 deletions
@@ -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,14 +34,40 @@ 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<BroadcastRequest>(
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?,
characteristic: BluetoothGattCharacteristic?
) {
val packet = routed.packet
val data = packet.toBinaryData() ?: return
// Check if we need to fragment
// Check if we need to fragment
if (fragmentManager != null) {
val fragments = fragmentManager.createFragments(packet)
if (fragments.size > 1) {
@@ -41,7 +76,7 @@ class BluetoothPacketBroadcaster(
fragments.forEach { fragment ->
broadcastSinglePacket(RoutedPacket(fragment), gattServer, characteristic)
// 20ms delay between fragments (matching iOS/Rust)
delay(20)
delay(200)
}
}
return
@@ -54,12 +89,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
@@ -96,7 +151,7 @@ class BluetoothPacketBroadcaster(
val subscribedDevices = connectionTracker.getSubscribedDevices()
val connectedDevices = connectionTracker.getConnectedDevices()
Log.d(TAG, "Broadcasting packet type ${packet.type} to ${subscribedDevices.size} server + ${connectedDevices.size} client connections")
Log.i(TAG, "Broadcasting packet type ${packet.type} to ${subscribedDevices.size} server + ${connectedDevices.size} client connections")
val senderID = String(packet.senderID).replace("\u0000", "")
@@ -177,4 +232,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")
}
}