Fix handling of early receipts.

We were storing the early content under the wrong recipient.
This commit is contained in:
Greyson Parrelli 2022-08-02 10:46:45 -04:00
parent 370c2b941c
commit a42c3d7ce8
5 changed files with 74 additions and 112 deletions

View file

@ -45,6 +45,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -128,7 +129,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
public abstract void markGiftRedemptionFailed(long messageId);
public abstract Set<MessageUpdate> incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType, boolean storiesOnly);
abstract @NonNull MmsSmsDatabase.TimestampReadResult setTimestampRead(SyncMessageId messageId, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead);
public abstract List<MarkedMessageInfo> setEntireThreadRead(long threadId);
public abstract List<MarkedMessageInfo> setMessagesReadSince(long threadId, long timestamp);
public abstract List<MarkedMessageInfo> setAllMessagesRead();
@ -276,6 +277,50 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
}
}
/**
* Handles a synchronized read message.
* @param messageId An id representing the author-timestamp pair of the message that was read on a linked device. Note that the author could be self when
* syncing read receipts for reactions.
*/
final @NonNull MmsSmsDatabase.TimestampReadResult setTimestampReadFromSyncMessage(SyncMessageId messageId, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead) {
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
List<Pair<Long, Long>> expiring = new LinkedList<>();
String[] projection = new String[] { ID, THREAD_ID, EXPIRES_IN, EXPIRE_STARTED };
String query = getDateSentColumnName() + " = ? AND (" + RECIPIENT_ID + " = ? OR (" + RECIPIENT_ID + " = ? AND " + getOutgoingTypeClause() + "))";
String[] args = SqlUtil.buildArgs(messageId.getTimetamp(), messageId.getRecipientId(), Recipient.self().getId());
List<Long> threads = new LinkedList<>();
try (Cursor cursor = database.query(getTableName(), projection, query, args, null, null, null)) {
while (cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED));
expireStarted = expireStarted > 0 ? Math.min(proposedExpireStarted, expireStarted) : proposedExpireStarted;
ContentValues values = new ContentValues();
values.put(READ, 1);
values.put(REACTIONS_UNREAD, 0);
values.put(REACTIONS_LAST_SEEN, System.currentTimeMillis());
if (expiresIn > 0) {
values.put(EXPIRE_STARTED, expireStarted);
expiring.add(new Pair<>(id, expiresIn));
}
database.update(getTableName(), values, ID_WHERE, SqlUtil.buildArgs(id));
threads.add(threadId);
Long latest = threadToLatestRead.get(threadId);
threadToLatestRead.put(threadId, (latest != null) ? Math.max(latest, messageId.getTimetamp()) : messageId.getTimetamp());
}
}
return new MmsSmsDatabase.TimestampReadResult(expiring, threads);
}
private int getMessageCountForRecipientsAndType(String typeClause) {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();

View file

@ -1538,51 +1538,6 @@ public class MmsDatabase extends MessageDatabase {
return result;
}
@Override
@NonNull MmsSmsDatabase.TimestampReadResult setTimestampRead(SyncMessageId messageId, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead) {
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
List<Pair<Long, Long>> expiring = new LinkedList<>();
String[] projection = new String[] { ID, THREAD_ID, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED, RECIPIENT_ID };
String query = DATE_SENT + " = ?";
String[] args = SqlUtil.buildArgs(messageId.getTimetamp());
List<Long> threads = new LinkedList<>();
try (Cursor cursor = database.query(TABLE_NAME, projection, query, args, null, null, null)) {
while (cursor.moveToNext()) {
RecipientId theirRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID)));
RecipientId ourRecipientId = messageId.getRecipientId();
if (ourRecipientId.equals(theirRecipientId) || Recipient.resolved(theirRecipientId).isGroup() || ourRecipientId.equals(Recipient.self().getId())) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED));
expireStarted = expireStarted > 0 ? Math.min(proposedExpireStarted, expireStarted) : proposedExpireStarted;
ContentValues values = new ContentValues();
values.put(READ, 1);
values.put(REACTIONS_UNREAD, 0);
values.put(REACTIONS_LAST_SEEN, System.currentTimeMillis());
if (expiresIn > 0) {
values.put(EXPIRE_STARTED, expireStarted);
expiring.add(new Pair<>(id, expiresIn));
}
database.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(id));
threads.add(threadId);
Long latest = threadToLatestRead.get(threadId);
threadToLatestRead.put(threadId, (latest != null) ? Math.max(latest, messageId.getTimetamp()) : messageId.getTimetamp());
}
}
}
return new MmsSmsDatabase.TimestampReadResult(expiring, threads);
}
@Override
public @Nullable Pair<RecipientId, Long> getOldestUnreadMentionDetails(long threadId) {
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();

View file

@ -45,7 +45,6 @@ import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@ -617,7 +616,7 @@ public class MmsSmsDatabase extends Database {
/**
* @return Unhandled ids
*/
public Collection<SyncMessageId> setTimestampRead(@NonNull Recipient senderRecipient, @NonNull List<ReadMessage> readMessages, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead) {
public Collection<SyncMessageId> setTimestampReadFromSyncMessage(@NonNull List<ReadMessage> readMessages, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead) {
SQLiteDatabase db = getWritableDatabase();
List<Pair<Long, Long>> expiringText = new LinkedList<>();
@ -628,12 +627,13 @@ public class MmsSmsDatabase extends Database {
db.beginTransaction();
try {
for (ReadMessage readMessage : readMessages) {
TimestampReadResult textResult = SignalDatabase.sms().setTimestampRead(new SyncMessageId(senderRecipient.getId(), readMessage.getTimestamp()),
proposedExpireStarted,
threadToLatestRead);
TimestampReadResult mediaResult = SignalDatabase.mms().setTimestampRead(new SyncMessageId(senderRecipient.getId(), readMessage.getTimestamp()),
proposedExpireStarted,
threadToLatestRead);
RecipientId authorId = Recipient.externalPush(readMessage.getSender()).getId();
TimestampReadResult textResult = SignalDatabase.sms().setTimestampReadFromSyncMessage(new SyncMessageId(authorId, readMessage.getTimestamp()),
proposedExpireStarted,
threadToLatestRead);
TimestampReadResult mediaResult = SignalDatabase.mms().setTimestampReadFromSyncMessage(new SyncMessageId(authorId, readMessage.getTimestamp()),
proposedExpireStarted,
threadToLatestRead);
expiringText.addAll(textResult.expiring);
expiringMedia.addAll(mediaResult.expiring);
@ -642,7 +642,7 @@ public class MmsSmsDatabase extends Database {
updatedThreads.addAll(mediaResult.threads);
if (textResult.threads.isEmpty() && mediaResult.threads.isEmpty()) {
unhandled.add(new SyncMessageId(senderRecipient.getId(), readMessage.getTimestamp()));
unhandled.add(new SyncMessageId(authorId, readMessage.getTimestamp()));
}
}

View file

@ -548,51 +548,6 @@ public class SmsDatabase extends MessageDatabase {
}
}
@Override
@NonNull MmsSmsDatabase.TimestampReadResult setTimestampRead(SyncMessageId messageId, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead) {
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
List<Pair<Long, Long>> expiring = new LinkedList<>();
String[] projection = new String[] {ID, THREAD_ID, RECIPIENT_ID, TYPE, EXPIRES_IN, EXPIRE_STARTED};
String query = DATE_SENT + " = ?";
String[] args = SqlUtil.buildArgs(messageId.getTimetamp());
List<Long> threads = new LinkedList<>();
try (Cursor cursor = database.query(TABLE_NAME, projection, query, args, null, null, null)) {
while (cursor.moveToNext()) {
RecipientId theirRecipientId = messageId.getRecipientId();
RecipientId outRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID)));
if (outRecipientId.equals(theirRecipientId) || theirRecipientId.equals(Recipient.self().getId())) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED));
expireStarted = expireStarted > 0 ? Math.min(proposedExpireStarted, expireStarted) : proposedExpireStarted;
ContentValues contentValues = new ContentValues();
contentValues.put(READ, 1);
contentValues.put(REACTIONS_UNREAD, 0);
contentValues.put(REACTIONS_LAST_SEEN, System.currentTimeMillis());
if (expiresIn > 0) {
contentValues.put(EXPIRE_STARTED, expireStarted);
expiring.add(new Pair<>(id, expiresIn));
}
database.update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(id));
threads.add(threadId);
Long latest = threadToLatestRead.get(threadId);
threadToLatestRead.put(threadId, (latest != null) ? Math.max(latest, messageId.getTimetamp()) : messageId.getTimetamp());
}
}
}
return new MmsSmsDatabase.TimestampReadResult(expiring, threads);
}
@Override
public List<MarkedMessageInfo> setEntireThreadRead(long threadId) {
return setMessagesRead(THREAD_ID + " = ?", new String[] {String.valueOf(threadId)});

View file

@ -338,7 +338,7 @@ public final class MessageContentProcessor {
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get(), senderRecipient);
else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get(), content.getTimestamp());
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(content, syncMessage.getRead().get(), content.getTimestamp(), senderRecipient);
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(content, syncMessage.getRead().get(), content.getTimestamp());
else if (syncMessage.getViewed().isPresent()) handleSynchronizeViewedMessage(syncMessage.getViewed().get(), content.getTimestamp());
else if (syncMessage.getViewOnceOpen().isPresent()) handleSynchronizeViewOnceOpenMessage(content, syncMessage.getViewOnceOpen().get(), content.getTimestamp());
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
@ -1279,14 +1279,13 @@ public final class MessageContentProcessor {
private void handleSynchronizeReadMessage(@NonNull SignalServiceContent content,
@NonNull List<ReadMessage> readMessages,
long envelopeTimestamp,
@NonNull Recipient senderRecipient)
long envelopeTimestamp)
{
log(envelopeTimestamp, "Synchronize read message. Count: " + readMessages.size() + ", Timestamps: " + Stream.of(readMessages).map(ReadMessage::getTimestamp).toList());
Map<Long, Long> threadToLatestRead = new HashMap<>();
Collection<SyncMessageId> unhandled = SignalDatabase.mmsSms().setTimestampRead(senderRecipient, readMessages, envelopeTimestamp, threadToLatestRead);
Collection<SyncMessageId> unhandled = SignalDatabase.mmsSms().setTimestampReadFromSyncMessage(readMessages, envelopeTimestamp, threadToLatestRead);
List<MessageDatabase.MarkedMessageInfo> markedMessages = SignalDatabase.threads().setReadSince(threadToLatestRead, false);
@ -2521,10 +2520,14 @@ public final class MessageContentProcessor {
SignalDatabase.mmsSms().updateViewedStories(handled);
for (SyncMessageId id : unhandled) {
warn(String.valueOf(content.getTimestamp()), "[handleViewedReceipt] Could not find matching message! timestamp: " + id.getTimetamp() + " author: " + id.getRecipientId());
if (!processingEarlyContent) {
ApplicationDependencies.getEarlyMessageCache().store(id.getRecipientId(), id.getTimetamp(), content);
if (unhandled.size() > 0) {
RecipientId selfId = Recipient.self().getId();
for (SyncMessageId id : unhandled) {
warn(String.valueOf(content.getTimestamp()), "[handleViewedReceipt] Could not find matching message! timestamp: " + id.getTimetamp() + ", author: " + id.getRecipientId() + " | Receipt so associating with message from self (" + selfId + ")");
if (!processingEarlyContent) {
ApplicationDependencies.getEarlyMessageCache().store(selfId, id.getTimetamp(), content);
}
}
}
@ -2576,10 +2579,14 @@ public final class MessageContentProcessor {
Collection<SyncMessageId> unhandled = SignalDatabase.mmsSms().incrementReadReceiptCounts(ids, content.getTimestamp());
for (SyncMessageId id : unhandled) {
warn(String.valueOf(content.getTimestamp()), "[handleReadReceipt] Could not find matching message! timestamp: " + id.getTimetamp() + " author: " + id.getRecipientId());
if (!processingEarlyContent) {
ApplicationDependencies.getEarlyMessageCache().store(id.getRecipientId(), id.getTimetamp(), content);
if (unhandled.size() > 0) {
RecipientId selfId = Recipient.self().getId();
for (SyncMessageId id : unhandled) {
warn(String.valueOf(content.getTimestamp()), "[handleReadReceipt] Could not find matching message! timestamp: " + id.getTimetamp() + ", author: " + id.getRecipientId() + " | Receipt, so associating with message from self (" + selfId + ")");
if (!processingEarlyContent) {
ApplicationDependencies.getEarlyMessageCache().store(selfId, id.getTimetamp(), content);
}
}
}