forked from GitHub-Mirror/riotX-android
Compare commits
No commits in common. "develop" and "feature/room_members_perf" have entirely different histories.
develop
...
feature/ro
103
CHANGES.md
103
CHANGES.md
@ -1,106 +1,11 @@
|
||||
Changes in RiotX 0.5.0 (2019-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
-
|
||||
|
||||
Improvements:
|
||||
- Reduce default release build log level, and lab option to enable more logs.
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
||||
Bugfix:
|
||||
- Fix crash due to missing informationData (#535)
|
||||
- Progress in initial sync dialog is decreasing for a step and should not (#532)
|
||||
|
||||
Translations:
|
||||
-
|
||||
|
||||
Build:
|
||||
- Fix issue with version name (#533)
|
||||
- Fix rendering issue of accepted third party invitation event
|
||||
|
||||
Changes in RiotX 0.4.0 (2019-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Display read receipts in timeline (#81)
|
||||
|
||||
Improvements:
|
||||
- Reactions: Reinstate the ability to react with non-unicode keys (#307)
|
||||
|
||||
Bugfix:
|
||||
- Fix text diff linebreak display (#441)
|
||||
- Date change message repeats for each redaction until a normal message (#358)
|
||||
- Slide-in reply icon is distorted (#423)
|
||||
- Regression / e2e replies not encrypted
|
||||
- Some video won't play
|
||||
- Privacy: remove log of notifiable event (#519)
|
||||
- Fix crash with EmojiCompat (#530)
|
||||
|
||||
Changes in RiotX 0.3.0 (2019-08-08)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Create Direct Room flow
|
||||
- Handle `/markdown` command
|
||||
|
||||
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
|
||||
- Add "View Edit History" item in the message bottom sheet (#401)
|
||||
- Cancel sync request on pause and timeout to 0 after pause (#404)
|
||||
|
||||
Other changes:
|
||||
- Show sync progress also in room detail screen (#403)
|
||||
|
||||
Bugfix:
|
||||
- Edited message: link confusion when (edited) appears in body (#398)
|
||||
- Close detail room screen when the room is left with another client (#256)
|
||||
- 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)
|
||||
- Fix `(edited)` link can be copied to clipboard (#402)
|
||||
|
||||
Build:
|
||||
- Split APK: generate one APK per arch, to reduce APK size of about 30%
|
||||
|
||||
|
||||
Changes in RiotX 0.2.0 (2019-07-18)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Message Editing: View edit history (#121)
|
||||
- Rooms filtering (#304)
|
||||
- Edit in encrypted room
|
||||
|
||||
Improvements:
|
||||
- Handle click on redacted events: view source and create permalink
|
||||
- Improve long tap menu: reply on top, more compact (#368)
|
||||
- Quick reply in timeline with swipe gesture (#167)
|
||||
- Improve edit of replies
|
||||
- Improve performance on Room Members and Users management (#381)
|
||||
|
||||
Other changes:
|
||||
- migrate from rxbinding 2 to rxbinding 3
|
||||
|
||||
Bugfix:
|
||||
- Fix regression on permalink click
|
||||
- Fix crash reported by the PlayStore (#341)
|
||||
- Fix Chat composer separator color in dark/black theme
|
||||
- Fix bad layout for room directory filter (#349)
|
||||
- Fix Copying link from a message shouldn't open context menu (#364)
|
||||
|
||||
Changes in RiotX 0.1.0 (2019-07-11)
|
||||
===================================================
|
||||
|
||||
First release!
|
||||
|
||||
Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-android-b17952e8f771
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
=======================================================
|
||||
@ -108,7 +13,7 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a
|
||||
=======================================================
|
||||
|
||||
|
||||
Changes in RiotX 0.0.0 (2019-XX-XX)
|
||||
Changes in RiotX 0.XX (2019-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
|
29
README.md
29
README.md
@ -1,4 +1,4 @@
|
||||
[](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
[](https://buildkite.com/matrix-dot-org/riotx-android)
|
||||
[](https://translate.riot.im/engage/riot-android/?utm_source=widget)
|
||||
[](https://matrix.to/#/#riotx:matrix.org)
|
||||
[](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||
@ -7,31 +7,14 @@
|
||||
|
||||
# RiotX Android
|
||||
|
||||
RiotX is an Android Matrix Client currently in beta but in active development.
|
||||
RiotX is an Android Matrix Client currently in development. The application is not yet available on the PlayStore.
|
||||
|
||||
It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented.
|
||||
It's based on a new Matrix SDK, written in Kotlin.
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.riotx)
|
||||
|
||||
Nightly build: [](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
|
||||
# New Android SDK
|
||||
|
||||
RiotX is based on a new Android SDK fully written in Kotlin (like RiotX). In order to make the early development as fast as possible, RiotX and the new SDK currently share the same git repository. We will make separate repos once the API is stable enough.
|
||||
|
||||
|
||||
# Roadmap
|
||||
|
||||
The current target is to release an application out of beta with the same level of features (and even more) as Riot.
|
||||
The roadmap has 3 phases:
|
||||
|
||||
- [phase 0](https://github.com/vector-im/riotX-android/labels/phase0): Prototyping / Project setup
|
||||
- [phase 1](https://github.com/vector-im/riotX-android/labels/phase1): Beta release to the Play Store
|
||||
- [phase 2](https://github.com/vector-im/riotX-android/labels/phase2): Out of beta
|
||||
Download nightly build here: [](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
|
||||
Matrix Room: [](https://matrix.to/#/#riotx:matrix.org)
|
||||
|
||||
## Contributing
|
||||
|
||||
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute on Matrix Android projects!
|
||||
|
||||
Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/#riotx:matrix.org).
|
||||
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute the Matrix on Android projects!
|
||||
|
21
build.gradle
21
build.gradle
@ -1,5 +1,3 @@
|
||||
import javax.tools.JavaCompiler
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
@ -47,27 +45,8 @@ allprojects {
|
||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||
google()
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://repo.adobe.com/nexus/content/repositories/public/'
|
||||
content {
|
||||
includeGroupByRegex "diff_match_patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).all {
|
||||
options.compilerArgs += [
|
||||
'-Adagger.gradle.incremental=enabled'
|
||||
]
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
extensions.findByName("kapt")?.arguments {
|
||||
arg("dagger.gradle.incremental", "enabled")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
|
@ -33,12 +33,11 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation project(":matrix-sdk-android")
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
// Paging
|
||||
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
|
@ -20,8 +20,6 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.MainThreadDisposable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
private class LiveDataObservable<T>(
|
||||
private val liveData: LiveData<T>,
|
||||
@ -59,5 +57,5 @@ private class LiveDataObservable<T>(
|
||||
}
|
||||
|
||||
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||
return LiveDataObservable(this).observeOn(Schedulers.computation())
|
||||
return LiveDataObservable(this)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
@ -18,40 +18,27 @@ package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
class RxRoom(private val room: Room) {
|
||||
|
||||
fun liveRoomSummary(): Observable<RoomSummary> {
|
||||
return room.liveRoomSummary().asObservable()
|
||||
return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation())
|
||||
}
|
||||
|
||||
fun liveRoomMemberIds(): Observable<List<String>> {
|
||||
return room.getRoomMemberIdsLive().asObservable()
|
||||
return room.getRoomMemberIdsLive().asObservable().observeOn(Schedulers.computation())
|
||||
}
|
||||
|
||||
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
|
||||
return room.getEventSummaryLive(eventId).asObservable()
|
||||
return room.getEventSummaryLive(eventId).asObservable().observeOn(Schedulers.computation())
|
||||
}
|
||||
|
||||
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
|
||||
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)
|
||||
}
|
||||
|
||||
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
||||
return room.getEventReadReceiptsLive(eventId).asObservable()
|
||||
return room.liveTimeLineEvent(eventId).asObservable().observeOn(Schedulers.computation())
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,55 +16,30 @@
|
||||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import androidx.paging.PagedList
|
||||
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.pushers.Pusher
|
||||
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.user.model.User
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
class RxSession(private val session: Session) {
|
||||
|
||||
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
|
||||
return session.liveRoomSummaries().asObservable()
|
||||
return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation())
|
||||
}
|
||||
|
||||
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
||||
return session.liveGroupSummaries().asObservable()
|
||||
return session.liveGroupSummaries().asObservable().observeOn(Schedulers.computation())
|
||||
}
|
||||
|
||||
fun liveSyncState(): Observable<SyncState> {
|
||||
return session.syncState().asObservable()
|
||||
return session.syncState().asObservable().observeOn(Schedulers.computation())
|
||||
}
|
||||
|
||||
fun livePushers(): Observable<List<Pusher>> {
|
||||
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)
|
||||
return session.livePushers().asObservable().observeOn(Schedulers.computation())
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -94,6 +94,7 @@ dependencies {
|
||||
def markwon_version = '3.0.0'
|
||||
def daggerVersion = '2.23.1'
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
@ -109,7 +110,7 @@ dependencies {
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.14.1'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
|
||||
implementation 'com.novoda:merlin:1.2.0'
|
||||
implementation 'com.novoda:merlin:1.1.6'
|
||||
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
||||
|
||||
@ -125,6 +126,9 @@ dependencies {
|
||||
// FP
|
||||
implementation "io.arrow-kt:arrow-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
|
||||
implementation 'org.matrix.gitlab.matrix-org:olm:3.1.2'
|
||||
|
BIN
matrix-sdk-android/libs/react-native-webrtc.aar
Normal file
BIN
matrix-sdk-android/libs/react-native-webrtc.aar
Normal file
Binary file not shown.
@ -16,10 +16,10 @@
|
||||
|
||||
package im.vector.matrix.android;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -21,7 +21,7 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlin.random.Random
|
||||
import java.util.*
|
||||
|
||||
internal class CryptoStoreHelper {
|
||||
|
||||
@ -35,7 +35,7 @@ internal class CryptoStoreHelper {
|
||||
}
|
||||
|
||||
fun createCredential() = Credentials(
|
||||
userId = "userId_" + Random.nextInt(),
|
||||
userId = "userId_" + Random().nextInt(),
|
||||
homeServer = "http://matrix.org",
|
||||
accessToken = "access_token",
|
||||
refreshToken = null,
|
||||
|
@ -19,7 +19,11 @@ package im.vector.matrix.android.session.room.timeline
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.internal.database.helper.*
|
||||
import im.vector.matrix.android.internal.database.helper.add
|
||||
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.session.room.timeline.PaginationDirection
|
||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
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.PaginationDirection
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||
@ -23,7 +24,7 @@ import kotlin.random.Random
|
||||
|
||||
internal class FakeGetContextOfEventTask constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : GetContextOfEventTask {
|
||||
|
||||
override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result {
|
||||
override suspend fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> {
|
||||
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||
val tokenChunkEvent = FakeTokenChunkEvent(
|
||||
Random.nextLong(System.currentTimeMillis()).toString(),
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
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.TokenChunkEventPersistor
|
||||
import javax.inject.Inject
|
||||
@ -23,7 +24,7 @@ import kotlin.random.Random
|
||||
|
||||
internal class FakePaginationTask @Inject constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask {
|
||||
|
||||
override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result {
|
||||
override suspend fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> {
|
||||
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
|
||||
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
|
||||
|
@ -28,7 +28,7 @@ object MatrixPatterns {
|
||||
// regex pattern to find matrix user ids in a string.
|
||||
// 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"
|
||||
val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find room ids in a string.
|
||||
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"
|
||||
@ -137,23 +137,4 @@ object MatrixPatterns {
|
||||
fun isGroupId(str: String?): Boolean {
|
||||
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)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import okhttp3.TlsVersion
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class HomeServerConnectionConfig(
|
||||
val homeServerUri: Uri,
|
||||
val identityServerUri: Uri? = null,
|
||||
val identityServerUri: Uri,
|
||||
val antiVirusServerUri: Uri? = null,
|
||||
val allowedFingerprints: MutableList<Fingerprint> = ArrayList(),
|
||||
val shouldPin: Boolean = false,
|
||||
@ -48,7 +48,7 @@ data class HomeServerConnectionConfig(
|
||||
class Builder {
|
||||
|
||||
private lateinit var homeServerUri: Uri
|
||||
private var identityServerUri: Uri? = null
|
||||
private lateinit var identityServerUri: Uri
|
||||
private var antiVirusServerUri: Uri? = null
|
||||
private val allowedFingerprints: MutableList<Fingerprint> = ArrayList()
|
||||
private var shouldPin: Boolean = false
|
||||
|
@ -17,6 +17,7 @@
|
||||
package im.vector.matrix.android.api.comparators
|
||||
|
||||
import im.vector.matrix.android.api.interfaces.DatedObject
|
||||
import java.util.*
|
||||
|
||||
object DatedObjectComparators {
|
||||
|
||||
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.api.extensions
|
||||
import im.vector.matrix.android.api.comparators.DatedObjectComparators
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import java.util.Collections
|
||||
import java.util.*
|
||||
|
||||
/* ==========================================================================================
|
||||
* MXDeviceInfo
|
||||
|
@ -26,13 +26,8 @@ import com.squareup.moshi.JsonClass
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MatrixError(
|
||||
@Json(name = "errcode") val code: 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) {
|
||||
|
||||
@Json(name = "error") val message: String
|
||||
) {
|
||||
|
||||
companion object {
|
||||
const val FORBIDDEN = "M_FORBIDDEN"
|
||||
@ -60,8 +55,5 @@ data class MatrixError(
|
||||
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
||||
const val RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
|
||||
const val WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||
|
||||
// Possible value for "limit_type"
|
||||
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
||||
}
|
||||
}
|
@ -42,7 +42,7 @@ object MatrixLinkify {
|
||||
hasMatch = true
|
||||
val startPos = match.range.first
|
||||
if (startPos == 0 || text[startPos - 1] != '/') {
|
||||
val endPos = match.range.last + 1
|
||||
val endPos = match.range.last
|
||||
val url = text.substring(match.range)
|
||||
val span = MatrixPermalinkSpan(url, callback)
|
||||
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
@ -18,7 +18,6 @@ package im.vector.matrix.android.api.permalinks
|
||||
|
||||
import android.text.style.ClickableSpan
|
||||
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.
|
||||
|
@ -18,7 +18,6 @@ package im.vector.matrix.android.api.pushrules
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
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.util.Cancelable
|
||||
|
||||
interface PushRuleService {
|
||||
|
||||
@ -32,7 +31,7 @@ interface PushRuleService {
|
||||
|
||||
//TODO update rule
|
||||
|
||||
fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>)
|
||||
|
||||
fun addPushRuleListener(listener: PushRuleListener)
|
||||
|
||||
@ -42,7 +41,6 @@ interface PushRuleService {
|
||||
|
||||
interface PushRuleListener {
|
||||
fun onMatchRule(event: Event, actions: List<Action>)
|
||||
fun onRoomLeft(roomId: String)
|
||||
fun batchFinish()
|
||||
}
|
||||
}
|
@ -18,17 +18,18 @@ package im.vector.matrix.android.api.pushrules
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
import timber.log.Timber
|
||||
import java.util.regex.Pattern
|
||||
|
||||
private val regex = Regex("^(==|<=|>=|<|>)?(\\d*)$")
|
||||
private val regex = Pattern.compile("^(==|<=|>=|<|>)?(\\d*)$")
|
||||
|
||||
class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_count) {
|
||||
class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_count) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveRoomMemberCountCondition(this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
return "Room member count is $iz"
|
||||
return "Room member count is $`is`"
|
||||
}
|
||||
|
||||
fun isSatisfied(event: Event, session: RoomService?): Boolean {
|
||||
@ -55,9 +56,12 @@ class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_coun
|
||||
*/
|
||||
private fun parseIsField(): Pair<String?, Int>? {
|
||||
try {
|
||||
val match = regex.find(iz) ?: return null
|
||||
val (prefix, count) = match.destructured
|
||||
return prefix to count.toInt()
|
||||
val match = regex.matcher(`is`)
|
||||
if (match.find()) {
|
||||
val prefix = match.group(1)
|
||||
val count = match.group(2).toInt()
|
||||
return prefix to count
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Timber.d(t)
|
||||
}
|
||||
|
@ -20,10 +20,10 @@ import androidx.lifecycle.LiveData
|
||||
|
||||
interface InitialSyncProgressService {
|
||||
|
||||
fun getInitialSyncProgressStatus() : LiveData<Status?>
|
||||
fun getLiveStatus() : LiveData<Status?>
|
||||
|
||||
data class Status(
|
||||
@StringRes val statusText: Int,
|
||||
@StringRes val statusText: Int?,
|
||||
val percentProgress: Int = 0
|
||||
)
|
||||
}
|
@ -57,9 +57,6 @@ interface Session :
|
||||
*/
|
||||
val sessionParams: SessionParams
|
||||
|
||||
/**
|
||||
* Useful shortcut to get access to the userId
|
||||
*/
|
||||
val myUserId: String
|
||||
get() = sessionParams.credentials.userId
|
||||
|
||||
@ -87,7 +84,7 @@ interface Session :
|
||||
/**
|
||||
* This method start the sync thread.
|
||||
*/
|
||||
fun startSync(fromForeground : Boolean)
|
||||
fun startSync()
|
||||
|
||||
/**
|
||||
* This method stop the sync thread.
|
||||
|
@ -26,12 +26,14 @@ 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.internal.crypto.MXEventDecryptionResult
|
||||
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.MXDeviceInfo
|
||||
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.rest.DevicesListResponse
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
import java.io.File
|
||||
|
||||
interface CryptoService {
|
||||
|
||||
|
@ -20,9 +20,6 @@ import android.text.TextUtils
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
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.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
@ -82,15 +79,9 @@ data class Event(
|
||||
) {
|
||||
|
||||
|
||||
@Transient
|
||||
var mxDecryptionResult: OlmDecryptionResult? = null
|
||||
|
||||
@Transient
|
||||
var mCryptoError: MXCryptoError.ErrorType? = null
|
||||
|
||||
@Transient
|
||||
var sendState: SendState = SendState.UNKNOWN
|
||||
|
||||
|
||||
/**
|
||||
* Check if event is a state event.
|
||||
@ -104,6 +95,42 @@ data class Event(
|
||||
// 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.
|
||||
*/
|
||||
@ -111,11 +138,51 @@ data class Event(
|
||||
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.
|
||||
*/
|
||||
fun getSenderKey(): String? {
|
||||
return mxDecryptionResult?.senderKey
|
||||
// return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key
|
||||
}
|
||||
|
||||
/**
|
||||
@ -123,13 +190,23 @@ data class Event(
|
||||
*/
|
||||
fun getKeysClaimed(): Map<String, String> {
|
||||
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
|
||||
*/
|
||||
fun getClearType(): String {
|
||||
return mxDecryptionResult?.payload?.get("type")?.toString() ?: type
|
||||
return mxDecryptionResult?.payload?.get("type")?.toString()
|
||||
?: type//get("type")?.toString() ?: type
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,8 +216,30 @@ data class Event(
|
||||
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 {
|
||||
val contentMap = toContent()?.toMutableMap() ?: HashMap()
|
||||
val contentMap = this.toContent()?.toMutableMap() ?: HashMap()
|
||||
contentMap.remove("mxDecryptionResult")
|
||||
contentMap.remove("mCryptoError")
|
||||
return JSONObject(contentMap).toString(4)
|
||||
}
|
||||
|
||||
@ -173,7 +272,6 @@ data class Event(
|
||||
if (redacts != other.redacts) return false
|
||||
if (mxDecryptionResult != other.mxDecryptionResult) return false
|
||||
if (mCryptoError != other.mCryptoError) return false
|
||||
if (sendState != other.sendState) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@ -191,27 +289,6 @@ data class Event(
|
||||
result = 31 * result + (redacts?.hashCode() ?: 0)
|
||||
result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0)
|
||||
result = 31 * result + (mCryptoError?.hashCode() ?: 0)
|
||||
result = 31 * result + sendState.hashCode()
|
||||
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
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.events.model
|
||||
|
||||
|
||||
/**
|
||||
* Constants defining known event relation types from Matrix specifications
|
||||
* Constants defining known event relation types from Matrix specifications.
|
||||
*/
|
||||
object RelationType {
|
||||
|
||||
@ -25,7 +25,7 @@ object RelationType {
|
||||
const val ANNOTATION = "m.annotation"
|
||||
/** Lets you define an event which replaces an existing event.*/
|
||||
const val REPLACE = "m.replace"
|
||||
/** Lets you define an event which references an existing event.*/
|
||||
/** ets you define an event which references an existing event.*/
|
||||
const val REFERENCE = "m.reference"
|
||||
|
||||
}
|
@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.pushers
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import java.util.UUID
|
||||
import java.util.*
|
||||
|
||||
|
||||
interface PushersService {
|
||||
|
@ -30,17 +30,20 @@ interface RoomDirectoryService {
|
||||
/**
|
||||
* Get rooms from directory
|
||||
*/
|
||||
fun getPublicRooms(server: String?, publicRoomsParams: PublicRoomsParams, callback: MatrixCallback<PublicRoomsResponse>): Cancelable
|
||||
fun getPublicRooms(server: String?,
|
||||
publicRoomsParams: PublicRoomsParams,
|
||||
callback: MatrixCallback<PublicRoomsResponse>): Cancelable
|
||||
|
||||
/**
|
||||
* Join a room by id
|
||||
*/
|
||||
fun joinRoom(roomId: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
fun joinRoom(roomId: String,
|
||||
callback: MatrixCallback<Unit>)
|
||||
|
||||
/**
|
||||
* Fetches the overall metadata about protocols supported by the homeserver.
|
||||
* Includes both the available protocols and all fields required for queries against each protocol.
|
||||
*/
|
||||
fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>): Cancelable
|
||||
fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>)
|
||||
|
||||
}
|
@ -20,7 +20,6 @@ import androidx.lifecycle.LiveData
|
||||
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.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* This interface defines methods to get rooms. It's implemented at the session level.
|
||||
@ -28,18 +27,10 @@ import im.vector.matrix.android.api.util.Cancelable
|
||||
interface RoomService {
|
||||
|
||||
/**
|
||||
* Create a room asynchronously
|
||||
* Create a room
|
||||
*/
|
||||
fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable
|
||||
|
||||
/**
|
||||
* 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
|
||||
fun createRoom(createRoomParams: CreateRoomParams,
|
||||
callback: MatrixCallback<String>)
|
||||
|
||||
/**
|
||||
* Get a room from a roomId
|
||||
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
* 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()
|
||||
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
* 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()
|
||||
|
||||
}
|
@ -30,7 +30,7 @@ interface MembershipService {
|
||||
* This methods load all room members if it was done yet.
|
||||
* @return a [Cancelable]
|
||||
*/
|
||||
fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||
fun loadRoomMembersIfNeeded(): Cancelable
|
||||
|
||||
/**
|
||||
* Return the roomMember with userId or null.
|
||||
@ -52,17 +52,16 @@ interface MembershipService {
|
||||
/**
|
||||
* Invite a user in the room
|
||||
*/
|
||||
fun invite(userId: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
fun invite(userId: String, callback: MatrixCallback<Unit>)
|
||||
|
||||
/**
|
||||
* Join the room, or accept an invitation.
|
||||
*/
|
||||
|
||||
fun join(viaServers: List<String> = emptyList(), callback: MatrixCallback<Unit>): Cancelable
|
||||
fun join(callback: MatrixCallback<Unit>)
|
||||
|
||||
/**
|
||||
* Leave the room, or reject an invitation.
|
||||
*/
|
||||
fun leave(callback: MatrixCallback<Unit>): Cancelable
|
||||
fun leave(callback: MatrixCallback<Unit>)
|
||||
|
||||
}
|
@ -16,9 +16,8 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
|
||||
data class ReadReceipt(
|
||||
val user: User,
|
||||
val userId: String,
|
||||
val eventId: String,
|
||||
val originServerTs: Long
|
||||
)
|
@ -34,10 +34,5 @@ data class RoomSummary(
|
||||
val notificationCount: Int = 0,
|
||||
val highlightCount: Int = 0,
|
||||
val tags: List<RoomTag> = emptyList(),
|
||||
val membership: Membership = Membership.NONE,
|
||||
val versioningState: VersioningState = VersioningState.NONE
|
||||
) {
|
||||
|
||||
val isVersioned: Boolean
|
||||
get() = versioningState != VersioningState.NONE
|
||||
}
|
||||
val membership: Membership = Membership.NONE
|
||||
)
|
@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.room.model.PowerLevels
|
||||
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
|
||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Parameter to create a room, with facilities functions to configure it
|
||||
@ -132,7 +133,7 @@ class CreateRoomParams {
|
||||
)
|
||||
|
||||
if (null == initialStates) {
|
||||
initialStates = mutableListOf(algoEvent)
|
||||
initialStates = Arrays.asList<Event>(algoEvent)
|
||||
} else {
|
||||
initialStates!!.add(algoEvent)
|
||||
}
|
||||
@ -165,7 +166,7 @@ class CreateRoomParams {
|
||||
content = contentMap.toContent())
|
||||
|
||||
if (null == initialStates) {
|
||||
initialStates = mutableListOf(historyVisibilityEvent)
|
||||
initialStates = Arrays.asList<Event>(historyVisibilityEvent)
|
||||
} else {
|
||||
initialStates!!.add(historyVisibilityEvent)
|
||||
}
|
||||
@ -222,10 +223,11 @@ class CreateRoomParams {
|
||||
credentials: Credentials,
|
||||
ids: List<String>) {
|
||||
for (id in ids) {
|
||||
if (Patterns.EMAIL_ADDRESS.matcher(id).matches() && hsConfig.identityServerUri != null) {
|
||||
if (Patterns.EMAIL_ADDRESS.matcher(id).matches()) {
|
||||
if (null == invite3pids) {
|
||||
invite3pids = ArrayList()
|
||||
}
|
||||
|
||||
val pid = Invite3Pid(idServer = hsConfig.identityServerUri.host!!,
|
||||
medium = ThreePidMedium.EMAIL,
|
||||
address = id)
|
||||
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
)
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
)
|
||||
|
||||
|
@ -26,8 +26,3 @@ interface MessageContent {
|
||||
val relatesTo: RelationDefaultContent?
|
||||
val newContent: Content?
|
||||
}
|
||||
|
||||
|
||||
fun MessageContent?.isReply(): Boolean {
|
||||
return this?.relatesTo?.inReplyTo?.eventId != null
|
||||
}
|
||||
|
@ -16,10 +16,7 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.room.model.relation
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||
|
||||
interface RelationContent {
|
||||
/** See [RelationType] for known possible values */
|
||||
val type: String?
|
||||
val eventId: String?
|
||||
val inReplyTo: ReplyToContent?
|
||||
|
@ -16,8 +16,6 @@
|
||||
package im.vector.matrix.android.api.session.room.model.relation
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
@ -81,25 +79,6 @@ interface RelationService {
|
||||
compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
||||
|
||||
|
||||
/**
|
||||
* Edit a reply. This is a special case because replies contains fallback text as a prefix.
|
||||
* This method will take the new body (stripped from fallbacks) and re-add them before sending.
|
||||
* @param replyToEdit The event to edit
|
||||
* @param originalTimelineEvent the message that this reply (being edited) is relating to
|
||||
* @param newBodyText The edited body (stripped from in reply to content)
|
||||
* @param compatibilityBodyText The text that will appear on clients that don't support yet edition
|
||||
*/
|
||||
fun editReply(replyToEdit: TimelineEvent,
|
||||
originalTimelineEvent: TimelineEvent,
|
||||
newBodyText: String,
|
||||
compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
||||
|
||||
/**
|
||||
* Get's the edit history of the given event
|
||||
*/
|
||||
fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>)
|
||||
|
||||
|
||||
/**
|
||||
* Reply to an event in the timeline (must be in same room)
|
||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
|
||||
@ -112,6 +91,4 @@ interface RelationService {
|
||||
autoMarkdown: Boolean = false): Cancelable?
|
||||
|
||||
fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary>
|
||||
|
||||
|
||||
}
|
@ -21,5 +21,5 @@ import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ReplyToContent(
|
||||
@Json(name = "event_id") val eventId: String? = null
|
||||
@Json(name = "event_id") val eventId: String
|
||||
)
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
* 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?
|
||||
)
|
@ -16,9 +16,7 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.room.read
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||
|
||||
/**
|
||||
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
|
||||
@ -41,6 +39,4 @@ interface ReadService {
|
||||
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
|
||||
|
||||
fun isEventRead(eventId: String): Boolean
|
||||
|
||||
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
|
||||
}
|
@ -19,7 +19,6 @@ 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.events.model.Event
|
||||
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
|
||||
|
||||
|
||||
@ -66,31 +65,4 @@ interface SendService {
|
||||
*/
|
||||
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()
|
||||
|
||||
}
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.room.send
|
||||
|
||||
|
||||
enum class SendState {
|
||||
UNKNOWN,
|
||||
// the event has not been sent
|
||||
@ -34,19 +33,12 @@ enum class SendState {
|
||||
// the event failed to be sent because some unknown devices have been found while encrypting it
|
||||
FAILED_UNKNOWN_DEVICES;
|
||||
|
||||
internal companion object {
|
||||
val HAS_FAILED_STATES = listOf(UNDELIVERED, FAILED_UNKNOWN_DEVICES)
|
||||
val IS_SENT_STATES = listOf(SENT, SYNCED)
|
||||
val IS_SENDING_STATES = listOf(UNSENT, ENCRYPTING, SENDING)
|
||||
val PENDING_STATES = IS_SENDING_STATES + HAS_FAILED_STATES
|
||||
fun isSent(): Boolean {
|
||||
return this == SENT || this == SYNCED
|
||||
}
|
||||
|
||||
fun isSent() = IS_SENT_STATES.contains(this)
|
||||
|
||||
fun hasFailed() = HAS_FAILED_STATES.contains(this)
|
||||
|
||||
fun isSending() = IS_SENDING_STATES.contains(this)
|
||||
|
||||
fun hasFailed(): Boolean {
|
||||
return this == UNDELIVERED || this == FAILED_UNKNOWN_DEVICES
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
package im.vector.matrix.android.api.session.room.state
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
|
||||
interface StateService {
|
||||
|
||||
@ -26,6 +25,4 @@ interface StateService {
|
||||
*/
|
||||
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
|
||||
|
||||
fun getStateEvent(eventType: String): Event?
|
||||
|
||||
}
|
@ -56,9 +56,6 @@ interface Timeline {
|
||||
*/
|
||||
fun paginate(direction: Direction, count: Int)
|
||||
|
||||
fun pendingEventCount() : Int
|
||||
|
||||
fun failedToDeliverEventCount() : Int
|
||||
|
||||
interface Listener {
|
||||
/**
|
||||
|
@ -20,11 +20,8 @@ import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||
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.util.ContentUtils.extractUsefulTextFromReply
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
|
||||
/**
|
||||
* This data class is a wrapper around an Event. It allows to get useful data in the context of a timeline.
|
||||
@ -38,8 +35,8 @@ data class TimelineEvent(
|
||||
val senderName: String?,
|
||||
val isUniqueDisplayName: Boolean,
|
||||
val senderAvatar: String?,
|
||||
val annotations: EventAnnotationsSummary? = null,
|
||||
val readReceipts: List<ReadReceipt> = emptyList()
|
||||
val sendState: SendState,
|
||||
val annotations: EventAnnotationsSummary? = null
|
||||
) {
|
||||
|
||||
val metadata = HashMap<String, Any>()
|
||||
@ -86,26 +83,8 @@ data class TimelineEvent(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tells if the event has been edited
|
||||
*/
|
||||
fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null
|
||||
|
||||
/**
|
||||
* Get last MessageContent, after a possible edition
|
||||
*/
|
||||
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
|
||||
?: root.getClearContent().toModel()
|
||||
|
||||
|
||||
fun TimelineEvent.getTextEditableContent(): String? {
|
||||
val originalContent = root.getClearContent().toModel<MessageContent>() ?: return null
|
||||
val isReply = originalContent.isReply() || root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId != null
|
||||
val lastContent = getLastMessageContent()
|
||||
return if (isReply) {
|
||||
return extractUsefulTextFromReply(lastContent?.body ?: "")
|
||||
} else {
|
||||
lastContent?.body ?: ""
|
||||
}
|
||||
}
|
||||
|
@ -25,12 +25,12 @@ interface TimelineService {
|
||||
|
||||
/**
|
||||
* Instantiate a [Timeline] with an optional initial eventId, to be used with permalink.
|
||||
* You can also configure some settings with the [settings] param.
|
||||
* You can filter the type you want to grab with the allowedTypes param.
|
||||
* @param eventId the optional initial eventId.
|
||||
* @param settings settings to configure the timeline.
|
||||
* @param allowedTypes the optional filter types
|
||||
* @return the instantiated timeline
|
||||
*/
|
||||
fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline
|
||||
fun createTimeline(eventId: String?, allowedTypes: List<String>? = null): Timeline
|
||||
|
||||
|
||||
fun getTimeLineEvent(eventId: String): TimelineEvent?
|
||||
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* 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.timeline
|
||||
|
||||
/**
|
||||
* Data class holding setting values for a [Timeline] instance.
|
||||
*/
|
||||
data class TimelineSettings(
|
||||
/**
|
||||
* The initial number of events to retrieve from cache. You might get less events if you don't have loaded enough yet.
|
||||
*/
|
||||
val initialSize: Int,
|
||||
/**
|
||||
* A flag to filter edit events
|
||||
*/
|
||||
val filterEdits: Boolean = false,
|
||||
/**
|
||||
* A flag to filter by types. It should be used with [allowedTypes] field
|
||||
*/
|
||||
val filterTypes: Boolean = false,
|
||||
/**
|
||||
* If [filterTypes] is true, the list of types allowed by the list.
|
||||
*/
|
||||
val allowedTypes: List<String> = emptyList(),
|
||||
/**
|
||||
* If true, will build read receipts for each event.
|
||||
*/
|
||||
val buildReadReceipts: Boolean = true
|
||||
|
||||
)
|
@ -18,7 +18,7 @@ package im.vector.matrix.android.api.session.sync
|
||||
|
||||
sealed class SyncState {
|
||||
object IDLE : SyncState()
|
||||
data class RUNNING(val afterPause: Boolean) : SyncState()
|
||||
data class RUNNING(val catchingUp: Boolean) : SyncState()
|
||||
object PAUSED : SyncState()
|
||||
object KILLING : SyncState()
|
||||
object KILLED : SyncState()
|
||||
|
@ -17,10 +17,7 @@
|
||||
package im.vector.matrix.android.api.session.user
|
||||
|
||||
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.util.Cancelable
|
||||
|
||||
/**
|
||||
* This interface defines methods to get users. It's implemented at the session level.
|
||||
@ -34,34 +31,11 @@ interface UserService {
|
||||
*/
|
||||
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
|
||||
* @param userId the userId to look for.
|
||||
* @return a Livedata of user with userId
|
||||
*/
|
||||
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>>
|
||||
fun observeUser(userId: String): LiveData<User?>
|
||||
|
||||
}
|
@ -19,6 +19,5 @@ package im.vector.matrix.android.api.util
|
||||
class CancelableBag : Cancelable, MutableList<Cancelable> by ArrayList() {
|
||||
override fun cancel() {
|
||||
forEach { it.cancel() }
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
|
||||
object ContentUtils {
|
||||
fun extractUsefulTextFromReply(repliedBody: String): String {
|
||||
val lines = repliedBody.lines()
|
||||
var wellFormed = repliedBody.startsWith(">")
|
||||
var endOfPreviousFound = false
|
||||
val usefullines = ArrayList<String>()
|
||||
lines.forEach {
|
||||
if (it == "") {
|
||||
endOfPreviousFound = true
|
||||
return@forEach
|
||||
}
|
||||
if (!endOfPreviousFound) {
|
||||
wellFormed = wellFormed && it.startsWith(">")
|
||||
} else {
|
||||
usefullines.add(it)
|
||||
}
|
||||
}
|
||||
return usefullines.joinToString("\n").takeIf { wellFormed } ?: repliedBody
|
||||
}
|
||||
|
||||
fun extractUsefulTextFromHtmlReply(repliedBody: String): String {
|
||||
if (repliedBody.startsWith("<mx-reply>")) {
|
||||
val closingTagIndex = repliedBody.lastIndexOf("</mx-reply>")
|
||||
if (closingTagIndex != -1)
|
||||
return repliedBody.substring(closingTagIndex + "</mx-reply>".length).trim()
|
||||
}
|
||||
return repliedBody
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ import java.math.BigInteger
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.KeyStore
|
||||
import java.security.SecureRandom
|
||||
import java.util.Calendar
|
||||
import java.util.*
|
||||
import javax.crypto.*
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
@ -479,7 +479,12 @@ object SecretStoringUtils {
|
||||
val output = Cipher.getInstance(RSA_MODE)
|
||||
output.init(Cipher.DECRYPT_MODE, privateKeyEntry.privateKey)
|
||||
|
||||
return CipherInputStream(encrypted, output).use { it.readBytes() }
|
||||
val bos = ByteArrayOutputStream()
|
||||
CipherInputStream(encrypted, output).use {
|
||||
it.copyTo(bos)
|
||||
}
|
||||
|
||||
return bos.toByteArray()
|
||||
}
|
||||
|
||||
private fun formatMExtract(bis: InputStream): Pair<ByteArray, ByteArray> {
|
||||
@ -490,7 +495,14 @@ object SecretStoringUtils {
|
||||
val iv = ByteArray(ivSize)
|
||||
bis.read(iv, 0, ivSize)
|
||||
|
||||
val encrypted = bis.readBytes()
|
||||
|
||||
val bos = ByteArrayOutputStream()
|
||||
var next = bis.read()
|
||||
while (next != -1) {
|
||||
bos.write(next)
|
||||
next = bis.read()
|
||||
}
|
||||
val encrypted = bos.toByteArray()
|
||||
return Pair(iv, encrypted)
|
||||
}
|
||||
|
||||
@ -518,7 +530,14 @@ object SecretStoringUtils {
|
||||
val iv = ByteArray(ivSize)
|
||||
bis.read(iv)
|
||||
|
||||
val encrypted = bis.readBytes()
|
||||
val bos = ByteArrayOutputStream()
|
||||
|
||||
var next = bis.read()
|
||||
while (next != -1) {
|
||||
bos.write(next)
|
||||
next = bis.read()
|
||||
}
|
||||
val encrypted = bos.toByteArray()
|
||||
return Triple(encryptedKey, iv, encrypted)
|
||||
}
|
||||
|
||||
@ -560,7 +579,14 @@ object SecretStoringUtils {
|
||||
val iv = ByteArray(ivSize)
|
||||
bis.read(iv)
|
||||
|
||||
val encrypted = bis.readBytes()
|
||||
val bos = ByteArrayOutputStream()
|
||||
|
||||
var next = bis.read()
|
||||
while (next != -1) {
|
||||
bos.write(next)
|
||||
next = bis.read()
|
||||
}
|
||||
val encrypted = bos.toByteArray()
|
||||
return Triple(salt, iv, encrypted)
|
||||
}
|
||||
}
|
@ -50,10 +50,16 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M
|
||||
}
|
||||
|
||||
private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
|
||||
return sessionComponents.getOrPut(sessionParams.credentials.userId) {
|
||||
DaggerSessionComponent
|
||||
val userId = sessionParams.credentials.userId
|
||||
if (sessionComponents.containsKey(userId)) {
|
||||
return sessionComponents[userId]!!
|
||||
}
|
||||
return DaggerSessionComponent
|
||||
.factory()
|
||||
.create(matrixComponent, sessionParams)
|
||||
.also {
|
||||
sessionComponents[sessionParams.credentials.userId] = it
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -68,9 +68,7 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
|
||||
callback: MatrixCallback<Session>): Cancelable {
|
||||
|
||||
val job = GlobalScope.launch(coroutineDispatchers.main) {
|
||||
val sessionOrFailure = runCatching {
|
||||
authenticate(homeServerConnectionConfig, login, password)
|
||||
}
|
||||
val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password)
|
||||
sessionOrFailure.foldToCallback(callback)
|
||||
}
|
||||
return CancelableCoroutine(job)
|
||||
@ -87,12 +85,16 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
|
||||
} else {
|
||||
PasswordLoginParams.userIdentifier(login, password, "Mobile")
|
||||
}
|
||||
val credentials = executeRequest<Credentials> {
|
||||
executeRequest<Credentials> {
|
||||
apiCall = authAPI.login(loginParams)
|
||||
}
|
||||
val sessionParams = SessionParams(credentials, homeServerConnectionConfig)
|
||||
}.map {
|
||||
val sessionParams = SessionParams(it, homeServerConnectionConfig)
|
||||
sessionParamsStore.save(sessionParams)
|
||||
sessionManager.getOrCreateSession(sessionParams)
|
||||
sessionParams
|
||||
}.map {
|
||||
sessionManager.getOrCreateSession(it)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
|
||||
|
@ -27,7 +27,7 @@ internal interface SessionParamsStore {
|
||||
|
||||
fun getAll(): List<SessionParams>
|
||||
|
||||
fun save(sessionParams: SessionParams): Try<Unit>
|
||||
fun save(sessionParams: SessionParams): Try<SessionParams>
|
||||
|
||||
fun delete(userId: String): Try<Unit>
|
||||
|
||||
|
@ -62,7 +62,7 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
|
||||
return sessionParams
|
||||
}
|
||||
|
||||
override fun save(sessionParams: SessionParams): Try<Unit> {
|
||||
override fun save(sessionParams: SessionParams): Try<SessionParams> {
|
||||
return Try {
|
||||
val entity = mapper.map(sessionParams)
|
||||
if (entity != null) {
|
||||
@ -72,6 +72,7 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
|
||||
}
|
||||
realm.close()
|
||||
}
|
||||
sessionParams
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import com.squareup.moshi.Moshi
|
||||
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.SessionParams
|
||||
import im.vector.matrix.android.internal.di.MatrixScope
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
||||
|
@ -21,6 +21,7 @@ package im.vector.matrix.android.internal.crypto
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.TextUtils
|
||||
import arrow.core.Try
|
||||
import com.squareup.moshi.Types
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
@ -72,15 +73,17 @@ 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.task.TaskExecutor
|
||||
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.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.util.fetchCopied
|
||||
import kotlinx.coroutines.*
|
||||
import org.matrix.olm.OlmManager
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.max
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
/**
|
||||
* A `CryptoService` class instance manages the end-to-end crypto for a session.
|
||||
@ -93,7 +96,7 @@ import kotlin.math.max
|
||||
* Specially, it tracks all room membership changes events in order to do keys updates.
|
||||
*/
|
||||
@SessionScope
|
||||
internal class DefaultCryptoService @Inject constructor(
|
||||
internal class CryptoManager @Inject constructor(
|
||||
// Olm Manager
|
||||
private val olmManager: OlmManager,
|
||||
// The credentials,
|
||||
@ -166,25 +169,22 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
|
||||
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
||||
setDeviceNameTask
|
||||
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName))
|
||||
.dispatchTo(callback)
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
||||
deleteDeviceTask
|
||||
.configureWith(DeleteDeviceTask.Params(deviceId)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.configureWith(DeleteDeviceTask.Params(deviceId))
|
||||
.dispatchTo(callback)
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>) {
|
||||
deleteDeviceWithUserPasswordTask
|
||||
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password))
|
||||
.dispatchTo(callback)
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
@ -198,9 +198,8 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
|
||||
override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
||||
getDevicesTask
|
||||
.configureWith {
|
||||
this.callback = callback
|
||||
}
|
||||
.toConfigurableTask()
|
||||
.dispatchTo(callback)
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
@ -249,7 +248,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
return
|
||||
}
|
||||
isStarting.set(true)
|
||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
||||
internalStart(isInitialSync)
|
||||
}
|
||||
}
|
||||
@ -257,9 +256,16 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
private suspend fun internalStart(isInitialSync: Boolean) {
|
||||
// Open the store
|
||||
cryptoStore.open()
|
||||
runCatching {
|
||||
uploadDeviceKeys()
|
||||
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
||||
.flatMap { oneTimeKeysUploader.maybeUploadOneTimeKeys() }
|
||||
.fold(
|
||||
{
|
||||
Timber.e("Start failed: $it")
|
||||
delay(1000)
|
||||
isStarting.set(false)
|
||||
internalStart(isInitialSync)
|
||||
},
|
||||
{
|
||||
outgoingRoomKeyRequestManager.start()
|
||||
keysBackup.checkAndStartKeysBackup()
|
||||
if (isInitialSync) {
|
||||
@ -269,16 +275,8 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
} else {
|
||||
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
|
||||
}
|
||||
}.fold(
|
||||
{
|
||||
isStarting.set(false)
|
||||
isStarted.set(true)
|
||||
},
|
||||
{
|
||||
Timber.e("Start failed: $it")
|
||||
delay(1000)
|
||||
isStarting.set(false)
|
||||
internalStart(isInitialSync)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -286,7 +284,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
/**
|
||||
* Close the crypto
|
||||
*/
|
||||
fun close() = runBlocking(coroutineDispatchers.crypto) {
|
||||
fun close() {
|
||||
olmDevice.release()
|
||||
cryptoStore.close()
|
||||
outgoingRoomKeyRequestManager.stop()
|
||||
@ -317,7 +315,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
* @param syncResponse the syncResponse
|
||||
*/
|
||||
fun onSyncCompleted(syncResponse: SyncResponse) {
|
||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
||||
if (syncResponse.deviceLists != null) {
|
||||
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
|
||||
}
|
||||
@ -342,7 +340,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
* @return the device info, or null if not found / unsupported algorithm / crypto released
|
||||
*/
|
||||
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): MXDeviceInfo? {
|
||||
return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) {
|
||||
return if (!TextUtils.equals(algorithm, MXCRYPTO_ALGORITHM_MEGOLM) && !TextUtils.equals(algorithm, MXCRYPTO_ALGORITHM_OLM)) {
|
||||
// We only deal in olm keys
|
||||
null
|
||||
} else cryptoStore.deviceWithIdentityKey(senderKey)
|
||||
@ -355,8 +353,8 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
* @param deviceId the device id
|
||||
*/
|
||||
override fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo? {
|
||||
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
|
||||
cryptoStore.getUserDevice(deviceId, userId)
|
||||
return if (!TextUtils.isEmpty(userId) && !TextUtils.isEmpty(deviceId)) {
|
||||
cryptoStore.getUserDevice(deviceId!!, userId)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@ -441,7 +439,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
// (for now at least. Maybe we should alert the user somehow?)
|
||||
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
|
||||
|
||||
if (!existingAlgorithm.isNullOrEmpty() && existingAlgorithm != algorithm) {
|
||||
if (!TextUtils.isEmpty(existingAlgorithm) && !TextUtils.equals(existingAlgorithm, algorithm)) {
|
||||
Timber.e("## setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
|
||||
return false
|
||||
}
|
||||
@ -537,7 +535,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
eventType: String,
|
||||
roomId: String,
|
||||
callback: MatrixCallback<MXEncryptEventContentResult>) {
|
||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
||||
if (!isStarted()) {
|
||||
Timber.v("## encryptEventContent() : wait after e2e init")
|
||||
internalStart(false)
|
||||
@ -560,16 +558,13 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
if (safeAlgorithm != null) {
|
||||
val t0 = System.currentTimeMillis()
|
||||
Timber.v("## encryptEventContent() starts")
|
||||
runCatching {
|
||||
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
|
||||
}
|
||||
.fold(
|
||||
{ callback.onFailure(it) },
|
||||
{
|
||||
Timber.v("## encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||
callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED))
|
||||
},
|
||||
{ callback.onFailure(it) }
|
||||
|
||||
}
|
||||
)
|
||||
} else {
|
||||
val algorithm = getEncryptionAlgorithm(roomId)
|
||||
@ -591,7 +586,10 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
@Throws(MXCryptoError::class)
|
||||
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||
return runBlocking {
|
||||
internalDecryptEvent(event, timeline)
|
||||
internalDecryptEvent(event, timeline).fold(
|
||||
{ throw it },
|
||||
{ it }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -603,12 +601,10 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
* @param callback the callback to return data or null
|
||||
*/
|
||||
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
|
||||
GlobalScope.launch {
|
||||
val result = runCatching {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
GlobalScope.launch(EmptyCoroutineContext) {
|
||||
val result = withContext(coroutineDispatchers.crypto) {
|
||||
internalDecryptEvent(event, timeline)
|
||||
}
|
||||
}
|
||||
result.foldToCallback(callback)
|
||||
}
|
||||
}
|
||||
@ -618,22 +614,22 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
*
|
||||
* @param event the raw event.
|
||||
* @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
|
||||
* @return the MXEventDecryptionResult data, or null in case of error wrapped into [Try]
|
||||
*/
|
||||
private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||
private suspend fun internalDecryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
|
||||
val eventContent = event.content
|
||||
if (eventContent == null) {
|
||||
return if (eventContent == null) {
|
||||
Timber.e("## decryptEvent : empty event content")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
|
||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
|
||||
} else {
|
||||
val algorithm = eventContent["algorithm"]?.toString()
|
||||
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
|
||||
if (alg == null) {
|
||||
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
|
||||
Timber.e("## decryptEvent() : $reason")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason))
|
||||
} else {
|
||||
return alg.decryptEvent(event, timeline)
|
||||
alg.decryptEvent(event, timeline)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -653,7 +649,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
* @param event the event
|
||||
*/
|
||||
fun onToDeviceEvent(event: Event) {
|
||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
||||
when (event.getClearType()) {
|
||||
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
|
||||
onRoomKeyEvent(event)
|
||||
@ -675,7 +671,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
*/
|
||||
private fun onRoomKeyEvent(event: Event) {
|
||||
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
|
||||
if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) {
|
||||
if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.algorithm)) {
|
||||
Timber.e("## onRoomKeyEvent() : missing fields")
|
||||
return
|
||||
}
|
||||
@ -693,14 +689,13 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
* @param event the encryption event.
|
||||
*/
|
||||
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
|
||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
||||
val params = LoadRoomMembersTask.Params(roomId)
|
||||
try {
|
||||
loadRoomMembersTask.execute(params)
|
||||
loadRoomMembersTask
|
||||
.execute(params)
|
||||
.map { _ ->
|
||||
val userIds = getRoomUserIds(roomId)
|
||||
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
|
||||
} catch (throwable: Throwable) {
|
||||
Timber.e(throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -743,7 +738,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
val membership = roomMember?.membership
|
||||
if (membership == Membership.JOIN) {
|
||||
// make sure we are tracking the deviceList for this user.
|
||||
deviceListManager.startTrackingDeviceList(listOf(userId))
|
||||
deviceListManager.startTrackingDeviceList(Arrays.asList(userId))
|
||||
} else if (membership == Membership.INVITE
|
||||
&& shouldEncryptForInvitedMembers(roomId)
|
||||
&& cryptoConfig.enableEncryptionForInvitedMembers) {
|
||||
@ -752,7 +747,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
// know what other servers are in the room at the time they've been invited.
|
||||
// They therefore will not send device updates if a user logs in whilst
|
||||
// their state is invite.
|
||||
deviceListManager.startTrackingDeviceList(listOf(userId))
|
||||
deviceListManager.startTrackingDeviceList(Arrays.asList(userId))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -768,7 +763,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
/**
|
||||
* Upload my user's device keys.
|
||||
*/
|
||||
private suspend fun uploadDeviceKeys(): KeysUploadResponse {
|
||||
private suspend fun uploadDeviceKeys(): Try<KeysUploadResponse> {
|
||||
// Prepare the device keys data to send
|
||||
// Sign it
|
||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
|
||||
@ -787,11 +782,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
* @param callback the exported keys
|
||||
*/
|
||||
override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) {
|
||||
GlobalScope.launch(coroutineDispatchers.main) {
|
||||
runCatching {
|
||||
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
|
||||
}.fold(callback::onSuccess, callback::onFailure)
|
||||
}
|
||||
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -801,17 +792,31 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
* @param anIterationCount the encryption iteration count (0 means no encryption)
|
||||
* @param callback the exported keys
|
||||
*/
|
||||
private suspend fun exportRoomKeys(password: String, anIterationCount: Int): ByteArray {
|
||||
return withContext(coroutineDispatchers.crypto) {
|
||||
val iterationCount = max(0, anIterationCount)
|
||||
private fun exportRoomKeys(password: String, anIterationCount: Int, callback: MatrixCallback<ByteArray>) {
|
||||
GlobalScope.launch(coroutineDispatchers.main) {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
Try {
|
||||
val iterationCount = Math.max(0, anIterationCount)
|
||||
|
||||
val exportedSessions = cryptoStore.getInboundGroupSessions().mapNotNull { it.exportKeys() }
|
||||
val exportedSessions = ArrayList<MegolmSessionData>()
|
||||
|
||||
val inboundGroupSessions = cryptoStore.getInboundGroupSessions()
|
||||
|
||||
for (session in inboundGroupSessions) {
|
||||
val megolmSessionData = session.exportKeys()
|
||||
|
||||
if (null != megolmSessionData) {
|
||||
exportedSessions.add(megolmSessionData)
|
||||
}
|
||||
}
|
||||
|
||||
val adapter = MoshiProvider.providesMoshi()
|
||||
.adapter(List::class.java)
|
||||
|
||||
MXMegolmExportEncryption.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount)
|
||||
}
|
||||
}.foldToCallback(callback)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -874,9 +879,11 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
*/
|
||||
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
|
||||
// force the refresh to ensure that the devices list is up-to-date
|
||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||
runCatching { deviceListManager.downloadKeys(userIds, true) }
|
||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
||||
deviceListManager
|
||||
.downloadKeys(userIds, true)
|
||||
.fold(
|
||||
{ callback.onFailure(it) },
|
||||
{
|
||||
val unknownDevices = getUnknownDevices(it)
|
||||
if (unknownDevices.map.isEmpty()) {
|
||||
@ -885,8 +892,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
// trigger an an unknown devices exception
|
||||
callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices)))
|
||||
}
|
||||
},
|
||||
{ callback.onFailure(it) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -938,7 +944,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
val roomIds = cryptoStore.getRoomsListBlacklistUnverifiedDevices().toMutableList()
|
||||
|
||||
if (add) {
|
||||
if (roomId !in roomIds) {
|
||||
if (!roomIds.contains(roomId)) {
|
||||
roomIds.add(roomId)
|
||||
}
|
||||
} else {
|
||||
@ -1027,7 +1033,8 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
|
||||
val userIds = devicesInRoom.userIds
|
||||
for (userId in userIds) {
|
||||
devicesInRoom.getUserDeviceIds(userId)?.forEach { deviceId ->
|
||||
val deviceIds = devicesInRoom.getUserDeviceIds(userId)
|
||||
deviceIds?.forEach { deviceId ->
|
||||
devicesInRoom.getObject(userId, deviceId)
|
||||
?.takeIf { it.isUnknown }
|
||||
?.let {
|
||||
@ -1040,18 +1047,17 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
}
|
||||
|
||||
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
|
||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||
runCatching {
|
||||
deviceListManager.downloadKeys(userIds, forceDownload)
|
||||
}.foldToCallback(callback)
|
||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
||||
deviceListManager
|
||||
.downloadKeys(userIds, forceDownload)
|
||||
.foldToCallback(callback)
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearCryptoCache(callback: MatrixCallback<Unit>) {
|
||||
clearCryptoDataTask
|
||||
.configureWith {
|
||||
this.callback = callback
|
||||
}
|
||||
.toConfigurableTask()
|
||||
.dispatchTo(callback)
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
@ -1067,6 +1073,6 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
* ========================================================================================== */
|
||||
|
||||
override fun toString(): String {
|
||||
return "DefaultCryptoService of " + credentials.userId + " (" + credentials.deviceId + ")"
|
||||
return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")"
|
||||
}
|
||||
}
|
@ -105,7 +105,7 @@ internal abstract class CryptoModule {
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindCryptoService(cryptoService: DefaultCryptoService): CryptoService
|
||||
abstract fun bindCryptoService(cryptoManager: CryptoManager): CryptoService
|
||||
|
||||
@Binds
|
||||
abstract fun bindDeleteDeviceTask(deleteDeviceTask: DefaultDeleteDeviceTask): DeleteDeviceTask
|
||||
|
@ -18,12 +18,14 @@
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import android.text.TextUtils
|
||||
import arrow.core.Try
|
||||
import im.vector.matrix.android.api.MatrixPatterns
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
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.sync.SyncTokenStore
|
||||
import timber.log.Timber
|
||||
@ -235,7 +237,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
* @param forceDownload Always download the keys even if cached.
|
||||
* @param callback the asynchronous callback
|
||||
*/
|
||||
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): MXUsersDevicesMap<MXDeviceInfo> {
|
||||
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): Try<MXUsersDevicesMap<MXDeviceInfo>> {
|
||||
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")
|
||||
// Map from userId -> deviceId -> MXDeviceInfo
|
||||
val stored = MXUsersDevicesMap<MXDeviceInfo>()
|
||||
@ -266,14 +268,15 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
}
|
||||
return if (downloadUsers.isEmpty()) {
|
||||
Timber.v("## downloadKeys() : no new user device")
|
||||
stored
|
||||
Try.just(stored)
|
||||
} else {
|
||||
Timber.v("## downloadKeys() : starts")
|
||||
val t0 = System.currentTimeMillis()
|
||||
val result = doKeyDownloadForUsers(downloadUsers)
|
||||
doKeyDownloadForUsers(downloadUsers)
|
||||
.map {
|
||||
Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms")
|
||||
result.also {
|
||||
it.addEntriesFromMap(stored)
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -283,22 +286,17 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
*
|
||||
* @param downloadUsers the user ids list
|
||||
*/
|
||||
private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): MXUsersDevicesMap<MXDeviceInfo> {
|
||||
private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): Try<MXUsersDevicesMap<MXDeviceInfo>> {
|
||||
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
|
||||
// get the user ids which did not already trigger a keys download
|
||||
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
|
||||
if (filteredUsers.isEmpty()) {
|
||||
// trigger nothing
|
||||
return MXUsersDevicesMap()
|
||||
return Try.just(MXUsersDevicesMap())
|
||||
}
|
||||
val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken())
|
||||
val response = try {
|
||||
downloadKeysForUsersTask.execute(params)
|
||||
} catch (throwable: Throwable) {
|
||||
Timber.e(throwable, "##doKeyDownloadForUsers(): error")
|
||||
onKeysDownloadFailed(filteredUsers)
|
||||
throw throwable
|
||||
}
|
||||
return downloadKeysForUsersTask.execute(params)
|
||||
.map { response ->
|
||||
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
|
||||
for (userId in filteredUsers) {
|
||||
val devices = response.deviceKeys?.get(userId)
|
||||
@ -336,7 +334,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
cryptoStore.storeUserDevices(userId, mutableDevices)
|
||||
}
|
||||
}
|
||||
return onKeysDownloadSucceed(filteredUsers, response.failures)
|
||||
onKeysDownloadSucceed(filteredUsers, response.failures)
|
||||
}
|
||||
.onError {
|
||||
Timber.e(it, "##doKeyDownloadForUsers(): error")
|
||||
onKeysDownloadFailed(filteredUsers)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -462,14 +465,13 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
}
|
||||
|
||||
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
|
||||
runCatching {
|
||||
doKeyDownloadForUsers(users)
|
||||
}.fold(
|
||||
{
|
||||
Timber.v("## refreshOutdatedDeviceLists() : done")
|
||||
},
|
||||
.fold(
|
||||
{
|
||||
Timber.e(it, "## refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
|
||||
},
|
||||
{
|
||||
Timber.v("## refreshOutdatedDeviceLists() : done")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import android.text.TextUtils
|
||||
import arrow.core.Try
|
||||
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.JsonDict
|
||||
@ -79,7 +80,8 @@ internal class MXOlmDevice @Inject constructor(
|
||||
//
|
||||
// The first level keys are timeline ids.
|
||||
// The second level keys are strings of form "<senderKey>|<session_id>|<message_index>"
|
||||
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableSet<String>> = HashMap()
|
||||
// Values are true.
|
||||
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap()
|
||||
|
||||
init {
|
||||
// Retrieve the account from the store
|
||||
@ -504,8 +506,11 @@ internal class MXOlmDevice @Inject constructor(
|
||||
keysClaimed: Map<String, String>,
|
||||
exportFormat: Boolean): Boolean {
|
||||
val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat)
|
||||
runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
|
||||
.fold(
|
||||
|
||||
getInboundGroupSession(sessionId, senderKey, roomId).fold(
|
||||
{
|
||||
// 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")
|
||||
@ -518,9 +523,6 @@ internal class MXOlmDevice @Inject constructor(
|
||||
session.olmInboundGroupSession?.releaseSession()
|
||||
return false
|
||||
}
|
||||
},
|
||||
{
|
||||
// Nothing to do in case of error
|
||||
}
|
||||
)
|
||||
|
||||
@ -593,8 +595,12 @@ internal class MXOlmDevice @Inject constructor(
|
||||
continue
|
||||
}
|
||||
|
||||
runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
|
||||
getInboundGroupSession(sessionId, senderKey, roomId)
|
||||
.fold(
|
||||
{
|
||||
// Session does not already exist, add it
|
||||
sessions.add(session)
|
||||
},
|
||||
{
|
||||
// If we already have this session, consider updating it
|
||||
Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
||||
@ -607,12 +613,7 @@ internal class MXOlmDevice @Inject constructor(
|
||||
sessions.add(session)
|
||||
}
|
||||
Unit
|
||||
},
|
||||
{
|
||||
// Session does not already exist, add it
|
||||
sessions.add(session)
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@ -647,8 +648,9 @@ internal class MXOlmDevice @Inject constructor(
|
||||
roomId: String,
|
||||
timeline: String?,
|
||||
sessionId: String,
|
||||
senderKey: String): OlmDecryptionResult {
|
||||
val session = getInboundGroupSession(sessionId, senderKey, roomId)
|
||||
senderKey: String): Try<OlmDecryptionResult> {
|
||||
return getInboundGroupSession(sessionId, senderKey, roomId)
|
||||
.flatMap { session ->
|
||||
// Check that the room id matches the original one for the session. This stops
|
||||
// the HS pretending a message was targeting a different room.
|
||||
if (roomId == session.roomId) {
|
||||
@ -657,21 +659,23 @@ internal class MXOlmDevice @Inject constructor(
|
||||
decryptResult = session.olmInboundGroupSession!!.decryptMessage(body)
|
||||
} catch (e: OlmException) {
|
||||
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
|
||||
throw MXCryptoError.OlmError(e)
|
||||
return@flatMap Try.Failure(MXCryptoError.OlmError(e))
|
||||
}
|
||||
|
||||
if (null != timeline) {
|
||||
val timelineSet = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableSetOf() }
|
||||
if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
|
||||
inboundGroupSessionMessageIndexes[timeline] = HashMap()
|
||||
}
|
||||
|
||||
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
|
||||
|
||||
if (timelineSet.contains(messageIndexKey)) {
|
||||
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)
|
||||
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason))
|
||||
}
|
||||
|
||||
timelineSet.add(messageIndexKey)
|
||||
inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
|
||||
}
|
||||
|
||||
store.storeInboundGroupSessions(listOf(session))
|
||||
@ -681,20 +685,23 @@ internal class MXOlmDevice @Inject constructor(
|
||||
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@flatMap Try.Failure(
|
||||
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
|
||||
}
|
||||
|
||||
return OlmDecryptionResult(
|
||||
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")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
|
||||
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -759,26 +766,26 @@ internal class MXOlmDevice @Inject constructor(
|
||||
* @param senderKey the base64-encoded curve25519 key of the sender.
|
||||
* @return the inbound group session.
|
||||
*/
|
||||
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper {
|
||||
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): Try<OlmInboundGroupSessionWrapper> {
|
||||
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON))
|
||||
}
|
||||
|
||||
val session = store.getInboundGroupSession(sessionId, senderKey)
|
||||
|
||||
if (session != null) {
|
||||
return if (null != session) {
|
||||
// Check that the room id matches the original one for the session. This stops
|
||||
// the HS pretending a message was targeting a different room.
|
||||
if (!TextUtils.equals(roomId, session.roomId)) {
|
||||
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
|
||||
Timber.e("## getInboundGroupSession() : $errorDescription")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription)
|
||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription))
|
||||
} else {
|
||||
return session
|
||||
Try.just(session)
|
||||
}
|
||||
} else {
|
||||
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)
|
||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON))
|
||||
}
|
||||
}
|
||||
|
||||
@ -791,6 +798,6 @@ internal class MXOlmDevice @Inject constructor(
|
||||
* @return true if the unbound session keys are known.
|
||||
*/
|
||||
fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean {
|
||||
return runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }.isSuccess
|
||||
return getInboundGroupSession(sessionId, senderKey, roomId).isSuccess()
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ObjectSigner @Inject constructor(private val credentials: Credentials,
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
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.internal.crypto.model.MXKey
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
||||
@ -57,13 +59,13 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||
/**
|
||||
* Check if the OTK must be uploaded.
|
||||
*/
|
||||
suspend fun maybeUploadOneTimeKeys() {
|
||||
suspend fun maybeUploadOneTimeKeys(): Try<Unit> {
|
||||
if (oneTimeKeyCheckInProgress) {
|
||||
return
|
||||
return Try.just(Unit)
|
||||
}
|
||||
if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
|
||||
// we've done a key upload recently.
|
||||
return
|
||||
return Try.just(Unit)
|
||||
}
|
||||
|
||||
lastOneTimeKeyCheck = System.currentTimeMillis()
|
||||
@ -79,12 +81,13 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||
// discard the oldest private keys first. This will eventually clean
|
||||
// out stale private keys that won't receive a message.
|
||||
val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt()
|
||||
if (oneTimeKeyCount != null) {
|
||||
val result = if (oneTimeKeyCount != null) {
|
||||
uploadOTK(oneTimeKeyCount!!, keyLimit)
|
||||
} else {
|
||||
// ask the server how many keys we have
|
||||
val uploadKeysParams = UploadKeysTask.Params(null, null, credentials.deviceId!!)
|
||||
val response = uploadKeysTask.execute(uploadKeysParams)
|
||||
uploadKeysTask.execute(uploadKeysParams)
|
||||
.flatMap {
|
||||
// We need to keep a pool of one time public keys on the server so that
|
||||
// other devices can start conversations with us. But we can only store
|
||||
// a finite number of private keys in the olm Account object.
|
||||
@ -98,13 +101,22 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||
// these factors.
|
||||
// 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)
|
||||
val keyCount = response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
|
||||
val keyCount = it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
|
||||
uploadOTK(keyCount, keyLimit)
|
||||
}
|
||||
}
|
||||
return result
|
||||
.map {
|
||||
Timber.v("## uploadKeys() : success")
|
||||
oneTimeKeyCount = null
|
||||
oneTimeKeyCheckInProgress = false
|
||||
}
|
||||
.handleError {
|
||||
Timber.e(it, "## uploadKeys() : failed")
|
||||
oneTimeKeyCount = null
|
||||
oneTimeKeyCheckInProgress = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload some the OTKs.
|
||||
@ -112,26 +124,29 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||
* @param keyCount the key count
|
||||
* @param keyLimit the limit
|
||||
*/
|
||||
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int) {
|
||||
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Try<Unit> {
|
||||
if (keyLimit <= keyCount) {
|
||||
// If we don't need to generate any more keys then we are done.
|
||||
return
|
||||
return Try.just(Unit)
|
||||
}
|
||||
|
||||
val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
|
||||
olmDevice.generateOneTimeKeys(keysThisLoop)
|
||||
val response = uploadOneTimeKeys()
|
||||
if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
|
||||
uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
|
||||
return uploadOneTimeKeys()
|
||||
.flatMap {
|
||||
if (it.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
|
||||
uploadOTK(it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
|
||||
} else {
|
||||
Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
|
||||
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.
|
||||
*/
|
||||
private suspend fun uploadOneTimeKeys(): KeysUploadResponse {
|
||||
private suspend fun uploadOneTimeKeys(): Try<KeysUploadResponse> {
|
||||
val oneTimeKeys = olmDevice.getOneTimeKeys()
|
||||
val oneTimeJson = HashMap<String, Any>()
|
||||
|
||||
@ -154,10 +169,13 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||
// For now, we set the device id explicitly, as we may not be using the
|
||||
// same one as used in login.
|
||||
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!)
|
||||
val response = uploadKeysTask.execute(uploadParams)
|
||||
return uploadKeysTask
|
||||
.execute(uploadParams)
|
||||
.map {
|
||||
lastPublishedOneTimeKeys = oneTimeKeys
|
||||
olmDevice.markKeysAsPublished()
|
||||
return response
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -299,12 +299,10 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
||||
// TODO Change this two hard coded key to something better
|
||||
contentMap.setObject(recipient["userId"], recipient["deviceId"], message)
|
||||
}
|
||||
sendToDeviceTask
|
||||
.configureWith(SendToDeviceTask.Params(EventType.ROOM_KEY_REQUEST, contentMap, transactionId)) {
|
||||
this.callback = callback
|
||||
this.callbackThread = TaskThread.CALLER
|
||||
this.executionThread = TaskThread.CALLER
|
||||
}
|
||||
sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.ROOM_KEY_REQUEST, contentMap, transactionId))
|
||||
.dispatchTo(callback)
|
||||
.executeOn(TaskThread.CALLER)
|
||||
.callbackOn(TaskThread.CALLER)
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
package im.vector.matrix.android.internal.crypto.actions
|
||||
|
||||
import android.text.TextUtils
|
||||
import arrow.core.Try
|
||||
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.MXKey
|
||||
@ -31,7 +32,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
||||
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
|
||||
|
||||
|
||||
suspend fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>): MXUsersDevicesMap<MXOlmSessionResult> {
|
||||
suspend fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>): Try<MXUsersDevicesMap<MXOlmSessionResult>> {
|
||||
val devicesWithoutSession = ArrayList<MXDeviceInfo>()
|
||||
|
||||
val results = MXUsersDevicesMap<MXOlmSessionResult>()
|
||||
@ -57,7 +58,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
||||
}
|
||||
|
||||
if (devicesWithoutSession.size == 0) {
|
||||
return results
|
||||
return Try.just(results)
|
||||
}
|
||||
|
||||
// Prepare the request for claiming one-time keys
|
||||
@ -78,13 +79,15 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
||||
Timber.v("## claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")
|
||||
|
||||
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
|
||||
val oneTimeKeys = oneTimeKeysForUsersDeviceTask.execute(claimParams)
|
||||
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
|
||||
return oneTimeKeysForUsersDeviceTask
|
||||
.execute(claimParams)
|
||||
.map {
|
||||
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $it")
|
||||
for (userId in userIds) {
|
||||
val deviceInfos = devicesByUser[userId]
|
||||
for (deviceInfo in deviceInfos!!) {
|
||||
var oneTimeKey: MXKey? = null
|
||||
val deviceIds = oneTimeKeys.getUserDeviceIds(userId)
|
||||
val deviceIds = it.getUserDeviceIds(userId)
|
||||
if (null != deviceIds) {
|
||||
for (deviceId in deviceIds) {
|
||||
val olmSessionResult = results.getObject(userId, deviceId)
|
||||
@ -92,7 +95,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
||||
// We already have a result for this device
|
||||
continue
|
||||
}
|
||||
val key = oneTimeKeys.getObject(userId, deviceId)
|
||||
val key = it.getObject(userId, deviceId)
|
||||
if (key?.type == oneTimeKeyAlgorithm) {
|
||||
oneTimeKey = key
|
||||
}
|
||||
@ -107,7 +110,8 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
results
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? {
|
||||
|
@ -17,11 +17,14 @@
|
||||
package im.vector.matrix.android.internal.crypto.actions
|
||||
|
||||
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.model.MXDeviceInfo
|
||||
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.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
@ -34,7 +37,7 @@ internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val o
|
||||
* Try to make sure we have established olm sessions for the given users.
|
||||
* @param users a list of user ids.
|
||||
*/
|
||||
suspend fun handle(users: List<String>) : MXUsersDevicesMap<MXOlmSessionResult> {
|
||||
suspend fun handle(users: List<String>) : Try<MXUsersDevicesMap<MXOlmSessionResult>> {
|
||||
Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users")
|
||||
val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>()
|
||||
|
||||
|
@ -26,6 +26,7 @@ import im.vector.matrix.android.internal.crypto.RoomDecryptorProvider
|
||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.actions
|
||||
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.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
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.internal.crypto.IncomingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||
@ -34,7 +35,7 @@ internal interface IMXDecrypting {
|
||||
* @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
|
||||
*/
|
||||
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||
suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult>
|
||||
|
||||
/**
|
||||
* Handle a key event.
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.algorithms
|
||||
|
||||
import arrow.core.Try
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
|
||||
/**
|
||||
@ -30,7 +31,7 @@ internal interface IMXEncrypting {
|
||||
* @param eventContent the content of the event.
|
||||
* @param eventType the type of the event.
|
||||
* @param userIds the room members the event will be sent to.
|
||||
* @return the encrypted content
|
||||
* @return the encrypted content wrapped by [Try]
|
||||
*/
|
||||
suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content
|
||||
suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Try<Content>
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
||||
|
||||
import android.text.TextUtils
|
||||
import arrow.core.Try
|
||||
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.events.model.Event
|
||||
@ -28,6 +29,7 @@ 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.algorithms.IMXDecrypting
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
|
||||
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.event.EncryptedEventContent
|
||||
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
||||
@ -36,9 +38,11 @@ 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.tasks.SendToDeviceTask
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
internal class MXMegolmDecryption(private val credentials: Credentials,
|
||||
private val olmDevice: MXOlmDevice,
|
||||
@ -59,46 +63,30 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
||||
*/
|
||||
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
|
||||
|
||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
|
||||
return decryptEvent(event, timeline, true)
|
||||
}
|
||||
|
||||
private suspend fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
|
||||
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): Try<MXEventDecryptionResult> {
|
||||
if (event.roomId.isNullOrBlank()) {
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
||||
}
|
||||
|
||||
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
||||
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||
?: return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
||||
|
||||
if (encryptedEventContent.senderKey.isNullOrBlank()
|
||||
|| encryptedEventContent.sessionId.isNullOrBlank()
|
||||
|| encryptedEventContent.ciphertext.isNullOrBlank()) {
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
||||
}
|
||||
|
||||
return runCatching {
|
||||
olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext,
|
||||
return olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext,
|
||||
event.roomId,
|
||||
timeline,
|
||||
encryptedEventContent.sessionId,
|
||||
encryptedEventContent.senderKey)
|
||||
}
|
||||
.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 ->
|
||||
if (throwable is MXCryptoError.OlmError) {
|
||||
// TODO Check the value of .message
|
||||
@ -112,10 +100,10 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
||||
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
|
||||
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
|
||||
|
||||
throw MXCryptoError.Base(
|
||||
Try.Failure(MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.OLM,
|
||||
reason,
|
||||
detailedReason)
|
||||
detailedReason))
|
||||
}
|
||||
if (throwable is MXCryptoError.Base) {
|
||||
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
||||
@ -125,7 +113,23 @@ 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))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -308,41 +312,45 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
||||
return
|
||||
}
|
||||
val userId = request.userId ?: return
|
||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
|
||||
.mapCatching {
|
||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
||||
deviceListManager
|
||||
.downloadKeys(listOf(userId), false)
|
||||
.flatMap {
|
||||
val deviceId = request.deviceId
|
||||
val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId)
|
||||
if (deviceInfo == null) {
|
||||
throw RuntimeException()
|
||||
} else {
|
||||
val devicesByUser = mapOf(userId to listOf(deviceInfo))
|
||||
val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
||||
val devicesByUser = HashMap<String, List<MXDeviceInfo>>()
|
||||
devicesByUser[userId] = ArrayList(Arrays.asList(deviceInfo))
|
||||
ensureOlmSessionsForDevicesAction
|
||||
.handle(devicesByUser)
|
||||
.flatMap {
|
||||
val body = request.requestBody
|
||||
val olmSessionResult = usersDeviceMap.getObject(userId, deviceId)
|
||||
val olmSessionResult = it.getObject(userId, deviceId)
|
||||
if (olmSessionResult?.sessionId == null) {
|
||||
// no session with this device, probably because there
|
||||
// were no one-time keys.
|
||||
return@mapCatching
|
||||
Try.just(Unit)
|
||||
}
|
||||
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)
|
||||
runCatching { olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) }
|
||||
val payloadJson = HashMap<String, Any>()
|
||||
payloadJson["type"] = EventType.FORWARDED_ROOM_KEY
|
||||
|
||||
olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId)
|
||||
.fold(
|
||||
{
|
||||
// TODO
|
||||
payloadJson["content"] = it.exportKeys()
|
||||
?: ""
|
||||
},
|
||||
{
|
||||
// TODO
|
||||
payloadJson["content"] = it.exportKeys() ?: ""
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
|
||||
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo))
|
||||
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
|
||||
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
|
||||
@ -354,3 +362,4 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ 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.store.IMXCryptoStore
|
||||
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 javax.inject.Inject
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
||||
|
||||
import android.text.TextUtils
|
||||
import arrow.core.Try
|
||||
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.events.model.Content
|
||||
@ -68,10 +69,12 @@ internal class MXMegolmEncryption(
|
||||
|
||||
override suspend fun encryptEventContent(eventContent: Content,
|
||||
eventType: String,
|
||||
userIds: List<String>): Content {
|
||||
val devices = getDevicesInRoom(userIds)
|
||||
val outboundSession = ensureOutboundSession(devices)
|
||||
return encryptContent(outboundSession, eventType, eventContent)
|
||||
userIds: List<String>): Try<Content> {
|
||||
return getDevicesInRoom(userIds)
|
||||
.flatMap { ensureOutboundSession(it) }
|
||||
.flatMap {
|
||||
encryptContent(it, eventType, eventContent)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,7 +101,7 @@ internal class MXMegolmEncryption(
|
||||
*
|
||||
* @param devicesInRoom the devices list
|
||||
*/
|
||||
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): MXOutboundSessionInfo {
|
||||
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): Try<MXOutboundSessionInfo> {
|
||||
var session = outboundSession
|
||||
if (session == null
|
||||
// Need to make a brand new session?
|
||||
@ -123,8 +126,7 @@ internal class MXMegolmEncryption(
|
||||
}
|
||||
}
|
||||
}
|
||||
shareKey(safeSession, shareMap)
|
||||
return safeSession
|
||||
return shareKey(safeSession, shareMap).map { safeSession!! }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,11 +136,11 @@ internal class MXMegolmEncryption(
|
||||
* @param devicesByUsers the devices map
|
||||
*/
|
||||
private suspend fun shareKey(session: MXOutboundSessionInfo,
|
||||
devicesByUsers: Map<String, List<MXDeviceInfo>>) {
|
||||
devicesByUsers: Map<String, List<MXDeviceInfo>>): Try<Unit> {
|
||||
// nothing to send, the task is done
|
||||
if (devicesByUsers.isEmpty()) {
|
||||
Timber.v("## shareKey() : nothing more to do")
|
||||
return
|
||||
return Try.just(Unit)
|
||||
}
|
||||
// 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>>()
|
||||
@ -155,10 +157,12 @@ internal class MXMegolmEncryption(
|
||||
}
|
||||
}
|
||||
Timber.v("## shareKey() ; userId $userIds")
|
||||
shareUserDevicesKey(session, subMap)
|
||||
return shareUserDevicesKey(session, subMap)
|
||||
.flatMap {
|
||||
val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it).not() }
|
||||
shareKey(session, remainingDevices)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Share the device keys of a an user
|
||||
@ -168,7 +172,7 @@ internal class MXMegolmEncryption(
|
||||
* @param callback the asynchronous callback
|
||||
*/
|
||||
private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo,
|
||||
devicesByUser: Map<String, List<MXDeviceInfo>>) {
|
||||
devicesByUser: Map<String, List<MXDeviceInfo>>): Try<Unit> {
|
||||
val sessionKey = olmDevice.getSessionKey(session.sessionId)
|
||||
val chainIndex = olmDevice.getMessageIndex(session.sessionId)
|
||||
|
||||
@ -186,16 +190,17 @@ internal class MXMegolmEncryption(
|
||||
var t0 = System.currentTimeMillis()
|
||||
Timber.v("## shareUserDevicesKey() : starts")
|
||||
|
||||
val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
||||
return ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
||||
.flatMap {
|
||||
Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
|
||||
+ (System.currentTimeMillis() - t0) + " ms")
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
var haveTargets = false
|
||||
val userIds = results.userIds
|
||||
val userIds = it.userIds
|
||||
for (userId in userIds) {
|
||||
val devicesToShareWith = devicesByUser[userId]
|
||||
for ((deviceID) in devicesToShareWith!!) {
|
||||
val sessionResult = results.getObject(userId, deviceID)
|
||||
val sessionResult = it.getObject(userId, deviceID)
|
||||
if (sessionResult?.sessionId == null) {
|
||||
// no session with this device, probably because there
|
||||
// were no one-time keys.
|
||||
@ -221,6 +226,7 @@ internal class MXMegolmEncryption(
|
||||
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")
|
||||
|
||||
@ -235,15 +241,20 @@ internal class MXMegolmEncryption(
|
||||
session.sharedWithDevices.setObject(userId, deviceId, chainIndex)
|
||||
}
|
||||
}
|
||||
Unit
|
||||
}
|
||||
} else {
|
||||
Timber.v("## shareUserDevicesKey() : no need to sharekey")
|
||||
Try.just(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* process the pending encryptions
|
||||
*/
|
||||
private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Content {
|
||||
private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Try<Content> {
|
||||
return Try<Content> {
|
||||
// Everything is in place, encrypt all pending events
|
||||
val payloadJson = HashMap<String, Any>()
|
||||
payloadJson["room_id"] = roomId
|
||||
@ -265,7 +276,8 @@ internal class MXMegolmEncryption(
|
||||
// m.new_device message if they don't have our session key.
|
||||
map["device_id"] = credentials.deviceId!!
|
||||
session.useCount++
|
||||
return map
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -275,22 +287,24 @@ internal class MXMegolmEncryption(
|
||||
* @param userIds the user ids whose devices must be checked.
|
||||
* @param callback the asynchronous callback
|
||||
*/
|
||||
private suspend fun getDevicesInRoom(userIds: List<String>): MXUsersDevicesMap<MXDeviceInfo> {
|
||||
private suspend fun getDevicesInRoom(userIds: List<String>): Try<MXUsersDevicesMap<MXDeviceInfo>> {
|
||||
// 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
|
||||
// with them, which means that they will have announced any new devices via
|
||||
// an m.new_device.
|
||||
val keys = deviceListManager.downloadKeys(userIds, false)
|
||||
return deviceListManager
|
||||
.downloadKeys(userIds, false)
|
||||
.flatMap {
|
||||
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|
||||
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
|
||||
|
||||
val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
|
||||
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
|
||||
|
||||
for (userId in keys.userIds) {
|
||||
val deviceIds = keys.getUserDeviceIds(userId) ?: continue
|
||||
for (userId in it.userIds) {
|
||||
val deviceIds = it.getUserDeviceIds(userId) ?: continue
|
||||
for (deviceId in deviceIds) {
|
||||
val deviceInfo = keys.getObject(userId, deviceId) ?: continue
|
||||
val deviceInfo = it.getObject(userId, deviceId) ?: continue
|
||||
if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) {
|
||||
// The device is not yet known by the user
|
||||
unknownDevices.setObject(userId, deviceId, deviceInfo)
|
||||
@ -313,9 +327,10 @@ internal class MXMegolmEncryption(
|
||||
}
|
||||
}
|
||||
if (unknownDevices.isEmpty) {
|
||||
return devicesInRoom
|
||||
Try.just(devicesInRoom)
|
||||
} else {
|
||||
throw MXCryptoError.UnknownDevice(unknownDevices)
|
||||
Try.Failure(MXCryptoError.UnknownDevice(unknownDevices))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
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.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
@ -31,6 +32,7 @@ import im.vector.matrix.android.internal.crypto.model.event.OlmPayloadContent
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import im.vector.matrix.android.internal.util.convertFromUTF8
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
internal class MXOlmDecryption(
|
||||
// The olm device interface
|
||||
@ -39,28 +41,29 @@ internal class MXOlmDecryption(
|
||||
private val credentials: Credentials)
|
||||
: IMXDecrypting {
|
||||
|
||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
|
||||
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run {
|
||||
Timber.e("## decryptEvent() : bad event format")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
|
||||
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
|
||||
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON))
|
||||
}
|
||||
|
||||
val cipherText = olmEventContent.ciphertext ?: run {
|
||||
Timber.e("## decryptEvent() : missing cipher text")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
|
||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
|
||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
||||
}
|
||||
|
||||
val senderKey = olmEventContent.senderKey ?: run {
|
||||
Timber.e("## decryptEvent() : missing sender key")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY,
|
||||
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY,
|
||||
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON))
|
||||
}
|
||||
|
||||
val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run {
|
||||
Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS,
|
||||
MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON))
|
||||
}
|
||||
|
||||
// The message for myUser
|
||||
@ -70,12 +73,14 @@ internal class MXOlmDecryption(
|
||||
|
||||
if (decryptedPayload == null) {
|
||||
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE,
|
||||
MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
|
||||
}
|
||||
val payloadString = convertFromUTF8(decryptedPayload)
|
||||
if (payloadString == null) {
|
||||
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE,
|
||||
MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
|
||||
}
|
||||
|
||||
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||
@ -83,70 +88,73 @@ internal class MXOlmDecryption(
|
||||
|
||||
if (payload == null) {
|
||||
Timber.e("## decryptEvent failed : null payload")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
|
||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
||||
}
|
||||
|
||||
val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run {
|
||||
Timber.e("## decryptEvent() : bad olmPayloadContent format")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT,
|
||||
MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
|
||||
}
|
||||
|
||||
if (olmPayloadContent.recipient.isNullOrBlank()) {
|
||||
val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient")
|
||||
Timber.e("## decryptEvent() : $reason")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||
reason))
|
||||
}
|
||||
|
||||
if (olmPayloadContent.recipient != credentials.userId) {
|
||||
Timber.e("## decryptEvent() : Event ${event.eventId}:" +
|
||||
" Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
|
||||
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
|
||||
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)))
|
||||
}
|
||||
|
||||
val recipientKeys = olmPayloadContent.recipient_keys ?: run {
|
||||
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys' property; cannot prevent unknown-key attack")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")))
|
||||
}
|
||||
|
||||
val ed25519 = recipientKeys["ed25519"]
|
||||
|
||||
if (ed25519 != olmDevice.deviceEd25519Key) {
|
||||
Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
|
||||
MXCryptoError.BAD_RECIPIENT_KEY_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
|
||||
MXCryptoError.BAD_RECIPIENT_KEY_REASON))
|
||||
}
|
||||
|
||||
if (olmPayloadContent.sender.isNullOrBlank()) {
|
||||
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")))
|
||||
}
|
||||
|
||||
if (olmPayloadContent.sender != event.senderId) {
|
||||
Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE,
|
||||
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE,
|
||||
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)))
|
||||
}
|
||||
|
||||
if (olmPayloadContent.room_id != event.roomId) {
|
||||
Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.room_id} does not match reported room ${event.roomId}")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM,
|
||||
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM,
|
||||
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id)))
|
||||
}
|
||||
|
||||
val keys = olmPayloadContent.keys ?: run {
|
||||
Timber.e("## decryptEvent failed : null keys")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
|
||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON)
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
|
||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
||||
}
|
||||
|
||||
return MXEventDecryptionResult(
|
||||
return Try.just(MXEventDecryptionResult(
|
||||
clearEvent = payload,
|
||||
senderCurve25519Key = senderKey,
|
||||
claimedEd25519Key = keys["ed25519"]
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,14 +165,33 @@ internal class MXOlmDecryption(
|
||||
* @return payload, if decrypted successfully.
|
||||
*/
|
||||
private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
|
||||
val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey) ?: emptySet()
|
||||
val sessionIdsSet = olmDevice.getSessionIds(theirDeviceIdentityKey)
|
||||
|
||||
val messageBody = message["body"] as? String ?: return null
|
||||
val messageType = when (val typeAsVoid = message["type"]) {
|
||||
is Double -> typeAsVoid.toInt()
|
||||
is Int -> typeAsVoid
|
||||
is Long -> typeAsVoid.toInt()
|
||||
else -> return null
|
||||
val sessionIds: List<String>
|
||||
|
||||
if (null == sessionIdsSet) {
|
||||
sessionIds = ArrayList()
|
||||
} else {
|
||||
sessionIds = ArrayList(sessionIdsSet)
|
||||
}
|
||||
|
||||
val messageBody = message["body"] as? String
|
||||
var messageType: Int? = null
|
||||
|
||||
val typeAsVoid = message["type"]
|
||||
|
||||
if (null != typeAsVoid) {
|
||||
if (typeAsVoid is Double) {
|
||||
messageType = typeAsVoid.toInt()
|
||||
} else if (typeAsVoid is Int) {
|
||||
messageType = typeAsVoid
|
||||
} else if (typeAsVoid is Long) {
|
||||
messageType = typeAsVoid.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
if (null == messageBody || null == messageType) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Try each session in turn
|
||||
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.algorithms.olm
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice,
|
||||
|
@ -19,6 +19,7 @@
|
||||
package im.vector.matrix.android.internal.crypto.algorithms.olm
|
||||
|
||||
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.toContent
|
||||
import im.vector.matrix.android.internal.crypto.DeviceListManager
|
||||
@ -39,11 +40,12 @@ internal class MXOlmEncryption(
|
||||
private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction)
|
||||
: IMXEncrypting {
|
||||
|
||||
override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content {
|
||||
override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Try<Content> {
|
||||
// pick the list of recipients based on the membership list.
|
||||
//
|
||||
// TODO: there is a race condition here! What if a new user turns up
|
||||
ensureSession(userIds)
|
||||
return ensureSession(userIds)
|
||||
.map {
|
||||
val deviceInfos = ArrayList<MXDeviceInfo>()
|
||||
for (userId in userIds) {
|
||||
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
|
||||
@ -67,7 +69,8 @@ internal class MXOlmEncryption(
|
||||
messageMap["content"] = eventContent
|
||||
|
||||
messageEncrypter.encryptMessage(messageMap, deviceInfos)
|
||||
return messageMap.toContent()!!
|
||||
messageMap.toContent()!!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -75,9 +78,13 @@ internal class MXOlmEncryption(
|
||||
* Ensure that the session
|
||||
*
|
||||
* @param users the user ids list
|
||||
* @param callback the asynchronous callback
|
||||
*/
|
||||
private suspend fun ensureSession(users: List<String>) {
|
||||
deviceListManager.downloadKeys(users, false)
|
||||
ensureOlmSessionsForUsersAction.handle(users)
|
||||
private suspend fun ensureSession(users: List<String>): Try<Unit> {
|
||||
return deviceListManager
|
||||
.downloadKeys(users, false)
|
||||
.flatMap { ensureOlmSessionsForUsersAction.handle(users) }
|
||||
.map { Unit }
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ 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.MessageEncrypter
|
||||
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 javax.inject.Inject
|
||||
|
||||
|
@ -18,6 +18,8 @@ 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.KeysUploadBody
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody
|
||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.*
|
||||
|
@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
@ -58,7 +59,8 @@ object MXEncryptedAttachments {
|
||||
// Half of the IV is random, the lower order bits are zeroed
|
||||
// such that the counter never wraps.
|
||||
// See https://github.com/matrix-org/matrix-ios-kit/blob/3dc0d8e46b4deb6669ed44f72ad79be56471354c/MatrixKit/Models/Room/MXEncryptedAttachments.m#L75
|
||||
val initVectorBytes = ByteArray(16) { 0.toByte() }
|
||||
val initVectorBytes = ByteArray(16)
|
||||
Arrays.fill(initVectorBytes, 0.toByte())
|
||||
|
||||
val ivRandomPart = ByteArray(8)
|
||||
secureRandom.nextBytes(ivRandomPart)
|
||||
@ -113,7 +115,7 @@ object MXEncryptedAttachments {
|
||||
encryptedByteArray = outStream.toByteArray()
|
||||
)
|
||||
|
||||
Timber.v("Encrypt in ${System.currentTimeMillis() - t0} ms")
|
||||
Timber.v("Encrypt in " + (System.currentTimeMillis() - t0) + " ms")
|
||||
return Try.just(result)
|
||||
} catch (oom: OutOfMemoryError) {
|
||||
Timber.e(oom, "## encryptAttachment failed")
|
||||
@ -204,13 +206,13 @@ object MXEncryptedAttachments {
|
||||
val decryptedStream = ByteArrayInputStream(outStream.toByteArray())
|
||||
outStream.close()
|
||||
|
||||
Timber.v("Decrypt in ${System.currentTimeMillis() - t0} ms")
|
||||
Timber.v("Decrypt in " + (System.currentTimeMillis() - t0) + " ms")
|
||||
|
||||
return decryptedStream
|
||||
} catch (oom: OutOfMemoryError) {
|
||||
Timber.e(oom, "## decryptAttachment() : failed ${oom.message}")
|
||||
Timber.e(oom, "## decryptAttachment() : failed " + oom.message)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## decryptAttachment() : failed ${e.message}")
|
||||
Timber.e(e, "## decryptAttachment() : failed " + e.message)
|
||||
}
|
||||
|
||||
try {
|
||||
@ -226,20 +228,34 @@ object MXEncryptedAttachments {
|
||||
* Base64 URL conversion methods
|
||||
*/
|
||||
|
||||
private fun base64UrlToBase64(base64Url: String): String {
|
||||
return base64Url.replace('-', '+')
|
||||
.replace('_', '/')
|
||||
private fun base64UrlToBase64(base64Url: String?): String? {
|
||||
var result = base64Url
|
||||
if (null != result) {
|
||||
result = result.replace("-".toRegex(), "+")
|
||||
result = result.replace("_".toRegex(), "/")
|
||||
}
|
||||
|
||||
private fun base64ToBase64Url(base64: String): String {
|
||||
return base64.replace("\n".toRegex(), "")
|
||||
.replace("\\+".toRegex(), "-")
|
||||
.replace('/', '_')
|
||||
.replace("=", "")
|
||||
return result
|
||||
}
|
||||
|
||||
private fun base64ToUnpaddedBase64(base64: String): String {
|
||||
return base64.replace("\n".toRegex(), "")
|
||||
.replace("=", "")
|
||||
private fun base64ToBase64Url(base64: String?): String? {
|
||||
var result = base64
|
||||
if (null != result) {
|
||||
result = result.replace("\n".toRegex(), "")
|
||||
result = result.replace("\\+".toRegex(), "-")
|
||||
result = result.replace("/".toRegex(), "_")
|
||||
result = result.replace("=".toRegex(), "")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun base64ToUnpaddedBase64(base64: String?): String? {
|
||||
var result = base64
|
||||
if (null != result) {
|
||||
result = result.replace("\n".toRegex(), "")
|
||||
result = result.replace("=".toRegex(), "")
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ 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.extensions.foldToCallback
|
||||
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.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.TaskThread
|
||||
@ -66,8 +67,9 @@ import org.matrix.olm.OlmPkEncryption
|
||||
import org.matrix.olm.OlmPkMessage
|
||||
import timber.log.Timber
|
||||
import java.security.InvalidParameterException
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.random.Random
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* A KeysBackup class instance manage incremental backup of e2e keys (megolm keys)
|
||||
@ -113,6 +115,8 @@ internal class KeysBackup @Inject constructor(
|
||||
// The backup key being used.
|
||||
private var backupOlmPkEncryption: OlmPkEncryption? = null
|
||||
|
||||
private val random = Random()
|
||||
|
||||
private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
|
||||
|
||||
private var keysBackupStateListener: KeysBackupStateListener? = null
|
||||
@ -200,8 +204,8 @@ internal class KeysBackup @Inject constructor(
|
||||
keysBackupStateManager.state = KeysBackupState.Enabling
|
||||
|
||||
createKeysBackupVersionTask
|
||||
.configureWith(createKeysBackupVersionBody) {
|
||||
this.callback = object : MatrixCallback<KeysVersion> {
|
||||
.configureWith(createKeysBackupVersionBody)
|
||||
.dispatchTo(object : MatrixCallback<KeysVersion> {
|
||||
override fun onSuccess(info: KeysVersion) {
|
||||
// Reset backup markers.
|
||||
cryptoStore.resetBackupMarkers()
|
||||
@ -224,8 +228,7 @@ internal class KeysBackup @Inject constructor(
|
||||
keysBackupStateManager.state = KeysBackupState.Disabled
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
@ -240,9 +243,8 @@ internal class KeysBackup @Inject constructor(
|
||||
keysBackupStateManager.state = KeysBackupState.Unknown
|
||||
}
|
||||
|
||||
deleteBackupTask
|
||||
.configureWith(DeleteBackupTask.Params(version)) {
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
deleteBackupTask.configureWith(DeleteBackupTask.Params(version))
|
||||
.dispatchTo(object : MatrixCallback<Unit> {
|
||||
private fun eventuallyRestartBackup() {
|
||||
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
|
||||
if (state == KeysBackupState.Unknown) {
|
||||
@ -261,8 +263,7 @@ internal class KeysBackup @Inject constructor(
|
||||
|
||||
uiHandler.post { callback?.onFailure(failure) }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
}
|
||||
@ -354,14 +355,15 @@ internal class KeysBackup @Inject constructor(
|
||||
callback: MatrixCallback<KeysBackupVersionTrust>) {
|
||||
// TODO Validate with François that this is correct
|
||||
object : Task<KeysVersionResult, KeysBackupVersionTrust> {
|
||||
override suspend fun execute(params: KeysVersionResult): KeysBackupVersionTrust {
|
||||
return getKeysBackupTrustBg(params)
|
||||
override suspend fun execute(params: KeysVersionResult): Try<KeysBackupVersionTrust> {
|
||||
return Try {
|
||||
getKeysBackupTrustBg(params)
|
||||
}
|
||||
}
|
||||
.configureWith(keysBackupVersion) {
|
||||
this.callback = callback
|
||||
this.executionThread = TaskThread.COMPUTATION
|
||||
}
|
||||
.configureWith(keysBackupVersion)
|
||||
.dispatchTo(callback)
|
||||
.executeOn(TaskThread.COMPUTATION)
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
@ -452,8 +454,7 @@ internal class KeysBackup @Inject constructor(
|
||||
val myUserId = credentials.userId
|
||||
|
||||
// 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) {
|
||||
// Add current device signature
|
||||
@ -491,8 +492,8 @@ internal class KeysBackup @Inject constructor(
|
||||
|
||||
// And send it to the homeserver
|
||||
updateKeysBackupVersionTask
|
||||
.configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody)) {
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
.configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody))
|
||||
.dispatchTo(object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
// Relaunch the state machine on this updated backup version
|
||||
val newKeysBackupVersion = KeysVersionResult()
|
||||
@ -511,8 +512,7 @@ internal class KeysBackup @Inject constructor(
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
}
|
||||
@ -758,8 +758,8 @@ internal class KeysBackup @Inject constructor(
|
||||
if (roomId != null && sessionId != null) {
|
||||
// Get key for the room and for the session
|
||||
getRoomSessionDataTask
|
||||
.configureWith(GetRoomSessionDataTask.Params(roomId, sessionId, version)) {
|
||||
this.callback = object : MatrixCallback<KeyBackupData> {
|
||||
.configureWith(GetRoomSessionDataTask.Params(roomId, sessionId, version))
|
||||
.dispatchTo(object : MatrixCallback<KeyBackupData> {
|
||||
override fun onSuccess(data: KeyBackupData) {
|
||||
// Convert to KeysBackupData
|
||||
val keysBackupData = KeysBackupData()
|
||||
@ -775,14 +775,13 @@ internal class KeysBackup @Inject constructor(
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.executeBy(taskExecutor)
|
||||
} else if (roomId != null) {
|
||||
// Get all keys for the room
|
||||
getRoomSessionsDataTask
|
||||
.configureWith(GetRoomSessionsDataTask.Params(roomId, version)) {
|
||||
this.callback = object : MatrixCallback<RoomKeysBackupData> {
|
||||
.configureWith(GetRoomSessionsDataTask.Params(roomId, version))
|
||||
.dispatchTo(object : MatrixCallback<RoomKeysBackupData> {
|
||||
override fun onSuccess(data: RoomKeysBackupData) {
|
||||
// Convert to KeysBackupData
|
||||
val keysBackupData = KeysBackupData()
|
||||
@ -795,15 +794,13 @@ internal class KeysBackup @Inject constructor(
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.executeBy(taskExecutor)
|
||||
} else {
|
||||
// Get all keys
|
||||
getSessionsDataTask
|
||||
.configureWith(GetSessionsDataTask.Params(version)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.configureWith(GetSessionsDataTask.Params(version))
|
||||
.dispatchTo(callback)
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
}
|
||||
@ -845,7 +842,7 @@ internal class KeysBackup @Inject constructor(
|
||||
// Wait between 0 and 10 seconds, to avoid backup requests from
|
||||
// different clients hitting the server all at the same time when a
|
||||
// new key is sent
|
||||
val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS)
|
||||
val delayInMs = random.nextInt(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS).toLong()
|
||||
|
||||
uiHandler.postDelayed({ backupKeys() }, delayInMs)
|
||||
}
|
||||
@ -858,8 +855,8 @@ internal class KeysBackup @Inject constructor(
|
||||
override fun getVersion(version: String,
|
||||
callback: MatrixCallback<KeysVersionResult?>) {
|
||||
getKeysBackupVersionTask
|
||||
.configureWith(version) {
|
||||
this.callback = object : MatrixCallback<KeysVersionResult> {
|
||||
.configureWith(version)
|
||||
.dispatchTo(object : MatrixCallback<KeysVersionResult> {
|
||||
override fun onSuccess(data: KeysVersionResult) {
|
||||
callback.onSuccess(data)
|
||||
}
|
||||
@ -874,15 +871,14 @@ internal class KeysBackup @Inject constructor(
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) {
|
||||
getKeysBackupLastVersionTask
|
||||
.configureWith {
|
||||
this.callback = object : MatrixCallback<KeysVersionResult> {
|
||||
.toConfigurableTask()
|
||||
.dispatchTo(object : MatrixCallback<KeysVersionResult> {
|
||||
override fun onSuccess(data: KeysVersionResult) {
|
||||
callback.onSuccess(data)
|
||||
}
|
||||
@ -897,8 +893,7 @@ internal class KeysBackup @Inject constructor(
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
@ -1241,7 +1236,10 @@ internal class KeysBackup @Inject constructor(
|
||||
|
||||
Timber.v("backupKeys: 4 - Sending request")
|
||||
|
||||
val sendingRequestCallback = object : MatrixCallback<BackupKeysResult> {
|
||||
// Make the request
|
||||
storeSessionDataTask
|
||||
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData))
|
||||
.dispatchTo(object : MatrixCallback<BackupKeysResult> {
|
||||
override fun onSuccess(data: BackupKeysResult) {
|
||||
uiHandler.post {
|
||||
Timber.v("backupKeys: 5a - Request complete")
|
||||
@ -1300,13 +1298,7 @@ internal class KeysBackup @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make the request
|
||||
storeSessionDataTask
|
||||
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) {
|
||||
this.callback = sendingRequestCallback
|
||||
}
|
||||
})
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
}
|
||||
@ -1402,7 +1394,7 @@ internal class KeysBackup @Inject constructor(
|
||||
|
||||
companion object {
|
||||
// Maximum delay in ms in {@link maybeBackupKeys}
|
||||
private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L
|
||||
private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10000
|
||||
|
||||
// Maximum number of keys to send at a time to the homeserver.
|
||||
private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100
|
||||
|
@ -22,7 +22,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup
|
||||
import androidx.annotation.WorkerThread
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import java.util.*
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.experimental.xor
|
||||
@ -142,11 +142,12 @@ private fun deriveKey(password: String,
|
||||
* Generate a 32 chars salt
|
||||
*/
|
||||
private fun generateSalt(): String {
|
||||
val salt = buildString {
|
||||
var salt = ""
|
||||
|
||||
do {
|
||||
append(UUID.randomUUID().toString())
|
||||
} while (length < SALT_LENGTH)
|
||||
}
|
||||
salt += UUID.randomUUID().toString()
|
||||
} while (salt.length < SALT_LENGTH)
|
||||
|
||||
|
||||
return salt.substring(0, SALT_LENGTH)
|
||||
}
|
@ -20,6 +20,7 @@ import android.os.Handler
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
internal class KeysBackupStateManager(private val uiHandler: Handler) {
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
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.model.rest.CreateKeysBackupVersionBody
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||
@ -29,7 +30,7 @@ internal class DefaultCreateKeysBackupVersionTask @Inject constructor(private va
|
||||
: CreateKeysBackupVersionTask {
|
||||
|
||||
|
||||
override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion {
|
||||
override suspend fun execute(params: CreateKeysBackupVersionBody): Try<KeysVersion> {
|
||||
return executeRequest {
|
||||
apiCall = roomKeysApi.createKeysBackupVersion(params)
|
||||
}
|
||||
|
@ -16,8 +16,10 @@
|
||||
|
||||
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.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -31,9 +33,10 @@ internal interface DeleteBackupTask : Task<DeleteBackupTask.Params, Unit> {
|
||||
internal class DefaultDeleteBackupTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
|
||||
: DeleteBackupTask {
|
||||
|
||||
override suspend fun execute(params: DeleteBackupTask.Params) {
|
||||
override suspend fun execute(params: DeleteBackupTask.Params): Try<Unit> {
|
||||
return executeRequest {
|
||||
apiCall = roomKeysApi.deleteBackup(params.version)
|
||||
apiCall = roomKeysApi.deleteBackup(
|
||||
params.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,10 @@
|
||||
|
||||
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.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -32,7 +34,7 @@ internal interface DeleteRoomSessionDataTask : Task<DeleteRoomSessionDataTask.Pa
|
||||
internal class DefaultDeleteRoomSessionDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
|
||||
: DeleteRoomSessionDataTask {
|
||||
|
||||
override suspend fun execute(params: DeleteRoomSessionDataTask.Params) {
|
||||
override suspend fun execute(params: DeleteRoomSessionDataTask.Params): Try<Unit> {
|
||||
return executeRequest {
|
||||
apiCall = roomKeysApi.deleteRoomSessionData(
|
||||
params.roomId,
|
||||
|
@ -16,8 +16,10 @@
|
||||
|
||||
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.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -31,7 +33,7 @@ internal interface DeleteRoomSessionsDataTask : Task<DeleteRoomSessionsDataTask.
|
||||
internal class DefaultDeleteRoomSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
|
||||
: DeleteRoomSessionsDataTask {
|
||||
|
||||
override suspend fun execute(params: DeleteRoomSessionsDataTask.Params) {
|
||||
override suspend fun execute(params: DeleteRoomSessionsDataTask.Params): Try<Unit> {
|
||||
return executeRequest {
|
||||
apiCall = roomKeysApi.deleteRoomSessionsData(
|
||||
params.roomId,
|
||||
|
@ -16,8 +16,10 @@
|
||||
|
||||
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.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -30,9 +32,10 @@ internal interface DeleteSessionsDataTask : Task<DeleteSessionsDataTask.Params,
|
||||
internal class DefaultDeleteSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
|
||||
: DeleteSessionsDataTask {
|
||||
|
||||
override suspend fun execute(params: DeleteSessionsDataTask.Params) {
|
||||
override suspend fun execute(params: DeleteSessionsDataTask.Params): Try<Unit> {
|
||||
return executeRequest {
|
||||
apiCall = roomKeysApi.deleteSessionsData(params.version)
|
||||
apiCall = roomKeysApi.deleteSessionsData(
|
||||
params.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
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.model.rest.KeysVersionResult
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
@ -28,7 +29,7 @@ internal class DefaultGetKeysBackupLastVersionTask @Inject constructor(private v
|
||||
: GetKeysBackupLastVersionTask {
|
||||
|
||||
|
||||
override suspend fun execute(params: Unit): KeysVersionResult {
|
||||
override suspend fun execute(params: Unit): Try<KeysVersionResult> {
|
||||
return executeRequest {
|
||||
apiCall = roomKeysApi.getKeysBackupLastVersion()
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
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.model.rest.KeysVersionResult
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
@ -28,7 +29,7 @@ internal class DefaultGetKeysBackupVersionTask @Inject constructor(private val r
|
||||
: GetKeysBackupVersionTask {
|
||||
|
||||
|
||||
override suspend fun execute(params: String): KeysVersionResult {
|
||||
override suspend fun execute(params: String): Try<KeysVersionResult> {
|
||||
return executeRequest {
|
||||
apiCall = roomKeysApi.getKeysBackupVersion(params)
|
||||
}
|
||||
|
@ -16,9 +16,11 @@
|
||||
|
||||
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.model.rest.KeyBackupData
|
||||
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 javax.inject.Inject
|
||||
|
||||
@ -33,7 +35,7 @@ internal interface GetRoomSessionDataTask : Task<GetRoomSessionDataTask.Params,
|
||||
internal class DefaultGetRoomSessionDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
|
||||
: GetRoomSessionDataTask {
|
||||
|
||||
override suspend fun execute(params: GetRoomSessionDataTask.Params): KeyBackupData {
|
||||
override suspend fun execute(params: GetRoomSessionDataTask.Params): Try<KeyBackupData> {
|
||||
return executeRequest {
|
||||
apiCall = roomKeysApi.getRoomSessionData(
|
||||
params.roomId,
|
||||
|
@ -16,9 +16,11 @@
|
||||
|
||||
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.model.rest.RoomKeysBackupData
|
||||
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 javax.inject.Inject
|
||||
|
||||
@ -33,7 +35,7 @@ internal interface GetRoomSessionsDataTask : Task<GetRoomSessionsDataTask.Params
|
||||
internal class DefaultGetRoomSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
|
||||
: GetRoomSessionsDataTask {
|
||||
|
||||
override suspend fun execute(params: GetRoomSessionsDataTask.Params): RoomKeysBackupData {
|
||||
override suspend fun execute(params: GetRoomSessionsDataTask.Params): Try<RoomKeysBackupData> {
|
||||
return executeRequest {
|
||||
apiCall = roomKeysApi.getRoomSessionsData(
|
||||
params.roomId,
|
||||
|
@ -16,9 +16,11 @@
|
||||
|
||||
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.model.rest.KeysBackupData
|
||||
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 javax.inject.Inject
|
||||
|
||||
@ -31,9 +33,10 @@ internal interface GetSessionsDataTask : Task<GetSessionsDataTask.Params, KeysBa
|
||||
internal class DefaultGetSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
|
||||
: GetSessionsDataTask {
|
||||
|
||||
override suspend fun execute(params: GetSessionsDataTask.Params): KeysBackupData {
|
||||
override suspend fun execute(params: GetSessionsDataTask.Params): Try<KeysBackupData> {
|
||||
return executeRequest {
|
||||
apiCall = roomKeysApi.getSessionsData(params.version)
|
||||
apiCall = roomKeysApi.getSessionsData(
|
||||
params.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,12 @@
|
||||
|
||||
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.model.rest.BackupKeysResult
|
||||
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.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -35,7 +37,7 @@ internal interface StoreRoomSessionDataTask : Task<StoreRoomSessionDataTask.Para
|
||||
internal class DefaultStoreRoomSessionDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
|
||||
: StoreRoomSessionDataTask {
|
||||
|
||||
override suspend fun execute(params: StoreRoomSessionDataTask.Params): BackupKeysResult {
|
||||
override suspend fun execute(params: StoreRoomSessionDataTask.Params): Try<BackupKeysResult> {
|
||||
return executeRequest {
|
||||
apiCall = roomKeysApi.storeRoomSessionData(
|
||||
params.roomId,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user