2019-07-08 08:58:41 +00:00
|
|
|
/*
|
|
|
|
* 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.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) {
|
2019-07-08 10:49:22 +00:00
|
|
|
val toDecryptAgain = ArrayList<String>()
|
2019-07-08 08:58:41 +00:00
|
|
|
unknownSessionsFailure[sessionId]?.let { eventIds ->
|
2019-07-08 10:49:22 +00:00
|
|
|
toDecryptAgain.addAll(eventIds)
|
|
|
|
}
|
|
|
|
if (toDecryptAgain.isNotEmpty()) {
|
|
|
|
unknownSessionsFailure[sessionId]?.clear()
|
|
|
|
toDecryptAgain.forEach {
|
2019-07-08 08:58:41 +00:00
|
|
|
requestDecryption(it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2019-07-08 10:05:41 +00:00
|
|
|
Timber.v("Successfully decrypted event ${eventId}")
|
|
|
|
eventEntity.setDecryptionResult(result)
|
2019-07-08 09:18:27 +00:00
|
|
|
} catch (e: MXCryptoError) {
|
2019-07-08 10:05:41 +00:00
|
|
|
Timber.v("Failed to decrypte event ${eventId} ${e}")
|
2019-07-08 09:18:27 +00:00
|
|
|
if (e is MXCryptoError.Base && e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
2019-07-08 08:58:41 +00:00
|
|
|
//Keep track of unknown sessions to automatically try to decrypt on new session
|
2019-07-08 09:18:27 +00:00
|
|
|
eventEntity.decryptionErrorCode = e.errorType.name
|
2019-07-08 08:58:41 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|