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 {

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 tokenChunkEvent = FakeTokenChunkEvent(
Random.nextLong(System.currentTimeMillis()).toString(),

View File

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

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 tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
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.os.Handler
import android.text.TextUtils
import arrow.core.Try
import arrow.instances.`try`.applicativeError.handleError
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
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.sync.model.SyncResponse
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.olm.OlmManager
import timber.log.Timber
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean

/**
* 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()

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

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

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

/**
@ -229,7 +233,7 @@ internal class CryptoManager(
* @return true if the crypto is starting
*/
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
*/
fun start(isInitialSync: Boolean) {
if (isStarting) {
if (isStarting.get()) {
return
}

@ -254,65 +258,43 @@ internal class CryptoManager(
// return
//}

isStarting = true

isStarting.set(true)
// Open the store
cryptoStore.open()

uploadDeviceKeys(object : MatrixCallback<KeysUploadResponse> {
private fun onError() {
Handler().postDelayed({
if (!isStarted()) {
isStarting = false
start(isInitialSync)
}
}, 1000)
}

override fun onSuccess(data: KeysUploadResponse) {
Timber.v("###########################################################")
Timber.v("uploadDeviceKeys done for " + credentials.userId)
Timber.v(" - device id : " + credentials.deviceId)
Timber.v(" - ed25519 : " + olmDevice.deviceEd25519Key)
Timber.v(" - curve25519 : " + olmDevice.deviceCurve25519Key)
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()

keysBackup.checkAndStartKeysBackup()

if (isInitialSync) {
// refresh the devices list for each known room members
deviceListManager.invalidateAllDeviceLists()
deviceListManager.refreshOutdatedDeviceLists()
} else {
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
}
CoroutineScope(coroutineDispatchers.crypto).launch {
uploadDeviceKeys()
.flatMap {
oneTimeKeysUploader.maybeUploadOneTimeKeys()
}

override fun onFailure(failure: Throwable) {
Timber.e(failure, "## start failed")
onError()
.handleError {
Handler().postDelayed(
{
if (!isStarted()) {
isStarting.set(false)
start(isInitialSync)
}
}, 1000
)
}
})
}

override fun onFailure(failure: Throwable) {
Timber.e(failure, "## start failed")
onError()
}
})
.fold(
{
Timber.e("Start failed: $it")
},
{
isStarting.set(false)
isStarted.set(true)
outgoingRoomKeyRequestManager.start()
keysBackup.checkAndStartKeysBackup()
if (isInitialSync) {
// refresh the devices list for each known room members
deviceListManager.invalidateAllDeviceLists()
deviceListManager.refreshOutdatedDeviceLists()
} else {
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
}
}
)
}
}

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

cryptoStore.close()

outgoingRoomKeyRequestManager.stop()
}

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

if (isStarted()) {
// Make sure we process to-device messages before generating new one-time-keys #2782
deviceListManager.refreshOutdatedDeviceLists()
oneTimeKeysUploader.maybeUploadOneTimeKeys()
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
CoroutineScope(coroutineDispatchers.crypto).launch {
if (syncResponse.deviceLists != null) {
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
}
if (syncResponse.deviceOneTimeKeysCount != null) {
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
}
if (isStarted()) {
// Make sure we process to-device messages before generating new one-time-keys #2782
deviceListManager.refreshOutdatedDeviceLists()
oneTimeKeysUploader.maybeUploadOneTimeKeys()
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
}
}
}

@ -398,7 +379,7 @@ internal class CryptoManager(
/**
* 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
*/
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
// it means that the device is known
if (null != device && device.isUnknown) {
device.mVerified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED
device.verified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED
isUpdated = true
}
}
@ -459,7 +440,6 @@ internal class CryptoManager(

/**
* Configure a room to use encryption.
* This method must be called in getEncryptingThreadHandler
*
* @param roomId the room id to enable encryption in.
* @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
* @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
// (for now at least. Maybe we should alert the user somehow?)
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
@ -578,49 +558,47 @@ internal class CryptoManager(
// wait that the crypto is really started
if (!isStarted()) {
Timber.v("## encryptEventContent() : wait after e2e init")

start(false)
return
}

val userIds = getRoomUserIds(roomId)
var alg = synchronized(roomEncryptors) {
roomEncryptors[roomId]
}
if (alg == null) {
val algorithm = getEncryptionAlgorithm(roomId)
if (null != algorithm) {
if (setEncryptionInRoom(roomId, algorithm, false, userIds)) {
synchronized(roomEncryptors) {
alg = roomEncryptors[roomId]
CoroutineScope(coroutineDispatchers.crypto).launch {
val userIds = getRoomUserIds(roomId)
var alg = synchronized(roomEncryptors) {
roomEncryptors[roomId]
}
if (alg == null) {
val algorithm = getEncryptionAlgorithm(roomId)
if (algorithm != null) {
if (setEncryptionInRoom(roomId, algorithm, false, userIds)) {
synchronized(roomEncryptors) {
alg = roomEncryptors[roomId]
}
}
}
}
}
val safeAlgorithm = alg
if (safeAlgorithm != null) {
val t0 = System.currentTimeMillis()
Timber.v("## encryptEventContent() starts")
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds, object : MatrixCallback<Content> {
override fun onSuccess(data: Content) {
Timber.v("## encryptEventContent() : succeeds after " + (System.currentTimeMillis() - t0) + " ms")
callback.onSuccess(MXEncryptEventContentResult(data, EventType.ENCRYPTED))
}

if (alg != null) {
val t0 = System.currentTimeMillis()
Timber.v("## encryptEventContent() starts")
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
})
} else {
val algorithm = getEncryptionAlgorithm(roomId)
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
Timber.e("## encryptEventContent() : $reason")

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

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

override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
})
} else {
val algorithm = getEncryptionAlgorithm(roomId)
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
Timber.e("## encryptEventContent() : $reason")

callback.onFailure(Failure.CryptoError(MXCryptoError(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, reason)))
callback.onFailure(Failure.CryptoError(MXCryptoError(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, reason)))
}
}
}

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

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

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

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

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

if (null != result) {
results.add(result) // TODO simplify
return runBlocking {
withContext(coroutineDispatchers.crypto) {
alg.decryptEvent(event, timeline)
}
}
}

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
*/
fun onToDeviceEvent(event: Event) {
if (event.getClearType() == EventType.ROOM_KEY || event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
onRoomKeyEvent(event)
} else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) {
incomingRoomKeyRequestManager.onRoomKeyRequestEvent(event)
CoroutineScope(coroutineDispatchers.crypto).launch {
if (event.getClearType() == EventType.ROOM_KEY || event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
onRoomKeyEvent(event)
} else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) {
incomingRoomKeyRequestManager.onRoomKeyRequestEvent(event)
}
}
}

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

/**
* 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
// Sign it
val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())

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

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

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

if (unknownDevices.map.isEmpty()) {
callback.onSuccess(Unit)
} else {
// trigger an an unknown devices exception
callback.onFailure(
Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)))
}
}

override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
})
CoroutineScope(coroutineDispatchers.crypto).launch {
deviceListManager
.downloadKeys(userIds, true)
.fold(
{ callback.onFailure(it) },
{
val unknownDevices = getUnknownDevices(it)
if (unknownDevices.map.isEmpty()) {
callback.onSuccess(Unit)
} else {
// trigger an an unknown devices exception
callback.onFailure(
Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)))
}
}
)
}
}

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

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

View File

@ -19,7 +19,11 @@ package im.vector.matrix.android.internal.crypto
import android.content.Context
import im.vector.matrix.android.api.auth.data.Credentials
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.MXMegolmEncryptionFactory
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.keysbackup.KeysBackup
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.store.IMXCryptoStore
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.RealmCryptoStoreModule
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.session.DefaultSession
import io.realm.RealmConfiguration
@ -109,7 +155,7 @@ internal class CryptoModule {

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

// Actions
@ -164,7 +210,7 @@ internal class CryptoModule {

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

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

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

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

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

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.auth.data.Credentials
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.rest.KeysQueryResponse
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
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 java.util.*

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

// keys in progress
private val userKeyDownloadsInProgress = HashSet<String>()
private val downloadKeysForUsersTask: DownloadKeysForUsersTask) {

// HS not ready for retry
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 {
var isUpdated = false

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

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.
deviceTrackingStatuses.put(userId, TRACKING_STATUS_PENDING_DOWNLOAD)
isUpdated = true
}
}

if (isUpdated) {
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
}
@ -120,43 +79,6 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
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
*/
@ -180,7 +102,7 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
for (userId in userIds) {
if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[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
}
}
@ -202,24 +124,20 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()

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

for (userId in changed) {
if (deviceTrackingStatuses.containsKey(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
}
}
}

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

for (userId in left) {
if (deviceTrackingStatuses.containsKey(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
}
}
@ -243,21 +161,12 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
*
* @param userIds the user ids list
*/
private fun onKeysDownloadFailed(userIds: List<String>?) {
if (null != userIds) {
synchronized(userKeyDownloadsInProgress) {
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()

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

cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
}
private fun onKeysDownloadFailed(userIds: List<String>) {
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
for (userId in userIds) {
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
}

isDownloadingKeys = false
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
}

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

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

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

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

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

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>()

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

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

val callback = promise.callback

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

promisesToRemove.add(promise)
val usersDevicesInfoMap = MXUsersDevicesMap<MXDeviceInfo>()
for (userId in userIds) {
val devices = cryptoStore.getUserDevices(userId)
if (null == devices) {
if (canRetryKeysDownload(userId)) {
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
Timber.e("failed to retry the devices of $userId : retry later")
} else {
if (deviceTrackingStatuses.containsKey(userId) && TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == deviceTrackingStatuses[userId]) {
deviceTrackingStatuses[userId] = TRACKING_STATUS_UNREACHABLE_SERVER
Timber.e("failed to retry the devices of $userId : the HS is not available")
}
}
downloadKeysQueues.removeAll(promisesToRemove)
} else {
if (deviceTrackingStatuses.containsKey(userId) && TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == deviceTrackingStatuses[userId]) {
// we didn't get any new invalidations since this download started:
// this user's device list is now up to date.
deviceTrackingStatuses[userId] = TRACKING_STATUS_UP_TO_DATE
Timber.v("Device list for $userId now up to date")
}
// And the response result
usersDevicesInfoMap.setObjects(devices, userId)
}

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

cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
}

isDownloadingKeys = false
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
return usersDevicesInfoMap
}

/**
@ -361,31 +234,27 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
* @param forceDownload Always download the keys even if cached.
* @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")

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

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

if (null != userIds) {
if (forceDownload) {
downloadUsers.addAll(userIds)
} else {
for (userId in userIds) {
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
// 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)
} else {
val devices = cryptoStore.getUserDevices(userId)

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

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

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

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

data.addEntriesFromMap(stored)

callback?.onSuccess(data)
}

override fun onFailure(failure: Throwable) {
Timber.e(failure, "## downloadKeys() : doKeyDownloadForUsers onFailure")
callback?.onFailure(failure)
}
})
doKeyDownloadForUsers(downloadUsers)
.flatMap {
Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms")
it.addEntriesFromMap(stored)
Try.just(it)
}
}
}

@ -430,134 +286,61 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
* @param downloadUsers the user ids list
* @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")

// get the user ids which did not already trigger a keys download
val filteredUsers = addDownloadKeysPromise(downloadUsers, callback)

// if there is no new keys request
if (0 == filteredUsers!!.size) {
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
if (filteredUsers.isEmpty()) {
// trigger nothing
return
return Try.just(MXUsersDevicesMap())
}
val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken())
return downloadKeysForUsersTask.execute(params)
.map { response ->
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
for (userId in filteredUsers) {
val devices = response.deviceKeys?.get(userId)
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices")
if (devices != null) {
val mutableDevices = HashMap(devices)
val deviceIds = ArrayList(mutableDevices.keys)
for (deviceId in deviceIds) {
// Get the potential previously store device keys for this device
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId)
val deviceInfo = mutableDevices[deviceId]

// sanity check
//if (null == mxSession.dataHandler || null == mxSession.dataHandler.store) {
// 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")
val userIdsList = ArrayList(filteredUsers)

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")

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

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
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId)
val deviceInfo = mutableDevices[deviceId]

// in some race conditions (like unit tests)
// the self device must be seen as verified
if (TextUtils.equals(deviceInfo!!.deviceId, credentials.deviceId) && TextUtils.equals(userId, credentials.userId)) {
deviceInfo.mVerified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
}

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

if (null != previouslyStoredDeviceKeys) {
// But keep old validated ones if any
mutableDevices[deviceId] = previouslyStoredDeviceKeys
}
} else if (null != previouslyStoredDeviceKeys) {
// The verified status is not sync'ed with hs.
// This is a client side information, valid only for this client.
// So, transfer its previous value
mutableDevices[deviceId]!!.mVerified = previouslyStoredDeviceKeys.mVerified
}
}

// Update the store
// Note that devices which aren't in the response will be removed from the stores
cryptoStore.storeUserDevices(userId, mutableDevices)
// in some race conditions (like unit tests)
// the self device must be seen as verified
if (TextUtils.equals(deviceInfo!!.deviceId, credentials.deviceId) && TextUtils.equals(userId, credentials.userId)) {
deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
}
// Validate received keys
if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) {
// New device keys are not valid. Do not store them
mutableDevices.remove(deviceId)
if (null != previouslyStoredDeviceKeys) {
// But keep old validated ones if any
mutableDevices[deviceId] = previouslyStoredDeviceKeys
}

// the response is the latest request one
pendingDownloadKeysRequestToken.remove(userId)
} else if (null != previouslyStoredDeviceKeys) {
// The verified status is not sync'ed with hs.
// This is a client side information, valid only for this client.
// So, transfer its previous value
mutableDevices[deviceId]!!.verified = previouslyStoredDeviceKeys.verified
}
}

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

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)
}
}

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

onFailed()

callback?.onFailure(failure)
}
})
.executeBy(taskExecutor)
onKeysDownloadSucceed(filteredUsers, response.failures)
}
.handleError {
Timber.e(it, "##doKeyDownloadForUsers(): error")
onKeysDownloadFailed(filteredUsers)
throw it
}
}

/**
@ -631,7 +414,7 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,

if (!isVerified) {
Timber.e("## validateDeviceKeys() : Unable to verify signature on device " + userId + ":"
+ deviceKeys.deviceId + " with error " + errorMessage)
+ deviceKeys.deviceId + " with error " + errorMessage)
return false
}

@ -642,8 +425,8 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
//
// Should we warn the user about it somehow?
Timber.e("## validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":"
+ deviceKeys.deviceId + " has changed : "
+ previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey)
+ deviceKeys.deviceId + " has changed : "
+ previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey)

Timber.e("## validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys")
Timber.e("## validateDeviceKeys() : " + previouslyStoredDeviceKeys.keys + " -> " + deviceKeys.keys)
@ -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
* This method must be called on getEncryptingThreadHandler() thread.
*/
fun refreshOutdatedDeviceLists() {
suspend fun refreshOutdatedDeviceLists() {
val users = ArrayList<String>()

val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
@ -674,33 +457,24 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,
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
for (userId in users) {
val status = deviceTrackingStatuses[userId]

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

cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)

doKeyDownloadForUsers(users, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> {
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
CryptoAsyncHelper.getEncryptBackgroundHandler().post { Timber.v("## refreshOutdatedDeviceLists() : done") }
}

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

companion object {

View File

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


View File

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

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

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

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

// The outbound group session.
// 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.
// 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.
// 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 second level keys are strings of form "<senderKey>|<session_id>|<message_index>"
// Values are true.
private val mInboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap()
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap()

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

init {
// 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")
// Else, create it
try {
mOlmAccount = OlmAccount()
mStore.storeAccount(mOlmAccount!!)
olmAccount = OlmAccount()
store.storeAccount(olmAccount!!)
} catch (e: Exception) {
Timber.e(e, "MXOlmDevice : cannot initialize mOlmAccount")
Timber.e(e, "MXOlmDevice : cannot initialize olmAccount")
}

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

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

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

try {
deviceEd25519Key = mOlmAccount!!.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY]
deviceEd25519Key = olmAccount!!.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY]
} catch (e: Exception) {
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>>? {
try {
return mOlmAccount!!.oneTimeKeys()
return olmAccount!!.oneTimeKeys()
} catch (e: Exception) {
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.
*/
fun getMaxNumberOfOneTimeKeys(): Long {
return mOlmAccount?.maxOneTimeKeys() ?: -1
return olmAccount?.maxOneTimeKeys() ?: -1
}

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

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

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

val mxOlmSession = MXOlmSession(olmSession, 0)

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

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

val sessionIdentifier = olmSession.sessionIdentifier()

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

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

Timber.v("## createInboundSession() : ciphertext: $ciphertext")
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)
} catch (e: Exception) {
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
mxOlmSession.onMessageReceived()

mStore.storeSession(mxOlmSession, theirDeviceIdentityKey)
store.storeSession(mxOlmSession, theirDeviceIdentityKey)
} catch (e: Exception) {
Timber.e(e, "## createInboundSession() : decryptMessage failed")
}
@ -316,7 +316,7 @@ internal class MXOlmDevice(
* @return a list of known session ids for the device.
*/
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.
*/
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);

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

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

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

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

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

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

return sessions
}
@ -622,7 +622,7 @@ internal class MXOlmDevice(
*/
fun removeInboundGroupSession(sessionId: String?, sessionKey: String?) {
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 != timeline) {
if (!mInboundGroupSessionMessageIndexes.containsKey(timeline)) {
mInboundGroupSessionMessageIndexes[timeline] = HashMap()
if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
inboundGroupSessionMessageIndexes[timeline] = HashMap()
}

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)
Timber.e("## decryptGroupMessage() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.DUPLICATED_MESSAGE_INDEX_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, reason))
}

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

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

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

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

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

@ -737,7 +737,7 @@ internal class MXOlmDevice(
@Throws(Exception::class)
fun verifySignature(key: String, jsonDictionary: Map<String, Any>, signature: String) {
// 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.
*/
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? {
// sanity check
return if (!TextUtils.isEmpty(theirDeviceIdentityKey) && !TextUtils.isEmpty(sessionId)) {
mStore.getDeviceSession(sessionId, theirDeviceIdentityKey)
store.getDeviceSession(sessionId, theirDeviceIdentityKey)
} else null

}

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

View File

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

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

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

View File

@ -16,15 +16,13 @@

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.internal.crypto.model.MXKey
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.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 timber.log.Timber
import java.util.*
@ -33,15 +31,13 @@ internal class OneTimeKeysUploader(
private val mCredentials: Credentials,
private val mOlmDevice: MXOlmDevice,
private val mObjectSigner: ObjectSigner,
private val mUploadKeysTask: UploadKeysTask,
private val mTaskExecutor: TaskExecutor
private val mUploadKeysTask: UploadKeysTask
) {
// tell if there is a OTK check in progress
private var mOneTimeKeyCheckInProgress = false

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

private var mOneTimeKeyCount: Int? = null

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

/**
* 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) {
callback?.onSuccess(Unit)
return
return Try.just(Unit)
}

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

mLastOneTimeKeyCheck = System.currentTimeMillis()

mOneTimeKeyCheckInProgress = true

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

if (null != mOneTimeKeyCount) {
uploadOTK(mOneTimeKeyCount!!, keyLimit, callback)
if (mOneTimeKeyCount != null) {
return uploadOTK(mOneTimeKeyCount!!, keyLimit)
} else {
// ask the server how many keys we have
mUploadKeysTask
.configureWith(UploadKeysTask.Params(null, null, mCredentials.deviceId!!))
.executeOn(TaskThread.ENCRYPTION)
.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
// other devices can start conversations with us. But we can only store
// a finite number of private keys in the olm Account object.
// To complicate things further then can be a delay between a device
// claiming a public one time key from the server and it sending us a
// message. We need to keep the corresponding private key locally until
// we receive the message.
// But that message might never arrive leaving us stuck with duff
// private keys clogging up our local storage.
// So we need some kind of engineering compromise to balance all of
// these factors.
// 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)
val keyCount = data.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
uploadOTK(keyCount, keyLimit, callback)
}

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

mOneTimeKeyCount = null
mOneTimeKeyCheckInProgress = false

callback?.onFailure(failure)
}
})
.executeBy(mTaskExecutor)
val uploadKeysParams = UploadKeysTask.Params(null, null, mCredentials.deviceId!!)
return mUploadKeysTask.execute(uploadKeysParams)
.flatMap {
// 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
// a finite number of private keys in the olm Account object.
// To complicate things further then can be a delay between a device
// claiming a public one time key from the server and it sending us a
// message. We need to keep the corresponding private key locally until
// we receive the message.
// But that message might never arrive leaving us stuck with duff
// private keys clogging up our local storage.
// So we need some kind of engineering compromise to balance all of
// these factors.
// 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)
val keyCount = it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
uploadOTK(keyCount, keyLimit)
}
.handleError {
Timber.e(it, "## uploadKeys() : failed")
mOneTimeKeyCount = null
mOneTimeKeyCheckInProgress = false
}
}
}

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

/**
* OTK upload loop
*
* @param keyCount the number of key to generate
* @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)
}

val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
mOlmDevice.generateOneTimeKeys(keysThisLoop)
return uploadOneTimeKeys()
.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"))
}
}
mOneTimeKeyCount = null
mOneTimeKeyCheckInProgress = false
}

override fun onSuccess(data: Unit) {
Timber.v("## maybeUploadOneTimeKeys() : succeeded")
uploadKeysDone(null)

callback?.onSuccess(Unit)
}

override fun onFailure(failure: Throwable) {
uploadKeysDone(failure.message)

callback?.onFailure(failure)
}
})

}

/**
* 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 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
// same one as used in login.
mUploadKeysTask
.configureWith(UploadKeysTask.Params(null, oneTimeJson, mCredentials.deviceId!!))
.executeOn(TaskThread.ENCRYPTION)
.dispatchTo(object : MatrixCallback<KeysUploadResponse> {
override fun onSuccess(data: KeysUploadResponse) {
mLastPublishedOneTimeKeys = oneTimeKeys
mOlmDevice.markKeysAsPublished()

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"))
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, mCredentials.deviceId!!)
return mUploadKeysTask
.execute(uploadParams)
.map {
mLastPublishedOneTimeKeys = oneTimeKeys
mOlmDevice.markKeysAsPublished()
it
}
}

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

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.tasks.SendToDeviceTask
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 timber.log.Timber
import java.util.*
@ -87,7 +88,7 @@ internal class OutgoingRoomKeyRequestManager(
OutgoingRoomKeyRequest(requestBody, recipients, makeTxnId(), OutgoingRoomKeyRequest.RequestState.UNSENT))


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

View File

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

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

// 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.
@ -43,44 +43,36 @@ internal class RoomDecryptorProvider(
*/
fun getOrCreateRoomDecryptor(roomId: String?, algorithm: String?): IMXDecrypting? {
// sanity check
if (TextUtils.isEmpty(algorithm)) {
if (algorithm.isNullOrEmpty() || roomId.isNullOrEmpty()) {
Timber.e("## getRoomDecryptor() : null algorithm")
return null
}

var alg: IMXDecrypting? = null

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

alg = mRoomDecryptors[roomId]!![algorithm]
var alg: IMXDecrypting?
synchronized(roomDecryptors) {
if (!roomDecryptors.containsKey(roomId)) {
roomDecryptors[roomId!!] = HashMap()
}

if (null != alg) {
return alg
}
alg = roomDecryptors[roomId]!![algorithm]
}
if (alg != null) {
return alg
}

val decryptingClass = MXCryptoAlgorithms.hasDecryptorClassForAlgorithm(algorithm)

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

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

return alg
}

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

return mRoomDecryptors[roomId]?.get(algorithm)
return roomDecryptors[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.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.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@ -53,7 +57,6 @@ internal class MegolmSessionDataImporter(private val mOlmDevice: MXOlmDevice,
progressListener.onProgress(0, 100)
}
}

val sessions = mOlmDevice.importInboundGroupSessions(megolmSessionsData)

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

totalNumbersOfImportedKeys++


View File

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

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

if (userId == mCredentials.userId) {

View File

@ -25,21 +25,21 @@ data class MXDecryptionResult(
/**
* 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:
* 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.
*/
var mSenderKey: String? = null,
var senderKey: String? = null,

/**
* 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)

private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult? {
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
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
if (decryptGroupMessageResult?.mPayload != null && cryptoError == null) {
if (decryptGroupMessageResult?.payload != null && cryptoError == null) {
eventDecryptionResult = MXEventDecryptionResult()

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

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

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

View File

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

View File

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


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

View File

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

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

View File

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

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

View File

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

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

View File

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

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

View File

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


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

View File

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


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

View File

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

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

View File

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

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

View File

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

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

View File

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

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

View File

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

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

View File

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

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

View File

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


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

View File

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

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

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

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

/**
* @return the fingerprint

View File

@ -37,7 +37,7 @@ internal interface ClaimOneTimeKeysForUsersDeviceTask : Task<ClaimOneTimeKeysFor
internal class DefaultClaimOneTimeKeysForUsersDevice(private val cryptoApi: CryptoApi)
: 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)

return executeRequest<KeysClaimResponse> {

View File

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

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

View File

@ -36,7 +36,7 @@ internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Para
internal class DefaultDownloadKeysForUsers(private val cryptoApi: CryptoApi)
: 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>>()

if (null != params.userIds) {

View File

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

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

View File

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

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

View File

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

override fun execute(params: SendToDeviceTask.Params): Try<Unit> {
override suspend fun execute(params: SendToDeviceTask.Params): Try<Unit> {
val sendToDeviceBody = SendToDeviceBody()
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)
: SetDeviceNameTask {

override fun execute(params: SetDeviceNameTask.Params): Try<Unit> {
override suspend fun execute(params: SetDeviceNameTask.Params): Try<Unit> {
val body = UpdateDeviceInfoBody(
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)
: UploadKeysTask {

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

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.model.MXDeviceInfo
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.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.task.TaskExecutor
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 java.util.*
import kotlin.collections.HashMap
@ -53,6 +60,7 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
private val deviceListManager: DeviceListManager,
private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val mSendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val mTaskExecutor: TaskExecutor)
: VerificationTransaction.Listener, SasVerificationService {

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

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

override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
setDeviceVerificationAction.handle(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED,
deviceID,
userId)
deviceID,
userId)

listeners.forEach {
try {
@ -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 otherUserId = event.sender
@ -199,7 +206,7 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
} else {
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
cancelTransaction(tid, otherUserId, startReq.fromDevice
?: event.getSenderKey()!!, CancelCode.UnknownMethod)
?: event.getSenderKey()!!, CancelCode.UnknownMethod)
}
}
},
@ -208,30 +215,24 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
})
}

private fun checkKeysAreDownloaded(otherUserId: String,
startReq: KeyVerificationStart,
success: (MXUsersDevicesMap<MXDeviceInfo>) -> Unit,
error: () -> Unit) {
deviceListManager.downloadKeys(listOf(otherUserId), true, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> {
override fun onFailure(failure: Throwable) {
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
error()
}
}

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

private fun onCancelReceived(event: Event) {
private suspend fun onCancelReceived(event: Event) {
Timber.v("## SAS onCancelReceived")
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>()!!

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>()!!

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>()!!

if (!macReq.isValid()) {
@ -398,9 +399,9 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
override fun transactionUpdated(tx: VerificationTransaction) {
dispatchTxUpdated(tx)
if (tx is SASVerificationTransaction
&& (tx.state == SasVerificationTxState.Cancelled
|| tx.state == SasVerificationTxState.OnCancelled
|| tx.state == SasVerificationTxState.Verified)
&& (tx.state == SasVerificationTxState.Cancelled
|| tx.state == SasVerificationTxState.OnCancelled
|| tx.state == SasVerificationTxState.Verified)
) {
//remove
this.removeTransaction(tx.otherUserId, tx.transactionId)

View File

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

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

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


View File

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

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

View File

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

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


View File

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

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

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

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

private val getGroupDataTask by inject<GetGroupDataTask>()

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

@ -47,7 +48,7 @@ internal class GetGroupDataWorker(context: Context,
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))
}


View File

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


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

View File

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

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 {
val body = InviteBody(params.userId)
apiCall = roomAPI.invite(params.roomId, body)

View File

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

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

View File

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

override fun execute(params: SetReadMarkersTask.Params): Try<Unit> {
override suspend fun execute(params: SetReadMarkersTask.Params): Try<Unit> {
val markers = HashMap<String, String>()
if (params.fullyReadEventId != null && MatrixPatterns.isEventId(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 {
override fun execute(params: SendStateTask.Params): Try<Unit> {
override suspend fun execute(params: SendStateTask.Params): Try<Unit> {
return executeRequest {
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
) : GetContextOfEventTask {

override fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> {
override suspend fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> {
val filter = filterRepository.getRoomFilter()
return executeRequest<EventContextResponse> {
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
) : PaginationTask {

override fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> {
override suspend fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> {
val filter = filterRepository.getRoomFilter()
return executeRequest<PaginationResponse> {
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
)

override fun execute(params: Params): Try<Event> {
override suspend fun execute(params: Params): Try<Event> {
return executeRequest {
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,
private val sessionParamsStore: SessionParamsStore) : SignOutTask {

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

View File

@ -37,7 +37,6 @@ internal class CryptoSyncHandler(private val cryptoManager: CryptoManager,
toDevice.events?.forEach { event ->
// Decrypt event if necessary
decryptEvent(event, null)

if (TextUtils.equals(event.getClearType(), EventType.MESSAGE)
&& event.mClearEvent?.content?.toModel<MessageContent>()?.type == "m.bad.encrypted") {
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 {


override fun execute(params: SyncTask.Params): Try<SyncResponse> {
override suspend fun execute(params: SyncTask.Params): Try<SyncResponse> {
val requestParams = HashMap<String, String>()
var timeout = 0
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 {

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

View File

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


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


View File

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

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.IO -> coroutineDispatchers.io
TaskThread.CALLER -> EmptyCoroutineContext
TaskThread.ENCRYPTION -> coroutineDispatchers.crypto
TaskThread.DECRYPTION -> coroutineDispatchers.crypto
TaskThread.CRYPTO -> coroutineDispatchers.crypto
}



View File

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