diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index f02b7d31..a79b306b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -20,12 +20,11 @@ import android.text.TextUtils import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.crypto.MXCryptoError +import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.util.JsonDict -import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult +import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult import im.vector.matrix.android.internal.di.MoshiProvider import timber.log.Timber -import java.util.* -import kotlin.collections.HashMap typealias Content = JsonDict @@ -79,6 +78,10 @@ data class Event( @Json(name = "redacts") val redacts: String? = null ) { + + var mxDecryptionResult: MXDecryptionResult? = null + var mCryptoError: MXCryptoError? = null + /** * Check if event is a state event. * @return true if event is state event. @@ -91,41 +94,41 @@ data class Event( // Crypto //============================================================================================================== - /** - * For encrypted events, the plaintext payload for the event. - * This is a small MXEvent instance with typically value for `type` and 'content' fields. - */ - @Transient - var mClearEvent: Event? = null - private set - - /** - * Curve25519 key which we believe belongs to the sender of the event. - * See `senderKey` property. - */ - @Transient - private var mSenderCurve25519Key: String? = null - - /** - * Ed25519 key which the sender of this event (for olm) or the creator of the megolm session (for megolm) claims to own. - * See `claimedEd25519Key` property. - */ - @Transient - private var mClaimedEd25519Key: String? = null - - /** - * Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key. - * See `forwardingCurve25519KeyChain` property. - */ - @Transient - private var mForwardingCurve25519KeyChain: List = ArrayList() - - /** - * Decryption error - */ - @Transient - var mCryptoError: MXCryptoError? = null - private set +// /** +// * For encrypted events, the plaintext payload for the event. +// * This is a small MXEvent instance with typically value for `type` and 'content' fields. +// */ +// @Transient +// var mClearEvent: Event? = null +// private set +// +// /** +// * Curve25519 key which we believe belongs to the sender of the event. +// * See `senderKey` property. +// */ +// @Transient +// private var mSenderCurve25519Key: String? = null +// +// /** +// * Ed25519 key which the sender of this event (for olm) or the creator of the megolm session (for megolm) claims to own. +// * See `claimedEd25519Key` property. +// */ +// @Transient +// private var mClaimedEd25519Key: String? = null +// +// /** +// * Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key. +// * See `forwardingCurve25519KeyChain` property. +// */ +// @Transient +// private var mForwardingCurve25519KeyChain: List = ArrayList() +// +// /** +// * Decryption error +// */ +// @Transient +// var mCryptoError: MXCryptoError? = null +// private set /** * @return true if this event is encrypted. @@ -140,88 +143,96 @@ data class Event( * * @param decryptionResult the decryption result, including the plaintext and some key info. */ - internal fun setClearData(decryptionResult: MXEventDecryptionResult) { - mClearEvent = null - mCryptoError = null - - val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java) - mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent) - - if (mClearEvent != null) { - mSenderCurve25519Key = decryptionResult.senderCurve25519Key - mClaimedEd25519Key = decryptionResult.claimedEd25519Key - mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain - - // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back - // in the clear event - try { - content?.get("m.relates_to")?.let { clearRelates -> - mClearEvent = mClearEvent?.copy( - content = HashMap(mClearEvent!!.content).apply { - this["m.relates_to"] = clearRelates - } - ) - } - } catch (e: Exception) { - Timber.e(e, "Unable to restore 'm.relates_to' the clear event") - } - } - } +// internal fun setClearData(decryptionResult: MXEventDecryptionResult?) { +// mClearEvent = null +// if (decryptionResult != null) { +// if (decryptionResult.clearEvent != null) { +// val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java) +// mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent) +// +// if (mClearEvent != null) { +// mSenderCurve25519Key = decryptionResult.senderCurve25519Key +// mClaimedEd25519Key = decryptionResult.claimedEd25519Key +// mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain +// +// // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back +// // in the clear event +// try { +// content?.get("m.relates_to")?.let { clearRelates -> +// mClearEvent = mClearEvent?.copy( +// content = HashMap(mClearEvent!!.content).apply { +// this["m.relates_to"] = clearRelates +// } +// ) +// } +// } catch (e: Exception) { +// Timber.e(e, "Unable to restore 'm.relates_to' the clear event") +// } +// } +// +// +// } +// } +// mCryptoError = null +// } /** * @return The curve25519 key that sent this event. */ fun getSenderKey(): String? { - return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key + return mxDecryptionResult?.senderKey + // return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key } /** * @return The additional keys the sender of this encrypted event claims to possess. */ fun getKeysClaimed(): Map { - val res = HashMap() - - val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key - - if (null != claimedEd25519Key) { - res["ed25519"] = claimedEd25519Key - } - - return res + return mxDecryptionResult?.keysClaimed ?: HashMap() +// val res = HashMap() +// +// val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key +// +// if (null != claimedEd25519Key) { +// res["ed25519"] = claimedEd25519Key +// } +// +// return res } - +// /** * @return the event type */ fun getClearType(): String { - return mClearEvent?.type ?: type + return mxDecryptionResult?.payload?.get("type")?.toString() + ?: type//get("type")?.toString() ?: type } /** * @return the event content */ fun getClearContent(): Content? { - return mClearEvent?.content ?: content + return mxDecryptionResult?.payload?.get("content") as? Content ?: content } - /** - * @return the linked crypto error - */ - fun getCryptoError(): MXCryptoError? { - return mCryptoError - } - - /** - * Update the linked crypto error - * - * @param error the new crypto error. - */ - fun setCryptoError(error: MXCryptoError?) { - mCryptoError = error - if (null != error) { - mClearEvent = null - } - } +// /** +// * @return the linked crypto error +// */ +// fun getCryptoError(): MXCryptoError? { +// return mCryptoError +// } +// +// /** +// * Update the linked crypto error +// * +// * @param error the new crypto error. +// */ +// fun setCryptoError(error: MXCryptoError?) { +// mCryptoError = error +// if (null != error) { +// mClearEvent = null +// } +// } /** * Tells if the event is redacted diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index 14b9e334..7dc7083d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.api.session.room.timeline +import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary @@ -34,7 +35,7 @@ data class TimelineEvent( val isUniqueDisplayName: Boolean, val senderAvatar: String?, val sendState: SendState, - val hasClearEventFlag: Boolean = false, +// val hasClearEventFlag: Boolean = false, val annotations: EventAnnotationsSummary? = null ) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt index 17df08e5..fcd6a848 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt @@ -17,10 +17,13 @@ package im.vector.matrix.android.internal.database.mapper import com.squareup.moshi.JsonDataException +import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.UnsignedData +import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.di.MoshiProvider +import timber.log.Timber import java.util.* internal object EventMapper { @@ -46,7 +49,6 @@ internal object EventMapper { } fun map(eventEntity: EventEntity): Event { - //TODO proxy the event to only parse unsigned data when accessed? val ud = if (eventEntity.unsignedData.isNullOrBlank()) { null } else { @@ -68,7 +70,17 @@ internal object EventMapper { roomId = eventEntity.roomId, unsignedData = ud, redacts = eventEntity.redacts - ) + ).also { + eventEntity.decryptionResultJson?.let { json -> + try { + it.mxDecryptionResult = MoshiProvider.providesMoshi().adapter(MXDecryptionResult::class.java).fromJson(json) + } catch (t: JsonDataException) { + Timber.e(t, "Failed to parse decryption result") + } + } + //TODO get the full crypto error object + it.mCryptoError = eventEntity.decryptionErrorCode?.let { MXCryptoError(it, it) } + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt index 27d4a11b..72057b39 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt @@ -16,10 +16,12 @@ package im.vector.matrix.android.internal.database.mapper +import com.squareup.moshi.Types import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.database.model.TimelineEventEntity +import im.vector.matrix.android.internal.di.MoshiProvider internal object TimelineEventMapper { @@ -33,17 +35,18 @@ internal object TimelineEventMapper { } fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent { + val listMapper = MoshiProvider.providesMoshi().adapter>(Types.newParameterizedType(List::class.java, String::class.java)) + return TimelineEvent( root = timelineEventEntity.root?.asDomain() - ?: Event("", timelineEventEntity.eventId), + ?: Event("", timelineEventEntity.eventId), annotations = timelineEventEntity.annotations?.asDomain(), localId = timelineEventEntity.localId, displayIndex = timelineEventEntity.root?.displayIndex ?: 0, senderName = timelineEventEntity.senderName, isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, senderAvatar = timelineEventEntity.senderAvatar, - sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN, - hasClearEventFlag = false + sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt index 91375a96..edb6bf61 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt @@ -17,7 +17,9 @@ package im.vector.matrix.android.internal.database.model import im.vector.matrix.android.api.session.room.send.SendState -import im.vector.matrix.android.api.session.room.timeline.Timeline +import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult +import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult +import im.vector.matrix.android.internal.di.MoshiProvider import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.Index @@ -39,7 +41,9 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI var redacts: String? = null, @Index var stateIndex: Int = 0, @Index var displayIndex: Int = 0, - @Index var isUnlinked: Boolean = false + @Index var isUnlinked: Boolean = false, + var decryptionResultJson: String? = null, + var decryptionErrorCode: String? = null ) : RealmObject() { enum class LinkFilterMode { @@ -68,4 +72,14 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI val timelineEventEntity: RealmResults? = null + fun setDecryptionResult(result: MXEventDecryptionResult) { + val decryptionResult = MXDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + val adapter = MoshiProvider.providesMoshi().adapter(MXDecryptionResult::class.java) + decryptionResultJson = adapter.toJson(decryptionResult) + } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt index bb910aec..8c42e4c9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt @@ -16,6 +16,10 @@ package im.vector.matrix.android.internal.database.model +import com.squareup.moshi.Types +import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult +import im.vector.matrix.android.internal.database.mapper.ContentMapper +import im.vector.matrix.android.internal.di.MoshiProvider import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.Index diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 542270c3..18664de6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -19,19 +19,15 @@ package im.vector.matrix.android.internal.session.room.timeline import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.util.CancelableBag +import im.vector.matrix.android.api.util.addTo import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.* -import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.RoomEntity -import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.findIncludingEvent import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where @@ -41,13 +37,7 @@ import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.Debouncer import im.vector.matrix.android.internal.util.createBackgroundHandler import im.vector.matrix.android.internal.util.createUIHandler -import io.realm.OrderedCollectionChangeSet -import io.realm.OrderedRealmCollectionChangeListener -import io.realm.Realm -import io.realm.RealmConfiguration -import io.realm.RealmQuery -import io.realm.RealmResults -import io.realm.Sort +import io.realm.* import timber.log.Timber import java.util.* import java.util.concurrent.atomic.AtomicBoolean @@ -67,7 +57,7 @@ internal class DefaultTimeline( private val taskExecutor: TaskExecutor, private val contextOfEventTask: GetContextOfEventTask, private val paginationTask: PaginationTask, - private val cryptoService: CryptoService, + cryptoService: CryptoService, private val allowedTypes: List? ) : Timeline { @@ -102,8 +92,12 @@ internal class DefaultTimeline( private val forwardsPaginationState = AtomicReference(PaginationState()) + private val timelineID = UUID.randomUUID().toString() + private lateinit var eventRelations: RealmResults + private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService) + private val eventsChangeListener = OrderedRealmCollectionChangeListener> { results, changeSet -> if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) { handleInitialLoad() @@ -172,32 +166,32 @@ internal class DefaultTimeline( postSnapshot() } - private val newSessionListener = object : NewSessionListener { - override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { - if (roomId == this@DefaultTimeline.roomId) { - Timber.v("New session id detected for this room") - BACKGROUND_HANDLER.post { - val realm = backgroundRealm.get() - var hasChange = false - builtEvents.forEachIndexed { index, timelineEvent -> - if (timelineEvent.isEncrypted()) { - val eventContent = timelineEvent.root.content.toModel() - if (eventContent?.sessionId == sessionId - && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) { - //we need to rebuild this event - EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let { - //builtEvents[index] = timelineEventFactory.create(it, realm) - hasChange = true - } - } - } - } - if (hasChange) postSnapshot() - } - } - } - - } +// private val newSessionListener = object : NewSessionListener { +// override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { +// if (roomId == this@DefaultTimeline.roomId) { +// Timber.v("New session id detected for this room") +// BACKGROUND_HANDLER.post { +// val realm = backgroundRealm.get() +// var hasChange = false +// builtEvents.forEachIndexed { index, timelineEvent -> +// if (timelineEvent.isEncrypted()) { +// val eventContent = timelineEvent.root.content.toModel() +// if (eventContent?.sessionId == sessionId +// && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) { +// //we need to rebuild this event +// EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let { +// //builtEvents[index] = timelineEventFactory.create(it, realm) +// hasChange = true +// } +// } +// } +// } +// if (hasChange) postSnapshot() +// } +// } +// } +// +// } // Public methods ****************************************************************************** @@ -219,7 +213,7 @@ internal class DefaultTimeline( override fun start() { if (isStarted.compareAndSet(false, true)) { Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId") - cryptoService.addNewSessionListener(newSessionListener) + eventDecryptor.start() BACKGROUND_HANDLER.post { val realm = Realm.getInstance(realmConfiguration) backgroundRealm.set(realm) @@ -247,7 +241,7 @@ internal class DefaultTimeline( override fun dispose() { if (isStarted.compareAndSet(true, false)) { - cryptoService.removeSessionListener(newSessionListener) + eventDecryptor.destroy() Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId") BACKGROUND_HANDLER.post { cancelableBag.cancel() @@ -387,9 +381,9 @@ internal class DefaultTimeline( private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { val token = getTokenLive(direction) ?: return val params = PaginationTask.Params(roomId = roomId, - from = token, - direction = direction.toPaginationDirection(), - limit = limit) + from = token, + direction = direction.toPaginationDirection(), + limit = limit) Timber.v("Should fetch $limit items $direction") cancelableBag += paginationTask.configureWith(params) @@ -453,6 +447,12 @@ internal class DefaultTimeline( } offsetResults.forEach { eventEntity -> val timelineEvent = eventEntity.asDomain() + + if (timelineEvent.isEncrypted() + && timelineEvent.root.mxDecryptionResult == null) { + timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) } + } + val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size builtEvents.add(position, timelineEvent) //Need to shift :/ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt new file mode 100644 index 00000000..e822d37f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2019 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 im.vector.matrix.android.internal.session.room.timeline + +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.crypto.MXCryptoError +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.internal.crypto.MXDecryptionException +import im.vector.matrix.android.internal.crypto.NewSessionListener +import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent +import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.query.where +import io.realm.Realm +import io.realm.RealmConfiguration +import timber.log.Timber +import java.util.concurrent.Executors + + +internal class TimelineEventDecryptor( + private val realmConfiguration: RealmConfiguration, + private val timelineId: String, + private val cryptoService: CryptoService +) { + + private val newSessionListener = object : NewSessionListener { + override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { + synchronized(unknownSessionsFailure) { + unknownSessionsFailure[sessionId]?.let { eventIds -> + eventIds.forEach { + requestDecryption(it) + } + } + unknownSessionsFailure[sessionId]?.clear() + } + } + + } + + private val executor = Executors.newSingleThreadExecutor() + + private val existingRequests = HashSet() + private val unknownSessionsFailure = HashMap>() + + fun start() { + cryptoService.addNewSessionListener(newSessionListener) + } + + fun destroy() { + cryptoService.removeSessionListener(newSessionListener) + executor.shutdownNow() + unknownSessionsFailure.clear() + existingRequests.clear() + } + + fun requestDecryption(eventId: String) { + synchronized(existingRequests) { + if (existingRequests.contains(eventId)) { + return Unit.also { + Timber.d("Skip Decryption request for event ${eventId}, already requested") + } + } + existingRequests.add(eventId) + } + synchronized(unknownSessionsFailure) { + unknownSessionsFailure.values.forEach { + if (it.contains(eventId)) return@synchronized Unit.also { + Timber.d("Skip Decryption request for event ${eventId}, unknown session") + } + } + } + executor.execute { + Realm.getInstance(realmConfiguration).use { realm -> + realm.executeTransaction { + processDecryptRequest(eventId, it) + } + } + } + } + + private fun processDecryptRequest(eventId: String, realm: Realm) { + Timber.v("Decryption request for event ${eventId}") + val eventEntity = EventEntity.where(realm, eventId = eventId).findFirst() + ?: return Unit.also { + Timber.d("Decryption request for unknown message") + } + val event = eventEntity.asDomain() + try { + val result = cryptoService.decryptEvent(event, timelineId) + if (result == null) { + Timber.e("Null decryption result for event ${eventId}") + } else { + Timber.v("Successfully decrypted event ${eventId}") + eventEntity.setDecryptionResult(result) + } + + } catch (e: MXDecryptionException) { + if (e.cryptoError?.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) { + //Keep track of unknown sessions to automatically try to decrypt on new session + event.content?.toModel()?.let { content -> + content.sessionId?.let { sessionId -> + synchronized(unknownSessionsFailure) { + val list = unknownSessionsFailure[sessionId] + ?: ArrayList().also { + unknownSessionsFailure[sessionId] = it + } + list.add(eventId) + } + } + } + } + } catch (t: Throwable) { + Timber.e(t, "Failed to decrypt event $eventId") + } finally { + synchronized(existingRequests) { + existingRequests.remove(eventId) + } + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt index 5ee64b1c..f286ec4b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult +import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.ToDeviceSyncResponse @@ -39,7 +40,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager: // Decrypt event if necessary decryptEvent(event, null) if (TextUtils.equals(event.getClearType(), EventType.MESSAGE) - && event.mClearEvent?.content?.toModel()?.type == "m.bad.encrypted") { + && event.getClearContent()?.toModel()?.type == "m.bad.encrypted") { Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content) } else { sasVerificationService.onToDeviceEvent(event) @@ -70,7 +71,18 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager: } if (null != result) { - event.setClearData(result) +// event.mxDecryptionResult = MXDecryptionResult( +// payload = result.clearEvent, +// keysClaimed = map +// ) + //TODO persist that? + event.mxDecryptionResult = MXDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) +// event.setClearData(result) return true } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index c8accc8e..7a9a918a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -176,7 +176,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, JSONObject(event.root.toContent()).toString(4))) if (event.isEncrypted()) { - val decryptedContent = event.root.mClearEvent.toContent()?.let { + val decryptedContent = event.root.getClearContent()?.let { JSONObject(it).toString(4) } ?: stringProvider.getString(R.string.encryption_information_decryption_error) this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, decryptedContent))