Skip to content

Commit 2da109c

Browse files
authored
Merge pull request #5886 from vector-im/feature/bca/fix_graceperiod_autorageshake
Fix UISIDetector grace period bug
2 parents cbc29d0 + f57e20c commit 2da109c

File tree

3 files changed

+136
-8
lines changed

3 files changed

+136
-8
lines changed

changelog.d/5886.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix UISIDetector grace period bug

vector/src/main/java/im/vector/app/UISIDetector.kt

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ data class E2EMessageDetected(
5252
}
5353
}
5454

55-
class UISIDetector : LiveEventListener {
55+
class UISIDetector(private val timeoutMillis: Long = 30_000L) : LiveEventListener {
5656

5757
interface UISIDetectorCallback {
5858
val enabled: Boolean
@@ -66,7 +66,6 @@ class UISIDetector : LiveEventListener {
6666
private val trackedEvents = mutableMapOf<String, TimerTask>()
6767
private val executor = Executors.newSingleThreadExecutor()
6868
private val timer = Timer()
69-
private val timeoutMillis = 30_000L
7069
private val enabled: Boolean get() = callback?.enabled.orFalse()
7170

7271
override fun onEventDecrypted(event: Event, clearEvent: JsonDict) {
@@ -90,30 +89,35 @@ class UISIDetector : LiveEventListener {
9089
val roomId = event.roomId
9190
if (!enabled || eventId == null || roomId == null) return
9291

93-
val trackerId: String = trackerId(eventId, roomId)
94-
if (trackedEvents.containsKey(trackerId)) {
95-
Timber.w("## UISIDetector: Event $eventId is already tracked")
92+
val trackedId: String = trackedId(eventId, roomId)
93+
if (trackedEvents.containsKey(trackedId)) {
94+
Timber.v("## UISIDetector: Event $eventId is already tracked")
9695
return
9796
}
9897
// track it and start timer
9998
val timeoutTask = object : TimerTask() {
10099
override fun run() {
101100
executor.execute {
101+
// we should check if it's still tracked (it might have been decrypted)
102+
if (!trackedEvents.containsKey(trackedId)) {
103+
Timber.v("## UISIDetector: E2E error for $eventId was resolved")
104+
return@execute
105+
}
102106
unTrack(eventId, roomId)
103107
Timber.v("## UISIDetector: Timeout on $eventId")
104108
triggerUISI(E2EMessageDetected.fromEvent(event, roomId))
105109
}
106110
}
107111
}
108-
trackedEvents[trackerId] = timeoutTask
112+
trackedEvents[trackedId] = timeoutTask
109113
timer.schedule(timeoutTask, timeoutMillis)
110114
}
111115

112116
override fun onLiveEvent(roomId: String, event: Event) {}
113117

114118
override fun onPaginatedEvent(roomId: String, event: Event) {}
115119

116-
private fun trackerId(eventId: String, roomId: String): String = "$roomId-$eventId"
120+
private fun trackedId(eventId: String, roomId: String): String = "$roomId-$eventId"
117121

118122
private fun triggerUISI(source: E2EMessageDetected) {
119123
if (!enabled) return
@@ -122,6 +126,6 @@ class UISIDetector : LiveEventListener {
122126
}
123127

124128
private fun unTrack(eventId: String, roomId: String) {
125-
trackedEvents.remove(trackerId(eventId, roomId))?.cancel()
129+
trackedEvents.remove(trackedId(eventId, roomId))?.cancel()
126130
}
127131
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright (c) 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.crypto
18+
19+
import im.vector.app.E2EMessageDetected
20+
import im.vector.app.UISIDetector
21+
import kotlinx.coroutines.CoroutineScope
22+
import kotlinx.coroutines.Dispatchers
23+
import kotlinx.coroutines.SupervisorJob
24+
import kotlinx.coroutines.delay
25+
import kotlinx.coroutines.launch
26+
import kotlinx.coroutines.runBlocking
27+
import org.amshove.kluent.fail
28+
import org.junit.Assert
29+
import org.junit.Test
30+
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
31+
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
32+
import org.matrix.android.sdk.api.session.events.model.Event
33+
import org.matrix.android.sdk.api.session.events.model.EventType
34+
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
35+
import org.matrix.android.sdk.api.session.events.model.toContent
36+
37+
class UISIDetectorTest {
38+
39+
@Test
40+
fun `trigger detection after grace period`() {
41+
val gracePeriod = 5_000L
42+
val uisiDetector = UISIDetector(gracePeriod)
43+
var detectedEvent: E2EMessageDetected? = null
44+
45+
uisiDetector.callback = object : UISIDetector.UISIDetectorCallback {
46+
override val enabled = true
47+
override val reciprocateToDeviceEventType = "foo"
48+
49+
override fun uisiDetected(source: E2EMessageDetected) {
50+
detectedEvent = source
51+
}
52+
53+
override fun uisiReciprocateRequest(source: Event) {
54+
// nop
55+
}
56+
}
57+
58+
// report a decryption error
59+
val eventId = "0001"
60+
val event = fakeEncryptedEvent(eventId, "s1", "r1")
61+
uisiDetector.onEventDecryptionError(event, fakeCryptoError())
62+
63+
runBlocking {
64+
delay((gracePeriod * 1.2).toLong())
65+
}
66+
Assert.assertEquals(eventId, detectedEvent?.eventId)
67+
}
68+
69+
@Test
70+
fun `If event decrypted during grace period should not trigger detection`() {
71+
val scope = CoroutineScope(SupervisorJob())
72+
val gracePeriod = 5_000L
73+
val uisiDetector = UISIDetector(gracePeriod)
74+
75+
uisiDetector.callback = object : UISIDetector.UISIDetectorCallback {
76+
override val enabled = true
77+
override val reciprocateToDeviceEventType = "foo"
78+
79+
override fun uisiDetected(source: E2EMessageDetected) {
80+
fail("Shouldn't trigger")
81+
}
82+
83+
override fun uisiReciprocateRequest(source: Event) {
84+
// nop
85+
}
86+
}
87+
88+
// report a decryption error
89+
val event = fakeEncryptedEvent("0001", "s1", "r1")
90+
uisiDetector.onEventDecryptionError(event, fakeCryptoError())
91+
92+
// the grace period is 30s
93+
scope.launch(Dispatchers.Default) {
94+
delay((gracePeriod * 0.5).toLong())
95+
uisiDetector.onEventDecrypted(event, emptyMap())
96+
}
97+
98+
runBlocking {
99+
delay((gracePeriod * 1.2).toLong())
100+
}
101+
}
102+
103+
private fun fakeEncryptedEvent(eventId: String, sessionId: String, roomId: String): Event {
104+
return Event(
105+
type = EventType.ENCRYPTED,
106+
eventId = eventId,
107+
roomId = roomId,
108+
content = EncryptedEventContent(
109+
algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
110+
ciphertext = "AwgBEpACQEKOkd4Gp0+gSXG4M+btcrnPgsF23xs/lUmS2I4YjmqF...",
111+
sessionId = sessionId,
112+
senderKey = "5e3EIqg3JfooZnLQ2qHIcBarbassQ4qXblai0",
113+
deviceId = "FAKEE"
114+
).toContent()
115+
)
116+
}
117+
118+
private fun fakeCryptoError(error: MXCryptoError.ErrorType = MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) = MXCryptoError.Base(
119+
error,
120+
"A description",
121+
"Human readable"
122+
)
123+
}

0 commit comments

Comments
 (0)