diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt index 54a1e896ae..b9d0c0ad2c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt @@ -52,7 +52,7 @@ interface VerificationService { transactionId: String?): String? /** - * Request a key verification from another user using toDevice events. + * Request key verification with another user via room events (instead of the to-device API) */ fun requestKeyVerificationInDMs(methods: List, otherUserId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt index 7e92ff3e88..0ec020bc46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt @@ -23,12 +23,12 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams @@ -43,6 +43,9 @@ internal class CancelGossipRequestWorker(context: Context, override val sessionId: String, val requestId: String, val recipients: Map>, + // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided + // to use the same value if this worker is retried. + val txnId: String? = null, override val lastFailureMessage: String? = null ) : SessionWorkerParams { companion object { @@ -51,6 +54,7 @@ internal class CancelGossipRequestWorker(context: Context, sessionId = sessionId, requestId = request.requestId, recipients = request.recipients, + txnId = createUniqueTxnId(), lastFailureMessage = null ) } @@ -66,7 +70,10 @@ internal class CancelGossipRequestWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - val localId = LocalEcho.createLocalEchoId() + // params.txnId should be provided in all cases now. But Params can be deserialized by + // the WorkManager from data serialized in a previous version of the application, so without the txnId field. + // So if not present, we create a txnId + val txnId = params.txnId ?: createUniqueTxnId() val contentMap = MXUsersDevicesMap() val toDeviceContent = ShareRequestCancellation( requestingDeviceId = credentials.deviceId, @@ -92,7 +99,7 @@ internal class CancelGossipRequestWorker(context: Context, SendToDeviceTask.Params( eventType = EventType.ROOM_KEY_REQUEST, contentMap = contentMap, - transactionId = localId + transactionId = txnId ) ) cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index 4a0a274f4f..e8640d5011 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.GossipingDefaultContent import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers @@ -356,7 +357,8 @@ internal class IncomingGossipingRequestManager @Inject constructor( secretValue = secretValue, requestUserId = request.userId, requestDeviceId = request.deviceId, - requestId = request.requestId + requestId = request.requestId, + txnId = createUniqueTxnId() ) cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING) @@ -376,13 +378,13 @@ internal class IncomingGossipingRequestManager @Inject constructor( } request.share = { secretValue -> - val params = SendGossipWorker.Params( sessionId = userId, secretValue = secretValue, requestUserId = request.userId, requestDeviceId = request.deviceId, - requestId = request.requestId + requestId = request.requestId, + txnId = createUniqueTxnId() ) cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt index c86f2be0a3..ccdb5ab137 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.crypto -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.di.SessionId @@ -26,6 +25,8 @@ import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId +import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper import timber.log.Timber import javax.inject.Inject @@ -131,7 +132,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor( val params = SendGossipRequestWorker.Params( sessionId = sessionId, keyShareRequest = request as? OutgoingRoomKeyRequest, - secretShareRequest = request as? OutgoingSecretRequest + secretShareRequest = request as? OutgoingSecretRequest, + txnId = createUniqueTxnId() ) cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.SENDING) val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true) @@ -154,7 +156,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor( if (resend) { val reSendParams = SendGossipRequestWorker.Params( sessionId = sessionId, - keyShareRequest = request.copy(requestId = LocalEcho.createLocalEchoId()) + keyShareRequest = request.copy(requestId = RequestIdHelper.createUniqueRequestId()), + txnId = createUniqueTxnId() ) val reSendWorkRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(reSendParams), true) gossipingWorkManager.postWork(reSendWorkRequest) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt index e8d567b944..2e26720abb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject @@ -31,6 +30,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams @@ -46,6 +46,9 @@ internal class SendGossipRequestWorker(context: Context, override val sessionId: String, val keyShareRequest: OutgoingRoomKeyRequest? = null, val secretShareRequest: OutgoingSecretRequest? = null, + // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided + // to use the same value if this worker is retried. + val txnId: String? = null, override val lastFailureMessage: String? = null ) : SessionWorkerParams @@ -58,7 +61,10 @@ internal class SendGossipRequestWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - val localId = LocalEcho.createLocalEchoId() + // params.txnId should be provided in all cases now. But Params can be deserialized by + // the WorkManager from data serialized in a previous version of the application, so without the txnId field. + // So if not present, we create a txnId + val txnId = params.txnId ?: createUniqueTxnId() val contentMap = MXUsersDevicesMap() val eventType: String val requestId: String @@ -122,7 +128,7 @@ internal class SendGossipRequestWorker(context: Context, SendToDeviceTask.Params( eventType = eventType, contentMap = contentMap, - transactionId = localId + transactionId = txnId ) ) cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt index 8c68057056..c5c6d26f79 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter @@ -31,6 +30,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams @@ -48,6 +48,9 @@ internal class SendGossipWorker(context: Context, val requestUserId: String?, val requestDeviceId: String?, val requestId: String?, + // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided + // to use the same value if this worker is retried. + val txnId: String? = null, override val lastFailureMessage: String? = null ) : SessionWorkerParams @@ -62,7 +65,10 @@ internal class SendGossipWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - val localId = LocalEcho.createLocalEchoId() + // params.txnId should be provided in all cases now. But Params can be deserialized by + // the WorkManager from data serialized in a previous version of the application, so without the txnId field. + // So if not present, we create a txnId + val txnId = params.txnId ?: createUniqueTxnId() val eventType: String = EventType.SEND_SECRET val toDeviceContent = SecretSendEventContent( @@ -127,7 +133,7 @@ internal class SendGossipWorker(context: Context, SendToDeviceTask.Params( eventType = EventType.ENCRYPTED, contentMap = sendToDeviceMap, - transactionId = localId + transactionId = txnId ) ) cryptoStore.updateGossipingRequestState( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index d997998836..3c8353e83e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -27,7 +27,6 @@ import io.realm.Sort import io.realm.kotlin.where import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional @@ -88,6 +87,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.query.delete import org.matrix.android.sdk.internal.crypto.store.db.query.get import org.matrix.android.sdk.internal.crypto.store.db.query.getById import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate +import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.tools.RealmDebugTools import org.matrix.android.sdk.internal.di.CryptoDatabase @@ -1121,7 +1121,7 @@ internal class RealmCryptoStore @Inject constructor( if (existing == null) { request = realm.createObject(OutgoingGossipingRequestEntity::class.java).apply { - this.requestId = LocalEcho.createLocalEchoId() + this.requestId = RequestIdHelper.createUniqueRequestId() this.setRecipients(recipients) this.requestState = OutgoingGossipingRequestState.UNSENT this.type = GossipRequestType.KEY @@ -1151,7 +1151,7 @@ internal class RealmCryptoStore @Inject constructor( this.type = GossipRequestType.SECRET setRecipients(recipients) this.requestState = OutgoingGossipingRequestState.UNSENT - this.requestId = LocalEcho.createLocalEchoId() + this.requestId = RequestIdHelper.createUniqueRequestId() this.requestedInfoStr = secretName }.toOutgoingGossipingRequest() as? OutgoingSecretRequest } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt index 41a5118be0..c6af9b0cd1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt @@ -22,8 +22,8 @@ import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task +import java.util.UUID import javax.inject.Inject -import kotlin.random.Random internal interface SendToDeviceTask : Task { data class Params( @@ -31,7 +31,7 @@ internal interface SendToDeviceTask : Task { val eventType: String, // the content to send. Map from user_id to device_id to content dictionary. val contentMap: MXUsersDevicesMap, - // the transactionId + // the transactionId. If not provided, a transactionId will be created by the task val transactionId: String? = null ) } @@ -46,16 +46,23 @@ internal class DefaultSendToDeviceTask @Inject constructor( messages = params.contentMap.map ) + // If params.transactionId is not provided, we create a unique txnId. + // It's important to do that outside the requestBlock parameter of executeRequest() + // to use the same value if the request is retried + val txnId = params.transactionId ?: createUniqueTxnId() + return executeRequest( globalErrorReceiver, canRetry = true, maxRetriesCount = 3 ) { cryptoApi.sendToDevice( - params.eventType, - params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(), - sendToDeviceBody + eventType = params.eventType, + transactionId = txnId, + body = sendToDeviceBody ) } } } + +internal fun createUniqueTxnId() = UUID.randomUUID().toString() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt new file mode 100644 index 0000000000..3106d5820c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021 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. + * 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.util + +import java.util.UUID + +internal object RequestIdHelper { + fun createUniqueRequestId() = UUID.randomUUID().toString() +}