Merge pull request #5639 from vector-im/feature/dla/uisi_match_web_implementation

Align Autorageshake with web implementation
This commit is contained in:
David Langley 2022-04-12 10:36:43 +01:00 committed by GitHub
commit 454a65602b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 69 additions and 95 deletions

1
changelog.d/5596.bugfix Normal file
View file

@ -0,0 +1 @@
Align auto-reporting of decryption errors implementation with web client.

1
changelog.d/5639.sdk Normal file
View file

@ -0,0 +1 @@
Include original event in live decryption listeners and update sync status naming to InitialSyncProgressing for clarity.

View file

@ -25,9 +25,9 @@ interface LiveEventListener {
fun onPaginatedEvent(roomId: String, event: Event)
fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict)
fun onEventDecrypted(event: Event, clearEvent: JsonDict)
fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable)
fun onEventDecryptionError(event: Event, throwable: Throwable)
fun onLiveToDeviceEvent(event: Event)

View file

@ -28,7 +28,7 @@ interface SyncStatusService {
abstract class InitialSyncStatus : Status()
object Idle : InitialSyncStatus()
data class Progressing(
data class InitialSyncProgressing(
val initSyncStep: InitSyncStep,
val percentProgress: Int = 0
) : InitialSyncStatus()

View file

@ -71,7 +71,7 @@ internal class StreamEventsManager @Inject constructor() {
coroutineScope.launch {
listeners.forEach {
tryOrNull {
it.onEventDecrypted(event.eventId ?: "", event.roomId ?: "", result.clearEvent)
it.onEventDecrypted(event, result.clearEvent)
}
}
}
@ -82,7 +82,7 @@ internal class StreamEventsManager @Inject constructor() {
coroutineScope.launch {
listeners.forEach {
tryOrNull {
it.onEventDecryptionError(event.eventId ?: "", event.roomId ?: "", error)
it.onEventDecryptionError(event, error)
}
}
}

View file

@ -72,7 +72,7 @@ internal class DefaultSyncStatusService @Inject constructor() :
// Update the progress of the leaf and all its parents
leaf.setProgress(progress)
// Then update the live data using leaf wording and root progress
status.postValue(SyncStatusService.Status.Progressing(leaf.initSyncStep, root.currentProgress.toInt()))
status.postValue(SyncStatusService.Status.InitialSyncProgressing(leaf.initSyncStep, root.currentProgress.toInt()))
}
}
}

View file

@ -17,9 +17,11 @@
package im.vector.app
import android.content.SharedPreferences
import androidx.lifecycle.asFlow
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.features.rageshake.BugReporter
import im.vector.app.features.rageshake.ReportType
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -34,6 +36,7 @@ import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@ -62,10 +65,11 @@ class AutoRageShaker @Inject constructor(
private val e2eDetectedFlow = MutableSharedFlow<E2EMessageDetected>(replay = 0)
private val matchingRSRequestFlow = MutableSharedFlow<Event>(replay = 0)
private var hasSynced = false
private var preferenceEnabled = false
fun initialize() {
observeActiveSession()
enable(vectorPreferences.labsAutoReportUISI())
preferenceEnabled = vectorPreferences.labsAutoReportUISI()
// It's a singleton...
vectorPreferences.subscribeToChanges(this)
@ -74,7 +78,7 @@ class AutoRageShaker @Inject constructor(
e2eDetectedFlow
.onEach {
sendRageShake(it)
delay(2_000)
delay(60_000)
}
.catch { cause ->
Timber.w(cause, "Failed to RS")
@ -84,7 +88,7 @@ class AutoRageShaker @Inject constructor(
matchingRSRequestFlow
.onEach {
sendMatchingRageShake(it)
delay(2_000)
delay(60_000)
}
.catch { cause ->
Timber.w(cause, "Failed to send matching rageshake")
@ -93,14 +97,7 @@ class AutoRageShaker @Inject constructor(
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
enable(vectorPreferences.labsAutoReportUISI())
}
var _enabled = false
fun enable(enabled: Boolean) {
if (enabled == _enabled) return
_enabled = enabled
detector.enabled = enabled
preferenceEnabled = vectorPreferences.labsAutoReportUISI()
}
private fun observeActiveSession() {
@ -115,7 +112,6 @@ class AutoRageShaker @Inject constructor(
}
fun decryptionErrorDetected(target: E2EMessageDetected) {
if (target.source == UISIEventSource.INITIAL_SYNC) return
if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return
val shouldSendRS = synchronized(alreadyReportedUisi) {
val reportInfo = ReportInfo(target.roomId, target.sessionId)
@ -148,7 +144,6 @@ class AutoRageShaker @Inject constructor(
append("\"room_id\": \"${target.roomId}\",")
append("\"sender_key\": \"${target.senderKey}\",")
append("\"device_id\": \"${target.senderDeviceId}\",")
append("\"source\": \"${target.source}\",")
append("\"user_id\": \"${target.senderUserId}\",")
append("\"session_id\": \"${target.sessionId}\"")
append("}")
@ -245,6 +240,9 @@ class AutoRageShaker @Inject constructor(
override val reciprocateToDeviceEventType: String
get() = AUTO_RS_REQUEST
override val enabled: Boolean
get() = this@AutoRageShaker.preferenceEnabled && this@AutoRageShaker.hasSynced
override fun uisiDetected(source: E2EMessageDetected) {
decryptionErrorDetected(source)
}
@ -261,7 +259,14 @@ class AutoRageShaker @Inject constructor(
return
}
this.currentActiveSessionId = sessionId
this.detector.enabled = _enabled
hasSynced = session.hasAlreadySynced()
session.getSyncStatusLive()
.asFlow()
.onEach {
hasSynced = it !is SyncStatusService.Status.InitialSyncProgressing
}
.launchIn(session.coroutineScope)
activeSessionIds.add(sessionId)
session.addListener(this)
session.addEventStreamListener(detector)

View file

@ -16,6 +16,7 @@
package im.vector.app
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.LiveEventListener
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel
@ -26,23 +27,17 @@ import java.util.Timer
import java.util.TimerTask
import java.util.concurrent.Executors
enum class UISIEventSource {
INITIAL_SYNC,
INCREMENTAL_SYNC,
PAGINATION
}
data class E2EMessageDetected(
val eventId: String,
val roomId: String,
val senderUserId: String,
val senderDeviceId: String,
val senderKey: String,
val sessionId: String,
val source: UISIEventSource) {
val sessionId: String
) {
companion object {
fun fromEvent(event: Event, roomId: String, source: UISIEventSource): E2EMessageDetected {
fun fromEvent(event: Event, roomId: String): E2EMessageDetected {
val encryptedContent = event.content.toModel<EncryptedEventContent>()
return E2EMessageDetected(
@ -51,8 +46,7 @@ data class E2EMessageDetected(
senderUserId = event.senderId ?: "",
senderDeviceId = encryptedContent?.deviceId ?: "",
senderKey = encryptedContent?.senderKey ?: "",
sessionId = encryptedContent?.sessionId ?: "",
source = source
sessionId = encryptedContent?.sessionId ?: ""
)
}
}
@ -61,6 +55,7 @@ data class E2EMessageDetected(
class UISIDetector : LiveEventListener {
interface UISIDetectorCallback {
val enabled: Boolean
val reciprocateToDeviceEventType: String
fun uisiDetected(source: E2EMessageDetected)
fun uisiReciprocateRequest(source: Event)
@ -68,30 +63,16 @@ class UISIDetector : LiveEventListener {
var callback: UISIDetectorCallback? = null
private val trackedEvents = mutableListOf<Pair<E2EMessageDetected, TimerTask>>()
private val trackedEvents = mutableMapOf<String, TimerTask>()
private val executor = Executors.newSingleThreadExecutor()
private val timer = Timer()
private val timeoutMillis = 30_000L
var enabled = false
private val enabled: Boolean get() = callback?.enabled.orFalse()
override fun onLiveEvent(roomId: String, event: Event) {
if (!enabled) return
if (!event.isEncrypted()) return
executor.execute {
handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.INCREMENTAL_SYNC))
}
}
override fun onPaginatedEvent(roomId: String, event: Event) {
if (!enabled) return
if (!event.isEncrypted()) return
executor.execute {
handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.PAGINATION))
}
}
override fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict) {
if (!enabled) return
override fun onEventDecrypted(event: Event, clearEvent: JsonDict) {
val eventId = event.eventId
val roomId = event.roomId
if (!enabled || eventId == null || roomId == null) return
executor.execute {
unTrack(eventId, roomId)
}
@ -104,57 +85,43 @@ class UISIDetector : LiveEventListener {
}
}
override fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) {
if (!enabled) return
executor.execute {
unTrack(eventId, roomId)?.let {
triggerUISI(it)
}
// if (throwable is MXCryptoError.OlmError) {
// if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
// unTrack(eventId, roomId)?.let {
// triggerUISI(it)
// }
// }
// }
}
}
override fun onEventDecryptionError(event: Event, throwable: Throwable) {
val eventId = event.eventId
val roomId = event.roomId
if (!enabled || eventId == null || roomId == null) return
private fun handleEventReceived(detectorEvent: E2EMessageDetected) {
if (!enabled) return
if (trackedEvents.any { it.first == detectorEvent }) {
Timber.w("## UISIDetector: Event ${detectorEvent.eventId} is already tracked")
} else {
// track it and start timer
val timeoutTask = object : TimerTask() {
override fun run() {
executor.execute {
unTrack(detectorEvent.eventId, detectorEvent.roomId)
Timber.v("## UISIDetector: Timeout on ${detectorEvent.eventId} ")
triggerUISI(detectorEvent)
}
val trackerId: String = trackerId(eventId, roomId)
if (trackedEvents.containsKey(trackerId)) {
Timber.w("## UISIDetector: Event $eventId is already tracked")
return
}
// track it and start timer
val timeoutTask = object : TimerTask() {
override fun run() {
executor.execute {
unTrack(eventId, roomId)
Timber.v("## UISIDetector: Timeout on $eventId")
triggerUISI(E2EMessageDetected.fromEvent(event, roomId))
}
}
trackedEvents.add(detectorEvent to timeoutTask)
timer.schedule(timeoutTask, timeoutMillis)
}
trackedEvents[trackerId] = timeoutTask
timer.schedule(timeoutTask, timeoutMillis)
}
override fun onLiveEvent(roomId: String, event: Event) { }
override fun onPaginatedEvent(roomId: String, event: Event) { }
private fun trackerId(eventId: String, roomId: String): String = "$roomId-$eventId"
private fun triggerUISI(source: E2EMessageDetected) {
if (!enabled) return
Timber.i("## UISIDetector: Unable To Decrypt $source")
callback?.uisiDetected(source)
}
private fun unTrack(eventId: String, roomId: String): E2EMessageDetected? {
val index = trackedEvents.indexOfFirst { it.first.eventId == eventId && it.first.roomId == roomId }
return if (index != -1) {
trackedEvents.removeAt(index).let {
it.second.cancel()
it.first
}
} else {
null
}
private fun unTrack(eventId: String, roomId: String) {
trackedEvents.remove(trackerId(eventId, roomId))?.cancel()
}
}

View file

@ -352,7 +352,7 @@ class HomeActivity :
private fun renderState(state: HomeActivityViewState) {
when (val status = state.syncStatusServiceStatus) {
is SyncStatusService.Status.Progressing -> {
is SyncStatusService.Status.InitialSyncProgressing -> {
val initSyncStepStr = initSyncStepFormatter.format(status.initSyncStep)
Timber.v("$initSyncStepStr ${status.percentProgress}")
views.waitingView.root.setOnClickListener {

View file

@ -179,11 +179,11 @@ class HomeActivityViewModel @AssistedInject constructor(
.asFlow()
.onEach { status ->
when (status) {
is SyncStatusService.Status.Progressing -> {
is SyncStatusService.Status.InitialSyncProgressing -> {
// Schedule a check of the bootstrap when the init sync will be finished
checkBootstrap = true
}
is SyncStatusService.Status.Idle -> {
is SyncStatusService.Status.Idle -> {
if (checkBootstrap) {
checkBootstrap = false
maybeBootstrapCrossSigningAfterInitialSync()