Merge branch 'develop' into kt-remove_java_util

This commit is contained in:
Dominic Fischer 2019-08-06 18:27:39 +01:00 committed by GitHub
commit 456908c851
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
313 changed files with 6559 additions and 2586 deletions

View File

@ -2,10 +2,14 @@ Changes in RiotX 0.3.0 (2019-XX-XX)
=================================================== ===================================================


Features: Features:
- - Create Direct Room flow


Improvements: Improvements:
- - UI for pending edits (#193)
- UX image preview screen transition (#393)
- Basic support for resending failed messages (retry/remove)
- Enable proper cancellation of suspending functions (including db transaction)
- Enhances network connectivity checks in SDK


Other changes: Other changes:
- -
@ -14,6 +18,8 @@ Bugfix:
- Edited message: link confusion when (edited) appears in body (#398) - Edited message: link confusion when (edited) appears in body (#398)
- Close detail room screen when the room is left with another client (#256) - Close detail room screen when the room is left with another client (#256)
- Clear notification for a room left on another client - Clear notification for a room left on another client
- Fix messages with empty `in_reply_to` not rendering (#447)
- Fix clear cache (#408) and Logout (#205 )


Translations: Translations:
- -

View File

@ -38,6 +38,8 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0-beta01' implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
// Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.0"


testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.2.0'

View File

@ -20,6 +20,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.MainThreadDisposable import io.reactivex.android.MainThreadDisposable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers


private class LiveDataObservable<T>( private class LiveDataObservable<T>(
private val liveData: LiveData<T>, private val liveData: LiveData<T>,
@ -57,5 +59,5 @@ private class LiveDataObservable<T>(
} }


fun <T> LiveData<T>.asObservable(): Observable<T> { fun <T> LiveData<T>.asObservable(): Observable<T> {
return LiveDataObservable(this) return LiveDataObservable(this).observeOn(Schedulers.computation())
} }

View File

@ -0,0 +1,39 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.matrix.rx

import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import io.reactivex.CompletableEmitter
import io.reactivex.SingleEmitter

internal class MatrixCallbackCompletable<T>(private val completableEmitter: CompletableEmitter) : MatrixCallback<T> {

override fun onSuccess(data: T) {
completableEmitter.onComplete()
}

override fun onFailure(failure: Throwable) {
completableEmitter.tryOnError(failure)
}
}

fun Cancelable.toCompletable(completableEmitter: CompletableEmitter) {
completableEmitter.setCancellable {
this.cancel()
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.matrix.rx

import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import io.reactivex.SingleEmitter

internal class MatrixCallbackSingle<T>(private val singleEmitter: SingleEmitter<T>) : MatrixCallback<T> {

override fun onSuccess(data: T) {
singleEmitter.onSuccess(data)
}

override fun onFailure(failure: Throwable) {
singleEmitter.tryOnError(failure)
}
}

fun <T> Cancelable.toSingle(singleEmitter: SingleEmitter<T>) {
singleEmitter.setCancellable {
this.cancel()
}
}

View File

@ -21,24 +21,32 @@ import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers import io.reactivex.Single


class RxRoom(private val room: Room) { class RxRoom(private val room: Room) {


fun liveRoomSummary(): Observable<RoomSummary> { fun liveRoomSummary(): Observable<RoomSummary> {
return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation()) return room.liveRoomSummary().asObservable()
} }


fun liveRoomMemberIds(): Observable<List<String>> { fun liveRoomMemberIds(): Observable<List<String>> {
return room.getRoomMemberIdsLive().asObservable().observeOn(Schedulers.computation()) return room.getRoomMemberIdsLive().asObservable()
} }


fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> { fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
return room.getEventSummaryLive(eventId).asObservable().observeOn(Schedulers.computation()) return room.getEventSummaryLive(eventId).asObservable()
} }


fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> { fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
return room.liveTimeLineEvent(eventId).asObservable().observeOn(Schedulers.computation()) return room.liveTimeLineEvent(eventId).asObservable()
}

fun loadRoomMembersIfNeeded(): Single<Unit> = Single.create {
room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it)
}

fun joinRoom(viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
} }


} }

View File

@ -16,30 +16,55 @@


package im.vector.matrix.rx package im.vector.matrix.rx


import androidx.paging.PagedList
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.user.model.User
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers import io.reactivex.Single


class RxSession(private val session: Session) { class RxSession(private val session: Session) {


fun liveRoomSummaries(): Observable<List<RoomSummary>> { fun liveRoomSummaries(): Observable<List<RoomSummary>> {
return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation()) return session.liveRoomSummaries().asObservable()
} }


fun liveGroupSummaries(): Observable<List<GroupSummary>> { fun liveGroupSummaries(): Observable<List<GroupSummary>> {
return session.liveGroupSummaries().asObservable().observeOn(Schedulers.computation()) return session.liveGroupSummaries().asObservable()
} }


fun liveSyncState(): Observable<SyncState> { fun liveSyncState(): Observable<SyncState> {
return session.syncState().asObservable().observeOn(Schedulers.computation()) return session.syncState().asObservable()
} }


fun livePushers(): Observable<List<Pusher>> { fun livePushers(): Observable<List<Pusher>> {
return session.livePushers().asObservable().observeOn(Schedulers.computation()) return session.livePushers().asObservable()
}

fun liveUsers(): Observable<List<User>> {
return session.liveUsers().asObservable()
}

fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
return session.livePagedUsers(filter).asObservable()
}

fun createRoom(roomParams: CreateRoomParams): Single<String> = Single.create {
session.createRoom(roomParams, MatrixCallbackSingle(it)).toSingle(it)
}

fun searchUsersDirectory(search: String,
limit: Int,
excludedUserIds: Set<String>): Single<List<User>> = Single.create {
session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it)
}

fun joinRoom(roomId: String, viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
session.joinRoom(roomId, viaServers, MatrixCallbackSingle(it)).toSingle(it)
} }


} }

View File

@ -110,7 +110,7 @@ dependencies {
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0' implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
implementation 'com.squareup.okhttp3:okhttp:3.14.1' implementation 'com.squareup.okhttp3:okhttp:3.14.1'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
implementation 'com.novoda:merlin:1.1.6' implementation 'com.novoda:merlin:1.2.0'
implementation "com.squareup.moshi:moshi-adapters:$moshi_version" implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"


@ -126,9 +126,6 @@ dependencies {
// FP // FP
implementation "io.arrow-kt:arrow-core:$arrow_version" implementation "io.arrow-kt:arrow-core:$arrow_version"
implementation "io.arrow-kt:arrow-instances-core:$arrow_version" implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
implementation "io.arrow-kt:arrow-effects:$arrow_version"
implementation "io.arrow-kt:arrow-effects-instances:$arrow_version"
implementation "io.arrow-kt:arrow-integration-retrofit-adapter:$arrow_version"


// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm // olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
implementation 'org.matrix.gitlab.matrix-org:olm:3.1.2' implementation 'org.matrix.gitlab.matrix-org:olm:3.1.2'

View File

@ -16,10 +16,10 @@


package im.vector.matrix.android; package im.vector.matrix.android;


import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.annotation.Nullable;


import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;

View File

@ -19,11 +19,7 @@ package im.vector.matrix.android.session.room.timeline
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.internal.database.helper.add import im.vector.matrix.android.internal.database.helper.*
import im.vector.matrix.android.internal.database.helper.addAll
import im.vector.matrix.android.internal.database.helper.isUnlinked
import im.vector.matrix.android.internal.database.helper.lastStateIndex
import im.vector.matrix.android.internal.database.helper.merge
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents

View File

@ -16,7 +16,6 @@


package im.vector.matrix.android.session.room.timeline package im.vector.matrix.android.session.room.timeline


import arrow.core.Try
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
@ -24,7 +23,7 @@ import kotlin.random.Random


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


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

View File

@ -16,7 +16,6 @@


package im.vector.matrix.android.session.room.timeline package im.vector.matrix.android.session.room.timeline


import arrow.core.Try
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
import javax.inject.Inject import javax.inject.Inject
@ -24,7 +23,7 @@ import kotlin.random.Random


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


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

View File

@ -28,7 +28,7 @@ object MatrixPatterns {
// regex pattern to find matrix user ids in a string. // regex pattern to find matrix user ids in a string.
// See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids // See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids
private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX" private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)


// regex pattern to find room ids in a string. // regex pattern to find room ids in a string.
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX" private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"
@ -123,9 +123,9 @@ object MatrixPatterns {
*/ */
fun isEventId(str: String?): Boolean { fun isEventId(str: String?): Boolean {
return str != null return str != null
&& (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER && (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 || str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4) || str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4)
} }


/** /**
@ -137,4 +137,23 @@ object MatrixPatterns {
fun isGroupId(str: String?): Boolean { fun isGroupId(str: String?): Boolean {
return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER
} }

/**
* Extract server name from a matrix id
*
* @param matrixId
* @return null if not found or if matrixId is null
*/
fun extractServerNameFromId(matrixId: String?): String? {
if (matrixId == null) {
return null
}

val index = matrixId.indexOf(":")

return if (index == -1) {
null
} else matrixId.substring(index + 1)

}
} }

View File

@ -31,7 +31,7 @@ import okhttp3.TlsVersion
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class HomeServerConnectionConfig( data class HomeServerConnectionConfig(
val homeServerUri: Uri, val homeServerUri: Uri,
val identityServerUri: Uri, val identityServerUri: Uri? = null,
val antiVirusServerUri: Uri? = null, val antiVirusServerUri: Uri? = null,
val allowedFingerprints: MutableList<Fingerprint> = ArrayList(), val allowedFingerprints: MutableList<Fingerprint> = ArrayList(),
val shouldPin: Boolean = false, val shouldPin: Boolean = false,
@ -48,7 +48,7 @@ data class HomeServerConnectionConfig(
class Builder { class Builder {


private lateinit var homeServerUri: Uri private lateinit var homeServerUri: Uri
private lateinit var identityServerUri: Uri private var identityServerUri: Uri? = null
private var antiVirusServerUri: Uri? = null private var antiVirusServerUri: Uri? = null
private val allowedFingerprints: MutableList<Fingerprint> = ArrayList() private val allowedFingerprints: MutableList<Fingerprint> = ArrayList()
private var shouldPin: Boolean = false private var shouldPin: Boolean = false

View File

@ -26,8 +26,13 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MatrixError( data class MatrixError(
@Json(name = "errcode") val code: String, @Json(name = "errcode") val code: String,
@Json(name = "error") val message: String @Json(name = "error") val message: String,
) {
@Json(name = "consent_uri") val consentUri: String? = null,
// RESOURCE_LIMIT_EXCEEDED data
@Json(name = "limit_type") val limitType: String? = null,
@Json(name = "admin_contact") val adminUri: String? = null) {



companion object { companion object {
const val FORBIDDEN = "M_FORBIDDEN" const val FORBIDDEN = "M_FORBIDDEN"
@ -55,5 +60,8 @@ data class MatrixError(
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN" const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
const val RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED" const val RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
const val WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION" const val WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"

// Possible value for "limit_type"
const val LIMIT_TYPE_MAU = "monthly_active_user"
} }
} }

View File

@ -18,6 +18,7 @@ package im.vector.matrix.android.api.permalinks


import android.text.style.ClickableSpan import android.text.style.ClickableSpan
import android.view.View import android.view.View
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan.Callback


/** /**
* This MatrixPermalinkSpan is a clickable span which use a [Callback] to communicate back. * This MatrixPermalinkSpan is a clickable span which use a [Callback] to communicate back.

View File

@ -18,6 +18,7 @@ package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.util.Cancelable


interface PushRuleService { interface PushRuleService {


@ -31,7 +32,7 @@ interface PushRuleService {


//TODO update rule //TODO update rule


fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>) fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable


fun addPushRuleListener(listener: PushRuleListener) fun addPushRuleListener(listener: PushRuleListener)



View File

@ -26,14 +26,12 @@ import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import java.io.File


interface CryptoService { interface CryptoService {



View File

@ -20,6 +20,9 @@ import android.text.TextUtils
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
@ -79,9 +82,15 @@ data class Event(
) { ) {




@Transient
var mxDecryptionResult: OlmDecryptionResult? = null var mxDecryptionResult: OlmDecryptionResult? = null

@Transient
var mCryptoError: MXCryptoError.ErrorType? = null var mCryptoError: MXCryptoError.ErrorType? = null


@Transient
var sendState: SendState = SendState.UNKNOWN



/** /**
* Check if event is a state event. * Check if event is a state event.
@ -95,42 +104,6 @@ data class Event(
// Crypto // Crypto
//============================================================================================================== //==============================================================================================================


// /**
// * For encrypted events, the plaintext payload for the event.
// * This is a small MXEvent instance with typically value for `type` and 'content' fields.
// */
// @Transient
// var mClearEvent: Event? = null
// private set
//
// /**
// * Curve25519 key which we believe belongs to the sender of the event.
// * See `senderKey` property.
// */
// @Transient
// private var mSenderCurve25519Key: String? = null
//
// /**
// * Ed25519 key which the sender of this event (for olm) or the creator of the megolm session (for megolm) claims to own.
// * See `claimedEd25519Key` property.
// */
// @Transient
// private var mClaimedEd25519Key: String? = null
//
// /**
// * Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key.
// * See `forwardingCurve25519KeyChain` property.
// */
// @Transient
// private var mForwardingCurve25519KeyChain: List<String> = ArrayList()
//
// /**
// * Decryption error
// */
// @Transient
// var mCryptoError: MXCryptoError? = null
// private set

/** /**
* @return true if this event is encrypted. * @return true if this event is encrypted.
*/ */
@ -138,51 +111,11 @@ data class Event(
return TextUtils.equals(type, EventType.ENCRYPTED) return TextUtils.equals(type, EventType.ENCRYPTED)
} }


/**
* Update the clear data on this event.
* This is used after decrypting an event; it should not be used by applications.
*
* @param decryptionResult the decryption result, including the plaintext and some key info.
*/
// internal fun setClearData(decryptionResult: MXEventDecryptionResult?) {
// mClearEvent = null
// if (decryptionResult != null) {
// if (decryptionResult.clearEvent != null) {
// val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
// mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent)
//
// if (mClearEvent != null) {
// mSenderCurve25519Key = decryptionResult.senderCurve25519Key
// mClaimedEd25519Key = decryptionResult.claimedEd25519Key
// mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain
//
// // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back
// // in the clear event
// try {
// content?.get("m.relates_to")?.let { clearRelates ->
// mClearEvent = mClearEvent?.copy(
// content = HashMap(mClearEvent!!.content).apply {
// this["m.relates_to"] = clearRelates
// }
// )
// }
// } catch (e: Exception) {
// Timber.e(e, "Unable to restore 'm.relates_to' the clear event")
// }
// }
//
//
// }
// }
// mCryptoError = null
// }

/** /**
* @return The curve25519 key that sent this event. * @return The curve25519 key that sent this event.
*/ */
fun getSenderKey(): String? { fun getSenderKey(): String? {
return mxDecryptionResult?.senderKey return mxDecryptionResult?.senderKey
// return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key
} }


/** /**
@ -190,23 +123,13 @@ data class Event(
*/ */
fun getKeysClaimed(): Map<String, String> { fun getKeysClaimed(): Map<String, String> {
return mxDecryptionResult?.keysClaimed ?: HashMap() return mxDecryptionResult?.keysClaimed ?: HashMap()
// val res = HashMap<String, String>()
//
// val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key
//
// if (null != claimedEd25519Key) {
// res["ed25519"] = claimedEd25519Key
// }
//
// return res
} }
//
/** /**
* @return the event type * @return the event type
*/ */
fun getClearType(): String { fun getClearType(): String {
return mxDecryptionResult?.payload?.get("type")?.toString() return mxDecryptionResult?.payload?.get("type")?.toString() ?: type
?: type//get("type")?.toString() ?: type
} }


/** /**
@ -216,30 +139,8 @@ data class Event(
return mxDecryptionResult?.payload?.get("content") as? Content ?: content return mxDecryptionResult?.payload?.get("content") as? Content ?: content
} }


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


fun toContentStringWithIndent(): String { fun toContentStringWithIndent(): String {
val contentMap = this.toContent()?.toMutableMap() ?: HashMap() val contentMap = toContent()?.toMutableMap() ?: HashMap()
contentMap.remove("mxDecryptionResult")
contentMap.remove("mCryptoError")
return JSONObject(contentMap).toString(4) return JSONObject(contentMap).toString(4)
} }


@ -272,6 +173,7 @@ data class Event(
if (redacts != other.redacts) return false if (redacts != other.redacts) return false
if (mxDecryptionResult != other.mxDecryptionResult) return false if (mxDecryptionResult != other.mxDecryptionResult) return false
if (mCryptoError != other.mCryptoError) return false if (mCryptoError != other.mCryptoError) return false
if (sendState != other.sendState) return false


return true return true
} }
@ -289,6 +191,27 @@ data class Event(
result = 31 * result + (redacts?.hashCode() ?: 0) result = 31 * result + (redacts?.hashCode() ?: 0)
result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0) result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0)
result = 31 * result + (mCryptoError?.hashCode() ?: 0) result = 31 * result + (mCryptoError?.hashCode() ?: 0)
result = 31 * result + sendState.hashCode()
return result return result
} }

}


fun Event.isTextMessage(): Boolean {
return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
MessageType.MSGTYPE_TEXT,
MessageType.MSGTYPE_EMOTE,
MessageType.MSGTYPE_NOTICE -> true
else -> false
}
}

fun Event.isImageMessage(): Boolean {
return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
MessageType.MSGTYPE_IMAGE -> true
else -> false
}
} }

View File

@ -30,20 +30,17 @@ interface RoomDirectoryService {
/** /**
* Get rooms from directory * Get rooms from directory
*/ */
fun getPublicRooms(server: String?, fun getPublicRooms(server: String?, publicRoomsParams: PublicRoomsParams, callback: MatrixCallback<PublicRoomsResponse>): Cancelable
publicRoomsParams: PublicRoomsParams,
callback: MatrixCallback<PublicRoomsResponse>): Cancelable


/** /**
* Join a room by id * Join a room by id
*/ */
fun joinRoom(roomId: String, fun joinRoom(roomId: String, callback: MatrixCallback<Unit>): Cancelable
callback: MatrixCallback<Unit>)


/** /**
* Fetches the overall metadata about protocols supported by the homeserver. * Fetches the overall metadata about protocols supported by the homeserver.
* Includes both the available protocols and all fields required for queries against each protocol. * Includes both the available protocols and all fields required for queries against each protocol.
*/ */
fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>) fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>): Cancelable


} }

View File

@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.util.Cancelable


/** /**
* This interface defines methods to get rooms. It's implemented at the session level. * This interface defines methods to get rooms. It's implemented at the session level.
@ -27,10 +28,18 @@ import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
interface RoomService { interface RoomService {


/** /**
* Create a room * Create a room asynchronously
*/ */
fun createRoom(createRoomParams: CreateRoomParams, fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable
callback: MatrixCallback<String>)
/**
* Join a room by id
* @param roomId the roomId of the room to join
* @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room.
*/
fun joinRoom(roomId: String,
viaServers: List<String> = emptyList(),
callback: MatrixCallback<Unit>): Cancelable


/** /**
* Get a room from a roomId * Get a room from a roomId

View File

@ -0,0 +1,25 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.matrix.android.api.session.room.failure

import im.vector.matrix.android.api.failure.Failure

sealed class CreateRoomFailure : Failure.FeatureFailure() {

object CreatedWithTimeout: CreateRoomFailure()

}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.matrix.android.api.session.room.failure

import im.vector.matrix.android.api.failure.Failure

sealed class JoinRoomFailure : Failure.FeatureFailure() {

object JoinedWithTimeout : JoinRoomFailure()

}

View File

@ -30,7 +30,7 @@ interface MembershipService {
* This methods load all room members if it was done yet. * This methods load all room members if it was done yet.
* @return a [Cancelable] * @return a [Cancelable]
*/ */
fun loadRoomMembersIfNeeded(): Cancelable fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable


/** /**
* Return the roomMember with userId or null. * Return the roomMember with userId or null.
@ -52,16 +52,17 @@ interface MembershipService {
/** /**
* Invite a user in the room * Invite a user in the room
*/ */
fun invite(userId: String, callback: MatrixCallback<Unit>) fun invite(userId: String, callback: MatrixCallback<Unit>): Cancelable


/** /**
* Join the room, or accept an invitation. * Join the room, or accept an invitation.
*/ */
fun join(callback: MatrixCallback<Unit>)
fun join(viaServers: List<String> = emptyList(), callback: MatrixCallback<Unit>): Cancelable


/** /**
* Leave the room, or reject an invitation. * Leave the room, or reject an invitation.
*/ */
fun leave(callback: MatrixCallback<Unit>) fun leave(callback: MatrixCallback<Unit>): Cancelable


} }

View File

@ -34,5 +34,10 @@ data class RoomSummary(
val notificationCount: Int = 0, val notificationCount: Int = 0,
val highlightCount: Int = 0, val highlightCount: Int = 0,
val tags: List<RoomTag> = emptyList(), val tags: List<RoomTag> = emptyList(),
val membership: Membership = Membership.NONE val membership: Membership = Membership.NONE,
) val versioningState: VersioningState = VersioningState.NONE
) {

val isVersioned: Boolean
get() = versioningState != VersioningState.NONE
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.matrix.android.api.session.room.model

enum class VersioningState {
NONE,
UPGRADED_ROOM_NOT_JOINED,
UPGRADED_ROOM_JOINED
}

View File

@ -127,8 +127,8 @@ class CreateRoomParams {
contentMap["algorithm"] = algorithm contentMap["algorithm"] = algorithm


val algoEvent = Event(type = EventType.ENCRYPTION, val algoEvent = Event(type = EventType.ENCRYPTION,
stateKey = "", stateKey = "",
content = contentMap.toContent() content = contentMap.toContent()
) )


if (null == initialStates) { if (null == initialStates) {
@ -161,8 +161,8 @@ class CreateRoomParams {
contentMap["history_visibility"] = historyVisibility contentMap["history_visibility"] = historyVisibility


val historyVisibilityEvent = Event(type = EventType.STATE_HISTORY_VISIBILITY, val historyVisibilityEvent = Event(type = EventType.STATE_HISTORY_VISIBILITY,
stateKey = "", stateKey = "",
content = contentMap.toContent()) content = contentMap.toContent())


if (null == initialStates) { if (null == initialStates) {
initialStates = mutableListOf(historyVisibilityEvent) initialStates = mutableListOf(historyVisibilityEvent)
@ -201,8 +201,8 @@ class CreateRoomParams {
*/ */
fun isDirect(): Boolean { fun isDirect(): Boolean {
return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
&& isDirect == true && isDirect == true
&& (1 == getInviteCount() || 1 == getInvite3PidCount()) && (1 == getInviteCount() || 1 == getInvite3PidCount())
} }


/** /**
@ -222,14 +222,13 @@ class CreateRoomParams {
credentials: Credentials, credentials: Credentials,
ids: List<String>) { ids: List<String>) {
for (id in ids) { for (id in ids) {
if (Patterns.EMAIL_ADDRESS.matcher(id).matches()) { if (Patterns.EMAIL_ADDRESS.matcher(id).matches() && hsConfig.identityServerUri != null) {
if (null == invite3pids) { if (null == invite3pids) {
invite3pids = ArrayList() invite3pids = ArrayList()
} }

val pid = Invite3Pid(idServer = hsConfig.identityServerUri.host!!, val pid = Invite3Pid(idServer = hsConfig.identityServerUri.host!!,
medium = ThreePidMedium.EMAIL, medium = ThreePidMedium.EMAIL,
address = id) address = id)


invite3pids!!.add(pid) invite3pids!!.add(pid)
} else if (isUserId(id)) { } else if (isUserId(id)) {

View File

@ -0,0 +1,28 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.create

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

/**
* A link to an old room in case of room versioning
*/
@JsonClass(generateAdapter = true)
data class Predecessor(
@Json(name = "room_id") val roomId: String? = null,
@Json(name = "event_id") val eventId: String? = null
)

View File

@ -0,0 +1,32 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.matrix.android.api.session.room.model.create

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

/**
* Content of a m.room.create type event
*/
@JsonClass(generateAdapter = true)
data class RoomCreateContent(
@Json(name = "creator") val creator: String? = null,
@Json(name = "room_version") val roomVersion: String? = null,
@Json(name = "predecessor") val predecessor: Predecessor? = null
)


View File

@ -29,5 +29,5 @@ interface MessageContent {




fun MessageContent?.isReply(): Boolean { fun MessageContent?.isReply(): Boolean {
return this?.relatesTo?.inReplyTo != null return this?.relatesTo?.inReplyTo?.eventId != null
} }

View File

@ -21,5 +21,5 @@ import com.squareup.moshi.JsonClass


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class ReplyToContent( data class ReplyToContent(
@Json(name = "event_id") val eventId: String @Json(name = "event_id") val eventId: String? = null
) )

View File

@ -0,0 +1,28 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.tombstone

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

/**
* Class to contains Tombstone information
*/
@JsonClass(generateAdapter = true)
data class RoomTombstoneContent(
@Json(name = "body") val body: String? = null,
@Json(name = "replacement_room") val replacementRoom: String?
)

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.api.session.room.send
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable




@ -65,4 +66,31 @@ interface SendService {
*/ */
fun redactEvent(event: Event, reason: String?): Cancelable fun redactEvent(event: Event, reason: String?): Cancelable



/**
* Schedule this message to be resent
* @param localEcho the unsent local echo
*/
fun resendTextMessage(localEcho: TimelineEvent): Cancelable?

/**
* Schedule this message to be resent
* @param localEcho the unsent local echo
*/
fun resendMediaMessage(localEcho: TimelineEvent): Cancelable?


/**
* Remove this failed message from the timeline
* @param localEcho the unsent local echo
*/
fun deleteFailedEcho(localEcho: TimelineEvent)

fun clearSendingQueue()

/**
* Resend all failed messages one by one (and keep order)
*/
fun resendAllFailedMessages()

} }

View File

@ -41,4 +41,8 @@ enum class SendState {
return this == UNDELIVERED || this == FAILED_UNKNOWN_DEVICES return this == UNDELIVERED || this == FAILED_UNKNOWN_DEVICES
} }


fun isSending(): Boolean {
return this == UNSENT || this == ENCRYPTING || this == SENDING
}

} }

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.room.state package im.vector.matrix.android.api.session.room.state


import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Event


interface StateService { interface StateService {


@ -25,4 +26,6 @@ interface StateService {
*/ */
fun updateTopic(topic: String, callback: MatrixCallback<Unit>) fun updateTopic(topic: String, callback: MatrixCallback<Unit>)


fun getStateEvent(eventType: String): Event?

} }

View File

@ -56,6 +56,9 @@ interface Timeline {
*/ */
fun paginate(direction: Direction, count: Int) fun paginate(direction: Direction, count: Int)


fun pendingEventCount() : Int

fun failedToDeliverEventCount() : Int


interface Listener { interface Listener {
/** /**

View File

@ -22,7 +22,6 @@ import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.isReply import im.vector.matrix.android.api.session.room.model.message.isReply
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent


@ -38,7 +37,6 @@ data class TimelineEvent(
val senderName: String?, val senderName: String?,
val isUniqueDisplayName: Boolean, val isUniqueDisplayName: Boolean,
val senderAvatar: String?, val senderAvatar: String?,
val sendState: SendState,
val annotations: EventAnnotationsSummary? = null val annotations: EventAnnotationsSummary? = null
) { ) {



View File

@ -17,7 +17,10 @@
package im.vector.matrix.android.api.session.user package im.vector.matrix.android.api.session.user


import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.Cancelable


/** /**
* This interface defines methods to get users. It's implemented at the session level. * This interface defines methods to get users. It's implemented at the session level.
@ -31,11 +34,34 @@ interface UserService {
*/ */
fun getUser(userId: String): User? fun getUser(userId: String): User?


/**
* Search list of users on server directory.
* @param search the searched term
* @param limit the max number of users to return
* @param excludedUserIds the user ids to filter from the search
* @param callback the async callback
* @return Cancelable
*/
fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set<String>, callback: MatrixCallback<List<User>>): Cancelable

/** /**
* Observe a live user from a userId * Observe a live user from a userId
* @param userId the userId to look for. * @param userId the userId to look for.
* @return a Livedata of user with userId * @return a Livedata of user with userId
*/ */
fun observeUser(userId: String): LiveData<User?> fun liveUser(userId: String): LiveData<User?>

/**
* Observe a live list of users sorted alphabetically
* @return a Livedata of users
*/
fun liveUsers(): LiveData<List<User>>

/**
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
* @param filter the filter. It will look into userId and displayName.
* @return a Livedata of users
*/
fun livePagedUsers(filter: String? = null): LiveData<PagedList<User>>


} }

View File

@ -19,5 +19,6 @@ package im.vector.matrix.android.api.util
class CancelableBag : Cancelable, MutableList<Cancelable> by ArrayList() { class CancelableBag : Cancelable, MutableList<Cancelable> by ArrayList() {
override fun cancel() { override fun cancel() {
forEach { it.cancel() } forEach { it.cancel() }
clear()
} }
} }

View File

@ -68,7 +68,9 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
callback: MatrixCallback<Session>): Cancelable { callback: MatrixCallback<Session>): Cancelable {


val job = GlobalScope.launch(coroutineDispatchers.main) { val job = GlobalScope.launch(coroutineDispatchers.main) {
val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password) val sessionOrFailure = runCatching {
authenticate(homeServerConnectionConfig, login, password)
}
sessionOrFailure.foldToCallback(callback) sessionOrFailure.foldToCallback(callback)
} }
return CancelableCoroutine(job) return CancelableCoroutine(job)
@ -85,16 +87,12 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
} else { } else {
PasswordLoginParams.userIdentifier(login, password, "Mobile") PasswordLoginParams.userIdentifier(login, password, "Mobile")
} }
executeRequest<Credentials> { val credentials = executeRequest<Credentials> {
apiCall = authAPI.login(loginParams) apiCall = authAPI.login(loginParams)
}.map {
val sessionParams = SessionParams(it, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams)
sessionParams
}.map {
sessionManager.getOrCreateSession(it)
} }

val sessionParams = SessionParams(credentials, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams)
sessionManager.getOrCreateSession(sessionParams)
} }


private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {

View File

@ -27,7 +27,7 @@ internal interface SessionParamsStore {


fun getAll(): List<SessionParams> fun getAll(): List<SessionParams>


fun save(sessionParams: SessionParams): Try<SessionParams> fun save(sessionParams: SessionParams): Try<Unit>


fun delete(userId: String): Try<Unit> fun delete(userId: String): Try<Unit>



View File

@ -62,7 +62,7 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
return sessionParams return sessionParams
} }


override fun save(sessionParams: SessionParams): Try<SessionParams> { override fun save(sessionParams: SessionParams): Try<Unit> {
return Try { return Try {
val entity = mapper.map(sessionParams) val entity = mapper.map(sessionParams)
if (entity != null) { if (entity != null) {
@ -72,7 +72,6 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
} }
realm.close() realm.close()
} }
sessionParams
} }
} }



View File

@ -20,7 +20,6 @@ import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.di.MatrixScope
import javax.inject.Inject import javax.inject.Inject


internal class SessionParamsMapper @Inject constructor(moshi: Moshi) { internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {

View File

@ -72,7 +72,6 @@ import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.task.toConfigurableTask
import im.vector.matrix.android.internal.util.JsonCanonicalizer import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.fetchCopied import im.vector.matrix.android.internal.util.fetchCopied
@ -167,22 +166,25 @@ internal class CryptoManager @Inject constructor(


override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) { override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
setDeviceNameTask setDeviceNameTask
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) .configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
.dispatchTo(callback) this.callback = callback
}
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }


override fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) { override fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) {
deleteDeviceTask deleteDeviceTask
.configureWith(DeleteDeviceTask.Params(deviceId)) .configureWith(DeleteDeviceTask.Params(deviceId)) {
.dispatchTo(callback) this.callback = callback
}
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }


override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>) { override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>) {
deleteDeviceWithUserPasswordTask deleteDeviceWithUserPasswordTask
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password)) .configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password)) {
.dispatchTo(callback) this.callback = callback
}
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }


@ -196,8 +198,9 @@ internal class CryptoManager @Inject constructor(


override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) { override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {
getDevicesTask getDevicesTask
.toConfigurableTask() .configureWith {
.dispatchTo(callback) this.callback = callback
}
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }


@ -254,35 +257,36 @@ internal class CryptoManager @Inject constructor(
private suspend fun internalStart(isInitialSync: Boolean) { private suspend fun internalStart(isInitialSync: Boolean) {
// Open the store // Open the store
cryptoStore.open() cryptoStore.open()
uploadDeviceKeys() runCatching {
.flatMap { oneTimeKeysUploader.maybeUploadOneTimeKeys() } uploadDeviceKeys()
.fold( oneTimeKeysUploader.maybeUploadOneTimeKeys()
{ outgoingRoomKeyRequestManager.start()
Timber.e("Start failed: $it") keysBackup.checkAndStartKeysBackup()
delay(1000) if (isInitialSync) {
isStarting.set(false) // refresh the devices list for each known room members
internalStart(isInitialSync) deviceListManager.invalidateAllDeviceLists()
}, deviceListManager.refreshOutdatedDeviceLists()
{ } else {
outgoingRoomKeyRequestManager.start() incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
keysBackup.checkAndStartKeysBackup() }
if (isInitialSync) { }.fold(
// refresh the devices list for each known room members {
deviceListManager.invalidateAllDeviceLists() isStarting.set(false)
deviceListManager.refreshOutdatedDeviceLists() isStarted.set(true)
} else { },
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests() {
} Timber.e("Start failed: $it")
isStarting.set(false) delay(1000)
isStarted.set(true) isStarting.set(false)
} internalStart(isInitialSync)
) }
)
} }


/** /**
* Close the crypto * Close the crypto
*/ */
fun close() { fun close() = runBlocking(coroutineDispatchers.crypto) {
olmDevice.release() olmDevice.release()
cryptoStore.close() cryptoStore.close()
outgoingRoomKeyRequestManager.stop() outgoingRoomKeyRequestManager.stop()
@ -556,13 +560,16 @@ internal class CryptoManager @Inject constructor(
if (safeAlgorithm != null) { if (safeAlgorithm != null) {
val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()
Timber.v("## encryptEventContent() starts") Timber.v("## encryptEventContent() starts")
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds) runCatching {
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
}
.fold( .fold(
{ callback.onFailure(it) },
{ {
Timber.v("## encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms") Timber.v("## encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED)) callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED))
} },
{ callback.onFailure(it) }

) )
} else { } else {
val algorithm = getEncryptionAlgorithm(roomId) val algorithm = getEncryptionAlgorithm(roomId)
@ -584,10 +591,7 @@ internal class CryptoManager @Inject constructor(
@Throws(MXCryptoError::class) @Throws(MXCryptoError::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return runBlocking { return runBlocking {
internalDecryptEvent(event, timeline).fold( internalDecryptEvent(event, timeline)
{ throw it },
{ it }
)
} }
} }


@ -600,8 +604,10 @@ internal class CryptoManager @Inject constructor(
*/ */
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) { override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
GlobalScope.launch { GlobalScope.launch {
val result = withContext(coroutineDispatchers.crypto) { val result = runCatching {
internalDecryptEvent(event, timeline) withContext(coroutineDispatchers.crypto) {
internalDecryptEvent(event, timeline)
}
} }
result.foldToCallback(callback) result.foldToCallback(callback)
} }
@ -612,22 +618,22 @@ internal class CryptoManager @Inject constructor(
* *
* @param event the raw event. * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the MXEventDecryptionResult data, or null in case of error wrapped into [Try] * @return the MXEventDecryptionResult data, or null in case of error
*/ */
private suspend fun internalDecryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> { private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
val eventContent = event.content val eventContent = event.content
return if (eventContent == null) { if (eventContent == null) {
Timber.e("## decryptEvent : empty event content") Timber.e("## decryptEvent : empty event content")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)) throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
} else { } else {
val algorithm = eventContent["algorithm"]?.toString() val algorithm = eventContent["algorithm"]?.toString()
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm) val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
if (alg == null) { if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm) val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
Timber.e("## decryptEvent() : $reason") Timber.e("## decryptEvent() : $reason")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)) throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
} else { } else {
alg.decryptEvent(event, timeline) return alg.decryptEvent(event, timeline)
} }
} }
} }
@ -689,12 +695,13 @@ internal class CryptoManager @Inject constructor(
private fun onRoomEncryptionEvent(roomId: String, event: Event) { private fun onRoomEncryptionEvent(roomId: String, event: Event) {
GlobalScope.launch(coroutineDispatchers.crypto) { GlobalScope.launch(coroutineDispatchers.crypto) {
val params = LoadRoomMembersTask.Params(roomId) val params = LoadRoomMembersTask.Params(roomId)
loadRoomMembersTask try {
.execute(params) loadRoomMembersTask.execute(params)
.map { _ -> val userIds = getRoomUserIds(roomId)
val userIds = getRoomUserIds(roomId) setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds) } catch (throwable: Throwable) {
} Timber.e(throwable)
}
} }
} }


@ -761,7 +768,7 @@ internal class CryptoManager @Inject constructor(
/** /**
* Upload my user's device keys. * Upload my user's device keys.
*/ */
private suspend fun uploadDeviceKeys(): Try<KeysUploadResponse> { private suspend fun uploadDeviceKeys(): KeysUploadResponse {
// Prepare the device keys data to send // Prepare the device keys data to send
// Sign it // Sign it
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary()) val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
@ -868,10 +875,8 @@ internal class CryptoManager @Inject constructor(
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) { fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
// force the refresh to ensure that the devices list is up-to-date // force the refresh to ensure that the devices list is up-to-date
GlobalScope.launch(coroutineDispatchers.crypto) { GlobalScope.launch(coroutineDispatchers.crypto) {
deviceListManager runCatching { deviceListManager.downloadKeys(userIds, true) }
.downloadKeys(userIds, true)
.fold( .fold(
{ callback.onFailure(it) },
{ {
val unknownDevices = getUnknownDevices(it) val unknownDevices = getUnknownDevices(it)
if (unknownDevices.map.isEmpty()) { if (unknownDevices.map.isEmpty()) {
@ -880,7 +885,8 @@ internal class CryptoManager @Inject constructor(
// trigger an an unknown devices exception // trigger an an unknown devices exception
callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices))) callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices)))
} }
} },
{ callback.onFailure(it) }
) )
} }
} }
@ -1035,16 +1041,17 @@ internal class CryptoManager @Inject constructor(


override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) { override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
GlobalScope.launch(coroutineDispatchers.crypto) { GlobalScope.launch(coroutineDispatchers.crypto) {
deviceListManager runCatching {
.downloadKeys(userIds, forceDownload) deviceListManager.downloadKeys(userIds, forceDownload)
.foldToCallback(callback) }.foldToCallback(callback)
} }
} }


override fun clearCryptoCache(callback: MatrixCallback<Unit>) { override fun clearCryptoCache(callback: MatrixCallback<Unit>) {
clearCryptoDataTask clearCryptoDataTask
.toConfigurableTask() .configureWith {
.dispatchTo(callback) this.callback = callback
}
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }



View File

@ -18,14 +18,12 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto


import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.MatrixPatterns
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
import im.vector.matrix.android.internal.extensions.onError
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import timber.log.Timber import timber.log.Timber
@ -237,7 +235,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* @param forceDownload Always download the keys even if cached. * @param forceDownload Always download the keys even if cached.
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): Try<MXUsersDevicesMap<MXDeviceInfo>> { suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): MXUsersDevicesMap<MXDeviceInfo> {
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds") Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")
// Map from userId -> deviceId -> MXDeviceInfo // Map from userId -> deviceId -> MXDeviceInfo
val stored = MXUsersDevicesMap<MXDeviceInfo>() val stored = MXUsersDevicesMap<MXDeviceInfo>()
@ -268,16 +266,15 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
} }
return if (downloadUsers.isEmpty()) { return if (downloadUsers.isEmpty()) {
Timber.v("## downloadKeys() : no new user device") Timber.v("## downloadKeys() : no new user device")
Try.just(stored) stored
} else { } else {
Timber.v("## downloadKeys() : starts") Timber.v("## downloadKeys() : starts")
val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()
doKeyDownloadForUsers(downloadUsers) val result = doKeyDownloadForUsers(downloadUsers)
.map { Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms")
Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms") result.also {
it.addEntriesFromMap(stored) it.addEntriesFromMap(stored)
it }
}
} }
} }


@ -286,60 +283,60 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* *
* @param downloadUsers the user ids list * @param downloadUsers the user ids list
*/ */
private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): Try<MXUsersDevicesMap<MXDeviceInfo>> { private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): MXUsersDevicesMap<MXDeviceInfo> {
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers") Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
// get the user ids which did not already trigger a keys download // get the user ids which did not already trigger a keys download
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) } val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
if (filteredUsers.isEmpty()) { if (filteredUsers.isEmpty()) {
// trigger nothing // trigger nothing
return Try.just(MXUsersDevicesMap()) return MXUsersDevicesMap()
} }
val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken()) val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken())
return downloadKeysForUsersTask.execute(params) val response = try {
.map { response -> downloadKeysForUsersTask.execute(params)
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") } catch (throwable: Throwable) {
for (userId in filteredUsers) { Timber.e(throwable, "##doKeyDownloadForUsers(): error")
val devices = response.deviceKeys?.get(userId) onKeysDownloadFailed(filteredUsers)
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices") throw throwable
if (devices != null) { }
val mutableDevices = HashMap(devices) Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
val deviceIds = ArrayList(mutableDevices.keys) for (userId in filteredUsers) {
for (deviceId in deviceIds) { val devices = response.deviceKeys?.get(userId)
// Get the potential previously store device keys for this device Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices")
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId) if (devices != null) {
val deviceInfo = mutableDevices[deviceId] 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]


// in some race conditions (like unit tests) // in some race conditions (like unit tests)
// the self device must be seen as verified // the self device must be seen as verified
if (TextUtils.equals(deviceInfo!!.deviceId, credentials.deviceId) && TextUtils.equals(userId, credentials.userId)) { if (TextUtils.equals(deviceInfo!!.deviceId, credentials.deviceId) && TextUtils.equals(userId, credentials.userId)) {
deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
} }
// Validate received keys // Validate received keys
if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) { if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) {
// New device keys are not valid. Do not store them // New device keys are not valid. Do not store them
mutableDevices.remove(deviceId) mutableDevices.remove(deviceId)
if (null != previouslyStoredDeviceKeys) { if (null != previouslyStoredDeviceKeys) {
// But keep old validated ones if any // But keep old validated ones if any
mutableDevices[deviceId] = previouslyStoredDeviceKeys mutableDevices[deviceId] = previouslyStoredDeviceKeys
} }
} else if (null != previouslyStoredDeviceKeys) { } else if (null != previouslyStoredDeviceKeys) {
// The verified status is not sync'ed with hs. // The verified status is not sync'ed with hs.
// This is a client side information, valid only for this client. // This is a client side information, valid only for this client.
// So, transfer its previous value // So, transfer its previous value
mutableDevices[deviceId]!!.verified = previouslyStoredDeviceKeys.verified mutableDevices[deviceId]!!.verified = previouslyStoredDeviceKeys.verified
}
}
// Update the store
// Note that devices which aren't in the response will be removed from the stores
cryptoStore.storeUserDevices(userId, mutableDevices)
}
} }
onKeysDownloadSucceed(filteredUsers, response.failures)
}
.onError {
Timber.e(it, "##doKeyDownloadForUsers(): error")
onKeysDownloadFailed(filteredUsers)
} }
// Update the store
// Note that devices which aren't in the response will be removed from the stores
cryptoStore.storeUserDevices(userId, mutableDevices)
}
}
return onKeysDownloadSucceed(filteredUsers, response.failures)
} }


/** /**
@ -465,15 +462,16 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
} }


cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
doKeyDownloadForUsers(users) runCatching {
.fold( doKeyDownloadForUsers(users)
{ }.fold(
Timber.e(it, "## refreshOutdatedDeviceLists() : ERROR updating device keys for users $users") {
}, Timber.v("## refreshOutdatedDeviceLists() : done")
{ },
Timber.v("## refreshOutdatedDeviceLists() : done") {
} Timber.e(it, "## refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
) }
)
} }


companion object { companion object {

View File

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


import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
@ -506,25 +505,25 @@ internal class MXOlmDevice @Inject constructor(
keysClaimed: Map<String, String>, keysClaimed: Map<String, String>,
exportFormat: Boolean): Boolean { exportFormat: Boolean): Boolean {
val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat) val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat)
runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
.fold(
{
// If we already have this session, consider updating it
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")


getInboundGroupSession(sessionId, senderKey, roomId).fold( val existingFirstKnown = it.firstKnownIndex!!
{ val newKnownFirstIndex = session.firstKnownIndex
// Nothing to do in case of error
},
{
// If we already have this session, consider updating it
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")


val existingFirstKnown = it.firstKnownIndex!! //If our existing session is better we keep it
val newKnownFirstIndex = session.firstKnownIndex if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {

session.olmInboundGroupSession?.releaseSession()
//If our existing session is better we keep it return false
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) { }
session.olmInboundGroupSession?.releaseSession() },
return false {
} // Nothing to do in case of error
} }
) )


// sanity check // sanity check
if (null == session.olmInboundGroupSession) { if (null == session.olmInboundGroupSession) {
@ -595,12 +594,8 @@ internal class MXOlmDevice @Inject constructor(
continue continue
} }


getInboundGroupSession(sessionId, senderKey, roomId) runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
.fold( .fold(
{
// Session does not already exist, add it
sessions.add(session)
},
{ {
// If we already have this session, consider updating it // If we already have this session, consider updating it
Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId") Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
@ -613,7 +608,12 @@ internal class MXOlmDevice @Inject constructor(
sessions.add(session) sessions.add(session)
} }
Unit Unit
},
{
// Session does not already exist, add it
sessions.add(session)
} }

) )
} }


@ -648,61 +648,57 @@ internal class MXOlmDevice @Inject constructor(
roomId: String, roomId: String,
timeline: String?, timeline: String?,
sessionId: String, sessionId: String,
senderKey: String): Try<OlmDecryptionResult> { senderKey: String): OlmDecryptionResult {
return getInboundGroupSession(sessionId, senderKey, roomId) val session = getInboundGroupSession(sessionId, senderKey, roomId)
.flatMap { session -> // Check that the room id matches the original one for the session. This stops
// Check that the room id matches the original one for the session. This stops // the HS pretending a message was targeting a different room.
// the HS pretending a message was targeting a different room. if (roomId == session.roomId) {
if (roomId == session.roomId) { var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null
var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null try {
try { decryptResult = session.olmInboundGroupSession!!.decryptMessage(body)
decryptResult = session.olmInboundGroupSession!!.decryptMessage(body) } catch (e: OlmException) {
} catch (e: OlmException) { Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed") throw MXCryptoError.OlmError(e)
return@flatMap Try.Failure(MXCryptoError.OlmError(e)) }
}


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

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

if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
Timber.e("## decryptGroupMessage() : $reason")
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason))
}

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

store.storeInboundGroupSessions(listOf(session))
val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
adapter.fromJson(payloadString)
} catch (e: Exception) {
Timber.e("## decryptGroupMessage() : fails to parse the payload")
return@flatMap Try.Failure(
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
}

return@flatMap Try.just(
OlmDecryptionResult(
payload,
session.keysClaimed,
senderKey,
session.forwardingCurve25519KeyChain
)
)
} else {
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## decryptGroupMessage() : $reason")
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason))
}
} }

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

if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
Timber.e("## decryptGroupMessage() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
}

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

store.storeInboundGroupSessions(listOf(session))
val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
adapter.fromJson(payloadString)
} catch (e: Exception) {
Timber.e("## decryptGroupMessage() : fails to parse the payload")
throw
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
}

return OlmDecryptionResult(
payload,
session.keysClaimed,
senderKey,
session.forwardingCurve25519KeyChain
)
} else {
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## decryptGroupMessage() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
}
} }


/** /**
@ -766,26 +762,26 @@ internal class MXOlmDevice @Inject constructor(
* @param senderKey the base64-encoded curve25519 key of the sender. * @param senderKey the base64-encoded curve25519 key of the sender.
* @return the inbound group session. * @return the inbound group session.
*/ */
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): Try<OlmInboundGroupSessionWrapper> { fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper {
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) { if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)) throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)
} }


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


return if (null != session) { if (session != null) {
// Check that the room id matches the original one for the session. This stops // Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room. // the HS pretending a message was targeting a different room.
if (!TextUtils.equals(roomId, session.roomId)) { if (!TextUtils.equals(roomId, session.roomId)) {
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId) val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## getInboundGroupSession() : $errorDescription") Timber.e("## getInboundGroupSession() : $errorDescription")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription)) throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription)
} else { } else {
Try.just(session) return session
} }
} else { } else {
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId") Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)) throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)
} }
} }


@ -798,6 +794,6 @@ internal class MXOlmDevice @Inject constructor(
* @return true if the unbound session keys are known. * @return true if the unbound session keys are known.
*/ */
fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean { fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean {
return getInboundGroupSession(sessionId, senderKey, roomId).isSuccess() return runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }.isSuccess
} }
} }

View File

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


import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.session.SessionScope
import javax.inject.Inject import javax.inject.Inject


internal class ObjectSigner @Inject constructor(private val credentials: Credentials, internal class ObjectSigner @Inject constructor(private val credentials: Credentials,

View File

@ -16,8 +16,6 @@


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


import arrow.core.Try
import arrow.instances.`try`.applicativeError.handleError
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.model.MXKey import im.vector.matrix.android.internal.crypto.model.MXKey
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
@ -59,13 +57,13 @@ internal class OneTimeKeysUploader @Inject constructor(
/** /**
* Check if the OTK must be uploaded. * Check if the OTK must be uploaded.
*/ */
suspend fun maybeUploadOneTimeKeys(): Try<Unit> { suspend fun maybeUploadOneTimeKeys() {
if (oneTimeKeyCheckInProgress) { if (oneTimeKeyCheckInProgress) {
return Try.just(Unit) return
} }
if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) { if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
// we've done a key upload recently. // we've done a key upload recently.
return Try.just(Unit) return
} }


lastOneTimeKeyCheck = System.currentTimeMillis() lastOneTimeKeyCheck = System.currentTimeMillis()
@ -81,41 +79,31 @@ internal class OneTimeKeysUploader @Inject constructor(
// discard the oldest private keys first. This will eventually clean // discard the oldest private keys first. This will eventually clean
// out stale private keys that won't receive a message. // out stale private keys that won't receive a message.
val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt() val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt()
val result = if (oneTimeKeyCount != null) { if (oneTimeKeyCount != null) {
uploadOTK(oneTimeKeyCount!!, keyLimit) uploadOTK(oneTimeKeyCount!!, keyLimit)
} else { } else {
// ask the server how many keys we have // ask the server how many keys we have
val uploadKeysParams = UploadKeysTask.Params(null, null, credentials.deviceId!!) val uploadKeysParams = UploadKeysTask.Params(null, null, credentials.deviceId!!)
uploadKeysTask.execute(uploadKeysParams) val response = uploadKeysTask.execute(uploadKeysParams)
.flatMap { // We need to keep a pool of one time public keys on the server so that
// We need to keep a pool of one time public keys on the server so that // other devices can start conversations with us. But we can only store
// other devices can start conversations with us. But we can only store // a finite number of private keys in the olm Account object.
// a finite number of private keys in the olm Account object. // To complicate things further then can be a delay between a device
// 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
// 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
// message. We need to keep the corresponding private key locally until // we receive the message.
// we receive the message. // But that message might never arrive leaving us stuck with duff
// But that message might never arrive leaving us stuck with duff // private keys clogging up our local storage.
// private keys clogging up our local storage. // So we need some kind of engineering compromise to balance all of
// So we need some kind of engineering compromise to balance all of // these factors.
// these factors. // TODO Why we do not set oneTimeKeyCount here?
// TODO Why we do not set oneTimeKeyCount here? // TODO This is not needed anymore, see https://github.com/matrix-org/matrix-js-sdk/pull/493 (TODO on iOS also)
// TODO This is not needed anymore, see https://github.com/matrix-org/matrix-js-sdk/pull/493 (TODO on iOS also) val keyCount = response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
val keyCount = it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) uploadOTK(keyCount, keyLimit)
uploadOTK(keyCount, keyLimit)
}
} }
return result Timber.v("## uploadKeys() : success")
.map { oneTimeKeyCount = null
Timber.v("## uploadKeys() : success") oneTimeKeyCheckInProgress = false
oneTimeKeyCount = null
oneTimeKeyCheckInProgress = false
}
.handleError {
Timber.e(it, "## uploadKeys() : failed")
oneTimeKeyCount = null
oneTimeKeyCheckInProgress = false
}
} }


/** /**
@ -124,29 +112,26 @@ internal class OneTimeKeysUploader @Inject constructor(
* @param keyCount the key count * @param keyCount the key count
* @param keyLimit the limit * @param keyLimit the limit
*/ */
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Try<Unit> { private suspend fun uploadOTK(keyCount: Int, keyLimit: Int) {
if (keyLimit <= keyCount) { if (keyLimit <= keyCount) {
// If we don't need to generate any more keys then we are done. // If we don't need to generate any more keys then we are done.
return Try.just(Unit) return
} }

val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER) val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
olmDevice.generateOneTimeKeys(keysThisLoop) olmDevice.generateOneTimeKeys(keysThisLoop)
return uploadOneTimeKeys() val response = uploadOneTimeKeys()
.flatMap { if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
if (it.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) { uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
uploadOTK(it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) } else {
} else { Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519") throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")
Try.raise(Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")) }
}
}
} }


/** /**
* Upload my user's one time keys. * Upload my user's one time keys.
*/ */
private suspend fun uploadOneTimeKeys(): Try<KeysUploadResponse> { private suspend fun uploadOneTimeKeys(): KeysUploadResponse {
val oneTimeKeys = olmDevice.getOneTimeKeys() val oneTimeKeys = olmDevice.getOneTimeKeys()
val oneTimeJson = HashMap<String, Any>() val oneTimeJson = HashMap<String, Any>()


@ -169,13 +154,10 @@ internal class OneTimeKeysUploader @Inject constructor(
// For now, we set the device id explicitly, as we may not be using the // For now, we set the device id explicitly, as we may not be using the
// same one as used in login. // same one as used in login.
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!) val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!)
return uploadKeysTask val response = uploadKeysTask.execute(uploadParams)
.execute(uploadParams) lastPublishedOneTimeKeys = oneTimeKeys
.map { olmDevice.markKeysAsPublished()
lastPublishedOneTimeKeys = oneTimeKeys return response
olmDevice.markKeysAsPublished()
it
}
} }


companion object { companion object {

View File

@ -299,10 +299,12 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
// TODO Change this two hard coded key to something better // TODO Change this two hard coded key to something better
contentMap.setObject(recipient["userId"], recipient["deviceId"], message) contentMap.setObject(recipient["userId"], recipient["deviceId"], message)
} }
sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.ROOM_KEY_REQUEST, contentMap, transactionId)) sendToDeviceTask
.dispatchTo(callback) .configureWith(SendToDeviceTask.Params(EventType.ROOM_KEY_REQUEST, contentMap, transactionId)) {
.executeOn(TaskThread.CALLER) this.callback = callback
.callbackOn(TaskThread.CALLER) this.callbackThread = TaskThread.CALLER
this.executionThread = TaskThread.CALLER
}
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }



View File

@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.crypto.actions package im.vector.matrix.android.internal.crypto.actions


import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXKey import im.vector.matrix.android.internal.crypto.model.MXKey
@ -32,7 +31,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) { private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {




suspend fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>): Try<MXUsersDevicesMap<MXOlmSessionResult>> { suspend fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>): MXUsersDevicesMap<MXOlmSessionResult> {
val devicesWithoutSession = ArrayList<MXDeviceInfo>() val devicesWithoutSession = ArrayList<MXDeviceInfo>()


val results = MXUsersDevicesMap<MXOlmSessionResult>() val results = MXUsersDevicesMap<MXOlmSessionResult>()
@ -58,7 +57,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
} }


if (devicesWithoutSession.size == 0) { if (devicesWithoutSession.size == 0) {
return Try.just(results) return results
} }


// Prepare the request for claiming one-time keys // Prepare the request for claiming one-time keys
@ -79,39 +78,36 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
Timber.v("## claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim") Timber.v("## claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")


val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim) val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
return oneTimeKeysForUsersDeviceTask val oneTimeKeys = oneTimeKeysForUsersDeviceTask.execute(claimParams)
.execute(claimParams) Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
.map { for (userId in userIds) {
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $it") val deviceInfos = devicesByUser[userId]
for (userId in userIds) { for (deviceInfo in deviceInfos!!) {
val deviceInfos = devicesByUser[userId] var oneTimeKey: MXKey? = null
for (deviceInfo in deviceInfos!!) { val deviceIds = oneTimeKeys.getUserDeviceIds(userId)
var oneTimeKey: MXKey? = null if (null != deviceIds) {
val deviceIds = it.getUserDeviceIds(userId) for (deviceId in deviceIds) {
if (null != deviceIds) { val olmSessionResult = results.getObject(userId, deviceId)
for (deviceId in deviceIds) { if (olmSessionResult!!.sessionId != null) {
val olmSessionResult = results.getObject(userId, deviceId) // We already have a result for this device
if (olmSessionResult!!.sessionId != null) { continue
// We already have a result for this device
continue
}
val key = it.getObject(userId, deviceId)
if (key?.type == oneTimeKeyAlgorithm) {
oneTimeKey = key
}
if (oneTimeKey == null) {
Timber.v("## ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm
+ " for device " + userId + " : " + deviceId)
continue
}
// Update the result for this device in results
olmSessionResult.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
}
}
} }
val key = oneTimeKeys.getObject(userId, deviceId)
if (key?.type == oneTimeKeyAlgorithm) {
oneTimeKey = key
}
if (oneTimeKey == null) {
Timber.v("## ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm
+ " for device " + userId + " : " + deviceId)
continue
}
// Update the result for this device in results
olmSessionResult.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
} }
results
} }
}
}
return results
} }


private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? { private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? {

View File

@ -17,14 +17,11 @@
package im.vector.matrix.android.internal.crypto.actions package im.vector.matrix.android.internal.crypto.actions


import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.session.SessionScope
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -37,7 +34,7 @@ internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val o
* Try to make sure we have established olm sessions for the given users. * Try to make sure we have established olm sessions for the given users.
* @param users a list of user ids. * @param users a list of user ids.
*/ */
suspend fun handle(users: List<String>) : Try<MXUsersDevicesMap<MXOlmSessionResult>> { suspend fun handle(users: List<String>) : MXUsersDevicesMap<MXOlmSessionResult> {
Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users") Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users")
val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>() val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>()



View File

@ -26,7 +26,6 @@ import im.vector.matrix.android.internal.crypto.RoomDecryptorProvider
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.session.SessionScope
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject



View File

@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.crypto.actions
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.session.SessionScope
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject



View File

@ -17,7 +17,6 @@


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


import arrow.core.Try
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
@ -35,7 +34,7 @@ internal interface IMXDecrypting {
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the decryption information, or an error * @return the decryption information, or an error
*/ */
suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult


/** /**
* Handle a key event. * Handle a key event.

View File

@ -17,7 +17,6 @@


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


import arrow.core.Try
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content


/** /**
@ -31,7 +30,7 @@ internal interface IMXEncrypting {
* @param eventContent the content of the event. * @param eventContent the content of the event.
* @param eventType the type of the event. * @param eventType the type of the event.
* @param userIds the room members the event will be sent to. * @param userIds the room members the event will be sent to.
* @return the encrypted content wrapped by [Try] * @return the encrypted content
*/ */
suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Try<Content> suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content
} }

View File

@ -18,7 +18,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm package im.vector.matrix.android.internal.crypto.algorithms.megolm


import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
@ -40,7 +39,6 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import kotlin.collections.HashMap


internal class MXMegolmDecryption(private val credentials: Credentials, internal class MXMegolmDecryption(private val credentials: Credentials,
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
@ -61,30 +59,46 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
*/ */
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap() private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()


override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> { override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return decryptEvent(event, timeline, true) return decryptEvent(event, timeline, true)
} }


private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): Try<MXEventDecryptionResult> { private suspend fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
if (event.roomId.isNullOrBlank()) { if (event.roomId.isNullOrBlank()) {
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
} }


val encryptedEventContent = event.content.toModel<EncryptedEventContent>() val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
?: return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) ?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)


if (encryptedEventContent.senderKey.isNullOrBlank() if (encryptedEventContent.senderKey.isNullOrBlank()
|| encryptedEventContent.sessionId.isNullOrBlank() || encryptedEventContent.sessionId.isNullOrBlank()
|| encryptedEventContent.ciphertext.isNullOrBlank()) { || encryptedEventContent.ciphertext.isNullOrBlank()) {
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
} }


return olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext, return runCatching {
event.roomId, olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext,
timeline, event.roomId,
encryptedEventContent.sessionId, timeline,
encryptedEventContent.senderKey) encryptedEventContent.sessionId,
encryptedEventContent.senderKey)
}
.fold( .fold(
{ olmDecryptionResult ->
// the decryption succeeds
if (olmDecryptionResult.payload != null) {
MXEventDecryptionResult(
clearEvent = olmDecryptionResult.payload,
senderCurve25519Key = olmDecryptionResult.senderKey,
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
?: emptyList()
)
} else {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
},
{ throwable -> { throwable ->
if (throwable is MXCryptoError.OlmError) { if (throwable is MXCryptoError.OlmError) {
// TODO Check the value of .message // TODO Check the value of .message
@ -98,10 +112,10 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message) val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason) val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)


Try.Failure(MXCryptoError.Base( throw MXCryptoError.Base(
MXCryptoError.ErrorType.OLM, MXCryptoError.ErrorType.OLM,
reason, reason,
detailedReason)) detailedReason)
} }
if (throwable is MXCryptoError.Base) { if (throwable is MXCryptoError.Base) {
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
@ -111,23 +125,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
} }
} }
} }

throw throwable
Try.Failure(throwable)
},
{ olmDecryptionResult ->
// the decryption succeeds
if (olmDecryptionResult.payload != null) {
Try.just(
MXEventDecryptionResult(
clearEvent = olmDecryptionResult.payload,
senderCurve25519Key = olmDecryptionResult.senderKey,
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain ?: emptyList()
)
)
} else {
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
}
} }
) )
} }
@ -311,51 +309,48 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
} }
val userId = request.userId ?: return val userId = request.userId ?: return
GlobalScope.launch(coroutineDispatchers.crypto) { GlobalScope.launch(coroutineDispatchers.crypto) {
deviceListManager runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
.downloadKeys(listOf(userId), false) .mapCatching {
.flatMap {
val deviceId = request.deviceId val deviceId = request.deviceId
val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId) val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId)
if (deviceInfo == null) { if (deviceInfo == null) {
throw RuntimeException() throw RuntimeException()
} else { } else {
val devicesByUser = mapOf(userId to listOf(deviceInfo)) val devicesByUser = mapOf(userId to listOf(deviceInfo))
ensureOlmSessionsForDevicesAction val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
.handle(devicesByUser) val body = request.requestBody
.flatMap { val olmSessionResult = usersDeviceMap.getObject(userId, deviceId)
val body = request.requestBody if (olmSessionResult?.sessionId == null) {
val olmSessionResult = it.getObject(userId, deviceId) // no session with this device, probably because there
if (olmSessionResult?.sessionId == null) { // were no one-time keys.
// no session with this device, probably because there return@mapCatching
// were no one-time keys. }
Try.just(Unit) Timber.v("## shareKeysWithDevice() : sharing keys for session" +
} " ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId")
Timber.v("## shareKeysWithDevice() : sharing keys for session" +
" ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId")


val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY) val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY)
runCatching { olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) }
.fold(
{
// TODO
payloadJson["content"] = it.exportKeys()
?: ""
},
{
// TODO
}


olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) )
.fold(
{
// TODO
},
{
// TODO
payloadJson["content"] = it.exportKeys() ?: ""
}
)


val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>() val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload) sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId") Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
sendToDeviceTask.execute(sendToDeviceParams) sendToDeviceTask.execute(sendToDeviceParams)
}
} }
} }
} }
} }

} }


View File

@ -24,7 +24,6 @@ import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevi
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import javax.inject.Inject import javax.inject.Inject



View File

@ -19,7 +19,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm package im.vector.matrix.android.internal.crypto.algorithms.megolm


import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
@ -69,12 +68,10 @@ internal class MXMegolmEncryption(


override suspend fun encryptEventContent(eventContent: Content, override suspend fun encryptEventContent(eventContent: Content,
eventType: String, eventType: String,
userIds: List<String>): Try<Content> { userIds: List<String>): Content {
return getDevicesInRoom(userIds) val devices = getDevicesInRoom(userIds)
.flatMap { ensureOutboundSession(it) } val outboundSession = ensureOutboundSession(devices)
.flatMap { return encryptContent(outboundSession, eventType, eventContent)
encryptContent(it, eventType, eventContent)
}
} }


/** /**
@ -101,7 +98,7 @@ internal class MXMegolmEncryption(
* *
* @param devicesInRoom the devices list * @param devicesInRoom the devices list
*/ */
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): Try<MXOutboundSessionInfo> { private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): MXOutboundSessionInfo {
var session = outboundSession var session = outboundSession
if (session == null if (session == null
// Need to make a brand new session? // Need to make a brand new session?
@ -126,7 +123,8 @@ internal class MXMegolmEncryption(
} }
} }
} }
return shareKey(safeSession, shareMap).map { safeSession!! } shareKey(safeSession, shareMap)
return safeSession
} }


/** /**
@ -136,11 +134,11 @@ internal class MXMegolmEncryption(
* @param devicesByUsers the devices map * @param devicesByUsers the devices map
*/ */
private suspend fun shareKey(session: MXOutboundSessionInfo, private suspend fun shareKey(session: MXOutboundSessionInfo,
devicesByUsers: Map<String, List<MXDeviceInfo>>): Try<Unit> { devicesByUsers: Map<String, List<MXDeviceInfo>>) {
// nothing to send, the task is done // nothing to send, the task is done
if (devicesByUsers.isEmpty()) { if (devicesByUsers.isEmpty()) {
Timber.v("## shareKey() : nothing more to do") Timber.v("## shareKey() : nothing more to do")
return Try.just(Unit) return
} }
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user) // reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
val subMap = HashMap<String, List<MXDeviceInfo>>() val subMap = HashMap<String, List<MXDeviceInfo>>()
@ -157,11 +155,9 @@ internal class MXMegolmEncryption(
} }
} }
Timber.v("## shareKey() ; userId $userIds") Timber.v("## shareKey() ; userId $userIds")
return shareUserDevicesKey(session, subMap) shareUserDevicesKey(session, subMap)
.flatMap { val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it).not() }
val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it).not() } shareKey(session, remainingDevices)
shareKey(session, remainingDevices)
}
} }


/** /**
@ -172,7 +168,7 @@ internal class MXMegolmEncryption(
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo, private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo,
devicesByUser: Map<String, List<MXDeviceInfo>>): Try<Unit> { devicesByUser: Map<String, List<MXDeviceInfo>>) {
val sessionKey = olmDevice.getSessionKey(session.sessionId) val sessionKey = olmDevice.getSessionKey(session.sessionId)
val chainIndex = olmDevice.getMessageIndex(session.sessionId) val chainIndex = olmDevice.getMessageIndex(session.sessionId)


@ -190,94 +186,86 @@ internal class MXMegolmEncryption(
var t0 = System.currentTimeMillis() var t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : starts") Timber.v("## shareUserDevicesKey() : starts")


return ensureOlmSessionsForDevicesAction.handle(devicesByUser) val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
.flatMap { Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after " + (System.currentTimeMillis() - t0) + " ms")
+ (System.currentTimeMillis() - t0) + " ms") val contentMap = MXUsersDevicesMap<Any>()
val contentMap = MXUsersDevicesMap<Any>() var haveTargets = false
var haveTargets = false val userIds = results.userIds
val userIds = it.userIds for (userId in userIds) {
for (userId in userIds) { val devicesToShareWith = devicesByUser[userId]
val devicesToShareWith = devicesByUser[userId] for ((deviceID) in devicesToShareWith!!) {
for ((deviceID) in devicesToShareWith!!) { val sessionResult = results.getObject(userId, deviceID)
val sessionResult = it.getObject(userId, deviceID) if (sessionResult?.sessionId == null) {
if (sessionResult?.sessionId == null) { // no session with this device, probably because there
// no session with this device, probably because there // were no one-time keys.
// were no one-time keys. //
// // we could send them a to_device message anyway, as a
// we could send them a to_device message anyway, as a // signal that they have missed out on the key sharing
// signal that they have missed out on the key sharing // message because of the lack of keys, but there's not
// message because of the lack of keys, but there's not // much point in that really; it will mostly serve to clog
// much point in that really; it will mostly serve to clog // up to_device inboxes.
// up to_device inboxes. //
// // ensureOlmSessionsForUsers has already done the logging,
// ensureOlmSessionsForUsers has already done the logging, // so just skip it.
// so just skip it. continue
continue
}
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
//noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.deviceInfo)))
haveTargets = true
}
}
if (haveTargets) {
t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : has target")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
sendToDeviceTask.execute(sendToDeviceParams)
.map {
Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")

// Add the devices we have shared with to session.sharedWithDevices.
// we deliberately iterate over devicesByUser (ie, the devices we
// attempted to share with) rather than the contentMap (those we did
// share with), because we don't want to try to claim a one-time-key
// for dead devices on every message.
for (userId in devicesByUser.keys) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceId) in devicesToShareWith!!) {
session.sharedWithDevices.setObject(userId, deviceId, chainIndex)
}
}
Unit
}
} else {
Timber.v("## shareUserDevicesKey() : no need to sharekey")
Try.just(Unit)
}
} }
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
//noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.deviceInfo)))
haveTargets = true
}
}
if (haveTargets) {
t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : has target")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
sendToDeviceTask.execute(sendToDeviceParams)
Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")

// Add the devices we have shared with to session.sharedWithDevices.
// we deliberately iterate over devicesByUser (ie, the devices we
// attempted to share with) rather than the contentMap (those we did
// share with), because we don't want to try to claim a one-time-key
// for dead devices on every message.
for (userId in devicesByUser.keys) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceId) in devicesToShareWith!!) {
session.sharedWithDevices.setObject(userId, deviceId, chainIndex)
}
}
} else {
Timber.v("## shareUserDevicesKey() : no need to sharekey")
}
} }


/** /**
* process the pending encryptions * process the pending encryptions
*/ */
private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Try<Content> { private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Content {
return Try<Content> { // Everything is in place, encrypt all pending events
// Everything is in place, encrypt all pending events val payloadJson = HashMap<String, Any>()
val payloadJson = HashMap<String, Any>() payloadJson["room_id"] = roomId
payloadJson["room_id"] = roomId payloadJson["type"] = eventType
payloadJson["type"] = eventType payloadJson["content"] = eventContent
payloadJson["content"] = eventContent


// Get canonical Json from // Get canonical Json from


val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson)) val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson))
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!) val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!)


val map = HashMap<String, Any>() val map = HashMap<String, Any>()
map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
map["sender_key"] = olmDevice.deviceCurve25519Key!! map["sender_key"] = olmDevice.deviceCurve25519Key!!
map["ciphertext"] = ciphertext!! map["ciphertext"] = ciphertext!!
map["session_id"] = session.sessionId map["session_id"] = session.sessionId


// Include our device ID so that recipients can send us a // Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key. // m.new_device message if they don't have our session key.
map["device_id"] = credentials.deviceId!! map["device_id"] = credentials.deviceId!!
session.useCount++ session.useCount++
map return map
}
} }


/** /**
@ -287,50 +275,47 @@ internal class MXMegolmEncryption(
* @param userIds the user ids whose devices must be checked. * @param userIds the user ids whose devices must be checked.
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
private suspend fun getDevicesInRoom(userIds: List<String>): Try<MXUsersDevicesMap<MXDeviceInfo>> { private suspend fun getDevicesInRoom(userIds: List<String>): MXUsersDevicesMap<MXDeviceInfo> {
// We are happy to use a cached version here: we assume that if we already // We are happy to use a cached version here: we assume that if we already
// have a list of the user's devices, then we already share an e2e room // have a list of the user's devices, then we already share an e2e room
// with them, which means that they will have announced any new devices via // with them, which means that they will have announced any new devices via
// an m.new_device. // an m.new_device.
return deviceListManager val keys = deviceListManager.downloadKeys(userIds, false)
.downloadKeys(userIds, false) val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
.flatMap { || cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)


val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>() val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>() val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()


for (userId in it.userIds) { for (userId in keys.userIds) {
val deviceIds = it.getUserDeviceIds(userId) ?: continue val deviceIds = keys.getUserDeviceIds(userId) ?: continue
for (deviceId in deviceIds) { for (deviceId in deviceIds) {
val deviceInfo = it.getObject(userId, deviceId) ?: continue val deviceInfo = keys.getObject(userId, deviceId) ?: continue
if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) { if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) {
// The device is not yet known by the user // The device is not yet known by the user
unknownDevices.setObject(userId, deviceId, deviceInfo) unknownDevices.setObject(userId, deviceId, deviceInfo)
continue continue
}
if (deviceInfo.isBlocked) {
// Remove any blocked devices
continue
}

if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) {
continue
}

if (TextUtils.equals(deviceInfo.identityKey(), olmDevice.deviceCurve25519Key)) {
// Don't bother sending to ourself
continue
}
devicesInRoom.setObject(userId, deviceId, deviceInfo)
}
}
if (unknownDevices.isEmpty) {
Try.just(devicesInRoom)
} else {
Try.Failure(MXCryptoError.UnknownDevice(unknownDevices))
}
} }
if (deviceInfo.isBlocked) {
// Remove any blocked devices
continue
}

if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) {
continue
}

if (TextUtils.equals(deviceInfo.identityKey(), olmDevice.deviceCurve25519Key)) {
// Don't bother sending to ourself
continue
}
devicesInRoom.setObject(userId, deviceId, deviceInfo)
}
}
if (unknownDevices.isEmpty) {
return devicesInRoom
} else {
throw MXCryptoError.UnknownDevice(unknownDevices)
}
} }
} }

View File

@ -17,7 +17,6 @@


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


import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
@ -40,29 +39,28 @@ internal class MXOlmDecryption(
private val credentials: Credentials) private val credentials: Credentials)
: IMXDecrypting { : IMXDecrypting {


override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> { override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run { val olmEventContent = event.content.toModel<OlmEventContent>() ?: run {
Timber.e("## decryptEvent() : bad event format") Timber.e("## decryptEvent() : bad event format")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)) MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)
} }


val cipherText = olmEventContent.ciphertext ?: run { val cipherText = olmEventContent.ciphertext ?: run {
Timber.e("## decryptEvent() : missing cipher text") Timber.e("## decryptEvent() : missing cipher text")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT, throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
MXCryptoError.MISSING_CIPHER_TEXT_REASON)) MXCryptoError.MISSING_CIPHER_TEXT_REASON)
} }


val senderKey = olmEventContent.senderKey ?: run { val senderKey = olmEventContent.senderKey ?: run {
Timber.e("## decryptEvent() : missing sender key") Timber.e("## decryptEvent() : missing sender key")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY,
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)) MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)
} }


val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run { val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run {
Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients") Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS, throw MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON)
MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON))
} }


// The message for myUser // The message for myUser
@ -72,14 +70,12 @@ internal class MXOlmDecryption(


if (decryptedPayload == null) { if (decryptedPayload == null) {
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey") Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
} }
val payloadString = convertFromUTF8(decryptedPayload) val payloadString = convertFromUTF8(decryptedPayload)
if (payloadString == null) { if (payloadString == null) {
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey") Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
} }


val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE) val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
@ -87,73 +83,70 @@ internal class MXOlmDecryption(


if (payload == null) { if (payload == null) {
Timber.e("## decryptEvent failed : null payload") Timber.e("## decryptEvent failed : null payload")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON)
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
} }


val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run { val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run {
Timber.e("## decryptEvent() : bad olmPayloadContent format") Timber.e("## decryptEvent() : bad olmPayloadContent format")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
} }


if (olmPayloadContent.recipient.isNullOrBlank()) { if (olmPayloadContent.recipient.isNullOrBlank()) {
val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient") val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient")
Timber.e("## decryptEvent() : $reason") Timber.e("## decryptEvent() : $reason")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason)
reason))
} }


if (olmPayloadContent.recipient != credentials.userId) { if (olmPayloadContent.recipient != credentials.userId) {
Timber.e("## decryptEvent() : Event ${event.eventId}:" + Timber.e("## decryptEvent() : Event ${event.eventId}:" +
" Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}") " Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))) String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))
} }


val recipientKeys = olmPayloadContent.recipient_keys ?: run { val recipientKeys = olmPayloadContent.recipient_keys ?: run {
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys' property; cannot prevent unknown-key attack") Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys' property; cannot prevent unknown-key attack")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))) String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))
} }


val ed25519 = recipientKeys["ed25519"] val ed25519 = recipientKeys["ed25519"]


if (ed25519 != olmDevice.deviceEd25519Key) { if (ed25519 != olmDevice.deviceEd25519Key) {
Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours") Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
MXCryptoError.BAD_RECIPIENT_KEY_REASON)) MXCryptoError.BAD_RECIPIENT_KEY_REASON)
} }


if (olmPayloadContent.sender.isNullOrBlank()) { if (olmPayloadContent.sender.isNullOrBlank()) {
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack") Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))) String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))
} }


if (olmPayloadContent.sender != event.senderId) { if (olmPayloadContent.sender != event.senderId) {
Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}") Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE, throw MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE,
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))) String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))
} }


if (olmPayloadContent.room_id != event.roomId) { if (olmPayloadContent.room_id != event.roomId) {
Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.room_id} does not match reported room ${event.roomId}") Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.room_id} does not match reported room ${event.roomId}")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM,
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id))) String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id))
} }


val keys = olmPayloadContent.keys ?: run { val keys = olmPayloadContent.keys ?: run {
Timber.e("## decryptEvent failed : null keys") Timber.e("## decryptEvent failed : null keys")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
MXCryptoError.MISSING_CIPHER_TEXT_REASON)) MXCryptoError.MISSING_CIPHER_TEXT_REASON)
} }


return Try.just(MXEventDecryptionResult( return MXEventDecryptionResult(
clearEvent = payload, clearEvent = payload,
senderCurve25519Key = senderKey, senderCurve25519Key = senderKey,
claimedEd25519Key = keys["ed25519"] claimedEd25519Key = keys["ed25519"]
)) )
} }


/** /**

View File

@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.crypto.algorithms.olm


import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.session.SessionScope
import javax.inject.Inject import javax.inject.Inject


internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice, internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice,

View File

@ -19,7 +19,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm package im.vector.matrix.android.internal.crypto.algorithms.olm


import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.DeviceListManager
@ -40,37 +39,35 @@ internal class MXOlmEncryption(
private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction) private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction)
: IMXEncrypting { : IMXEncrypting {


override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Try<Content> { override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content {
// pick the list of recipients based on the membership list. // pick the list of recipients based on the membership list.
// //
// TODO: there is a race condition here! What if a new user turns up // TODO: there is a race condition here! What if a new user turns up
return ensureSession(userIds) ensureSession(userIds)
.map { val deviceInfos = ArrayList<MXDeviceInfo>()
val deviceInfos = ArrayList<MXDeviceInfo>() for (userId in userIds) {
for (userId in userIds) { val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList() for (device in devices) {
for (device in devices) { val key = device.identityKey()
val key = device.identityKey() if (TextUtils.equals(key, olmDevice.deviceCurve25519Key)) {
if (TextUtils.equals(key, olmDevice.deviceCurve25519Key)) { // Don't bother setting up session to ourself
// Don't bother setting up session to ourself continue
continue
}
if (device.isBlocked) {
// Don't bother setting up sessions with blocked users
continue
}
deviceInfos.add(device)
}
}

val messageMap = HashMap<String, Any>()
messageMap["room_id"] = roomId
messageMap["type"] = eventType
messageMap["content"] = eventContent

messageEncrypter.encryptMessage(messageMap, deviceInfos)
messageMap.toContent()!!
} }
if (device.isBlocked) {
// Don't bother setting up sessions with blocked users
continue
}
deviceInfos.add(device)
}
}

val messageMap = HashMap<String, Any>()
messageMap["room_id"] = roomId
messageMap["type"] = eventType
messageMap["content"] = eventContent

messageEncrypter.encryptMessage(messageMap, deviceInfos)
return messageMap.toContent()!!
} }




@ -78,13 +75,9 @@ internal class MXOlmEncryption(
* Ensure that the session * Ensure that the session
* *
* @param users the user ids list * @param users the user ids list
* @param callback the asynchronous callback
*/ */
private suspend fun ensureSession(users: List<String>): Try<Unit> { private suspend fun ensureSession(users: List<String>) {
return deviceListManager deviceListManager.downloadKeys(users, false)
.downloadKeys(users, false) ensureOlmSessionsForUsersAction.handle(users)
.flatMap { ensureOlmSessionsForUsersAction.handle(users) }
.map { Unit }

} }
} }

View File

@ -21,7 +21,6 @@ import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForUsersAction import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForUsersAction
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import javax.inject.Inject import javax.inject.Inject



View File

@ -18,8 +18,6 @@ package im.vector.matrix.android.internal.crypto.api




import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.model.rest.*
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadBody
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody
import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call import retrofit2.Call
import retrofit2.http.* import retrofit2.http.*

View File

@ -51,7 +51,6 @@ import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEnt
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.*
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.task.TaskThread
@ -202,31 +201,32 @@ internal class KeysBackup @Inject constructor(
keysBackupStateManager.state = KeysBackupState.Enabling keysBackupStateManager.state = KeysBackupState.Enabling


createKeysBackupVersionTask createKeysBackupVersionTask
.configureWith(createKeysBackupVersionBody) .configureWith(createKeysBackupVersionBody) {
.dispatchTo(object : MatrixCallback<KeysVersion> { this.callback = object : MatrixCallback<KeysVersion> {
override fun onSuccess(info: KeysVersion) { override fun onSuccess(info: KeysVersion) {
// Reset backup markers. // Reset backup markers.
cryptoStore.resetBackupMarkers() cryptoStore.resetBackupMarkers()


val keyBackupVersion = KeysVersionResult() val keyBackupVersion = KeysVersionResult()
keyBackupVersion.algorithm = createKeysBackupVersionBody.algorithm keyBackupVersion.algorithm = createKeysBackupVersionBody.algorithm
keyBackupVersion.authData = createKeysBackupVersionBody.authData keyBackupVersion.authData = createKeysBackupVersionBody.authData
keyBackupVersion.version = info.version keyBackupVersion.version = info.version


// We can consider that the server does not have keys yet // We can consider that the server does not have keys yet
keyBackupVersion.count = 0 keyBackupVersion.count = 0
keyBackupVersion.hash = null keyBackupVersion.hash = null


enableKeysBackup(keyBackupVersion) enableKeysBackup(keyBackupVersion)


callback.onSuccess(info) callback.onSuccess(info)
}

override fun onFailure(failure: Throwable) {
keysBackupStateManager.state = KeysBackupState.Disabled
callback.onFailure(failure)
}
} }

}
override fun onFailure(failure: Throwable) {
keysBackupStateManager.state = KeysBackupState.Disabled
callback.onFailure(failure)
}
})
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }


@ -241,27 +241,29 @@ internal class KeysBackup @Inject constructor(
keysBackupStateManager.state = KeysBackupState.Unknown keysBackupStateManager.state = KeysBackupState.Unknown
} }


deleteBackupTask.configureWith(DeleteBackupTask.Params(version)) deleteBackupTask
.dispatchTo(object : MatrixCallback<Unit> { .configureWith(DeleteBackupTask.Params(version)) {
private fun eventuallyRestartBackup() { this.callback = object : MatrixCallback<Unit> {
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver private fun eventuallyRestartBackup() {
if (state == KeysBackupState.Unknown) { // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
checkAndStartKeysBackup() if (state == KeysBackupState.Unknown) {
checkAndStartKeysBackup()
}
}

override fun onSuccess(data: Unit) {
eventuallyRestartBackup()

uiHandler.post { callback?.onSuccess(Unit) }
}

override fun onFailure(failure: Throwable) {
eventuallyRestartBackup()

uiHandler.post { callback?.onFailure(failure) }
} }
} }

}
override fun onSuccess(data: Unit) {
eventuallyRestartBackup()

uiHandler.post { callback?.onSuccess(Unit) }
}

override fun onFailure(failure: Throwable) {
eventuallyRestartBackup()

uiHandler.post { callback?.onFailure(failure) }
}
})
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
} }
@ -353,15 +355,14 @@ internal class KeysBackup @Inject constructor(
callback: MatrixCallback<KeysBackupVersionTrust>) { callback: MatrixCallback<KeysBackupVersionTrust>) {
// TODO Validate with François that this is correct // TODO Validate with François that this is correct
object : Task<KeysVersionResult, KeysBackupVersionTrust> { object : Task<KeysVersionResult, KeysBackupVersionTrust> {
override suspend fun execute(params: KeysVersionResult): Try<KeysBackupVersionTrust> { override suspend fun execute(params: KeysVersionResult): KeysBackupVersionTrust {
return Try { return getKeysBackupTrustBg(params)
getKeysBackupTrustBg(params)
}
} }
} }
.configureWith(keysBackupVersion) .configureWith(keysBackupVersion) {
.dispatchTo(callback) this.callback = callback
.executeOn(TaskThread.COMPUTATION) this.executionThread = TaskThread.COMPUTATION
}
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }


@ -452,7 +453,8 @@ internal class KeysBackup @Inject constructor(
val myUserId = credentials.userId val myUserId = credentials.userId


// Get current signatures, or create an empty set // Get current signatures, or create an empty set
val myUserSignatures = authData.signatures?.get(myUserId)?.toMutableMap() ?: HashMap() val myUserSignatures = authData.signatures?.get(myUserId)?.toMutableMap()
?: HashMap()


if (trust) { if (trust) {
// Add current device signature // Add current device signature
@ -490,27 +492,28 @@ internal class KeysBackup @Inject constructor(


// And send it to the homeserver // And send it to the homeserver
updateKeysBackupVersionTask updateKeysBackupVersionTask
.configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody)) .configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody)) {
.dispatchTo(object : MatrixCallback<Unit> { this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
// Relaunch the state machine on this updated backup version // Relaunch the state machine on this updated backup version
val newKeysBackupVersion = KeysVersionResult() val newKeysBackupVersion = KeysVersionResult()


newKeysBackupVersion.version = keysBackupVersion.version newKeysBackupVersion.version = keysBackupVersion.version
newKeysBackupVersion.algorithm = keysBackupVersion.algorithm newKeysBackupVersion.algorithm = keysBackupVersion.algorithm
newKeysBackupVersion.count = keysBackupVersion.count newKeysBackupVersion.count = keysBackupVersion.count
newKeysBackupVersion.hash = keysBackupVersion.hash newKeysBackupVersion.hash = keysBackupVersion.hash
newKeysBackupVersion.authData = updateKeysBackupVersionBody.authData newKeysBackupVersion.authData = updateKeysBackupVersionBody.authData


checkAndStartWithKeysBackupVersion(newKeysBackupVersion) checkAndStartWithKeysBackupVersion(newKeysBackupVersion)


callback.onSuccess(data) callback.onSuccess(data)
}

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

}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
})
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
} }
@ -756,49 +759,52 @@ internal class KeysBackup @Inject constructor(
if (roomId != null && sessionId != null) { if (roomId != null && sessionId != null) {
// Get key for the room and for the session // Get key for the room and for the session
getRoomSessionDataTask getRoomSessionDataTask
.configureWith(GetRoomSessionDataTask.Params(roomId, sessionId, version)) .configureWith(GetRoomSessionDataTask.Params(roomId, sessionId, version)) {
.dispatchTo(object : MatrixCallback<KeyBackupData> { this.callback = object : MatrixCallback<KeyBackupData> {
override fun onSuccess(data: KeyBackupData) { override fun onSuccess(data: KeyBackupData) {
// Convert to KeysBackupData // Convert to KeysBackupData
val keysBackupData = KeysBackupData() val keysBackupData = KeysBackupData()
keysBackupData.roomIdToRoomKeysBackupData = HashMap() keysBackupData.roomIdToRoomKeysBackupData = HashMap()
val roomKeysBackupData = RoomKeysBackupData() val roomKeysBackupData = RoomKeysBackupData()
roomKeysBackupData.sessionIdToKeyBackupData = HashMap() roomKeysBackupData.sessionIdToKeyBackupData = HashMap()
roomKeysBackupData.sessionIdToKeyBackupData[sessionId] = data roomKeysBackupData.sessionIdToKeyBackupData[sessionId] = data
keysBackupData.roomIdToRoomKeysBackupData[roomId] = roomKeysBackupData keysBackupData.roomIdToRoomKeysBackupData[roomId] = roomKeysBackupData


callback.onSuccess(keysBackupData) callback.onSuccess(keysBackupData)
} }


override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
callback.onFailure(failure) callback.onFailure(failure)
}
} }
}) }
.executeBy(taskExecutor) .executeBy(taskExecutor)
} else if (roomId != null) { } else if (roomId != null) {
// Get all keys for the room // Get all keys for the room
getRoomSessionsDataTask getRoomSessionsDataTask
.configureWith(GetRoomSessionsDataTask.Params(roomId, version)) .configureWith(GetRoomSessionsDataTask.Params(roomId, version)) {
.dispatchTo(object : MatrixCallback<RoomKeysBackupData> { this.callback = object : MatrixCallback<RoomKeysBackupData> {
override fun onSuccess(data: RoomKeysBackupData) { override fun onSuccess(data: RoomKeysBackupData) {
// Convert to KeysBackupData // Convert to KeysBackupData
val keysBackupData = KeysBackupData() val keysBackupData = KeysBackupData()
keysBackupData.roomIdToRoomKeysBackupData = HashMap() keysBackupData.roomIdToRoomKeysBackupData = HashMap()
keysBackupData.roomIdToRoomKeysBackupData[roomId] = data keysBackupData.roomIdToRoomKeysBackupData[roomId] = data


callback.onSuccess(keysBackupData) callback.onSuccess(keysBackupData)
} }


override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
callback.onFailure(failure) callback.onFailure(failure)
}
} }
}) }
.executeBy(taskExecutor) .executeBy(taskExecutor)
} else { } else {
// Get all keys // Get all keys
getSessionsDataTask getSessionsDataTask
.configureWith(GetSessionsDataTask.Params(version)) .configureWith(GetSessionsDataTask.Params(version)) {
.dispatchTo(callback) this.callback = callback
}
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
} }
@ -853,45 +859,47 @@ internal class KeysBackup @Inject constructor(
override fun getVersion(version: String, override fun getVersion(version: String,
callback: MatrixCallback<KeysVersionResult?>) { callback: MatrixCallback<KeysVersionResult?>) {
getKeysBackupVersionTask getKeysBackupVersionTask
.configureWith(version) .configureWith(version) {
.dispatchTo(object : MatrixCallback<KeysVersionResult> { this.callback = object : MatrixCallback<KeysVersionResult> {
override fun onSuccess(data: KeysVersionResult) { override fun onSuccess(data: KeysVersionResult) {
callback.onSuccess(data) callback.onSuccess(data)
} }


override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
if (failure is Failure.ServerError if (failure is Failure.ServerError
&& failure.error.code == MatrixError.NOT_FOUND) { && failure.error.code == MatrixError.NOT_FOUND) {
// Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup // Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup
callback.onSuccess(null) callback.onSuccess(null)
} else { } else {
// Transmit the error // Transmit the error
callback.onFailure(failure) callback.onFailure(failure)
}
} }
} }
}) }
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }


override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) { override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) {
getKeysBackupLastVersionTask getKeysBackupLastVersionTask
.toConfigurableTask() .configureWith {
.dispatchTo(object : MatrixCallback<KeysVersionResult> { this.callback = object : MatrixCallback<KeysVersionResult> {
override fun onSuccess(data: KeysVersionResult) { override fun onSuccess(data: KeysVersionResult) {
callback.onSuccess(data) callback.onSuccess(data)
} }


override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
if (failure is Failure.ServerError if (failure is Failure.ServerError
&& failure.error.code == MatrixError.NOT_FOUND) { && failure.error.code == MatrixError.NOT_FOUND) {
// Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup // Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup
callback.onSuccess(null) callback.onSuccess(null)
} else { } else {
// Transmit the error // Transmit the error
callback.onFailure(failure) callback.onFailure(failure)
}
} }
} }
}) }
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }


@ -1234,69 +1242,72 @@ internal class KeysBackup @Inject constructor(


Timber.v("backupKeys: 4 - Sending request") Timber.v("backupKeys: 4 - Sending request")


// Make the request val sendingRequestCallback = object : MatrixCallback<BackupKeysResult> {
storeSessionDataTask override fun onSuccess(data: BackupKeysResult) {
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) uiHandler.post {
.dispatchTo(object : MatrixCallback<BackupKeysResult> { Timber.v("backupKeys: 5a - Request complete")
override fun onSuccess(data: BackupKeysResult) {
uiHandler.post {
Timber.v("backupKeys: 5a - Request complete")


// Mark keys as backed up // Mark keys as backed up
cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers) cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers)


if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) { if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) {
Timber.v("backupKeys: All keys have been backed up") Timber.v("backupKeys: All keys have been backed up")
onServerDataRetrieved(data.count, data.hash) onServerDataRetrieved(data.count, data.hash)


// Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() // Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess()
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
} else { } else {
Timber.v("backupKeys: Continue to back up keys") Timber.v("backupKeys: Continue to back up keys")
keysBackupStateManager.state = KeysBackupState.WillBackUp keysBackupStateManager.state = KeysBackupState.WillBackUp


backupKeys() backupKeys()
}
}
} }
}
}


override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
if (failure is Failure.ServerError) { if (failure is Failure.ServerError) {
uiHandler.post { uiHandler.post {
Timber.e(failure, "backupKeys: backupKeys failed.") Timber.e(failure, "backupKeys: backupKeys failed.")


when (failure.error.code) { when (failure.error.code) {
MatrixError.NOT_FOUND, MatrixError.NOT_FOUND,
MatrixError.WRONG_ROOM_KEYS_VERSION -> { MatrixError.WRONG_ROOM_KEYS_VERSION -> {
// Backup has been deleted on the server, or we are not using the last backup version // Backup has been deleted on the server, or we are not using the last backup version
keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion
backupAllGroupSessionsCallback?.onFailure(failure)
resetBackupAllGroupSessionsListeners()
resetKeysBackupData()
keysBackupVersion = null

// Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver
checkAndStartKeysBackup()
}
else ->
// Come back to the ready state so that we will retry on the next received key
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
}
}
} else {
uiHandler.post {
backupAllGroupSessionsCallback?.onFailure(failure) backupAllGroupSessionsCallback?.onFailure(failure)
resetBackupAllGroupSessionsListeners() resetBackupAllGroupSessionsListeners()
resetKeysBackupData()
keysBackupVersion = null


Timber.e("backupKeys: backupKeys failed.") // Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver

checkAndStartKeysBackup()
// Retry a bit later
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
maybeBackupKeys()
} }
else ->
// Come back to the ready state so that we will retry on the next received key
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
} }
} }
}) } else {
uiHandler.post {
backupAllGroupSessionsCallback?.onFailure(failure)
resetBackupAllGroupSessionsListeners()

Timber.e("backupKeys: backupKeys failed.")

// Retry a bit later
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
maybeBackupKeys()
}
}
}
}

// Make the request
storeSessionDataTask
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)){
this.callback = sendingRequestCallback
}
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
} }

View File

@ -142,12 +142,11 @@ private fun deriveKey(password: String,
* Generate a 32 chars salt * Generate a 32 chars salt
*/ */
private fun generateSalt(): String { private fun generateSalt(): String {
var salt = "" val salt = buildString {

do {
do { append(UUID.randomUUID().toString())
salt += UUID.randomUUID().toString() } while (length < SALT_LENGTH)
} while (salt.length < SALT_LENGTH) }



return salt.substring(0, SALT_LENGTH) return salt.substring(0, SALT_LENGTH)
} }

View File

@ -16,7 +16,6 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
@ -30,7 +29,7 @@ internal class DefaultCreateKeysBackupVersionTask @Inject constructor(private va
: CreateKeysBackupVersionTask { : CreateKeysBackupVersionTask {




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

View File

@ -16,10 +16,8 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


@ -33,10 +31,9 @@ internal interface DeleteBackupTask : Task<DeleteBackupTask.Params, Unit> {
internal class DefaultDeleteBackupTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultDeleteBackupTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: DeleteBackupTask { : DeleteBackupTask {


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

View File

@ -16,10 +16,8 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


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


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

View File

@ -16,10 +16,8 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


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


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

View File

@ -16,10 +16,8 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


@ -32,10 +30,9 @@ internal interface DeleteSessionsDataTask : Task<DeleteSessionsDataTask.Params,
internal class DefaultDeleteSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultDeleteSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: DeleteSessionsDataTask { : DeleteSessionsDataTask {


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

View File

@ -16,7 +16,6 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
@ -29,7 +28,7 @@ internal class DefaultGetKeysBackupLastVersionTask @Inject constructor(private v
: GetKeysBackupLastVersionTask { : GetKeysBackupLastVersionTask {




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

View File

@ -16,7 +16,6 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
@ -29,7 +28,7 @@ internal class DefaultGetKeysBackupVersionTask @Inject constructor(private val r
: GetKeysBackupVersionTask { : GetKeysBackupVersionTask {




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

View File

@ -16,11 +16,9 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


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


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

View File

@ -16,11 +16,9 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.RoomKeysBackupData import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


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


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

View File

@ -16,11 +16,9 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysBackupData import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysBackupData
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


@ -33,10 +31,9 @@ internal interface GetSessionsDataTask : Task<GetSessionsDataTask.Params, KeysBa
internal class DefaultGetSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultGetSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: GetSessionsDataTask { : GetSessionsDataTask {


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

View File

@ -16,12 +16,10 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


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


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

View File

@ -16,12 +16,10 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


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


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

View File

@ -16,12 +16,10 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysBackupData
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysBackupData
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


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


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

View File

@ -16,7 +16,6 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
@ -34,7 +33,7 @@ internal class DefaultUpdateKeysBackupVersionTask @Inject constructor(private va
: UpdateKeysBackupVersionTask { : UpdateKeysBackupVersionTask {




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

View File

@ -62,9 +62,7 @@ data class MXKey(
fun signatureForUserId(userId: String, signkey: String): String? { fun signatureForUserId(userId: String, signkey: String): String? {
// sanity checks // sanity checks
if (userId.isNotBlank() && signkey.isNotBlank()) { if (userId.isNotBlank() && signkey.isNotBlank()) {
if (signatures.containsKey(userId)) { return signatures[userId]?.get(signkey)
return signatures[userId]?.get(signkey)
}
} }


return null return null

View File

@ -16,8 +16,8 @@


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


import io.realm.annotations.RealmModule
import im.vector.matrix.android.internal.crypto.store.db.model.* import im.vector.matrix.android.internal.crypto.store.db.model.*
import io.realm.annotations.RealmModule


/** /**
* Realm module for Crypto store classes * Realm module for Crypto store classes

View File

@ -16,10 +16,10 @@


package im.vector.matrix.android.internal.crypto.store.db.model package im.vector.matrix.android.internal.crypto.store.db.model


import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import org.matrix.olm.OlmAccount import org.matrix.olm.OlmAccount


internal open class CryptoMetadataEntity( internal open class CryptoMetadataEntity(

View File

@ -16,9 +16,9 @@


package im.vector.matrix.android.internal.crypto.store.db.model package im.vector.matrix.android.internal.crypto.store.db.model


import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.annotations.LinkingObjects import io.realm.annotations.LinkingObjects

View File

@ -16,11 +16,11 @@


package im.vector.matrix.android.internal.crypto.store.db.model package im.vector.matrix.android.internal.crypto.store.db.model


import io.realm.RealmObject import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import io.realm.annotations.PrimaryKey
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper import io.realm.RealmObject
import io.realm.annotations.PrimaryKey


internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: String?, senderKey: String?) = "$sessionId|$senderKey" internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: String?, senderKey: String?) = "$sessionId|$senderKey"



View File

@ -16,10 +16,10 @@


package im.vector.matrix.android.internal.crypto.store.db.model package im.vector.matrix.android.internal.crypto.store.db.model


import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import org.matrix.olm.OlmSession import org.matrix.olm.OlmSession


internal fun OlmSessionEntity.Companion.createPrimaryKey(sessionId: String, deviceKey: String) = "$sessionId|$deviceKey" internal fun OlmSessionEntity.Companion.createPrimaryKey(sessionId: String, deviceKey: String) = "$sessionId|$deviceKey"

View File

@ -17,9 +17,9 @@
package im.vector.matrix.android.internal.crypto.store.db.model package im.vector.matrix.android.internal.crypto.store.db.model


import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey



View File

@ -16,11 +16,11 @@


package im.vector.matrix.android.internal.crypto.store.db.query package im.vector.matrix.android.internal.crypto.store.db.query


import io.realm.Realm
import io.realm.kotlin.where
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.createPrimaryKey import im.vector.matrix.android.internal.crypto.store.db.model.createPrimaryKey
import io.realm.Realm
import io.realm.kotlin.where


/** /**
* Get or create a device info * Get or create a device info

View File

@ -16,10 +16,10 @@


package im.vector.matrix.android.internal.crypto.store.db.query package im.vector.matrix.android.internal.crypto.store.db.query


import io.realm.Realm
import io.realm.kotlin.where
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntity import im.vector.matrix.android.internal.crypto.store.db.model.UserEntity
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields
import io.realm.Realm
import io.realm.kotlin.where


/** /**
* Get or create a user * Get or create a user

View File

@ -16,7 +16,6 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.MXKey import im.vector.matrix.android.internal.crypto.model.MXKey
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
@ -37,35 +36,30 @@ internal interface ClaimOneTimeKeysForUsersDeviceTask : Task<ClaimOneTimeKeysFor
internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(private val cryptoApi: CryptoApi)
: ClaimOneTimeKeysForUsersDeviceTask { : ClaimOneTimeKeysForUsersDeviceTask {


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


return executeRequest<KeysClaimResponse> { val keysClaimResponse = executeRequest<KeysClaimResponse> {
apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body) apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
}.flatMap { keysClaimResponse -> }
Try { val map = MXUsersDevicesMap<MXKey>()
val map = MXUsersDevicesMap<MXKey>() keysClaimResponse.oneTimeKeys?.let { oneTimeKeys ->
for (userId in oneTimeKeys.keys) {
val mapByUserId = oneTimeKeys[userId]


keysClaimResponse.oneTimeKeys?.let { oneTimeKeys -> if (mapByUserId != null) {
for (userId in oneTimeKeys.keys) { for (deviceId in mapByUserId.keys) {
val mapByUserId = oneTimeKeys[userId] val mxKey = MXKey.from(mapByUserId[deviceId])


if (mapByUserId != null) { if (mxKey != null) {
for (deviceId in mapByUserId.keys) { map.setObject(userId, deviceId, mxKey)
val mxKey = MXKey.from(mapByUserId[deviceId]) } else {

Timber.e("## claimOneTimeKeysForUsersDevices : fail to create a MXKey")
if (mxKey != null) {
map.setObject(userId, deviceId, mxKey)
} else {
Timber.e("## claimOneTimeKeysForUsersDevices : fail to create a MXKey")
}
}
} }
} }
} }

map
} }
} }
return map
} }
} }

View File

@ -16,16 +16,12 @@


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


import arrow.core.Try
import arrow.core.failure
import arrow.core.recoverWith
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


@ -38,10 +34,12 @@ internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
internal class DefaultDeleteDeviceTask @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultDeleteDeviceTask @Inject constructor(private val cryptoApi: CryptoApi)
: DeleteDeviceTask { : DeleteDeviceTask {


override suspend fun execute(params: DeleteDeviceTask.Params): Try<Unit> { override suspend fun execute(params: DeleteDeviceTask.Params) {
return executeRequest<Unit> { try {
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams()) executeRequest<Unit> {
}.recoverWith { throwable -> apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
}
} catch (throwable: Throwable) {
if (throwable is Failure.OtherServerError && throwable.httpCode == 401) { if (throwable is Failure.OtherServerError && throwable.httpCode == 401) {
// Parse to get a RegistrationFlowResponse // Parse to get a RegistrationFlowResponse
val registrationFlowResponse = try { val registrationFlowResponse = try {
@ -51,17 +49,16 @@ internal class DefaultDeleteDeviceTask @Inject constructor(private val cryptoApi
} catch (e: Exception) { } catch (e: Exception) {
null null
} }

// check if the server response can be casted // check if the server response can be casted
if (registrationFlowResponse != null) { if (registrationFlowResponse != null) {
Failure.RegistrationFlowError(registrationFlowResponse).failure() throw Failure.RegistrationFlowError(registrationFlowResponse)
} else { } else {
throwable.failure() throw throwable
} }


} else { } else {
// Other error // Other error
throwable.failure() throw throwable
} }
} }
} }

View File

@ -16,7 +16,6 @@


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


import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
@ -38,7 +37,7 @@ internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(priva
private val credentials: Credentials) private val credentials: Credentials)
: DeleteDeviceWithUserPasswordTask { : DeleteDeviceWithUserPasswordTask {


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

View File

@ -17,12 +17,10 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks


import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryBody import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryBody
import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryResponse import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryResponse
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -38,7 +36,7 @@ internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Para
internal class DefaultDownloadKeysForUsers @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultDownloadKeysForUsers @Inject constructor(private val cryptoApi: CryptoApi)
: DownloadKeysForUsersTask { : DownloadKeysForUsersTask {


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


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

View File

@ -16,11 +16,9 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


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


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

View File

@ -16,11 +16,9 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.KeyChangesResponse import im.vector.matrix.android.internal.crypto.model.rest.KeyChangesResponse
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


@ -36,10 +34,9 @@ internal interface GetKeyChangesTask : Task<GetKeyChangesTask.Params, KeyChanges
internal class DefaultGetKeyChangesTask @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultGetKeyChangesTask @Inject constructor(private val cryptoApi: CryptoApi)
: GetKeyChangesTask { : GetKeyChangesTask {


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

View File

@ -16,12 +16,10 @@


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


import arrow.core.Try
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
import kotlin.random.Random import kotlin.random.Random
@ -40,7 +38,7 @@ internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
internal class DefaultSendToDeviceTask @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultSendToDeviceTask @Inject constructor(private val cryptoApi: CryptoApi)
: SendToDeviceTask { : SendToDeviceTask {


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



View File

@ -17,11 +17,9 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks


import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.UpdateDeviceInfoBody import im.vector.matrix.android.internal.crypto.model.rest.UpdateDeviceInfoBody
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject


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


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

return executeRequest { return executeRequest {
apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body) apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body)
} }

View File

@ -16,14 +16,12 @@


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


import arrow.core.Try
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.DeviceKeys import im.vector.matrix.android.internal.crypto.model.rest.DeviceKeys
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadBody import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadBody
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.convertToUTF8 import im.vector.matrix.android.internal.util.convertToUTF8
import javax.inject.Inject import javax.inject.Inject
@ -41,7 +39,7 @@ internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadRespon
internal class DefaultUploadKeysTask @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultUploadKeysTask @Inject constructor(private val cryptoApi: CryptoApi)
: UploadKeysTask { : UploadKeysTask {


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


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

View File

@ -37,6 +37,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.*
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.TaskConstraints
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
@ -219,17 +220,20 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
startReq: KeyVerificationStart, startReq: KeyVerificationStart,
success: (MXUsersDevicesMap<MXDeviceInfo>) -> Unit, success: (MXUsersDevicesMap<MXDeviceInfo>) -> Unit,
error: () -> Unit) { error: () -> Unit) {
deviceListManager.downloadKeys(listOf(otherUserId), true) runCatching {
.fold( deviceListManager.downloadKeys(listOf(otherUserId), true)
{ error() }, }.fold(
{ {
if (it.getUserDeviceIds(otherUserId)?.contains(startReq.fromDevice) == true) { if (it.getUserDeviceIds(otherUserId)?.contains(startReq.fromDevice) == true) {
success(it) success(it)
} else { } else {
error() error()
} }
} },
) {
error()
}
)
} }


private suspend fun onCancelReceived(event: Event) { private suspend fun onCancelReceived(event: Event) {
@ -412,16 +416,18 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
val contentMap = MXUsersDevicesMap<Any>() val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(userId, userDevice, cancelMessage) contentMap.setObject(userId, userDevice, cancelMessage)


sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) sendToDeviceTask
.dispatchTo(object : MatrixCallback<Unit> { .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) {
override fun onSuccess(data: Unit) { this.callback = object : MatrixCallback<Unit> {
Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") override fun onSuccess(data: Unit) {
} Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}")
}


override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.") Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.")
}
} }
}) }
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
} }

View File

@ -287,23 +287,25 @@ internal abstract class SASVerificationTransaction(
val contentMap = MXUsersDevicesMap<Any>() val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(otherUserId, otherDeviceId, keyToDevice) contentMap.setObject(otherUserId, otherDeviceId, keyToDevice)


sendToDeviceTask.configureWith(SendToDeviceTask.Params(type, contentMap, transactionId)) sendToDeviceTask
.dispatchTo(object : MatrixCallback<Unit> { .configureWith(SendToDeviceTask.Params(type, contentMap, transactionId)) {
override fun onSuccess(data: Unit) { this.callback = object : MatrixCallback<Unit> {
Timber.v("## SAS verification [$transactionId] toDevice type '$type' success.") override fun onSuccess(data: Unit) {
if (onDone != null) { Timber.v("## SAS verification [$transactionId] toDevice type '$type' success.")
onDone() if (onDone != null) {
} else { onDone()
state = nextState } else {
state = nextState
}
}

override fun onFailure(failure: Throwable) {
Timber.e("## SAS verification [$transactionId] failed to send toDevice in state : $state")

cancel(onErrorReason)
} }
} }

}
override fun onFailure(failure: Throwable) {
Timber.e("## SAS verification [$transactionId] failed to send toDevice in state : $state")

cancel(onErrorReason)
}
})
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }



View File

@ -0,0 +1,39 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.database

import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext


suspend fun awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> Unit) = withContext(Dispatchers.IO) {
Realm.getInstance(config).use { bgRealm ->
bgRealm.beginTransaction()
try {
transaction(bgRealm)
if (isActive) {
bgRealm.commitTransaction()
}
} finally {
if (bgRealm.isInTransaction) {
bgRealm.cancelTransaction()
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More