merge develop

This commit is contained in:
Valere 2019-07-08 10:58:41 +02:00
parent 4521ea14ee
commit e50dd265d4
10 changed files with 341 additions and 151 deletions

View File

@ -20,12 +20,11 @@ import android.text.TextUtils
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.crypto.MXCryptoError 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.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 im.vector.matrix.android.internal.di.MoshiProvider
import timber.log.Timber import timber.log.Timber
import java.util.*
import kotlin.collections.HashMap


typealias Content = JsonDict typealias Content = JsonDict


@ -79,6 +78,10 @@ data class Event(
@Json(name = "redacts") val redacts: String? = null @Json(name = "redacts") val redacts: String? = null
) { ) {



var mxDecryptionResult: MXDecryptionResult? = null
var mCryptoError: MXCryptoError? = null

/** /**
* Check if event is a state event. * Check if event is a state event.
* @return true if event is state event. * @return true if event is state event.
@ -91,41 +94,41 @@ data class Event(
// Crypto // Crypto
//============================================================================================================== //==============================================================================================================


/** // /**
* For encrypted events, the plaintext payload for the event. // * For encrypted events, the plaintext payload for the event.
* This is a small MXEvent instance with typically value for `type` and 'content' fields. // * This is a small MXEvent instance with typically value for `type` and 'content' fields.
*/ // */
@Transient // @Transient
var mClearEvent: Event? = null // var mClearEvent: Event? = null
private set // private set

//
/** // /**
* Curve25519 key which we believe belongs to the sender of the event. // * Curve25519 key which we believe belongs to the sender of the event.
* See `senderKey` property. // * See `senderKey` property.
*/ // */
@Transient // @Transient
private var mSenderCurve25519Key: String? = null // 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. // * 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. // * See `claimedEd25519Key` property.
*/ // */
@Transient // @Transient
private var mClaimedEd25519Key: String? = null // private var mClaimedEd25519Key: String? = null

//
/** // /**
* Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key. // * Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key.
* See `forwardingCurve25519KeyChain` property. // * See `forwardingCurve25519KeyChain` property.
*/ // */
@Transient // @Transient
private var mForwardingCurve25519KeyChain: List<String> = ArrayList() // private var mForwardingCurve25519KeyChain: List<String> = ArrayList()

//
/** // /**
* Decryption error // * Decryption error
*/ // */
@Transient // @Transient
var mCryptoError: MXCryptoError? = null // var mCryptoError: MXCryptoError? = null
private set // private set


/** /**
* @return true if this event is encrypted. * @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. * @param decryptionResult the decryption result, including the plaintext and some key info.
*/ */
internal fun setClearData(decryptionResult: MXEventDecryptionResult) { // internal fun setClearData(decryptionResult: MXEventDecryptionResult?) {
mClearEvent = null // mClearEvent = null
mCryptoError = null // if (decryptionResult != null) {

// if (decryptionResult.clearEvent != null) {
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java) // val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent) // mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent)

//
if (mClearEvent != null) { // if (mClearEvent != null) {
mSenderCurve25519Key = decryptionResult.senderCurve25519Key // mSenderCurve25519Key = decryptionResult.senderCurve25519Key
mClaimedEd25519Key = decryptionResult.claimedEd25519Key // mClaimedEd25519Key = decryptionResult.claimedEd25519Key
mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain // mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain

//
// For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back // // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back
// in the clear event // // in the clear event
try { // try {
content?.get("m.relates_to")?.let { clearRelates -> // content?.get("m.relates_to")?.let { clearRelates ->
mClearEvent = mClearEvent?.copy( // mClearEvent = mClearEvent?.copy(
content = HashMap(mClearEvent!!.content).apply { // content = HashMap(mClearEvent!!.content).apply {
this["m.relates_to"] = clearRelates // this["m.relates_to"] = clearRelates
} // }
) // )
} // }
} catch (e: Exception) { // } catch (e: Exception) {
Timber.e(e, "Unable to restore 'm.relates_to' the clear event") // Timber.e(e, "Unable to restore 'm.relates_to' the clear event")
} // }
} // }
} //
//
// }
// }
// mCryptoError = null
// }


/** /**
* @return The curve25519 key that sent this event. * @return The curve25519 key that sent this event.
*/ */
fun getSenderKey(): String? { 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. * @return The additional keys the sender of this encrypted event claims to possess.
*/ */
fun getKeysClaimed(): Map<String, String> { fun getKeysClaimed(): Map<String, String> {
val res = HashMap<String, String>() return mxDecryptionResult?.keysClaimed ?: HashMap()

// val res = HashMap<String, String>()
val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key //

// val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key
if (null != claimedEd25519Key) { //
res["ed25519"] = claimedEd25519Key // if (null != claimedEd25519Key) {
// res["ed25519"] = claimedEd25519Key
// }
//
// return res
} }

//
return res
}

/** /**
* @return the event type * @return the event type
*/ */
fun getClearType(): String { fun getClearType(): String {
return mClearEvent?.type ?: type return mxDecryptionResult?.payload?.get("type")?.toString()
?: type//get("type")?.toString() ?: type
} }


/** /**
* @return the event content * @return the event content
*/ */
fun getClearContent(): Content? { fun getClearContent(): Content? {
return mClearEvent?.content ?: content return mxDecryptionResult?.payload?.get("content") as? Content ?: content
} }


/** // /**
* @return the linked crypto error // * @return the linked crypto error
*/ // */
fun getCryptoError(): MXCryptoError? { // fun getCryptoError(): MXCryptoError? {
return mCryptoError // return mCryptoError
} // }

//
/** // /**
* Update the linked crypto error // * Update the linked crypto error
* // *
* @param error the new crypto error. // * @param error the new crypto error.
*/ // */
fun setCryptoError(error: MXCryptoError?) { // fun setCryptoError(error: MXCryptoError?) {
mCryptoError = error // mCryptoError = error
if (null != error) { // if (null != error) {
mClearEvent = null // mClearEvent = null
} // }
} // }


/** /**
* Tells if the event is redacted * Tells if the event is redacted

View File

@ -16,6 +16,7 @@


package im.vector.matrix.android.api.session.room.timeline 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.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
@ -34,7 +35,7 @@ data class TimelineEvent(
val isUniqueDisplayName: Boolean, val isUniqueDisplayName: Boolean,
val senderAvatar: String?, val senderAvatar: String?,
val sendState: SendState, val sendState: SendState,
val hasClearEventFlag: Boolean = false, // val hasClearEventFlag: Boolean = false,
val annotations: EventAnnotationsSummary? = null val annotations: EventAnnotationsSummary? = null
) { ) {



View File

@ -17,10 +17,13 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper


import com.squareup.moshi.JsonDataException 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.Event
import im.vector.matrix.android.api.session.events.model.UnsignedData 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.database.model.EventEntity
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import timber.log.Timber
import java.util.* import java.util.*


internal object EventMapper { internal object EventMapper {
@ -46,7 +49,6 @@ internal object EventMapper {
} }


fun map(eventEntity: EventEntity): Event { fun map(eventEntity: EventEntity): Event {
//TODO proxy the event to only parse unsigned data when accessed?
val ud = if (eventEntity.unsignedData.isNullOrBlank()) { val ud = if (eventEntity.unsignedData.isNullOrBlank()) {
null null
} else { } else {
@ -68,7 +70,17 @@ internal object EventMapper {
roomId = eventEntity.roomId, roomId = eventEntity.roomId,
unsignedData = ud, unsignedData = ud,
redacts = eventEntity.redacts 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) }
}
} }


} }

View File

@ -16,10 +16,12 @@


package im.vector.matrix.android.internal.database.mapper 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.events.model.Event
import im.vector.matrix.android.api.session.room.send.SendState 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.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.di.MoshiProvider


internal object TimelineEventMapper { internal object TimelineEventMapper {


@ -33,6 +35,8 @@ internal object TimelineEventMapper {
} }


fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent { fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent {
val listMapper = MoshiProvider.providesMoshi().adapter<List<String>>(Types.newParameterizedType(List::class.java, String::class.java))

return TimelineEvent( return TimelineEvent(
root = timelineEventEntity.root?.asDomain() root = timelineEventEntity.root?.asDomain()
?: Event("", timelineEventEntity.eventId), ?: Event("", timelineEventEntity.eventId),
@ -42,8 +46,7 @@ internal object TimelineEventMapper {
senderName = timelineEventEntity.senderName, senderName = timelineEventEntity.senderName,
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
senderAvatar = timelineEventEntity.senderAvatar, senderAvatar = timelineEventEntity.senderAvatar,
sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN, sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN
hasClearEventFlag = false
) )
} }



View File

@ -17,7 +17,9 @@
package im.vector.matrix.android.internal.database.model 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.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.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.annotations.Index import io.realm.annotations.Index
@ -39,7 +41,9 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI
var redacts: String? = null, var redacts: String? = null,
@Index var stateIndex: Int = 0, @Index var stateIndex: Int = 0,
@Index var displayIndex: 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() { ) : RealmObject() {


enum class LinkFilterMode { enum class LinkFilterMode {
@ -68,4 +72,14 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI
val timelineEventEntity: RealmResults<TimelineEventEntity>? = null val timelineEventEntity: RealmResults<TimelineEventEntity>? = 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>(MXDecryptionResult::class.java)
decryptionResultJson = adapter.toJson(decryptionResult)
}
} }

View File

@ -16,6 +16,10 @@


package im.vector.matrix.android.internal.database.model 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.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.annotations.Index import io.realm.annotations.Index

View File

@ -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.MatrixCallback
import im.vector.matrix.android.api.session.crypto.CryptoService 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.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.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent 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.CancelableBag
import im.vector.matrix.android.api.util.addTo
import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent 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.mapper.asDomain
import im.vector.matrix.android.internal.database.model.* 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.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.findIncludingEvent
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where 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.Debouncer
import im.vector.matrix.android.internal.util.createBackgroundHandler import im.vector.matrix.android.internal.util.createBackgroundHandler
import im.vector.matrix.android.internal.util.createUIHandler import im.vector.matrix.android.internal.util.createUIHandler
import io.realm.OrderedCollectionChangeSet import io.realm.*
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 timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -67,7 +57,7 @@ internal class DefaultTimeline(
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val cryptoService: CryptoService, cryptoService: CryptoService,
private val allowedTypes: List<String>? private val allowedTypes: List<String>?
) : Timeline { ) : Timeline {


@ -102,8 +92,12 @@ internal class DefaultTimeline(
private val forwardsPaginationState = AtomicReference(PaginationState()) private val forwardsPaginationState = AtomicReference(PaginationState())




private val timelineID = UUID.randomUUID().toString()

private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity> private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>


private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService)

private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<TimelineEventEntity>> { results, changeSet -> private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<TimelineEventEntity>> { results, changeSet ->
if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) { if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) {
handleInitialLoad() handleInitialLoad()
@ -172,32 +166,32 @@ internal class DefaultTimeline(
postSnapshot() postSnapshot()
} }


private val newSessionListener = object : NewSessionListener { // private val newSessionListener = object : NewSessionListener {
override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { // override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
if (roomId == this@DefaultTimeline.roomId) { // if (roomId == this@DefaultTimeline.roomId) {
Timber.v("New session id detected for this room") // Timber.v("New session id detected for this room")
BACKGROUND_HANDLER.post { // BACKGROUND_HANDLER.post {
val realm = backgroundRealm.get() // val realm = backgroundRealm.get()
var hasChange = false // var hasChange = false
builtEvents.forEachIndexed { index, timelineEvent -> // builtEvents.forEachIndexed { index, timelineEvent ->
if (timelineEvent.isEncrypted()) { // if (timelineEvent.isEncrypted()) {
val eventContent = timelineEvent.root.content.toModel<EncryptedEventContent>() // val eventContent = timelineEvent.root.content.toModel<EncryptedEventContent>()
if (eventContent?.sessionId == sessionId // if (eventContent?.sessionId == sessionId
&& (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) { // && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) {
//we need to rebuild this event // //we need to rebuild this event
EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let { // EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let {
//builtEvents[index] = timelineEventFactory.create(it, realm) // //builtEvents[index] = timelineEventFactory.create(it, realm)
hasChange = true // hasChange = true
} // }
} // }
} // }
} // }
if (hasChange) postSnapshot() // if (hasChange) postSnapshot()
} // }
} // }
} // }

//
} // }


// Public methods ****************************************************************************** // Public methods ******************************************************************************


@ -219,7 +213,7 @@ internal class DefaultTimeline(
override fun start() { override fun start() {
if (isStarted.compareAndSet(false, true)) { if (isStarted.compareAndSet(false, true)) {
Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId") Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId")
cryptoService.addNewSessionListener(newSessionListener) eventDecryptor.start()
BACKGROUND_HANDLER.post { BACKGROUND_HANDLER.post {
val realm = Realm.getInstance(realmConfiguration) val realm = Realm.getInstance(realmConfiguration)
backgroundRealm.set(realm) backgroundRealm.set(realm)
@ -247,7 +241,7 @@ internal class DefaultTimeline(


override fun dispose() { override fun dispose() {
if (isStarted.compareAndSet(true, false)) { if (isStarted.compareAndSet(true, false)) {
cryptoService.removeSessionListener(newSessionListener) eventDecryptor.destroy()
Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId") Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId")
BACKGROUND_HANDLER.post { BACKGROUND_HANDLER.post {
cancelableBag.cancel() cancelableBag.cancel()
@ -453,6 +447,12 @@ internal class DefaultTimeline(
} }
offsetResults.forEach { eventEntity -> offsetResults.forEach { eventEntity ->
val timelineEvent = eventEntity.asDomain() 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 val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size
builtEvents.add(position, timelineEvent) builtEvents.add(position, timelineEvent)
//Need to shift :/ //Need to shift :/

View File

@ -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<String>()
private val unknownSessionsFailure = HashMap<String, MutableList<String>>()

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<EncryptedEventContent>()?.let { content ->
content.sessionId?.let { sessionId ->
synchronized(unknownSessionsFailure) {
val list = unknownSessionsFailure[sessionId]
?: ArrayList<String>().also {
unknownSessionsFailure[sessionId] = it
}
list.add(eventId)
}
}
}
}
} catch (t: Throwable) {
Timber.e(t, "Failed to decrypt event $eventId")
} finally {
synchronized(existingRequests) {
existingRequests.remove(eventId)
}
}
}
}

View File

@ -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.api.session.room.model.message.MessageContent
import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult 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.crypto.verification.DefaultSasVerificationService
import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.session.sync.model.ToDeviceSyncResponse 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 // Decrypt event if necessary
decryptEvent(event, null) decryptEvent(event, null)
if (TextUtils.equals(event.getClearType(), EventType.MESSAGE) if (TextUtils.equals(event.getClearType(), EventType.MESSAGE)
&& event.mClearEvent?.content?.toModel<MessageContent>()?.type == "m.bad.encrypted") { && event.getClearContent()?.toModel<MessageContent>()?.type == "m.bad.encrypted") {
Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content) Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content)
} else { } else {
sasVerificationService.onToDeviceEvent(event) sasVerificationService.onToDeviceEvent(event)
@ -70,7 +71,18 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager:
} }


if (null != result) { 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 return true
} }
} }

View File

@ -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))) this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, JSONObject(event.root.toContent()).toString(4)))
if (event.isEncrypted()) { if (event.isEncrypted()) {
val decryptedContent = event.root.mClearEvent.toContent()?.let { val decryptedContent = event.root.getClearContent()?.let {
JSONObject(it).toString(4) JSONObject(it).toString(4)
} ?: stringProvider.getString(R.string.encryption_information_decryption_error) } ?: 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)) this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, decryptedContent))