Mark verification state as dirty

to avoid false positive decrypted by deleted session
This commit is contained in:
valere 2023-02-02 16:23:56 +01:00
parent ebed3195bf
commit 9d75a66aea
13 changed files with 293 additions and 64 deletions

View file

@ -20,6 +20,7 @@ import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -29,6 +30,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
@ -183,6 +185,88 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
return sentEvents
}
suspend fun sendMessageInRoom(room: Room, text: String): String {
room.sendService().sendTextMessage(text)
val timeline = room.timelineService().createTimeline(null, TimelineSettings(60))
timeline.start()
val messageSent = CompletableDeferred<String>()
timeline.addListener(object : Timeline.Listener {
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
val decryptedMsg = timeline.getSnapshot()
.filter { it.root.getClearType() == EventType.MESSAGE }
.also { list ->
val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" }
Log.v("#E2E TEST", "Timeline snapshot is $message")
}
.filter { it.root.sendState == SendState.SYNCED }
.firstOrNull { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(text) == true }
if (decryptedMsg != null) {
timeline.dispose()
messageSent.complete(decryptedMsg.eventId)
}
}
})
return withTimeout(TestConstants.timeOutMillis) { messageSent.await() }
}
suspend fun ensureMessage(room: Room, eventId: String, block: ((event: TimelineEvent) -> Boolean)) {
val timeline = room.timelineService().createTimeline(null, TimelineSettings(60))
timeline.start()
val messageSent = CompletableDeferred<Unit>()
timeline.addListener(object : Timeline.Listener {
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
val success = timeline.getSnapshot()
.filter { it.root.getClearType() == EventType.MESSAGE }
.also { list ->
val message = list.joinToString(",", "[", "]") {
"${it.root.type}|${it.root.getClearType()}|${it.root.sendState}|${it.root.mxDecryptionResult?.verificationState}"
}
Log.v("#E2E TEST", "Timeline snapshot is $message")
}
.firstOrNull { it.eventId == eventId }
?.let {
block(it)
} ?: false
if (success) {
messageSent.complete(Unit)
timeline.dispose()
}
}
})
return withTimeout(TestConstants.timeOutMillis) {
messageSent.await()
}
}
fun ensureMessagePromise(room: Room, eventId: String, block: ((event: TimelineEvent) -> Boolean)): CompletableDeferred<Unit> {
val timeline = room.timelineService().createTimeline(null, TimelineSettings(60))
timeline.start()
val messageSent = CompletableDeferred<Unit>()
timeline.addListener(object : Timeline.Listener {
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
val success = timeline.getSnapshot()
.filter { it.root.getClearType() == EventType.MESSAGE }
.also { list ->
val message = list.joinToString(",", "[", "]") {
"${it.root.type}|${it.root.getClearType()}|${it.root.sendState}|${it.root.mxDecryptionResult?.verificationState}"
}
Log.v("#E2E TEST", "Promise Timeline snapshot is $message")
}
.firstOrNull { it.eventId == eventId }
?.let {
block(it)
} ?: false
if (success) {
messageSent.complete(Unit)
timeline.dispose()
}
}
})
return messageSent
}
/**
* Will send nb of messages provided by count parameter but waits every 10 messages to avoid gap in sync
*/

View file

@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.crypto
import android.util.Log
import androidx.test.filters.LargeTest
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.delay
import org.amshove.kluent.fail
import org.amshove.kluent.internal.assertEquals
@ -36,18 +35,14 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.model.MessageVerificationState
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
@ -94,22 +89,16 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Alice is sending the message")
val text = "This is my message"
val sentEventId: String? = sendMessageInRoom(aliceRoomPOV, text)
Assert.assertTrue("Message should be sent", sentEventId != null)
val sentEventId: String = testHelper.sendMessageInRoom(aliceRoomPOV, text)
Log.v("#E2E TEST", "Alice just sent message with id:$sentEventId")
// All should be able to decrypt
otherAccounts.forEach { otherSession ->
testHelper.retryWithBackoff(
onFail = {
fail("${otherSession.myUserId.take(10)} should be able to decrypt")
}) {
val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}|${it?.root?.mxDecryptionResult?.isSafe}")
}
timeLineEvent != null &&
timeLineEvent.isEncrypted() &&
timeLineEvent.root.getClearType() == EventType.MESSAGE &&
timeLineEvent.root.mxDecryptionResult?.isSafe == true
val room = otherSession.getRoom(e2eRoomID)!!
testHelper.ensureMessage(room, sentEventId) {
it.isEncrypted() &&
it.root.getClearType() == EventType.MESSAGE &&
it.root.mxDecryptionResult?.verificationState == MessageVerificationState.UN_SIGNED_DEVICE
}
}
Log.v("#E2E TEST", "Everybody received the encrypted message and could decrypt")
@ -143,7 +132,7 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Alice sends a new message")
val secondMessage = "2 This is my message"
val secondSentEventId: String? = sendMessageInRoom(aliceRoomPOV, secondMessage)
val secondSentEventId: String = testHelper.sendMessageInRoom(aliceRoomPOV, secondMessage)
// new members should be able to decrypt it
newAccount.forEach { otherSession ->
@ -206,7 +195,7 @@ class E2eeSanityTests : InstrumentedTest {
val sentEventIds = mutableListOf<String>()
val messagesText = listOf("1. Hello", "2. Bob", "3. Good morning")
messagesText.forEach { text ->
val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
val sentEventId = testHelper.sendMessageInRoom(aliceRoomPOV, text).also {
sentEventIds.add(it)
}
@ -297,7 +286,7 @@ class E2eeSanityTests : InstrumentedTest {
sentEventIds.forEach { sentEventId ->
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)!!
val result = newBobSession.cryptoService().decryptEvent(timelineEvent.root, "")
assertEquals("Keys from history should be deniable", false, result.isSafe)
assertEquals("Keys from history should be deniable", MessageVerificationState.UNSAFE_SOURCE, result.messageVerificationState)
}
}
@ -321,7 +310,7 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Alice sends some messages")
messagesText.forEach { text ->
val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
val sentEventId = testHelper.sendMessageInRoom(aliceRoomPOV, text).also {
sentEventIds.add(it)
}
@ -404,7 +393,7 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Alice sends some messages")
firstMessage.let { text ->
firstEventId = sendMessageInRoom(aliceRoomPOV, text)!!
firstEventId = testHelper.sendMessageInRoom(aliceRoomPOV, text)
testHelper.retryWithBackoff {
val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
@ -430,7 +419,7 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Alice sends some messages")
secondMessage.let { text ->
secondEventId = sendMessageInRoom(aliceRoomPOV, text)!!
secondEventId = testHelper.sendMessageInRoom(aliceRoomPOV, text)
testHelper.retryWithBackoff {
val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
@ -493,32 +482,6 @@ class E2eeSanityTests : InstrumentedTest {
}
}
private suspend fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? {
aliceRoomPOV.sendService().sendTextMessage(text)
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
timeline.start()
val messageSent = CompletableDeferred<String>()
timeline.addListener(object : Timeline.Listener {
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
val decryptedMsg = timeline.getSnapshot()
.filter { it.root.getClearType() == EventType.MESSAGE }
.also { list ->
val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" }
Log.v("#E2E TEST", "Timeline snapshot is $message")
}
.filter { it.root.sendState == SendState.SYNCED }
.firstOrNull { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(text) == true }
if (decryptedMsg != null) {
timeline.dispose()
messageSent.complete(decryptedMsg.eventId)
}
}
})
return messageSent.await()
}
/**
* Test that if a better key is forwared (lower index, it is then used)
*/
@ -632,7 +595,7 @@ class E2eeSanityTests : InstrumentedTest {
val roomFromAlicePOV = aliceSession.getRoom(cryptoTestData.roomId)!!
Timber.v("#TEST: Send a first message that should be withheld")
val sentEvent = sendMessageInRoom(roomFromAlicePOV, "Hello")!!
val sentEvent = testHelper.sendMessageInRoom(roomFromAlicePOV, "Hello")
// wait for it to be synced back the other side
Timber.v("#TEST: Wait for message to be synced back")
@ -655,7 +618,7 @@ class E2eeSanityTests : InstrumentedTest {
Timber.v("#TEST: Send a second message, outbound session should have rotated and only bob 1rst session should decrypt")
val secondEvent = sendMessageInRoom(roomFromAlicePOV, "World")!!
val secondEvent = testHelper.sendMessageInRoom(roomFromAlicePOV, "World")
Timber.v("#TEST: Wait for message to be synced back")
testHelper.retryWithBackoff {
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null

View file

@ -0,0 +1,98 @@
/*
* Copyright 2023 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
import android.util.Log
import androidx.test.filters.LargeTest
import junit.framework.Assert.fail
import kotlinx.coroutines.delay
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.model.MessageVerificationState
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.SessionTestParams
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@LargeTest
class E2eeTestVerificationTestDirty : InstrumentedTest {
@Test
fun testVerificationStateRefreshedAfterKeyDownload() = CommonTestHelper.runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
val e2eRoomID = cryptoTestData.roomId
// We are going to setup a second session for bob that will send a message while alice session
// has stopped syncing.
aliceSession.syncService().stopSync()
aliceSession.syncService().stopAnyBackgroundSync()
// wait a bit for session to be really closed
delay(1_000)
Log.v("#E2E TEST", "Create a new session for Bob")
val newBobSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
Log.v("#E2E TEST", "New bob session will send a message")
val eventId = testHelper.sendMessageInRoom(newBobSession.getRoom(e2eRoomID)!!, "I am unknown")
aliceSession.syncService().startSync(true)
// Check without starting a timeline so that it doesn't update itself
testHelper.retryWithBackoff(
onFail = {
fail("${aliceSession.myUserId.take(10)} should not have downloaded the device at time of decryption")
}) {
val timeLineEvent = aliceSession.getRoom(e2eRoomID)?.getTimelineEvent(eventId).also {
Log.v("#E2E TEST", "Verification state is ${it?.root?.mxDecryptionResult?.verificationState}")
}
timeLineEvent != null &&
timeLineEvent.isEncrypted() &&
timeLineEvent.root.getClearType() == EventType.MESSAGE &&
timeLineEvent.root.mxDecryptionResult?.verificationState == MessageVerificationState.UNKNOWN_DEVICE
}
// After key download it should be dirty (that will happen after sync completed)
testHelper.retryWithBackoff(
onFail = {
fail("${aliceSession.myUserId.take(10)} should be dirty")
}) {
val timeLineEvent = aliceSession.getRoom(e2eRoomID)?.getTimelineEvent(eventId).also {
Log.v("#E2E TEST", "Is verification state dirty ${it?.root?.verificationStateIsDirty}")
}
timeLineEvent?.root?.verificationStateIsDirty.orFalse()
}
Log.v("#E2E TEST", "Start timeline and check that verification state is updated")
// eventually should be marked as dirty then have correct state when a timeline is started
testHelper.ensureMessage(aliceSession.getRoom(e2eRoomID)!!, eventId) {
it.isEncrypted() &&
it.root.getClearType() == EventType.MESSAGE &&
it.root.mxDecryptionResult?.verificationState == MessageVerificationState.UN_SIGNED_DEVICE
}
}
}

View file

@ -189,7 +189,7 @@ internal class DefaultCryptoService @Inject constructor(
private val liveEventManager: Lazy<StreamEventsManager>,
private val unrequestedForwardManager: UnRequestedForwardManager,
private val cryptoSyncHandler: CryptoSyncHandler,
) : CryptoService {
) : CryptoService, DeviceListManager.UserDevicesUpdateListener {
private val isStarting = AtomicBoolean(false)
private val isStarted = AtomicBoolean(false)
@ -312,6 +312,7 @@ internal class DefaultCryptoService @Inject constructor(
fetchDevicesList()
}
cryptoStore.tidyUpDataBase()
deviceListManager.addListener(this@DefaultCryptoService)
}
}
@ -375,6 +376,7 @@ internal class DefaultCryptoService @Inject constructor(
* Close the crypto.
*/
override fun close() = runBlocking(coroutineDispatchers.crypto) {
deviceListManager.removeListener(this@DefaultCryptoService)
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
incomingKeyRequestManager.close()
outgoingKeyRequestManager.close()
@ -1309,6 +1311,10 @@ internal class DefaultCryptoService @Inject constructor(
override fun removeSessionListener(listener: NewSessionListener) {
roomDecryptorProvider.removeSessionListener(listener)
}
override fun onUsersDeviceUpdate(userIds: List<String>) {
cryptoSessionInfoProvider.markMessageVerificationStateAsDirty(userIds)
}
/* ==========================================================================================
* DEBUG INFO
* ========================================================================================== */

View file

@ -108,6 +108,9 @@ data class Event(
@Transient
var threadDetails: ThreadDetails? = null
@Transient
var verificationStateIsDirty: Boolean? = null
fun sendStateError(): MatrixError? {
return sendStateDetails?.let {
val matrixErrorAdapter = MoshiProvider.providesMoshi().adapter(MatrixError::class.java)

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.database.mapper.EventMapper
import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.EventEntityFields
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
@ -100,6 +101,23 @@ internal class CryptoSessionInfoProvider @Inject constructor(
return roomIds.orEmpty()
}
fun markMessageVerificationStateAsDirty(userList: List<String>) {
monarchy.writeAsync { sessionRealm ->
sessionRealm.where(EventEntity::class.java)
.`in`(EventEntityFields.SENDER, userList.toTypedArray())
.equalTo(EventEntityFields.TYPE, EventType.ENCRYPTED)
.isNotNull(EventEntityFields.DECRYPTION_RESULT_JSON)
// // A bit annoying to have to do that like that and it could break :/
// .contains(EventEntityFields.DECRYPTION_RESULT_JSON, "\"verification_state\":\"UNKNOWN_DEVICE\"")
.findAll()
.onEach {
it.isVerificationStateDirty = true
}
.map { EventMapper.map(it) }
.also { Timber.v("## VerificationState refresh - ... impacted events ${it.joinToString{ it.eventId.orEmpty() }}") }
}
}
fun updateShieldForRoom(roomId: String, shield: RoomEncryptionTrustLevel?) {
monarchy.writeAsync { realm ->
val summary = RoomSummaryEntity.where(realm, roomId = roomId).findFirst()

View file

@ -67,6 +67,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo049
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo050
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo051
import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject
@ -75,7 +76,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : MatrixRealmMigration(
dbName = "Session",
schemaVersion = 50L,
schemaVersion = 51L,
) {
/**
* Forces all RealmSessionStoreMigration instances to be equal.
@ -135,5 +136,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 48) MigrateSessionTo048(realm).perform()
if (oldVersion < 49) MigrateSessionTo049(realm).perform()
if (oldVersion < 50) MigrateSessionTo050(realm).perform()
if (oldVersion < 51) MigrateSessionTo051(realm).perform()
}
}

View file

@ -54,6 +54,7 @@ internal object EventMapper {
eventEntity.decryptionResultJson = event.mxDecryptionResult?.let {
MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(it)
}
eventEntity.isVerificationStateDirty = event.verificationStateIsDirty
eventEntity.decryptionErrorReason = event.mCryptoErrorReason
eventEntity.decryptionErrorCode = event.mCryptoError?.name
eventEntity.isRootThread = event.threadDetails?.isRootThread ?: false
@ -93,6 +94,7 @@ internal object EventMapper {
eventEntity.decryptionResultJson?.let { json ->
try {
it.mxDecryptionResult = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).fromJson(json)
it.verificationStateIsDirty = eventEntity.isVerificationStateDirty
} catch (t: JsonDataException) {
Timber.e(t, "Failed to parse decryption result")
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 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.database.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.database.model.EventEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
/**
* As we compute message e2ee verification state at decryption time, it might get outdated.
* Adding a new field to mark a decryption state as dirty
*/
internal class MigrateSessionTo051(realm: DynamicRealm) : RealmMigrator(realm, 51) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.create("EventEntity")
.addField(EventEntityFields.IS_VERIFICATION_STATE_DIRTY, Boolean::class.java)
.setNullable(EventEntityFields.IS_VERIFICATION_STATE_DIRTY, true)
}
}

View file

@ -47,7 +47,8 @@ internal open class EventEntity(
@Index var rootThreadEventId: String? = null,
// Number messages within the thread
var numberOfThreads: Int = 0,
var threadSummaryLatestMessage: TimelineEventEntity? = null
var threadSummaryLatestMessage: TimelineEventEntity? = null,
var isVerificationStateDirty: Boolean? = null,
) : RealmObject() {
private var sendStateStr: String = SendState.UNKNOWN.name
@ -94,6 +95,7 @@ internal open class EventEntity(
decryptionResultJson = adapter.toJson(decryptionResult)
decryptionErrorCode = null
decryptionErrorReason = null
isVerificationStateDirty = false
// If we have an EventInsertEntity for the eventId we make sures it can be processed now.
realm.where(EventInsertEntity::class.java)

View file

@ -25,6 +25,7 @@ import io.realm.Sort
import kotlinx.coroutines.CompletableDeferred
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.model.MessageVerificationState
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.isReply
import org.matrix.android.sdk.api.session.room.timeline.Timeline
@ -420,13 +421,22 @@ internal class TimelineChunk(
}
fun decryptIfNeeded(timelineEvent: TimelineEvent) {
if (timelineEvent.isEncrypted() &&
timelineEvent.root.mxDecryptionResult == null) {
timelineEvent.root.eventId?.also { eventDecryptor.requestDecryption(TimelineEventDecryptor.DecryptionRequest(timelineEvent.root, timelineId)) }
}
if (!timelineEvent.isEncrypted() && !lightweightSettingsStorage.areThreadMessagesEnabled()) {
// Thread aware for not encrypted events
if (!timelineEvent.isEncrypted()) return
val mxDecryptionResult = timelineEvent.root.mxDecryptionResult
if (mxDecryptionResult == null) {
timelineEvent.root.eventId?.also { eventDecryptor.requestDecryption(TimelineEventDecryptor.DecryptionRequest(timelineEvent.root, timelineId)) }
} else if (timelineEvent.root.verificationStateIsDirty.orFalse() &&
mxDecryptionResult.verificationState == MessageVerificationState.UNKNOWN_DEVICE
) {
// The goal is to catch late download of devices
timelineEvent.root.eventId?.also {
eventDecryptor.requestDecryption(
TimelineEventDecryptor.DecryptionRequest(
timelineEvent.root,
timelineId
)
)
}
}
}

View file

@ -414,7 +414,7 @@ internal class RoomSyncHandler @Inject constructor(
// It's annoying roomId is not there, but lot of code rely on it.
// And had to do it now as copy would delete all decryption results..
val ageLocalTs = syncLocalTimestampMillis - (rawEvent.unsignedData?.age ?: 0)
val event = rawEvent.copy(roomId = roomId).also {
val event = rawEvent.copyAll(roomId = roomId).also {
it.ageLocalTs = ageLocalTs
}
if (event.eventId == null || event.senderId == null || event.type == null) {

View file

@ -24,6 +24,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.internal.crypto.ComputeShieldForGroupUseCase
@ -44,6 +45,7 @@ internal class OutgoingRequestsProcessor @Inject constructor(
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
private val computeShieldForGroup: ComputeShieldForGroupUseCase,
private val matrixConfiguration: MatrixConfiguration,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
) {
private val lock: Mutex = Mutex()
@ -136,6 +138,7 @@ internal class OutgoingRequestsProcessor @Inject constructor(
val response = requestSender.queryKeys(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_QUERY, response)
coroutineScope.updateShields(olmMachine, request.users)
coroutineScope.markMessageVerificationStatesAsDirty(request.users)
true
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## queryKeys(): error")
@ -143,7 +146,7 @@ internal class OutgoingRequestsProcessor @Inject constructor(
}
}
private fun CoroutineScope.updateShields(olmMachine: OlmMachine, userIds: List<String>) = launch {
private fun CoroutineScope.updateShields(olmMachine: OlmMachine, userIds: List<String>) = launch(coroutineDispatchers.computation) {
cryptoSessionInfoProvider.getRoomsWhereUsersAreParticipating(userIds).forEach { roomId ->
if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) {
val userGroup = cryptoSessionInfoProvider.getUserListForShieldComputation(roomId)
@ -155,6 +158,10 @@ internal class OutgoingRequestsProcessor @Inject constructor(
}
}
private fun CoroutineScope.markMessageVerificationStatesAsDirty(userIds: List<String>) = launch(coroutineDispatchers.computation) {
cryptoSessionInfoProvider.markMessageVerificationStateAsDirty(userIds)
}
private suspend fun sendToDevice(olmMachine: OlmMachine, request: Request.ToDevice): Boolean {
return try {
requestSender.sendToDevice(request)