diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SasVerificationTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SasVerificationTestHelper.kt index 05031e8c13..35fe349b13 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SasVerificationTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SasVerificationTestHelper.kt @@ -16,56 +16,71 @@ package org.matrix.android.sdk.internal.crypto.verification -import org.amshove.kluent.fail +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.cancellable +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState +import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod +import org.matrix.android.sdk.api.session.crypto.verification.getRequest import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestData class SasVerificationTestHelper(private val testHelper: CommonTestHelper) { - suspend fun requestVerificationAndWaitForReadyState(cryptoTestData: CryptoTestData, supportedMethods: List): String { + suspend fun requestVerificationAndWaitForReadyState( + scope: CoroutineScope, + cryptoTestData: CryptoTestData, supportedMethods: List + ): String { val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession!! val aliceVerificationService = aliceSession.cryptoService().verificationService() val bobVerificationService = bobSession.cryptoService().verificationService() + val bobSeesVerification = CompletableDeferred() + scope.launch(Dispatchers.IO) { + bobVerificationService.requestEventFlow() + .cancellable() + .collect { + val request = it.getRequest() + if (request != null) { + bobSeesVerification.complete(request) + return@collect cancel() + } + } + } + val bobUserId = bobSession.myUserId // Step 1: Alice starts a verification request val transactionId = aliceVerificationService.requestKeyVerificationInDMs( supportedMethods, bobUserId, cryptoTestData.roomId + ).transactionId + + val aliceReady = CompletableDeferred() + scope.launch(Dispatchers.IO) { + aliceVerificationService.requestEventFlow() + .cancellable() + .collect { + val request = it.getRequest() + if (request?.state == EVerificationState.Ready) { + aliceReady.complete(request) + return@collect cancel() + } + } + } + + bobSeesVerification.await() + bobVerificationService.readyPendingVerification( + supportedMethods, + aliceSession.myUserId, + transactionId ) - .transactionId - - testHelper.retryWithBackoff( - onFail = { - fail("bob should see an incoming verification request with id $transactionId") - } - ) { - val incomingRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId) - if (incomingRequest != null) { - bobVerificationService.readyPendingVerification( - supportedMethods, - aliceSession.myUserId, - incomingRequest.transactionId - ) - true - } else { - false - } - } - - // wait for alice to see the ready - testHelper.retryWithBackoff( - onFail = { - fail("Alice request whould be ready $transactionId") - } - ) { - val pendingRequest = aliceVerificationService.getExistingVerificationRequest(bobUserId, transactionId) - pendingRequest?.state == EVerificationState.Ready - } + aliceReady.await() return transactionId } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTest.kt index 19219d5cd0..aacf6b3f0e 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTest.kt @@ -17,6 +17,13 @@ package org.matrix.android.sdk.internal.crypto.verification import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.cancellable +import kotlinx.coroutines.launch import org.amshove.kluent.shouldBe import org.junit.FixMethodOrder import org.junit.Test @@ -24,7 +31,9 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState +import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod +import org.matrix.android.sdk.api.session.crypto.verification.getRequest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest @RunWith(AndroidJUnit4::class) @@ -142,7 +151,7 @@ class VerificationTest : InstrumentedTest { bobSupportedMethods: List, expectedResultForAlice: ExpectedResult, expectedResultForBob: ExpectedResult - ) = runCryptoTest(context()) { cryptoTestHelper, testHelper -> + ) = runCryptoTest(context()) { cryptoTestHelper, _ -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -151,49 +160,76 @@ class VerificationTest : InstrumentedTest { cryptoTestHelper.initializeCrossSigning(aliceSession) cryptoTestHelper.initializeCrossSigning(bobSession) + val scope = CoroutineScope(SupervisorJob()) + val aliceVerificationService = aliceSession.cryptoService().verificationService() val bobVerificationService = bobSession.cryptoService().verificationService() - val transactionId = aliceVerificationService.requestKeyVerificationInDMs( - aliceSupportedMethods, bobSession.myUserId, cryptoTestData.roomId + val bobSeesVerification = CompletableDeferred() + scope.launch(Dispatchers.IO) { + bobVerificationService.requestEventFlow() + .cancellable() + .collect { + val request = it.getRequest() + if (request != null) { + bobSeesVerification.complete(request) + return@collect cancel() + } + } + } + + val aliceReady = CompletableDeferred() + scope.launch(Dispatchers.IO) { + aliceVerificationService.requestEventFlow() + .cancellable() + .collect { + val request = it.getRequest() + if (request?.state == EVerificationState.Ready) { + aliceReady.complete(request) + return@collect cancel() + } + } + } + val bobReady = CompletableDeferred() + scope.launch(Dispatchers.IO) { + bobVerificationService.requestEventFlow() + .cancellable() + .collect { + val request = it.getRequest() + if (request?.state == EVerificationState.Ready) { + bobReady.complete(request) + return@collect cancel() + } + } + } + + val requestID = aliceVerificationService.requestKeyVerificationInDMs( + methods = aliceSupportedMethods, + otherUserId = bobSession.myUserId, + roomId = cryptoTestData.roomId + ).transactionId + + bobSeesVerification.await() + bobVerificationService.readyPendingVerification( + bobSupportedMethods, + aliceSession.myUserId, + requestID ) - .transactionId + val aliceRequest = aliceReady.await() + val bobRequest = bobReady.await() - testHelper.retryPeriodically { - val incomingRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId) - if (incomingRequest != null) { - bobVerificationService.readyPendingVerification( - bobSupportedMethods, - aliceSession.myUserId, - incomingRequest.transactionId - ) - true - } else { - false - } - } - - // wait for alice to see the ready - testHelper.retryPeriodically { - val pendingRequest = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, transactionId) - pendingRequest?.state == EVerificationState.Ready - } - - val aliceReadyPendingVerificationRequest = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, transactionId)!! - val bobReadyPendingVerificationRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId)!! - - aliceReadyPendingVerificationRequest.let { pr -> + aliceRequest.let { pr -> pr.isSasSupported shouldBe expectedResultForAlice.sasIsSupported pr.weShouldShowScanOption shouldBe expectedResultForAlice.otherCanShowQrCode pr.weShouldDisplayQRCode shouldBe expectedResultForAlice.otherCanScanQrCode } - bobReadyPendingVerificationRequest.let { pr -> + bobRequest.let { pr -> pr.isSasSupported shouldBe expectedResultForBob.sasIsSupported pr.weShouldShowScanOption shouldBe expectedResultForBob.otherCanShowQrCode pr.weShouldDisplayQRCode shouldBe expectedResultForBob.otherCanScanQrCode } - cryptoTestData.cleanUp(testHelper) + scope.cancel() } } diff --git a/matrix-sdk-android/src/androidTestKotlinCrypto/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTestKotlinCrypto/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt index 4d5a937b5d..b1969e13e9 100644 --- a/matrix-sdk-android/src/androidTestKotlinCrypto/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTestKotlinCrypto/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto.verification +import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope @@ -35,10 +36,10 @@ import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction -import org.matrix.android.sdk.api.session.crypto.verification.VerificationEvent import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod +import org.matrix.android.sdk.api.session.crypto.verification.dbgState +import org.matrix.android.sdk.api.session.crypto.verification.getTransaction import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest -import timber.log.Timber @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @@ -49,9 +50,9 @@ class SASTest : InstrumentedTest { @Test fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - Timber.v("verification: doE2ETestWithAliceAndBobInARoom") + Log.d("#E2E", "verification: doE2ETestWithAliceAndBobInARoom") val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() - Timber.v("verification: initializeCrossSigning") + Log.d("#E2E", "verification: initializeCrossSigning") cryptoTestData.initializeCrossSigning(cryptoTestHelper) val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession @@ -59,19 +60,20 @@ class SASTest : InstrumentedTest { val aliceVerificationService = aliceSession.cryptoService().verificationService() val bobVerificationService = bobSession!!.cryptoService().verificationService() - Timber.v("verification: requestVerificationAndWaitForReadyState") + Log.d("#E2E", "verification: requestVerificationAndWaitForReadyState") val txId = SasVerificationTestHelper(testHelper) - .requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS)) + .requestVerificationAndWaitForReadyState(scope, cryptoTestData, listOf(VerificationMethod.SAS)) - Timber.v("verification: startKeyVerification") + Log.d("#E2E", "verification: startKeyVerification") aliceVerificationService.startKeyVerification( VerificationMethod.SAS, bobSession.myUserId, txId ) - Timber.v("verification: ensure bob has received starete") + Log.d("#E2E", "verification: ensure bob has received start") testHelper.retryWithBackoff { + Log.d("#E2E", "verification: ${bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, txId)?.state}") bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, txId)?.state == EVerificationState.Started } @@ -85,41 +87,35 @@ class SASTest : InstrumentedTest { assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId) - val aliceCancelled = CompletableDeferred() + val aliceCancelled = CompletableDeferred() aliceVerificationService.requestEventFlow().onEach { - println("alice flow event $it") - if (it is VerificationEvent.TransactionUpdated && it.transactionId == txId) { - val sasVerificationTransaction = it.transaction as SasVerificationTransaction - if (sasVerificationTransaction.state() is SasTransactionState.Cancelled) { - aliceCancelled.complete(Unit) + Log.d("#E2E", "alice flow event $it | ${it.getTransaction()?.dbgState()}") + val tx = it.getTransaction() + if (tx?.transactionId == txId && tx is SasVerificationTransaction) { + if (tx.state() is SasTransactionState.Cancelled) { + aliceCancelled.complete(tx.state() as SasTransactionState.Cancelled) } } }.launchIn(scope) - val bobCancelled = CompletableDeferred() + val bobCancelled = CompletableDeferred() bobVerificationService.requestEventFlow().onEach { - println("alice flow event $it") - if (it is VerificationEvent.TransactionUpdated && it.transactionId == txId) { - val sasVerificationTransaction = it.transaction as SasVerificationTransaction - if (sasVerificationTransaction.state() is SasTransactionState.Cancelled) { - bobCancelled.complete(Unit) + Log.d("#E2E", "bob flow event $it | ${it.getTransaction()?.dbgState()}") + val tx = it.getTransaction() + if (tx?.transactionId == txId && tx is SasVerificationTransaction) { + if (tx.state() is SasTransactionState.Cancelled) { + bobCancelled.complete(tx.state() as SasTransactionState.Cancelled) } } }.launchIn(scope) aliceVerificationService.cancelVerificationRequest(bobSession.myUserId, txId) - aliceCancelled.await() - bobCancelled.await() + val cancelledAlice = aliceCancelled.await() + val cancelledBob = bobCancelled.await() - val cancelledAlice = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, txId)!! - val cancelledBob = aliceVerificationService.getExistingVerificationRequest(aliceSession.myUserId, txId)!! - - assertEquals("Should be cancelled on alice side", cancelledAlice.state, EVerificationState.Cancelled) - assertEquals("Should be cancelled on alice side", cancelledBob.state, EVerificationState.Cancelled) - - assertEquals("Should be User cancelled on alice side", CancelCode.User, cancelledAlice.cancelConclusion) - assertEquals("Should be User cancelled on bob side", CancelCode.User, cancelledBob.cancelConclusion) + assertEquals("Should be User cancelled on alice side", CancelCode.User, cancelledAlice.cancelCode) + assertEquals("Should be User cancelled on bob side", CancelCode.User, cancelledBob.cancelCode) assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txId)) assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txId)) diff --git a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/verification/VerificationActor.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/verification/VerificationActor.kt index a6f5a6e302..dff2fe921b 100644 --- a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/verification/VerificationActor.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/verification/VerificationActor.kt @@ -271,15 +271,15 @@ internal class VerificationActor @AssistedInject constructor( is VerificationIntent.GetExistingTransaction -> { verificationRequestsStore .getExistingTransaction(msg.fromUser, msg.transactionId) - ?.let { + .let { msg.deferred.complete(it) } } is VerificationIntent.GetExistingRequest -> { verificationRequestsStore .getExistingRequest(msg.otherUserId, msg.transactionId) - ?.let { - msg.deferred.complete(it.toPendingVerificationRequest()) + .let { + msg.deferred.complete(it?.toPendingVerificationRequest()) } } is VerificationIntent.OnCancelReceived -> {