Files
bitchat-android/debug_noise_xx_test.kt
T
callebtc 75cc4615c7 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
2025-07-20 18:45:21 +02:00

130 lines
5.5 KiB
Kotlin

/**
* 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) }}")
}
}