Remove job-based decryption support and MCPv1.
This commit is contained in:
parent
3d94122abc
commit
fbf4de0ec5
43 changed files with 476 additions and 5165 deletions
|
@ -1,62 +0,0 @@
|
|||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import android.app.Application
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Rule
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessor.ExceptionMetadata
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessor.MessageState
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.testing.TestProtos
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||
|
||||
abstract class MessageContentProcessorTest {
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
protected fun MessageContentProcessor.doProcess(
|
||||
messageState: MessageState = MessageState.DECRYPTED_OK,
|
||||
content: SignalServiceContent,
|
||||
exceptionMetadata: ExceptionMetadata = ExceptionMetadata("sender", 1),
|
||||
timestamp: Long = 100L,
|
||||
smsMessageId: Long = -1L
|
||||
) {
|
||||
process(messageState, content, exceptionMetadata, timestamp, smsMessageId)
|
||||
}
|
||||
|
||||
protected fun createNormalContentTestSubject(): MessageContentProcessor {
|
||||
val context = ApplicationProvider.getApplicationContext<Application>()
|
||||
|
||||
return MessageContentProcessor.create(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid ServiceContentProto with a data message which can be built via
|
||||
* `injectDataMessage`. This function is intended to be built on-top of for more
|
||||
* specific scenario in subclasses.
|
||||
*
|
||||
* Example can be seen in __handleStoryMessageTest
|
||||
*/
|
||||
protected fun createServiceContentWithDataMessage(
|
||||
messageSender: Recipient = Recipient.resolved(harness.others.first()),
|
||||
injectDataMessage: SignalServiceProtos.DataMessage.Builder.() -> Unit
|
||||
): SignalServiceContentProto {
|
||||
return TestProtos.build {
|
||||
serviceContent(
|
||||
localAddress = address(uuid = harness.self.requireServiceId().rawUuid).build(),
|
||||
metadata = metadata(
|
||||
address = address(uuid = messageSender.requireServiceId().rawUuid).build()
|
||||
).build()
|
||||
).apply {
|
||||
content = content().apply {
|
||||
dataMessage = dataMessage().apply {
|
||||
injectDataMessage()
|
||||
}.build()
|
||||
}.build()
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.signal.storageservice.protos.groups.Member
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.MmsHelper
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.testing.TestProtos
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.api.push.DistributionId
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||
import kotlin.random.Random
|
||||
|
||||
@Suppress("ClassName")
|
||||
class MessageContentProcessor__handleStoryMessageTest : MessageContentProcessorTest() {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenContentWithADirectStoryReplyWhenIProcessThenIInsertAReplyInTheCorrectThread() {
|
||||
val sender = Recipient.resolved(harness.others.first())
|
||||
val senderThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(sender)
|
||||
val myStory = Recipient.resolved(SignalDatabase.distributionLists.getRecipientId(DistributionListId.MY_STORY)!!)
|
||||
val myStoryThread = SignalDatabase.threads.getOrCreateThreadIdFor(myStory)
|
||||
val expectedSentTime = 200L
|
||||
val storyMessageId = MmsHelper.insert(
|
||||
sentTimeMillis = expectedSentTime,
|
||||
recipient = myStory,
|
||||
storyType = StoryType.STORY_WITH_REPLIES,
|
||||
threadId = myStoryThread
|
||||
)
|
||||
|
||||
SignalDatabase.storySends.insert(
|
||||
messageId = storyMessageId,
|
||||
recipientIds = listOf(sender.id),
|
||||
sentTimestamp = expectedSentTime,
|
||||
allowsReplies = true,
|
||||
distributionId = DistributionId.MY_STORY
|
||||
)
|
||||
|
||||
val expectedBody = "Hello!"
|
||||
|
||||
val storyContent: SignalServiceContentProto = createServiceContentWithStoryContext(
|
||||
messageSender = sender,
|
||||
storyAuthor = harness.self,
|
||||
storySentTimestamp = expectedSentTime
|
||||
) {
|
||||
body = expectedBody
|
||||
}
|
||||
|
||||
runTestWithContent(contentProto = storyContent)
|
||||
|
||||
val replyId = SignalDatabase.messages.getConversation(senderThreadId, 0, 1).use {
|
||||
it.moveToFirst()
|
||||
it.requireLong(MessageTable.ID)
|
||||
}
|
||||
|
||||
val replyRecord = SignalDatabase.messages.getMessageRecord(replyId) as MediaMmsMessageRecord
|
||||
assertEquals(ParentStoryId.DirectReply(storyMessageId).serialize(), replyRecord.parentStoryId!!.serialize())
|
||||
assertEquals(expectedBody, replyRecord.body)
|
||||
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenContentWithAGroupStoryReplyWhenIProcessThenIInsertAReplyToTheCorrectStory() {
|
||||
val sender = Recipient.resolved(harness.others[0])
|
||||
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
|
||||
val decryptedGroupState = DecryptedGroup.newBuilder()
|
||||
.addAllMembers(
|
||||
listOf(
|
||||
DecryptedMember.newBuilder()
|
||||
.setAciBytes(harness.self.requireAci().toByteString())
|
||||
.setJoinedAtRevision(0)
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.build(),
|
||||
DecryptedMember.newBuilder()
|
||||
.setAciBytes(sender.requireAci().toByteString())
|
||||
.setJoinedAtRevision(0)
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
.setRevision(0)
|
||||
.build()
|
||||
|
||||
val group = SignalDatabase.groups.create(
|
||||
groupMasterKey,
|
||||
decryptedGroupState
|
||||
)
|
||||
|
||||
val groupRecipient = Recipient.externalGroupExact(group!!)
|
||||
val threadForGroup = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
|
||||
|
||||
val insertResult = MmsHelper.insert(
|
||||
message = IncomingMediaMessage(
|
||||
from = sender.id,
|
||||
sentTimeMillis = 100L,
|
||||
serverTimeMillis = 101L,
|
||||
receivedTimeMillis = 102L,
|
||||
storyType = StoryType.STORY_WITH_REPLIES
|
||||
),
|
||||
threadId = threadForGroup
|
||||
)
|
||||
|
||||
val expectedBody = "Hello, World!"
|
||||
val storyContent: SignalServiceContentProto = createServiceContentWithStoryContext(
|
||||
messageSender = sender,
|
||||
storyAuthor = sender,
|
||||
storySentTimestamp = 100L
|
||||
) {
|
||||
groupV2 = TestProtos.build { groupContextV2(masterKeyBytes = groupMasterKey.serialize()).build() }
|
||||
body = expectedBody
|
||||
}
|
||||
|
||||
runTestWithContent(storyContent)
|
||||
|
||||
val replyId = SignalDatabase.messages.getStoryReplies(insertResult.get().messageId).use { cursor ->
|
||||
assertEquals(1, cursor.count)
|
||||
cursor.moveToFirst()
|
||||
cursor.requireLong(MessageTable.ID)
|
||||
}
|
||||
|
||||
val replyRecord = SignalDatabase.messages.getMessageRecord(replyId) as MediaMmsMessageRecord
|
||||
assertEquals(ParentStoryId.GroupReply(insertResult.get().messageId).serialize(), replyRecord.parentStoryId?.serialize())
|
||||
assertEquals(threadForGroup, replyRecord.threadId)
|
||||
assertEquals(expectedBody, replyRecord.body)
|
||||
|
||||
SignalDatabase.messages.deleteGroupStoryReplies(insertResult.get().messageId)
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ServiceContent proto with a StoryContext, and then
|
||||
* uses `injectDataMessage` to fill in the data message object.
|
||||
*/
|
||||
private fun createServiceContentWithStoryContext(
|
||||
messageSender: Recipient,
|
||||
storyAuthor: Recipient,
|
||||
storySentTimestamp: Long,
|
||||
injectDataMessage: DataMessage.Builder.() -> Unit
|
||||
): SignalServiceContentProto {
|
||||
return createServiceContentWithDataMessage(messageSender) {
|
||||
storyContext = TestProtos.build {
|
||||
storyContext(
|
||||
sentTimestamp = storySentTimestamp,
|
||||
authorUuid = storyAuthor.requireServiceId().toString()
|
||||
).build()
|
||||
}
|
||||
injectDataMessage()
|
||||
}
|
||||
}
|
||||
|
||||
private fun runTestWithContent(contentProto: SignalServiceContentProto) {
|
||||
val content = SignalServiceContent.createFromProto(contentProto)
|
||||
val testSubject = createNormalContentTestSubject()
|
||||
testSubject.doProcess(content = content!!)
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||
|
||||
@Suppress("ClassName")
|
||||
class MessageContentProcessor__handleTextMessageTest : MessageContentProcessorTest() {
|
||||
@Test
|
||||
fun givenContentWithATextMessageWhenIProcessThenIInsertTheTextMessage() {
|
||||
val testSubject: MessageContentProcessor = createNormalContentTestSubject()
|
||||
val expectedBody = "Hello, World!"
|
||||
val contentProto: SignalServiceContentProto = createServiceContentWithDataMessage {
|
||||
body = expectedBody
|
||||
}
|
||||
|
||||
val content = SignalServiceContent.createFromProto(contentProto)
|
||||
|
||||
// WHEN
|
||||
testSubject.doProcess(content = content!!)
|
||||
|
||||
// THEN
|
||||
val record = SignalDatabase.messages.getMessageRecord(1)
|
||||
val threadSize = SignalDatabase.messages.getMessageCountForThread(record.threadId)
|
||||
assertEquals(1, threadSize)
|
||||
|
||||
assertTrue(record.isSecure)
|
||||
assertEquals(expectedBody, record.body)
|
||||
}
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class PushTable extends DatabaseTable {
|
||||
|
||||
private static final String TAG = Log.tag(PushTable.class);
|
||||
|
||||
private static final String TABLE_NAME = "push";
|
||||
public static final String ID = "_id";
|
||||
public static final String TYPE = "type";
|
||||
public static final String SOURCE_E164 = "source";
|
||||
public static final String SOURCE_UUID = "source_uuid";
|
||||
public static final String DEVICE_ID = "device_id";
|
||||
public static final String LEGACY_MSG = "body";
|
||||
public static final String CONTENT = "content";
|
||||
public static final String TIMESTAMP = "timestamp";
|
||||
public static final String SERVER_RECEIVED_TIMESTAMP = "server_timestamp";
|
||||
public static final String SERVER_DELIVERED_TIMESTAMP = "server_delivered_timestamp";
|
||||
public static final String SERVER_GUID = "server_guid";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
||||
TYPE + " INTEGER, " +
|
||||
SOURCE_E164 + " TEXT, " +
|
||||
SOURCE_UUID + " TEXT, " +
|
||||
DEVICE_ID + " INTEGER, " +
|
||||
LEGACY_MSG + " TEXT, " +
|
||||
CONTENT + " TEXT, " +
|
||||
TIMESTAMP + " INTEGER, " +
|
||||
SERVER_RECEIVED_TIMESTAMP + " INTEGER DEFAULT 0, " +
|
||||
SERVER_DELIVERED_TIMESTAMP + " INTEGER DEFAULT 0, " +
|
||||
SERVER_GUID + " TEXT DEFAULT NULL);";
|
||||
|
||||
public PushTable(Context context, SignalDatabase databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public long insert(@NonNull SignalServiceEnvelope envelope) {
|
||||
Optional<Long> messageId = find(envelope);
|
||||
|
||||
if (messageId.isPresent()) {
|
||||
return -1;
|
||||
} else {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(TYPE, envelope.getType());
|
||||
values.put(SOURCE_UUID, envelope.getSourceServiceId().orElse(null));
|
||||
values.put(DEVICE_ID, envelope.getSourceDevice());
|
||||
values.put(CONTENT, envelope.hasContent() ? Base64.encodeBytes(envelope.getContent()) : "");
|
||||
values.put(TIMESTAMP, envelope.getTimestamp());
|
||||
values.put(SERVER_RECEIVED_TIMESTAMP, envelope.getServerReceivedTimestamp());
|
||||
values.put(SERVER_DELIVERED_TIMESTAMP, envelope.getServerDeliveredTimestamp());
|
||||
values.put(SERVER_GUID, envelope.getServerGuid());
|
||||
|
||||
return databaseHelper.getSignalWritableDatabase().insert(TABLE_NAME, null, values);
|
||||
}
|
||||
}
|
||||
|
||||
public SignalServiceEnvelope get(long id) throws NoSuchMessageException {
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, ID_WHERE,
|
||||
new String[] {String.valueOf(id)},
|
||||
null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
String legacyMessage = cursor.getString(cursor.getColumnIndexOrThrow(LEGACY_MSG));
|
||||
String content = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT));
|
||||
String uuid = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE_UUID));
|
||||
String e164 = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE_E164));
|
||||
|
||||
return new SignalServiceEnvelope(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)),
|
||||
SignalServiceAddress.fromRaw(uuid, e164),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)),
|
||||
Util.isEmpty(content) ? null : Base64.decode(content),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_RECEIVED_TIMESTAMP)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_DELIVERED_TIMESTAMP)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(SERVER_GUID)),
|
||||
"",
|
||||
true,
|
||||
false,
|
||||
null);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new NoSuchMessageException(e);
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
throw new NoSuchMessageException("Not found");
|
||||
}
|
||||
|
||||
public Cursor getPending() {
|
||||
return databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public void delete(long id) {
|
||||
databaseHelper.getSignalWritableDatabase().delete(TABLE_NAME, ID_WHERE, new String[] {id+""});
|
||||
}
|
||||
|
||||
public Reader readerFor(Cursor cursor) {
|
||||
return new Reader(cursor);
|
||||
}
|
||||
|
||||
private Optional<Long> find(SignalServiceEnvelope envelope) {
|
||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||
String query = TYPE + " = ? AND " +
|
||||
DEVICE_ID + " = ? AND " +
|
||||
LEGACY_MSG + " = ? AND " +
|
||||
CONTENT + " = ? AND " +
|
||||
TIMESTAMP + " = ? AND " +
|
||||
"(" + SOURCE_UUID + " NOT NULL AND " + SOURCE_UUID + " = ?)";
|
||||
|
||||
String[] args = new String[] { String.valueOf(envelope.getType()),
|
||||
String.valueOf(envelope.getSourceDevice()),
|
||||
envelope.hasContent() ? Base64.encodeBytes(envelope.getContent()) : "",
|
||||
String.valueOf(envelope.getTimestamp()),
|
||||
String.valueOf(envelope.getSourceServiceId().orElse(null)) };
|
||||
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, null, query, args, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return Optional.of(cursor.getLong(cursor.getColumnIndexOrThrow(ID)));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Reader implements Closeable {
|
||||
private final Cursor cursor;
|
||||
|
||||
public Reader(Cursor cursor) {
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
public SignalServiceEnvelope getNext() {
|
||||
try {
|
||||
if (cursor == null || !cursor.moveToNext())
|
||||
return null;
|
||||
|
||||
int type = cursor.getInt(cursor.getColumnIndexOrThrow(TYPE));
|
||||
String sourceUuid = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE_UUID));
|
||||
String sourceE164 = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE_E164));
|
||||
int deviceId = cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID));
|
||||
String content = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT));
|
||||
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
|
||||
long serverReceivedTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_RECEIVED_TIMESTAMP));
|
||||
long serverDeliveredTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_DELIVERED_TIMESTAMP));
|
||||
String serverGuid = cursor.getString(cursor.getColumnIndexOrThrow(SERVER_GUID));
|
||||
|
||||
return new SignalServiceEnvelope(type,
|
||||
SignalServiceAddress.fromRaw(sourceUuid, sourceE164),
|
||||
deviceId,
|
||||
timestamp,
|
||||
content != null ? Base64.decode(content) : null,
|
||||
serverReceivedTimestamp,
|
||||
serverDeliveredTimestamp,
|
||||
serverGuid,
|
||||
"",
|
||||
true,
|
||||
false,
|
||||
null);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,7 +44,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
|||
val threadTable: ThreadTable = ThreadTable(context, this)
|
||||
val identityTable: IdentityTable = IdentityTable(context, this)
|
||||
val draftTable: DraftTable = DraftTable(context, this)
|
||||
val pushTable: PushTable = PushTable(context, this)
|
||||
val groupTable: GroupTable = GroupTable(context, this)
|
||||
val recipientTable: RecipientTable = RecipientTable(context, this)
|
||||
val groupReceiptTable: GroupReceiptTable = GroupReceiptTable(context, this)
|
||||
|
@ -86,7 +85,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
|||
db.execSQL(ThreadTable.CREATE_TABLE)
|
||||
db.execSQL(IdentityTable.CREATE_TABLE)
|
||||
db.execSQL(DraftTable.CREATE_TABLE)
|
||||
db.execSQL(PushTable.CREATE_TABLE)
|
||||
executeStatements(db, GroupTable.CREATE_TABLES)
|
||||
db.execSQL(RecipientTable.CREATE_TABLE)
|
||||
db.execSQL(GroupReceiptTable.CREATE_TABLE)
|
||||
|
@ -467,12 +465,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
|||
val pendingPniSignatureMessages: PendingPniSignatureMessageTable
|
||||
get() = instance!!.pendingPniSignatureMessageTable
|
||||
|
||||
@get:Deprecated("This only exists to migrate from legacy storage. There shouldn't be any new usages.")
|
||||
@get:JvmStatic
|
||||
@get:JvmName("push")
|
||||
val push: PushTable
|
||||
get() = instance!!.pushTable
|
||||
|
||||
@get:JvmStatic
|
||||
@get:JvmName("recipients")
|
||||
val recipients: RecipientTable
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
|||
import com.google.i18n.phonenumbers.Phonenumber;
|
||||
import com.google.i18n.phonenumbers.ShortNumberInfo;
|
||||
|
||||
import org.signal.core.util.Hex;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
|
@ -31,12 +32,11 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.DraftTable;
|
||||
import org.thoughtcrime.securesms.database.GroupTable;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptTable;
|
||||
import org.thoughtcrime.securesms.database.GroupTable;
|
||||
import org.thoughtcrime.securesms.database.IdentityTable;
|
||||
import org.thoughtcrime.securesms.database.MessageTypes;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
import org.thoughtcrime.securesms.database.PushTable;
|
||||
import org.thoughtcrime.securesms.database.MessageTypes;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.ThreadTable;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
|
@ -46,7 +46,6 @@ import org.thoughtcrime.securesms.permissions.Permissions;
|
|||
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.DelimiterUtil;
|
||||
import org.signal.core.util.Hex;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
@ -133,7 +132,6 @@ public class ClassicOpenHelper extends SQLiteOpenHelper {
|
|||
db.execSQL(ThreadTable.CREATE_TABLE);
|
||||
db.execSQL(IdentityTable.CREATE_TABLE);
|
||||
db.execSQL(DraftTable.CREATE_TABLE);
|
||||
db.execSQL(PushTable.CREATE_TABLE);
|
||||
db.execSQL(GroupTable.CREATE_TABLE);
|
||||
db.execSQL(RecipientTable.CREATE_TABLE);
|
||||
db.execSQL(GroupReceiptTable.CREATE_TABLE);
|
||||
|
|
|
@ -60,6 +60,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V201_RecipientTable
|
|||
import org.thoughtcrime.securesms.database.helpers.migration.V202_DropMessageTableThreadDateIndex
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V203_PreKeyStaleTimestamp
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V204_GroupForeignKeyMigration
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V205_DropPushTable
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
|
@ -68,7 +69,7 @@ object SignalDatabaseMigrations {
|
|||
|
||||
val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass)
|
||||
|
||||
const val DATABASE_VERSION = 204
|
||||
const val DATABASE_VERSION = 205
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
@ -295,6 +296,10 @@ object SignalDatabaseMigrations {
|
|||
if (oldVersion < 204) {
|
||||
V204_GroupForeignKeyMigration.migrate(context, db, oldVersion, newVersion)
|
||||
}
|
||||
|
||||
if (oldVersion < 205) {
|
||||
V205_DropPushTable.migrate(context, db, oldVersion, newVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* Drop the no longer used push table.
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V205_DropPushTable : SignalDatabaseMigration {
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("DROP TABLE IF EXISTS push")
|
||||
}
|
||||
}
|
|
@ -35,13 +35,11 @@ import org.thoughtcrime.securesms.jobmanager.JobMigrator;
|
|||
import org.thoughtcrime.securesms.jobmanager.impl.FactoryJobPredicate;
|
||||
import org.thoughtcrime.securesms.jobs.FastJobStorage;
|
||||
import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.IndividualSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
|
||||
import org.thoughtcrime.securesms.jobs.MarkerJob;
|
||||
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.IndividualSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessMessageJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessMessageJobV2;
|
||||
import org.thoughtcrime.securesms.jobs.ReactionSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
||||
|
@ -172,7 +170,7 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
|||
.setConstraintObservers(JobManagerFactories.getConstraintObservers(context))
|
||||
.setJobStorage(new FastJobStorage(JobDatabase.getInstance(context)))
|
||||
.setJobMigrator(new JobMigrator(TextSecurePreferences.getJobManagerVersion(context), JobManager.CURRENT_VERSION, JobManagerFactories.getJobMigrations(context)))
|
||||
.addReservedJobRunner(new FactoryJobPredicate(PushDecryptMessageJob.KEY, PushProcessMessageJob.KEY, PushProcessMessageJobV2.KEY, MarkerJob.KEY))
|
||||
.addReservedJobRunner(new FactoryJobPredicate(PushProcessMessageJobV2.KEY, MarkerJob.KEY))
|
||||
.addReservedJobRunner(new FactoryJobPredicate(IndividualSendJob.KEY, PushGroupSendJob.KEY, ReactionSendJob.KEY, TypingSendJob.KEY, GroupCallUpdateSendJob.KEY))
|
||||
.build();
|
||||
return new JobManager(context, config);
|
||||
|
|
|
@ -43,7 +43,7 @@ public class JobManager implements ConstraintObserver.Notifier {
|
|||
|
||||
private static final String TAG = Log.tag(JobManager.class);
|
||||
|
||||
public static final int CURRENT_VERSION = 9;
|
||||
public static final int CURRENT_VERSION = 10;
|
||||
|
||||
private final Application application;
|
||||
private final Configuration configuration;
|
||||
|
|
|
@ -1,60 +1,24 @@
|
|||
package org.thoughtcrime.securesms.jobmanager.migrations;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.PushTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobMigration;
|
||||
import org.thoughtcrime.securesms.jobs.FailingJob;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
|
||||
/**
|
||||
* We removed the messageId property from the job data and replaced it with a serialized envelope,
|
||||
* so we need to take jobs that referenced an ID and replace it with the envelope instead.
|
||||
*
|
||||
* @deprecated No longer have a PushDecryptJob to migrate, job now maps to {@link org.thoughtcrime.securesms.jobs.FailingJob}
|
||||
* in {@link org.thoughtcrime.securesms.jobs.JobManagerFactories}
|
||||
*/
|
||||
public class PushDecryptMessageJobEnvelopeMigration extends JobMigration {
|
||||
|
||||
private static final String TAG = Log.tag(PushDecryptMessageJobEnvelopeMigration.class);
|
||||
|
||||
private final PushTable pushDatabase;
|
||||
|
||||
public PushDecryptMessageJobEnvelopeMigration(@NonNull Context context) {
|
||||
public PushDecryptMessageJobEnvelopeMigration() {
|
||||
super(8);
|
||||
this.pushDatabase = SignalDatabase.push();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull JobData migrate(@NonNull JobData jobData) {
|
||||
if ("PushDecryptJob".equals(jobData.getFactoryKey())) {
|
||||
Log.i(TAG, "Found a PushDecryptJob to migrate.");
|
||||
return migratePushDecryptMessageJob(pushDatabase, jobData);
|
||||
} else {
|
||||
return jobData;
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull JobData migratePushDecryptMessageJob(@NonNull PushTable pushDatabase, @NonNull JobData jobData) {
|
||||
JsonJobData data = JsonJobData.deserialize(jobData.getData());
|
||||
|
||||
if (data.hasLong("message_id")) {
|
||||
long messageId = data.getLong("message_id");
|
||||
try {
|
||||
SignalServiceEnvelope envelope = pushDatabase.get(messageId);
|
||||
return jobData.withData(data.buildUpon()
|
||||
.putBlobAsString("envelope", envelope.serialize())
|
||||
.serialize());
|
||||
} catch (NoSuchMessageException e) {
|
||||
Log.w(TAG, "Failed to find envelope in DB! Failing.");
|
||||
return jobData.withFactoryKey(FailingJob.KEY);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "No message_id property?");
|
||||
return jobData;
|
||||
}
|
||||
return jobData;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package org.thoughtcrime.securesms.jobmanager.migrations
|
||||
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.jobmanager.JobMigration
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData
|
||||
import org.thoughtcrime.securesms.jobs.FailingJob
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessMessageErrorV2Job
|
||||
import org.thoughtcrime.securesms.messages.MessageState
|
||||
import org.thoughtcrime.securesms.util.Base64
|
||||
import org.whispersystems.signalservice.api.crypto.protos.CompleteMessage
|
||||
import org.whispersystems.signalservice.api.crypto.protos.EnvelopeMetadata
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||
|
||||
/**
|
||||
* Migrate PushProcessMessageJob V1 to V2 versions.
|
||||
*/
|
||||
class PushProcessMessageJobMigration : JobMigration(10) {
|
||||
override fun migrate(jobData: JobData): JobData {
|
||||
return if ("PushProcessJob" == jobData.factoryKey) {
|
||||
migrateJob(jobData)
|
||||
} else {
|
||||
jobData
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(PushProcessMessageJobMigration::class.java)
|
||||
|
||||
@Suppress("MoveVariableDeclarationIntoWhen")
|
||||
private fun migrateJob(jobData: JobData): JobData {
|
||||
val data = JsonJobData.deserialize(jobData.data)
|
||||
return if (data.hasInt("message_state")) {
|
||||
val state = MessageState.values()[data.getInt("message_state")]
|
||||
return when (state) {
|
||||
MessageState.NOOP -> jobData.withFactoryKey(FailingJob.KEY)
|
||||
|
||||
MessageState.DECRYPTED_OK -> {
|
||||
try {
|
||||
migratePushProcessJobWithDecryptedData(jobData, data)
|
||||
} catch (t: Throwable) {
|
||||
Log.w(TAG, "Unable to migrate successful process job", t)
|
||||
jobData.withFactoryKey(FailingJob.KEY)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.i(TAG, "Migrating push process error job for state: $state")
|
||||
jobData.withFactoryKey(PushProcessMessageErrorV2Job.KEY)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
jobData.withFactoryKey(FailingJob.KEY)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migratePushProcessJobWithDecryptedData(jobData: JobData, inputData: JsonJobData): JobData {
|
||||
Log.i(TAG, "Migrating PushProcessJob to V2")
|
||||
|
||||
val protoBytes: ByteArray = Base64.decode(inputData.getString("message_content"))
|
||||
val proto = SignalServiceContentProto.parseFrom(protoBytes)
|
||||
|
||||
val sourceServiceId = ServiceId.parseOrThrow(proto.metadata.address.uuid)
|
||||
val destinationServiceId = ServiceId.parseOrThrow(proto.metadata.destinationUuid)
|
||||
|
||||
val envelope = Envelope.newBuilder()
|
||||
.setSourceServiceId(sourceServiceId.toString())
|
||||
.setSourceDevice(proto.metadata.senderDevice)
|
||||
.setDestinationServiceId(destinationServiceId.toString())
|
||||
.setTimestamp(proto.metadata.timestamp)
|
||||
.setServerGuid(proto.metadata.serverGuid)
|
||||
.setServerTimestamp(proto.metadata.serverReceivedTimestamp)
|
||||
|
||||
val metadata = EnvelopeMetadata(
|
||||
sourceServiceId = sourceServiceId.toByteArray().toByteString(),
|
||||
sourceE164 = if (proto.metadata.address.hasE164()) proto.metadata.address.e164 else null,
|
||||
sourceDeviceId = proto.metadata.senderDevice,
|
||||
sealedSender = proto.metadata.needsReceipt,
|
||||
groupId = if (proto.metadata.hasGroupId()) proto.metadata.groupId.toByteArray().toByteString() else null,
|
||||
destinationServiceId = destinationServiceId.toByteArray().toByteString()
|
||||
)
|
||||
|
||||
val completeMessage = CompleteMessage(
|
||||
envelope = envelope.build().toByteArray().toByteString(),
|
||||
content = proto.content.toByteArray().toByteString(),
|
||||
metadata = metadata,
|
||||
serverDeliveredTimestamp = proto.metadata.serverDeliveredTimestamp
|
||||
)
|
||||
|
||||
return jobData
|
||||
.withFactoryKey("PushProcessMessageJobV2")
|
||||
.withData(completeMessage.encode())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,7 +50,7 @@ public class AutomaticSessionResetJob extends BaseJob {
|
|||
|
||||
public AutomaticSessionResetJob(@NonNull RecipientId recipientId, int deviceId, long sentTimestamp) {
|
||||
this(new Parameters.Builder()
|
||||
.setQueue(PushProcessMessageJob.getQueueName(recipientId))
|
||||
.setQueue(PushProcessMessageJobV2.getQueueName(recipientId))
|
||||
.addConstraint(DecryptionsDrainedConstraint.KEY)
|
||||
.setMaxInstancesForQueue(1)
|
||||
.build(),
|
||||
|
|
|
@ -37,7 +37,7 @@ internal class CallLinkPeekJob private constructor(
|
|||
|
||||
constructor(callLinkRecipientId: RecipientId) : this(
|
||||
Parameters.Builder()
|
||||
.setQueue(PushProcessMessageJob.getQueueName(callLinkRecipientId))
|
||||
.setQueue(PushProcessMessageJobV2.getQueueName(callLinkRecipientId))
|
||||
.setMaxInstancesForQueue(1)
|
||||
.setLifespan(TimeUnit.MINUTES.toMillis(1))
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
|
|
|
@ -37,7 +37,7 @@ final class ForceUpdateGroupV2WorkerJob extends BaseJob {
|
|||
private final GroupId.V2 groupId;
|
||||
|
||||
ForceUpdateGroupV2WorkerJob(@NonNull GroupId.V2 groupId) {
|
||||
this(new Parameters.Builder().setQueue(PushProcessMessageJob.getQueueName(Recipient.externalGroupExact(groupId).getId()))
|
||||
this(new Parameters.Builder().setQueue(PushProcessMessageJobV2.getQueueName(Recipient.externalGroupExact(groupId).getId()))
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build(),
|
||||
|
|
|
@ -21,7 +21,7 @@ final class GroupCallPeekWorkerJob extends BaseJob {
|
|||
|
||||
public GroupCallPeekWorkerJob(@NonNull RecipientId groupRecipientId) {
|
||||
this(new Parameters.Builder()
|
||||
.setQueue(PushProcessMessageJob.getQueueName(groupRecipientId))
|
||||
.setQueue(PushProcessMessageJobV2.getQueueName(groupRecipientId))
|
||||
.setMaxInstancesForQueue(2)
|
||||
.build(),
|
||||
groupRecipientId);
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NotInCallConstraintObserver;
|
|||
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
|
||||
import org.thoughtcrime.securesms.jobmanager.migrations.PushDecryptMessageJobEnvelopeMigration;
|
||||
import org.thoughtcrime.securesms.jobmanager.migrations.PushProcessMessageJobMigration;
|
||||
import org.thoughtcrime.securesms.jobmanager.migrations.PushProcessMessageQueueJobMigration;
|
||||
import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration;
|
||||
import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration2;
|
||||
|
@ -47,7 +48,6 @@ import org.thoughtcrime.securesms.migrations.BlobStorageLocationMigrationJob;
|
|||
import org.thoughtcrime.securesms.migrations.CachedAttachmentsMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.ClearGlideCacheMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.DecryptionsDrainedMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.DeleteDeprecatedLogsMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.DirectoryRefreshMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.EmojiDownloadMigrationJob;
|
||||
|
@ -165,14 +165,12 @@ public final class JobManagerFactories {
|
|||
put(PreKeysSyncJob.KEY, new PreKeysSyncJob.Factory());
|
||||
put(ProfileKeySendJob.KEY, new ProfileKeySendJob.Factory());
|
||||
put(ProfileUploadJob.KEY, new ProfileUploadJob.Factory());
|
||||
put(PushDecryptMessageJob.KEY, new PushDecryptMessageJob.Factory());
|
||||
put(PushDecryptDrainedJob.KEY, new PushDecryptDrainedJob.Factory());
|
||||
put(PushDistributionListSendJob.KEY, new PushDistributionListSendJob.Factory());
|
||||
put(PushGroupSendJob.KEY, new PushGroupSendJob.Factory());
|
||||
put(PushGroupSilentUpdateSendJob.KEY, new PushGroupSilentUpdateSendJob.Factory());
|
||||
put(MessageFetchJob.KEY, new MessageFetchJob.Factory());
|
||||
put(PushProcessEarlyMessagesJob.KEY, new PushProcessEarlyMessagesJob.Factory());
|
||||
put(PushProcessMessageJob.KEY, new PushProcessMessageJob.Factory());
|
||||
put(PushProcessMessageErrorV2Job.KEY, new PushProcessMessageErrorV2Job.Factory());
|
||||
put(PushProcessMessageJobV2.KEY, new PushProcessMessageJobV2.Factory());
|
||||
put(ReactionSendJob.KEY, new ReactionSendJob.Factory());
|
||||
put(RebuildMessageSearchIndexJob.KEY, new RebuildMessageSearchIndexJob.Factory());
|
||||
|
@ -234,7 +232,6 @@ public final class JobManagerFactories {
|
|||
put(CachedAttachmentsMigrationJob.KEY, new CachedAttachmentsMigrationJob.Factory());
|
||||
put(ClearGlideCacheMigrationJob.KEY, new ClearGlideCacheMigrationJob.Factory());
|
||||
put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory());
|
||||
put(DecryptionsDrainedMigrationJob.KEY, new DecryptionsDrainedMigrationJob.Factory());
|
||||
put(DeleteDeprecatedLogsMigrationJob.KEY, new DeleteDeprecatedLogsMigrationJob.Factory());
|
||||
put(DirectoryRefreshMigrationJob.KEY, new DirectoryRefreshMigrationJob.Factory());
|
||||
put(EmojiDownloadMigrationJob.KEY, new EmojiDownloadMigrationJob.Factory());
|
||||
|
@ -295,6 +292,10 @@ public final class JobManagerFactories {
|
|||
put("KbsEnclaveMigrationWorkerJob", new FailingJob.Factory());
|
||||
put("KbsEnclaveMigrationJob", new PassingMigrationJob.Factory());
|
||||
put("ClearFallbackKbsEnclaveJob", new FailingJob.Factory());
|
||||
put("PushDecryptJob", new FailingJob.Factory());
|
||||
put("PushDecryptDrainedJob", new FailingJob.Factory());
|
||||
put("PushProcessJob", new FailingJob.Factory());
|
||||
put("DecryptionsDrainedMigrationJob", new PassingMigrationJob.Factory());
|
||||
}};
|
||||
}
|
||||
|
||||
|
@ -331,7 +332,8 @@ public final class JobManagerFactories {
|
|||
new SendReadReceiptsJobMigration(SignalDatabase.messages()),
|
||||
new PushProcessMessageQueueJobMigration(application),
|
||||
new RetrieveProfileJobMigration(),
|
||||
new PushDecryptMessageJobEnvelopeMigration(application),
|
||||
new SenderKeyDistributionSendJobRecipientMigration());
|
||||
new PushDecryptMessageJobEnvelopeMigration(),
|
||||
new SenderKeyDistributionSendJobRecipientMigration(),
|
||||
new PushProcessMessageJobMigration());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ class LeaveGroupV2WorkerJob(parameters: Parameters, private val groupId: GroupId
|
|||
|
||||
constructor(groupId: GroupId.V2) : this(
|
||||
parameters = Parameters.Builder()
|
||||
.setQueue(PushProcessMessageJob.getQueueName(Recipient.externalGroupExact(groupId).id))
|
||||
.setQueue(PushProcessMessageJobV2.getQueueName(Recipient.externalGroupExact(groupId).id))
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.setMaxInstancesForQueue(2)
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
|
||||
/**
|
||||
* A job that has the same queue as {@link PushDecryptMessageJob} that we enqueue so we can notify
|
||||
* the {@link org.thoughtcrime.securesms.messages.IncomingMessageObserver} when the decryption job
|
||||
* queue is empty.
|
||||
*/
|
||||
public class PushDecryptDrainedJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "PushDecryptDrainedJob";
|
||||
|
||||
private static final String TAG = Log.tag(PushDecryptDrainedJob.class);
|
||||
|
||||
public PushDecryptDrainedJob() {
|
||||
this(new Parameters.Builder()
|
||||
.setQueue(PushDecryptMessageJob.QUEUE)
|
||||
.build());
|
||||
}
|
||||
|
||||
private PushDecryptDrainedJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable byte[] serialize() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRun() throws Exception {
|
||||
Log.i(TAG, "Decryptions are caught-up.");
|
||||
ApplicationDependencies.getIncomingMessageObserver().notifyDecryptionsDrained();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<PushDecryptDrainedJob> {
|
||||
@Override
|
||||
public @NonNull PushDecryptDrainedJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
|
||||
return new PushDecryptDrainedJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,198 +0,0 @@
|
|||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import android.app.PendingIntent
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import org.signal.core.util.PendingIntentFlags.mutable
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.MainActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessor.ExceptionMetadata
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessor.MessageState
|
||||
import org.thoughtcrime.securesms.messages.MessageDecryptor
|
||||
import org.thoughtcrime.securesms.messages.protocol.BufferedProtocolStore
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceMetadata
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer
|
||||
import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||
import java.util.Optional
|
||||
|
||||
/**
|
||||
* Decrypts an envelope. Enqueues a separate job, [PushProcessMessageJob], to actually insert
|
||||
* the result into our database.
|
||||
*/
|
||||
class PushDecryptMessageJob private constructor(
|
||||
parameters: Parameters,
|
||||
private val envelope: SignalServiceEnvelope,
|
||||
private val smsMessageId: Long
|
||||
) : BaseJob(parameters) {
|
||||
|
||||
companion object {
|
||||
val TAG = Log.tag(PushDecryptMessageJob::class.java)
|
||||
|
||||
const val KEY = "PushDecryptJob"
|
||||
const val QUEUE = "__PUSH_DECRYPT_JOB__"
|
||||
|
||||
private const val KEY_SMS_MESSAGE_ID = "sms_message_id"
|
||||
private const val KEY_ENVELOPE = "envelope"
|
||||
}
|
||||
|
||||
@Deprecated("No more jobs of this type should be enqueued. Decryptions now happen as things come off of the websocket.")
|
||||
@JvmOverloads
|
||||
constructor(envelope: SignalServiceEnvelope, smsMessageId: Long = -1) : this(
|
||||
Parameters.Builder()
|
||||
.setQueue(QUEUE)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build(),
|
||||
envelope,
|
||||
smsMessageId
|
||||
)
|
||||
|
||||
override fun shouldTrace() = true
|
||||
|
||||
override fun serialize(): ByteArray? {
|
||||
return JsonJobData.Builder()
|
||||
.putBlobAsString(KEY_ENVELOPE, envelope.serialize())
|
||||
.putLong(KEY_SMS_MESSAGE_ID, smsMessageId)
|
||||
.serialize()
|
||||
}
|
||||
|
||||
override fun getFactoryKey() = KEY
|
||||
|
||||
@Throws(RetryLaterException::class)
|
||||
public override fun onRun() {
|
||||
if (needsMigration()) {
|
||||
Log.w(TAG, "Migration is still needed.")
|
||||
postMigrationNotification()
|
||||
throw RetryLaterException()
|
||||
}
|
||||
|
||||
val bufferedProtocolStore = BufferedProtocolStore.create()
|
||||
val result = MessageDecryptor.decrypt(context, bufferedProtocolStore, envelope.proto, envelope.serverDeliveredTimestamp)
|
||||
bufferedProtocolStore.flushToDisk()
|
||||
|
||||
when (result) {
|
||||
is MessageDecryptor.Result.Success -> {
|
||||
ApplicationDependencies.getJobManager().add(
|
||||
PushProcessMessageJob(
|
||||
result.toMessageState(),
|
||||
result.toSignalServiceContent(),
|
||||
null,
|
||||
smsMessageId,
|
||||
result.envelope.timestamp
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is MessageDecryptor.Result.Error -> {
|
||||
ApplicationDependencies.getJobManager().add(
|
||||
PushProcessMessageJob(
|
||||
result.toMessageState(),
|
||||
null,
|
||||
result.errorMetadata.toExceptionMetadata(),
|
||||
smsMessageId,
|
||||
result.envelope.timestamp
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is MessageDecryptor.Result.Ignore -> {
|
||||
// No action needed
|
||||
}
|
||||
|
||||
else -> {
|
||||
throw AssertionError("Unexpected result! ${result.javaClass.simpleName}")
|
||||
}
|
||||
}
|
||||
|
||||
result.followUpOperations.forEach { it.run() }
|
||||
}
|
||||
|
||||
public override fun onShouldRetry(exception: Exception): Boolean {
|
||||
return exception is RetryLaterException
|
||||
}
|
||||
|
||||
override fun onFailure() = Unit
|
||||
|
||||
private fun needsMigration(): Boolean {
|
||||
return TextSecurePreferences.getNeedsSqlCipherMigration(context)
|
||||
}
|
||||
|
||||
private fun MessageDecryptor.Result.toMessageState(): MessageState {
|
||||
return when (this) {
|
||||
is MessageDecryptor.Result.DecryptionError -> MessageState.DECRYPTION_ERROR
|
||||
is MessageDecryptor.Result.Ignore -> MessageState.NOOP
|
||||
is MessageDecryptor.Result.InvalidVersion -> MessageState.INVALID_VERSION
|
||||
is MessageDecryptor.Result.LegacyMessage -> MessageState.LEGACY_MESSAGE
|
||||
is MessageDecryptor.Result.Success -> MessageState.DECRYPTED_OK
|
||||
is MessageDecryptor.Result.UnsupportedDataMessage -> MessageState.UNSUPPORTED_DATA_MESSAGE
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageDecryptor.Result.Success.toSignalServiceContent(): SignalServiceContent {
|
||||
val localAddress = SignalServiceAddress(this.metadata.destinationServiceId, Optional.ofNullable(SignalStore.account().e164))
|
||||
val metadata = SignalServiceMetadata(
|
||||
SignalServiceAddress(this.metadata.sourceServiceId, Optional.ofNullable(this.metadata.sourceE164)),
|
||||
this.metadata.sourceDeviceId,
|
||||
this.envelope.timestamp,
|
||||
this.envelope.serverTimestamp,
|
||||
this.serverDeliveredTimestamp,
|
||||
this.metadata.sealedSender,
|
||||
this.envelope.serverGuid,
|
||||
Optional.ofNullable(this.metadata.groupId),
|
||||
this.metadata.destinationServiceId.toString()
|
||||
)
|
||||
|
||||
val contentProto = SignalServiceContentProto.newBuilder()
|
||||
.setLocalAddress(SignalServiceAddressProtobufSerializer.toProtobuf(localAddress))
|
||||
.setMetadata(SignalServiceMetadataProtobufSerializer.toProtobuf(metadata))
|
||||
.setContent(content)
|
||||
.build()
|
||||
|
||||
return SignalServiceContent.createFromProto(contentProto)!!
|
||||
}
|
||||
|
||||
private fun MessageDecryptor.ErrorMetadata.toExceptionMetadata(): ExceptionMetadata {
|
||||
return ExceptionMetadata(
|
||||
this.sender,
|
||||
this.senderDevice,
|
||||
this.groupId
|
||||
)
|
||||
}
|
||||
|
||||
private fun postMigrationNotification() {
|
||||
val notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().messagesChannel)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||
.setContentTitle(context.getString(R.string.PushDecryptJob_new_locked_message))
|
||||
.setContentText(context.getString(R.string.PushDecryptJob_unlock_to_view_pending_messages))
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0, MainActivity.clearTop(context), mutable()))
|
||||
.setDefaults(NotificationCompat.DEFAULT_SOUND or NotificationCompat.DEFAULT_VIBRATE)
|
||||
.build()
|
||||
|
||||
NotificationManagerCompat.from(context).notify(NotificationIds.LEGACY_SQLCIPHER_MIGRATION, notification)
|
||||
}
|
||||
|
||||
class Factory : Job.Factory<PushDecryptMessageJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): PushDecryptMessageJob {
|
||||
val data = JsonJobData.deserialize(serializedData)
|
||||
return PushDecryptMessageJob(
|
||||
parameters,
|
||||
SignalServiceEnvelope.deserialize(data.getStringAsBlob(KEY_ENVELOPE)),
|
||||
data.getLong(KEY_SMS_MESSAGE_ID)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,10 +6,8 @@ import org.thoughtcrime.securesms.database.SignalDatabase
|
|||
import org.thoughtcrime.securesms.database.model.ServiceMessageId
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessor
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessorV2
|
||||
import org.thoughtcrime.securesms.util.EarlyMessageCacheEntry
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
|
||||
/**
|
||||
* A job that should be enqueued whenever we process a message that we think has arrived "early" (see [org.thoughtcrime.securesms.util.EarlyMessageCache]).
|
||||
|
@ -43,15 +41,9 @@ class PushProcessEarlyMessagesJob private constructor(parameters: Parameters) :
|
|||
Log.i(TAG, "There are ${earlyIds.size} items in the early message cache with matches.")
|
||||
|
||||
for (id: ServiceMessageId in earlyIds) {
|
||||
val contents: List<SignalServiceContent>? = ApplicationDependencies.getEarlyMessageCache().retrieve(id.sender, id.sentTimestamp).orNull()
|
||||
val earlyEntries: List<EarlyMessageCacheEntry>? = ApplicationDependencies.getEarlyMessageCache().retrieveV2(id.sender, id.sentTimestamp).orNull()
|
||||
val earlyEntries: List<EarlyMessageCacheEntry>? = ApplicationDependencies.getEarlyMessageCache().retrieve(id.sender, id.sentTimestamp).orNull()
|
||||
|
||||
if (contents != null) {
|
||||
for (content: SignalServiceContent in contents) {
|
||||
Log.i(TAG, "[${id.sentTimestamp}] Processing early content for $id")
|
||||
MessageContentProcessor.create(context).processEarlyContent(MessageContentProcessor.MessageState.DECRYPTED_OK, content, null, id.sentTimestamp, -1)
|
||||
}
|
||||
} else if (earlyEntries != null) {
|
||||
if (earlyEntries != null) {
|
||||
for (entry in earlyEntries) {
|
||||
Log.i(TAG, "[${id.sentTimestamp}] Processing early V2 content for $id")
|
||||
MessageContentProcessorV2.create(context).process(entry.envelope, entry.content, entry.metadata, entry.serverDeliveredTimestamp, processingEarlyContent = true)
|
||||
|
@ -84,13 +76,13 @@ class PushProcessEarlyMessagesJob private constructor(parameters: Parameters) :
|
|||
const val KEY = "PushProcessEarlyMessageJob"
|
||||
|
||||
/**
|
||||
* Enqueues a job to run after the most-recently-enqueued [PushProcessMessageJob].
|
||||
* Enqueues a job to run after the most-recently-enqueued [PushProcessMessageJobV2].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun enqueue() {
|
||||
val jobManger = ApplicationDependencies.getJobManager()
|
||||
|
||||
val youngestProcessJobId: String? = jobManger.find { it.factoryKey == PushProcessMessageJob.KEY }
|
||||
val youngestProcessJobId: String? = jobManger.find { it.factoryKey == PushProcessMessageJobV2.KEY }
|
||||
.maxByOrNull { it.createTime }
|
||||
?.id
|
||||
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.ChangeNumberConstraint
|
||||
import org.thoughtcrime.securesms.messages.ExceptionMetadata
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessorV2
|
||||
import org.thoughtcrime.securesms.messages.MessageState
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
/**
|
||||
* Process messages that did not decrypt/validate successfully.
|
||||
*/
|
||||
class PushProcessMessageErrorV2Job private constructor(
|
||||
parameters: Parameters,
|
||||
private val messageState: MessageState,
|
||||
private val exceptionMetadata: ExceptionMetadata,
|
||||
private val timestamp: Long
|
||||
) : BaseJob(parameters) {
|
||||
|
||||
constructor(messageState: MessageState, exceptionMetadata: ExceptionMetadata, timestamp: Long) : this(
|
||||
parameters = createParameters(exceptionMetadata),
|
||||
messageState = messageState,
|
||||
exceptionMetadata = exceptionMetadata,
|
||||
timestamp = timestamp
|
||||
)
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun shouldTrace(): Boolean = true
|
||||
|
||||
override fun serialize(): ByteArray? {
|
||||
return JsonJobData.Builder()
|
||||
.putInt(KEY_MESSAGE_STATE, messageState.ordinal)
|
||||
.putLong(KEY_TIMESTAMP, timestamp)
|
||||
.putString(KEY_EXCEPTION_SENDER, exceptionMetadata.sender)
|
||||
.putInt(KEY_EXCEPTION_DEVICE, exceptionMetadata.senderDevice)
|
||||
.putString(KEY_EXCEPTION_GROUP_ID, exceptionMetadata.groupId?.toString())
|
||||
.serialize()
|
||||
}
|
||||
|
||||
override fun onRun() {
|
||||
if (messageState == MessageState.DECRYPTED_OK || messageState == MessageState.NOOP) {
|
||||
Log.w(TAG, "Error job queued for valid or no-op decryption, generally this shouldn't happen. Bailing on state: $messageState")
|
||||
return
|
||||
}
|
||||
|
||||
MessageContentProcessorV2.create(context).processException(messageState, exceptionMetadata, timestamp)
|
||||
}
|
||||
|
||||
override fun onShouldRetry(e: Exception): Boolean = false
|
||||
|
||||
override fun onFailure() = Unit
|
||||
|
||||
class Factory : Job.Factory<PushProcessMessageErrorV2Job?> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): PushProcessMessageErrorV2Job {
|
||||
val data = JsonJobData.deserialize(serializedData)
|
||||
|
||||
val state = MessageState.values()[data.getInt(KEY_MESSAGE_STATE)]
|
||||
check(state != MessageState.DECRYPTED_OK && state != MessageState.NOOP)
|
||||
|
||||
val exceptionMetadata = ExceptionMetadata(
|
||||
sender = data.getString(KEY_EXCEPTION_SENDER),
|
||||
senderDevice = data.getInt(KEY_EXCEPTION_DEVICE),
|
||||
groupId = GroupId.parseNullableOrThrow(data.getStringOrDefault(KEY_EXCEPTION_GROUP_ID, null))
|
||||
)
|
||||
|
||||
return PushProcessMessageErrorV2Job(parameters, state, exceptionMetadata, data.getLong(KEY_TIMESTAMP))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY = "PushProcessMessageErrorV2Job"
|
||||
|
||||
val TAG = Log.tag(PushProcessMessageErrorV2Job::class.java)
|
||||
|
||||
private const val KEY_MESSAGE_STATE = "message_state"
|
||||
private const val KEY_TIMESTAMP = "timestamp"
|
||||
private const val KEY_EXCEPTION_SENDER = "exception_sender"
|
||||
private const val KEY_EXCEPTION_DEVICE = "exception_device"
|
||||
private const val KEY_EXCEPTION_GROUP_ID = "exception_groupId"
|
||||
|
||||
@WorkerThread
|
||||
private fun createParameters(exceptionMetadata: ExceptionMetadata): Parameters {
|
||||
val context: Context = ApplicationDependencies.getApplication()
|
||||
|
||||
val recipient = exceptionMetadata.groupId?.let { Recipient.externalPossiblyMigratedGroup(it) } ?: Recipient.external(context, exceptionMetadata.sender)
|
||||
|
||||
return Parameters.Builder()
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.addConstraint(ChangeNumberConstraint.KEY)
|
||||
.setQueue(PushProcessMessageJobV2.getQueueName(recipient.id))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,212 +0,0 @@
|
|||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupsV1MigratedCache;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.ChangeNumberConstraint;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessor;
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessor.ExceptionMetadata;
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessor.MessageState;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class PushProcessMessageJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "PushProcessJob";
|
||||
public static final String QUEUE_PREFIX = "__PUSH_PROCESS_JOB__";
|
||||
|
||||
public static final String TAG = Log.tag(PushProcessMessageJob.class);
|
||||
|
||||
private static final String KEY_MESSAGE_STATE = "message_state";
|
||||
private static final String KEY_MESSAGE_PLAINTEXT = "message_content";
|
||||
private static final String KEY_SMS_MESSAGE_ID = "sms_message_id";
|
||||
private static final String KEY_TIMESTAMP = "timestamp";
|
||||
private static final String KEY_EXCEPTION_SENDER = "exception_sender";
|
||||
private static final String KEY_EXCEPTION_DEVICE = "exception_device";
|
||||
private static final String KEY_EXCEPTION_GROUP_ID = "exception_groupId";
|
||||
|
||||
@NonNull private final MessageState messageState;
|
||||
@Nullable private final SignalServiceContent content;
|
||||
@Nullable private final ExceptionMetadata exceptionMetadata;
|
||||
private final long smsMessageId;
|
||||
private final long timestamp;
|
||||
|
||||
@WorkerThread
|
||||
public PushProcessMessageJob(@NonNull MessageState messageState,
|
||||
@Nullable SignalServiceContent content,
|
||||
@Nullable ExceptionMetadata exceptionMetadata,
|
||||
long smsMessageId,
|
||||
long timestamp)
|
||||
{
|
||||
this(createParameters(content, exceptionMetadata),
|
||||
messageState,
|
||||
content,
|
||||
exceptionMetadata,
|
||||
smsMessageId,
|
||||
timestamp);
|
||||
}
|
||||
|
||||
private PushProcessMessageJob(@NonNull Parameters parameters,
|
||||
@NonNull MessageState messageState,
|
||||
@Nullable SignalServiceContent content,
|
||||
@Nullable ExceptionMetadata exceptionMetadata,
|
||||
long smsMessageId,
|
||||
long timestamp)
|
||||
{
|
||||
super(parameters);
|
||||
|
||||
this.messageState = messageState;
|
||||
this.exceptionMetadata = exceptionMetadata;
|
||||
this.content = content;
|
||||
this.smsMessageId = smsMessageId;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public static @NonNull String getQueueName(@NonNull RecipientId recipientId) {
|
||||
return QUEUE_PREFIX + recipientId.toQueueKey();
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static @NonNull Parameters createParameters(@Nullable SignalServiceContent content, @Nullable ExceptionMetadata exceptionMetadata) {
|
||||
Context context = ApplicationDependencies.getApplication();
|
||||
String queueName = QUEUE_PREFIX;
|
||||
Parameters.Builder builder = new Parameters.Builder()
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.addConstraint(ChangeNumberConstraint.KEY);
|
||||
|
||||
if (content != null) {
|
||||
SignalServiceGroupV2 signalServiceGroupContext = GroupUtil.getGroupContextIfPresent(content);
|
||||
|
||||
if (signalServiceGroupContext != null) {
|
||||
GroupId groupId = GroupId.v2(signalServiceGroupContext.getMasterKey());
|
||||
|
||||
queueName = getQueueName(Recipient.externalPossiblyMigratedGroup(groupId).getId());
|
||||
|
||||
if (groupId.isV2()) {
|
||||
int localRevision = SignalDatabase.groups().getGroupV2Revision(groupId.requireV2());
|
||||
|
||||
if (signalServiceGroupContext.getRevision() > localRevision ||
|
||||
GroupsV1MigratedCache.hasV1Group(groupId.requireV2()))
|
||||
{
|
||||
Log.i(TAG, "Adding network constraint to group-related job.");
|
||||
builder.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(30));
|
||||
}
|
||||
}
|
||||
} else if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getSent().isPresent() && content.getSyncMessage().get().getSent().get().getDestination().isPresent()) {
|
||||
queueName = getQueueName(RecipientId.from(content.getSyncMessage().get().getSent().get().getDestination().get()));
|
||||
} else {
|
||||
queueName = getQueueName(RecipientId.from(content.getSender()));
|
||||
}
|
||||
} else if (exceptionMetadata != null) {
|
||||
Recipient recipient = exceptionMetadata.getGroupId() != null ? Recipient.externalPossiblyMigratedGroup(exceptionMetadata.getGroupId())
|
||||
: Recipient.external(context, exceptionMetadata.getSender());
|
||||
queueName = getQueueName(recipient.getId());
|
||||
}
|
||||
|
||||
builder.setQueue(queueName);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldTrace() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable byte[] serialize() {
|
||||
JsonJobData.Builder dataBuilder = new JsonJobData.Builder()
|
||||
.putInt(KEY_MESSAGE_STATE, messageState.ordinal())
|
||||
.putLong(KEY_SMS_MESSAGE_ID, smsMessageId)
|
||||
.putLong(KEY_TIMESTAMP, timestamp);
|
||||
|
||||
if (messageState == MessageState.DECRYPTED_OK) {
|
||||
dataBuilder.putString(KEY_MESSAGE_PLAINTEXT, Base64.encodeBytes(Objects.requireNonNull(content).serialize()));
|
||||
} else {
|
||||
Objects.requireNonNull(exceptionMetadata);
|
||||
dataBuilder.putString(KEY_EXCEPTION_SENDER, exceptionMetadata.getSender())
|
||||
.putInt(KEY_EXCEPTION_DEVICE, exceptionMetadata.getSenderDevice())
|
||||
.putString(KEY_EXCEPTION_GROUP_ID, exceptionMetadata.getGroupId() == null ? null : exceptionMetadata.getGroupId().toString());
|
||||
}
|
||||
|
||||
return dataBuilder.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRun() throws Exception {
|
||||
MessageContentProcessor processor = MessageContentProcessor.create(context);
|
||||
processor.process(messageState, content, exceptionMetadata, timestamp, smsMessageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(@NonNull Exception e) {
|
||||
return e instanceof PushNetworkException ||
|
||||
e instanceof NoCredentialForRedemptionTimeException ||
|
||||
e instanceof GroupChangeBusyException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<PushProcessMessageJob> {
|
||||
@Override
|
||||
public @NonNull PushProcessMessageJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
|
||||
JsonJobData data = JsonJobData.deserialize(serializedData);
|
||||
|
||||
try {
|
||||
MessageState state = MessageState.values()[data.getInt(KEY_MESSAGE_STATE)];
|
||||
|
||||
if (state == MessageState.DECRYPTED_OK) {
|
||||
return new PushProcessMessageJob(parameters,
|
||||
state,
|
||||
SignalServiceContent.deserialize(Base64.decode(data.getString(KEY_MESSAGE_PLAINTEXT))),
|
||||
null,
|
||||
data.getLong(KEY_SMS_MESSAGE_ID),
|
||||
data.getLong(KEY_TIMESTAMP));
|
||||
} else {
|
||||
ExceptionMetadata exceptionMetadata = new ExceptionMetadata(data.getString(KEY_EXCEPTION_SENDER),
|
||||
data.getInt(KEY_EXCEPTION_DEVICE),
|
||||
GroupId.parseNullableOrThrow(data.getStringOrDefault(KEY_EXCEPTION_GROUP_ID, null)));
|
||||
|
||||
return new PushProcessMessageJob(parameters,
|
||||
state,
|
||||
null,
|
||||
exceptionMetadata,
|
||||
data.getLong(KEY_SMS_MESSAGE_ID),
|
||||
data.getLong(KEY_TIMESTAMP));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import org.whispersystems.signalservice.api.crypto.protos.CompleteMessage
|
|||
import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
|
||||
import java.io.IOException
|
||||
|
@ -29,7 +30,7 @@ import org.whispersystems.signalservice.api.crypto.protos.EnvelopeMetadata as En
|
|||
|
||||
class PushProcessMessageJobV2 private constructor(
|
||||
parameters: Parameters,
|
||||
private val envelope: Envelope,
|
||||
private val envelope: SignalServiceProtos.Envelope,
|
||||
private val content: Content,
|
||||
private val metadata: EnvelopeMetadata,
|
||||
private val serverDeliveredTimestamp: Long
|
||||
|
@ -108,7 +109,8 @@ class PushProcessMessageJobV2 private constructor(
|
|||
*/
|
||||
private val empty1to1QueueCache = HashSet<String>()
|
||||
|
||||
private fun getQueueName(recipientId: RecipientId): String {
|
||||
@JvmStatic
|
||||
fun getQueueName(recipientId: RecipientId): String {
|
||||
return QUEUE_PREFIX + recipientId.toQueueKey()
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ final class RequestGroupV2InfoWorkerJob extends BaseJob {
|
|||
@WorkerThread
|
||||
RequestGroupV2InfoWorkerJob(@NonNull GroupId.V2 groupId, int toRevision) {
|
||||
this(new Parameters.Builder()
|
||||
.setQueue(PushProcessMessageJob.getQueueName(Recipient.externalGroupExact(groupId).getId()))
|
||||
.setQueue(PushProcessMessageJobV2.getQueueName(Recipient.externalGroupExact(groupId).getId()))
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
|
|
|
@ -48,14 +48,13 @@ import org.thoughtcrime.securesms.jobs.PaymentLedgerUpdateJob
|
|||
import org.thoughtcrime.securesms.jobs.PaymentTransactionCheckJob
|
||||
import org.thoughtcrime.securesms.jobs.ProfileKeySendJob
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessEarlyMessagesJob
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessMessageJob
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessMessageJobV2
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob
|
||||
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob
|
||||
import org.thoughtcrime.securesms.jobs.TrimThreadJob
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessor.StorageFailedException
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessorV2.Companion.debug
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessorV2.Companion.log
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessorV2.Companion.warn
|
||||
|
@ -613,7 +612,7 @@ object DataMessageProcessor {
|
|||
|
||||
val paymentNotification = message.payment.notification
|
||||
val uuid = UUID.randomUUID()
|
||||
val queue = "Payment_" + PushProcessMessageJob.getQueueName(senderRecipientId)
|
||||
val queue = "Payment_" + PushProcessMessageJobV2.getQueueName(senderRecipientId)
|
||||
|
||||
try {
|
||||
SignalDatabase.payments.createIncomingPayment(
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.util.hasSharedContact
|
|||
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
|
||||
import java.util.Optional
|
||||
|
||||
object EditMessageProcessor {
|
||||
|
@ -41,7 +42,7 @@ object EditMessageProcessor {
|
|||
context: Context,
|
||||
senderRecipient: Recipient,
|
||||
threadRecipient: Recipient,
|
||||
envelope: SignalServiceProtos.Envelope,
|
||||
envelope: Envelope,
|
||||
content: SignalServiceProtos.Content,
|
||||
metadata: EnvelopeMetadata,
|
||||
earlyMessageCacheEntry: EarlyMessageCacheEntry?
|
||||
|
@ -116,7 +117,7 @@ object EditMessageProcessor {
|
|||
private fun handleEditMediaMessage(
|
||||
senderRecipientId: RecipientId,
|
||||
groupId: GroupId.V2?,
|
||||
envelope: SignalServiceProtos.Envelope,
|
||||
envelope: Envelope,
|
||||
metadata: EnvelopeMetadata,
|
||||
message: DataMessage,
|
||||
targetMessage: MediaMmsMessageRecord
|
||||
|
@ -176,7 +177,7 @@ object EditMessageProcessor {
|
|||
private fun handleEditTextMessage(
|
||||
senderRecipientId: RecipientId,
|
||||
groupId: GroupId.V2?,
|
||||
envelope: SignalServiceProtos.Envelope,
|
||||
envelope: Envelope,
|
||||
metadata: EnvelopeMetadata,
|
||||
message: DataMessage,
|
||||
targetMessage: MediaMmsMessageRecord
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
|
||||
/**
|
||||
* Message processing exception metadata.
|
||||
*/
|
||||
class ExceptionMetadata @JvmOverloads constructor(val sender: String, val senderDevice: Int, val groupId: GroupId? = null)
|
|
@ -1,6 +1,5 @@
|
|||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
|
@ -17,15 +16,11 @@ import org.thoughtcrime.securesms.crypto.ReentrantSessionLock
|
|||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.groups.GroupsV2ProcessingLock
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker.JobListener
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.BackoffUtil
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil
|
||||
import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil.startWhenCapable
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessMessageJob
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessMessageErrorV2Job
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessMessageJobV2
|
||||
import org.thoughtcrime.securesms.jobs.UnableToStartException
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
@ -161,19 +156,6 @@ class IncomingMessageObserver(private val context: Application) {
|
|||
decryptionDrainedListeners.remove(listener)
|
||||
}
|
||||
|
||||
fun notifyDecryptionsDrained() {
|
||||
if (ApplicationDependencies.getJobManager().isQueueEmpty(PushDecryptMessageJob.QUEUE)) {
|
||||
Log.i(TAG, "Queue was empty when notified. Signaling change.")
|
||||
connectionNecessarySemaphore.release()
|
||||
} else {
|
||||
Log.i(TAG, "Queue still had items when notified. Registering listener to signal change.")
|
||||
ApplicationDependencies.getJobManager().addListener(
|
||||
{ it.parameters.queue == PushDecryptMessageJob.QUEUE },
|
||||
DecryptionDrainedQueueListener()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAppForegrounded() {
|
||||
lock.withLock {
|
||||
appVisible = true
|
||||
|
@ -213,17 +195,15 @@ class IncomingMessageObserver(private val context: Application) {
|
|||
val hasNetwork = NetworkConstraint.isMet(context)
|
||||
val hasProxy = SignalStore.proxy().isProxyEnabled
|
||||
val forceWebsocket = SignalStore.internalValues().isWebsocketModeForced
|
||||
val decryptQueueEmpty = ApplicationDependencies.getJobManager().isQueueEmpty(PushDecryptMessageJob.QUEUE)
|
||||
|
||||
val lastInteractionString = if (appVisibleSnapshot) "N/A" else timeIdle.toString() + " ms (" + (if (timeIdle < maxBackgroundTime) "within limit" else "over limit") + ")"
|
||||
val conclusion = registered &&
|
||||
(appVisibleSnapshot || timeIdle < maxBackgroundTime || !fcmEnabled || keepAliveEntries.isNotEmpty()) &&
|
||||
hasNetwork &&
|
||||
decryptQueueEmpty
|
||||
hasNetwork
|
||||
|
||||
val needsConnectionString = if (conclusion) "Needs Connection" else "Does Not Need Connection"
|
||||
|
||||
Log.d(TAG, "[$needsConnectionString] Network: $hasNetwork, Foreground: $appVisibleSnapshot, Time Since Last Interaction: $lastInteractionString, FCM: $fcmEnabled, Stay open requests: $keepAliveEntries, Registered: $registered, Proxy: $hasProxy, Force websocket: $forceWebsocket, Decrypt Queue Empty: $decryptQueueEmpty")
|
||||
Log.d(TAG, "[$needsConnectionString] Network: $hasNetwork, Foreground: $appVisibleSnapshot, Time Since Last Interaction: $lastInteractionString, FCM: $fcmEnabled, Stay open requests: $keepAliveEntries, Registered: $registered, Proxy: $hasProxy, Force websocket: $forceWebsocket")
|
||||
return conclusion
|
||||
}
|
||||
|
||||
|
@ -309,11 +289,9 @@ class IncomingMessageObserver(private val context: Application) {
|
|||
}
|
||||
is MessageDecryptor.Result.Error -> {
|
||||
return result.followUpOperations + FollowUpOperation {
|
||||
PushProcessMessageJob(
|
||||
PushProcessMessageErrorV2Job(
|
||||
result.toMessageState(),
|
||||
null,
|
||||
result.errorMetadata.toExceptionMetadata(),
|
||||
-1,
|
||||
result.envelope.timestamp
|
||||
)
|
||||
}
|
||||
|
@ -342,19 +320,19 @@ class IncomingMessageObserver(private val context: Application) {
|
|||
SignalDatabase.messageLog.deleteEntryForRecipient(envelope.timestamp, senderId, envelope.sourceDevice)
|
||||
}
|
||||
|
||||
private fun MessageDecryptor.Result.toMessageState(): MessageContentProcessor.MessageState {
|
||||
private fun MessageDecryptor.Result.toMessageState(): MessageState {
|
||||
return when (this) {
|
||||
is MessageDecryptor.Result.DecryptionError -> MessageContentProcessor.MessageState.DECRYPTION_ERROR
|
||||
is MessageDecryptor.Result.Ignore -> MessageContentProcessor.MessageState.NOOP
|
||||
is MessageDecryptor.Result.InvalidVersion -> MessageContentProcessor.MessageState.INVALID_VERSION
|
||||
is MessageDecryptor.Result.LegacyMessage -> MessageContentProcessor.MessageState.LEGACY_MESSAGE
|
||||
is MessageDecryptor.Result.Success -> MessageContentProcessor.MessageState.DECRYPTED_OK
|
||||
is MessageDecryptor.Result.UnsupportedDataMessage -> MessageContentProcessor.MessageState.UNSUPPORTED_DATA_MESSAGE
|
||||
is MessageDecryptor.Result.DecryptionError -> MessageState.DECRYPTION_ERROR
|
||||
is MessageDecryptor.Result.Ignore -> MessageState.NOOP
|
||||
is MessageDecryptor.Result.InvalidVersion -> MessageState.INVALID_VERSION
|
||||
is MessageDecryptor.Result.LegacyMessage -> MessageState.LEGACY_MESSAGE
|
||||
is MessageDecryptor.Result.Success -> MessageState.DECRYPTED_OK
|
||||
is MessageDecryptor.Result.UnsupportedDataMessage -> MessageState.UNSUPPORTED_DATA_MESSAGE
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageDecryptor.ErrorMetadata.toExceptionMetadata(): MessageContentProcessor.ExceptionMetadata {
|
||||
return MessageContentProcessor.ExceptionMetadata(
|
||||
private fun MessageDecryptor.ErrorMetadata.toExceptionMetadata(): ExceptionMetadata {
|
||||
return ExceptionMetadata(
|
||||
this.sender,
|
||||
this.senderDevice,
|
||||
this.groupId
|
||||
|
@ -463,21 +441,6 @@ class IncomingMessageObserver(private val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
private inner class DecryptionDrainedQueueListener : JobListener {
|
||||
@SuppressLint("WrongThread")
|
||||
override fun onStateChanged(job: Job, jobState: JobTracker.JobState) {
|
||||
if (jobState.isComplete) {
|
||||
if (ApplicationDependencies.getJobManager().isQueueEmpty(PushDecryptMessageJob.QUEUE)) {
|
||||
Log.i(TAG, "Queue is now empty. Signaling change.")
|
||||
connectionNecessarySemaphore.release()
|
||||
ApplicationDependencies.getJobManager().removeListener(this)
|
||||
} else {
|
||||
Log.i(TAG, "Item finished in queue, but it's still not empty. Waiting to signal change.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ForegroundService : Service() {
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.messages
|
|||
import android.content.Context
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.core.util.toOptional
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey
|
||||
import org.signal.libsignal.protocol.message.DecryptionErrorMessage
|
||||
|
@ -21,6 +22,7 @@ import org.thoughtcrime.securesms.groups.GroupNotAMemberException
|
|||
import org.thoughtcrime.securesms.groups.GroupsV1MigratedCache
|
||||
import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil
|
||||
import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor
|
||||
import org.thoughtcrime.securesms.jobs.AutomaticSessionResetJob
|
||||
import org.thoughtcrime.securesms.jobs.NullMessageSendJob
|
||||
import org.thoughtcrime.securesms.jobs.ResendMessageJob
|
||||
import org.thoughtcrime.securesms.jobs.SenderKeyDistributionSendJob
|
||||
|
@ -38,6 +40,8 @@ import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.toDecryptionEr
|
|||
import org.thoughtcrime.securesms.notifications.v2.ConversationId
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage
|
||||
import org.thoughtcrime.securesms.util.EarlyMessageCacheEntry
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics
|
||||
|
@ -284,6 +288,29 @@ open class MessageContentProcessorV2(private val context: Context) {
|
|||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertErrorMessage(context: Context, sender: Recipient, senderDevice: Int, timestamp: Long, groupId: Optional<GroupId>, marker: (Long) -> Unit) {
|
||||
val textMessage = IncomingTextMessage(
|
||||
sender.id,
|
||||
senderDevice,
|
||||
timestamp,
|
||||
-1,
|
||||
System.currentTimeMillis(),
|
||||
"",
|
||||
groupId,
|
||||
0,
|
||||
false,
|
||||
null
|
||||
)
|
||||
|
||||
SignalDatabase
|
||||
.messages
|
||||
.insertMessageInbox(IncomingEncryptedMessage(textMessage, ""))
|
||||
.ifPresent {
|
||||
marker(it.messageId)
|
||||
ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(it.threadId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -304,7 +331,7 @@ open class MessageContentProcessorV2(private val context: Context) {
|
|||
|
||||
val earlyCacheEntries: List<EarlyMessageCacheEntry>? = ApplicationDependencies
|
||||
.getEarlyMessageCache()
|
||||
.retrieveV2(senderRecipient.id, envelope.timestamp)
|
||||
.retrieve(senderRecipient.id, envelope.timestamp)
|
||||
.orNull()
|
||||
|
||||
if (!processingEarlyContent && earlyCacheEntries != null) {
|
||||
|
@ -315,6 +342,63 @@ open class MessageContentProcessorV2(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun processException(messageState: MessageState, exceptionMetadata: ExceptionMetadata, timestamp: Long) {
|
||||
val sender = Recipient.external(context, exceptionMetadata.sender)
|
||||
|
||||
if (sender.isBlocked) {
|
||||
warn("Ignoring exception content from blocked sender, message state: $messageState")
|
||||
return
|
||||
}
|
||||
|
||||
when (messageState) {
|
||||
MessageState.DECRYPTION_ERROR -> {
|
||||
warn(timestamp, "Handling encryption error.")
|
||||
|
||||
val threadRecipient = if (exceptionMetadata.groupId != null) Recipient.externalPossiblyMigratedGroup(exceptionMetadata.groupId) else sender
|
||||
SignalDatabase
|
||||
.messages
|
||||
.insertBadDecryptMessage(
|
||||
recipientId = sender.id,
|
||||
senderDevice = exceptionMetadata.senderDevice,
|
||||
sentTimestamp = timestamp,
|
||||
receivedTimestamp = System.currentTimeMillis(),
|
||||
threadId = SignalDatabase.threads.getOrCreateThreadIdFor(threadRecipient)
|
||||
)
|
||||
}
|
||||
|
||||
MessageState.INVALID_VERSION -> {
|
||||
warn(timestamp, "Handling invalid version.")
|
||||
insertErrorMessage(context, sender, exceptionMetadata.senderDevice, timestamp, exceptionMetadata.groupId.toOptional()) { messageId ->
|
||||
SignalDatabase.messages.markAsInvalidVersionKeyExchange(messageId)
|
||||
}
|
||||
}
|
||||
|
||||
MessageState.LEGACY_MESSAGE -> {
|
||||
warn(timestamp, "Handling legacy message.")
|
||||
insertErrorMessage(context, sender, exceptionMetadata.senderDevice, timestamp, exceptionMetadata.groupId.toOptional()) { messageId ->
|
||||
SignalDatabase.messages.markAsLegacyVersion(messageId)
|
||||
}
|
||||
}
|
||||
|
||||
MessageState.UNSUPPORTED_DATA_MESSAGE -> {
|
||||
warn(timestamp, "Handling unsupported data message.")
|
||||
insertErrorMessage(context, sender, exceptionMetadata.senderDevice, timestamp, exceptionMetadata.groupId.toOptional()) { messageId ->
|
||||
SignalDatabase.messages.markAsUnsupportedProtocolVersion(messageId)
|
||||
}
|
||||
}
|
||||
|
||||
MessageState.CORRUPT_MESSAGE,
|
||||
MessageState.NO_SESSION -> {
|
||||
warn(timestamp, "Discovered old enqueued bad encrypted message. Scheduling reset.")
|
||||
ApplicationDependencies.getJobManager().add(AutomaticSessionResetJob(sender.id, exceptionMetadata.senderDevice, timestamp))
|
||||
}
|
||||
|
||||
MessageState.DUPLICATE_MESSAGE -> warn(timestamp, "Duplicate message. Dropping.")
|
||||
|
||||
else -> throw AssertionError("Not handled $messageState. ($timestamp)")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMessage(
|
||||
senderRecipient: Recipient,
|
||||
envelope: Envelope,
|
||||
|
@ -350,6 +434,7 @@ open class MessageContentProcessorV2(private val context: Context) {
|
|||
localMetric
|
||||
)
|
||||
}
|
||||
|
||||
content.hasSyncMessage() -> {
|
||||
TextSecurePreferences.setMultiDevice(context, true)
|
||||
|
||||
|
@ -362,6 +447,7 @@ open class MessageContentProcessorV2(private val context: Context) {
|
|||
if (processingEarlyContent) null else EarlyMessageCacheEntry(envelope, content, metadata, serverDeliveredTimestamp)
|
||||
)
|
||||
}
|
||||
|
||||
content.hasCallMessage() -> {
|
||||
log(envelope.timestamp, "Got call message...")
|
||||
|
||||
|
@ -375,6 +461,7 @@ open class MessageContentProcessorV2(private val context: Context) {
|
|||
|
||||
CallMessageProcessor.process(senderRecipient, envelope, content, metadata, serverDeliveredTimestamp)
|
||||
}
|
||||
|
||||
content.hasReceiptMessage() -> {
|
||||
ReceiptMessageProcessor.process(
|
||||
context,
|
||||
|
@ -385,9 +472,11 @@ open class MessageContentProcessorV2(private val context: Context) {
|
|||
if (processingEarlyContent) null else EarlyMessageCacheEntry(envelope, content, metadata, serverDeliveredTimestamp)
|
||||
)
|
||||
}
|
||||
|
||||
content.hasTypingMessage() -> {
|
||||
handleTypingMessage(envelope, metadata, content.typingMessage, senderRecipient)
|
||||
}
|
||||
|
||||
content.hasStoryMessage() -> {
|
||||
StoryMessageProcessor.process(
|
||||
envelope,
|
||||
|
@ -397,9 +486,11 @@ open class MessageContentProcessorV2(private val context: Context) {
|
|||
threadRecipient
|
||||
)
|
||||
}
|
||||
|
||||
content.hasDecryptionErrorMessage() -> {
|
||||
handleRetryReceipt(envelope, metadata, content.decryptionErrorMessage!!.toDecryptionErrorMessage(metadata), senderRecipient)
|
||||
}
|
||||
|
||||
content.hasEditMessage() -> {
|
||||
EditMessageProcessor.process(
|
||||
context,
|
||||
|
@ -411,9 +502,11 @@ open class MessageContentProcessorV2(private val context: Context) {
|
|||
if (processingEarlyContent) null else EarlyMessageCacheEntry(envelope, content, metadata, serverDeliveredTimestamp)
|
||||
)
|
||||
}
|
||||
|
||||
content.hasSenderKeyDistributionMessage() || content.hasPniSignatureMessage() -> {
|
||||
// Already handled, here in order to prevent unrecognized message log
|
||||
}
|
||||
|
||||
else -> {
|
||||
warn(envelope.timestamp, "Got unrecognized message!")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.messages;
|
||||
|
||||
/**
|
||||
* Message processing state/result
|
||||
*/
|
||||
public enum MessageState {
|
||||
DECRYPTED_OK,
|
||||
INVALID_VERSION,
|
||||
CORRUPT_MESSAGE, // Not used, but can't remove due to serialization
|
||||
NO_SESSION, // Not used, but can't remove due to serialization
|
||||
LEGACY_MESSAGE,
|
||||
DUPLICATE_MESSAGE,
|
||||
UNSUPPORTED_DATA_MESSAGE,
|
||||
NOOP,
|
||||
DECRYPTION_ERROR
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.messages;
|
||||
|
||||
class StorageFailedException extends Exception {
|
||||
private final String sender;
|
||||
private final int senderDevice;
|
||||
|
||||
StorageFailedException(Exception e, String sender, int senderDevice) {
|
||||
super(e);
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
}
|
||||
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public int getSenderDevice() {
|
||||
return senderDevice;
|
||||
}
|
||||
}
|
|
@ -21,10 +21,11 @@ import org.thoughtcrime.securesms.util.Base64
|
|||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
|
||||
|
||||
object StoryMessageProcessor {
|
||||
|
||||
fun process(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, senderRecipient: Recipient, threadRecipient: Recipient) {
|
||||
fun process(envelope: Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, senderRecipient: Recipient, threadRecipient: Recipient) {
|
||||
val storyMessage = content.storyMessage
|
||||
|
||||
log(envelope.timestamp, "Story message.")
|
||||
|
@ -79,7 +80,7 @@ object StoryMessageProcessor {
|
|||
SignalDatabase.messages.setTransactionSuccessful()
|
||||
}
|
||||
} catch (e: MmsException) {
|
||||
throw MessageContentProcessor.StorageFailedException(e, metadata.sourceServiceId.toString(), metadata.sourceDeviceId)
|
||||
throw StorageFailedException(e, metadata.sourceServiceId.toString(), metadata.sourceDeviceId)
|
||||
} finally {
|
||||
SignalDatabase.messages.endTransaction()
|
||||
}
|
||||
|
|
|
@ -52,7 +52,6 @@ import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob
|
|||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessor.StorageFailedException
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessorV2.Companion.log
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessorV2.Companion.warn
|
||||
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.groupId
|
||||
|
|
|
@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.jobmanager.Job
|
|||
import org.thoughtcrime.securesms.jobmanager.JobTracker
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker.JobListener
|
||||
import org.thoughtcrime.securesms.jobs.MarkerJob
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessMessageJob
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessMessageJobV2
|
||||
import org.thoughtcrime.securesms.util.NetworkUtil
|
||||
import org.thoughtcrime.securesms.util.PowerManagerCompat
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||
|
@ -81,7 +81,7 @@ object WebSocketDrainer {
|
|||
val queueListener = QueueFindingJobListener()
|
||||
|
||||
jobManager.addListener(
|
||||
{ job: Job -> job.parameters.queue?.startsWith(PushProcessMessageJob.QUEUE_PREFIX) ?: false },
|
||||
{ job: Job -> job.parameters.queue?.startsWith(PushProcessMessageJobV2.QUEUE_PREFIX) ?: false },
|
||||
queueListener
|
||||
)
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ public class ApplicationMigrations {
|
|||
static final int GLIDE_CACHE_CLEAR = 77;
|
||||
static final int SYSTEM_NAME_RESYNC = 78;
|
||||
static final int RECOVERY_PASSWORD_SYNC = 79;
|
||||
static final int DECRYPTIONS_DRAINED = 80;
|
||||
// static final int DECRYPTIONS_DRAINED = 80;
|
||||
static final int REBUILD_MESSAGE_FTS_INDEX_3 = 81;
|
||||
static final int TO_FROM_RECIPIENTS = 82;
|
||||
static final int REBUILD_MESSAGE_FTS_INDEX_4 = 83;
|
||||
|
@ -556,9 +556,10 @@ public class ApplicationMigrations {
|
|||
jobs.put(Version.RECOVERY_PASSWORD_SYNC, new AttributesMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.DECRYPTIONS_DRAINED) {
|
||||
jobs.put(Version.DECRYPTIONS_DRAINED, new DecryptionsDrainedMigrationJob());
|
||||
}
|
||||
// Needed for the conversion to inline decryptions and is no longer necessary
|
||||
// if (lastSeenVersion < Version.DECRYPTIONS_DRAINED) {
|
||||
// jobs.put(Version.DECRYPTIONS_DRAINED, new DecryptionsDrainedMigrationJob());
|
||||
// }
|
||||
|
||||
if (lastSeenVersion < Version.REBUILD_MESSAGE_FTS_INDEX_3) {
|
||||
jobs.put(Version.REBUILD_MESSAGE_FTS_INDEX_3, new RebuildMessageSearchIndexMigrationJob());
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
package org.thoughtcrime.securesms.migrations
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptDrainedJob
|
||||
|
||||
/**
|
||||
* Kicks off a job to notify the [org.thoughtcrime.securesms.messages.IncomingMessageObserver] when the decryption queue is empty.
|
||||
*/
|
||||
internal class DecryptionsDrainedMigrationJob(
|
||||
parameters: Parameters = Parameters.Builder().build()
|
||||
) : MigrationJob(parameters) {
|
||||
|
||||
companion object {
|
||||
val TAG = Log.tag(DecryptionsDrainedMigrationJob::class.java)
|
||||
const val KEY = "DecryptionsDrainedMigrationJob"
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun isUiBlocking(): Boolean = false
|
||||
|
||||
override fun performMigration() {
|
||||
ApplicationDependencies.getJobManager().add(PushDecryptDrainedJob())
|
||||
}
|
||||
|
||||
override fun shouldRetry(e: Exception): Boolean = false
|
||||
|
||||
class Factory : Job.Factory<DecryptionsDrainedMigrationJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): DecryptionsDrainedMigrationJob {
|
||||
return DecryptionsDrainedMigrationJob(parameters)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,16 +12,13 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.MmsReader;
|
||||
import org.thoughtcrime.securesms.database.PushTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
|
@ -30,7 +27,6 @@ import org.thoughtcrime.securesms.transport.RetryLaterException;
|
|||
import org.thoughtcrime.securesms.util.FileUtils;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
@ -131,20 +127,20 @@ public class LegacyMigrationJob extends MigrationJob {
|
|||
PreKeysSyncJob.enqueueIfNeeded();
|
||||
}
|
||||
|
||||
if (lastSeenVersion < NO_DECRYPT_QUEUE_VERSION) {
|
||||
scheduleMessagesInPushDatabase(context);
|
||||
}
|
||||
// if (lastSeenVersion < NO_DECRYPT_QUEUE_VERSION) {
|
||||
// scheduleMessagesInPushDatabase(context);
|
||||
// }
|
||||
|
||||
if (lastSeenVersion < PUSH_DECRYPT_SERIAL_ID_VERSION) {
|
||||
scheduleMessagesInPushDatabase(context);
|
||||
}
|
||||
// if (lastSeenVersion < PUSH_DECRYPT_SERIAL_ID_VERSION) {
|
||||
// scheduleMessagesInPushDatabase(context);
|
||||
// }
|
||||
|
||||
if (lastSeenVersion < MIGRATE_SESSION_PLAINTEXT) {
|
||||
// new TextSecureSessionStore(context, masterSecret).migrateSessions();
|
||||
// new TextSecurePreKeyStore(context, masterSecret).migrateRecords();
|
||||
|
||||
scheduleMessagesInPushDatabase(context);;
|
||||
}
|
||||
// if (lastSeenVersion < MIGRATE_SESSION_PLAINTEXT) {
|
||||
//// new TextSecureSessionStore(context, masterSecret).migrateSessions();
|
||||
//// new TextSecurePreKeyStore(context, masterSecret).migrateRecords();
|
||||
//
|
||||
// scheduleMessagesInPushDatabase(context);;
|
||||
// }
|
||||
|
||||
if (lastSeenVersion < CONTACTS_ACCOUNT_VERSION) {
|
||||
ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(false));
|
||||
|
@ -184,9 +180,9 @@ public class LegacyMigrationJob extends MigrationJob {
|
|||
}
|
||||
}
|
||||
|
||||
if (lastSeenVersion < SQLCIPHER) {
|
||||
scheduleMessagesInPushDatabase(context);
|
||||
}
|
||||
// if (lastSeenVersion < SQLCIPHER) {
|
||||
// scheduleMessagesInPushDatabase(context);
|
||||
// }
|
||||
|
||||
if (lastSeenVersion < SQLCIPHER_COMPLETE) {
|
||||
File file = context.getDatabasePath("messages.db");
|
||||
|
@ -264,17 +260,17 @@ public class LegacyMigrationJob extends MigrationJob {
|
|||
}
|
||||
}
|
||||
|
||||
private static void scheduleMessagesInPushDatabase(@NonNull Context context) {
|
||||
PushTable pushDatabase = SignalDatabase.push();
|
||||
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
|
||||
try (PushTable.Reader pushReader = pushDatabase.readerFor(pushDatabase.getPending())) {
|
||||
SignalServiceEnvelope envelope;
|
||||
while ((envelope = pushReader.getNext()) != null) {
|
||||
jobManager.add(new PushDecryptMessageJob(envelope));
|
||||
}
|
||||
}
|
||||
}
|
||||
// private static void scheduleMessagesInPushDatabase(@NonNull Context context) {
|
||||
// PushTable pushDatabase = SignalDatabase.push();
|
||||
// JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
//
|
||||
// try (PushTable.Reader pushReader = pushDatabase.readerFor(pushDatabase.getPending())) {
|
||||
// SignalServiceEnvelope envelope;
|
||||
// while ((envelope = pushReader.getNext()) != null) {
|
||||
// jobManager.add(new PushDecryptMessageJob(envelope));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
public interface DatabaseUpgradeListener {
|
||||
void setProgress(int progress, int total);
|
||||
|
|
|
@ -4,14 +4,12 @@ import androidx.annotation.NonNull;
|
|||
|
||||
import org.thoughtcrime.securesms.database.model.ServiceMessageId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Sometimes a message that is referencing another message can arrive out of order. In these cases,
|
||||
|
@ -20,32 +18,18 @@ import java.util.Set;
|
|||
*/
|
||||
public final class EarlyMessageCache {
|
||||
|
||||
private final LRUCache<ServiceMessageId, List<SignalServiceContent>> cache = new LRUCache<>(100);
|
||||
private final LRUCache<ServiceMessageId, List<EarlyMessageCacheEntry>> cacheV2 = new LRUCache<>(100);
|
||||
private final LRUCache<ServiceMessageId, List<EarlyMessageCacheEntry>> cache = new LRUCache<>(100);
|
||||
|
||||
/**
|
||||
* @param targetSender The sender of the message this message depends on.
|
||||
* @param targetSentTimestamp The sent timestamp of the message this message depends on.
|
||||
*/
|
||||
public synchronized void store(@NonNull RecipientId targetSender, long targetSentTimestamp, @NonNull SignalServiceContent content) {
|
||||
ServiceMessageId messageId = new ServiceMessageId(targetSender, targetSentTimestamp);
|
||||
List<SignalServiceContent> contentList = cache.get(messageId);
|
||||
|
||||
if (contentList == null) {
|
||||
contentList = new LinkedList<>();
|
||||
}
|
||||
|
||||
contentList.add(content);
|
||||
|
||||
cache.put(messageId, contentList);
|
||||
}
|
||||
|
||||
public synchronized void store(@NonNull RecipientId targetSender,
|
||||
long targetSentTimestamp,
|
||||
@NonNull EarlyMessageCacheEntry cacheEntry)
|
||||
{
|
||||
ServiceMessageId messageId = new ServiceMessageId(targetSender, targetSentTimestamp);
|
||||
List<EarlyMessageCacheEntry> envelopeList = cacheV2.get(messageId);
|
||||
List<EarlyMessageCacheEntry> envelopeList = cache.get(messageId);
|
||||
|
||||
if (envelopeList == null) {
|
||||
envelopeList = new LinkedList<>();
|
||||
|
@ -53,7 +37,7 @@ public final class EarlyMessageCache {
|
|||
|
||||
envelopeList.add(cacheEntry);
|
||||
|
||||
cacheV2.put(messageId, envelopeList);
|
||||
cache.put(messageId, envelopeList);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,21 +46,15 @@ public final class EarlyMessageCache {
|
|||
* @param sender The sender of the message in question.
|
||||
* @param sentTimestamp The sent timestamp of the message in question.
|
||||
*/
|
||||
public synchronized Optional<List<SignalServiceContent>> retrieve(@NonNull RecipientId sender, long sentTimestamp) {
|
||||
public synchronized Optional<List<EarlyMessageCacheEntry>> retrieve(@NonNull RecipientId sender, long sentTimestamp) {
|
||||
return Optional.ofNullable(cache.remove(new ServiceMessageId(sender, sentTimestamp)));
|
||||
}
|
||||
|
||||
public synchronized Optional<List<EarlyMessageCacheEntry>> retrieveV2(@NonNull RecipientId sender, long sentTimestamp) {
|
||||
return Optional.ofNullable(cacheV2.remove(new ServiceMessageId(sender, sentTimestamp)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of all of the {@link ServiceMessageId}s referenced in the cache at the moment of inquiry.
|
||||
* Caution: There is no guarantee that this list will be relevant for any amount of time afterwards.
|
||||
*/
|
||||
public synchronized @NonNull Collection<ServiceMessageId> getAllReferencedIds() {
|
||||
Set<ServiceMessageId> allIds = new HashSet<>(cache.keySet());
|
||||
allIds.addAll(cacheV2.keySet());
|
||||
return allIds;
|
||||
return new HashSet<>(cache.keySet());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,11 +15,9 @@ import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
|||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifest;
|
||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
|
||||
|
@ -29,8 +27,6 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceConf
|
|||
import org.whispersystems.signalservice.internal.push.IdentityCheckRequest;
|
||||
import org.whispersystems.signalservice.internal.push.IdentityCheckResponse;
|
||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceEnvelopeEntity;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceMessagesResult;
|
||||
import org.whispersystems.signalservice.internal.sticker.StickerProtos;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
import org.whispersystems.signalservice.internal.util.concurrent.FutureTransformers;
|
||||
|
@ -43,7 +39,6 @@ import java.io.FileInputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
@ -201,67 +196,4 @@ public class SignalServiceMessageReceiver {
|
|||
|
||||
return new SignalServiceStickerManifest(pack.getTitle(), pack.getAuthor(), cover, stickers);
|
||||
}
|
||||
|
||||
public List<SignalServiceEnvelope> retrieveMessages(boolean allowStories, MessageReceivedCallback callback)
|
||||
throws IOException
|
||||
{
|
||||
List<SignalServiceEnvelope> results = new LinkedList<>();
|
||||
SignalServiceMessagesResult messageResult = socket.getMessages(allowStories);
|
||||
|
||||
for (SignalServiceEnvelopeEntity entity : messageResult.getEnvelopes()) {
|
||||
SignalServiceEnvelope envelope;
|
||||
|
||||
if (entity.hasSource() && entity.getSourceDevice() > 0) {
|
||||
SignalServiceAddress address = new SignalServiceAddress(ServiceId.parseOrThrow(entity.getSourceUuid()), entity.getSourceE164());
|
||||
envelope = new SignalServiceEnvelope(entity.getType(),
|
||||
Optional.of(address),
|
||||
entity.getSourceDevice(),
|
||||
entity.getTimestamp(),
|
||||
entity.getContent(),
|
||||
entity.getServerTimestamp(),
|
||||
messageResult.getServerDeliveredTimestamp(),
|
||||
entity.getServerUuid(),
|
||||
entity.getDestinationUuid(),
|
||||
entity.isUrgent(),
|
||||
entity.isStory(),
|
||||
entity.getReportSpamToken());
|
||||
} else {
|
||||
envelope = new SignalServiceEnvelope(entity.getType(),
|
||||
entity.getTimestamp(),
|
||||
entity.getContent(),
|
||||
entity.getServerTimestamp(),
|
||||
messageResult.getServerDeliveredTimestamp(),
|
||||
entity.getServerUuid(),
|
||||
entity.getDestinationUuid(),
|
||||
entity.isUrgent(),
|
||||
entity.isStory(),
|
||||
entity.getReportSpamToken());
|
||||
}
|
||||
|
||||
callback.onMessage(envelope);
|
||||
results.add(envelope);
|
||||
|
||||
if (envelope.hasServerGuid()) {
|
||||
socket.acknowledgeMessage(envelope.getServerGuid());
|
||||
} else {
|
||||
socket.acknowledgeMessage(entity.getSourceE164(), entity.getTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public void setSoTimeoutMillis(long soTimeoutMillis) {
|
||||
socket.setSoTimeoutMillis(soTimeoutMillis);
|
||||
}
|
||||
|
||||
public interface MessageReceivedCallback {
|
||||
public void onMessage(SignalServiceEnvelope envelope);
|
||||
}
|
||||
|
||||
public static class NullMessageReceivedCallback implements MessageReceivedCallback {
|
||||
@Override
|
||||
public void onMessage(SignalServiceEnvelope envelope) {}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -44,28 +44,22 @@ import org.signal.libsignal.protocol.message.SignalMessage;
|
|||
import org.whispersystems.signalservice.api.InvalidMessageStructureException;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceMetadata;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage;
|
||||
import org.whispersystems.signalservice.internal.push.PushTransportDetails;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope;
|
||||
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
|
||||
import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer;
|
||||
import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer;
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* This is used to encrypt + decrypt received {@link SignalServiceEnvelope}s.
|
||||
* This is used to encrypt + decrypt received envelopes.
|
||||
*/
|
||||
public class SignalServiceCipher {
|
||||
|
||||
|
@ -129,41 +123,6 @@ public class SignalServiceCipher {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a received {@link SignalServiceEnvelope}
|
||||
*
|
||||
* @param envelope The received SignalServiceEnvelope
|
||||
*
|
||||
* @return a decrypted SignalServiceContent
|
||||
*/
|
||||
public SignalServiceContent decrypt(SignalServiceEnvelope envelope)
|
||||
throws InvalidMetadataMessageException, InvalidMetadataVersionException,
|
||||
ProtocolInvalidKeyIdException, ProtocolLegacyMessageException,
|
||||
ProtocolUntrustedIdentityException, ProtocolNoSessionException,
|
||||
ProtocolInvalidVersionException, ProtocolInvalidMessageException,
|
||||
ProtocolInvalidKeyException, ProtocolDuplicateMessageException,
|
||||
SelfSendException, UnsupportedDataMessageException, InvalidMessageStructureException
|
||||
{
|
||||
try {
|
||||
if (envelope.hasContent()) {
|
||||
Plaintext plaintext = decryptInternal(envelope.getProto(), envelope.getServerDeliveredTimestamp());
|
||||
SignalServiceProtos.Content content = SignalServiceProtos.Content.parseFrom(plaintext.getData());
|
||||
|
||||
SignalServiceContentProto contentProto = SignalServiceContentProto.newBuilder()
|
||||
.setLocalAddress(SignalServiceAddressProtobufSerializer.toProtobuf(localAddress))
|
||||
.setMetadata(SignalServiceMetadataProtobufSerializer.toProtobuf(plaintext.metadata))
|
||||
.setContent(content)
|
||||
.build();
|
||||
|
||||
return SignalServiceContent.createFromProto(contentProto);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidMetadataMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SignalServiceCipherResult decrypt(Envelope envelope, long serverDeliveredTimestamp)
|
||||
throws InvalidMetadataMessageException, InvalidMetadataVersionException,
|
||||
ProtocolInvalidKeyIdException, ProtocolLegacyMessageException,
|
||||
|
|
|
@ -1,333 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.messages;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.Preconditions;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope;
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceEnvelopeProto;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* This class represents an encrypted Signal Service envelope.
|
||||
*
|
||||
* The envelope contains the wrapping information, such as the sender, the
|
||||
* message timestamp, the encrypted message type, etc.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class SignalServiceEnvelope {
|
||||
|
||||
private static final String TAG = SignalServiceEnvelope.class.getSimpleName();
|
||||
|
||||
private final Envelope envelope;
|
||||
private final long serverDeliveredTimestamp;
|
||||
|
||||
/**
|
||||
* Construct an envelope from a serialized, Base64 encoded SignalServiceEnvelope, encrypted
|
||||
* with a signaling key.
|
||||
*
|
||||
* @param message The serialized SignalServiceEnvelope, base64 encoded and encrypted.
|
||||
*/
|
||||
public SignalServiceEnvelope(String message, long serverDeliveredTimestamp) throws IOException {
|
||||
this(Base64.decode(message), serverDeliveredTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an envelope from a serialized SignalServiceEnvelope, encrypted with a signaling key.
|
||||
*
|
||||
* @param input The serialized and (optionally) encrypted SignalServiceEnvelope.
|
||||
*/
|
||||
public SignalServiceEnvelope(byte[] input, long serverDeliveredTimestamp) throws IOException {
|
||||
this.envelope = Envelope.parseFrom(input);
|
||||
this.serverDeliveredTimestamp = serverDeliveredTimestamp;
|
||||
}
|
||||
|
||||
public SignalServiceEnvelope(int type,
|
||||
Optional<SignalServiceAddress> sender,
|
||||
int senderDevice,
|
||||
long timestamp,
|
||||
byte[] content,
|
||||
long serverReceivedTimestamp,
|
||||
long serverDeliveredTimestamp,
|
||||
String uuid,
|
||||
String destinationServiceId,
|
||||
boolean urgent,
|
||||
boolean story,
|
||||
byte[] reportingToken)
|
||||
{
|
||||
Envelope.Builder builder = Envelope.newBuilder()
|
||||
.setType(Envelope.Type.valueOf(type))
|
||||
.setSourceDevice(senderDevice)
|
||||
.setTimestamp(timestamp)
|
||||
.setServerTimestamp(serverReceivedTimestamp)
|
||||
.setDestinationServiceId(destinationServiceId)
|
||||
.setUrgent(urgent)
|
||||
.setStory(story);
|
||||
|
||||
if (sender.isPresent()) {
|
||||
builder.setSourceServiceId(sender.get().getServiceId().toString());
|
||||
}
|
||||
|
||||
if (uuid != null) {
|
||||
builder.setServerGuid(uuid);
|
||||
}
|
||||
|
||||
if (content != null) {
|
||||
builder.setContent(ByteString.copyFrom(content));
|
||||
}
|
||||
|
||||
if (reportingToken != null) {
|
||||
builder.setReportingToken(ByteString.copyFrom(reportingToken));
|
||||
}
|
||||
|
||||
this.envelope = builder.build();
|
||||
this.serverDeliveredTimestamp = serverDeliveredTimestamp;
|
||||
}
|
||||
|
||||
public SignalServiceEnvelope(int type,
|
||||
long timestamp,
|
||||
byte[] content,
|
||||
long serverReceivedTimestamp,
|
||||
long serverDeliveredTimestamp,
|
||||
String uuid,
|
||||
String destinationServiceId,
|
||||
boolean urgent,
|
||||
boolean story,
|
||||
byte[] reportingToken)
|
||||
{
|
||||
Envelope.Builder builder = Envelope.newBuilder()
|
||||
.setType(Envelope.Type.valueOf(type))
|
||||
.setTimestamp(timestamp)
|
||||
.setServerTimestamp(serverReceivedTimestamp)
|
||||
.setDestinationServiceId(destinationServiceId)
|
||||
.setUrgent(urgent)
|
||||
.setStory(story);
|
||||
|
||||
if (uuid != null) {
|
||||
builder.setServerGuid(uuid);
|
||||
}
|
||||
|
||||
if (content != null) {
|
||||
builder.setContent(ByteString.copyFrom(content));
|
||||
}
|
||||
|
||||
if (reportingToken != null) {
|
||||
builder.setReportingToken(ByteString.copyFrom(reportingToken));
|
||||
}
|
||||
|
||||
this.envelope = builder.build();
|
||||
this.serverDeliveredTimestamp = serverDeliveredTimestamp;
|
||||
}
|
||||
|
||||
public String getServerGuid() {
|
||||
return envelope.getServerGuid();
|
||||
}
|
||||
|
||||
public boolean hasServerGuid() {
|
||||
return envelope.hasServerGuid();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if either a source E164 or UUID is present.
|
||||
*/
|
||||
public boolean hasSourceServiceId() {
|
||||
return envelope.hasSourceServiceId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The envelope's sender as a UUID.
|
||||
*/
|
||||
public Optional<String> getSourceServiceId() {
|
||||
return Optional.ofNullable(envelope.getSourceServiceId());
|
||||
}
|
||||
|
||||
public String getSourceIdentifier() {
|
||||
return getSourceServiceId().get().toString();
|
||||
}
|
||||
|
||||
public boolean hasSourceDevice() {
|
||||
return envelope.hasSourceDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The envelope's sender device ID.
|
||||
*/
|
||||
public int getSourceDevice() {
|
||||
return envelope.getSourceDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The envelope content type.
|
||||
*/
|
||||
public int getType() {
|
||||
return envelope.getType().getNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The timestamp this envelope was sent.
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return envelope.getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The server timestamp of when the server received the envelope.
|
||||
*/
|
||||
public long getServerReceivedTimestamp() {
|
||||
return envelope.getServerTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The server timestamp of when the envelope was delivered to us.
|
||||
*/
|
||||
public long getServerDeliveredTimestamp() {
|
||||
return serverDeliveredTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the envelope contains an encrypted SignalServiceContent
|
||||
*/
|
||||
public boolean hasContent() {
|
||||
return envelope.hasContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The envelope's encrypted SignalServiceContent.
|
||||
*/
|
||||
public byte[] getContent() {
|
||||
return envelope.getContent().toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the containing message is a {@link org.signal.libsignal.protocol.message.SignalMessage}
|
||||
*/
|
||||
public boolean isSignalMessage() {
|
||||
return envelope.getType().getNumber() == Envelope.Type.CIPHERTEXT_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the containing message is a {@link org.signal.libsignal.protocol.message.PreKeySignalMessage}
|
||||
*/
|
||||
public boolean isPreKeySignalMessage() {
|
||||
return envelope.getType().getNumber() == Envelope.Type.PREKEY_BUNDLE_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the containing message is a delivery receipt.
|
||||
*/
|
||||
public boolean isReceipt() {
|
||||
return envelope.getType().getNumber() == Envelope.Type.RECEIPT_VALUE;
|
||||
}
|
||||
|
||||
public boolean isUnidentifiedSender() {
|
||||
return envelope.getType().getNumber() == Envelope.Type.UNIDENTIFIED_SENDER_VALUE;
|
||||
}
|
||||
|
||||
public boolean isPlaintextContent() {
|
||||
return envelope.getType().getNumber() == Envelope.Type.PLAINTEXT_CONTENT_VALUE;
|
||||
}
|
||||
|
||||
public boolean hasDestinationUuid() {
|
||||
return envelope.hasDestinationServiceId() && UuidUtil.isUuid(envelope.getDestinationServiceId());
|
||||
}
|
||||
|
||||
public String getDestinationServiceId() {
|
||||
return envelope.getDestinationServiceId();
|
||||
}
|
||||
|
||||
public boolean isUrgent() {
|
||||
return envelope.getUrgent();
|
||||
}
|
||||
|
||||
public boolean isStory() {
|
||||
return envelope.getStory();
|
||||
}
|
||||
|
||||
public boolean hasReportingToken() {
|
||||
return envelope.hasReportingToken();
|
||||
}
|
||||
|
||||
public byte[] getReportingToken() {
|
||||
return envelope.getReportingToken().toByteArray();
|
||||
}
|
||||
|
||||
public Envelope getProto() {
|
||||
return envelope;
|
||||
}
|
||||
|
||||
private SignalServiceEnvelopeProto.Builder serializeToProto() {
|
||||
SignalServiceEnvelopeProto.Builder builder = SignalServiceEnvelopeProto.newBuilder()
|
||||
.setType(getType())
|
||||
.setDeviceId(getSourceDevice())
|
||||
.setTimestamp(getTimestamp())
|
||||
.setServerReceivedTimestamp(getServerReceivedTimestamp())
|
||||
.setServerDeliveredTimestamp(getServerDeliveredTimestamp())
|
||||
.setUrgent(isUrgent())
|
||||
.setStory(isStory());
|
||||
|
||||
if (getSourceServiceId().isPresent()) {
|
||||
builder.setSourceServiceId(getSourceServiceId().get());
|
||||
}
|
||||
|
||||
if (hasContent()) {
|
||||
builder.setContent(ByteString.copyFrom(getContent()));
|
||||
}
|
||||
|
||||
if (hasServerGuid()) {
|
||||
builder.setServerGuid(getServerGuid());
|
||||
}
|
||||
|
||||
if (hasDestinationUuid()) {
|
||||
builder.setDestinationServiceId(getDestinationServiceId());
|
||||
}
|
||||
|
||||
if (hasReportingToken()) {
|
||||
builder.setReportingToken(ByteString.copyFrom(getReportingToken()));
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return serializeToProto().build().toByteArray();
|
||||
}
|
||||
|
||||
public static SignalServiceEnvelope deserialize(byte[] serialized) {
|
||||
SignalServiceEnvelopeProto proto = null;
|
||||
try {
|
||||
proto = SignalServiceEnvelopeProto.parseFrom(serialized);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Preconditions.checkNotNull(proto);
|
||||
|
||||
ServiceId sourceServiceId = proto.hasSourceServiceId() ? ServiceId.parseOrNull(proto.getSourceServiceId()) : null;
|
||||
|
||||
return new SignalServiceEnvelope(proto.getType(),
|
||||
sourceServiceId != null ? Optional.of(new SignalServiceAddress(sourceServiceId)) : Optional.empty(),
|
||||
proto.getDeviceId(),
|
||||
proto.getTimestamp(),
|
||||
proto.hasContent() ? proto.getContent().toByteArray() : null,
|
||||
proto.getServerReceivedTimestamp(),
|
||||
proto.getServerDeliveredTimestamp(),
|
||||
proto.getServerGuid(),
|
||||
proto.getDestinationServiceId(),
|
||||
proto.getUrgent(),
|
||||
proto.getStory(),
|
||||
proto.hasReportingToken() ? proto.getReportingToken().toByteArray() : null);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue