Remove job-based decryption support and MCPv1.

This commit is contained in:
Cody Henthorne 2023-08-16 14:28:14 -04:00 committed by GitHub
parent 3d94122abc
commit fbf4de0ec5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 476 additions and 5165 deletions

View file

@ -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()
}
}
}

View file

@ -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!!)
}
}

View file

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

View file

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

View file

@ -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

View file

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

View file

@ -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

View file

@ -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")
}
}

View file

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

View file

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

View file

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

View file

@ -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())
}
}
}

View file

@ -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(),

View file

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

View file

@ -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(),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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

View file

@ -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()
}
}
}

View file

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

View file

@ -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()
}

View file

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

View file

@ -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(

View file

@ -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

View file

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

View file

@ -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

View file

@ -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!")
}

View file

@ -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
}

View file

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

View file

@ -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()
}

View file

@ -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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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) {}
}
}

View file

@ -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,

View file

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