Allow for MSL entries to be associated with multiple messages.

This commit is contained in:
Greyson Parrelli 2021-06-29 16:38:24 -04:00 committed by Alex Hart
parent 92e8f9de0e
commit 5372f79c40
6 changed files with 176 additions and 89 deletions

View file

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database
import android.content.ContentValues
import android.content.Context
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageLogEntry
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
@ -17,41 +18,51 @@ import java.util.UUID
/**
* Stores a 24-hr buffer of all outgoing messages. Used for the retry logic required for sender key.
*
* General note: This class is actually two tables -- one to store the entry, and another to store all the devices that were sent it.
* General note: This class is actually three tables:
* - one to store the entry
* - one to store all the devices that were sent it, and
* - one to store the set of related messages.
*
* The general lifecycle of entries in the store goes something like this:
* - Upon sending a message, throw an entry in the 'message table' and throw an entry for each recipient you sent it to in the 'recipient table'
* - Upon sending a message, put an entry in the 'payload table', an entry for each recipient you sent it to in the 'recipient table', and an entry for each
* related message in the 'message table'
* - Whenever you get a delivery receipt, delete the entries in the 'recipient table'
* - Whenever there's no more records in the 'recipient table' for a given message, delete the entry in the 'message table'
* - Whenever you delete a message, delete the entry in the 'message table'
* - Whenever you delete a message, delete the relevant entries from the 'payload table'
* - Whenever you read an entry from the table, first trim off all the entries that are too old
*
* Because of all of this, you can be sure that if an entry is in this store, it's safe to resend to someone upon request
*
* Worth noting that we use triggers + foreign keys to make sure entries in this table are properly cleaned up. Triggers for when you delete a message, and
* a cascading delete foreign key between these two tables.
* cascading delete foreign keys between these three tables.
*
* Performance considerations:
* - The most common operations by far are:
* - Inserting into the table
* - Deleting a recipient (in response to a delivery receipt)
* - We should also optimize for when we delete messages from the sms/mms tables, since you can delete a bunch at once
* - We *don't* really need to optimize for retrieval, since that happens very infrequently. In particular, we don't want to slow down inserts in order to
* improve retrieval time. That means we shouldn't be adding indexes that optimize for retrieval.
*/
class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLCipherOpenHelper?) : Database(context, databaseHelper) {
companion object {
@JvmField
val CREATE_TABLE: Array<String> = arrayOf(MessageTable.CREATE_TABLE, RecipientTable.CREATE_TABLE)
val CREATE_TABLE: Array<String> = arrayOf(PayloadTable.CREATE_TABLE, RecipientTable.CREATE_TABLE, MessageTable.CREATE_TABLE)
@JvmField
val CREATE_INDEXES: Array<String> = MessageTable.CREATE_INDEXES + RecipientTable.CREATE_INDEXES
val CREATE_INDEXES: Array<String> = PayloadTable.CREATE_INDEXES + RecipientTable.CREATE_INDEXES + MessageTable.CREATE_INDEXES
@JvmField
val CREATE_TRIGGERS: Array<String> = MessageTable.CREATE_TRIGGERS
val CREATE_TRIGGERS: Array<String> = PayloadTable.CREATE_TRIGGERS
}
private object MessageTable {
const val TABLE_NAME = "message_send_log"
private object PayloadTable {
const val TABLE_NAME = "msl_payload"
const val ID = "_id"
const val DATE_SENT = "date_sent"
const val CONTENT = "content"
const val RELATED_MESSAGE_ID = "related_message_id"
const val IS_RELATED_MESSAGE_MMS = "is_related_message_mms"
const val CONTENT_HINT = "content_hint"
const val CREATE_TABLE = """
@ -59,54 +70,75 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
$ID INTEGER PRIMARY KEY,
$DATE_SENT INTEGER NOT NULL,
$CONTENT BLOB NOT NULL,
$RELATED_MESSAGE_ID INTEGER DEFAULT -1,
$IS_RELATED_MESSAGE_MMS INTEGER DEFAULT 0,
$CONTENT_HINT INTEGER NOT NULL
)
"""
@JvmField
/** Created for [deleteEntriesForRecipient] */
val CREATE_INDEXES = arrayOf(
"CREATE INDEX message_log_date_sent_index ON $TABLE_NAME ($DATE_SENT)",
"CREATE INDEX message_log_related_message_index ON $TABLE_NAME ($RELATED_MESSAGE_ID, $IS_RELATED_MESSAGE_MMS)"
"CREATE INDEX msl_payload_date_sent_index ON $TABLE_NAME ($DATE_SENT)",
)
@JvmField
val CREATE_TRIGGERS = arrayOf(
"""
CREATE TRIGGER msl_sms_delete AFTER DELETE ON ${SmsDatabase.TABLE_NAME}
BEGIN
DELETE FROM $TABLE_NAME WHERE $RELATED_MESSAGE_ID = old.${SmsDatabase.ID} AND $IS_RELATED_MESSAGE_MMS = 0;
DELETE FROM $TABLE_NAME WHERE $ID IN (SELECT ${MessageTable.PAYLOAD_ID} FROM ${MessageTable.TABLE_NAME} WHERE ${MessageTable.MESSAGE_ID} = old.${SmsDatabase.ID} AND ${MessageTable.IS_MMS} = 0);
END
""",
"""
CREATE TRIGGER msl_mms_delete AFTER DELETE ON ${MmsDatabase.TABLE_NAME}
BEGIN
DELETE FROM $TABLE_NAME WHERE $RELATED_MESSAGE_ID = old.${MmsDatabase.ID} AND $IS_RELATED_MESSAGE_MMS = 1;
DELETE FROM $TABLE_NAME WHERE $ID IN (SELECT ${MessageTable.PAYLOAD_ID} FROM ${MessageTable.TABLE_NAME} WHERE ${MessageTable.MESSAGE_ID} = old.${MmsDatabase.ID} AND ${MessageTable.IS_MMS} = 1);
END
"""
)
}
private object RecipientTable {
const val TABLE_NAME = "message_send_log_recipients"
const val TABLE_NAME = "msl_recipient"
const val ID = "_id"
const val MESSAGE_LOG_ID = "message_send_log_id"
const val PAYLOAD_ID = "payload_id"
const val RECIPIENT_ID = "recipient_id"
const val DEVICE = "device"
const val CREATE_TABLE = """
CREATE TABLE $TABLE_NAME (
$ID INTEGER PRIMARY KEY,
$MESSAGE_LOG_ID INTEGER NOT NULL REFERENCES ${MessageTable.TABLE_NAME} (${MessageTable.ID}) ON DELETE CASCADE,
$PAYLOAD_ID INTEGER NOT NULL REFERENCES ${PayloadTable.TABLE_NAME} (${PayloadTable.ID}) ON DELETE CASCADE,
$RECIPIENT_ID INTEGER NOT NULL,
$DEVICE INTEGER NOT NULL
)
"""
/** Created for [deleteEntriesForRecipient] */
val CREATE_INDEXES = arrayOf(
"CREATE INDEX message_send_log_recipients_recipient_index ON $TABLE_NAME ($RECIPIENT_ID, $DEVICE)"
"CREATE INDEX msl_recipient_recipient_index ON $TABLE_NAME ($RECIPIENT_ID, $DEVICE, $PAYLOAD_ID)",
"CREATE INDEX msl_recipient_payload_index ON $TABLE_NAME ($PAYLOAD_ID)"
)
}
private object MessageTable {
const val TABLE_NAME = "msl_message"
const val ID = "_id"
const val PAYLOAD_ID = "payload_id"
const val MESSAGE_ID = "message_id"
const val IS_MMS = "is_mms"
const val CREATE_TABLE = """
CREATE TABLE $TABLE_NAME (
$ID INTEGER PRIMARY KEY,
$PAYLOAD_ID INTEGER NOT NULL REFERENCES ${PayloadTable.TABLE_NAME} (${PayloadTable.ID}) ON DELETE CASCADE,
$MESSAGE_ID INTEGER NOT NULL,
$IS_MMS INTEGER NOT NULL
)
"""
/** Created for [PayloadTable.CREATE_TRIGGERS] and [deleteAllRelatedToMessage] */
val CREATE_INDEXES = arrayOf(
"CREATE INDEX msl_message_message_index ON $TABLE_NAME ($MESSAGE_ID, $IS_MMS, $PAYLOAD_ID)"
)
}
@ -115,7 +147,7 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
if (sendMessageResult.isSuccess && sendMessageResult.success.content.isPresent) {
val recipientDevice = listOf(RecipientDevice(recipientId, sendMessageResult.success.devices))
insert(recipientDevice, sentTimestamp, sendMessageResult.success.content.get(), contentHint, relatedMessageId, isRelatedMessageMms)
insert(recipientDevice, sentTimestamp, sendMessageResult.success.content.get(), contentHint, listOf(MessageId(relatedMessageId, isRelatedMessageMms)))
}
}
@ -140,35 +172,44 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
val content: SignalServiceProtos.Content = results.first { it.isSuccess && it.success.content.isPresent }.success.content.get()
insert(recipientDevices, sentTimestamp, content, contentHint, relatedMessageId, isRelatedMessageMms)
insert(recipientDevices, sentTimestamp, content, contentHint, listOf(MessageId(relatedMessageId, isRelatedMessageMms)))
}
private fun insert(recipients: List<RecipientDevice>, dateSent: Long, content: SignalServiceProtos.Content, contentHint: ContentHint, relatedMessageId: Long, isRelatedMessageMms: Boolean) {
private fun insert(recipients: List<RecipientDevice>, dateSent: Long, content: SignalServiceProtos.Content, contentHint: ContentHint, messageIds: List<MessageId>) {
val db = databaseHelper.writableDatabase
db.beginTransaction()
try {
val logValues = ContentValues().apply {
put(MessageTable.DATE_SENT, dateSent)
put(MessageTable.CONTENT, content.toByteArray())
put(MessageTable.CONTENT_HINT, contentHint.type)
put(MessageTable.RELATED_MESSAGE_ID, relatedMessageId)
put(MessageTable.IS_RELATED_MESSAGE_MMS, if (isRelatedMessageMms) 1 else 0)
val payloadValues = ContentValues().apply {
put(PayloadTable.DATE_SENT, dateSent)
put(PayloadTable.CONTENT, content.toByteArray())
put(PayloadTable.CONTENT_HINT, contentHint.type)
}
val messageLogId: Long = db.insert(MessageTable.TABLE_NAME, null, logValues)
val payloadId: Long = db.insert(PayloadTable.TABLE_NAME, null, payloadValues)
recipients.forEach { recipientDevice ->
recipientDevice.devices.forEach { device ->
val recipientValues = ContentValues()
recipientValues.put(RecipientTable.MESSAGE_LOG_ID, messageLogId)
recipientValues.put(RecipientTable.RECIPIENT_ID, recipientDevice.recipientId.serialize())
recipientValues.put(RecipientTable.DEVICE, device)
val recipientValues = ContentValues().apply {
put(RecipientTable.PAYLOAD_ID, payloadId)
put(RecipientTable.RECIPIENT_ID, recipientDevice.recipientId.serialize())
put(RecipientTable.DEVICE, device)
}
db.insert(RecipientTable.TABLE_NAME, null, recipientValues)
}
}
messageIds.forEach { messageId ->
val messageValues = ContentValues().apply {
put(MessageTable.PAYLOAD_ID, payloadId)
put(MessageTable.MESSAGE_ID, messageId.id)
put(MessageTable.IS_MMS, if (messageId.mms) 1 else 0)
}
db.insert(MessageTable.TABLE_NAME, null, messageValues)
}
db.setTransactionSuccessful()
} finally {
db.endTransaction()
@ -181,20 +222,34 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge())
val db = databaseHelper.readableDatabase
val table = "${MessageTable.TABLE_NAME} LEFT JOIN ${RecipientTable.TABLE_NAME} ON ${MessageTable.TABLE_NAME}.${MessageTable.ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.MESSAGE_LOG_ID}"
val query = "${MessageTable.DATE_SENT} = ? AND ${RecipientTable.RECIPIENT_ID} = ? AND ${RecipientTable.DEVICE} = ?"
val table = "${PayloadTable.TABLE_NAME} LEFT JOIN ${RecipientTable.TABLE_NAME} ON ${PayloadTable.TABLE_NAME}.${PayloadTable.ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.PAYLOAD_ID}"
val query = "${PayloadTable.DATE_SENT} = ? AND ${RecipientTable.RECIPIENT_ID} = ? AND ${RecipientTable.DEVICE} = ?"
val args = SqlUtil.buildArgs(dateSent, recipientId, device)
db.query(table, null, query, args, null, null, null).use { cursor ->
if (cursor.moveToFirst()) {
return MessageLogEntry(
recipientId = RecipientId.from(CursorUtil.requireLong(cursor, RecipientTable.RECIPIENT_ID)),
dateSent = CursorUtil.requireLong(cursor, MessageTable.DATE_SENT),
content = SignalServiceProtos.Content.parseFrom(CursorUtil.requireBlob(cursor, MessageTable.CONTENT)),
contentHint = ContentHint.fromType(CursorUtil.requireInt(cursor, MessageTable.CONTENT_HINT)),
relatedMessageId = CursorUtil.requireLong(cursor, MessageTable.RELATED_MESSAGE_ID),
isRelatedMessageMms = CursorUtil.requireBoolean(cursor, MessageTable.IS_RELATED_MESSAGE_MMS)
)
db.query(table, null, query, args, null, null, null).use { entryCursor ->
if (entryCursor.moveToFirst()) {
val payloadId = CursorUtil.requireLong(entryCursor, RecipientTable.PAYLOAD_ID)
db.query(MessageTable.TABLE_NAME, null, "${MessageTable.PAYLOAD_ID} = ?", SqlUtil.buildArgs(payloadId), null, null, null).use { messageCursor ->
val messageIds: MutableList<MessageId> = mutableListOf()
while (messageCursor.moveToNext()) {
messageIds.add(
MessageId(
id = CursorUtil.requireLong(messageCursor, MessageTable.MESSAGE_ID),
mms = CursorUtil.requireBoolean(messageCursor, MessageTable.IS_MMS)
)
)
}
return MessageLogEntry(
recipientId = RecipientId.from(CursorUtil.requireLong(entryCursor, RecipientTable.RECIPIENT_ID)),
dateSent = CursorUtil.requireLong(entryCursor, PayloadTable.DATE_SENT),
content = SignalServiceProtos.Content.parseFrom(CursorUtil.requireBlob(entryCursor, PayloadTable.CONTENT)),
contentHint = ContentHint.fromType(CursorUtil.requireInt(entryCursor, PayloadTable.CONTENT_HINT)),
relatedMessages = messageIds
)
}
}
}
@ -205,10 +260,10 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
if (!FeatureFlags.senderKey()) return
val db = databaseHelper.writableDatabase
val query = "${MessageTable.RELATED_MESSAGE_ID} = ? AND ${MessageTable.IS_RELATED_MESSAGE_MMS} = ?"
val query = "${PayloadTable.ID} IN (SELECT ${MessageTable.PAYLOAD_ID} FROM ${MessageTable.TABLE_NAME} WHERE ${MessageTable.MESSAGE_ID} = ? AND ${MessageTable.IS_MMS} = ?)"
val args = SqlUtil.buildArgs(messageId, if (mms) 1 else 0)
db.delete(MessageTable.TABLE_NAME, query, args)
db.delete(PayloadTable.TABLE_NAME, query, args)
}
fun deleteEntryForRecipient(dateSent: Long, recipientId: RecipientId, device: Int) {
@ -227,17 +282,17 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
val query = """
${RecipientTable.RECIPIENT_ID} = ? AND
${RecipientTable.DEVICE} = ? AND
${RecipientTable.MESSAGE_LOG_ID} IN (
SELECT ${MessageTable.ID}
FROM ${MessageTable.TABLE_NAME}
WHERE ${MessageTable.DATE_SENT} IN (${dateSent.joinToString(",")})
${RecipientTable.PAYLOAD_ID} IN (
SELECT ${PayloadTable.ID}
FROM ${PayloadTable.TABLE_NAME}
WHERE ${PayloadTable.DATE_SENT} IN (${dateSent.joinToString(",")})
)"""
val args = SqlUtil.buildArgs(recipientId, device)
db.delete(RecipientTable.TABLE_NAME, query, args)
val cleanQuery = "${MessageTable.ID} NOT IN (SELECT ${RecipientTable.MESSAGE_LOG_ID} FROM ${RecipientTable.TABLE_NAME})"
db.delete(MessageTable.TABLE_NAME, cleanQuery, null)
val cleanQuery = "${PayloadTable.ID} NOT IN (SELECT ${RecipientTable.PAYLOAD_ID} FROM ${RecipientTable.TABLE_NAME})"
db.delete(PayloadTable.TABLE_NAME, cleanQuery, null)
db.setTransactionSuccessful()
} finally {
@ -248,17 +303,17 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
fun deleteAll() {
if (!FeatureFlags.senderKey()) return
databaseHelper.writableDatabase.delete(MessageTable.TABLE_NAME, null, null)
databaseHelper.writableDatabase.delete(PayloadTable.TABLE_NAME, null, null)
}
fun trimOldMessages(currentTime: Long, maxAge: Long) {
if (!FeatureFlags.senderKey()) return
val db = databaseHelper.writableDatabase
val query = "${MessageTable.DATE_SENT} < ?"
val query = "${PayloadTable.DATE_SENT} < ?"
val args = SqlUtil.buildArgs(currentTime - maxAge)
db.delete(MessageTable.TABLE_NAME, query, args)
db.delete(PayloadTable.TABLE_NAME, query, args)
}
private data class RecipientDevice(val recipientId: RecipientId, val devices: List<Int>)

View file

@ -201,8 +201,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
private static final int SENDER_KEY = 103;
private static final int MESSAGE_DUPE_INDEX = 104;
private static final int MESSAGE_LOG = 105;
private static final int MESSAGE_LOG_2 = 106;
private static final int DATABASE_VERSION = 105;
private static final int DATABASE_VERSION = 106;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@ -1598,6 +1599,41 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
db.execSQL("CREATE INDEX message_send_log_recipients_recipient_index ON message_send_log_recipients (recipient_id, device)");
}
if (oldVersion < MESSAGE_LOG_2) {
db.execSQL("DROP TABLE message_send_log");
db.execSQL("DROP INDEX IF EXISTS message_log_date_sent_index");
db.execSQL("DROP INDEX IF EXISTS message_log_related_message_index");
db.execSQL("DROP TRIGGER msl_sms_delete");
db.execSQL("DROP TRIGGER msl_mms_delete");
db.execSQL("DROP TABLE message_send_log_recipients");
db.execSQL("DROP INDEX IF EXISTS message_send_log_recipients_recipient_index");
db.execSQL("CREATE TABLE msl_payload (_id INTEGER PRIMARY KEY, " +
"date_sent INTEGER NOT NULL, " +
"content BLOB NOT NULL, " +
"content_hint INTEGER NOT NULL)");
db.execSQL("CREATE INDEX msl_payload_date_sent_index ON msl_payload (date_sent)");
db.execSQL("CREATE TABLE msl_recipient (_id INTEGER PRIMARY KEY, " +
"payload_id INTEGER NOT NULL REFERENCES msl_payload (_id) ON DELETE CASCADE, " +
"recipient_id INTEGER NOT NULL, " +
"device INTEGER NOT NULL)");
db.execSQL("CREATE INDEX msl_recipient_recipient_index ON msl_recipient (recipient_id, device, payload_id)");
db.execSQL("CREATE INDEX msl_recipient_payload_index ON msl_recipient (payload_id)");
db.execSQL("CREATE TABLE msl_message (_id INTEGER PRIMARY KEY, " +
"payload_id INTEGER NOT NULL REFERENCES msl_payload (_id) ON DELETE CASCADE, " +
"message_id INTEGER NOT NULL, " +
"is_mms INTEGER NOT NULL)");
db.execSQL("CREATE INDEX msl_message_message_index ON msl_message (message_id, is_mms, payload_id)");
db.execSQL("CREATE TRIGGER msl_sms_delete AFTER DELETE ON sms BEGIN DELETE FROM msl_payload WHERE _id IN (SELECT payload_id FROM msl_message WHERE message_id = old._id AND is_mms = 0); END");
db.execSQL("CREATE TRIGGER msl_mms_delete AFTER DELETE ON mms BEGIN DELETE FROM msl_payload WHERE _id IN (SELECT payload_id FROM msl_message WHERE message_id = old._id AND is_mms = 1); END");
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();

View file

@ -0,0 +1,10 @@
package org.thoughtcrime.securesms.database.model
/**
* Represents a pair of values that can be used to find a message. Because we have two tables,
* that means this has both the primary key and a boolean indicating which table it's in.
*/
data class MessageId(
val id: Long,
@get:JvmName("isMms") val mms: Boolean
)

View file

@ -12,10 +12,9 @@ data class MessageLogEntry(
val dateSent: Long,
val content: SignalServiceProtos.Content,
val contentHint: ContentHint,
val relatedMessageId: Long,
val isRelatedMessageMms: Boolean,
val relatedMessages: List<MessageId>
) {
val hasRelatedMessage: Boolean
@JvmName("hasRelatedMessage")
get() = relatedMessageId > 0
get() = relatedMessages.isNotEmpty()
}

View file

@ -52,6 +52,7 @@ public final class SenderKeyDistributionSendJob extends BaseJob {
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.setMaxInstancesForQueue(1)
.build());
}

View file

@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.MessageLogEntry;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
@ -1795,9 +1796,10 @@ public final class MessageContentProcessor {
return;
}
DistributionId distributionId = DatabaseFactory.getGroupDatabase(context).getOrCreateDistributionId(threadRecipient.requireGroupId().requireV2());
GroupId.V2 groupId = threadRecipient.requireGroupId().requireV2();
DistributionId distributionId = DatabaseFactory.getGroupDatabase(context).getOrCreateDistributionId(groupId);
SignalProtocolAddress requesterAddress = new SignalProtocolAddress(requester.requireUuid().toString(), decryptionErrorMessage.getDeviceId());
DatabaseFactory.getSenderKeySharedDatabase(context).delete(distributionId, Collections.singleton(requesterAddress));
if (messageLogEntry != null) {
@ -1807,29 +1809,11 @@ public final class MessageContentProcessor {
messageLogEntry.getDateSent(),
messageLogEntry.getContent(),
messageLogEntry.getContentHint(),
threadRecipient.requireGroupId().requireV2(),
groupId,
distributionId));
} else {
warn(content.getTimestamp(), "[RetryReceipt-SK] Unable to find MSL entry for " + requester.getId() + " with timestamp " + sentTimestamp + ".");
if (!content.getGroupId().isPresent()) {
warn(content.getTimestamp(), "[RetryReceipt-SK] No groupId on the Content, so we cannot send them a SenderKeyDistributionMessage.");
return;
}
GroupId groupId;
try {
groupId = GroupId.push(content.getGroupId().get());
} catch (BadGroupIdException e) {
warn(String.valueOf(content.getTimestamp()), "[RetryReceipt-SK] Bad groupId!");
return;
}
if (!groupId.isV2()) {
warn(String.valueOf(content.getTimestamp()), "[RetryReceipt-SK] Not a valid GV2 ID!");
return;
}
Optional<GroupRecord> groupRecord = DatabaseFactory.getGroupDatabase(context).getGroup(groupId);
if (!groupRecord.isPresent()) {
@ -1875,12 +1859,14 @@ public final class MessageContentProcessor {
}
}
private static @Nullable MessageRecord findRetryReceiptRelatedMessage(@NonNull Context context, @Nullable MessageLogEntry messageLogEntry, long sentTimestamp) {
private @Nullable MessageRecord findRetryReceiptRelatedMessage(@NonNull Context context, @Nullable MessageLogEntry messageLogEntry, long sentTimestamp) {
if (messageLogEntry != null && messageLogEntry.hasRelatedMessage()) {
if (messageLogEntry.isRelatedMessageMms()) {
return DatabaseFactory.getMmsDatabase(context).getMessageRecordOrNull(messageLogEntry.getRelatedMessageId());
MessageId relatedMessage = messageLogEntry.getRelatedMessages().get(0);
if (relatedMessage.isMms()) {
return DatabaseFactory.getMmsDatabase(context).getMessageRecordOrNull(relatedMessage.getId());
} else {
return DatabaseFactory.getSmsDatabase(context).getMessageRecordOrNull(messageLogEntry.getRelatedMessageId());
return DatabaseFactory.getSmsDatabase(context).getMessageRecordOrNull(relatedMessage.getId());
}
} else {
return DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(sentTimestamp, Recipient.self().getId());