diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt
index 1dedcce8b6..7f043dc0f7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt
@@ -19,7 +19,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
- *
+ * ```
* {
* "m.annotation": {
* "chunk": [
@@ -43,7 +43,7 @@ import com.squareup.moshi.JsonClass
* "count": 1
* }
* }
- *
+ * ```
*/
@JsonClass(generateAdapter = true)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/ValidDecryptedEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/ValidDecryptedEvent.kt
index 0cee077807..b305bf19b0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/ValidDecryptedEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/ValidDecryptedEvent.kt
@@ -16,6 +16,9 @@
package org.matrix.android.sdk.api.session.events.model
+import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
+import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
+
data class ValidDecryptedEvent(
val type: String,
val eventId: String,
@@ -29,25 +32,6 @@ data class ValidDecryptedEvent(
val algorithm: String,
)
-fun Event.toValidDecryptedEvent(): ValidDecryptedEvent? {
- if (!this.isEncrypted()) return null
- val decryptedContent = this.getDecryptedContent() ?: return null
- val eventId = this.eventId ?: return null
- val roomId = this.roomId ?: return null
- val type = this.getDecryptedType() ?: return null
- val senderKey = this.getSenderKey() ?: return null
- val algorithm = this.content?.get("algorithm") as? String ?: return null
-
- return ValidDecryptedEvent(
- type = type,
- eventId = eventId,
- clearContent = decryptedContent,
- prevContent = this.prevContent,
- originServerTs = this.originServerTs ?: 0,
- cryptoSenderKey = senderKey,
- roomId = roomId,
- unsignedData = this.unsignedData,
- redacts = this.redacts,
- algorithm = algorithm
- )
+fun ValidDecryptedEvent.getRelationContent(): RelationDefaultContent? {
+ return clearContent.toModel()?.relatesTo
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EditAggregatedSummaryEntityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EditAggregatedSummaryEntityMapper.kt
new file mode 100644
index 0000000000..8c209f2f2a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EditAggregatedSummaryEntityMapper.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 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.mapper
+
+import org.matrix.android.sdk.api.session.room.model.EditAggregatedSummary
+import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity
+import org.matrix.android.sdk.internal.database.model.EditionOfEvent
+
+internal object EditAggregatedSummaryEntityMapper {
+
+ fun map(summary: EditAggregatedSummaryEntity?): EditAggregatedSummary? {
+ summary ?: return null
+ /**
+ * The most recent event is determined by comparing origin_server_ts;
+ * if two or more replacement events have identical origin_server_ts,
+ * the event with the lexicographically largest event_id is treated as more recent.
+ */
+ val latestEdition = summary.editions.sortedWith(compareBy { it.timestamp }.thenBy { it.eventId })
+ .lastOrNull() ?: return null
+ val editEvent = latestEdition.event
+
+ return EditAggregatedSummary(
+ latestEdit = editEvent?.asDomain(),
+ sourceEvents = summary.editions.filter { editionOfEvent -> !editionOfEvent.isLocalEcho }
+ .map { editionOfEvent -> editionOfEvent.eventId },
+ localEchos = summary.editions.filter { editionOfEvent -> editionOfEvent.isLocalEcho }
+ .map { editionOfEvent -> editionOfEvent.eventId },
+ lastEditTs = latestEdition.timestamp
+ )
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
index 5fb70ad1ee..d4bb5791a0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
@@ -16,11 +16,9 @@
package org.matrix.android.sdk.internal.database.mapper
-import org.matrix.android.sdk.api.session.room.model.EditAggregatedSummary
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary
import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedSummary
-import org.matrix.android.sdk.internal.database.model.EditionOfEvent
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
internal object EventAnnotationsSummaryMapper {
@@ -36,27 +34,7 @@ internal object EventAnnotationsSummaryMapper {
it.sourceLocalEcho.toList()
)
},
- editSummary = annotationsSummary.editSummary
- ?.let { summary ->
- /**
- * The most recent event is determined by comparing origin_server_ts;
- * if two or more replacement events have identical origin_server_ts,
- * the event with the lexicographically largest event_id is treated as more recent.
- */
- val latestEdition = summary.editions.sortedWith(compareBy { it.timestamp }.thenBy { it.eventId })
- .lastOrNull() ?: return@let null
- // get the event and validate?
- val editEvent = latestEdition.event
-
- EditAggregatedSummary(
- latestEdit = editEvent?.asDomain(),
- sourceEvents = summary.editions.filter { editionOfEvent -> !editionOfEvent.isLocalEcho }
- .map { editionOfEvent -> editionOfEvent.eventId },
- localEchos = summary.editions.filter { editionOfEvent -> editionOfEvent.isLocalEcho }
- .map { editionOfEvent -> editionOfEvent.eventId },
- lastEditTs = latestEdition.timestamp
- )
- },
+ editSummary = EditAggregatedSummaryEntityMapper.map(annotationsSummary.editSummary),
referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
ReferencesAggregatedSummary(
ContentMapper.map(it.content),
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt
index 42a47a9a27..f85a0661c2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt
@@ -25,11 +25,11 @@ internal class MigrateSessionTo008(realm: DynamicRealm) : RealmMigrator(realm, 8
override fun doMigrate(realm: DynamicRealm) {
val editionOfEventSchema = realm.schema.create("EditionOfEvent")
- .addField("content"/**EditionOfEventFields.CONTENT*/, String::class.java)
+ .addField("content", String::class.java)
.addField(EditionOfEventFields.EVENT_ID, String::class.java)
.setRequired(EditionOfEventFields.EVENT_ID, true)
- .addField("senderId" /*EditionOfEventFields.SENDER_ID*/, String::class.java)
- .setRequired("senderId" /*EditionOfEventFields.SENDER_ID*/, true)
+ .addField("senderId", String::class.java)
+ .setRequired("senderId", true)
.addField(EditionOfEventFields.TIMESTAMP, Long::class.java)
.addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt
index 91e709e464..63409a15bb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt
@@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.events
import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.ValidDecryptedEvent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
@@ -33,3 +34,32 @@ internal fun Event.getFixedRoomMemberContent(): RoomMemberContent? {
content
}
}
+
+fun Event.toValidDecryptedEvent(): ValidDecryptedEvent? {
+ if (!this.isEncrypted()) return null
+ val decryptedContent = this.getDecryptedContent() ?: return null
+ val eventId = this.eventId ?: return null
+ val roomId = this.roomId ?: return null
+ val type = this.getDecryptedType() ?: return null
+ val senderKey = this.getSenderKey() ?: return null
+ val algorithm = this.content?.get("algorithm") as? String ?: return null
+
+ // copy the relation as it's in clear in the encrypted content
+ val updatedContent = this.content.get("m.relates_to")?.let {
+ decryptedContent.toMutableMap().apply {
+ put("m.relates_to", it)
+ }
+ } ?: decryptedContent
+ return ValidDecryptedEvent(
+ type = type,
+ eventId = eventId,
+ clearContent = updatedContent,
+ prevContent = this.prevContent,
+ originServerTs = this.originServerTs ?: 0,
+ cryptoSenderKey = senderKey,
+ roomId = roomId,
+ unsignedData = this.unsignedData,
+ redacts = this.redacts,
+ algorithm = algorithm
+ )
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt
index 940da25f11..ea14aacfe4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt
@@ -17,11 +17,14 @@
package org.matrix.android.sdk.internal.session.room
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.events.model.RelationType
+import org.matrix.android.sdk.api.session.events.model.getRelationContent
import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.session.events.toValidDecryptedEvent
+import timber.log.Timber
import javax.inject.Inject
internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCryptoStore) {
@@ -47,12 +50,18 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto
* If the original event was encrypted, the replacement should be too.
*/
fun validateEdit(originalEvent: Event?, replaceEvent: Event): EditValidity {
+ Timber.v("###REPLACE valide event $originalEvent replaced $replaceEvent")
// we might not know the original event at that time. In this case we can't perform the validation
// Edits should be revalidated when the original event is received
if (originalEvent == null) {
return EditValidity.Unknown
}
+ if (LocalEcho.isLocalEchoId(replaceEvent.eventId.orEmpty())) {
+ // Don't validate local echo
+ return EditValidity.Unknown
+ }
+
if (originalEvent.roomId != replaceEvent.roomId) {
return EditValidity.Invalid("original event and replacement event must have the same room_id")
}
@@ -71,7 +80,7 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto
val originalCryptoSenderId = cryptoStore.deviceWithIdentityKey(originalDecrypted.cryptoSenderKey)?.userId
val editCryptoSenderId = cryptoStore.deviceWithIdentityKey(replaceDecrypted.cryptoSenderKey)?.userId
- if (originalDecrypted.clearContent.toModel()?.relatesTo?.type == RelationType.REPLACE) {
+ if (originalDecrypted.getRelationContent()?.type == RelationType.REPLACE) {
return EditValidity.Invalid("The original event must not, itself, have a rel_type of m.replace ")
}
@@ -96,7 +105,7 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto
return EditValidity.Invalid("replacement event must have an m.new_content property")
}
} else {
- if (originalEvent.content.toModel()?.relatesTo?.type == RelationType.REPLACE) {
+ if (originalEvent.getRelationContent()?.type == RelationType.REPLACE) {
return EditValidity.Invalid("The original event must not, itself, have a rel_type of m.replace ")
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index 837d00720b..48e75821bd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -23,6 +23,7 @@ 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.RelationType
+import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.getRelationContent
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
@@ -81,13 +82,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
EventType.REDACTION,
EventType.REACTION,
// The aggregator handles verification events but just to render tiles in the timeline
- // It's not participating in verfication it's self, just timeline display
+ // It's not participating in verification itself, just timeline display
EventType.KEY_VERIFICATION_DONE,
EventType.KEY_VERIFICATION_CANCEL,
EventType.KEY_VERIFICATION_ACCEPT,
EventType.KEY_VERIFICATION_START,
EventType.KEY_VERIFICATION_MAC,
- // TODO Add ?
EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_KEY,
EventType.ENCRYPTED
@@ -168,28 +168,28 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
// As for now Live event processors are not receiving UTD events.
// They will get an update if the event is decrypted later
EventType.ENCRYPTED -> {
-// // Relation type is in clear, it might be possible to do some things?
-// // Notice that if the event is decrypted later, process be called again
-// val encryptedEventContent = event.content.toModel()
-// when (encryptedEventContent?.relatesTo?.type) {
-// RelationType.REPLACE -> {
-// Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
-// // A replace!
-// handleReplace(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
-// }
-// RelationType.RESPONSE -> {
-// // can we / should we do we something for UTD response??
-// Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
-// }
-// RelationType.REFERENCE -> {
-// // can we / should we do we something for UTD reference??
-// Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
-// }
-// RelationType.ANNOTATION -> {
-// // can we / should we do we something for UTD annotation??
-// Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
-// }
-// }
+ // Relation type is in clear, it might be possible to do some things?
+ // Notice that if the event is decrypted later, process be called again
+ val encryptedEventContent = event.content.toModel()
+ when (encryptedEventContent?.relatesTo?.type) {
+ RelationType.REPLACE -> {
+ Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
+ // A replace!
+ handleReplace(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
+ }
+ RelationType.RESPONSE -> {
+ // can we / should we do we something for UTD response??
+ Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
+ }
+ RelationType.REFERENCE -> {
+ // can we / should we do we something for UTD reference??
+ Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
+ }
+ RelationType.ANNOTATION -> {
+ // can we / should we do we something for UTD annotation??
+ Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
+ }
+ }
}
EventType.REDACTION -> {
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
@@ -256,7 +256,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
relatedEventId: String?
) {
val eventId = event.eventId ?: return
- val targetEventId = relatedEventId ?: return // ?: content.relatesTo?.eventId ?: return
+ val targetEventId = relatedEventId ?: return
val editedEvent = EventEntity.where(realm, targetEventId).findFirst()
when (val validity = editValidator.validateEdit(editedEvent?.asDomain(), event)) {
@@ -301,15 +301,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
// ok it has already been managed
Timber.v("###REPLACE Receiving remote echo of edit (edit already done)")
existingSummary.editions.firstOrNull { it.eventId == txId }?.let {
- it.eventId = event.eventId
+ it.eventId = eventId
it.timestamp = event.originServerTs ?: clock.epochMillis()
it.isLocalEcho = false
+ it.event = EventEntity.where(realm, eventId).findFirst()
}
} else {
Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)")
existingSummary.editions.add(
EditionOfEvent(
- eventId = event.eventId,
+ eventId = eventId,
event = EventEntity.where(realm, eventId).findFirst(),
timestamp = if (isLocalEcho) {
clock.epochMillis()
@@ -343,7 +344,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
* @param editions list of edition of event
*/
private fun handleThreadSummaryEdition(
- editedEvent: EventEntity?, replaceEvent: TimelineEventEntity?,
+ editedEvent: EventEntity?,
+ replaceEvent: TimelineEventEntity?,
editions: List?
) {
replaceEvent ?: return
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/EditAggregationSummaryMapperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/EditAggregationSummaryMapperTest.kt
new file mode 100644
index 0000000000..12ff9c1d37
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/EditAggregationSummaryMapperTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.database.mapper
+
+import io.mockk.every
+import io.mockk.mockk
+import io.realm.RealmList
+import org.amshove.kluent.shouldBe
+import org.amshove.kluent.shouldNotBe
+import org.junit.Test
+import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity
+import org.matrix.android.sdk.internal.database.model.EditionOfEvent
+import org.matrix.android.sdk.internal.database.model.EventEntity
+
+class EditAggregationSummaryMapperTest {
+
+ @Test
+ fun test() {
+ val edits = RealmList(
+ EditionOfEvent(
+ timestamp = 0L,
+ eventId = "e0",
+ isLocalEcho = false,
+ event = mockEvent("e0")
+ ),
+ EditionOfEvent(
+ timestamp = 1L,
+ eventId = "e1",
+ isLocalEcho = false,
+ event = mockEvent("e1")
+ ),
+ EditionOfEvent(
+ timestamp = 30L,
+ eventId = "e2",
+ isLocalEcho = true,
+ event = mockEvent("e2")
+ )
+ )
+ val fakeSummaryEntity = mockk {
+ every { editions } returns edits
+ }
+
+ val mapped = EditAggregatedSummaryEntityMapper.map(fakeSummaryEntity)
+ mapped shouldNotBe null
+ mapped!!.sourceEvents.size shouldBe 2
+ mapped.localEchos.size shouldBe 1
+ mapped.localEchos.first() shouldBe "e2"
+
+ mapped.lastEditTs shouldBe 30L
+ mapped.latestEdit?.eventId shouldBe "e2"
+ }
+
+ private fun mockEvent(eventId: String): EventEntity {
+ return EventEntity().apply {
+ this.eventId = eventId
+ this.content = """
+ {
+ "body" : "Hello",
+ "msgtype": "text"
+ }
+ """.trimIndent()
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/event/ValidDecryptedEventTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/event/ValidDecryptedEventTest.kt
new file mode 100644
index 0000000000..8dead42f60
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/event/ValidDecryptedEventTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.session.event
+
+import org.amshove.kluent.shouldBe
+import org.amshove.kluent.shouldNotBe
+import org.junit.Test
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
+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.content.EncryptedEventContent
+import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
+import org.matrix.android.sdk.internal.session.events.toValidDecryptedEvent
+
+class ValidDecryptedEventTest {
+
+ val fakeEvent = Event(
+ type = EventType.ENCRYPTED,
+ eventId = "\$eventId",
+ roomId = "!fakeRoom",
+ content = EncryptedEventContent(
+ algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+ ciphertext = "AwgBEpACQEKOkd4Gp0+gSXG4M+btcrnPgsF23xs/lUmS2I4YjmqF...",
+ sessionId = "TO2G4u2HlnhtbIJk",
+ senderKey = "5e3EIqg3JfooZnLQ2qHIcBarbassQ4qXblai0",
+ deviceId = "FAKEE"
+ ).toContent()
+ )
+
+ @Test
+ fun `A failed to decrypt message should give a null validated decrypted event`() {
+ fakeEvent.toValidDecryptedEvent() shouldBe null
+ }
+
+ @Test
+ fun `Mismatch sender key detection`() {
+ val decryptedEvent = fakeEvent
+ .apply {
+ mxDecryptionResult = OlmDecryptionResult(
+ payload = mapOf(
+ "type" to EventType.MESSAGE,
+ "content" to mapOf(
+ "body" to "some message",
+ "msgtype" to "m.text"
+ ),
+ ),
+ senderKey = "the_real_sender_key",
+ )
+ }
+
+ val validDecryptedEvent = decryptedEvent.toValidDecryptedEvent()
+ validDecryptedEvent shouldNotBe null
+
+ fakeEvent.content!!["senderKey"] shouldNotBe "the_real_sender_key"
+ validDecryptedEvent!!.cryptoSenderKey shouldBe "the_real_sender_key"
+ }
+
+ @Test
+ fun `Mixed content event should be detected`() {
+ val mixedEvent = Event(
+ type = EventType.ENCRYPTED,
+ eventId = "\$eventd ",
+ roomId = "!fakeRoo",
+ content = mapOf(
+ "algorithm" to "m.megolm.v1.aes-sha2",
+ "ciphertext" to "AwgBEpACQEKOkd4Gp0+gSXG4M+btcrnPgsF23xs/lUmS2I4YjmqF...",
+ "sessionId" to "TO2G4u2HlnhtbIJk",
+ "senderKey" to "5e3EIqg3JfooZnLQ2qHIcBarbassQ4qXblai0",
+ "deviceId" to "FAKEE",
+ "body" to "some message",
+ "msgtype" to "m.text"
+ ).toContent()
+ )
+
+ val unValidatedContent = mixedEvent.getClearContent().toModel()
+ unValidatedContent?.body shouldBe "some message"
+
+ mixedEvent.toValidDecryptedEvent()?.clearContent?.toModel() shouldBe null
+ }
+
+ @Test
+ fun `Basic field validation`() {
+ val decryptedEvent = fakeEvent
+ .apply {
+ mxDecryptionResult = OlmDecryptionResult(
+ payload = mapOf(
+ "type" to EventType.MESSAGE,
+ "content" to mapOf(
+ "body" to "some message",
+ "msgtype" to "m.text"
+ ),
+ ),
+ senderKey = "the_real_sender_key",
+ )
+ }
+
+ decryptedEvent.toValidDecryptedEvent() shouldNotBe null
+ decryptedEvent.copy(roomId = null).toValidDecryptedEvent() shouldBe null
+ decryptedEvent.copy(eventId = null).toValidDecryptedEvent() shouldBe null
+ }
+
+ @Test
+ fun `A clear event is not a valid decrypted event`() {
+ val mockTextEvent = Event(
+ type = EventType.MESSAGE,
+ eventId = "eventId",
+ roomId = "!fooe:example.com",
+ content = mapOf(
+ "body" to "some message",
+ "msgtype" to "m.text"
+ ),
+ originServerTs = 1000,
+ senderId = "@anne:example.com",
+ )
+ mockTextEvent.toValidDecryptedEvent() shouldBe null
+ }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EditValidationTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventEditValidatorTest.kt
similarity index 96%
rename from matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EditValidationTest.kt
rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventEditValidatorTest.kt
index 429e6625ab..0ae712bff1 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EditValidationTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventEditValidatorTest.kt
@@ -26,7 +26,7 @@ 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.internal.crypto.store.IMXCryptoStore
-class EditValidationTest {
+class EventEditValidatorTest {
private val mockTextEvent = Event(
type = EventType.MESSAGE,
@@ -180,17 +180,23 @@ class EditValidationTest {
validator
.validateEdit(
- encryptedEvent.copy().apply {
+ encryptedEvent.copy(
+ content = encryptedEvent.content!!.toMutableMap().apply {
+ put(
+ "m.relates_to",
+ mapOf(
+ "rel_type" to "m.replace",
+ "event_id" to mockTextEvent.eventId
+ )
+ )
+ }
+ ).apply {
mxDecryptionResult = encryptedEditEvent.mxDecryptionResult!!.copy(
payload = mapOf(
"type" to EventType.MESSAGE,
"content" to mapOf(
"body" to "some message",
"msgtype" to "m.text",
- "m.relates_to" to mapOf(
- "rel_type" to "m.replace",
- "event_id" to mockTextEvent.eventId
- )
),
)
)
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt
index f5545b7e76..9ad7032262 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.test.fakes
import io.mockk.coEvery
import io.mockk.mockk
import io.mockk.mockkStatic
-import io.mockk.slot
import io.realm.Realm
import io.realm.RealmConfiguration
import org.matrix.android.sdk.internal.database.awaitTransaction
@@ -33,8 +32,7 @@ internal class FakeRealmConfiguration {
val instance = mockk()
fun givenAwaitTransaction(realm: Realm) {
- val transaction = slot<(Realm) -> T>()
- coEvery { awaitTransaction(instance, capture(transaction)) } answers {
+ coEvery { awaitTransaction(instance, any<(Realm) -> T>()) } answers {
secondArg<(Realm) -> T>().invoke(realm)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index d5d38f47a3..373410775b 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -161,7 +161,7 @@ class MessageItemFactory @Inject constructor(
val callback = params.callback
event.root.eventId ?: return null
roomId = event.roomId
- val informationData = messageInformationDataFactory.create(params, params.event.annotations?.editSummary?.latestEdit)
+ val informationData = messageInformationDataFactory.create(params)
val threadDetails = if (params.isFromThreadTimeline()) null else event.root.threadDetails
if (event.root.isRedacted()) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
index a969c294f5..3c8b342a1a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
@@ -37,7 +37,6 @@ import org.matrix.android.sdk.api.session.events.model.getMsgType
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
import org.matrix.android.sdk.api.session.events.model.isSticker
import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent
import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageType
@@ -45,6 +44,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
+import org.matrix.android.sdk.internal.session.events.toValidDecryptedEvent
import javax.inject.Inject
/**
@@ -57,7 +57,7 @@ class MessageInformationDataFactory @Inject constructor(
private val reactionsSummaryFactory: ReactionsSummaryFactory
) {
- fun create(params: TimelineItemFactoryParams, lastEdit: Event? = null): MessageInformationData {
+ fun create(params: TimelineItemFactoryParams): MessageInformationData {
val event = params.event
val nextDisplayableEvent = params.nextDisplayableEvent
val prevDisplayableEvent = params.prevDisplayableEvent
@@ -74,14 +74,8 @@ class MessageInformationDataFactory @Inject constructor(
prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
- val e2eDecoration = getE2EDecoration(roomSummary, lastEdit ?: event.root)
- val senderId = if (event.isEncrypted()) {
- event.root.toValidDecryptedEvent()?.let {
- session.cryptoService().deviceWithIdentityKey(it.cryptoSenderKey, it.algorithm)?.userId
- } ?: event.root.senderId.orEmpty()
- } else {
- event.root.senderId.orEmpty()
- }
+ val e2eDecoration = getE2EDecoration(roomSummary, params.lastEdit ?: event.root)
+ val senderId = getSenderId(event)
// SendState Decoration
val sendStateDecoration = if (isSentByMe) {
getSendStateDecoration(
@@ -139,6 +133,14 @@ class MessageInformationDataFactory @Inject constructor(
)
}
+ private fun getSenderId(event: TimelineEvent) = if (event.isEncrypted()) {
+ event.root.toValidDecryptedEvent()?.let {
+ session.cryptoService().deviceWithIdentityKey(it.cryptoSenderKey, it.algorithm)?.userId
+ } ?: event.root.senderId.orEmpty()
+ } else {
+ event.root.senderId.orEmpty()
+ }
+
private fun getSendStateDecoration(
event: TimelineEvent,
lastSentEventWithoutReadReceipts: String?,