Fix QR code not always displayed

This commit is contained in:
Valere 2022-11-19 22:49:20 +01:00
parent 0c1e439313
commit bed2c221e3
9 changed files with 473 additions and 256 deletions

View file

@ -206,14 +206,14 @@ class VerificationTest : InstrumentedTest {
aliceReadyPendingVerificationRequest!!.let { pr ->
pr.isSasSupported shouldBe expectedResultForAlice.sasIsSupported
pr.otherCanShowQrCode shouldBe expectedResultForAlice.otherCanShowQrCode
pr.otherCanScanQrCode shouldBe expectedResultForAlice.otherCanScanQrCode
pr.weShouldShowScanOption shouldBe expectedResultForAlice.otherCanShowQrCode
pr.weShouldDisplayQRCode shouldBe expectedResultForAlice.otherCanScanQrCode
}
bobReadyPendingVerificationRequest!!.let { pr ->
pr.isSasSupported shouldBe expectedResultForBob.sasIsSupported
pr.otherCanShowQrCode shouldBe expectedResultForBob.otherCanShowQrCode
pr.otherCanScanQrCode shouldBe expectedResultForBob.otherCanScanQrCode
pr.weShouldShowScanOption shouldBe expectedResultForBob.otherCanShowQrCode
pr.weShouldDisplayQRCode shouldBe expectedResultForBob.otherCanScanQrCode
}
cryptoTestData.cleanUp(testHelper)

View file

@ -246,14 +246,14 @@ class VerificationTest : InstrumentedTest {
aliceReadyPendingVerificationRequest!!.let { pr ->
pr.isSasSupported shouldBe expectedResultForAlice.sasIsSupported
pr.otherCanShowQrCode shouldBe expectedResultForAlice.otherCanShowQrCode
pr.otherCanScanQrCode shouldBe expectedResultForAlice.otherCanScanQrCode
pr.weShouldShowScanOption shouldBe expectedResultForAlice.otherCanShowQrCode
pr.weShouldDisplayQRCode shouldBe expectedResultForAlice.otherCanScanQrCode
}
bobReadyPendingVerificationRequest!!.let { pr ->
pr.isSasSupported shouldBe expectedResultForBob.sasIsSupported
pr.otherCanShowQrCode shouldBe expectedResultForBob.otherCanShowQrCode
pr.otherCanScanQrCode shouldBe expectedResultForBob.otherCanScanQrCode
pr.weShouldShowScanOption shouldBe expectedResultForBob.otherCanShowQrCode
pr.weShouldDisplayQRCode shouldBe expectedResultForBob.otherCanScanQrCode
}
}

View file

@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.api.extensions.orFalse
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.IVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
@ -35,7 +34,7 @@ internal class KotlinVerificationRequest(
val otherUserId: String,
var state: EVerificationState,
val ageLocalTs: Long
) : IVerificationRequest {
) {
var roomId: String? = null
var qrCodeData: QrCodeData? = null
@ -44,21 +43,21 @@ internal class KotlinVerificationRequest(
var readyInfo: ValidVerificationInfoReady? = null
var cancelCode: CancelCode? = null
override fun requestId() = requestId
// fun requestId() = requestId
//
// fun incoming() = incoming
//
// fun otherUserId() = otherUserId
//
// fun roomId() = roomId
//
// fun targetDevices() = targetDevices
//
// fun state() = state
//
// fun ageLocalTs() = ageLocalTs
override fun incoming() = incoming
override fun otherUserId() = otherUserId
override fun roomId() = roomId
override fun targetDevices() = targetDevices
override fun state() = state
override fun ageLocalTs() = ageLocalTs
override fun otherDeviceId(): String? {
fun otherDeviceId(): String? {
return if (incoming) {
requestInfo?.fromDevice
} else {
@ -66,12 +65,12 @@ internal class KotlinVerificationRequest(
}
}
override fun cancelCode(): CancelCode? = cancelCode
fun cancelCode(): CancelCode? = cancelCode
/**
* SAS is supported if I support it and the other party support it.
*/
override fun isSasSupported(): Boolean {
private fun isSasSupported(): Boolean {
return requestInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse()
}
@ -79,7 +78,7 @@ internal class KotlinVerificationRequest(
/**
* Other can show QR code if I can scan QR code and other can show QR code.
*/
override fun otherCanShowQrCode(): Boolean {
private fun otherCanShowQrCode(): Boolean {
return if (incoming) {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
@ -92,7 +91,7 @@ internal class KotlinVerificationRequest(
/**
* Other can scan QR code if I can show QR code and other can scan QR code.
*/
override fun otherCanScanQrCode(): Boolean {
private fun otherCanScanQrCode(): Boolean {
return if (incoming) {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
@ -102,7 +101,7 @@ internal class KotlinVerificationRequest(
}
}
override fun qrCodeText() = qrCodeData?.toEncodedString()
fun qrCodeText() = qrCodeData?.toEncodedString()
override fun toString(): String {
return toPendingVerificationRequest().toString()
@ -122,9 +121,11 @@ internal class KotlinVerificationRequest(
targetDevices = targetDevices,
qrCodeText = qrCodeText(),
isSasSupported = isSasSupported(),
otherCanShowQrCode = otherCanShowQrCode(),
otherCanScanQrCode = otherCanScanQrCode(),
weShouldShowScanOption = otherCanShowQrCode(),
weShouldDisplayQRCode = otherCanScanQrCode(),
otherDeviceId = otherDeviceId()
)
}
fun isFinished() = state == EVerificationState.Cancelled || state == EVerificationState.Done
}

View file

@ -130,8 +130,8 @@ internal class VerificationActor @AssistedInject constructor(
*/
private val pendingRequests = HashMap<String, MutableList<KotlinVerificationRequest>>()
// Replaces the typical list of listeners pattern. Looks to me as the sane setup, not sure if more than 1 is needed as extraBufferCapacity
// We don't want to use emit as it would block if no listener is subscribed
// Replaces the typical list of listeners pattern.
// Looks to me as the sane setup, not sure if more than 1 is needed as extraBufferCapacity
// So we should use try emit using extraBufferCapacity, we use drop_oldest instead of suspend.
val eventFlow = MutableSharedFlow<VerificationEvent>(extraBufferCapacity = 4, onBufferOverflow = BufferOverflow.DROP_OLDEST)
@ -214,7 +214,7 @@ internal class VerificationActor @AssistedInject constructor(
.v("[${myUserId.take(8)}]: $msg")
when (msg) {
is VerificationIntent.ActionRequestVerification -> {
handleRequestAdd(msg)
handleActionRequestVerification(msg)
}
is VerificationIntent.OnReadyReceived -> {
handleReadyReceived(msg)
@ -1046,7 +1046,7 @@ internal class VerificationActor @AssistedInject constructor(
existing.state = SasTransactionState.Done(true)
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
// we can forget about it
txMap[matchingRequest.otherUserId()]?.remove(matchingRequest.requestId)
txMap[matchingRequest.otherUserId]?.remove(matchingRequest.requestId)
// XXX whatabout waiting for done?
matchingRequest.state = EVerificationState.Done
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
@ -1137,7 +1137,7 @@ internal class VerificationActor @AssistedInject constructor(
existing.state = QRCodeVerificationState.Done
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
// we can forget about it
txMap[matchingRequest.otherUserId()]?.remove(matchingRequest.requestId)
txMap[matchingRequest.otherUserId]?.remove(matchingRequest.requestId)
matchingRequest.state = EVerificationState.WaitingForDone
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
@ -1309,6 +1309,11 @@ internal class VerificationActor @AssistedInject constructor(
if (commonMethods.isEmpty()) {
Timber.tag(loggerTag.value).v("Request ${msg.transactionId} no common methods")
cancelRequest(existing, CancelCode.UnknownMethod)
// Upon receipt of Alices m.key.verification.request message, if Bobs device does not understand any of the methods,
// it should not cancel the request as one of his other devices may support the request.
// XXX How to o that??
// Instead, Bobs device should tell Bob that no supported method was found, and allow him to manually reject the request.
msg.deferred.complete(null)
return
}
@ -1367,7 +1372,7 @@ internal class VerificationActor @AssistedInject constructor(
private fun getMethodAgreement(
otherUserMethods: List<String>?,
methods: List<VerificationMethod>,
myMethods: List<VerificationMethod>,
): List<String> {
if (otherUserMethods.isNullOrEmpty()) {
return emptyList()
@ -1375,18 +1380,18 @@ internal class VerificationActor @AssistedInject constructor(
val result = mutableSetOf<String>()
if (VERIFICATION_METHOD_SAS in otherUserMethods && VerificationMethod.SAS in methods) {
if (VERIFICATION_METHOD_SAS in otherUserMethods && VerificationMethod.SAS in myMethods) {
// Other can do SAS and so do I
result.add(VERIFICATION_METHOD_SAS)
}
if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods || VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods) {
if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods && VerificationMethod.QR_CODE_SHOW in methods) {
if (VERIFICATION_METHOD_RECIPROCATE in otherUserMethods) {
if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods && VerificationMethod.QR_CODE_SHOW in myMethods) {
// Other can Scan and I can show QR code
result.add(VERIFICATION_METHOD_QR_CODE_SHOW)
result.add(VERIFICATION_METHOD_RECIPROCATE)
}
if (VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods && VerificationMethod.QR_CODE_SCAN in methods) {
if (VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods && VerificationMethod.QR_CODE_SCAN in myMethods) {
// Other can show and I can scan QR code
result.add(VERIFICATION_METHOD_QR_CODE_SCAN)
result.add(VERIFICATION_METHOD_RECIPROCATE)
@ -1400,7 +1405,11 @@ internal class VerificationActor @AssistedInject constructor(
return contains(VERIFICATION_METHOD_QR_CODE_SCAN) && contains(VERIFICATION_METHOD_RECIPROCATE)
}
private suspend fun handleRequestAdd(msg: VerificationIntent.ActionRequestVerification) {
private fun List<String>.canShowCode(): Boolean {
return contains(VERIFICATION_METHOD_QR_CODE_SHOW) && contains(VERIFICATION_METHOD_RECIPROCATE)
}
private suspend fun handleActionRequestVerification(msg: VerificationIntent.ActionRequestVerification) {
val requestsForUser = pendingRequests.getOrPut(msg.otherUserId) { mutableListOf() }
// there can only be one active request per user, so cancel existing ones
requestsForUser.toList().forEach { existingRequest ->
@ -1410,8 +1419,6 @@ internal class VerificationActor @AssistedInject constructor(
}
}
val validLocalId = LocalEcho.createLocalEchoId()
val methodValues = if (cryptoStore.getMyCrossSigningInfo()?.isTrusted().orFalse()) {
// Add reciprocate method if application declares it can scan or show QR codes
// Not sure if it ok to do that (?)
@ -1521,6 +1528,10 @@ internal class VerificationActor @AssistedInject constructor(
return
}
if (matchingRequest.requestInfo?.methods?.canShowCode().orFalse() &&
msg.readyInfo.methods.canScanCode()) {
matchingRequest.qrCodeData = createQrCodeData(matchingRequest.requestId, msg.fromUser, msg.readyInfo.fromDevice)
}
matchingRequest.readyInfo = msg.readyInfo
matchingRequest.state = EVerificationState.Ready
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))

View file

@ -1,5 +1,5 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -32,31 +32,3 @@ enum class EVerificationState {
Cancelled,
HandledByOtherSession
}
// TODO remove that
interface IVerificationRequest {
fun requestId(): String
fun incoming(): Boolean
fun otherUserId(): String
fun roomId(): String?
// target devices in case of to_device self verification
fun targetDevices(): List<String>?
fun state(): EVerificationState
fun ageLocalTs(): Long
fun isSasSupported(): Boolean
fun otherCanShowQrCode(): Boolean
fun otherCanScanQrCode(): Boolean
fun otherDeviceId(): String?
fun qrCodeText(): String?
fun isFinished(): Boolean = state() == EVerificationState.Cancelled || state() == EVerificationState.Done
fun cancelCode(): CancelCode?
}

View file

@ -38,11 +38,10 @@ data class PendingVerificationRequest(
// if available store here the qr code to show
val qrCodeText: String? = null,
val isSasSupported: Boolean = false,
val otherCanShowQrCode: Boolean = false,
val otherCanScanQrCode: Boolean = false,
val weShouldShowScanOption: Boolean = false,
val weShouldDisplayQRCode: Boolean = false,
) {
// val isReady: Boolean = readyInfo != null
//
// val isFinished: Boolean = isSuccessful || cancelConclusion != null
}

View file

@ -0,0 +1,181 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification
import dagger.Lazy
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationReadyContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.util.time.Clock
import java.util.UUID
internal class VerificationActorHelper {
data class TestData(
val aliceActor: VerificationActor,
val bobActor: VerificationActor,
)
val actorAScope = CoroutineScope(SupervisorJob())
val actorBScope = CoroutineScope(SupervisorJob())
val transportScope = CoroutineScope(SupervisorJob())
var bobChannel: SendChannel<VerificationIntent>? = null
var aliceChannel: SendChannel<VerificationIntent>? = null
fun setUpActors(): TestData {
val aliceTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.aliceMxId) { bobChannel }
val bobTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.bobMxId) { aliceChannel }
val aliceActor = fakeActor(
actorAScope,
FakeCryptoStoreForVerification.aliceMxId,
FakeCryptoStoreForVerification(StoreMode.Alice).instance,
aliceTransportLayer,
mockk<Lazy<CrossSigningService>> {
every {
get()
} returns mockk<CrossSigningService>(relaxed = true)
}
)
aliceChannel = aliceActor.channel
val bobActor = fakeActor(
actorBScope,
FakeCryptoStoreForVerification.aliceMxId,
FakeCryptoStoreForVerification(StoreMode.Alice).instance,
bobTransportLayer,
mockk<Lazy<CrossSigningService>> {
every {
get()
} returns mockk<CrossSigningService>(relaxed = true)
}
)
bobChannel = bobActor.channel
return TestData(
aliceActor,
bobActor
)
}
private fun mockTransportTo(fromUser: String, otherChannel: (() -> SendChannel<VerificationIntent>?)): VerificationTransportLayer {
return mockk<VerificationTransportLayer> {
coEvery { sendToOther(any(), any(), any()) } answers {
val request = firstArg<KotlinVerificationRequest>()
val type = secondArg<String>()
val info = thirdArg<VerificationInfo<*>>()
transportScope.launch(Dispatchers.IO) {
when (type) {
EventType.KEY_VERIFICATION_READY -> {
val readyContent = info.asValidObject()
otherChannel()?.send(
VerificationIntent.OnReadyReceived(
transactionId = request.requestId,
fromUser = fromUser,
viaRoom = request.roomId,
readyInfo = readyContent as ValidVerificationInfoReady,
)
)
}
}
}
}
coEvery { sendInRoom(any(), any(), any(), any()) } answers {
val type = secondArg<String>()
val roomId = thirdArg<String>()
val content = arg<Content>(3)
val fakeEventId = UUID.randomUUID().toString()
transportScope.launch(Dispatchers.IO) {
when (type) {
EventType.MESSAGE -> {
val requestContent = content.toModel<MessageVerificationRequestContent>()?.copy(
transactionId = fakeEventId
)?.asValidObject()
otherChannel()?.send(
VerificationIntent.OnVerificationRequestReceived(
requestContent!!,
senderId = FakeCryptoStoreForVerification.aliceMxId,
roomId = roomId,
timeStamp = 0
)
)
}
EventType.KEY_VERIFICATION_READY -> {
val readyContent = content.toModel<MessageVerificationReadyContent>()
?.asValidObject()
otherChannel()?.send(
VerificationIntent.OnReadyReceived(
transactionId = readyContent!!.transactionId,
fromUser = fromUser,
viaRoom = roomId,
readyInfo = readyContent,
)
)
}
}
}
fakeEventId
}
}
}
private fun fakeActor(
scope: CoroutineScope,
userId: String,
cryptoStore: IMXCryptoStore,
transportLayer: VerificationTransportLayer,
crossSigningService: dagger.Lazy<CrossSigningService>,
): VerificationActor {
return VerificationActor(
scope,
// channel = channel,
clock = mockk<Clock> {
every { epochMillis() } returns System.currentTimeMillis()
},
myUserId = userId,
cryptoStore = cryptoStore,
secretShareManager = mockk<SecretShareManager> {},
transportLayer = transportLayer,
crossSigningService = crossSigningService,
setDeviceVerificationAction = SetDeviceVerificationAction(
cryptoStore = cryptoStore,
userId = userId,
defaultKeysBackupService = mockk {
coEvery { checkAndStartKeysBackup() } coAnswers { }
}
)
)
}
}

View file

@ -17,9 +17,7 @@
package org.matrix.android.sdk.internal.crypto.verification.org.matrix.android.sdk.internal.crypto.verification
import android.util.Base64
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
@ -27,44 +25,32 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
import org.amshove.kluent.fail
import org.amshove.kluent.internal.assertEquals
import org.amshove.kluent.internal.assertNotEquals
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldNotBe
import org.junit.Before
import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.IVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
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.events.model.Content
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationReadyContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.verification.FakeCryptoStoreForVerification
import org.matrix.android.sdk.internal.crypto.verification.StoreMode
import org.matrix.android.sdk.internal.crypto.verification.VerificationActor
import org.matrix.android.sdk.internal.crypto.verification.VerificationInfo
import org.matrix.android.sdk.internal.crypto.verification.VerificationActorHelper
import org.matrix.android.sdk.internal.crypto.verification.VerificationIntent
import org.matrix.android.sdk.internal.crypto.verification.VerificationTransportLayer
import org.matrix.android.sdk.internal.util.time.Clock
import java.util.UUID
@OptIn(ExperimentalCoroutinesApi::class)
class VerificationActorTest {
val transportScope = CoroutineScope(SupervisorJob())
val actorAScope = CoroutineScope(SupervisorJob())
val actorBScope = CoroutineScope(SupervisorJob())
// val actorAScope = CoroutineScope(SupervisorJob())
// val actorBScope = CoroutineScope(SupervisorJob())
@Before
fun setUp() {
@ -86,38 +72,10 @@ class VerificationActorTest {
}
@Test
fun `Request and accept`() = runTest {
var bobChannel: SendChannel<VerificationIntent>? = null
var aliceChannel: SendChannel<VerificationIntent>? = null
val aliceTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.aliceMxId) { bobChannel }
val bobTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.bobMxId) { aliceChannel }
val aliceActor = fakeActor(
actorAScope,
FakeCryptoStoreForVerification.aliceMxId,
FakeCryptoStoreForVerification(StoreMode.Alice).instance,
aliceTransportLayer,
mockk<dagger.Lazy<CrossSigningService>> {
every {
get()
} returns mockk<CrossSigningService>(relaxed = true)
}
)
aliceChannel = aliceActor.channel
val bobActor = fakeActor(
actorBScope,
FakeCryptoStoreForVerification.aliceMxId,
FakeCryptoStoreForVerification(StoreMode.Alice).instance,
bobTransportLayer,
mockk<dagger.Lazy<CrossSigningService>> {
every {
get()
} returns mockk<CrossSigningService>(relaxed = true)
}
)
bobChannel = bobActor.channel
fun `If ready both side should support sas and Qr show and scan`() = runTest {
val testData = VerificationActorHelper().setUpActors()
val aliceActor = testData.aliceActor
val bobActor = testData.bobActor
val completableDeferred = CompletableDeferred<PendingVerificationRequest>()
@ -130,23 +88,14 @@ class VerificationActorTest {
}
}
awaitDeferrable<PendingVerificationRequest> {
aliceActor.send(
VerificationIntent.ActionRequestVerification(
otherUserId = FakeCryptoStoreForVerification.bobMxId,
roomId = "aRoom",
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SHOW, VerificationMethod.QR_CODE_SCAN),
deferred = it
)
)
}
aliceActor.requestVerification(listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SHOW, VerificationMethod.QR_CODE_SCAN))
val bobIncomingRequest = completableDeferred.await()
bobIncomingRequest.state shouldBeEqualTo EVerificationState.Requested
val aliceReadied = CompletableDeferred<PendingVerificationRequest>()
val theJob = transportScope.launch {
transportScope.launch {
aliceActor.eventFlow.collect {
if (it is VerificationEvent.RequestUpdated && it.request.state == EVerificationState.Ready) {
aliceReadied.complete(it.request)
@ -156,7 +105,7 @@ class VerificationActorTest {
}
// test ready
awaitDeferrable<PendingVerificationRequest?> {
val bobReadied = awaitDeferrable<PendingVerificationRequest?> {
bobActor.send(
VerificationIntent.ActionReadyRequest(
bobIncomingRequest.transactionId,
@ -168,11 +117,143 @@ class VerificationActorTest {
val readiedAliceSide = aliceReadied.await()
println("transporte scope active? ${transportScope.isActive}")
println("the job? ${theJob.isActive}")
readiedAliceSide.isSasSupported shouldBeEqualTo true
readiedAliceSide.otherCanScanQrCode shouldBeEqualTo true
readiedAliceSide.weShouldDisplayQRCode shouldBeEqualTo true
bobReadied shouldNotBe null
bobReadied!!.isSasSupported shouldBeEqualTo true
bobReadied.weShouldDisplayQRCode shouldBeEqualTo true
bobReadied.qrCodeText shouldNotBe null
readiedAliceSide.qrCodeText shouldNotBe null
}
@Test
fun `Test alice can show but not scan QR`() = runTest {
val testData = VerificationActorHelper().setUpActors()
val aliceActor = testData.aliceActor
val bobActor = testData.bobActor
println("Alice sends a request")
val outgoingRequest = aliceActor.requestVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SHOW)
)
// wait for bob to get it
println("Wait for bob to get it")
waitForBobToSeeIncomingRequest(bobActor, outgoingRequest)
println("let bob ready it")
val bobReady = bobActor.readyVerification(
outgoingRequest.transactionId,
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW)
)
println("Wait for alice to get the ready")
retryUntil {
awaitDeferrable<PendingVerificationRequest?> {
aliceActor.send(VerificationIntent.GetExistingRequest(outgoingRequest.transactionId, outgoingRequest.otherUserId, it))
}?.state == EVerificationState.Ready
}
val aliceReady = awaitDeferrable<PendingVerificationRequest?> {
aliceActor.send(VerificationIntent.GetExistingRequest(outgoingRequest.transactionId, outgoingRequest.otherUserId, it))
}!!
aliceReady.isSasSupported shouldBeEqualTo bobReady.isSasSupported
// alice can't scan so there should not be option to do so
assertEquals("Alice should not show scan option", false, aliceReady.weShouldShowScanOption)
assertEquals("Alice should show QR as bob can scan", true, aliceReady.weShouldDisplayQRCode)
assertEquals("Bob should be able to scan", true, bobReady.weShouldShowScanOption)
assertEquals("Bob should not show QR as alice can scan", false, bobReady.weShouldDisplayQRCode)
}
@Test
fun `Test bob can show but not scan QR`() = runTest {
val testData = VerificationActorHelper().setUpActors()
val aliceActor = testData.aliceActor
val bobActor = testData.bobActor
println("Alice sends a request")
val outgoingRequest = aliceActor.requestVerification(
listOf(VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW)
)
// wait for bob to get it
println("Wait for bob to get it")
waitForBobToSeeIncomingRequest(bobActor, outgoingRequest)
println("let bob ready it")
val bobReady = bobActor.readyVerification(
outgoingRequest.transactionId,
listOf(VerificationMethod.QR_CODE_SHOW)
)
println("Wait for alice to get the ready")
retryUntil {
awaitDeferrable<PendingVerificationRequest?> {
aliceActor.send(VerificationIntent.GetExistingRequest(outgoingRequest.transactionId, outgoingRequest.otherUserId, it))
}?.state == EVerificationState.Ready
}
val aliceReady = awaitDeferrable<PendingVerificationRequest?> {
aliceActor.send(VerificationIntent.GetExistingRequest(outgoingRequest.transactionId, outgoingRequest.otherUserId, it))
}!!
assertEquals("Alice sas is not supported", false, aliceReady.isSasSupported)
aliceReady.isSasSupported shouldBeEqualTo bobReady.isSasSupported
// alice can't scan so there should not be option to do so
assertEquals("Alice should show scan option", true, aliceReady.weShouldShowScanOption)
assertEquals("Alice QR data should be null", null, aliceReady.qrCodeText)
assertEquals("Alice should not show QR as bob can scan", false, aliceReady.weShouldDisplayQRCode)
assertEquals("Bob should not should not show cam option as it can't scan", false, bobReady.weShouldShowScanOption)
assertNotEquals("Bob QR data should be there", null, bobReady.qrCodeText)
assertEquals("Bob should show QR as alice can scan", true, bobReady.weShouldDisplayQRCode)
}
private suspend fun VerificationActor.requestVerification(methods: List<VerificationMethod>): PendingVerificationRequest {
return awaitDeferrable<PendingVerificationRequest> {
send(
VerificationIntent.ActionRequestVerification(
otherUserId = FakeCryptoStoreForVerification.bobMxId,
roomId = "aRoom",
methods = methods,
deferred = it
)
)
}
}
private suspend fun waitForBobToSeeIncomingRequest(bobActor: VerificationActor, aliceOutgoing: PendingVerificationRequest) {
retryUntil {
awaitDeferrable<PendingVerificationRequest?> {
bobActor.send(
VerificationIntent.GetExistingRequest(
aliceOutgoing.transactionId,
FakeCryptoStoreForVerification.aliceMxId, it
)
)
}?.state == EVerificationState.Requested
}
}
private val backoff = listOf(500L, 1_000L, 1_000L, 3_000L, 3_000L, 5_000L)
private suspend fun retryUntil(condition: suspend (() -> Boolean)) {
var tryCount = 0
while (!condition()) {
if (tryCount >= backoff.size) {
fail("Retry Until Fialed")
}
withContext(Dispatchers.IO) {
delay(backoff[tryCount])
}
tryCount++
}
}
private suspend fun <T> awaitDeferrable(block: suspend ((CompletableDeferred<T>) -> Unit)): T {
@ -181,113 +262,85 @@ class VerificationActorTest {
return deferred.await()
}
private fun mockTransportTo(fromUser: String, otherChannel: (() -> SendChannel<VerificationIntent>?)): VerificationTransportLayer {
return mockk<VerificationTransportLayer> {
coEvery { sendToOther(any(), any(), any()) } answers {
val request = firstArg<IVerificationRequest>()
val type = secondArg<String>()
val info = thirdArg<VerificationInfo<*>>()
transportScope.launch(Dispatchers.IO) {
when (type) {
EventType.KEY_VERIFICATION_READY -> {
val readyContent = info.asValidObject()
otherChannel()?.send(
VerificationIntent.OnReadyReceived(
transactionId = request.requestId(),
fromUser = fromUser,
viaRoom = request.roomId(),
readyInfo = readyContent as ValidVerificationInfoReady,
private suspend fun VerificationActor.readyVerification(transactionId: String, methods: List<VerificationMethod>): PendingVerificationRequest {
return awaitDeferrable<PendingVerificationRequest?> {
send(
VerificationIntent.ActionReadyRequest(
transactionId,
methods = methods,
it
)
)
}
}
}
}
coEvery { sendInRoom(any(), any(), any(), any()) } answers {
val type = secondArg<String>()
val roomId = thirdArg<String>()
val content = arg<Content>(3)
val fakeEventId = UUID.randomUUID().toString()
transportScope.launch(Dispatchers.IO) {
when (type) {
EventType.MESSAGE -> {
val requestContent = content.toModel<MessageVerificationRequestContent>()?.copy(
transactionId = fakeEventId
)?.asValidObject()
otherChannel()?.send(
VerificationIntent.OnVerificationRequestReceived(
requestContent!!,
senderId = FakeCryptoStoreForVerification.aliceMxId,
roomId = roomId,
timeStamp = 0
)
)
}
EventType.KEY_VERIFICATION_READY -> {
val readyContent = content.toModel<MessageVerificationReadyContent>()
?.asValidObject()
otherChannel()?.send(
VerificationIntent.OnReadyReceived(
transactionId = readyContent!!.transactionId,
fromUser = fromUser,
viaRoom = roomId,
readyInfo = readyContent,
)
)
}
}
}
fakeEventId
}
}
}!!
}
@Test
fun `Every testing`() {
val mockStore = mockk<IMXCryptoStore>()
every { mockStore.getDeviceId() } returns "A"
println("every ${mockStore.getDeviceId()}")
every { mockStore.getDeviceId() } returns "B"
println("every ${mockStore.getDeviceId()}")
// @Test
// fun `Every testing`() {
// val mockStore = mockk<IMXCryptoStore>()
// every { mockStore.getDeviceId() } returns "A"
// println("every ${mockStore.getDeviceId()}")
// every { mockStore.getDeviceId() } returns "B"
// println("every ${mockStore.getDeviceId()}")
//
// every { mockStore.getDeviceId() } returns "A"
// every { mockStore.getDeviceId() } returns "B"
// println("every ${mockStore.getDeviceId()}")
//
// every { mockStore.getCrossSigningInfo(any()) } returns null
// every { mockStore.getCrossSigningInfo("alice") } returns MXCrossSigningInfo("alice", emptyList(), false)
//
// println("XS ${mockStore.getCrossSigningInfo("alice")}")
// println("XS ${mockStore.getCrossSigningInfo("bob")}")
// }
every { mockStore.getDeviceId() } returns "A"
every { mockStore.getDeviceId() } returns "B"
println("every ${mockStore.getDeviceId()}")
every { mockStore.getCrossSigningInfo(any()) } returns null
every { mockStore.getCrossSigningInfo("alice") } returns MXCrossSigningInfo("alice", emptyList(), false)
println("XS ${mockStore.getCrossSigningInfo("alice")}")
println("XS ${mockStore.getCrossSigningInfo("bob")}")
}
private fun fakeActor(
scope: CoroutineScope,
userId: String,
cryptoStore: IMXCryptoStore,
transportLayer: VerificationTransportLayer,
crossSigningService: dagger.Lazy<CrossSigningService>,
): VerificationActor {
return VerificationActor(
scope,
// channel = channel,
clock = mockk<Clock> {
every { epochMillis() } returns System.currentTimeMillis()
},
myUserId = userId,
cryptoStore = cryptoStore,
secretShareManager = mockk<SecretShareManager> {},
transportLayer = transportLayer,
crossSigningService = crossSigningService,
setDeviceVerificationAction = SetDeviceVerificationAction(
cryptoStore = cryptoStore,
userId = userId,
defaultKeysBackupService = mockk {
coEvery { checkAndStartKeysBackup() } coAnswers { }
}
)
)
}
// @Test
// fun `Basic channel test`() {
// // val sharedFlow = MutableSharedFlow<Int>(replay = 0, extraBufferCapacity = 2, BufferOverflow.DROP_OLDEST)
// val sharedFlow = MutableSharedFlow<Int>(replay = 0)//, extraBufferCapacity = 0, BufferOverflow.DROP_OLDEST)
//
// val scope = CoroutineScope(SupervisorJob())
// val deferred = CompletableDeferred<Unit>()
// val listener = scope.launch {
// sharedFlow.onEach {
// println("L1 : Just collected $it")
// delay(1000)
// println("L1 : Just processed $it")
// if (it == 2) {
// deferred.complete(Unit)
// }
// }.launchIn(scope)
// }
//
// // scope.launch {
// // delay(700)
// println("Pre Emit 1")
// sharedFlow.tryEmit(1)
// println("Emited 1")
// sharedFlow.tryEmit(2)
// println("Emited 2")
// // }
//
// // runBlocking {
// // deferred.await()
// // }
//
// sharedFlow.onEach {
// println("L2: Just collected $it")
// delay(1000)
// println("L2: Just processed $it")
// }.launchIn(scope)
//
//
// runBlocking {
// deferred.await()
// }
//
// val now = System.currentTimeMillis()
// println("Just give some time for execution")
// val job = scope.launch { delay(10_000) }
// runBlocking {
// job.join()
// }
// println("enough ${System.currentTimeMillis() - now}")
// }
}

View file

@ -187,7 +187,7 @@ class UserVerificationController @Inject constructor(
notice(scanCodeInstructions.toEpoxyCharSequence())
}
if (request.otherCanScanQrCode && !request.qrCodeText.isNullOrEmpty()) {
if (request.weShouldDisplayQRCode && !request.qrCodeText.isNullOrEmpty()) {
bottomSheetVerificationQrCodeItem {
id("qr")
data(request.qrCodeText!!)
@ -198,7 +198,7 @@ class UserVerificationController @Inject constructor(
}
}
if (request.otherCanShowQrCode) {
if (request.weShouldShowScanOption) {
bottomSheetVerificationActionItem {
id("openCamera")
title(scanOtherCodeTitle)