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