Skip to content

Commit 4abf39b

Browse files
authored
Merge pull request #6486 from nextcloud/guardReadMessage
fix(chat): guard localLastReadMessage against implausible message ids
2 parents 4d2d3e7 + d747ce3 commit 4abf39b

2 files changed

Lines changed: 72 additions & 1 deletion

File tree

app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1796,6 +1796,18 @@ class ChatViewModel @AssistedInject constructor(
17961796
fun advanceLocalLastReadMessageIfNeeded(messageId: Int) {
17971797
Log.d(TAG, "advanceLocalLastReadMessageIfNeeded, messageId: $messageId")
17981798
Log.d(TAG, "advanceLocalLastReadMessageIfNeeded, localLastReadMessage: $localLastReadMessage")
1799+
1800+
val newestKnownRealMessageId = _uiState.value.conversation?.lastMessage?.id
1801+
1802+
if (!isPlausibleLastReadMessageId(messageId, newestKnownRealMessageId)) {
1803+
logger.w(
1804+
TAG,
1805+
"advanceLocalLastReadMessageIfNeeded, messageId ($messageId) is implausibly higher than the " +
1806+
"conversation's newest known message id ($newestKnownRealMessageId). We won't advance."
1807+
)
1808+
return
1809+
}
1810+
17991811
if (localLastReadMessage < messageId && -1 < messageId) {
18001812
Log.d(TAG, "advanceLocalLastReadMessageIfNeeded, setting localLastReadMessage to $messageId")
18011813
localLastReadMessage = messageId
@@ -2390,7 +2402,7 @@ class ChatViewModel @AssistedInject constructor(
23902402
}
23912403

23922404
companion object {
2393-
private val TAG = ChatViewModel::class.simpleName
2405+
private val TAG = ChatViewModel::class.java.simpleName
23942406
const val JOIN_ROOM_RETRY_COUNT: Long = 3
23952407
const val HTTP_CODE_OK: Int = 200
23962408
private const val CONVERSATION_AND_USER_FLOW_SHARING_TIMEOUT_MS = 5_000L
@@ -2406,6 +2418,11 @@ class ChatViewModel @AssistedInject constructor(
24062418
private const val LOAD_MORE_MESSAGES_LIMIT = 100
24072419
private const val POST_UPLOAD_FETCH_MAX_ATTEMPTS = 4
24082420
private const val POST_UPLOAD_FETCH_RETRY_DELAY_MS = 1_500L
2421+
2422+
private const val PLAUSIBLE_MESSAGE_ID_BUFFER = 10_000L
2423+
2424+
fun isPlausibleLastReadMessageId(messageId: Int, newestKnownRealMessageId: Long?): Boolean =
2425+
newestKnownRealMessageId == null || messageId <= newestKnownRealMessageId + PLAUSIBLE_MESSAGE_ID_BUFFER
24092426
}
24102427

24112428
sealed class OutOfOfficeUIState {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Nextcloud Talk - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: GPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.talk.chat.viewmodels
9+
10+
import org.junit.Assert.assertFalse
11+
import org.junit.Assert.assertTrue
12+
import org.junit.Test
13+
14+
class ChatViewModelTest {
15+
16+
@Test
17+
fun `isPlausibleLastReadMessageId returns true when there is no known real message id yet`() {
18+
assertTrue(ChatViewModel.isPlausibleLastReadMessageId(messageId = 158761, newestKnownRealMessageId = null))
19+
}
20+
21+
@Test
22+
fun `isPlausibleLastReadMessageId returns true for the newest known real message id itself`() {
23+
assertTrue(ChatViewModel.isPlausibleLastReadMessageId(messageId = 158761, newestKnownRealMessageId = 158761L))
24+
}
25+
26+
@Test
27+
fun `isPlausibleLastReadMessageId returns true for an older message id`() {
28+
assertTrue(ChatViewModel.isPlausibleLastReadMessageId(messageId = 100, newestKnownRealMessageId = 158761L))
29+
}
30+
31+
@Test
32+
fun `isPlausibleLastReadMessageId returns true within the buffer above the newest known message id`() {
33+
assertTrue(
34+
ChatViewModel.isPlausibleLastReadMessageId(messageId = 158761 + 2000, newestKnownRealMessageId = 158761L)
35+
)
36+
}
37+
38+
@Test
39+
fun `isPlausibleLastReadMessageId returns false just beyond the buffer above the newest known message id`() {
40+
assertFalse(
41+
ChatViewModel.isPlausibleLastReadMessageId(
42+
messageId = 158761 + 2000 + 1,
43+
newestKnownRealMessageId = 158761L
44+
)
45+
)
46+
}
47+
48+
@Test
49+
fun `isPlausibleLastReadMessageId rejects a hash-derived placeholder id far beyond the real message id space`() {
50+
assertFalse(
51+
ChatViewModel.isPlausibleLastReadMessageId(messageId = 1_963_726_147, newestKnownRealMessageId = 158761L)
52+
)
53+
}
54+
}

0 commit comments

Comments
 (0)