mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 11:45:19 +00:00
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
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Test to analyze the XX handshake pattern step by step
|
||||
*/
|
||||
|
||||
import com.bitchat.android.noise.southernstorm.protocol.*
|
||||
import java.security.SecureRandom
|
||||
|
||||
fun main() {
|
||||
println("=== Analyzing XX Handshake Pattern ===")
|
||||
|
||||
// Generate test keys like your app would
|
||||
val random = SecureRandom()
|
||||
val initiatorStaticPriv = ByteArray(32)
|
||||
val responderStaticPriv = ByteArray(32)
|
||||
random.nextBytes(initiatorStaticPriv)
|
||||
random.nextBytes(responderStaticPriv)
|
||||
|
||||
// Create DH states to derive public keys
|
||||
val initiatorDH = com.bitchat.android.noise.southernstorm.protocol.Noise.createDH("25519")
|
||||
val responderDH = com.bitchat.android.noise.southernstorm.protocol.Noise.createDH("25519")
|
||||
|
||||
initiatorDH.setPrivateKey(initiatorStaticPriv, 0)
|
||||
responderDH.setPrivateKey(responderStaticPriv, 0)
|
||||
|
||||
val initiatorStaticPub = ByteArray(32)
|
||||
val responderStaticPub = ByteArray(32)
|
||||
initiatorDH.getPublicKey(initiatorStaticPub, 0)
|
||||
responderDH.getPublicKey(responderStaticPub, 0)
|
||||
|
||||
println("Initiator static public: ${initiatorStaticPub.joinToString("") { "%02x".format(it) }}")
|
||||
println("Responder static public: ${responderStaticPub.joinToString("") { "%02x".format(it) }}")
|
||||
|
||||
// Create handshake states
|
||||
val initiator = HandshakeState("Noise_XX_25519_ChaChaPoly_SHA256", HandshakeState.INITIATOR)
|
||||
val responder = HandshakeState("Noise_XX_25519_ChaChaPoly_SHA256", HandshakeState.RESPONDER)
|
||||
|
||||
// Set static keys
|
||||
initiator.getLocalKeyPair()?.setPrivateKey(initiatorStaticPriv, 0)
|
||||
responder.getLocalKeyPair()?.setPrivateKey(responderStaticPriv, 0)
|
||||
|
||||
// Start handshakes
|
||||
initiator.start()
|
||||
responder.start()
|
||||
|
||||
println("\n=== XX Pattern Flow ===")
|
||||
println("Expected: -> e")
|
||||
println(" <- e, ee, s, es")
|
||||
println(" -> s, se")
|
||||
|
||||
// Message 1: -> e
|
||||
println("\n--- Message 1: Initiator -> Responder ---")
|
||||
val msg1Buffer = ByteArray(256)
|
||||
val msg1Len = initiator.writeMessage(msg1Buffer, 0, ByteArray(0), 0, 0)
|
||||
val msg1 = msg1Buffer.copyOf(msg1Len)
|
||||
println("Message 1 length: $msg1Len bytes (expected: 32)")
|
||||
println("Message 1 content: ${msg1.joinToString("") { "%02x".format(it) }}")
|
||||
println("Initiator action after msg1: ${initiator.getAction()}")
|
||||
|
||||
// Responder processes message 1
|
||||
val responderPayload1 = ByteArray(256)
|
||||
val responderPayload1Len = responder.readMessage(msg1, 0, msg1.size, responderPayload1, 0)
|
||||
println("Responder processed msg1, payload len: $responderPayload1Len")
|
||||
println("Responder action after processing msg1: ${responder.getAction()}")
|
||||
|
||||
// Message 2: <- e, ee, s, es
|
||||
println("\n--- Message 2: Responder -> Initiator ---")
|
||||
val msg2Buffer = ByteArray(256)
|
||||
val msg2Len = responder.writeMessage(msg2Buffer, 0, ByteArray(0), 0, 0)
|
||||
val msg2 = msg2Buffer.copyOf(msg2Len)
|
||||
println("Message 2 length: $msg2Len bytes (expected: 80)")
|
||||
println("Message 2 content: ${msg2.joinToString("") { "%02x".format(it) }}")
|
||||
println("Responder action after msg2: ${responder.getAction()}")
|
||||
|
||||
// This is where the initiator should be able to process message 2
|
||||
// Let's see what happens
|
||||
try {
|
||||
val initiatorPayload2 = ByteArray(256)
|
||||
val initiatorPayload2Len = initiator.readMessage(msg2, 0, msg2.size, initiatorPayload2, 0)
|
||||
println("Initiator processed msg2 successfully, payload len: $initiatorPayload2Len")
|
||||
println("Initiator action after processing msg2: ${initiator.getAction()}")
|
||||
|
||||
// Message 3: -> s, se
|
||||
println("\n--- Message 3: Initiator -> Responder ---")
|
||||
val msg3Buffer = ByteArray(256)
|
||||
val msg3Len = initiator.writeMessage(msg3Buffer, 0, ByteArray(0), 0, 0)
|
||||
val msg3 = msg3Buffer.copyOf(msg3Len)
|
||||
println("Message 3 length: $msg3Len bytes (expected: 48)")
|
||||
println("Message 3 content: ${msg3.joinToString("") { "%02x".format(it) }}")
|
||||
println("Initiator action after msg3: ${initiator.getAction()}")
|
||||
|
||||
// Responder processes message 3
|
||||
val responderPayload3 = ByteArray(256)
|
||||
val responderPayload3Len = responder.readMessage(msg3, 0, msg3.size, responderPayload3, 0)
|
||||
println("Responder processed msg3, payload len: $responderPayload3Len")
|
||||
println("Responder action after processing msg3: ${responder.getAction()}")
|
||||
|
||||
println("\n✓ Success: Handshake completed without errors")
|
||||
|
||||
} catch (e: Exception) {
|
||||
println("\n❌ Error during initiator processing message 2:")
|
||||
println("Exception: ${e.javaClass.simpleName}")
|
||||
println("Message: ${e.message}")
|
||||
e.printStackTrace()
|
||||
|
||||
// Let's analyze what went wrong
|
||||
analyzeMessage2Structure(msg2)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
initiatorDH.destroy()
|
||||
responderDH.destroy()
|
||||
initiator.destroy()
|
||||
responder.destroy()
|
||||
}
|
||||
|
||||
fun analyzeMessage2Structure(msg2: ByteArray) {
|
||||
println("\n=== Analyzing Message 2 Structure ===")
|
||||
println("Total length: ${msg2.size}")
|
||||
|
||||
if (msg2.size >= 32) {
|
||||
println("Ephemeral key (bytes 0-31): ${msg2.sliceArray(0..31).joinToString("") { "%02x".format(it) }}")
|
||||
}
|
||||
|
||||
if (msg2.size >= 80) {
|
||||
println("Encrypted static + MAC (bytes 32-79): ${msg2.sliceArray(32..79).joinToString("") { "%02x".format(it) }}")
|
||||
println(" - Encrypted static (bytes 32-63): ${msg2.sliceArray(32..63).joinToString("") { "%02x".format(it) }}")
|
||||
println(" - MAC tag (bytes 64-79): ${msg2.sliceArray(64..79).joinToString("") { "%02x".format(it) }}")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user