Crypto: start reworking threading - WIP (to squash)

This commit is contained in:
ganfra 2019-06-04 16:26:37 +02:00
parent 3d50393b33
commit e125862794
60 changed files with 541 additions and 843 deletions

View File

@ -25,7 +25,7 @@ import kotlin.random.Random


internal class FakeGetContextOfEventTask(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : GetContextOfEventTask { internal class FakeGetContextOfEventTask(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : GetContextOfEventTask {


override fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> { override suspend fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> {
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30) val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
val tokenChunkEvent = FakeTokenChunkEvent( val tokenChunkEvent = FakeTokenChunkEvent(
Random.nextLong(System.currentTimeMillis()).toString(), Random.nextLong(System.currentTimeMillis()).toString(),

View File

@ -23,7 +23,7 @@ import kotlin.random.Random


internal class FakePaginationTask(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask { internal class FakePaginationTask(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask {


override fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> { override suspend fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> {
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30) val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents) val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction) return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)

View File

@ -21,6 +21,8 @@ package im.vector.matrix.android.internal.crypto
import android.content.Context import android.content.Context
import android.os.Handler import android.os.Handler
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import arrow.instances.`try`.applicativeError.handleError
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
@ -66,14 +68,16 @@ import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTas
import im.vector.matrix.android.internal.session.room.members.RoomMembers import im.vector.matrix.android.internal.session.room.members.RoomMembers
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.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.olm.OlmManager import org.matrix.olm.OlmManager
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean


/** /**
* A `CryptoService` class instance manages the end-to-end crypto for a session. * A `CryptoService` class instance manages the end-to-end crypto for a session.
@ -137,10 +141,10 @@ internal class CryptoManager(
private val roomEncryptors: MutableMap<String, IMXEncrypting> = HashMap() private val roomEncryptors: MutableMap<String, IMXEncrypting> = HashMap()


// the encryption is starting // the encryption is starting
private var isStarting: Boolean = false private var isStarting = AtomicBoolean(false)


// tell if the crypto is started // tell if the crypto is started
private var isStarted: Boolean = false private var isStarted = AtomicBoolean(false)


// TODO // TODO
//private val mNetworkListener = object : IMXNetworkEventListener { //private val mNetworkListener = object : IMXNetworkEventListener {
@ -220,7 +224,7 @@ internal class CryptoManager(
* @return true if the crypto is started * @return true if the crypto is started
*/ */
fun isStarted(): Boolean { fun isStarted(): Boolean {
return isStarted return isStarted.get()
} }


/** /**
@ -229,7 +233,7 @@ internal class CryptoManager(
* @return true if the crypto is starting * @return true if the crypto is starting
*/ */
fun isStarting(): Boolean { fun isStarting(): Boolean {
return isStarting return isStarting.get()
} }


/** /**
@ -241,7 +245,7 @@ internal class CryptoManager(
* @param isInitialSync true if it starts from an initial sync * @param isInitialSync true if it starts from an initial sync
*/ */
fun start(isInitialSync: Boolean) { fun start(isInitialSync: Boolean) {
if (isStarting) { if (isStarting.get()) {
return return
} }


@ -254,44 +258,33 @@ internal class CryptoManager(
// return // return
//} //}


isStarting = true isStarting.set(true)

// Open the store // Open the store
cryptoStore.open() cryptoStore.open()

CoroutineScope(coroutineDispatchers.crypto).launch {
uploadDeviceKeys(object : MatrixCallback<KeysUploadResponse> { uploadDeviceKeys()
private fun onError() { .flatMap {
Handler().postDelayed({ oneTimeKeysUploader.maybeUploadOneTimeKeys()
}
.handleError {
Handler().postDelayed(
{
if (!isStarted()) { if (!isStarted()) {
isStarting = false isStarting.set(false)
start(isInitialSync) start(isInitialSync)
} }
}, 1000) }, 1000
)
} }

.fold(
override fun onSuccess(data: KeysUploadResponse) { {
Timber.v("###########################################################") Timber.e("Start failed: $it")
Timber.v("uploadDeviceKeys done for " + credentials.userId) },
Timber.v(" - device id : " + credentials.deviceId) {
Timber.v(" - ed25519 : " + olmDevice.deviceEd25519Key) isStarting.set(false)
Timber.v(" - curve25519 : " + olmDevice.deviceCurve25519Key) isStarted.set(true)
Timber.v(" - oneTimeKeys: " + oneTimeKeysUploader.mLastPublishedOneTimeKeys)
Timber.v("")

oneTimeKeysUploader.maybeUploadOneTimeKeys(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// TODO
//if (null != mNetworkConnectivityReceiver) {
// mNetworkConnectivityReceiver!!.removeEventListener(mNetworkListener)
//}

isStarting = false
isStarted = true

outgoingRoomKeyRequestManager.start() outgoingRoomKeyRequestManager.start()

keysBackup.checkAndStartKeysBackup() keysBackup.checkAndStartKeysBackup()

if (isInitialSync) { if (isInitialSync) {
// refresh the devices list for each known room members // refresh the devices list for each known room members
deviceListManager.invalidateAllDeviceLists() deviceListManager.invalidateAllDeviceLists()
@ -300,19 +293,8 @@ internal class CryptoManager(
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests() incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
} }
} }

)
override fun onFailure(failure: Throwable) {
Timber.e(failure, "## start failed")
onError()
} }
})
}

override fun onFailure(failure: Throwable) {
Timber.e(failure, "## start failed")
onError()
}
})
} }


/** /**
@ -320,9 +302,7 @@ internal class CryptoManager(
*/ */
fun close() { fun close() {
olmDevice.release() olmDevice.release()

cryptoStore.close() cryptoStore.close()

outgoingRoomKeyRequestManager.stop() outgoingRoomKeyRequestManager.stop()
} }


@ -351,6 +331,7 @@ internal class CryptoManager(
* @param syncResponse the syncResponse * @param syncResponse the syncResponse
*/ */
fun onSyncCompleted(syncResponse: SyncResponse) { fun onSyncCompleted(syncResponse: SyncResponse) {
CoroutineScope(coroutineDispatchers.crypto).launch {
if (syncResponse.deviceLists != null) { if (syncResponse.deviceLists != null) {
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left) deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
} }
@ -358,7 +339,6 @@ internal class CryptoManager(
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0 val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount) oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
} }

if (isStarted()) { if (isStarted()) {
// Make sure we process to-device messages before generating new one-time-keys #2782 // Make sure we process to-device messages before generating new one-time-keys #2782
deviceListManager.refreshOutdatedDeviceLists() deviceListManager.refreshOutdatedDeviceLists()
@ -366,6 +346,7 @@ internal class CryptoManager(
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests() incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
} }
} }
}


/** /**
* Find a device by curve25519 identity key * Find a device by curve25519 identity key
@ -398,7 +379,7 @@ internal class CryptoManager(
/** /**
* Set the devices as known * Set the devices as known
* *
* @param devices the devices. Note that the mVerified member of the devices in this list will not be updated by this method. * @param devices the devices. Note that the verified member of the devices in this list will not be updated by this method.
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
override fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?) { override fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?) {
@ -431,7 +412,7 @@ internal class CryptoManager(
// assume if the device is either verified or blocked // assume if the device is either verified or blocked
// it means that the device is known // it means that the device is known
if (null != device && device.isUnknown) { if (null != device && device.isUnknown) {
device.mVerified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED device.verified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED
isUpdated = true isUpdated = true
} }
} }
@ -459,7 +440,6 @@ internal class CryptoManager(


/** /**
* Configure a room to use encryption. * Configure a room to use encryption.
* This method must be called in getEncryptingThreadHandler
* *
* @param roomId the room id to enable encryption in. * @param roomId the room id to enable encryption in.
* @param algorithm the encryption config for the room. * @param algorithm the encryption config for the room.
@ -467,7 +447,7 @@ internal class CryptoManager(
* @param membersId list of members to start tracking their devices * @param membersId list of members to start tracking their devices
* @return true if the operation succeeds. * @return true if the operation succeeds.
*/ */
private fun setEncryptionInRoom(roomId: String, algorithm: String?, inhibitDeviceQuery: Boolean, membersId: List<String>): Boolean { private suspend fun setEncryptionInRoom(roomId: String, algorithm: String?, inhibitDeviceQuery: Boolean, membersId: List<String>): Boolean {
// If we already have encryption in this room, we should ignore this event // If we already have encryption in this room, we should ignore this event
// (for now at least. Maybe we should alert the user somehow?) // (for now at least. Maybe we should alert the user somehow?)
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId) val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
@ -578,18 +558,17 @@ internal class CryptoManager(
// wait that the crypto is really started // wait that the crypto is really started
if (!isStarted()) { if (!isStarted()) {
Timber.v("## encryptEventContent() : wait after e2e init") Timber.v("## encryptEventContent() : wait after e2e init")

start(false) start(false)
return return
} }

CoroutineScope(coroutineDispatchers.crypto).launch {
val userIds = getRoomUserIds(roomId) val userIds = getRoomUserIds(roomId)
var alg = synchronized(roomEncryptors) { var alg = synchronized(roomEncryptors) {
roomEncryptors[roomId] roomEncryptors[roomId]
} }
if (alg == null) { if (alg == null) {
val algorithm = getEncryptionAlgorithm(roomId) val algorithm = getEncryptionAlgorithm(roomId)
if (null != algorithm) { if (algorithm != null) {
if (setEncryptionInRoom(roomId, algorithm, false, userIds)) { if (setEncryptionInRoom(roomId, algorithm, false, userIds)) {
synchronized(roomEncryptors) { synchronized(roomEncryptors) {
alg = roomEncryptors[roomId] alg = roomEncryptors[roomId]
@ -597,15 +576,13 @@ internal class CryptoManager(
} }
} }
} }

val safeAlgorithm = alg
if (alg != null) { if (safeAlgorithm != null) {
val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()
Timber.v("## encryptEventContent() starts") Timber.v("## encryptEventContent() starts")

safeAlgorithm.encryptEventContent(eventContent, eventType, userIds, object : MatrixCallback<Content> {
alg!!.encryptEventContent(eventContent, eventType, userIds, object : MatrixCallback<Content> {
override fun onSuccess(data: Content) { override fun onSuccess(data: Content) {
Timber.v("## encryptEventContent() : succeeds after " + (System.currentTimeMillis() - t0) + " ms") Timber.v("## encryptEventContent() : succeeds after " + (System.currentTimeMillis() - t0) + " ms")

callback.onSuccess(MXEncryptEventContentResult(data, EventType.ENCRYPTED)) callback.onSuccess(MXEncryptEventContentResult(data, EventType.ENCRYPTED))
} }


@ -623,6 +600,7 @@ internal class CryptoManager(
MXCryptoError.UNABLE_TO_ENCRYPT, reason))) MXCryptoError.UNABLE_TO_ENCRYPT, reason)))
} }
} }
}


/** /**
* Decrypt an event * Decrypt an event
@ -633,43 +611,23 @@ internal class CryptoManager(
*/ */
@Throws(MXDecryptionException::class) @Throws(MXDecryptionException::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? { override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? {
val eventContent = event.content //wireEventContent? val eventContent = event.content

if (eventContent == null) {
if (null == eventContent) {
Timber.e("## decryptEvent : empty event content") Timber.e("## decryptEvent : empty event content")
return null return null
} }

val results = ArrayList<MXEventDecryptionResult>()
val exceptions = ArrayList<MXDecryptionException>()

var result: MXEventDecryptionResult? = null
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, eventContent["algorithm"] as String) val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, eventContent["algorithm"] as String)

if (alg == null) {
if (null == alg) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String) val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String)
Timber.e("## decryptEvent() : $reason") Timber.e("## decryptEvent() : $reason")
exceptions.add(MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason))
MXCryptoError.UNABLE_TO_DECRYPT, reason)))
} else { } else {
try { return runBlocking {
result = alg.decryptEvent(event, timeline) withContext(coroutineDispatchers.crypto) {
} catch (decryptionException: MXDecryptionException) { alg.decryptEvent(event, timeline)
exceptions.add(decryptionException)
}

if (null != result) {
results.add(result) // TODO simplify
} }
} }

if (!exceptions.isEmpty()) {
throw exceptions[0]
} }

return if (!results.isEmpty()) {
results[0]
} else null
} }


/** /**
@ -687,16 +645,17 @@ internal class CryptoManager(
* @param event the event * @param event the event
*/ */
fun onToDeviceEvent(event: Event) { fun onToDeviceEvent(event: Event) {
CoroutineScope(coroutineDispatchers.crypto).launch {
if (event.getClearType() == EventType.ROOM_KEY || event.getClearType() == EventType.FORWARDED_ROOM_KEY) { if (event.getClearType() == EventType.ROOM_KEY || event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
onRoomKeyEvent(event) onRoomKeyEvent(event)
} else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) { } else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) {
incomingRoomKeyRequestManager.onRoomKeyRequestEvent(event) incomingRoomKeyRequestManager.onRoomKeyRequestEvent(event)
} }
} }
}


/** /**
* Handle a key event. * Handle a key event.
* This method must be called on getDecryptingThreadHandler() thread.
* *
* @param event the key event. * @param event the key event.
*/ */
@ -798,25 +757,17 @@ internal class CryptoManager(


/** /**
* Upload my user's device keys. * Upload my user's device keys.
* This method must called on getEncryptingThreadHandler() thread.
* The callback will called on UI thread.
*
* @param callback the asynchronous callback
*/ */
private fun uploadDeviceKeys(callback: MatrixCallback<KeysUploadResponse>) { private suspend fun uploadDeviceKeys(): Try<KeysUploadResponse> {
// Prepare the device keys data to send // Prepare the device keys data to send
// Sign it // Sign it
val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary()) val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())

getMyDevice().signatures = objectSigner.signObject(canonicalJson) getMyDevice().signatures = objectSigner.signObject(canonicalJson)


// For now, we set the device id explicitly, as we may not be using the // For now, we set the device id explicitly, as we may not be using the
// same one as used in login. // same one as used in login.
uploadKeysTask val uploadDeviceKeysParams = UploadKeysTask.Params(getMyDevice().toDeviceKeys(), null, getMyDevice().deviceId)
.configureWith(UploadKeysTask.Params(getMyDevice().toDeviceKeys(), null, getMyDevice().deviceId)) return uploadKeysTask.execute(uploadDeviceKeysParams)
.executeOn(TaskThread.ENCRYPTION)
.dispatchTo(callback)
.executeBy(taskExecutor)
} }


/** /**
@ -934,10 +885,13 @@ internal class CryptoManager(
*/ */
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) { fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
// force the refresh to ensure that the devices list is up-to-date // force the refresh to ensure that the devices list is up-to-date
deviceListManager.downloadKeys(userIds, true, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> { CoroutineScope(coroutineDispatchers.crypto).launch {
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) { deviceListManager
val unknownDevices = getUnknownDevices(data) .downloadKeys(userIds, true)

.fold(
{ callback.onFailure(it) },
{
val unknownDevices = getUnknownDevices(it)
if (unknownDevices.map.isEmpty()) { if (unknownDevices.map.isEmpty()) {
callback.onSuccess(Unit) callback.onSuccess(Unit)
} else { } else {
@ -947,11 +901,8 @@ internal class CryptoManager(
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices))) MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)))
} }
} }

)
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
} }
})
} }


/** /**
@ -1091,7 +1042,6 @@ internal class CryptoManager(
*/ */
private fun getUnknownDevices(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): MXUsersDevicesMap<MXDeviceInfo> { private fun getUnknownDevices(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): MXUsersDevicesMap<MXDeviceInfo> {
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>() val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()

val userIds = devicesInRoom.userIds val userIds = devicesInRoom.userIds
for (userId in userIds) { for (userId in userIds) {
val deviceIds = devicesInRoom.getUserDeviceIds(userId) val deviceIds = devicesInRoom.getUserDeviceIds(userId)

View File

@ -19,7 +19,11 @@ package im.vector.matrix.android.internal.crypto
import android.content.Context import android.content.Context
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.internal.crypto.actions.* import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForUsersAction
import im.vector.matrix.android.internal.crypto.actions.MegolmSessionDataImporter
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmDecryptionFactory import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmDecryptionFactory
import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmDecryptionFactory import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmDecryptionFactory
@ -27,14 +31,56 @@ import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFa
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.* import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteBackupTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupLastVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultUpdateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteBackupTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
import im.vector.matrix.android.internal.crypto.store.db.hash import im.vector.matrix.android.internal.crypto.store.db.hash
import im.vector.matrix.android.internal.crypto.tasks.* import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultDownloadKeysForUsers
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDevicesTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetKeyChangesTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendToDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultSetDeviceNameTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultUploadKeysTask
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
import im.vector.matrix.android.internal.crypto.tasks.GetKeyChangesTask
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
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.DefaultSession import im.vector.matrix.android.internal.session.DefaultSession
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
@ -109,7 +155,7 @@ internal class CryptoModule {


// OneTimeKeysUploader // OneTimeKeysUploader
scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
OneTimeKeysUploader(get(), get(), get(), get(), get()) OneTimeKeysUploader(get(), get(), get(), get())
} }


// Actions // Actions
@ -217,7 +263,7 @@ internal class CryptoModule {


// Device list // Device list
scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
DeviceListManager(get(), get(), get(), get(), get(), get(), get()) DeviceListManager(get(), get(), get(), get(), get())
} }


// Crypto tasks // Crypto tasks
@ -319,7 +365,7 @@ internal class CryptoModule {
* ========================================================================================== */ * ========================================================================================== */


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
DefaultSasVerificationService(get(), get(), get(), get(), get(), get(), get()) DefaultSasVerificationService(get(), get(), get(), get(), get(), get(), get(), get())
} }


} }

View File

@ -18,19 +18,15 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto


import android.text.TextUtils import android.text.TextUtils
import im.vector.matrix.android.api.MatrixCallback import arrow.core.Try
import arrow.instances.`try`.applicativeError.handleError
import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.MatrixPatterns
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryResponse
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*


@ -39,59 +35,22 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
private val syncTokenStore: SyncTokenStore, private val syncTokenStore: SyncTokenStore,
private val credentials: Credentials, private val credentials: Credentials,
private val downloadKeysForUsersTask: DownloadKeysForUsersTask, private val downloadKeysForUsersTask: DownloadKeysForUsersTask) {
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor) {

// keys in progress
private val userKeyDownloadsInProgress = HashSet<String>()


// HS not ready for retry // HS not ready for retry
private val notReadyToRetryHS = HashSet<String>() private val notReadyToRetryHS = HashSet<String>()


// indexed by UserId
private val pendingDownloadKeysRequestToken = HashMap<String, String>()

// pending queues list
private val downloadKeysQueues = ArrayList<DownloadKeysPromise>()

// tells if there is a download keys request in progress
private var isDownloadingKeys = false

/**
* Creator
*
* @param userIds the user ids list
* @param callback the asynchronous callback
*/
internal inner class DownloadKeysPromise(userIds: List<String>,
val callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>?) {
// list of remain pending device keys
val mPendingUserIdsList: MutableList<String>

// the unfiltered user ids list
val mUserIdsList: List<String>

init {
mPendingUserIdsList = ArrayList(userIds)
mUserIdsList = ArrayList(userIds)
}
}

init { init {
var isUpdated = false var isUpdated = false

val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
for (userId in deviceTrackingStatuses.keys) { for (userId in deviceTrackingStatuses.keys) {
val status = deviceTrackingStatuses[userId]!! val status = deviceTrackingStatuses[userId]!!

if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) { if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) {
// if a download was in progress when we got shut down, it isn't any more. // if a download was in progress when we got shut down, it isn't any more.
deviceTrackingStatuses.put(userId, TRACKING_STATUS_PENDING_DOWNLOAD) deviceTrackingStatuses.put(userId, TRACKING_STATUS_PENDING_DOWNLOAD)
isUpdated = true isUpdated = true
} }
} }

if (isUpdated) { if (isUpdated) {
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
} }
@ -120,43 +79,6 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
return res return res
} }


/**
* Add a download keys promise
*
* @param userIds the user ids list
* @param callback the asynchronous callback
* @return the filtered user ids list i.e the one which require a remote request
*/
private fun addDownloadKeysPromise(userIds: MutableList<String>?, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>?): MutableList<String>? {
if (null != userIds) {
val filteredUserIds = ArrayList<String>()
val invalidUserIds = ArrayList<String>()

for (userId in userIds) {
if (MatrixPatterns.isUserId(userId)) {
filteredUserIds.add(userId)
} else {
Timber.e("## userId " + userId + "is not a valid user id")
invalidUserIds.add(userId)
}
}

synchronized(userKeyDownloadsInProgress) {
filteredUserIds.removeAll(userKeyDownloadsInProgress)
userKeyDownloadsInProgress.addAll(userIds)
// got some email addresses instead of matrix ids
userKeyDownloadsInProgress.removeAll(invalidUserIds)
userIds.removeAll(invalidUserIds)
}

downloadKeysQueues.add(DownloadKeysPromise(userIds, callback))

return filteredUserIds
} else {
return null
}
}

/** /**
* Clear the unavailable server lists * Clear the unavailable server lists
*/ */
@ -180,7 +102,7 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
for (userId in userIds) { for (userId in userIds) {
if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) { if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) {
Timber.v("## startTrackingDeviceList() : Now tracking device list for $userId") Timber.v("## startTrackingDeviceList() : Now tracking device list for $userId")
deviceTrackingStatuses.put(userId, TRACKING_STATUS_PENDING_DOWNLOAD) deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
isUpdated = true isUpdated = true
} }
} }
@ -202,24 +124,20 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()


if (changed?.isNotEmpty() == true) { if (changed?.isNotEmpty() == true) {
clearUnavailableServersList()

for (userId in changed) { for (userId in changed) {
if (deviceTrackingStatuses.containsKey(userId)) { if (deviceTrackingStatuses.containsKey(userId)) {
Timber.v("## invalidateUserDeviceList() : Marking device list outdated for $userId") Timber.v("## invalidateUserDeviceList() : Marking device list outdated for $userId")
deviceTrackingStatuses.put(userId, TRACKING_STATUS_PENDING_DOWNLOAD) deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
isUpdated = true isUpdated = true
} }
} }
} }


if (left?.isNotEmpty() == true) { if (left?.isNotEmpty() == true) {
clearUnavailableServersList()

for (userId in left) { for (userId in left) {
if (deviceTrackingStatuses.containsKey(userId)) { if (deviceTrackingStatuses.containsKey(userId)) {
Timber.v("## invalidateUserDeviceList() : No longer tracking device list for $userId") Timber.v("## invalidateUserDeviceList() : No longer tracking device list for $userId")
deviceTrackingStatuses.put(userId, TRACKING_STATUS_NOT_TRACKED) deviceTrackingStatuses[userId] = TRACKING_STATUS_NOT_TRACKED
isUpdated = true isUpdated = true
} }
} }
@ -243,22 +161,13 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
* *
* @param userIds the user ids list * @param userIds the user ids list
*/ */
private fun onKeysDownloadFailed(userIds: List<String>?) { private fun onKeysDownloadFailed(userIds: List<String>) {
if (null != userIds) {
synchronized(userKeyDownloadsInProgress) {
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()

for (userId in userIds) { for (userId in userIds) {
userKeyDownloadsInProgress.remove(userId) deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
deviceTrackingStatuses.put(userId, TRACKING_STATUS_PENDING_DOWNLOAD)
} }

cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
} }
}

isDownloadingKeys = false
}


/** /**
* The keys download succeeded. * The keys download succeeded.
@ -266,23 +175,19 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
* @param userIds the userIds list * @param userIds the userIds list
* @param failures the failure map. * @param failures the failure map.
*/ */
private fun onKeysDownloadSucceed(userIds: List<String>?, failures: Map<String, Map<String, Any>>?) { private fun onKeysDownloadSucceed(userIds: List<String>, failures: Map<String, Map<String, Any>>?): MXUsersDevicesMap<MXDeviceInfo> {
if (null != failures) { if (failures != null) {
val keys = failures.keys val keys = failures.keys

for (k in keys) { for (k in keys) {
val value = failures[k] val value = failures[k]

if (value!!.containsKey("status")) { if (value!!.containsKey("status")) {
val statusCodeAsVoid = value["status"] val statusCodeAsVoid = value["status"]
var statusCode = 0 var statusCode = 0

if (statusCodeAsVoid is Double) { if (statusCodeAsVoid is Double) {
statusCode = statusCodeAsVoid.toInt() statusCode = statusCodeAsVoid.toInt()
} else if (statusCodeAsVoid is Int) { } else if (statusCodeAsVoid is Int) {
statusCode = statusCodeAsVoid.toInt() statusCode = statusCodeAsVoid.toInt()
} }

if (statusCode == 503) { if (statusCode == 503) {
synchronized(notReadyToRetryHS) { synchronized(notReadyToRetryHS) {
notReadyToRetryHS.add(k) notReadyToRetryHS.add(k)
@ -291,29 +196,17 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
} }
} }
} }

val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()

if (null != userIds) {
if (downloadKeysQueues.size > 0) {
val promisesToRemove = ArrayList<DownloadKeysPromise>()

for (promise in downloadKeysQueues) {
promise.mPendingUserIdsList.removeAll(userIds)

if (promise.mPendingUserIdsList.size == 0) {
// private members
val usersDevicesInfoMap = MXUsersDevicesMap<MXDeviceInfo>() val usersDevicesInfoMap = MXUsersDevicesMap<MXDeviceInfo>()

for (userId in userIds) {
for (userId in promise.mUserIdsList) {
val devices = cryptoStore.getUserDevices(userId) val devices = cryptoStore.getUserDevices(userId)
if (null == devices) { if (null == devices) {
if (canRetryKeysDownload(userId)) { if (canRetryKeysDownload(userId)) {
deviceTrackingStatuses.put(userId, TRACKING_STATUS_PENDING_DOWNLOAD) deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
Timber.e("failed to retry the devices of $userId : retry later") Timber.e("failed to retry the devices of $userId : retry later")
} else { } else {
if (deviceTrackingStatuses.containsKey(userId) && TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == deviceTrackingStatuses[userId]) { if (deviceTrackingStatuses.containsKey(userId) && TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == deviceTrackingStatuses[userId]) {
deviceTrackingStatuses.put(userId, TRACKING_STATUS_UNREACHABLE_SERVER) deviceTrackingStatuses[userId] = TRACKING_STATUS_UNREACHABLE_SERVER
Timber.e("failed to retry the devices of $userId : the HS is not available") Timber.e("failed to retry the devices of $userId : the HS is not available")
} }
} }
@ -321,35 +214,15 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
if (deviceTrackingStatuses.containsKey(userId) && TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == deviceTrackingStatuses[userId]) { if (deviceTrackingStatuses.containsKey(userId) && TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == deviceTrackingStatuses[userId]) {
// we didn't get any new invalidations since this download started: // we didn't get any new invalidations since this download started:
// this user's device list is now up to date. // this user's device list is now up to date.
deviceTrackingStatuses.put(userId, TRACKING_STATUS_UP_TO_DATE) deviceTrackingStatuses[userId] = TRACKING_STATUS_UP_TO_DATE
Timber.v("Device list for $userId now up to date") Timber.v("Device list for $userId now up to date")
} }

// And the response result // And the response result
usersDevicesInfoMap.setObjects(devices, userId) usersDevicesInfoMap.setObjects(devices, userId)
} }
} }

val callback = promise.callback

if (null != callback) {
CryptoAsyncHelper.getUiHandler().post { callback.onSuccess(usersDevicesInfoMap) }
}

promisesToRemove.add(promise)
}
}
downloadKeysQueues.removeAll(promisesToRemove)
}

for (userId in userIds) {
userKeyDownloadsInProgress.remove(userId)
}

cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
} return usersDevicesInfoMap

isDownloadingKeys = false
} }


/** /**
@ -361,31 +234,27 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
* @param forceDownload Always download the keys even if cached. * @param forceDownload Always download the keys even if cached.
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
fun downloadKeys(userIds: List<String>?, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>?) { suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): Try<MXUsersDevicesMap<MXDeviceInfo>> {
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds") Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")

// Map from userid -> deviceid -> DeviceInfo // Map from userid -> deviceid -> DeviceInfo
val stored = MXUsersDevicesMap<MXDeviceInfo>() val stored = MXUsersDevicesMap<MXDeviceInfo>()


// List of user ids we need to download keys for // List of user ids we need to download keys for
val downloadUsers = ArrayList<String>() val downloadUsers = ArrayList<String>()

if (null != userIds) { if (null != userIds) {
if (forceDownload) { if (forceDownload) {
downloadUsers.addAll(userIds) downloadUsers.addAll(userIds)
} else { } else {
for (userId in userIds) { for (userId in userIds) {
val status = cryptoStore.getDeviceTrackingStatus(userId, TRACKING_STATUS_NOT_TRACKED) val status = cryptoStore.getDeviceTrackingStatus(userId, TRACKING_STATUS_NOT_TRACKED)

// downloading keys ->the keys download won't be triggered twice but the callback requires the dedicated keys // downloading keys ->the keys download won't be triggered twice but the callback requires the dedicated keys
// not yet retrieved // not yet retrieved
if (userKeyDownloadsInProgress.contains(userId) || TRACKING_STATUS_UP_TO_DATE != status && TRACKING_STATUS_UNREACHABLE_SERVER != status) { if (TRACKING_STATUS_UP_TO_DATE != status && TRACKING_STATUS_UNREACHABLE_SERVER != status) {
downloadUsers.add(userId) downloadUsers.add(userId)
} else { } else {
val devices = cryptoStore.getUserDevices(userId) val devices = cryptoStore.getUserDevices(userId)

// should always be true // should always be true
if (null != devices) { if (devices != null) {
stored.setObjects(devices, userId) stored.setObjects(devices, userId)
} else { } else {
downloadUsers.add(userId) downloadUsers.add(userId)
@ -394,31 +263,18 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
} }
} }
} }

return if (downloadUsers.isEmpty()) {
if (0 == downloadUsers.size) {
Timber.v("## downloadKeys() : no new user device") Timber.v("## downloadKeys() : no new user device")

Try.just(stored)
if (null != callback) {
CryptoAsyncHelper.getUiHandler().post { callback.onSuccess(stored) }
}
} else { } else {
Timber.v("## downloadKeys() : starts") Timber.v("## downloadKeys() : starts")
val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()

doKeyDownloadForUsers(downloadUsers)
doKeyDownloadForUsers(downloadUsers, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> { .flatMap {
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms") Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms")

it.addEntriesFromMap(stored)
data.addEntriesFromMap(stored) Try.just(it)

callback?.onSuccess(data)
} }

override fun onFailure(failure: Throwable) {
Timber.e(failure, "## downloadKeys() : doKeyDownloadForUsers onFailure")
callback?.onFailure(failure)
}
})
} }
} }


@ -430,65 +286,25 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
* @param downloadUsers the user ids list * @param downloadUsers the user ids list
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
private fun doKeyDownloadForUsers(downloadUsers: MutableList<String>, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>?) { private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): Try<MXUsersDevicesMap<MXDeviceInfo>> {
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers") Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")

// get the user ids which did not already trigger a keys download // get the user ids which did not already trigger a keys download
val filteredUsers = addDownloadKeysPromise(downloadUsers, callback) val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }

if (filteredUsers.isEmpty()) {
// if there is no new keys request
if (0 == filteredUsers!!.size) {
// trigger nothing // trigger nothing
return return Try.just(MXUsersDevicesMap())
} }

val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken())
// sanity check return downloadKeysForUsersTask.execute(params)
//if (null == mxSession.dataHandler || null == mxSession.dataHandler.store) { .map { response ->
// return
//}

isDownloadingKeys = true

// track the race condition while sending requests
// we defines a tag for each request
// and test if the response is the latest request one
val downloadToken = filteredUsers.hashCode().toString() + " " + System.currentTimeMillis()

for (userId in filteredUsers) {
pendingDownloadKeysRequestToken[userId] = downloadToken
}

downloadKeysForUsersTask
.configureWith(DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken()))
.executeOn(TaskThread.ENCRYPTION)
.dispatchTo(object : MatrixCallback<KeysQueryResponse> {
override fun onSuccess(data: KeysQueryResponse) {
CryptoAsyncHelper.getEncryptBackgroundHandler().post {
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
val userIdsList = ArrayList(filteredUsers) for (userId in filteredUsers) {

val devices = response.deviceKeys?.get(userId)
for (userId in userIdsList) {
// test if the response is the latest request one
if (!TextUtils.equals(pendingDownloadKeysRequestToken[userId], downloadToken)) {
Timber.e("## doKeyDownloadForUsers() : Another update in the queue for "
+ userId + " not marking up-to-date")
filteredUsers.remove(userId)
} else {
val devices = data.deviceKeys!![userId]

Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices") Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices")

if (devices != null) {
if (null != devices) {
val mutableDevices = HashMap(devices) val mutableDevices = HashMap(devices)
val deviceIds = ArrayList(mutableDevices.keys) val deviceIds = ArrayList(mutableDevices.keys)

for (deviceId in deviceIds) { for (deviceId in deviceIds) {
// the user has been logged out
// TODO
//if (null == cryptoStore) {
// break
//}

// Get the potential previously store device keys for this device // Get the potential previously store device keys for this device
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId) val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId)
val deviceInfo = mutableDevices[deviceId] val deviceInfo = mutableDevices[deviceId]
@ -496,14 +312,12 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
// in some race conditions (like unit tests) // in some race conditions (like unit tests)
// the self device must be seen as verified // the self device must be seen as verified
if (TextUtils.equals(deviceInfo!!.deviceId, credentials.deviceId) && TextUtils.equals(userId, credentials.userId)) { if (TextUtils.equals(deviceInfo!!.deviceId, credentials.deviceId) && TextUtils.equals(userId, credentials.userId)) {
deviceInfo.mVerified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
} }

// Validate received keys // Validate received keys
if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) { if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) {
// New device keys are not valid. Do not store them // New device keys are not valid. Do not store them
mutableDevices.remove(deviceId) mutableDevices.remove(deviceId)

if (null != previouslyStoredDeviceKeys) { if (null != previouslyStoredDeviceKeys) {
// But keep old validated ones if any // But keep old validated ones if any
mutableDevices[deviceId] = previouslyStoredDeviceKeys mutableDevices[deviceId] = previouslyStoredDeviceKeys
@ -512,54 +326,23 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
// The verified status is not sync'ed with hs. // The verified status is not sync'ed with hs.
// This is a client side information, valid only for this client. // This is a client side information, valid only for this client.
// So, transfer its previous value // So, transfer its previous value
mutableDevices[deviceId]!!.mVerified = previouslyStoredDeviceKeys.mVerified mutableDevices[deviceId]!!.verified = previouslyStoredDeviceKeys.verified
} }
} }

// Update the store // Update the store
// Note that devices which aren't in the response will be removed from the stores // Note that devices which aren't in the response will be removed from the stores
cryptoStore.storeUserDevices(userId, mutableDevices) cryptoStore.storeUserDevices(userId, mutableDevices)
} }

// the response is the latest request one
pendingDownloadKeysRequestToken.remove(userId)
} }
onKeysDownloadSucceed(filteredUsers, response.failures)
} }

.handleError {
onKeysDownloadSucceed(filteredUsers, data.failures) Timber.e(it, "##doKeyDownloadForUsers(): error")
}
}

private fun onFailed() {
CryptoAsyncHelper.getEncryptBackgroundHandler().post {
val userIdsList = ArrayList(filteredUsers)

// test if the response is the latest request one
for (userId in userIdsList) {
if (!TextUtils.equals(pendingDownloadKeysRequestToken[userId], downloadToken)) {
Timber.e("## doKeyDownloadForUsers() : Another update in the queue for $userId not marking up-to-date")
filteredUsers.remove(userId)
} else {
// the response is the latest request one
pendingDownloadKeysRequestToken.remove(userId)
}
}

onKeysDownloadFailed(filteredUsers) onKeysDownloadFailed(filteredUsers)
throw it
} }
} }


override fun onFailure(failure: Throwable) {
Timber.e(failure, "##doKeyDownloadForUsers() : onNetworkError")

onFailed()

callback?.onFailure(failure)
}
})
.executeBy(taskExecutor)
}

/** /**
* Validate device keys. * Validate device keys.
* This method must called on getEncryptingThreadHandler() thread. * This method must called on getEncryptingThreadHandler() thread.
@ -659,7 +442,7 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
* Start device queries for any users who sent us an m.new_device recently * Start device queries for any users who sent us an m.new_device recently
* This method must be called on getEncryptingThreadHandler() thread. * This method must be called on getEncryptingThreadHandler() thread.
*/ */
fun refreshOutdatedDeviceLists() { suspend fun refreshOutdatedDeviceLists() {
val users = ArrayList<String>() val users = ArrayList<String>()


val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
@ -674,33 +457,24 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
return return
} }


if (isDownloadingKeys) {
// request already in progress - do nothing. (We will automatically
// make another request if there are more users with outdated
// device lists when the current request completes).
return
}

// update the statuses // update the statuses
for (userId in users) { for (userId in users) {
val status = deviceTrackingStatuses[userId] val status = deviceTrackingStatuses[userId]

if (null != status && TRACKING_STATUS_PENDING_DOWNLOAD == status) { if (null != status && TRACKING_STATUS_PENDING_DOWNLOAD == status) {
deviceTrackingStatuses.put(userId, TRACKING_STATUS_DOWNLOAD_IN_PROGRESS) deviceTrackingStatuses.put(userId, TRACKING_STATUS_DOWNLOAD_IN_PROGRESS)
} }
} }


cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)

doKeyDownloadForUsers(users)
doKeyDownloadForUsers(users, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> { .fold(
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) { {
CryptoAsyncHelper.getEncryptBackgroundHandler().post { Timber.v("## refreshOutdatedDeviceLists() : done") } Timber.e(it, "## refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
},
{
Timber.v("## refreshOutdatedDeviceLists() : done")
} }

)
override fun onFailure(failure: Throwable) {
Timber.e(failure, "## refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
}
})
} }


companion object { companion object {

View File

@ -133,7 +133,7 @@ internal class IncomingRoomKeyRequestManager(
if (device.isVerified) { if (device.isVerified) {
Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys") Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys")
mCryptoStore.deleteIncomingRoomKeyRequest(request) mCryptoStore.deleteIncomingRoomKeyRequest(request)
request.share!!.run() request.share?.run()
continue continue
} }



View File

@ -38,7 +38,7 @@ internal class MXOlmDevice(
/** /**
* The store where crypto data is saved. * The store where crypto data is saved.
*/ */
private val mStore: IMXCryptoStore) { private val store: IMXCryptoStore) {


/** /**
* @return the Curve25519 key for the account. * @return the Curve25519 key for the account.
@ -53,16 +53,16 @@ internal class MXOlmDevice(
private set private set


// The OLM lib account instance. // The OLM lib account instance.
private var mOlmAccount: OlmAccount? = null private var olmAccount: OlmAccount? = null


// The OLM lib utility instance. // The OLM lib utility instance.
private var mOlmUtility: OlmUtility? = null private var olmUtility: OlmUtility? = null


// The outbound group session. // The outbound group session.
// They are not stored in 'store' to avoid to remember to which devices we sent the session key. // They are not stored in 'store' to avoid to remember to which devices we sent the session key.
// Plus, in cryptography, it is good to refresh sessions from time to time. // Plus, in cryptography, it is good to refresh sessions from time to time.
// The key is the session id, the value the outbound group session. // The key is the session id, the value the outbound group session.
private val mOutboundGroupSessionStore: MutableMap<String, OlmOutboundGroupSession> = HashMap() private val outboundGroupSessionStore: MutableMap<String, OlmOutboundGroupSession> = HashMap()


// Store a set of decrypted message indexes for each group session. // Store a set of decrypted message indexes for each group session.
// This partially mitigates a replay attack where a MITM resends a group // This partially mitigates a replay attack where a MITM resends a group
@ -76,25 +76,25 @@ internal class MXOlmDevice(
// The first level keys are timeline ids. // The first level keys are timeline ids.
// The second level keys are strings of form "<senderKey>|<session_id>|<message_index>" // The second level keys are strings of form "<senderKey>|<session_id>|<message_index>"
// Values are true. // Values are true.
private val mInboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap() private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap()


/** /**
* inboundGroupSessionWithId error * inboundGroupSessionWithId error
*/ */
private var mInboundGroupSessionWithIdError: MXCryptoError? = null private var inboundGroupSessionWithIdError: MXCryptoError? = null


init { init {
// Retrieve the account from the store // Retrieve the account from the store
mOlmAccount = mStore.getAccount() olmAccount = store.getAccount()


if (null == mOlmAccount) { if (null == olmAccount) {
Timber.v("MXOlmDevice : create a new olm account") Timber.v("MXOlmDevice : create a new olm account")
// Else, create it // Else, create it
try { try {
mOlmAccount = OlmAccount() olmAccount = OlmAccount()
mStore.storeAccount(mOlmAccount!!) store.storeAccount(olmAccount!!)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "MXOlmDevice : cannot initialize mOlmAccount") Timber.e(e, "MXOlmDevice : cannot initialize olmAccount")
} }


} else { } else {
@ -102,20 +102,20 @@ internal class MXOlmDevice(
} }


try { try {
mOlmUtility = OlmUtility() olmUtility = OlmUtility()
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## MXOlmDevice : OlmUtility failed with error") Timber.e(e, "## MXOlmDevice : OlmUtility failed with error")
mOlmUtility = null olmUtility = null
} }


try { try {
deviceCurve25519Key = mOlmAccount!!.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY] deviceCurve25519Key = olmAccount!!.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY]
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## MXOlmDevice : cannot find " + OlmAccount.JSON_KEY_IDENTITY_KEY + " with error") Timber.e(e, "## MXOlmDevice : cannot find " + OlmAccount.JSON_KEY_IDENTITY_KEY + " with error")
} }


try { try {
deviceEd25519Key = mOlmAccount!!.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY] deviceEd25519Key = olmAccount!!.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY]
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## MXOlmDevice : cannot find " + OlmAccount.JSON_KEY_FINGER_PRINT_KEY + " with error") Timber.e(e, "## MXOlmDevice : cannot find " + OlmAccount.JSON_KEY_FINGER_PRINT_KEY + " with error")
} }
@ -126,7 +126,7 @@ internal class MXOlmDevice(
*/ */
fun getOneTimeKeys(): Map<String, Map<String, String>>? { fun getOneTimeKeys(): Map<String, Map<String, String>>? {
try { try {
return mOlmAccount!!.oneTimeKeys() return olmAccount!!.oneTimeKeys()
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## getOneTimeKeys() : failed") Timber.e(e, "## getOneTimeKeys() : failed")
} }
@ -138,14 +138,14 @@ internal class MXOlmDevice(
* @return The maximum number of one-time keys the olm account can store. * @return The maximum number of one-time keys the olm account can store.
*/ */
fun getMaxNumberOfOneTimeKeys(): Long { fun getMaxNumberOfOneTimeKeys(): Long {
return mOlmAccount?.maxOneTimeKeys() ?: -1 return olmAccount?.maxOneTimeKeys() ?: -1
} }


/** /**
* Release the instance * Release the instance
*/ */
fun release() { fun release() {
mOlmAccount?.releaseAccount() olmAccount?.releaseAccount()
} }


/** /**
@ -156,7 +156,7 @@ internal class MXOlmDevice(
*/ */
fun signMessage(message: String): String? { fun signMessage(message: String): String? {
try { try {
return mOlmAccount!!.signMessage(message) return olmAccount!!.signMessage(message)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## signMessage() : failed") Timber.e(e, "## signMessage() : failed")
} }
@ -169,8 +169,8 @@ internal class MXOlmDevice(
*/ */
fun markKeysAsPublished() { fun markKeysAsPublished() {
try { try {
mOlmAccount!!.markOneTimeKeysAsPublished() olmAccount!!.markOneTimeKeysAsPublished()
mStore.storeAccount(mOlmAccount!!) store.storeAccount(olmAccount!!)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## markKeysAsPublished() : failed") Timber.e(e, "## markKeysAsPublished() : failed")
} }
@ -183,8 +183,8 @@ internal class MXOlmDevice(
*/ */
fun generateOneTimeKeys(numKeys: Int) { fun generateOneTimeKeys(numKeys: Int) {
try { try {
mOlmAccount!!.generateOneTimeKeys(numKeys) olmAccount!!.generateOneTimeKeys(numKeys)
mStore.storeAccount(mOlmAccount!!) store.storeAccount(olmAccount!!)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## generateOneTimeKeys() : failed") Timber.e(e, "## generateOneTimeKeys() : failed")
} }
@ -204,7 +204,7 @@ internal class MXOlmDevice(


try { try {
olmSession = OlmSession() olmSession = OlmSession()
olmSession.initOutboundSession(mOlmAccount!!, theirIdentityKey, theirOneTimeKey) olmSession.initOutboundSession(olmAccount!!, theirIdentityKey, theirOneTimeKey)


val mxOlmSession = MXOlmSession(olmSession, 0) val mxOlmSession = MXOlmSession(olmSession, 0)


@ -213,7 +213,7 @@ internal class MXOlmDevice(
// this session // this session
mxOlmSession.onMessageReceived() mxOlmSession.onMessageReceived()


mStore.storeSession(mxOlmSession, theirIdentityKey) store.storeSession(mxOlmSession, theirIdentityKey)


val sessionIdentifier = olmSession.sessionIdentifier() val sessionIdentifier = olmSession.sessionIdentifier()


@ -246,7 +246,7 @@ internal class MXOlmDevice(
try { try {
try { try {
olmSession = OlmSession() olmSession = OlmSession()
olmSession.initInboundSessionFrom(mOlmAccount!!, theirDeviceIdentityKey, ciphertext) olmSession.initInboundSessionFrom(olmAccount!!, theirDeviceIdentityKey, ciphertext)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## createInboundSession() : the session creation failed") Timber.e(e, "## createInboundSession() : the session creation failed")
return null return null
@ -255,15 +255,15 @@ internal class MXOlmDevice(
Timber.v("## createInboundSession() : sessionId: " + olmSession.sessionIdentifier()) Timber.v("## createInboundSession() : sessionId: " + olmSession.sessionIdentifier())


try { try {
mOlmAccount!!.removeOneTimeKeys(olmSession) olmAccount!!.removeOneTimeKeys(olmSession)
mStore.storeAccount(mOlmAccount!!) store.storeAccount(olmAccount!!)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## createInboundSession() : removeOneTimeKeys failed") Timber.e(e, "## createInboundSession() : removeOneTimeKeys failed")
} }


Timber.v("## createInboundSession() : ciphertext: $ciphertext") Timber.v("## createInboundSession() : ciphertext: $ciphertext")
try { try {
val sha256 = mOlmUtility!!.sha256(URLEncoder.encode(ciphertext, "utf-8")) val sha256 = olmUtility!!.sha256(URLEncoder.encode(ciphertext, "utf-8"))
Timber.v("## createInboundSession() :ciphertext: SHA256:" + sha256) Timber.v("## createInboundSession() :ciphertext: SHA256:" + sha256)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## createInboundSession() :ciphertext: cannot encode ciphertext") Timber.e(e, "## createInboundSession() :ciphertext: cannot encode ciphertext")
@ -282,7 +282,7 @@ internal class MXOlmDevice(
// This counts as a received message: set last received message time to now // This counts as a received message: set last received message time to now
mxOlmSession.onMessageReceived() mxOlmSession.onMessageReceived()


mStore.storeSession(mxOlmSession, theirDeviceIdentityKey) store.storeSession(mxOlmSession, theirDeviceIdentityKey)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## createInboundSession() : decryptMessage failed") Timber.e(e, "## createInboundSession() : decryptMessage failed")
} }
@ -316,7 +316,7 @@ internal class MXOlmDevice(
* @return a list of known session ids for the device. * @return a list of known session ids for the device.
*/ */
fun getSessionIds(theirDeviceIdentityKey: String): Set<String>? { fun getSessionIds(theirDeviceIdentityKey: String): Set<String>? {
return mStore.getDeviceSessionIds(theirDeviceIdentityKey) return store.getDeviceSessionIds(theirDeviceIdentityKey)
} }


/** /**
@ -326,7 +326,7 @@ internal class MXOlmDevice(
* @return the session id, or null if no established session. * @return the session id, or null if no established session.
*/ */
fun getSessionId(theirDeviceIdentityKey: String): String? { fun getSessionId(theirDeviceIdentityKey: String): String? {
return mStore.getLastUsedSessionId(theirDeviceIdentityKey) return store.getLastUsedSessionId(theirDeviceIdentityKey)
} }


/** /**
@ -348,7 +348,7 @@ internal class MXOlmDevice(
//Timber.v("## encryptMessage() : payloadString: " + payloadString); //Timber.v("## encryptMessage() : payloadString: " + payloadString);


olmMessage = mxOlmSession.olmSession.encryptMessage(payloadString) olmMessage = mxOlmSession.olmSession.encryptMessage(payloadString)
mStore.storeSession(mxOlmSession, theirDeviceIdentityKey) store.storeSession(mxOlmSession, theirDeviceIdentityKey)
res = HashMap() res = HashMap()


res["body"] = olmMessage.mCipherText res["body"] = olmMessage.mCipherText
@ -384,7 +384,7 @@ internal class MXOlmDevice(
try { try {
payloadString = mxOlmSession.olmSession.decryptMessage(olmMessage) payloadString = mxOlmSession.olmSession.decryptMessage(olmMessage)
mxOlmSession.onMessageReceived() mxOlmSession.onMessageReceived()
mStore.storeSession(mxOlmSession, theirDeviceIdentityKey) store.storeSession(mxOlmSession, theirDeviceIdentityKey)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## decryptMessage() : decryptMessage failed " + e.message) Timber.e(e, "## decryptMessage() : decryptMessage failed " + e.message)
} }
@ -424,7 +424,7 @@ internal class MXOlmDevice(
var session: OlmOutboundGroupSession? = null var session: OlmOutboundGroupSession? = null
try { try {
session = OlmOutboundGroupSession() session = OlmOutboundGroupSession()
mOutboundGroupSessionStore[session.sessionIdentifier()] = session outboundGroupSessionStore[session.sessionIdentifier()] = session
return session.sessionIdentifier() return session.sessionIdentifier()
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "createOutboundGroupSession " + e.message) Timber.e(e, "createOutboundGroupSession " + e.message)
@ -444,7 +444,7 @@ internal class MXOlmDevice(
fun getSessionKey(sessionId: String): String? { fun getSessionKey(sessionId: String): String? {
if (!TextUtils.isEmpty(sessionId)) { if (!TextUtils.isEmpty(sessionId)) {
try { try {
return mOutboundGroupSessionStore[sessionId]!!.sessionKey() return outboundGroupSessionStore[sessionId]!!.sessionKey()
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## getSessionKey() : failed " + e.message) Timber.e(e, "## getSessionKey() : failed " + e.message)
} }
@ -461,7 +461,7 @@ internal class MXOlmDevice(
*/ */
fun getMessageIndex(sessionId: String): Int { fun getMessageIndex(sessionId: String): Int {
return if (!TextUtils.isEmpty(sessionId)) { return if (!TextUtils.isEmpty(sessionId)) {
mOutboundGroupSessionStore[sessionId]!!.messageIndex() outboundGroupSessionStore[sessionId]!!.messageIndex()
} else 0 } else 0
} }


@ -475,7 +475,7 @@ internal class MXOlmDevice(
fun encryptGroupMessage(sessionId: String, payloadString: String): String? { fun encryptGroupMessage(sessionId: String, payloadString: String): String? {
if (!TextUtils.isEmpty(sessionId) && !TextUtils.isEmpty(payloadString)) { if (!TextUtils.isEmpty(sessionId) && !TextUtils.isEmpty(payloadString)) {
try { try {
return mOutboundGroupSessionStore[sessionId]!!.encryptMessage(payloadString) return outboundGroupSessionStore[sessionId]!!.encryptMessage(payloadString)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## encryptGroupMessage() : failed " + e.message) Timber.e(e, "## encryptGroupMessage() : failed " + e.message)
} }
@ -547,7 +547,7 @@ internal class MXOlmDevice(
session.mKeysClaimed = keysClaimed session.mKeysClaimed = keysClaimed
session.mForwardingCurve25519KeyChain = forwardingCurve25519KeyChain session.mForwardingCurve25519KeyChain = forwardingCurve25519KeyChain


mStore.storeInboundGroupSessions(listOf(session)) store.storeInboundGroupSessions(listOf(session))


return true return true
} }
@ -609,7 +609,7 @@ internal class MXOlmDevice(
sessions.add(session) sessions.add(session)
} }


mStore.storeInboundGroupSessions(sessions) store.storeInboundGroupSessions(sessions)


return sessions return sessions
} }
@ -622,7 +622,7 @@ internal class MXOlmDevice(
*/ */
fun removeInboundGroupSession(sessionId: String?, sessionKey: String?) { fun removeInboundGroupSession(sessionId: String?, sessionKey: String?) {
if (null != sessionId && null != sessionKey) { if (null != sessionId && null != sessionKey) {
mStore.removeInboundGroupSession(sessionId, sessionKey) store.removeInboundGroupSession(sessionId, sessionKey)
} }
} }


@ -660,41 +660,41 @@ internal class MXOlmDevice(


if (null != decryptResult) { if (null != decryptResult) {
if (null != timeline) { if (null != timeline) {
if (!mInboundGroupSessionMessageIndexes.containsKey(timeline)) { if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
mInboundGroupSessionMessageIndexes[timeline] = HashMap() inboundGroupSessionMessageIndexes[timeline] = HashMap()
} }


val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex


if (null != mInboundGroupSessionMessageIndexes[timeline]!![messageIndexKey]) { if (null != inboundGroupSessionMessageIndexes[timeline]!![messageIndexKey]) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex) val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
Timber.e("## decryptGroupMessage() : $reason") Timber.e("## decryptGroupMessage() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.DUPLICATED_MESSAGE_INDEX_ERROR_CODE, throw MXDecryptionException(MXCryptoError(MXCryptoError.DUPLICATED_MESSAGE_INDEX_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, reason)) MXCryptoError.UNABLE_TO_DECRYPT, reason))
} }


mInboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true) inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
} }


mStore.storeInboundGroupSessions(listOf(session)) store.storeInboundGroupSessions(listOf(session))
try { try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE) val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
val payload = adapter.fromJson(payloadString) val payload = adapter.fromJson(payloadString)
result.mPayload = payload result.payload = payload
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## decryptGroupMessage() : RLEncoder.encode failed " + e.message) Timber.e(e, "## decryptGroupMessage() : RLEncoder.encode failed " + e.message)
return null return null
} }


if (null == result.mPayload) { if (null == result.payload) {
Timber.e("## decryptGroupMessage() : fails to parse the payload") Timber.e("## decryptGroupMessage() : fails to parse the payload")
return null return null
} }


result.mKeysClaimed = session.mKeysClaimed result.keysClaimed = session.mKeysClaimed
result.mSenderKey = senderKey result.senderKey = senderKey
result.mForwardingCurve25519KeyChain = session.mForwardingCurve25519KeyChain result.forwardingCurve25519KeyChain = session.mForwardingCurve25519KeyChain
} else { } else {
Timber.e("## decryptGroupMessage() : failed to decode the message") Timber.e("## decryptGroupMessage() : failed to decode the message")
throw MXDecryptionException(MXCryptoError(MXCryptoError.OLM_ERROR_CODE, errorMessage, null)) throw MXDecryptionException(MXCryptoError(MXCryptoError.OLM_ERROR_CODE, errorMessage, null))
@ -707,7 +707,7 @@ internal class MXOlmDevice(
} }
} else { } else {
Timber.e("## decryptGroupMessage() : Cannot retrieve inbound group session $sessionId") Timber.e("## decryptGroupMessage() : Cannot retrieve inbound group session $sessionId")
throw MXDecryptionException(mInboundGroupSessionWithIdError) throw MXDecryptionException(inboundGroupSessionWithIdError)
} }


return result return result
@ -720,7 +720,7 @@ internal class MXOlmDevice(
*/ */
fun resetReplayAttackCheckInTimeline(timeline: String?) { fun resetReplayAttackCheckInTimeline(timeline: String?) {
if (null != timeline) { if (null != timeline) {
mInboundGroupSessionMessageIndexes.remove(timeline) inboundGroupSessionMessageIndexes.remove(timeline)
} }
} }


@ -737,7 +737,7 @@ internal class MXOlmDevice(
@Throws(Exception::class) @Throws(Exception::class)
fun verifySignature(key: String, jsonDictionary: Map<String, Any>, signature: String) { fun verifySignature(key: String, jsonDictionary: Map<String, Any>, signature: String) {
// Check signature on the canonical version of the JSON // Check signature on the canonical version of the JSON
mOlmUtility!!.verifyEd25519Signature(signature, key, MoshiProvider.getCanonicalJson<Map<*, *>>(Map::class.java, jsonDictionary)) olmUtility!!.verifyEd25519Signature(signature, key, MoshiProvider.getCanonicalJson<Map<*, *>>(Map::class.java, jsonDictionary))
} }


/** /**
@ -747,7 +747,7 @@ internal class MXOlmDevice(
* @return the base64-encoded hash value. * @return the base64-encoded hash value.
*/ */
fun sha256(message: String): String { fun sha256(message: String): String {
return mOlmUtility!!.sha256(convertToUTF8(message)) return olmUtility!!.sha256(convertToUTF8(message))
} }


/** /**
@ -760,14 +760,14 @@ internal class MXOlmDevice(
private fun getSessionForDevice(theirDeviceIdentityKey: String, sessionId: String): MXOlmSession? { private fun getSessionForDevice(theirDeviceIdentityKey: String, sessionId: String): MXOlmSession? {
// sanity check // sanity check
return if (!TextUtils.isEmpty(theirDeviceIdentityKey) && !TextUtils.isEmpty(sessionId)) { return if (!TextUtils.isEmpty(theirDeviceIdentityKey) && !TextUtils.isEmpty(sessionId)) {
mStore.getDeviceSession(sessionId, theirDeviceIdentityKey) store.getDeviceSession(sessionId, theirDeviceIdentityKey)
} else null } else null


} }


/** /**
* Extract an InboundGroupSession from the session store and do some check. * Extract an InboundGroupSession from the session store and do some check.
* mInboundGroupSessionWithIdError describes the failure reason. * inboundGroupSessionWithIdError describes the failure reason.
* *
* @param roomId the room where the session is used. * @param roomId the room where the session is used.
* @param sessionId the session identifier. * @param sessionId the session identifier.
@ -775,9 +775,9 @@ internal class MXOlmDevice(
* @return the inbound group session. * @return the inbound group session.
*/ */
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): MXOlmInboundGroupSession2? { fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): MXOlmInboundGroupSession2? {
mInboundGroupSessionWithIdError = null inboundGroupSessionWithIdError = null


val session = mStore.getInboundGroupSession(sessionId!!, senderKey!!) val session = store.getInboundGroupSession(sessionId!!, senderKey!!)


if (null != session) { if (null != session) {
// Check that the room id matches the original one for the session. This stops // Check that the room id matches the original one for the session. This stops
@ -785,12 +785,12 @@ internal class MXOlmDevice(
if (!TextUtils.equals(roomId, session.mRoomId)) { if (!TextUtils.equals(roomId, session.mRoomId)) {
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.mRoomId) val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.mRoomId)
Timber.e("## getInboundGroupSession() : $errorDescription") Timber.e("## getInboundGroupSession() : $errorDescription")
mInboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE, inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, errorDescription) MXCryptoError.UNABLE_TO_DECRYPT, errorDescription)
} }
} else { } else {
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId") Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
mInboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE, inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE,
MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON, null) MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON, null)
} }
return session return session

View File

@ -50,7 +50,7 @@ internal class MyDeviceInfoHolder(
myDevice.keys = keys myDevice.keys = keys


myDevice.algorithms = MXCryptoAlgorithms.supportedAlgorithms() myDevice.algorithms = MXCryptoAlgorithms.supportedAlgorithms()
myDevice.mVerified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED myDevice.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED


// Add our own deviceinfo to the store // Add our own deviceinfo to the store
val endToEndDevicesForUser = cryptoStore.getUserDevices(credentials.userId) val endToEndDevicesForUser = cryptoStore.getUserDevices(credentials.userId)

View File

@ -16,15 +16,13 @@


package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto


import im.vector.matrix.android.api.MatrixCallback import arrow.core.Try
import arrow.instances.`try`.applicativeError.handleError
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.model.MXKey import im.vector.matrix.android.internal.crypto.model.MXKey
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith
import org.matrix.olm.OlmAccount import org.matrix.olm.OlmAccount
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -33,15 +31,13 @@ internal class OneTimeKeysUploader(
private val mCredentials: Credentials, private val mCredentials: Credentials,
private val mOlmDevice: MXOlmDevice, private val mOlmDevice: MXOlmDevice,
private val mObjectSigner: ObjectSigner, private val mObjectSigner: ObjectSigner,
private val mUploadKeysTask: UploadKeysTask, private val mUploadKeysTask: UploadKeysTask
private val mTaskExecutor: TaskExecutor
) { ) {
// tell if there is a OTK check in progress // tell if there is a OTK check in progress
private var mOneTimeKeyCheckInProgress = false private var mOneTimeKeyCheckInProgress = false


// last OTK check timestamp // last OTK check timestamp
private var mLastOneTimeKeyCheck: Long = 0 private var mLastOneTimeKeyCheck: Long = 0

private var mOneTimeKeyCount: Int? = null private var mOneTimeKeyCount: Int? = null


var mLastPublishedOneTimeKeys: Map<String, Map<String, String>>? = null var mLastPublishedOneTimeKeys: Map<String, Map<String, String>>? = null
@ -60,23 +56,17 @@ internal class OneTimeKeysUploader(


/** /**
* Check if the OTK must be uploaded. * Check if the OTK must be uploaded.
*
* @param callback the asynchronous callback
*/ */
fun maybeUploadOneTimeKeys(callback: MatrixCallback<Unit>? = null) { suspend fun maybeUploadOneTimeKeys(): Try<Unit> {
if (mOneTimeKeyCheckInProgress) { if (mOneTimeKeyCheckInProgress) {
callback?.onSuccess(Unit) return Try.just(Unit)
return
} }

if (System.currentTimeMillis() - mLastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) { if (System.currentTimeMillis() - mLastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
// we've done a key upload recently. // we've done a key upload recently.
callback?.onSuccess(Unit) return Try.just(Unit)
return
} }


mLastOneTimeKeyCheck = System.currentTimeMillis() mLastOneTimeKeyCheck = System.currentTimeMillis()

mOneTimeKeyCheckInProgress = true mOneTimeKeyCheckInProgress = true


// We then check how many keys we can store in the Account object. // We then check how many keys we can store in the Account object.
@ -89,17 +79,13 @@ internal class OneTimeKeysUploader(
// discard the oldest private keys first. This will eventually clean // discard the oldest private keys first. This will eventually clean
// out stale private keys that won't receive a message. // out stale private keys that won't receive a message.
val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt() val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt()

if (mOneTimeKeyCount != null) {
if (null != mOneTimeKeyCount) { return uploadOTK(mOneTimeKeyCount!!, keyLimit)
uploadOTK(mOneTimeKeyCount!!, keyLimit, callback)
} else { } else {
// ask the server how many keys we have // ask the server how many keys we have
mUploadKeysTask val uploadKeysParams = UploadKeysTask.Params(null, null, mCredentials.deviceId!!)
.configureWith(UploadKeysTask.Params(null, null, mCredentials.deviceId!!)) return mUploadKeysTask.execute(uploadKeysParams)
.executeOn(TaskThread.ENCRYPTION) .flatMap {
.dispatchTo(object : MatrixCallback<KeysUploadResponse> {

override fun onSuccess(data: KeysUploadResponse) {
// We need to keep a pool of one time public keys on the server so that // We need to keep a pool of one time public keys on the server so that
// other devices can start conversations with us. But we can only store // other devices can start conversations with us. But we can only store
// a finite number of private keys in the olm Account object. // a finite number of private keys in the olm Account object.
@ -113,20 +99,14 @@ internal class OneTimeKeysUploader(
// these factors. // these factors.
// TODO Why we do not set mOneTimeKeyCount here? // TODO Why we do not set mOneTimeKeyCount here?
// TODO This is not needed anymore, see https://github.com/matrix-org/matrix-js-sdk/pull/493 (TODO on iOS also) // TODO This is not needed anymore, see https://github.com/matrix-org/matrix-js-sdk/pull/493 (TODO on iOS also)
val keyCount = data.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) val keyCount = it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
uploadOTK(keyCount, keyLimit, callback) uploadOTK(keyCount, keyLimit)
} }

.handleError {
override fun onFailure(failure: Throwable) { Timber.e(it, "## uploadKeys() : failed")
Timber.e(failure, "## uploadKeys() : failed")

mOneTimeKeyCount = null mOneTimeKeyCount = null
mOneTimeKeyCheckInProgress = false mOneTimeKeyCheckInProgress = false

callback?.onFailure(failure)
} }
})
.executeBy(mTaskExecutor)
} }
} }


@ -135,42 +115,40 @@ internal class OneTimeKeysUploader(
* *
* @param keyCount the key count * @param keyCount the key count
* @param keyLimit the limit * @param keyLimit the limit
* @param callback the asynchronous callback
*/ */
private fun uploadOTK(keyCount: Int, keyLimit: Int, callback: MatrixCallback<Unit>?) { private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Try<Unit> {
uploadLoop(keyCount, keyLimit, object : MatrixCallback<Unit> { return uploadLoop(keyCount, keyLimit)
private fun uploadKeysDone(errorMessage: String?) {
if (null != errorMessage) {
Timber.e("## maybeUploadOneTimeKeys() : failed $errorMessage")
}
mOneTimeKeyCount = null
mOneTimeKeyCheckInProgress = false
} }


override fun onSuccess(data: Unit) { /**
Timber.v("## maybeUploadOneTimeKeys() : succeeded") * OTK upload loop
uploadKeysDone(null) *

* @param keyCount the number of key to generate
callback?.onSuccess(Unit) * @param keyLimit the limit
*/
private suspend fun uploadLoop(keyCount: Int, keyLimit: Int): Try<Unit> {
if (keyLimit <= keyCount) {
// If we don't need to generate any more keys then we are done.
return Try.just(Unit)
} }


override fun onFailure(failure: Throwable) { val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
uploadKeysDone(failure.message) mOlmDevice.generateOneTimeKeys(keysThisLoop)

return uploadOneTimeKeys()
callback?.onFailure(failure) .flatMap {
if (it.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
uploadLoop(it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
} else {
Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
Try.raise(Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519"))
}
} }
})

} }


/** /**
* Upload my user's one time keys. * Upload my user's one time keys.
* This method must called on getEncryptingThreadHandler() thread.
* The callback will called on UI thread.
*
* @param callback the asynchronous callback
*/ */
private fun uploadOneTimeKeys(callback: MatrixCallback<KeysUploadResponse>?) { private suspend fun uploadOneTimeKeys(): Try<KeysUploadResponse> {
val oneTimeKeys = mOlmDevice.getOneTimeKeys() val oneTimeKeys = mOlmDevice.getOneTimeKeys()
val oneTimeJson = HashMap<String, Any>() val oneTimeJson = HashMap<String, Any>()


@ -192,57 +170,14 @@ internal class OneTimeKeysUploader(


// For now, we set the device id explicitly, as we may not be using the // For now, we set the device id explicitly, as we may not be using the
// same one as used in login. // same one as used in login.
mUploadKeysTask val uploadParams = UploadKeysTask.Params(null, oneTimeJson, mCredentials.deviceId!!)
.configureWith(UploadKeysTask.Params(null, oneTimeJson, mCredentials.deviceId!!)) return mUploadKeysTask
.executeOn(TaskThread.ENCRYPTION) .execute(uploadParams)
.dispatchTo(object : MatrixCallback<KeysUploadResponse> { .map {
override fun onSuccess(data: KeysUploadResponse) {
mLastPublishedOneTimeKeys = oneTimeKeys mLastPublishedOneTimeKeys = oneTimeKeys
mOlmDevice.markKeysAsPublished() mOlmDevice.markKeysAsPublished()

it
callback?.onSuccess(data)
} }

override fun onFailure(failure: Throwable) {
callback?.onFailure(failure)
}
})
.executeBy(mTaskExecutor)
}

/**
* OTK upload loop
*
* @param keyCount the number of key to generate
* @param keyLimit the limit
* @param callback the asynchronous callback
*/
private fun uploadLoop(keyCount: Int, keyLimit: Int, callback: MatrixCallback<Unit>) {
if (keyLimit <= keyCount) {
// If we don't need to generate any more keys then we are done.
callback.onSuccess(Unit)
return
}

val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)

mOlmDevice.generateOneTimeKeys(keysThisLoop)

uploadOneTimeKeys(object : MatrixCallback<KeysUploadResponse> {
override fun onSuccess(data: KeysUploadResponse) {
if (data.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
uploadLoop(data.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit, callback)
} else {
Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
callback.onFailure(
Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519"))
}
}

override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
})
} }


companion object { companion object {

View File

@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -87,7 +88,7 @@ internal class OutgoingRoomKeyRequestManager(
OutgoingRoomKeyRequest(requestBody, recipients, makeTxnId(), OutgoingRoomKeyRequest.RequestState.UNSENT)) OutgoingRoomKeyRequest(requestBody, recipients, makeTxnId(), OutgoingRoomKeyRequest.RequestState.UNSENT))




if (req!!.mState === OutgoingRoomKeyRequest.RequestState.UNSENT) { if (req?.mState == OutgoingRoomKeyRequest.RequestState.UNSENT) {
startTimer() startTimer()
} }
} }

View File

@ -24,12 +24,12 @@ import timber.log.Timber
import java.util.* import java.util.*


internal class RoomDecryptorProvider( internal class RoomDecryptorProvider(
private val mMXOlmDecryptionFactory: MXOlmDecryptionFactory, private val olmDecryptionFactory: MXOlmDecryptionFactory,
private val mMXMegolmDecryptionFactory: MXMegolmDecryptionFactory private val megolmDecryptionFactory: MXMegolmDecryptionFactory
) { ) {


// A map from algorithm to MXDecrypting instance, for each room // A map from algorithm to MXDecrypting instance, for each room
private val mRoomDecryptors: MutableMap<String /* room id */, MutableMap<String /* algorithm */, IMXDecrypting>> = HashMap() private val roomDecryptors: MutableMap<String /* room id */, MutableMap<String /* algorithm */, IMXDecrypting>> = HashMap()


/** /**
* Get a decryptor for a given room and algorithm. * Get a decryptor for a given room and algorithm.
@ -43,44 +43,36 @@ internal class RoomDecryptorProvider(
*/ */
fun getOrCreateRoomDecryptor(roomId: String?, algorithm: String?): IMXDecrypting? { fun getOrCreateRoomDecryptor(roomId: String?, algorithm: String?): IMXDecrypting? {
// sanity check // sanity check
if (TextUtils.isEmpty(algorithm)) { if (algorithm.isNullOrEmpty() || roomId.isNullOrEmpty()) {
Timber.e("## getRoomDecryptor() : null algorithm") Timber.e("## getRoomDecryptor() : null algorithm")
return null return null
} }


var alg: IMXDecrypting? = null var alg: IMXDecrypting?

synchronized(roomDecryptors) {
if (!TextUtils.isEmpty(roomId)) { if (!roomDecryptors.containsKey(roomId)) {
synchronized(mRoomDecryptors) { roomDecryptors[roomId!!] = HashMap()
if (!mRoomDecryptors.containsKey(roomId)) {
mRoomDecryptors[roomId!!] = HashMap()
} }


alg = mRoomDecryptors[roomId]!![algorithm] alg = roomDecryptors[roomId]!![algorithm]
} }

if (alg != null) {
if (null != alg) {
return alg return alg
} }
}

val decryptingClass = MXCryptoAlgorithms.hasDecryptorClassForAlgorithm(algorithm) val decryptingClass = MXCryptoAlgorithms.hasDecryptorClassForAlgorithm(algorithm)

if (decryptingClass) { if (decryptingClass) {
alg = when (algorithm) { alg = when (algorithm) {
MXCRYPTO_ALGORITHM_MEGOLM -> mMXMegolmDecryptionFactory.create() MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create()
else -> mMXOlmDecryptionFactory.create() else -> olmDecryptionFactory.create()
} }

if (null != alg) { if (null != alg) {
if (!TextUtils.isEmpty(roomId)) { if (!TextUtils.isEmpty(roomId)) {
synchronized(mRoomDecryptors) { synchronized(roomDecryptors) {
mRoomDecryptors[roomId]!!.put(algorithm!!, alg!!) roomDecryptors[roomId]!!.put(algorithm!!, alg!!)
} }
} }
} }
} }

return alg return alg
} }


@ -88,7 +80,6 @@ internal class RoomDecryptorProvider(
if (roomId == null || algorithm == null) { if (roomId == null || algorithm == null) {
return null return null
} }

return roomDecryptors[roomId]?.get(algorithm)
return mRoomDecryptors[roomId]?.get(algorithm)
} }
} }

View File

@ -18,7 +18,11 @@ package im.vector.matrix.android.internal.crypto.actions


import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.internal.crypto.* import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper
import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.MegolmSessionData
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager
import im.vector.matrix.android.internal.crypto.RoomDecryptorProvider
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@ -53,7 +57,6 @@ internal class MegolmSessionDataImporter(private val mOlmDevice: MXOlmDevice,
progressListener.onProgress(0, 100) progressListener.onProgress(0, 100)
} }
} }

val sessions = mOlmDevice.importInboundGroupSessions(megolmSessionsData) val sessions = mOlmDevice.importInboundGroupSessions(megolmSessionsData)


for (megolmSessionData in megolmSessionsData) { for (megolmSessionData in megolmSessionsData) {
@ -65,7 +68,7 @@ internal class MegolmSessionDataImporter(private val mOlmDevice: MXOlmDevice,
if (null != decrypting) { if (null != decrypting) {
try { try {
val sessionId = megolmSessionData.sessionId val sessionId = megolmSessionData.sessionId
Timber.v("## importRoomKeys retrieve mSenderKey " + megolmSessionData.senderKey + " sessionId " + sessionId) Timber.v("## importRoomKeys retrieve senderKey " + megolmSessionData.senderKey + " sessionId " + sessionId)


totalNumbersOfImportedKeys++ totalNumbersOfImportedKeys++



View File

@ -34,8 +34,8 @@ internal class SetDeviceVerificationAction(private val mCryptoStore: IMXCryptoSt
return return
} }


if (device.mVerified != verificationStatus) { if (device.verified != verificationStatus) {
device.mVerified = verificationStatus device.verified = verificationStatus
mCryptoStore.storeUserDevice(userId, device) mCryptoStore.storeUserDevice(userId, device)


if (userId == mCredentials.userId) { if (userId == mCredentials.userId) {

View File

@ -25,21 +25,21 @@ data class MXDecryptionResult(
/** /**
* The decrypted payload (with properties 'type', 'content') * The decrypted payload (with properties 'type', 'content')
*/ */
var mPayload: JsonDict? = null, var payload: JsonDict? = null,


/** /**
* keys that the sender of the event claims ownership of: * keys that the sender of the event claims ownership of:
* map from key type to base64-encoded key. * map from key type to base64-encoded key.
*/ */
var mKeysClaimed: Map<String, String>? = null, var keysClaimed: Map<String, String>? = null,


/** /**
* The curve25519 key that the sender of the event is known to have ownership of. * The curve25519 key that the sender of the event is known to have ownership of.
*/ */
var mSenderKey: String? = null, var senderKey: String? = null,


/** /**
* Devices which forwarded this session to us (normally empty). * Devices which forwarded this session to us (normally empty).
*/ */
var mForwardingCurve25519KeyChain: List<String>? = null var forwardingCurve25519KeyChain: List<String>? = null
) )

View File

@ -74,7 +74,6 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
} }


@Throws(MXDecryptionException::class) @Throws(MXDecryptionException::class)

private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult? { private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult? {
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!! val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
if (TextUtils.isEmpty(encryptedEventContent.senderKey) || TextUtils.isEmpty(encryptedEventContent.sessionId) || TextUtils.isEmpty(encryptedEventContent.ciphertext)) { if (TextUtils.isEmpty(encryptedEventContent.senderKey) || TextUtils.isEmpty(encryptedEventContent.sessionId) || TextUtils.isEmpty(encryptedEventContent.ciphertext)) {
@ -93,17 +92,17 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
} }


// the decryption succeeds // the decryption succeeds
if (decryptGroupMessageResult?.mPayload != null && cryptoError == null) { if (decryptGroupMessageResult?.payload != null && cryptoError == null) {
eventDecryptionResult = MXEventDecryptionResult() eventDecryptionResult = MXEventDecryptionResult()


eventDecryptionResult.clearEvent = decryptGroupMessageResult.mPayload eventDecryptionResult.clearEvent = decryptGroupMessageResult.payload
eventDecryptionResult.senderCurve25519Key = decryptGroupMessageResult.mSenderKey eventDecryptionResult.senderCurve25519Key = decryptGroupMessageResult.senderKey


if (null != decryptGroupMessageResult.mKeysClaimed) { if (null != decryptGroupMessageResult.keysClaimed) {
eventDecryptionResult.claimedEd25519Key = decryptGroupMessageResult.mKeysClaimed!!["ed25519"] eventDecryptionResult.claimedEd25519Key = decryptGroupMessageResult.keysClaimed!!["ed25519"]
} }


eventDecryptionResult.forwardingCurve25519KeyChain = decryptGroupMessageResult.mForwardingCurve25519KeyChain!! eventDecryptionResult.forwardingCurve25519KeyChain = decryptGroupMessageResult.forwardingCurve25519KeyChain!!
} else if (cryptoError != null) { } else if (cryptoError != null) {
if (cryptoError.isOlmError) { if (cryptoError.isOlmError) {
if (TextUtils.equals("UNKNOWN_MESSAGE_INDEX", cryptoError.message)) { if (TextUtils.equals("UNKNOWN_MESSAGE_INDEX", cryptoError.message)) {

View File

@ -387,7 +387,7 @@ internal class KeysBackup(
callback: MatrixCallback<KeysBackupVersionTrust>) { callback: MatrixCallback<KeysBackupVersionTrust>) {
// TODO Validate with François that this is correct // TODO Validate with François that this is correct
object : Task<KeysVersionResult, KeysBackupVersionTrust> { object : Task<KeysVersionResult, KeysBackupVersionTrust> {
override fun execute(params: KeysVersionResult): Try<KeysBackupVersionTrust> { override suspend fun execute(params: KeysVersionResult): Try<KeysBackupVersionTrust> {
return Try { return Try {
getKeysBackupTrustBg(params) getKeysBackupTrustBg(params)
} }

View File

@ -30,7 +30,7 @@ internal class DefaultCreateKeysBackupVersionTask(private val roomKeysApi: RoomK
: CreateKeysBackupVersionTask { : CreateKeysBackupVersionTask {




override fun execute(params: CreateKeysBackupVersionBody): Try<KeysVersion> { override suspend fun execute(params: CreateKeysBackupVersionBody): Try<KeysVersion> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.createKeysBackupVersion(params) apiCall = roomKeysApi.createKeysBackupVersion(params)
} }

View File

@ -32,7 +32,7 @@ internal interface DeleteBackupTask : Task<DeleteBackupTask.Params, Unit> {
internal class DefaultDeleteBackupTask(private val roomKeysApi: RoomKeysApi) internal class DefaultDeleteBackupTask(private val roomKeysApi: RoomKeysApi)
: DeleteBackupTask { : DeleteBackupTask {


override fun execute(params: DeleteBackupTask.Params): Try<Unit> { override suspend fun execute(params: DeleteBackupTask.Params): Try<Unit> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.deleteBackup( apiCall = roomKeysApi.deleteBackup(
params.version) params.version)

View File

@ -33,7 +33,7 @@ internal interface DeleteRoomSessionDataTask : Task<DeleteRoomSessionDataTask.Pa
internal class DefaultDeleteRoomSessionDataTask(private val roomKeysApi: RoomKeysApi) internal class DefaultDeleteRoomSessionDataTask(private val roomKeysApi: RoomKeysApi)
: DeleteRoomSessionDataTask { : DeleteRoomSessionDataTask {


override fun execute(params: DeleteRoomSessionDataTask.Params): Try<Unit> { override suspend fun execute(params: DeleteRoomSessionDataTask.Params): Try<Unit> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.deleteRoomSessionData( apiCall = roomKeysApi.deleteRoomSessionData(
params.roomId, params.roomId,

View File

@ -32,7 +32,7 @@ internal interface DeleteRoomSessionsDataTask : Task<DeleteRoomSessionsDataTask.
internal class DefaultDeleteRoomSessionsDataTask(private val roomKeysApi: RoomKeysApi) internal class DefaultDeleteRoomSessionsDataTask(private val roomKeysApi: RoomKeysApi)
: DeleteRoomSessionsDataTask { : DeleteRoomSessionsDataTask {


override fun execute(params: DeleteRoomSessionsDataTask.Params): Try<Unit> { override suspend fun execute(params: DeleteRoomSessionsDataTask.Params): Try<Unit> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.deleteRoomSessionsData( apiCall = roomKeysApi.deleteRoomSessionsData(
params.roomId, params.roomId,

View File

@ -31,7 +31,7 @@ internal interface DeleteSessionsDataTask : Task<DeleteSessionsDataTask.Params,
internal class DefaultDeleteSessionsDataTask(private val roomKeysApi: RoomKeysApi) internal class DefaultDeleteSessionsDataTask(private val roomKeysApi: RoomKeysApi)
: DeleteSessionsDataTask { : DeleteSessionsDataTask {


override fun execute(params: DeleteSessionsDataTask.Params): Try<Unit> { override suspend fun execute(params: DeleteSessionsDataTask.Params): Try<Unit> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.deleteSessionsData( apiCall = roomKeysApi.deleteSessionsData(
params.version) params.version)

View File

@ -29,7 +29,7 @@ internal class DefaultGetKeysBackupLastVersionTask(private val roomKeysApi: Room
: GetKeysBackupLastVersionTask { : GetKeysBackupLastVersionTask {




override fun execute(params: Unit): Try<KeysVersionResult> { override suspend fun execute(params: Unit): Try<KeysVersionResult> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.getKeysBackupLastVersion() apiCall = roomKeysApi.getKeysBackupLastVersion()
} }

View File

@ -29,7 +29,7 @@ internal class DefaultGetKeysBackupVersionTask(private val roomKeysApi: RoomKeys
: GetKeysBackupVersionTask { : GetKeysBackupVersionTask {




override fun execute(params: String): Try<KeysVersionResult> { override suspend fun execute(params: String): Try<KeysVersionResult> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.getKeysBackupVersion(params) apiCall = roomKeysApi.getKeysBackupVersion(params)
} }

View File

@ -34,7 +34,7 @@ internal interface GetRoomSessionDataTask : Task<GetRoomSessionDataTask.Params,
internal class DefaultGetRoomSessionDataTask(private val roomKeysApi: RoomKeysApi) internal class DefaultGetRoomSessionDataTask(private val roomKeysApi: RoomKeysApi)
: GetRoomSessionDataTask { : GetRoomSessionDataTask {


override fun execute(params: GetRoomSessionDataTask.Params): Try<KeyBackupData> { override suspend fun execute(params: GetRoomSessionDataTask.Params): Try<KeyBackupData> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.getRoomSessionData( apiCall = roomKeysApi.getRoomSessionData(
params.roomId, params.roomId,

View File

@ -34,7 +34,7 @@ internal interface GetRoomSessionsDataTask : Task<GetRoomSessionsDataTask.Params
internal class DefaultGetRoomSessionsDataTask(private val roomKeysApi: RoomKeysApi) internal class DefaultGetRoomSessionsDataTask(private val roomKeysApi: RoomKeysApi)
: GetRoomSessionsDataTask { : GetRoomSessionsDataTask {


override fun execute(params: GetRoomSessionsDataTask.Params): Try<RoomKeysBackupData> { override suspend fun execute(params: GetRoomSessionsDataTask.Params): Try<RoomKeysBackupData> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.getRoomSessionsData( apiCall = roomKeysApi.getRoomSessionsData(
params.roomId, params.roomId,

View File

@ -32,7 +32,7 @@ internal interface GetSessionsDataTask : Task<GetSessionsDataTask.Params, KeysBa
internal class DefaultGetSessionsDataTask(private val roomKeysApi: RoomKeysApi) internal class DefaultGetSessionsDataTask(private val roomKeysApi: RoomKeysApi)
: GetSessionsDataTask { : GetSessionsDataTask {


override fun execute(params: GetSessionsDataTask.Params): Try<KeysBackupData> { override suspend fun execute(params: GetSessionsDataTask.Params): Try<KeysBackupData> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.getSessionsData( apiCall = roomKeysApi.getSessionsData(
params.version) params.version)

View File

@ -36,7 +36,7 @@ internal interface StoreRoomSessionDataTask : Task<StoreRoomSessionDataTask.Para
internal class DefaultStoreRoomSessionDataTask(private val roomKeysApi: RoomKeysApi) internal class DefaultStoreRoomSessionDataTask(private val roomKeysApi: RoomKeysApi)
: StoreRoomSessionDataTask { : StoreRoomSessionDataTask {


override fun execute(params: StoreRoomSessionDataTask.Params): Try<BackupKeysResult> { override suspend fun execute(params: StoreRoomSessionDataTask.Params): Try<BackupKeysResult> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.storeRoomSessionData( apiCall = roomKeysApi.storeRoomSessionData(
params.roomId, params.roomId,

View File

@ -35,7 +35,7 @@ internal interface StoreRoomSessionsDataTask : Task<StoreRoomSessionsDataTask.Pa
internal class DefaultStoreRoomSessionsDataTask(private val roomKeysApi: RoomKeysApi) internal class DefaultStoreRoomSessionsDataTask(private val roomKeysApi: RoomKeysApi)
: StoreRoomSessionsDataTask { : StoreRoomSessionsDataTask {


override fun execute(params: StoreRoomSessionsDataTask.Params): Try<BackupKeysResult> { override suspend fun execute(params: StoreRoomSessionsDataTask.Params): Try<BackupKeysResult> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.storeRoomSessionsData( apiCall = roomKeysApi.storeRoomSessionsData(
params.roomId, params.roomId,

View File

@ -34,7 +34,7 @@ internal interface StoreSessionsDataTask : Task<StoreSessionsDataTask.Params, Ba
internal class DefaultStoreSessionsDataTask(private val roomKeysApi: RoomKeysApi) internal class DefaultStoreSessionsDataTask(private val roomKeysApi: RoomKeysApi)
: StoreSessionsDataTask { : StoreSessionsDataTask {


override fun execute(params: StoreSessionsDataTask.Params): Try<BackupKeysResult> { override suspend fun execute(params: StoreSessionsDataTask.Params): Try<BackupKeysResult> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.storeSessionsData( apiCall = roomKeysApi.storeSessionsData(
params.version, params.version,

View File

@ -34,7 +34,7 @@ internal class DefaultUpdateKeysBackupVersionTask(private val roomKeysApi: RoomK
: UpdateKeysBackupVersionTask { : UpdateKeysBackupVersionTask {




override fun execute(params: UpdateKeysBackupVersionTask.Params): Try<Unit> { override suspend fun execute(params: UpdateKeysBackupVersionTask.Params): Try<Unit> {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody) apiCall = roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody)
} }

View File

@ -69,7 +69,7 @@ data class MXDeviceInfo(
/** /**
* Verification state of this device. * Verification state of this device.
*/ */
var mVerified: Int = DEVICE_VERIFICATION_UNKNOWN var verified: Int = DEVICE_VERIFICATION_UNKNOWN
) : Serializable { ) : Serializable {
/** /**
* Tells if the device is unknown * Tells if the device is unknown
@ -77,7 +77,7 @@ data class MXDeviceInfo(
* @return true if the device is unknown * @return true if the device is unknown
*/ */
val isUnknown: Boolean val isUnknown: Boolean
get() = mVerified == DEVICE_VERIFICATION_UNKNOWN get() = verified == DEVICE_VERIFICATION_UNKNOWN


/** /**
* Tells if the device is verified. * Tells if the device is verified.
@ -85,7 +85,7 @@ data class MXDeviceInfo(
* @return true if the device is verified * @return true if the device is verified
*/ */
val isVerified: Boolean val isVerified: Boolean
get() = mVerified == DEVICE_VERIFICATION_VERIFIED get() = verified == DEVICE_VERIFICATION_VERIFIED


/** /**
* Tells if the device is unverified. * Tells if the device is unverified.
@ -93,7 +93,7 @@ data class MXDeviceInfo(
* @return true if the device is unverified * @return true if the device is unverified
*/ */
val isUnverified: Boolean val isUnverified: Boolean
get() = mVerified == DEVICE_VERIFICATION_UNVERIFIED get() = verified == DEVICE_VERIFICATION_UNVERIFIED


/** /**
* Tells if the device is blocked. * Tells if the device is blocked.
@ -101,7 +101,7 @@ data class MXDeviceInfo(
* @return true if the device is blocked * @return true if the device is blocked
*/ */
val isBlocked: Boolean val isBlocked: Boolean
get() = mVerified == DEVICE_VERIFICATION_BLOCKED get() = verified == DEVICE_VERIFICATION_BLOCKED


/** /**
* @return the fingerprint * @return the fingerprint

View File

@ -37,7 +37,7 @@ internal interface ClaimOneTimeKeysForUsersDeviceTask : Task<ClaimOneTimeKeysFor
internal class DefaultClaimOneTimeKeysForUsersDevice(private val cryptoApi: CryptoApi) internal class DefaultClaimOneTimeKeysForUsersDevice(private val cryptoApi: CryptoApi)
: ClaimOneTimeKeysForUsersDeviceTask { : ClaimOneTimeKeysForUsersDeviceTask {


override fun execute(params: ClaimOneTimeKeysForUsersDeviceTask.Params): Try<MXUsersDevicesMap<MXKey>> { override suspend fun execute(params: ClaimOneTimeKeysForUsersDeviceTask.Params): Try<MXUsersDevicesMap<MXKey>> {
val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map) val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map)


return executeRequest<KeysClaimResponse> { return executeRequest<KeysClaimResponse> {

View File

@ -32,7 +32,7 @@ internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
internal class DefaultDeleteDeviceTask(private val cryptoApi: CryptoApi) internal class DefaultDeleteDeviceTask(private val cryptoApi: CryptoApi)
: DeleteDeviceTask { : DeleteDeviceTask {


override fun execute(params: DeleteDeviceTask.Params): Try<Unit> { override suspend fun execute(params: DeleteDeviceTask.Params): Try<Unit> {
return executeRequest { return executeRequest {
apiCall = cryptoApi.deleteDevice(params.deviceId, apiCall = cryptoApi.deleteDevice(params.deviceId,
DeleteDeviceParams()) DeleteDeviceParams())

View File

@ -36,7 +36,7 @@ internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Para
internal class DefaultDownloadKeysForUsers(private val cryptoApi: CryptoApi) internal class DefaultDownloadKeysForUsers(private val cryptoApi: CryptoApi)
: DownloadKeysForUsersTask { : DownloadKeysForUsersTask {


override fun execute(params: DownloadKeysForUsersTask.Params): Try<KeysQueryResponse> { override suspend fun execute(params: DownloadKeysForUsersTask.Params): Try<KeysQueryResponse> {
val downloadQuery = HashMap<String, Map<String, Any>>() val downloadQuery = HashMap<String, Map<String, Any>>()


if (null != params.userIds) { if (null != params.userIds) {

View File

@ -27,7 +27,7 @@ internal interface GetDevicesTask : Task<Unit, DevicesListResponse>
internal class DefaultGetDevicesTask(private val cryptoApi: CryptoApi) internal class DefaultGetDevicesTask(private val cryptoApi: CryptoApi)
: GetDevicesTask { : GetDevicesTask {


override fun execute(params: Unit): Try<DevicesListResponse> { override suspend fun execute(params: Unit): Try<DevicesListResponse> {
return executeRequest { return executeRequest {
apiCall = cryptoApi.getDevices() apiCall = cryptoApi.getDevices()
} }

View File

@ -34,7 +34,7 @@ internal interface GetKeyChangesTask : Task<GetKeyChangesTask.Params, KeyChanges
internal class DefaultGetKeyChangesTask(private val cryptoApi: CryptoApi) internal class DefaultGetKeyChangesTask(private val cryptoApi: CryptoApi)
: GetKeyChangesTask { : GetKeyChangesTask {


override fun execute(params: GetKeyChangesTask.Params): Try<KeyChangesResponse> { override suspend fun execute(params: GetKeyChangesTask.Params): Try<KeyChangesResponse> {
return executeRequest { return executeRequest {
apiCall = cryptoApi.getKeyChanges(params.from, apiCall = cryptoApi.getKeyChanges(params.from,
params.to) params.to)

View File

@ -38,7 +38,7 @@ internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
internal class DefaultSendToDeviceTask(private val cryptoApi: CryptoApi) internal class DefaultSendToDeviceTask(private val cryptoApi: CryptoApi)
: SendToDeviceTask { : SendToDeviceTask {


override fun execute(params: SendToDeviceTask.Params): Try<Unit> { override suspend fun execute(params: SendToDeviceTask.Params): Try<Unit> {
val sendToDeviceBody = SendToDeviceBody() val sendToDeviceBody = SendToDeviceBody()
sendToDeviceBody.messages = params.contentMap.map sendToDeviceBody.messages = params.contentMap.map



View File

@ -35,7 +35,7 @@ internal interface SetDeviceNameTask : Task<SetDeviceNameTask.Params, Unit> {
internal class DefaultSetDeviceNameTask(private val cryptoApi: CryptoApi) internal class DefaultSetDeviceNameTask(private val cryptoApi: CryptoApi)
: SetDeviceNameTask { : SetDeviceNameTask {


override fun execute(params: SetDeviceNameTask.Params): Try<Unit> { override suspend fun execute(params: SetDeviceNameTask.Params): Try<Unit> {
val body = UpdateDeviceInfoBody( val body = UpdateDeviceInfoBody(
displayName = if (TextUtils.isEmpty(params.deviceName)) "" else params.deviceName displayName = if (TextUtils.isEmpty(params.deviceName)) "" else params.deviceName
) )

View File

@ -39,7 +39,7 @@ internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadRespon
internal class DefaultUploadKeysTask(private val cryptoApi: CryptoApi) internal class DefaultUploadKeysTask(private val cryptoApi: CryptoApi)
: UploadKeysTask { : UploadKeysTask {


override fun execute(params: UploadKeysTask.Params): Try<KeysUploadResponse> { override suspend fun execute(params: UploadKeysTask.Params): Try<KeysUploadResponse> {
val encodedDeviceId = convertToUTF8(params.deviceId) val encodedDeviceId = convertToUTF8(params.deviceId)


val body = KeysUploadBody() val body = KeysUploadBody()

View File

@ -33,11 +33,18 @@ import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import kotlin.collections.HashMap import kotlin.collections.HashMap
@ -53,6 +60,7 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val setDeviceVerificationAction: SetDeviceVerificationAction, private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val mSendToDeviceTask: SendToDeviceTask, private val mSendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val mTaskExecutor: TaskExecutor) private val mTaskExecutor: TaskExecutor)
: VerificationTransaction.Listener, SasVerificationService { : VerificationTransaction.Listener, SasVerificationService {


@ -63,8 +71,7 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia


// Event received from the sync // Event received from the sync
fun onToDeviceEvent(event: Event) { fun onToDeviceEvent(event: Event) {
CryptoAsyncHelper.getDecryptBackgroundHandler().post { CoroutineScope(coroutineDispatchers.crypto).launch {
// TODO We are already in a BG thread
when (event.getClearType()) { when (event.getClearType()) {
EventType.KEY_VERIFICATION_START -> { EventType.KEY_VERIFICATION_START -> {
onStartRequestReceived(event) onStartRequestReceived(event)
@ -143,7 +150,7 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
} }
} }


private fun onStartRequestReceived(event: Event) { private suspend fun onStartRequestReceived(event: Event) {
val startReq = event.getClearContent().toModel<KeyVerificationStart>()!! val startReq = event.getClearContent().toModel<KeyVerificationStart>()!!


val otherUserId = event.sender val otherUserId = event.sender
@ -208,30 +215,24 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
}) })
} }


private fun checkKeysAreDownloaded(otherUserId: String, private suspend fun checkKeysAreDownloaded(otherUserId: String,
startReq: KeyVerificationStart, startReq: KeyVerificationStart,
success: (MXUsersDevicesMap<MXDeviceInfo>) -> Unit, success: (MXUsersDevicesMap<MXDeviceInfo>) -> Unit,
error: () -> Unit) { error: () -> Unit) {
deviceListManager.downloadKeys(listOf(otherUserId), true, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> { deviceListManager.downloadKeys(listOf(otherUserId), true)
override fun onFailure(failure: Throwable) { .fold(
CryptoAsyncHelper.getDecryptBackgroundHandler().post { { error() },
error() {
} if (it != null && it.getUserDeviceIds(otherUserId).contains(startReq.fromDevice)) {
} success(it)

override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
if (data.getUserDeviceIds(otherUserId).contains(startReq.fromDevice)) {
success(data)
} else { } else {
error() error()
} }
} }
} )
})
} }


private fun onCancelReceived(event: Event) { private suspend fun onCancelReceived(event: Event) {
Timber.v("## SAS onCancelReceived") Timber.v("## SAS onCancelReceived")
val cancelReq = event.getClearContent().toModel<KeyVerificationCancel>()!! val cancelReq = event.getClearContent().toModel<KeyVerificationCancel>()!!


@ -254,7 +255,7 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
} }
} }


private fun onAcceptReceived(event: Event) { private suspend fun onAcceptReceived(event: Event) {
val acceptReq = event.getClearContent().toModel<KeyVerificationAccept>()!! val acceptReq = event.getClearContent().toModel<KeyVerificationAccept>()!!


if (!acceptReq.isValid()) { if (!acceptReq.isValid()) {
@ -278,7 +279,7 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
} }




private fun onKeyReceived(event: Event) { private suspend fun onKeyReceived(event: Event) {
val keyReq = event.getClearContent().toModel<KeyVerificationKey>()!! val keyReq = event.getClearContent().toModel<KeyVerificationKey>()!!


if (!keyReq.isValid()) { if (!keyReq.isValid()) {
@ -299,7 +300,7 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
} }
} }


private fun onMacReceived(event: Event) { private suspend fun onMacReceived(event: Event) {
val macReq = event.getClearContent().toModel<KeyVerificationMac>()!! val macReq = event.getClearContent().toModel<KeyVerificationMac>()!!


if (!macReq.isValid()) { if (!macReq.isValid()) {

View File

@ -25,7 +25,7 @@ internal interface ClearCacheTask : Task<Unit, Unit>


internal class RealmClearCacheTask(val realmConfiguration: RealmConfiguration) : ClearCacheTask { internal class RealmClearCacheTask(val realmConfiguration: RealmConfiguration) : ClearCacheTask {


override fun execute(params: Unit): Try<Unit> { override suspend fun execute(params: Unit): Try<Unit> {
return Try { return Try {
val realm = Realm.getInstance(realmConfiguration) val realm = Realm.getInstance(realmConfiguration)



View File

@ -38,7 +38,7 @@ internal class DefaultSaveFilterTask(private val sessionParams: SessionParams,
private val filterRepository: FilterRepository private val filterRepository: FilterRepository
) : SaveFilterTask { ) : SaveFilterTask {


override fun execute(params: SaveFilterTask.Params): Try<Unit> { override suspend fun execute(params: SaveFilterTask.Params): Try<Unit> {
return executeRequest<FilterResponse> { return executeRequest<FilterResponse> {
// TODO auto retry // TODO auto retry
apiCall = filterAPI.uploadFilter(sessionParams.credentials.userId, params.filter) apiCall = filterAPI.uploadFilter(sessionParams.credentials.userId, params.filter)

View File

@ -43,7 +43,7 @@ internal class DefaultGetGroupDataTask(
private val monarchy: Monarchy private val monarchy: Monarchy
) : GetGroupDataTask { ) : GetGroupDataTask {


override fun execute(params: GetGroupDataTask.Params): Try<Unit> { override suspend fun execute(params: GetGroupDataTask.Params): Try<Unit> {
val groupId = params.groupId val groupId = params.groupId
return Try.monad().binding { return Try.monad().binding {



View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.session.group package im.vector.matrix.android.internal.session.group


import android.content.Context import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import arrow.core.Try import arrow.core.Try
@ -27,7 +28,7 @@ import org.koin.standalone.inject


internal class GetGroupDataWorker(context: Context, internal class GetGroupDataWorker(context: Context,
workerParameters: WorkerParameters workerParameters: WorkerParameters
) : Worker(context, workerParameters), MatrixKoinComponent { ) : CoroutineWorker(context, workerParameters), MatrixKoinComponent {


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal data class Params(
@ -36,7 +37,7 @@ internal class GetGroupDataWorker(context: Context,


private val getGroupDataTask by inject<GetGroupDataTask>() private val getGroupDataTask by inject<GetGroupDataTask>()


override fun doWork(): Result { override suspend fun doWork(): Result {
val params = WorkerParamsFactory.fromData<Params>(inputData) val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.failure() ?: return Result.failure()


@ -47,7 +48,7 @@ internal class GetGroupDataWorker(context: Context,
return if (isSuccessful) Result.success() else Result.retry() return if (isSuccessful) Result.success() else Result.retry()
} }


private fun fetchGroupData(groupId: String): Try<Unit> { private suspend fun fetchGroupData(groupId: String): Try<Unit> {
return getGroupDataTask.execute(GetGroupDataTask.Params(groupId)) return getGroupDataTask.execute(GetGroupDataTask.Params(groupId))
} }



View File

@ -41,7 +41,7 @@ internal class DefaultCreateRoomTask(private val roomAPI: RoomAPI,
private val realmConfiguration: RealmConfiguration) : CreateRoomTask { private val realmConfiguration: RealmConfiguration) : CreateRoomTask {




override fun execute(params: CreateRoomParams): Try<String> { override suspend fun execute(params: CreateRoomParams): Try<String> {
return executeRequest<CreateRoomResponse> { return executeRequest<CreateRoomResponse> {
apiCall = roomAPI.createRoom(params) apiCall = roomAPI.createRoom(params)
}.flatMap { createRoomResponse -> }.flatMap { createRoomResponse ->

View File

@ -31,7 +31,7 @@ internal interface InviteTask : Task<InviteTask.Params, Unit> {


internal class DefaultInviteTask(private val roomAPI: RoomAPI) : InviteTask { internal class DefaultInviteTask(private val roomAPI: RoomAPI) : InviteTask {


override fun execute(params: InviteTask.Params): Try<Unit> { override suspend fun execute(params: InviteTask.Params): Try<Unit> {
return executeRequest { return executeRequest {
val body = InviteBody(params.userId) val body = InviteBody(params.userId)
apiCall = roomAPI.invite(params.roomId, body) apiCall = roomAPI.invite(params.roomId, body)

View File

@ -44,7 +44,7 @@ internal class DefaultLoadRoomMembersTask(private val roomAPI: RoomAPI,
private val roomSummaryUpdater: RoomSummaryUpdater private val roomSummaryUpdater: RoomSummaryUpdater
) : LoadRoomMembersTask { ) : LoadRoomMembersTask {


override fun execute(params: LoadRoomMembersTask.Params): Try<Boolean> { override suspend fun execute(params: LoadRoomMembersTask.Params): Try<Boolean> {
return if (areAllMembersAlreadyLoaded(params.roomId)) { return if (areAllMembersAlreadyLoaded(params.roomId)) {
Try.just(true) Try.just(true)
} else { } else {

View File

@ -50,7 +50,7 @@ internal class DefaultSetReadMarkersTask(private val roomAPI: RoomAPI,
private val monarchy: Monarchy private val monarchy: Monarchy
) : SetReadMarkersTask { ) : SetReadMarkersTask {


override fun execute(params: SetReadMarkersTask.Params): Try<Unit> { override suspend fun execute(params: SetReadMarkersTask.Params): Try<Unit> {
val markers = HashMap<String, String>() val markers = HashMap<String, String>()
if (params.fullyReadEventId != null && MatrixPatterns.isEventId(params.fullyReadEventId)) { if (params.fullyReadEventId != null && MatrixPatterns.isEventId(params.fullyReadEventId)) {
markers[READ_MARKER] = params.fullyReadEventId markers[READ_MARKER] = params.fullyReadEventId

View File

@ -30,7 +30,7 @@ internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
} }


internal class DefaultSendStateTask(private val roomAPI: RoomAPI) : SendStateTask { internal class DefaultSendStateTask(private val roomAPI: RoomAPI) : SendStateTask {
override fun execute(params: SendStateTask.Params): Try<Unit> { override suspend fun execute(params: SendStateTask.Params): Try<Unit> {
return executeRequest { return executeRequest {
apiCall = roomAPI.sendStateEvent(params.roomId, params.eventType, params.body) apiCall = roomAPI.sendStateEvent(params.roomId, params.eventType, params.body)
} }

View File

@ -36,7 +36,7 @@ internal class DefaultGetContextOfEventTask(private val roomAPI: RoomAPI,
private val tokenChunkEventPersistor: TokenChunkEventPersistor private val tokenChunkEventPersistor: TokenChunkEventPersistor
) : GetContextOfEventTask { ) : GetContextOfEventTask {


override fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> { override suspend fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> {
val filter = filterRepository.getRoomFilter() val filter = filterRepository.getRoomFilter()
return executeRequest<EventContextResponse> { return executeRequest<EventContextResponse> {
apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter) apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter)

View File

@ -39,7 +39,7 @@ internal class DefaultPaginationTask(private val roomAPI: RoomAPI,
private val tokenChunkEventPersistor: TokenChunkEventPersistor private val tokenChunkEventPersistor: TokenChunkEventPersistor
) : PaginationTask { ) : PaginationTask {


override fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> { override suspend fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> {
val filter = filterRepository.getRoomFilter() val filter = filterRepository.getRoomFilter()
return executeRequest<PaginationResponse> { return executeRequest<PaginationResponse> {
apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter)

View File

@ -30,7 +30,7 @@ internal class GetEventTask(private val roomAPI: RoomAPI
val eventId: String val eventId: String
) )


override fun execute(params: Params): Try<Event> { override suspend fun execute(params: Params): Try<Event> {
return executeRequest { return executeRequest {
apiCall = roomAPI.getEvent(params.roomId, params.eventId) apiCall = roomAPI.getEvent(params.roomId, params.eventId)
} }

View File

@ -27,7 +27,7 @@ internal interface SignOutTask : Task<Unit, Unit>
internal class DefaultSignOutTask(private val signOutAPI: SignOutAPI, internal class DefaultSignOutTask(private val signOutAPI: SignOutAPI,
private val sessionParamsStore: SessionParamsStore) : SignOutTask { private val sessionParamsStore: SessionParamsStore) : SignOutTask {


override fun execute(params: Unit): Try<Unit> { override suspend fun execute(params: Unit): Try<Unit> {
return executeRequest<Unit> { return executeRequest<Unit> {
apiCall = signOutAPI.signOut() apiCall = signOutAPI.signOut()
}.flatMap { }.flatMap {

View File

@ -37,7 +37,6 @@ internal class CryptoSyncHandler(private val cryptoManager: CryptoManager,
toDevice.events?.forEach { event -> toDevice.events?.forEach { event ->
// 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.mClearEvent?.content?.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)

View File

@ -40,7 +40,7 @@ internal class DefaultSyncTask(private val syncAPI: SyncAPI,
) : SyncTask { ) : SyncTask {




override fun execute(params: SyncTask.Params): Try<SyncResponse> { override suspend fun execute(params: SyncTask.Params): Try<SyncResponse> {
val requestParams = HashMap<String, String>() val requestParams = HashMap<String, String>()
var timeout = 0 var timeout = 0
if (params.token != null) { if (params.token != null) {

View File

@ -35,7 +35,7 @@ internal interface UpdateUserTask : Task<UpdateUserTask.Params, Unit> {


internal class DefaultUpdateUserTask(private val monarchy: Monarchy) : UpdateUserTask { internal class DefaultUpdateUserTask(private val monarchy: Monarchy) : UpdateUserTask {


override fun execute(params: UpdateUserTask.Params): Try<Unit> { override suspend fun execute(params: UpdateUserTask.Params): Try<Unit> {
return monarchy.tryTransactionSync { realm -> return monarchy.tryTransactionSync { realm ->
params.eventIds.forEach { eventId -> params.eventIds.forEach { eventId ->
val event = EventEntity.where(realm, eventId).findFirst()?.asDomain() val event = EventEntity.where(realm, eventId).findFirst()?.asDomain()

View File

@ -34,7 +34,7 @@ internal data class ConfigurableTask<PARAMS, RESULT>(
) : Task<PARAMS, RESULT> { ) : Task<PARAMS, RESULT> {




override fun execute(params: PARAMS): Try<RESULT> { override suspend fun execute(params: PARAMS): Try<RESULT> {
return task.execute(params) return task.execute(params)
} }



View File

@ -20,7 +20,7 @@ import arrow.core.Try


internal interface Task<PARAMS, RESULT> { internal interface Task<PARAMS, RESULT> {


fun execute(params: PARAMS): Try<RESULT> suspend fun execute(params: PARAMS): Try<RESULT>


} }



View File

@ -68,8 +68,7 @@ internal class TaskExecutor(private val coroutineDispatchers: MatrixCoroutineDis
TaskThread.COMPUTATION -> coroutineDispatchers.computation TaskThread.COMPUTATION -> coroutineDispatchers.computation
TaskThread.IO -> coroutineDispatchers.io TaskThread.IO -> coroutineDispatchers.io
TaskThread.CALLER -> EmptyCoroutineContext TaskThread.CALLER -> EmptyCoroutineContext
TaskThread.ENCRYPTION -> coroutineDispatchers.crypto TaskThread.CRYPTO -> coroutineDispatchers.crypto
TaskThread.DECRYPTION -> coroutineDispatchers.crypto
} }





View File

@ -21,6 +21,5 @@ internal enum class TaskThread {
COMPUTATION, COMPUTATION,
IO, IO,
CALLER, CALLER,
ENCRYPTION, CRYPTO
DECRYPTION
} }