Skip to content

Commit

Permalink
Fix handling of early receipts.
Browse files Browse the repository at this point in the history
We were storing the early content under the wrong recipient.
  • Loading branch information
greyson-signal committed Aug 2, 2022
1 parent 370c2b9 commit a42c3d7
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 112 deletions.
Expand Up @@ -45,6 +45,7 @@
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;
Expand Down Expand Up @@ -128,7 +129,7 @@ public MessageDatabase(Context context, SignalDatabase databaseHelper) {
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();
Expand Down Expand Up @@ -276,6 +277,50 @@ final int getOutgoingSecureMessageCount(long threadId) {
}
}

/**
* 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();
Expand Down
Expand Up @@ -1538,51 +1538,6 @@ private List<MarkedMessageInfo> setMessagesRead(String where, String[] arguments
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();
Expand Down
Expand Up @@ -45,7 +45,6 @@
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;
Expand Down Expand Up @@ -617,7 +616,7 @@ public void updateViewedStories(@NonNull Set<SyncMessageId> syncMessageIds) {
/**
* @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<>();
Expand All @@ -628,12 +627,13 @@ public Collection<SyncMessageId> setTimestampRead(@NonNull Recipient senderRecip
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);
Expand All @@ -642,7 +642,7 @@ public Collection<SyncMessageId> setTimestampRead(@NonNull Recipient senderRecip
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()));
}
}

Expand Down
Expand Up @@ -548,51 +548,6 @@ public void markAsNotified(long id) {
}
}

@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)});
Expand Down
Expand Up @@ -338,7 +338,7 @@ private void handleMessage(@NonNull SignalServiceContent content, long timestamp

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());
Expand Down Expand Up @@ -1279,14 +1279,13 @@ private void handleSynchronizeRequestMessage(@NonNull RequestMessage message, lo

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);

Expand Down Expand Up @@ -2521,10 +2520,14 @@ private void handleViewedReceipt(@NonNull SignalServiceContent content,

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);
}
}
}

Expand Down Expand Up @@ -2576,10 +2579,14 @@ private void handleReadReceipt(@NonNull SignalServiceContent content,

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);
}
}
}

Expand Down

0 comments on commit a42c3d7

Please sign in to comment.