Convert SignalService, Database, Group, Payment, and other remaining protos to wire.

This commit is contained in:
Cody Henthorne
2023-09-18 15:32:43 -04:00
committed by Alex Hart
parent a6b7d0bcc5
commit efbd5cab85
267 changed files with 7100 additions and 7214 deletions

View File

@@ -4,6 +4,7 @@ import org.signal.ringrtc.CallId
import org.thoughtcrime.securesms.database.model.IdentityRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.messages.MessageContentProcessor.Companion.log
import org.thoughtcrime.securesms.messages.MessageContentProcessor.Companion.warn
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.ringrtc.RemotePeer
@@ -18,49 +19,56 @@ import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.api.messages.calls.HangupMessage
import org.whispersystems.signalservice.api.messages.calls.OfferMessage
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.CallMessage.Offer
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.CallMessage.Opaque
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.CallMessage
import org.whispersystems.signalservice.internal.push.CallMessage.Offer
import org.whispersystems.signalservice.internal.push.CallMessage.Opaque
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.Envelope
import kotlin.time.Duration.Companion.milliseconds
object CallMessageProcessor {
fun process(
senderRecipient: Recipient,
envelope: Envelope,
content: SignalServiceProtos.Content,
content: Content,
metadata: EnvelopeMetadata,
serverDeliveredTimestamp: Long
) {
val callMessage = content.callMessage
val callMessage = content.callMessage!!
when {
callMessage.hasOffer() -> handleCallOfferMessage(envelope, metadata, callMessage.offer, senderRecipient.id, serverDeliveredTimestamp, callMessage.multiRing)
callMessage.hasAnswer() -> handleCallAnswerMessage(envelope, metadata, callMessage.answer, senderRecipient.id, callMessage.multiRing)
callMessage.iceUpdateList.isNotEmpty() -> handleCallIceUpdateMessage(envelope, metadata, callMessage.iceUpdateList, senderRecipient.id)
callMessage.hasHangup() || callMessage.hasLegacyHangup() -> {
val hangup = if (callMessage.hasHangup()) callMessage.hangup else callMessage.legacyHangup
handleCallHangupMessage(envelope, metadata, hangup, senderRecipient.id)
callMessage.offer != null -> handleCallOfferMessage(envelope, metadata, callMessage.offer!!, senderRecipient.id, serverDeliveredTimestamp)
callMessage.answer != null -> handleCallAnswerMessage(envelope, metadata, callMessage.answer!!, senderRecipient.id)
callMessage.iceUpdate.isNotEmpty() -> handleCallIceUpdateMessage(envelope, metadata, callMessage.iceUpdate, senderRecipient.id)
callMessage.hangup != null || callMessage.legacyHangup != null -> {
handleCallHangupMessage(envelope, metadata, callMessage.hangup ?: callMessage.legacyHangup, senderRecipient.id)
}
callMessage.hasBusy() -> handleCallBusyMessage(envelope, metadata, callMessage.busy, senderRecipient.id)
callMessage.hasOpaque() -> handleCallOpaqueMessage(envelope, metadata, callMessage.opaque, senderRecipient.requireAci(), serverDeliveredTimestamp)
callMessage.busy != null -> handleCallBusyMessage(envelope, metadata, callMessage.busy!!, senderRecipient.id)
callMessage.opaque != null -> handleCallOpaqueMessage(envelope, metadata, callMessage.opaque!!, senderRecipient.requireAci(), serverDeliveredTimestamp)
}
}
private fun handleCallOfferMessage(envelope: Envelope, metadata: EnvelopeMetadata, offer: Offer, senderRecipientId: RecipientId, serverDeliveredTimestamp: Long, multiRing: Boolean) {
log(envelope.timestamp, "handleCallOfferMessage...")
private fun handleCallOfferMessage(envelope: Envelope, metadata: EnvelopeMetadata, offer: Offer, senderRecipientId: RecipientId, serverDeliveredTimestamp: Long) {
log(envelope.timestamp!!, "handleCallOfferMessage...")
val remotePeer = RemotePeer(senderRecipientId, CallId(offer.id))
val offerId = if (offer.id != null && offer.type != null && ((offer.opaque != null) xor (offer.sdp != null))) {
offer.id!!
} else {
warn(envelope.timestamp!!, "Invalid offer, missing id/type, or invalid combination of opaque/sdp")
return
}
val remotePeer = RemotePeer(senderRecipientId, CallId(offerId))
val remoteIdentityKey = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecord(senderRecipientId).map { (_, identityKey): IdentityRecord -> identityKey.serialize() }.get()
ApplicationDependencies.getSignalCallManager()
.receivedOffer(
CallMetadata(remotePeer, metadata.sourceDeviceId),
offer.toCallOfferMetadata(),
OfferMetadata(offer.opaque?.toByteArray(), offer.sdp, OfferMessage.Type.fromProto(offer.type!!)),
ReceivedOfferMetadata(
remoteIdentityKey,
envelope.serverTimestamp,
serverDeliveredTimestamp,
multiRing
envelope.serverTimestamp!!,
serverDeliveredTimestamp
)
)
}
@@ -68,94 +76,119 @@ object CallMessageProcessor {
private fun handleCallAnswerMessage(
envelope: Envelope,
metadata: EnvelopeMetadata,
answer: SignalServiceProtos.CallMessage.Answer,
senderRecipientId: RecipientId,
multiRing: Boolean
answer: CallMessage.Answer,
senderRecipientId: RecipientId
) {
log(envelope.timestamp, "handleCallAnswerMessage...")
log(envelope.timestamp!!, "handleCallAnswerMessage...")
val remotePeer = RemotePeer(senderRecipientId, CallId(answer.id))
val answerId = if (answer.id != null && ((answer.opaque != null) xor (answer.sdp != null))) {
answer.id!!
} else {
warn(envelope.timestamp!!, "Invalid answer, missing id")
return
}
val remotePeer = RemotePeer(senderRecipientId, CallId(answerId))
val remoteIdentityKey = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecord(senderRecipientId).map { (_, identityKey): IdentityRecord -> identityKey.serialize() }.get()
ApplicationDependencies.getSignalCallManager()
.receivedAnswer(
CallMetadata(remotePeer, metadata.sourceDeviceId),
AnswerMetadata(if (answer.hasOpaque()) answer.opaque.toByteArray() else null, if (answer.hasSdp()) answer.sdp else null),
ReceivedAnswerMetadata(remoteIdentityKey, multiRing)
AnswerMetadata(answer.opaque?.toByteArray(), answer.sdp),
ReceivedAnswerMetadata(remoteIdentityKey)
)
}
private fun handleCallIceUpdateMessage(
envelope: Envelope,
metadata: EnvelopeMetadata,
iceUpdateList: MutableList<SignalServiceProtos.CallMessage.IceUpdate>,
iceUpdateList: List<CallMessage.IceUpdate>,
senderRecipientId: RecipientId
) {
log(envelope.timestamp, "handleCallIceUpdateMessage... " + iceUpdateList.size)
log(envelope.timestamp!!, "handleCallIceUpdateMessage... " + iceUpdateList.size)
val iceCandidates: MutableList<ByteArray> = ArrayList(iceUpdateList.size)
var callId: Long = -1
iceUpdateList
.filter { it.hasOpaque() }
.filter { it.opaque != null && it.id != null }
.forEach { iceUpdate ->
iceCandidates += iceUpdate.opaque.toByteArray()
callId = iceUpdate.id
iceCandidates += iceUpdate.opaque!!.toByteArray()
callId = iceUpdate.id!!
}
val remotePeer = RemotePeer(senderRecipientId, CallId(callId))
ApplicationDependencies.getSignalCallManager()
.receivedIceCandidates(
CallMetadata(remotePeer, metadata.sourceDeviceId),
iceCandidates
)
if (iceCandidates.isNotEmpty()) {
val remotePeer = RemotePeer(senderRecipientId, CallId(callId))
ApplicationDependencies.getSignalCallManager()
.receivedIceCandidates(
CallMetadata(remotePeer, metadata.sourceDeviceId),
iceCandidates
)
} else {
warn(envelope.timestamp!!, "Invalid ice updates, all missing opaque and/or call id")
}
}
private fun handleCallHangupMessage(
envelope: Envelope,
metadata: EnvelopeMetadata,
hangup: SignalServiceProtos.CallMessage.Hangup,
hangup: CallMessage.Hangup?,
senderRecipientId: RecipientId
) {
log(envelope.timestamp, "handleCallHangupMessage")
log(envelope.timestamp!!, "handleCallHangupMessage")
val remotePeer = RemotePeer(senderRecipientId, CallId(hangup.id))
val (hangupId, hangupDeviceId) = if (hangup?.id != null && hangup.deviceId != null) {
hangup.id!! to hangup.deviceId!!
} else {
warn(envelope.timestamp!!, "Invalid hangup, null message or missing id/deviceId")
return
}
val remotePeer = RemotePeer(senderRecipientId, CallId(hangupId))
ApplicationDependencies.getSignalCallManager()
.receivedCallHangup(
CallMetadata(remotePeer, metadata.sourceDeviceId),
HangupMetadata(HangupMessage.Type.fromProto(hangup.type), hangup.deviceId)
HangupMetadata(HangupMessage.Type.fromProto(hangup.type), hangupDeviceId)
)
}
private fun handleCallBusyMessage(envelope: Envelope, metadata: EnvelopeMetadata, busy: SignalServiceProtos.CallMessage.Busy, senderRecipientId: RecipientId) {
log(envelope.timestamp, "handleCallBusyMessage")
private fun handleCallBusyMessage(envelope: Envelope, metadata: EnvelopeMetadata, busy: CallMessage.Busy, senderRecipientId: RecipientId) {
log(envelope.timestamp!!, "handleCallBusyMessage")
val remotePeer = RemotePeer(senderRecipientId, CallId(busy.id))
val busyId = if (busy.id != null) {
busy.id!!
} else {
warn(envelope.timestamp!!, "Invalid busy, missing call id")
return
}
val remotePeer = RemotePeer(senderRecipientId, CallId(busyId))
ApplicationDependencies.getSignalCallManager().receivedCallBusy(CallMetadata(remotePeer, metadata.sourceDeviceId))
}
private fun handleCallOpaqueMessage(envelope: Envelope, metadata: EnvelopeMetadata, opaque: Opaque, senderServiceId: ServiceId, serverDeliveredTimestamp: Long) {
log(envelope.timestamp.toString(), "handleCallOpaqueMessage")
log(envelope.timestamp!!, "handleCallOpaqueMessage")
val data = if (opaque.data_ != null) {
opaque.data_!!.toByteArray()
} else {
warn(envelope.timestamp!!, "Invalid opaque message, null data")
return
}
var messageAgeSeconds: Long = 0
if (envelope.serverTimestamp in 1..serverDeliveredTimestamp) {
messageAgeSeconds = (serverDeliveredTimestamp - envelope.serverTimestamp) / 1000
messageAgeSeconds = (serverDeliveredTimestamp - envelope.serverTimestamp!!).milliseconds.inWholeSeconds
}
ApplicationDependencies.getSignalCallManager()
.receivedOpaqueMessage(
OpaqueMessageMetadata(
senderServiceId.rawUuid,
opaque.data.toByteArray(),
data,
metadata.sourceDeviceId,
messageAgeSeconds
)
)
}
private fun Offer.toCallOfferMetadata(): OfferMetadata {
val sdp = if (hasSdp()) sdp else null
val opaque = if (hasOpaque()) opaque else null
return OfferMetadata(opaque?.toByteArray(), sdp, OfferMessage.Type.fromProto(type))
}
}

View File

@@ -1,9 +1,10 @@
package org.thoughtcrime.securesms.messages
import ProtoUtil.isNotEmpty
import android.content.Context
import android.text.TextUtils
import com.google.protobuf.ByteString
import com.mobilecoin.lib.exceptions.SerializationException
import okio.ByteString.Companion.toByteString
import org.signal.core.util.Hex
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
@@ -36,6 +37,7 @@ import org.thoughtcrime.securesms.database.model.ParentStoryId
import org.thoughtcrime.securesms.database.model.ParentStoryId.DirectReply
import org.thoughtcrime.securesms.database.model.ParentStoryId.GroupReply
import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.database.model.StickerRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.database.model.toBodyRangeList
@@ -59,6 +61,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil
import org.thoughtcrime.securesms.messages.MessageContentProcessor.Companion.debug
import org.thoughtcrime.securesms.messages.MessageContentProcessor.Companion.log
import org.thoughtcrime.securesms.messages.MessageContentProcessor.Companion.warn
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.expireTimerDuration
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.groupMasterKey
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.hasGroupContext
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.hasRemoteDelete
@@ -98,12 +101,12 @@ import org.whispersystems.signalservice.api.payments.Money
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.util.OptionalUtil.asOptional
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.BodyRange
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Preview
import org.whispersystems.signalservice.internal.push.BodyRange
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.GroupContextV2
import org.whispersystems.signalservice.internal.push.Preview
import java.security.SecureRandom
import java.util.Optional
import java.util.UUID
@@ -125,13 +128,13 @@ object DataMessageProcessor {
earlyMessageCacheEntry: EarlyMessageCacheEntry?,
localMetrics: SignalLocalMetrics.MessageReceive?
) {
val message: DataMessage = content.dataMessage
val groupSecretParams = if (message.hasGroupContext) GroupSecretParams.deriveFromMasterKey(message.groupV2.groupMasterKey) else null
val message: DataMessage = content.dataMessage!!
val groupSecretParams = if (message.hasGroupContext) GroupSecretParams.deriveFromMasterKey(message.groupV2!!.groupMasterKey) else null
val groupId: GroupId.V2? = if (groupSecretParams != null) GroupId.v2(groupSecretParams.publicParams.groupIdentifier) else null
var groupProcessResult: MessageContentProcessor.Gv2PreProcessResult? = null
if (groupId != null) {
groupProcessResult = MessageContentProcessor.handleGv2PreProcessing(context, envelope.timestamp, content, metadata, groupId, message.groupV2, senderRecipient, groupSecretParams)
groupProcessResult = MessageContentProcessor.handleGv2PreProcessing(context, envelope.timestamp!!, content, metadata, groupId, message.groupV2!!, senderRecipient, groupSecretParams)
if (groupProcessResult == MessageContentProcessor.Gv2PreProcessResult.IGNORE) {
return
}
@@ -140,20 +143,20 @@ object DataMessageProcessor {
var messageId: MessageId? = null
when {
message.isInvalid -> handleInvalidMessage(context, senderRecipient.id, metadata.sourceDeviceId, groupId, envelope.timestamp)
message.isInvalid -> handleInvalidMessage(context, senderRecipient.id, metadata.sourceDeviceId, groupId, envelope.timestamp!!)
message.isEndSession -> messageId = handleEndSessionMessage(context, senderRecipient.id, envelope, metadata)
message.isExpirationUpdate -> messageId = handleExpirationUpdate(envelope, metadata, senderRecipient.id, threadRecipient.id, groupId, message.expireTimer.seconds, receivedTime, false)
message.isExpirationUpdate -> messageId = handleExpirationUpdate(envelope, metadata, senderRecipient.id, threadRecipient.id, groupId, message.expireTimerDuration, receivedTime, false)
message.isStoryReaction -> messageId = handleStoryReaction(context, envelope, metadata, message, senderRecipient.id, groupId)
message.hasReaction() -> messageId = handleReaction(context, envelope, message, senderRecipient.id, earlyMessageCacheEntry)
message.reaction != null -> messageId = handleReaction(context, envelope, message, senderRecipient.id, earlyMessageCacheEntry)
message.hasRemoteDelete -> messageId = handleRemoteDelete(context, envelope, message, senderRecipient.id, earlyMessageCacheEntry)
message.isPaymentActivationRequest -> messageId = handlePaymentActivation(envelope, metadata, message, senderRecipient.id, receivedTime, isActivatePaymentsRequest = true, isPaymentsActivated = false)
message.isPaymentActivated -> messageId = handlePaymentActivation(envelope, metadata, message, senderRecipient.id, receivedTime, isActivatePaymentsRequest = false, isPaymentsActivated = true)
message.hasPayment() -> messageId = handlePayment(context, envelope, metadata, message, senderRecipient.id, receivedTime)
message.hasStoryContext() -> messageId = handleStoryReply(context, envelope, metadata, message, senderRecipient, groupId, receivedTime)
message.hasGiftBadge() -> messageId = handleGiftMessage(context, envelope, metadata, message, senderRecipient, threadRecipient.id, receivedTime)
message.payment != null -> messageId = handlePayment(context, envelope, metadata, message, senderRecipient.id, receivedTime)
message.storyContext != null -> messageId = handleStoryReply(context, envelope, metadata, message, senderRecipient, groupId, receivedTime)
message.giftBadge != null -> messageId = handleGiftMessage(context, envelope, metadata, message, senderRecipient, threadRecipient.id, receivedTime)
message.isMediaMessage -> messageId = handleMediaMessage(context, envelope, metadata, message, senderRecipient, threadRecipient, groupId, receivedTime, localMetrics)
message.hasBody() -> messageId = handleTextMessage(context, envelope, metadata, message, senderRecipient, threadRecipient, groupId, receivedTime, localMetrics)
message.hasGroupCallUpdate() -> handleGroupCallUpdateMessage(envelope, message, senderRecipient.id, groupId)
message.body != null -> messageId = handleTextMessage(context, envelope, metadata, message, senderRecipient, threadRecipient, groupId, receivedTime, localMetrics)
message.groupCallUpdate != null -> handleGroupCallUpdateMessage(envelope, message, senderRecipient.id, groupId)
}
if (groupId != null) {
@@ -162,12 +165,12 @@ object DataMessageProcessor {
else -> SignalDatabase.groups.isUnknownGroup(groupId)
}
if (unknownGroup) {
handleUnknownGroupMessage(envelope.timestamp, message.groupV2)
handleUnknownGroupMessage(envelope.timestamp!!, message.groupV2!!)
}
}
if (message.hasProfileKey()) {
handleProfileKey(envelope.timestamp, message.profileKey.toByteArray(), senderRecipient)
if (message.profileKey.isNotEmpty()) {
handleProfileKey(envelope.timestamp!!, message.profileKey!!.toByteArray(), senderRecipient)
}
if (groupId == null && senderRecipient.hiddenState == HiddenState.HIDDEN) {
@@ -175,7 +178,7 @@ object DataMessageProcessor {
}
if (metadata.sealedSender && messageId != null) {
SignalExecutors.BOUNDED.execute { ApplicationDependencies.getJobManager().add(SendDeliveryReceiptJob(senderRecipient.id, message.timestamp, messageId)) }
SignalExecutors.BOUNDED.execute { ApplicationDependencies.getJobManager().add(SendDeliveryReceiptJob(senderRecipient.id, message.timestamp!!, messageId)) }
} else if (!metadata.sealedSender) {
if (RecipientUtil.shouldHaveProfileKey(threadRecipient)) {
Log.w(MessageContentProcessor.TAG, "Received an unsealed sender message from " + senderRecipient.id + ", but they should already have our profile key. Correcting.")
@@ -260,13 +263,13 @@ object DataMessageProcessor {
envelope: Envelope,
metadata: EnvelopeMetadata
): MessageId? {
log(envelope.timestamp, "End session message.")
log(envelope.timestamp!!, "End session message.")
val incomingTextMessage = IncomingTextMessage(
senderRecipientId,
metadata.sourceDeviceId,
envelope.timestamp,
envelope.serverTimestamp,
envelope.timestamp!!,
envelope.serverTimestamp!!,
System.currentTimeMillis(),
"",
Optional.empty(),
@@ -302,23 +305,23 @@ object DataMessageProcessor {
receivedTime: Long,
sideEffect: Boolean
): MessageId? {
log(envelope.timestamp, "Expiration update. Side effect: $sideEffect")
log(envelope.timestamp!!, "Expiration update. Side effect: $sideEffect")
if (groupId != null) {
warn(envelope.timestamp, "Expiration update received for GV2. Ignoring.")
warn(envelope.timestamp!!, "Expiration update received for GV2. Ignoring.")
return null
}
if (SignalDatabase.recipients.getExpiresInSeconds(threadRecipientId) == expiresIn.inWholeSeconds) {
log(envelope.timestamp, "No change in message expiry for group. Ignoring.")
log(envelope.timestamp!!, "No change in message expiry for group. Ignoring.")
return null
}
try {
val mediaMessage = IncomingMediaMessage(
from = senderRecipientId,
sentTimeMillis = envelope.timestamp - if (sideEffect) 1 else 0,
serverTimeMillis = envelope.serverTimestamp,
sentTimeMillis = envelope.timestamp!! - if (sideEffect) 1 else 0,
serverTimeMillis = envelope.serverTimestamp!!,
receivedTimeMillis = receivedTime,
expiresIn = expiresIn.inWholeMilliseconds,
isExpirationUpdate = true,
@@ -354,7 +357,7 @@ object DataMessageProcessor {
receivedTime: Long
) {
if (threadRecipient.expiresInSeconds.toLong() != expiresIn.inWholeSeconds) {
warn(envelope.timestamp, "Message expire time didn't match thread expire time. Handling timer update.")
warn(envelope.timestamp!!, "Message expire time didn't match thread expire time. Handling timer update.")
handleExpirationUpdate(envelope, metadata, senderRecipientId, threadRecipient.id, groupId, expiresIn, receivedTime, true)
}
}
@@ -368,16 +371,18 @@ object DataMessageProcessor {
senderRecipientId: RecipientId,
groupId: GroupId.V2?
): MessageId? {
log(envelope.timestamp, "Story reaction.")
log(envelope.timestamp!!, "Story reaction.")
val storyContext = message.storyContext!!
val emoji = message.reaction!!.emoji
val emoji = message.reaction.emoji
if (!EmojiUtil.isEmoji(emoji)) {
warn(envelope.timestamp, "Story reaction text is not a valid emoji! Ignoring the message.")
warn(envelope.timestamp!!, "Story reaction text is not a valid emoji! Ignoring the message.")
return null
}
val authorServiceId: ServiceId = ServiceId.parseOrThrow(message.storyContext.authorAci)
val sentTimestamp = message.storyContext.sentTimestamp
val authorServiceId: ServiceId = ServiceId.parseOrThrow(storyContext.authorAci!!)
val sentTimestamp = storyContext.sentTimestamp!!
SignalDatabase.messages.beginTransaction()
return try {
@@ -403,20 +408,20 @@ object DataMessageProcessor {
parentStoryId = DirectReply(storyId)
quoteModel = QuoteModel(sentTimestamp, authorRecipientId, displayText, false, story.slideDeck.asAttachments(), emptyList(), QuoteModel.Type.NORMAL, bodyRanges)
expiresIn = message.expireTimer.seconds
expiresIn = message.expireTimerDuration
} else {
warn(envelope.timestamp, "Story has reactions disabled. Dropping reaction.")
warn(envelope.timestamp!!, "Story has reactions disabled. Dropping reaction.")
return null
}
} catch (e: NoSuchMessageException) {
warn(envelope.timestamp, "Couldn't find story for reaction.", e)
warn(envelope.timestamp!!, "Couldn't find story for reaction.", e)
return null
}
val mediaMessage = IncomingMediaMessage(
from = senderRecipientId,
sentTimeMillis = envelope.timestamp,
serverTimeMillis = envelope.serverTimestamp,
sentTimeMillis = envelope.timestamp!!,
serverTimeMillis = envelope.serverTimestamp!!,
receivedTimeMillis = System.currentTimeMillis(),
parentStoryId = parentStoryId,
isStoryReaction = true,
@@ -445,7 +450,7 @@ object DataMessageProcessor {
null
}
} else {
warn(envelope.timestamp, "Failed to insert story reaction")
warn(envelope.timestamp!!, "Failed to insert story reaction")
null
}
} catch (e: MmsException) {
@@ -463,27 +468,29 @@ object DataMessageProcessor {
senderRecipientId: RecipientId,
earlyMessageCacheEntry: EarlyMessageCacheEntry?
): MessageId? {
log(envelope.timestamp, "Handle reaction for message " + message.reaction.targetSentTimestamp)
val reaction: DataMessage.Reaction = message.reaction!!
val emoji: String = message.reaction.emoji
val isRemove: Boolean = message.reaction.remove
val targetAuthorServiceId: ServiceId = ServiceId.parseOrThrow(message.reaction.targetAuthorAci)
val targetSentTimestamp = message.reaction.targetSentTimestamp
log(envelope.timestamp!!, "Handle reaction for message " + reaction.targetSentTimestamp!!)
val emoji: String? = reaction.emoji
val isRemove: Boolean = reaction.remove ?: false
val targetAuthorServiceId: ServiceId = ServiceId.parseOrThrow(reaction.targetAuthorAci!!)
val targetSentTimestamp: Long = reaction.targetSentTimestamp!!
if (targetAuthorServiceId.isUnknown) {
warn(envelope.timestamp, "Reaction was to an unknown UUID! Ignoring the message.")
warn(envelope.timestamp!!, "Reaction was to an unknown UUID! Ignoring the message.")
return null
}
if (!EmojiUtil.isEmoji(emoji)) {
warn(envelope.timestamp, "Reaction text is not a valid emoji! Ignoring the message.")
warn(envelope.timestamp!!, "Reaction text is not a valid emoji! Ignoring the message.")
return null
}
val targetAuthor = Recipient.externalPush(targetAuthorServiceId)
val targetMessage = SignalDatabase.messages.getMessageFor(targetSentTimestamp, targetAuthor.id)
if (targetMessage == null) {
warn(envelope.timestamp, "[handleReaction] Could not find matching message! Putting it in the early message cache. timestamp: " + targetSentTimestamp + " author: " + targetAuthor.id)
warn(envelope.timestamp!!, "[handleReaction] Could not find matching message! Putting it in the early message cache. timestamp: " + targetSentTimestamp + " author: " + targetAuthor.id)
if (earlyMessageCacheEntry != null) {
ApplicationDependencies.getEarlyMessageCache().store(targetAuthor.id, targetSentTimestamp, earlyMessageCacheEntry)
PushProcessEarlyMessagesJob.enqueue()
@@ -492,25 +499,25 @@ object DataMessageProcessor {
}
if (targetMessage.isRemoteDelete) {
warn(envelope.timestamp, "[handleReaction] Found a matching message, but it's flagged as remotely deleted. timestamp: " + targetSentTimestamp + " author: " + targetAuthor.id)
warn(envelope.timestamp!!, "[handleReaction] Found a matching message, but it's flagged as remotely deleted. timestamp: " + targetSentTimestamp + " author: " + targetAuthor.id)
return null
}
val targetThread = SignalDatabase.threads.getThreadRecord(targetMessage.threadId)
if (targetThread == null) {
warn(envelope.timestamp, "[handleReaction] Could not find a thread for the message! timestamp: " + targetSentTimestamp + " author: " + targetAuthor.id)
warn(envelope.timestamp!!, "[handleReaction] Could not find a thread for the message! timestamp: " + targetSentTimestamp + " author: " + targetAuthor.id)
return null
}
val targetThreadRecipientId = targetThread.recipient.id
val groupRecord = SignalDatabase.groups.getGroup(targetThreadRecipientId).orNull()
if (groupRecord != null && !groupRecord.members.contains(senderRecipientId)) {
warn(envelope.timestamp, "[handleReaction] Reaction author is not in the group! timestamp: " + targetSentTimestamp + " author: " + targetAuthor.id)
warn(envelope.timestamp!!, "[handleReaction] Reaction author is not in the group! timestamp: " + targetSentTimestamp + " author: " + targetAuthor.id)
return null
}
if (groupRecord == null && senderRecipientId != targetThreadRecipientId && Recipient.self().id != senderRecipientId) {
warn(envelope.timestamp, "[handleReaction] Reaction author is not a part of the 1:1 thread! timestamp: " + targetSentTimestamp + " author: " + targetAuthor.id)
warn(envelope.timestamp!!, "[handleReaction] Reaction author is not a part of the 1:1 thread! timestamp: " + targetSentTimestamp + " author: " + targetAuthor.id)
return null
}
@@ -520,7 +527,7 @@ object DataMessageProcessor {
SignalDatabase.reactions.deleteReaction(targetMessageId, senderRecipientId)
ApplicationDependencies.getMessageNotifier().updateNotification(context)
} else {
val reactionRecord = ReactionRecord(emoji, senderRecipientId, message.timestamp, System.currentTimeMillis())
val reactionRecord = ReactionRecord(emoji!!, senderRecipientId, message.timestamp!!, System.currentTimeMillis())
SignalDatabase.reactions.addReaction(targetMessageId, reactionRecord)
ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.fromMessageRecord(targetMessage), false)
}
@@ -529,12 +536,14 @@ object DataMessageProcessor {
}
fun handleRemoteDelete(context: Context, envelope: Envelope, message: DataMessage, senderRecipientId: RecipientId, earlyMessageCacheEntry: EarlyMessageCacheEntry?): MessageId? {
log(envelope.timestamp, "Remote delete for message ${message.delete.targetSentTimestamp}")
val delete = message.delete!!
val targetSentTimestamp: Long = message.delete.targetSentTimestamp
log(envelope.timestamp!!, "Remote delete for message ${delete.targetSentTimestamp}")
val targetSentTimestamp: Long = delete.targetSentTimestamp!!
val targetMessage: MessageRecord? = SignalDatabase.messages.getMessageFor(targetSentTimestamp, senderRecipientId)
return if (targetMessage != null && MessageConstraintsUtil.isValidRemoteDeleteReceive(targetMessage, senderRecipientId, envelope.serverTimestamp)) {
return if (targetMessage != null && MessageConstraintsUtil.isValidRemoteDeleteReceive(targetMessage, senderRecipientId, envelope.serverTimestamp!!)) {
SignalDatabase.messages.markAsRemoteDelete(targetMessage)
if (targetMessage.isStory()) {
SignalDatabase.messages.deleteRemotelyDeletedStory(targetMessage.id)
@@ -544,7 +553,7 @@ object DataMessageProcessor {
MessageId(targetMessage.id)
} else if (targetMessage == null) {
warn(envelope.timestamp, "[handleRemoteDelete] Could not find matching message! timestamp: $targetSentTimestamp author: $senderRecipientId")
warn(envelope.timestamp!!, "[handleRemoteDelete] Could not find matching message! timestamp: $targetSentTimestamp author: $senderRecipientId")
if (earlyMessageCacheEntry != null) {
ApplicationDependencies.getEarlyMessageCache().store(senderRecipientId, targetSentTimestamp, earlyMessageCacheEntry)
PushProcessEarlyMessagesJob.enqueue()
@@ -552,7 +561,7 @@ object DataMessageProcessor {
null
} else {
warn(envelope.timestamp, "[handleRemoteDelete] Invalid remote delete! deleteTime: ${envelope.serverTimestamp}, targetTime: ${targetMessage.serverTimestamp}, deleteAuthor: $senderRecipientId, targetAuthor: ${targetMessage.fromRecipient.id}")
warn(envelope.timestamp!!, "[handleRemoteDelete] Invalid remote delete! deleteTime: ${envelope.serverTimestamp!!}, targetTime: ${targetMessage.serverTimestamp}, deleteAuthor: $senderRecipientId, targetAuthor: ${targetMessage.fromRecipient.id}")
null
}
}
@@ -572,14 +581,14 @@ object DataMessageProcessor {
isActivatePaymentsRequest: Boolean,
isPaymentsActivated: Boolean
): MessageId? {
log(envelope.timestamp, "Payment activation request: $isActivatePaymentsRequest activated: $isPaymentsActivated")
log(envelope.timestamp!!, "Payment activation request: $isActivatePaymentsRequest activated: $isPaymentsActivated")
try {
val mediaMessage = IncomingMediaMessage(
from = senderRecipientId,
sentTimeMillis = envelope.timestamp,
serverTimeMillis = envelope.serverTimestamp,
sentTimeMillis = envelope.timestamp!!,
serverTimeMillis = envelope.serverTimestamp!!,
receivedTimeMillis = receivedTime,
expiresIn = message.expireTimer.seconds.inWholeMilliseconds,
expiresIn = message.expireTimerDuration.inWholeMilliseconds,
isUnidentified = metadata.sealedSender,
serverGuid = envelope.serverGuid,
isActivatePaymentsRequest = isActivatePaymentsRequest,
@@ -606,14 +615,14 @@ object DataMessageProcessor {
senderRecipientId: RecipientId,
receivedTime: Long
): MessageId? {
log(envelope.timestamp, "Payment message.")
log(envelope.timestamp!!, "Payment message.")
if (!message.payment.notification.mobileCoin.hasReceipt()) {
warn(envelope.timestamp, "Ignoring payment message without notification")
if (message.payment?.notification?.mobileCoin?.receipt == null) {
warn(envelope.timestamp!!, "Ignoring payment message without notification")
return null
}
val paymentNotification = message.payment.notification
val paymentNotification = message.payment!!.notification!!
val uuid = UUID.randomUUID()
val queue = "Payment_" + PushProcessMessageJob.getQueueName(senderRecipientId)
@@ -621,21 +630,21 @@ object DataMessageProcessor {
SignalDatabase.payments.createIncomingPayment(
uuid,
senderRecipientId,
message.timestamp,
paymentNotification.note,
message.timestamp!!,
paymentNotification.note ?: "",
Money.MobileCoin.ZERO,
Money.MobileCoin.ZERO,
paymentNotification.mobileCoin.receipt.toByteArray(),
paymentNotification.mobileCoin!!.receipt!!.toByteArray(),
true
)
val mediaMessage = IncomingMediaMessage(
from = senderRecipientId,
body = uuid.toString(),
sentTimeMillis = envelope.timestamp,
serverTimeMillis = envelope.serverTimestamp,
sentTimeMillis = envelope.timestamp!!,
serverTimeMillis = envelope.serverTimestamp!!,
receivedTimeMillis = receivedTime,
expiresIn = message.expireTimer.seconds.inWholeMilliseconds,
expiresIn = message.expireTimerDuration.inWholeMilliseconds,
isUnidentified = metadata.sealedSender,
serverGuid = envelope.serverGuid,
isPushMessage = true,
@@ -649,9 +658,9 @@ object DataMessageProcessor {
return messageId
}
} catch (e: PublicKeyConflictException) {
warn(envelope.timestamp, "Ignoring payment with public key already in database")
warn(envelope.timestamp!!, "Ignoring payment with public key already in database")
} catch (e: SerializationException) {
warn(envelope.timestamp, "Ignoring payment with bad data.", e)
warn(envelope.timestamp!!, "Ignoring payment with bad data.", e)
} catch (e: MmsException) {
throw StorageFailedException(e, metadata.sourceServiceId.toString(), metadata.sourceDeviceId)
} finally {
@@ -676,10 +685,16 @@ object DataMessageProcessor {
groupId: GroupId.V2?,
receivedTime: Long
): MessageId? {
log(envelope.timestamp, "Story reply.")
log(envelope.timestamp!!, "Story reply.")
val authorServiceId: ServiceId = ServiceId.parseOrThrow(message.storyContext.authorAci)
val sentTimestamp = message.storyContext.sentTimestamp
val storyContext: DataMessage.StoryContext = message.storyContext!!
val authorServiceId: ServiceId = ServiceId.parseOrThrow(storyContext.authorAci!!)
val sentTimestamp: Long = if (storyContext.sentTimestamp != null) {
storyContext.sentTimestamp!!
} else {
warn(envelope.timestamp!!, "Invalid story reply, missing sentTimestamp")
return null
}
SignalDatabase.messages.beginTransaction()
return try {
@@ -708,7 +723,7 @@ object DataMessageProcessor {
threadRecipient = senderRecipient
}
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient.id, threadRecipient, groupId, message.expireTimer.seconds, receivedTime)
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient.id, threadRecipient, groupId, message.expireTimerDuration, receivedTime)
if (message.hasGroupContext) {
parentStoryId = GroupReply(storyMessageId.id)
@@ -723,22 +738,22 @@ object DataMessageProcessor {
}
quoteModel = QuoteModel(sentTimestamp, storyAuthorRecipientId, displayText, false, story.slideDeck.asAttachments(), emptyList(), QuoteModel.Type.NORMAL, bodyRanges)
expiresInMillis = message.expireTimer.seconds
expiresInMillis = message.expireTimerDuration
} else {
warn(envelope.timestamp, "Story has replies disabled. Dropping reply.")
warn(envelope.timestamp!!, "Story has replies disabled. Dropping reply.")
return null
}
} catch (e: NoSuchMessageException) {
warn(envelope.timestamp, "Couldn't find story for reply.", e)
warn(envelope.timestamp!!, "Couldn't find story for reply.", e)
return null
}
val bodyRanges: BodyRangeList? = message.bodyRangesList.filter { it.hasStyle() }.toList().toBodyRangeList()
val bodyRanges: BodyRangeList? = message.bodyRanges.filter { it.mentionAci == null }.toList().toBodyRangeList()
val mediaMessage = IncomingMediaMessage(
from = senderRecipient.id,
sentTimeMillis = envelope.timestamp,
serverTimeMillis = envelope.serverTimestamp,
sentTimeMillis = envelope.timestamp!!,
serverTimeMillis = envelope.serverTimestamp!!,
receivedTimeMillis = System.currentTimeMillis(),
parentStoryId = parentStoryId,
expiresIn = expiresInMillis.inWholeMilliseconds,
@@ -746,7 +761,7 @@ object DataMessageProcessor {
body = message.body,
groupId = groupId,
quote = quoteModel,
mentions = getMentions(message.bodyRangesList),
mentions = getMentions(message.bodyRanges),
serverGuid = envelope.serverGuid,
messageRanges = bodyRanges
)
@@ -769,7 +784,7 @@ object DataMessageProcessor {
null
}
} else {
warn(envelope.timestamp, "Failed to insert story reply.")
warn(envelope.timestamp!!, "Failed to insert story reply.")
null
}
} catch (e: MmsException) {
@@ -789,29 +804,30 @@ object DataMessageProcessor {
threadRecipientId: RecipientId,
receivedTime: Long
): MessageId? {
log(message.timestamp, "Gift message.")
log(message.timestamp!!, "Gift message.")
check(message.giftBadge.hasReceiptCredentialPresentation())
val giftBadge: DataMessage.GiftBadge = message.giftBadge!!
check(giftBadge.receiptCredentialPresentation != null)
notifyTypingStoppedFromIncomingMessage(context, senderRecipient, threadRecipientId, metadata.sourceDeviceId)
val token = ReceiptCredentialPresentation(message.giftBadge.receiptCredentialPresentation.toByteArray()).serialize()
val giftBadge = GiftBadge.newBuilder()
.setRedemptionToken(ByteString.copyFrom(token))
.setRedemptionState(GiftBadge.RedemptionState.PENDING)
val token = ReceiptCredentialPresentation(giftBadge.receiptCredentialPresentation!!.toByteArray()).serialize()
val dbGiftBadge = GiftBadge.Builder()
.redemptionToken(token.toByteString())
.redemptionState(GiftBadge.RedemptionState.PENDING)
.build()
val insertResult: InsertResult? = try {
val mediaMessage = IncomingMediaMessage(
from = senderRecipient.id,
sentTimeMillis = envelope.timestamp,
serverTimeMillis = envelope.serverTimestamp,
sentTimeMillis = envelope.timestamp!!,
serverTimeMillis = envelope.serverTimestamp!!,
receivedTimeMillis = receivedTime,
expiresIn = message.expireTimer.seconds.inWholeMilliseconds,
expiresIn = message.expireTimerDuration.inWholeMilliseconds,
isUnidentified = metadata.sealedSender,
body = Base64.encodeBytes(giftBadge.toByteArray()),
body = Base64.encodeBytes(dbGiftBadge.encode()),
serverGuid = envelope.serverGuid,
giftBadge = giftBadge
giftBadge = dbGiftBadge
)
SignalDatabase.messages.insertSecureDecryptedMessageInbox(mediaMessage, -1).orNull()
@@ -840,7 +856,7 @@ object DataMessageProcessor {
receivedTime: Long,
localMetrics: SignalLocalMetrics.MessageReceive?
): MessageId? {
log(envelope.timestamp, "Media message.")
log(envelope.timestamp!!, "Media message.")
notifyTypingStoppedFromIncomingMessage(context, senderRecipient, threadRecipient.id, metadata.sourceDeviceId)
@@ -848,25 +864,25 @@ object DataMessageProcessor {
SignalDatabase.messages.beginTransaction()
try {
val quote: QuoteModel? = getValidatedQuote(context, envelope.timestamp, message)
val quote: QuoteModel? = getValidatedQuote(context, envelope.timestamp!!, message)
val contacts: List<Contact> = getContacts(message)
val linkPreviews: List<LinkPreview> = getLinkPreviews(message.previewList, message.body ?: "", false)
val mentions: List<Mention> = getMentions(message.bodyRangesList.take(BODY_RANGE_PROCESSING_LIMIT))
val sticker: Attachment? = getStickerAttachment(envelope.timestamp, message)
val attachments: List<Attachment> = message.attachmentsList.toPointersWithinLimit()
val messageRanges: BodyRangeList? = if (message.bodyRangesCount > 0) message.bodyRangesList.asSequence().take(BODY_RANGE_PROCESSING_LIMIT).filter { it.hasStyle() }.toList().toBodyRangeList() else null
val linkPreviews: List<LinkPreview> = getLinkPreviews(message.preview, message.body ?: "", false)
val mentions: List<Mention> = getMentions(message.bodyRanges.take(BODY_RANGE_PROCESSING_LIMIT))
val sticker: Attachment? = getStickerAttachment(envelope.timestamp!!, message)
val attachments: List<Attachment> = message.attachments.toPointersWithinLimit()
val messageRanges: BodyRangeList? = if (message.bodyRanges.isNotEmpty()) message.bodyRanges.asSequence().take(BODY_RANGE_PROCESSING_LIMIT).filter { it.mentionAci == null }.toList().toBodyRangeList() else null
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient.id, threadRecipient, groupId, message.expireTimer.seconds, receivedTime)
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient.id, threadRecipient, groupId, message.expireTimerDuration, receivedTime)
val mediaMessage = IncomingMediaMessage(
from = senderRecipient.id,
sentTimeMillis = envelope.timestamp,
serverTimeMillis = envelope.serverTimestamp,
sentTimeMillis = envelope.timestamp!!,
serverTimeMillis = envelope.serverTimestamp!!,
receivedTimeMillis = receivedTime,
expiresIn = message.expireTimer.seconds.inWholeMilliseconds,
isViewOnce = message.isViewOnce,
expiresIn = message.expireTimerDuration.inWholeMilliseconds,
isViewOnce = message.isViewOnce == true,
isUnidentified = metadata.sealedSender,
body = message.body.ifEmpty { null },
body = message.body?.ifEmpty { null },
groupId = groupId,
attachments = attachments + if (sticker != null) listOf(sticker) else emptyList(),
quote = quote,
@@ -909,7 +925,7 @@ object DataMessageProcessor {
ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.threadId))
TrimThreadJob.enqueueAsync(insertResult.threadId)
if (message.isViewOnce) {
if (message.isViewOnce == true) {
ApplicationDependencies.getViewOnceMessageManager().scheduleIfNecessary()
}
}
@@ -932,23 +948,23 @@ object DataMessageProcessor {
receivedTime: Long,
localMetrics: SignalLocalMetrics.MessageReceive?
): MessageId? {
log(envelope.timestamp, "Text message.")
log(envelope.timestamp!!, "Text message.")
val body = if (message.hasBody()) message.body else ""
val body = message.body ?: ""
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient.id, threadRecipient, groupId, message.expireTimer.seconds, receivedTime)
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient.id, threadRecipient, groupId, message.expireTimerDuration, receivedTime)
notifyTypingStoppedFromIncomingMessage(context, senderRecipient, threadRecipient.id, metadata.sourceDeviceId)
val textMessage = IncomingTextMessage(
senderRecipient.id,
metadata.sourceDeviceId,
envelope.timestamp,
envelope.serverTimestamp,
envelope.timestamp!!,
envelope.serverTimestamp!!,
receivedTime,
body,
Optional.ofNullable(groupId),
message.expireTimer.seconds.inWholeMilliseconds,
message.expireTimerDuration.inWholeMilliseconds,
metadata.sealedSender,
envelope.serverGuid
)
@@ -970,10 +986,12 @@ object DataMessageProcessor {
senderRecipientId: RecipientId,
groupId: GroupId.V2?
) {
log(envelope.timestamp, "Group call update message.")
log(envelope.timestamp!!, "Group call update message.")
if (groupId == null || !message.hasGroupCallUpdate()) {
warn(envelope.timestamp, "Invalid group for group call update message")
val groupCallUpdate: DataMessage.GroupCallUpdate = message.groupCallUpdate!!
if (groupId == null) {
warn(envelope.timestamp!!, "Invalid group for group call update message")
return
}
@@ -982,8 +1000,8 @@ object DataMessageProcessor {
SignalDatabase.calls.insertOrUpdateGroupCallFromExternalEvent(
groupRecipientId,
senderRecipientId,
envelope.serverTimestamp,
if (message.groupCallUpdate.hasEraId()) message.groupCallUpdate.eraId else null
envelope.serverTimestamp!!,
groupCallUpdate.eraId
)
GroupCallPeekJob.enqueue(groupRecipientId)
@@ -1000,13 +1018,13 @@ object DataMessageProcessor {
fun getMentions(mentionBodyRanges: List<BodyRange>): List<Mention> {
return mentionBodyRanges
.filter { it.hasMentionAci() }
.filter { it.mentionAci != null && it.start != null && it.length != null }
.mapNotNull {
val aci = ACI.parseOrNull(it.mentionAci)
if (aci != null && !aci.isUnknown) {
val id = Recipient.externalPush(aci).id
Mention(id, it.start, it.length)
Mention(id, it.start!!, it.length!!)
} else {
null
}
@@ -1030,19 +1048,15 @@ object DataMessageProcessor {
}
fun getValidatedQuote(context: Context, timestamp: Long, message: DataMessage): QuoteModel? {
if (!message.hasQuote()) {
return null
}
val quote: DataMessage.Quote = message.quote ?: return null
val quote: DataMessage.Quote = message.quote
if (quote.id <= 0) {
if (quote.id == null) {
warn(timestamp, "Received quote without an ID! Ignoring...")
return null
}
val authorId = Recipient.externalPush(ServiceId.parseOrThrow(quote.authorAci)).id
var quotedMessage = SignalDatabase.messages.getMessageFor(quote.id, authorId) as? MediaMmsMessageRecord
val authorId = Recipient.externalPush(ServiceId.parseOrThrow(quote.authorAci!!)).id
var quotedMessage = SignalDatabase.messages.getMessageFor(quote.id!!, authorId) as? MediaMmsMessageRecord
if (quotedMessage != null && !quotedMessage.isRemoteDelete) {
log(timestamp, "Found matching message record...")
@@ -1074,7 +1088,7 @@ object DataMessageProcessor {
val body = if (quotedMessage.isPaymentNotification) quotedMessage.getDisplayBody(context).toString() else quotedMessage.body
return QuoteModel(
quote.id,
quote.id!!,
authorId,
body,
false,
@@ -1089,19 +1103,19 @@ object DataMessageProcessor {
warn(timestamp, "Didn't find matching message record...")
return QuoteModel(
quote.id,
quote.id!!,
authorId,
quote.text,
quote.text ?: "",
true,
quote.attachmentsList.mapNotNull { PointerAttachment.forPointer(it).orNull() },
getMentions(quote.bodyRangesList),
quote.attachments.mapNotNull { PointerAttachment.forPointer(it).orNull() },
getMentions(quote.bodyRanges),
QuoteModel.Type.fromProto(quote.type),
quote.bodyRangesList.filterNot { it.hasMentionAci() }.toBodyRangeList()
quote.bodyRanges.filter { it.mentionAci == null }.toBodyRangeList()
)
}
fun getContacts(message: DataMessage): List<Contact> {
return message.contactList.map { ContactModelMapper.remoteToLocal(it) }
return message.contact.map { ContactModelMapper.remoteToLocal(it) }
}
fun getLinkPreviews(previews: List<Preview>, body: String, isStoryEmbed: Boolean): List<LinkPreview> {
@@ -1113,7 +1127,7 @@ object DataMessageProcessor {
return previews
.mapNotNull { preview ->
val thumbnail: Attachment? = preview.image.toPointer()
val thumbnail: Attachment? = preview.image?.toPointer()
val url: Optional<String> = preview.url.toOptional()
val title: Optional<String> = preview.title.toOptional()
val description: Optional<String> = preview.description.toOptional()
@@ -1122,34 +1136,31 @@ object DataMessageProcessor {
val validDomain = url.isPresent && LinkUtil.isValidPreviewUrl(url.get())
val isForCallLink = url.isPresent && CallLinks.isCallLink(url.get())
if ((hasTitle || isForCallLink) && (presentInBody || isStoryEmbed) && validDomain) {
val linkPreview = LinkPreview(url.get(), title.orElse(""), description.orElse(""), preview.date, thumbnail.toOptional())
if ((hasTitle || isForCallLink) && (presentInBody || isStoryEmbed) && validDomain && preview.date != null) {
val linkPreview = LinkPreview(url.get(), title.orElse(""), description.orElse(""), preview.date!!, thumbnail.toOptional())
linkPreview
} else {
warn(String.format("Discarding an invalid link preview. hasTitle: %b presentInBody: %b isStoryEmbed: %b validDomain: %b", hasTitle, presentInBody, isStoryEmbed, validDomain))
warn(String.format("Discarding an invalid link preview. hasTitle: %b presentInBody: %b isStoryEmbed: %b validDomain: %b date: %b", hasTitle, presentInBody, isStoryEmbed, validDomain, preview.date != null))
null
}
}
}
fun getStickerAttachment(timestamp: Long, message: DataMessage): Attachment? {
if (!message.hasSticker()) {
return null
}
val sticker = message.sticker ?: return null
val sticker = message.sticker
if (!(message.sticker.hasPackId() && message.sticker.hasPackKey() && message.sticker.hasStickerId() && message.sticker.hasData())) {
if (sticker.packId == null || sticker.packKey == null || sticker.stickerId == null || sticker.data_ == null) {
warn(timestamp, "Malformed sticker!")
return null
}
val packId = Hex.toStringCondensed(sticker.packId.toByteArray())
val packKey = Hex.toStringCondensed(sticker.packKey.toByteArray())
val stickerId = sticker.stickerId
val emoji = sticker.emoji
val stickerLocator = StickerLocator(packId, packKey, stickerId, emoji)
val packId: String = Hex.toStringCondensed(sticker.packId!!.toByteArray())
val packKey: String = Hex.toStringCondensed(sticker.packKey!!.toByteArray())
val stickerId: Int = sticker.stickerId!!
val emoji: String? = sticker.emoji
val stickerRecord = SignalDatabase.stickers.getSticker(stickerLocator.packId, stickerLocator.stickerId, false)
val stickerLocator = StickerLocator(packId, packKey, stickerId, emoji)
val stickerRecord: StickerRecord? = SignalDatabase.stickers.getSticker(stickerLocator.packId, stickerLocator.stickerId, false)
return if (stickerRecord != null) {
UriAttachment(
@@ -1172,7 +1183,7 @@ object DataMessageProcessor {
null
)
} else {
sticker.data.toPointer(stickerLocator)
sticker.data_!!.toPointer(stickerLocator)
}
}
}

View File

@@ -32,9 +32,9 @@ import org.thoughtcrime.securesms.util.MessageConstraintsUtil
import org.thoughtcrime.securesms.util.hasAudio
import org.thoughtcrime.securesms.util.hasSharedContact
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.Envelope
import java.util.Optional
object EditMessageProcessor {
@@ -43,45 +43,45 @@ object EditMessageProcessor {
senderRecipient: Recipient,
threadRecipient: Recipient,
envelope: Envelope,
content: SignalServiceProtos.Content,
content: Content,
metadata: EnvelopeMetadata,
earlyMessageCacheEntry: EarlyMessageCacheEntry?
) {
val editMessage = content.editMessage
val editMessage = content.editMessage!!
log(envelope.timestamp, "[handleEditMessage] Edit message for " + editMessage.targetSentTimestamp)
log(envelope.timestamp!!, "[handleEditMessage] Edit message for " + editMessage.targetSentTimestamp)
var targetMessage: MediaMmsMessageRecord? = SignalDatabase.messages.getMessageFor(editMessage.targetSentTimestamp, senderRecipient.id) as? MediaMmsMessageRecord
var targetMessage: MediaMmsMessageRecord? = SignalDatabase.messages.getMessageFor(editMessage.targetSentTimestamp!!, senderRecipient.id) as? MediaMmsMessageRecord
val targetThreadRecipient: Recipient? = if (targetMessage != null) SignalDatabase.threads.getRecipientForThreadId(targetMessage.threadId) else null
if (targetMessage == null || targetThreadRecipient == null) {
warn(envelope.timestamp, "[handleEditMessage] Could not find matching message! timestamp: ${editMessage.targetSentTimestamp} author: ${senderRecipient.id}")
warn(envelope.timestamp!!, "[handleEditMessage] Could not find matching message! timestamp: ${editMessage.targetSentTimestamp} author: ${senderRecipient.id}")
if (earlyMessageCacheEntry != null) {
ApplicationDependencies.getEarlyMessageCache().store(senderRecipient.id, editMessage.targetSentTimestamp, earlyMessageCacheEntry)
ApplicationDependencies.getEarlyMessageCache().store(senderRecipient.id, editMessage.targetSentTimestamp!!, earlyMessageCacheEntry)
PushProcessEarlyMessagesJob.enqueue()
}
return
}
val message = editMessage.dataMessage
val message = editMessage.dataMessage!!
val isMediaMessage = message.isMediaMessage
val groupId: GroupId.V2? = message.groupV2.groupId
val groupId: GroupId.V2? = message.groupV2?.groupId
val originalMessage = targetMessage.originalMessageId?.let { SignalDatabase.messages.getMessageRecord(it.id) } ?: targetMessage
val validTiming = MessageConstraintsUtil.isValidEditMessageReceive(originalMessage, senderRecipient, envelope.serverTimestamp)
val validTiming = MessageConstraintsUtil.isValidEditMessageReceive(originalMessage, senderRecipient, envelope.serverTimestamp!!)
val validAuthor = senderRecipient.id == originalMessage.fromRecipient.id
val validGroup = groupId == targetThreadRecipient.groupId.orNull()
val validTarget = !originalMessage.isViewOnce && !originalMessage.hasAudio() && !originalMessage.hasSharedContact()
if (!validTiming || !validAuthor || !validGroup || !validTarget) {
warn(envelope.timestamp, "[handleEditMessage] Invalid message edit! editTime: ${envelope.serverTimestamp}, targetTime: ${originalMessage.serverTimestamp}, editAuthor: ${senderRecipient.id}, targetAuthor: ${originalMessage.fromRecipient.id}, editThread: ${threadRecipient.id}, targetThread: ${targetThreadRecipient.id}, validity: (timing: $validTiming, author: $validAuthor, group: $validGroup, target: $validTarget)")
warn(envelope.timestamp!!, "[handleEditMessage] Invalid message edit! editTime: ${envelope.serverTimestamp}, targetTime: ${originalMessage.serverTimestamp}, editAuthor: ${senderRecipient.id}, targetAuthor: ${originalMessage.fromRecipient.id}, editThread: ${threadRecipient.id}, targetThread: ${targetThreadRecipient.id}, validity: (timing: $validTiming, author: $validAuthor, group: $validGroup, target: $validTarget)")
return
}
if (groupId != null && MessageContentProcessor.handleGv2PreProcessing(context, envelope.timestamp, content, metadata, groupId, message.groupV2, senderRecipient) == MessageContentProcessor.Gv2PreProcessResult.IGNORE) {
warn(envelope.timestamp, "[handleEditMessage] Group processor indicated we should ignore this.")
if (groupId != null && MessageContentProcessor.handleGv2PreProcessing(context, envelope.timestamp!!, content, metadata, groupId, message.groupV2!!, senderRecipient) == MessageContentProcessor.Gv2PreProcessResult.IGNORE) {
warn(envelope.timestamp!!, "[handleEditMessage] Group processor indicated we should ignore this.")
return
}
@@ -97,7 +97,7 @@ object EditMessageProcessor {
if (insertResult != null) {
SignalExecutors.BOUNDED.execute {
ApplicationDependencies.getJobManager().add(SendDeliveryReceiptJob(senderRecipient.id, message.timestamp, MessageId(insertResult.messageId)))
ApplicationDependencies.getJobManager().add(SendDeliveryReceiptJob(senderRecipient.id, message.timestamp!!, MessageId(insertResult.messageId)))
}
if (targetMessage.expireStarted > 0) {
@@ -122,9 +122,9 @@ object EditMessageProcessor {
message: DataMessage,
targetMessage: MediaMmsMessageRecord
): InsertResult? {
val messageRanges: BodyRangeList? = message.bodyRangesList.filter { it.hasStyle() }.toList().toBodyRangeList()
val messageRanges: BodyRangeList? = message.bodyRanges.filter { it.mentionAci == null }.toList().toBodyRangeList()
val targetQuote = targetMessage.quote
val quote: QuoteModel? = if (targetQuote != null && message.hasQuote()) {
val quote: QuoteModel? = if (targetQuote != null && message.quote != null) {
QuoteModel(
targetQuote.id,
targetQuote.author,
@@ -138,25 +138,25 @@ object EditMessageProcessor {
} else {
null
}
val attachments = message.attachmentsList.toPointersWithinLimit()
val attachments = message.attachments.toPointersWithinLimit()
attachments.filter {
MediaUtil.SlideType.LONG_TEXT == MediaUtil.getSlideTypeFromContentType(it.contentType)
}
val mediaMessage = IncomingMediaMessage(
from = senderRecipientId,
sentTimeMillis = message.timestamp,
serverTimeMillis = envelope.serverTimestamp,
sentTimeMillis = message.timestamp!!,
serverTimeMillis = envelope.serverTimestamp!!,
receivedTimeMillis = targetMessage.dateReceived,
expiresIn = targetMessage.expiresIn,
isViewOnce = message.isViewOnce,
isViewOnce = message.isViewOnce == true,
isUnidentified = metadata.sealedSender,
body = message.body,
groupId = groupId,
attachments = attachments,
quote = quote,
sharedContacts = emptyList(),
linkPreviews = DataMessageProcessor.getLinkPreviews(message.previewList, message.body ?: "", false),
mentions = DataMessageProcessor.getMentions(message.bodyRangesList),
linkPreviews = DataMessageProcessor.getLinkPreviews(message.preview, message.body ?: "", false),
mentions = DataMessageProcessor.getMentions(message.bodyRanges),
serverGuid = envelope.serverGuid,
messageRanges = messageRanges,
isPushMessage = true
@@ -185,8 +185,8 @@ object EditMessageProcessor {
var textMessage = IncomingTextMessage(
senderRecipientId,
metadata.sourceDeviceId,
envelope.timestamp,
envelope.timestamp,
envelope.timestamp!!,
envelope.timestamp!!,
targetMessage.dateReceived,
message.body,
Optional.ofNullable(groupId),

View File

@@ -34,7 +34,7 @@ import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.Envelope
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
@@ -264,17 +264,17 @@ class IncomingMessageObserver(private val context: Application) {
}
@VisibleForTesting
fun processEnvelope(bufferedProtocolStore: BufferedProtocolStore, envelope: SignalServiceProtos.Envelope, serverDeliveredTimestamp: Long): List<FollowUpOperation>? {
return when (envelope.type.number) {
SignalServiceProtos.Envelope.Type.RECEIPT_VALUE -> {
fun processEnvelope(bufferedProtocolStore: BufferedProtocolStore, envelope: Envelope, serverDeliveredTimestamp: Long): List<FollowUpOperation>? {
return when (envelope.type) {
Envelope.Type.RECEIPT -> {
processReceipt(envelope)
null
}
SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE,
SignalServiceProtos.Envelope.Type.CIPHERTEXT_VALUE,
SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE,
SignalServiceProtos.Envelope.Type.PLAINTEXT_CONTENT_VALUE -> {
Envelope.Type.PREKEY_BUNDLE,
Envelope.Type.CIPHERTEXT,
Envelope.Type.UNIDENTIFIED_SENDER,
Envelope.Type.PLAINTEXT_CONTENT -> {
processMessage(bufferedProtocolStore, envelope, serverDeliveredTimestamp)
}
@@ -285,12 +285,12 @@ class IncomingMessageObserver(private val context: Application) {
}
}
private fun processMessage(bufferedProtocolStore: BufferedProtocolStore, envelope: SignalServiceProtos.Envelope, serverDeliveredTimestamp: Long): List<FollowUpOperation> {
private fun processMessage(bufferedProtocolStore: BufferedProtocolStore, envelope: Envelope, serverDeliveredTimestamp: Long): List<FollowUpOperation> {
val localReceiveMetric = SignalLocalMetrics.MessageReceive.start()
val result = MessageDecryptor.decrypt(context, bufferedProtocolStore, envelope, serverDeliveredTimestamp)
localReceiveMetric.onEnvelopeDecrypted()
SignalLocalMetrics.MessageLatency.onMessageReceived(envelope.serverTimestamp, serverDeliveredTimestamp, envelope.urgent)
SignalLocalMetrics.MessageLatency.onMessageReceived(envelope.serverTimestamp!!, serverDeliveredTimestamp, envelope.urgent!!)
when (result) {
is MessageDecryptor.Result.Success -> {
val job = PushProcessMessageJob.processOrDefer(messageContentProcessor, result, localReceiveMetric)
@@ -303,7 +303,7 @@ class IncomingMessageObserver(private val context: Application) {
PushProcessMessageErrorJob(
result.toMessageState(),
result.errorMetadata.toExceptionMetadata(),
result.envelope.timestamp
result.envelope.timestamp!!
)
}
}
@@ -318,17 +318,17 @@ class IncomingMessageObserver(private val context: Application) {
return result.followUpOperations
}
private fun processReceipt(envelope: SignalServiceProtos.Envelope) {
private fun processReceipt(envelope: Envelope) {
if (!UuidUtil.isUuid(envelope.sourceServiceId)) {
Log.w(TAG, "Invalid envelope source UUID!")
return
}
val senderId = RecipientId.from(ServiceId.parseOrThrow(envelope.sourceServiceId))
val senderId = RecipientId.from(ServiceId.parseOrThrow(envelope.sourceServiceId!!))
Log.i(TAG, "Received server receipt. Sender: $senderId, Device: ${envelope.sourceDevice}, Timestamp: ${envelope.timestamp}")
SignalDatabase.messages.incrementDeliveryReceiptCount(envelope.timestamp, senderId, System.currentTimeMillis())
SignalDatabase.messageLog.deleteEntryForRecipient(envelope.timestamp, senderId, envelope.sourceDevice)
SignalDatabase.messages.incrementDeliveryReceiptCount(envelope.timestamp!!, senderId, System.currentTimeMillis())
SignalDatabase.messageLog.deleteEntryForRecipient(envelope.timestamp!!, senderId, envelope.sourceDevice!!)
}
private fun MessageDecryptor.Result.toMessageState(): MessageState {

View File

@@ -51,10 +51,11 @@ import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.TypingMessage
import org.whispersystems.signalservice.internal.push.CallMessage
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.GroupContextV2
import org.whispersystems.signalservice.internal.push.TypingMessage
import java.io.IOException
import java.util.Optional
@@ -123,18 +124,18 @@ open class MessageContentProcessor(private val context: Context) {
@Throws(BadGroupIdException::class)
private fun getMessageDestination(content: Content, sender: Recipient): Recipient {
return if (content.hasStoryMessage() && content.storyMessage.group.isValid) {
getGroupRecipient(content.storyMessage.group, sender)
return if (content.storyMessage != null && content.storyMessage!!.group.isValid) {
getGroupRecipient(content.storyMessage!!.group, sender)
} else if (content.dataMessage.hasGroupContext) {
getGroupRecipient(content.dataMessage.groupV2, sender)
} else if (content.editMessage.dataMessage.hasGroupContext) {
getGroupRecipient(content.editMessage.dataMessage.groupV2, sender)
getGroupRecipient(content.dataMessage!!.groupV2, sender)
} else if (content.editMessage?.dataMessage.hasGroupContext) {
getGroupRecipient(content.editMessage!!.dataMessage!!.groupV2, sender)
} else {
sender
}
}
private fun getGroupRecipient(groupContextV2: SignalServiceProtos.GroupContextV2?, senderRecipient: Recipient): Recipient {
private fun getGroupRecipient(groupContextV2: GroupContextV2?, senderRecipient: Recipient): Recipient {
return if (groupContextV2 != null) {
Recipient.externalPossiblyMigratedGroup(GroupId.v2(groupContextV2.groupMasterKey))
} else {
@@ -144,8 +145,8 @@ open class MessageContentProcessor(private val context: Context) {
@Throws(BadGroupIdException::class)
private fun shouldIgnore(content: Content, senderRecipient: Recipient, threadRecipient: Recipient): Boolean {
if (content.hasDataMessage()) {
val message = content.dataMessage
if (content.dataMessage != null) {
val message = content.dataMessage!!
return if (threadRecipient.isGroup && threadRecipient.isBlocked) {
true
} else if (threadRecipient.isGroup) {
@@ -153,7 +154,7 @@ open class MessageContentProcessor(private val context: Context) {
return senderRecipient.isBlocked
}
val isTextMessage = message.hasBody()
val isTextMessage = message.body != null
val isMediaMessage = message.isMediaMessage
val isExpireMessage = message.isExpirationUpdate
val isGv2Update = message.hasSignedGroupChange
@@ -164,15 +165,15 @@ open class MessageContentProcessor(private val context: Context) {
} else {
senderRecipient.isBlocked
}
} else if (content.hasCallMessage()) {
} else if (content.callMessage != null) {
return senderRecipient.isBlocked
} else if (content.hasTypingMessage()) {
} else if (content.typingMessage != null) {
if (senderRecipient.isBlocked) {
return true
}
if (content.typingMessage.hasGroupId()) {
val groupId: GroupId = GroupId.push(content.typingMessage.groupId)
if (content.typingMessage!!.groupId != null) {
val groupId: GroupId = GroupId.push(content.typingMessage!!.groupId!!)
val groupRecipient = Recipient.externalPossiblyMigratedGroup(groupId)
return if (groupRecipient.isBlocked || !groupRecipient.isActiveGroup) {
true
@@ -181,7 +182,7 @@ open class MessageContentProcessor(private val context: Context) {
groupRecord.isPresent && groupRecord.get().isAnnouncementGroup && !groupRecord.get().admins.contains(senderRecipient)
}
}
} else if (content.hasStoryMessage()) {
} else if (content.storyMessage != null) {
return if (threadRecipient.isGroup && threadRecipient.isBlocked) {
true
} else {
@@ -227,7 +228,7 @@ open class MessageContentProcessor(private val context: Context) {
content: Content,
metadata: EnvelopeMetadata,
groupId: GroupId.V2,
groupV2: SignalServiceProtos.GroupContextV2,
groupV2: GroupContextV2,
senderRecipient: Recipient,
groupSecretParams: GroupSecretParams? = null
): Gv2PreProcessResult {
@@ -254,12 +255,12 @@ open class MessageContentProcessor(private val context: Context) {
}
if (groupRecord.isPresent && groupRecord.get().isAnnouncementGroup && !groupRecord.get().admins.contains(senderRecipient)) {
if (content.hasDataMessage()) {
if (content.dataMessage.hasDisallowedAnnouncementOnlyContent) {
if (content.dataMessage != null) {
if (content.dataMessage!!.hasDisallowedAnnouncementOnlyContent) {
Log.w(TAG, "Ignoring message from ${senderRecipient.id} because it has disallowed content, and they're not an admin in an announcement-only group.")
return Gv2PreProcessResult.IGNORE
}
} else if (content.hasTypingMessage()) {
} else if (content.typingMessage != null) {
Log.w(TAG, "Ignoring typing indicator from ${senderRecipient.id} because they're not an admin in an announcement-only group.")
return Gv2PreProcessResult.IGNORE
}
@@ -275,14 +276,19 @@ open class MessageContentProcessor(private val context: Context) {
fun updateGv2GroupFromServerOrP2PChange(
context: Context,
timestamp: Long,
groupV2: SignalServiceProtos.GroupContextV2,
groupV2: GroupContextV2,
localRecord: Optional<GroupRecord>,
groupSecretParams: GroupSecretParams? = null
): GroupsV2StateProcessor.GroupUpdateResult? {
return try {
val signedGroupChange: ByteArray? = if (groupV2.hasSignedGroupChange) groupV2.signedGroupChange else null
val updatedTimestamp = if (signedGroupChange != null) timestamp else timestamp - 1
GroupManager.updateGroupFromServer(context, groupV2.groupMasterKey, localRecord, groupSecretParams, groupV2.revision, updatedTimestamp, signedGroupChange)
if (groupV2.revision != null) {
GroupManager.updateGroupFromServer(context, groupV2.groupMasterKey, localRecord, groupSecretParams, groupV2.revision!!, updatedTimestamp, signedGroupChange)
} else {
warn(timestamp, "Ignore group update message without a revision")
null
}
} catch (e: GroupNotAMemberException) {
warn(timestamp, "Ignoring message for a group we're not in")
null
@@ -331,11 +337,11 @@ open class MessageContentProcessor(private val context: Context) {
val earlyCacheEntries: List<EarlyMessageCacheEntry>? = ApplicationDependencies
.getEarlyMessageCache()
.retrieve(senderRecipient.id, envelope.timestamp)
.retrieve(senderRecipient.id, envelope.timestamp!!)
.orNull()
if (!processingEarlyContent && earlyCacheEntries != null) {
log(envelope.timestamp, "Found " + earlyCacheEntries.size + " dependent item(s) that were retrieved earlier. Processing.")
log(envelope.timestamp!!, "Found " + earlyCacheEntries.size + " dependent item(s) that were retrieved earlier. Processing.")
for (entry in earlyCacheEntries) {
handleMessage(senderRecipient, entry.envelope, entry.content, entry.metadata, entry.serverDeliveredTimestamp, processingEarlyContent = true, localMetric = null)
}
@@ -411,17 +417,17 @@ open class MessageContentProcessor(private val context: Context) {
val threadRecipient = getMessageDestination(content, senderRecipient)
if (shouldIgnore(content, senderRecipient, threadRecipient)) {
log(envelope.timestamp, "Ignoring message.")
log(envelope.timestamp!!, "Ignoring message.")
return
}
val pending: PendingRetryReceiptModel? = ApplicationDependencies.getPendingRetryReceiptCache().get(senderRecipient.id, envelope.timestamp)
val receivedTime: Long = handlePendingRetry(pending, envelope.timestamp, threadRecipient)
val pending: PendingRetryReceiptModel? = ApplicationDependencies.getPendingRetryReceiptCache().get(senderRecipient.id, envelope.timestamp!!)
val receivedTime: Long = handlePendingRetry(pending, envelope.timestamp!!, threadRecipient)
log(envelope.timestamp, "Beginning message processing. Sender: " + formatSender(senderRecipient.id, metadata.sourceServiceId, metadata.sourceDeviceId))
log(envelope.timestamp!!, "Beginning message processing. Sender: " + formatSender(senderRecipient.id, metadata.sourceServiceId, metadata.sourceDeviceId))
localMetric?.onPreProcessComplete()
when {
content.hasDataMessage() -> {
content.dataMessage != null -> {
DataMessageProcessor.process(
context,
senderRecipient,
@@ -435,7 +441,7 @@ open class MessageContentProcessor(private val context: Context) {
)
}
content.hasSyncMessage() -> {
content.syncMessage != null -> {
TextSecurePreferences.setMultiDevice(context, true)
SyncMessageProcessor.process(
@@ -448,21 +454,20 @@ open class MessageContentProcessor(private val context: Context) {
)
}
content.hasCallMessage() -> {
log(envelope.timestamp, "Got call message...")
content.callMessage != null -> {
log(envelope.timestamp!!, "Got call message...")
val message: SignalServiceProtos.CallMessage = content.callMessage
val destinationDeviceId: Int? = if (message.hasDestinationDeviceId()) message.destinationDeviceId else null
val message: CallMessage = content.callMessage!!
if (destinationDeviceId != null && destinationDeviceId != SignalStore.account().deviceId) {
log(envelope.timestamp, "Ignoring call message that is not for this device! intended: $destinationDeviceId, this: ${SignalStore.account().deviceId}")
if (message.destinationDeviceId != null && message.destinationDeviceId != SignalStore.account().deviceId) {
log(envelope.timestamp!!, "Ignoring call message that is not for this device! intended: ${message.destinationDeviceId}, this: ${SignalStore.account().deviceId}")
return
}
CallMessageProcessor.process(senderRecipient, envelope, content, metadata, serverDeliveredTimestamp)
}
content.hasReceiptMessage() -> {
content.receiptMessage != null -> {
ReceiptMessageProcessor.process(
context,
senderRecipient,
@@ -473,11 +478,11 @@ open class MessageContentProcessor(private val context: Context) {
)
}
content.hasTypingMessage() -> {
handleTypingMessage(envelope, metadata, content.typingMessage, senderRecipient)
content.typingMessage != null -> {
handleTypingMessage(envelope, metadata, content.typingMessage!!, senderRecipient)
}
content.hasStoryMessage() -> {
content.storyMessage != null -> {
StoryMessageProcessor.process(
envelope,
content,
@@ -487,11 +492,11 @@ open class MessageContentProcessor(private val context: Context) {
)
}
content.hasDecryptionErrorMessage() -> {
content.decryptionErrorMessage != null -> {
handleRetryReceipt(envelope, metadata, content.decryptionErrorMessage!!.toDecryptionErrorMessage(metadata), senderRecipient)
}
content.hasEditMessage() -> {
content.editMessage != null -> {
EditMessageProcessor.process(
context,
senderRecipient,
@@ -503,17 +508,17 @@ open class MessageContentProcessor(private val context: Context) {
)
}
content.hasSenderKeyDistributionMessage() || content.hasPniSignatureMessage() -> {
content.senderKeyDistributionMessage != null || content.pniSignatureMessage != null -> {
// Already handled, here in order to prevent unrecognized message log
}
else -> {
warn(envelope.timestamp, "Got unrecognized message!")
warn(envelope.timestamp!!, "Got unrecognized message!")
}
}
if (pending != null) {
warn(envelope.timestamp, "Pending retry was processed. Deleting.")
warn(envelope.timestamp!!, "Pending retry was processed. Deleting.")
ApplicationDependencies.getPendingRetryReceiptCache().delete(pending)
}
}
@@ -529,10 +534,10 @@ open class MessageContentProcessor(private val context: Context) {
return
}
val threadId: Long = if (typingMessage.hasGroupId()) {
val groupId = GroupId.push(typingMessage.groupId)
val threadId: Long = if (typingMessage.groupId != null) {
val groupId = GroupId.push(typingMessage.groupId!!)
if (!SignalDatabase.groups.isCurrentMember(groupId, senderRecipient.id)) {
warn(envelope.timestamp, "Seen typing indicator for non-member " + senderRecipient.id)
warn(envelope.timestamp!!, "Seen typing indicator for non-member " + senderRecipient.id)
return
}
@@ -543,7 +548,7 @@ open class MessageContentProcessor(private val context: Context) {
}
if (threadId <= 0) {
warn(envelope.timestamp, "Couldn't find a matching thread for a typing message.")
warn(envelope.timestamp!!, "Couldn't find a matching thread for a typing message.")
return
}
@@ -558,19 +563,19 @@ open class MessageContentProcessor(private val context: Context) {
private fun handleRetryReceipt(envelope: Envelope, metadata: EnvelopeMetadata, decryptionErrorMessage: DecryptionErrorMessage, senderRecipient: Recipient) {
if (!FeatureFlags.retryReceipts()) {
warn(envelope.timestamp, "[RetryReceipt] Feature flag disabled, skipping retry receipt.")
warn(envelope.timestamp!!, "[RetryReceipt] Feature flag disabled, skipping retry receipt.")
return
}
if (decryptionErrorMessage.deviceId != SignalStore.account().deviceId) {
log(envelope.timestamp, "[RetryReceipt] Received a DecryptionErrorMessage targeting a linked device. Ignoring.")
log(envelope.timestamp!!, "[RetryReceipt] Received a DecryptionErrorMessage targeting a linked device. Ignoring.")
return
}
val sentTimestamp = decryptionErrorMessage.timestamp
warn(envelope.timestamp, "[RetryReceipt] Received a retry receipt from ${formatSender(senderRecipient.id, metadata.sourceServiceId, metadata.sourceDeviceId)} for message with timestamp $sentTimestamp.")
warn(envelope.timestamp!!, "[RetryReceipt] Received a retry receipt from ${formatSender(senderRecipient.id, metadata.sourceServiceId, metadata.sourceDeviceId)} for message with timestamp $sentTimestamp.")
if (!senderRecipient.hasServiceId()) {
warn(envelope.timestamp, "[RetryReceipt] Requester ${senderRecipient.id} somehow has no UUID! timestamp: $sentTimestamp")
warn(envelope.timestamp!!, "[RetryReceipt] Requester ${senderRecipient.id} somehow has no UUID! timestamp: $sentTimestamp")
return
}
@@ -593,18 +598,18 @@ open class MessageContentProcessor(private val context: Context) {
val relatedMessage = findRetryReceiptRelatedMessage(messageLogEntry, sentTimestamp)
if (relatedMessage == null) {
warn(envelope.timestamp, "[RetryReceipt-SK] The related message could not be found! There shouldn't be any sender key resends where we can't find the related message. Skipping.")
warn(envelope.timestamp!!, "[RetryReceipt-SK] The related message could not be found! There shouldn't be any sender key resends where we can't find the related message. Skipping.")
return
}
val threadRecipient = SignalDatabase.threads.getRecipientForThreadId(relatedMessage.threadId)
if (threadRecipient == null) {
warn(envelope.timestamp, "[RetryReceipt-SK] Could not find a thread recipient! Skipping.")
warn(envelope.timestamp!!, "[RetryReceipt-SK] Could not find a thread recipient! Skipping.")
return
}
if (!threadRecipient.isPushV2Group && !threadRecipient.isDistributionList) {
warn(envelope.timestamp, "[RetryReceipt-SK] Thread recipient is not a V2 group or distribution list! Skipping.")
warn(envelope.timestamp!!, "[RetryReceipt-SK] Thread recipient is not a V2 group or distribution list! Skipping.")
return
}
@@ -628,7 +633,7 @@ open class MessageContentProcessor(private val context: Context) {
SignalDatabase.senderKeyShared.delete(distributionId, setOf(requesterAddress))
if (messageLogEntry != null) {
warn(envelope.timestamp, "[RetryReceipt-SK] Found MSL entry for ${requester.id} ($requesterAddress) with timestamp $sentTimestamp. Scheduling a resend.")
warn(envelope.timestamp!!, "[RetryReceipt-SK] Found MSL entry for ${requester.id} ($requesterAddress) with timestamp $sentTimestamp. Scheduling a resend.")
ApplicationDependencies.getJobManager().add(
ResendMessageJob(
messageLogEntry.recipientId,
@@ -641,7 +646,7 @@ open class MessageContentProcessor(private val context: Context) {
)
)
} else {
warn(envelope.timestamp, "[RetryReceipt-SK] Unable to find MSL entry for ${requester.id} ($requesterAddress) with timestamp $sentTimestamp for ${if (groupId != null) "group $groupId" else "distribution list"}. Scheduling a job to send them the SenderKeyDistributionMessage. Membership will be checked there.")
warn(envelope.timestamp!!, "[RetryReceipt-SK] Unable to find MSL entry for ${requester.id} ($requesterAddress) with timestamp $sentTimestamp for ${if (groupId != null) "group $groupId" else "distribution list"}. Scheduling a job to send them the SenderKeyDistributionMessage. Membership will be checked there.")
ApplicationDependencies.getJobManager().add(SenderKeyDistributionSendJob(requester.id, threadRecipient.id))
}
}
@@ -653,13 +658,13 @@ open class MessageContentProcessor(private val context: Context) {
if (decryptionErrorMessage.ratchetKey.isPresent &&
ratchetKeyMatches(requester, metadata.sourceDeviceId, decryptionErrorMessage.ratchetKey.get())
) {
warn(envelope.timestamp, "[RetryReceipt-I] Ratchet key matches. Archiving the session.")
warn(envelope.timestamp!!, "[RetryReceipt-I] Ratchet key matches. Archiving the session.")
ApplicationDependencies.getProtocolStore().aci().sessions().archiveSession(requester.requireServiceId(), metadata.sourceDeviceId)
archivedSession = true
}
if (messageLogEntry != null) {
warn(envelope.timestamp, "[RetryReceipt-I] Found an entry in the MSL. Resending.")
warn(envelope.timestamp!!, "[RetryReceipt-I] Found an entry in the MSL. Resending.")
ApplicationDependencies.getJobManager().add(
ResendMessageJob(
messageLogEntry.recipientId,
@@ -672,10 +677,10 @@ open class MessageContentProcessor(private val context: Context) {
)
)
} else if (archivedSession) {
warn(envelope.timestamp, "[RetryReceipt-I] Could not find an entry in the MSL, but we archived the session, so we're sending a null message to complete the reset.")
warn(envelope.timestamp!!, "[RetryReceipt-I] Could not find an entry in the MSL, but we archived the session, so we're sending a null message to complete the reset.")
ApplicationDependencies.getJobManager().add(NullMessageSendJob(requester.id))
} else {
warn(envelope.timestamp, "[RetryReceipt-I] Could not find an entry in the MSL. Skipping.")
warn(envelope.timestamp!!, "[RetryReceipt-I] Could not find an entry in the MSL. Skipping.")
}
}

View File

@@ -60,9 +60,9 @@ import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.PniSignatureMessage
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.PniSignatureMessage
import java.util.Optional
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.DurationUnit
@@ -103,7 +103,7 @@ object MessageDecryptor {
return Result.Ignore(envelope, serverDeliveredTimestamp, emptyList())
}
if (destination == selfPni && envelope.hasSourceServiceId()) {
if (destination == selfPni && envelope.sourceServiceId != null) {
Log.i(TAG, "${logPrefix(envelope)} Received a message at our PNI. Marking as needing a PNI signature.")
val sourceServiceId = ServiceId.parseOrNull(envelope.sourceServiceId)
@@ -116,7 +116,7 @@ object MessageDecryptor {
}
}
if (destination == selfPni && !envelope.hasSourceServiceId()) {
if (destination == selfPni && envelope.sourceServiceId == null) {
Log.w(TAG, "${logPrefix(envelope)} Got a sealed sender message to our PNI? Invalid message, ignoring.")
return Result.Ignore(envelope, serverDeliveredTimestamp, emptyList())
}
@@ -143,7 +143,7 @@ object MessageDecryptor {
return Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList())
}
Log.d(TAG, "${logPrefix(envelope, cipherResult)} Successfully decrypted the envelope in ${(endTimeNanos - startTimeNanos).nanoseconds.toDouble(DurationUnit.MILLISECONDS).roundedString(2)} ms (GUID ${envelope.serverGuid}). Delivery latency: ${serverDeliveredTimestamp - envelope.serverTimestamp} ms, Urgent: ${envelope.urgent}")
Log.d(TAG, "${logPrefix(envelope, cipherResult)} Successfully decrypted the envelope in ${(endTimeNanos - startTimeNanos).nanoseconds.toDouble(DurationUnit.MILLISECONDS).roundedString(2)} ms (GUID ${envelope.serverGuid}). Delivery latency: ${serverDeliveredTimestamp - envelope.serverTimestamp!!} ms, Urgent: ${envelope.urgent}")
val validationResult: EnvelopeContentValidator.Result = EnvelopeContentValidator.validate(envelope, cipherResult.content)
@@ -163,17 +163,17 @@ object MessageDecryptor {
}
// Must handle SKDM's immediately, because subsequent decryptions could rely on it
if (cipherResult.content.hasSenderKeyDistributionMessage()) {
if (cipherResult.content.senderKeyDistributionMessage != null) {
handleSenderKeyDistributionMessage(
envelope,
cipherResult.metadata.sourceServiceId,
cipherResult.metadata.sourceDeviceId,
SenderKeyDistributionMessage(cipherResult.content.senderKeyDistributionMessage.toByteArray()),
SenderKeyDistributionMessage(cipherResult.content.senderKeyDistributionMessage!!.toByteArray()),
bufferedProtocolStore.getAciStore()
)
}
if (cipherResult.content.hasPniSignatureMessage()) {
if (cipherResult.content.pniSignatureMessage != null) {
if (cipherResult.metadata.sourceServiceId is ACI) {
handlePniSignatureMessage(
envelope,
@@ -181,19 +181,19 @@ object MessageDecryptor {
cipherResult.metadata.sourceServiceId as ACI,
cipherResult.metadata.sourceE164,
cipherResult.metadata.sourceDeviceId,
cipherResult.content.pniSignatureMessage
cipherResult.content.pniSignatureMessage!!
)
} else {
Log.w(TAG, "${logPrefix(envelope)} Ignoring PNI signature because the sourceServiceId isn't an ACI!")
}
} else if (cipherResult.content.hasPniSignatureMessage()) {
} else if (cipherResult.content.pniSignatureMessage != null) {
Log.w(TAG, "${logPrefix(envelope)} Ignoring PNI signature because the feature flag is disabled!")
}
// TODO We can move this to the "message processing" stage once we give it access to the envelope. But for now it'll stay here.
if (envelope.hasReportingToken() && envelope.reportingToken != null && envelope.reportingToken.size() > 0) {
if (envelope.reportingToken != null && envelope.reportingToken!!.size > 0) {
val sender = RecipientId.from(cipherResult.metadata.sourceServiceId)
SignalDatabase.recipients.setReportingToken(sender, envelope.reportingToken.toByteArray())
SignalDatabase.recipients.setReportingToken(sender, envelope.reportingToken!!.toByteArray())
}
Result.Success(envelope, serverDeliveredTimestamp, cipherResult.content, cipherResult.metadata, followUpOperations.toUnmodifiableList())
@@ -218,7 +218,7 @@ object MessageDecryptor {
followUpOperations += FollowUpOperation {
val sender: Recipient = Recipient.external(context, e.sender)
AutomaticSessionResetJob(sender.id, e.senderDevice, envelope.timestamp)
AutomaticSessionResetJob(sender.id, e.senderDevice, envelope.timestamp!!)
}
Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList())
@@ -281,7 +281,7 @@ object MessageDecryptor {
Log.w(TAG, "${logPrefix(envelope)} Decryption error for a sync message! Enqueuing a session reset job.")
followUpOperations += FollowUpOperation {
AutomaticSessionResetJob(sender.id, senderDevice, envelope.timestamp)
AutomaticSessionResetJob(sender.id, senderDevice, envelope.timestamp!!)
}
return Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations)
@@ -309,7 +309,7 @@ object MessageDecryptor {
SignalDatabase.threads.getOrCreateThreadIdFor(sender)
}
ApplicationDependencies.getPendingRetryReceiptCache().insert(sender.id, senderDevice, envelope.timestamp, receivedTimestamp, threadId)
ApplicationDependencies.getPendingRetryReceiptCache().insert(sender.id, senderDevice, envelope.timestamp!!, receivedTimestamp, threadId)
ApplicationDependencies.getPendingRetryReceiptManager().scheduleIfNecessary()
null
}
@@ -334,7 +334,7 @@ object MessageDecryptor {
private fun handlePniSignatureMessage(envelope: Envelope, protocolStore: BufferedProtocolStore, aci: ACI, e164: String?, deviceId: Int, pniSignatureMessage: PniSignatureMessage) {
Log.i(TAG, "${logPrefix(envelope, aci)} Processing PniSignatureMessage")
val pni: PNI = PNI.parseOrThrow(pniSignatureMessage.pni.toByteArray())
val pni: PNI = PNI.parseOrThrow(pniSignatureMessage.pni!!.toByteArray())
if (SignalDatabase.recipients.isAssociated(aci, pni)) {
Log.i(TAG, "${logPrefix(envelope, aci)}[handlePniSignatureMessage] ACI ($aci) and PNI ($pni) are already associated.")
@@ -356,7 +356,7 @@ object MessageDecryptor {
return
}
if (pniIdentity.verifyAlternateIdentity(aciIdentity, pniSignatureMessage.signature.toByteArray())) {
if (pniIdentity.verifyAlternateIdentity(aciIdentity, pniSignatureMessage.signature!!.toByteArray())) {
Log.i(TAG, "${logPrefix(envelope, aci)}[validatePniSignature] PNI signature is valid. Associating ACI ($aci) with PNI ($pni)")
SignalDatabase.recipients.getAndPossiblyMergePnpVerified(aci, pni, e164)
} else {
@@ -387,28 +387,28 @@ object MessageDecryptor {
}
private fun logPrefix(envelope: Envelope): String {
return logPrefix(envelope.timestamp, envelope.sourceServiceId ?: "<sealed>", envelope.sourceDevice)
return logPrefix(envelope.timestamp!!, envelope.sourceServiceId ?: "<sealed>", envelope.sourceDevice)
}
private fun logPrefix(envelope: Envelope, sender: ServiceId): String {
return logPrefix(envelope.timestamp, sender.toString(), envelope.sourceDevice)
return logPrefix(envelope.timestamp!!, sender.toString(), envelope.sourceDevice)
}
private fun logPrefix(envelope: Envelope, cipherResult: SignalServiceCipherResult): String {
return logPrefix(envelope.timestamp, cipherResult.metadata.sourceServiceId.toString(), cipherResult.metadata.sourceDeviceId)
return logPrefix(envelope.timestamp!!, cipherResult.metadata.sourceServiceId.toString(), cipherResult.metadata.sourceDeviceId)
}
private fun logPrefix(envelope: Envelope, exception: ProtocolException): String {
return if (exception.sender != null) {
logPrefix(envelope.timestamp, exception.sender, exception.senderDevice)
logPrefix(envelope.timestamp!!, exception.sender, exception.senderDevice)
} else {
logPrefix(envelope.timestamp, envelope.sourceServiceId, envelope.sourceDevice)
logPrefix(envelope.timestamp!!, envelope.sourceServiceId, envelope.sourceDevice)
}
}
private fun logPrefix(timestamp: Long, sender: String?, deviceId: Int): String {
private fun logPrefix(timestamp: Long, sender: String?, deviceId: Int?): String {
val senderString = sender ?: "null"
return "[$timestamp] $senderString:$deviceId |"
return "[$timestamp] $senderString:${deviceId ?: 0} |"
}
private fun buildSendRetryReceiptJob(envelope: Envelope, protocolException: ProtocolException, sender: Recipient): SendRetryReceiptJob {
@@ -419,11 +419,11 @@ object MessageDecryptor {
originalContent = protocolException.unidentifiedSenderMessageContent.get().content
envelopeType = protocolException.unidentifiedSenderMessageContent.get().type
} else {
originalContent = envelope.content.toByteArray()
envelopeType = envelope.type.number.toCiphertextMessageType()
originalContent = envelope.content!!.toByteArray()
envelopeType = envelope.type!!.value.toCiphertextMessageType()
}
val decryptionErrorMessage: DecryptionErrorMessage = DecryptionErrorMessage.forOriginalMessage(originalContent, envelopeType, envelope.timestamp, protocolException.senderDevice)
val decryptionErrorMessage: DecryptionErrorMessage = DecryptionErrorMessage.forOriginalMessage(originalContent, envelopeType, envelope.timestamp!!, protocolException.senderDevice)
val groupId: GroupId? = protocolException.parseGroupId(envelope)
return SendRetryReceiptJob(sender.id, Optional.ofNullable(groupId), decryptionErrorMessage)
}
@@ -443,10 +443,10 @@ object MessageDecryptor {
private fun Int.toCiphertextMessageType(): Int {
return when (this) {
Envelope.Type.CIPHERTEXT_VALUE -> CiphertextMessage.WHISPER_TYPE
Envelope.Type.PREKEY_BUNDLE_VALUE -> CiphertextMessage.PREKEY_TYPE
Envelope.Type.UNIDENTIFIED_SENDER_VALUE -> CiphertextMessage.SENDERKEY_TYPE
Envelope.Type.PLAINTEXT_CONTENT_VALUE -> CiphertextMessage.PLAINTEXT_CONTENT_TYPE
Envelope.Type.CIPHERTEXT.value -> CiphertextMessage.WHISPER_TYPE
Envelope.Type.PREKEY_BUNDLE.value -> CiphertextMessage.PREKEY_TYPE
Envelope.Type.UNIDENTIFIED_SENDER.value -> CiphertextMessage.SENDERKEY_TYPE
Envelope.Type.PLAINTEXT_CONTENT.value -> CiphertextMessage.PLAINTEXT_CONTENT_TYPE
else -> CiphertextMessage.WHISPER_TYPE
}
}

View File

@@ -13,19 +13,19 @@ import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.EarlyMessageCacheEntry
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.ReceiptMessage
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.ReceiptMessage
object ReceiptMessageProcessor {
fun process(context: Context, senderRecipient: Recipient, envelope: Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, earlyMessageCacheEntry: EarlyMessageCacheEntry?) {
val receiptMessage = content.receiptMessage
fun process(context: Context, senderRecipient: Recipient, envelope: Envelope, content: Content, metadata: EnvelopeMetadata, earlyMessageCacheEntry: EarlyMessageCacheEntry?) {
val receiptMessage = content.receiptMessage!!
when (receiptMessage.type) {
ReceiptMessage.Type.DELIVERY -> handleDeliveryReceipt(envelope, metadata, receiptMessage, senderRecipient.id)
ReceiptMessage.Type.READ -> handleReadReceipt(context, senderRecipient.id, envelope, metadata, receiptMessage, earlyMessageCacheEntry)
ReceiptMessage.Type.VIEWED -> handleViewedReceipt(context, envelope, metadata, receiptMessage, senderRecipient.id, earlyMessageCacheEntry)
else -> warn(envelope.timestamp, "Unknown recipient message type ${receiptMessage.type}")
else -> warn(envelope.timestamp!!, "Unknown recipient message type ${receiptMessage.type}")
}
}
@@ -36,12 +36,12 @@ object ReceiptMessageProcessor {
deliveryReceipt: ReceiptMessage,
senderRecipientId: RecipientId
) {
log(envelope.timestamp, "Processing delivery receipts. Sender: $senderRecipientId, Device: ${metadata.sourceDeviceId}, Timestamps: ${deliveryReceipt.timestampList.joinToString(", ")}")
log(envelope.timestamp!!, "Processing delivery receipts. Sender: $senderRecipientId, Device: ${metadata.sourceDeviceId}, Timestamps: ${deliveryReceipt.timestamp.joinToString(", ")}")
val missingTargetTimestamps: Set<Long> = SignalDatabase.messages.incrementDeliveryReceiptCounts(deliveryReceipt.timestampList, senderRecipientId, envelope.timestamp)
val missingTargetTimestamps: Set<Long> = SignalDatabase.messages.incrementDeliveryReceiptCounts(deliveryReceipt.timestamp, senderRecipientId, envelope.timestamp!!)
for (targetTimestamp in missingTargetTimestamps) {
warn(envelope.timestamp, "[handleDeliveryReceipt] Could not find matching message! targetTimestamp: $targetTimestamp, receiptAuthor: $senderRecipientId")
warn(envelope.timestamp!!, "[handleDeliveryReceipt] Could not find matching message! targetTimestamp: $targetTimestamp, receiptAuthor: $senderRecipientId")
// Early delivery receipts are special-cased in the database methods
}
@@ -49,8 +49,8 @@ object ReceiptMessageProcessor {
PushProcessEarlyMessagesJob.enqueue()
}
SignalDatabase.pendingPniSignatureMessages.acknowledgeReceipts(senderRecipientId, deliveryReceipt.timestampList, metadata.sourceDeviceId)
SignalDatabase.messageLog.deleteEntriesForRecipient(deliveryReceipt.timestampList, senderRecipientId, metadata.sourceDeviceId)
SignalDatabase.pendingPniSignatureMessages.acknowledgeReceipts(senderRecipientId, deliveryReceipt.timestamp, metadata.sourceDeviceId)
SignalDatabase.messageLog.deleteEntriesForRecipient(deliveryReceipt.timestamp, senderRecipientId, metadata.sourceDeviceId)
}
@SuppressLint("DefaultLocale")
@@ -63,19 +63,19 @@ object ReceiptMessageProcessor {
earlyMessageCacheEntry: EarlyMessageCacheEntry?
) {
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
log(envelope.timestamp, "Ignoring read receipts for IDs: " + readReceipt.timestampList.joinToString(", "))
log(envelope.timestamp!!, "Ignoring read receipts for IDs: " + readReceipt.timestamp.joinToString(", "))
return
}
log(envelope.timestamp, "Processing read receipts. Sender: $senderRecipientId, Device: ${metadata.sourceDeviceId}, Timestamps: ${readReceipt.timestampList.joinToString(", ")}")
log(envelope.timestamp!!, "Processing read receipts. Sender: $senderRecipientId, Device: ${metadata.sourceDeviceId}, Timestamps: ${readReceipt.timestamp.joinToString(", ")}")
val missingTargetTimestamps: Set<Long> = SignalDatabase.messages.incrementReadReceiptCounts(readReceipt.timestampList, senderRecipientId, envelope.timestamp)
val missingTargetTimestamps: Set<Long> = SignalDatabase.messages.incrementReadReceiptCounts(readReceipt.timestamp, senderRecipientId, envelope.timestamp!!)
if (missingTargetTimestamps.isNotEmpty()) {
val selfId = Recipient.self().id
for (targetTimestamp in missingTargetTimestamps) {
warn(envelope.timestamp, "[handleReadReceipt] Could not find matching message! targetTimestamp: $targetTimestamp, receiptAuthor: $senderRecipientId | Receipt, so associating with message from self ($selfId)")
warn(envelope.timestamp!!, "[handleReadReceipt] Could not find matching message! targetTimestamp: $targetTimestamp, receiptAuthor: $senderRecipientId | Receipt, so associating with message from self ($selfId)")
if (earlyMessageCacheEntry != null) {
ApplicationDependencies.getEarlyMessageCache().store(selfId, targetTimestamp, earlyMessageCacheEntry)
}
@@ -99,28 +99,28 @@ object ReceiptMessageProcessor {
val storyViewedReceipts = SignalStore.storyValues().viewedReceiptsEnabled
if (!readReceipts && !storyViewedReceipts) {
log(envelope.timestamp, "Ignoring viewed receipts for IDs: ${viewedReceipt.timestampList.joinToString(", ")}")
log(envelope.timestamp!!, "Ignoring viewed receipts for IDs: ${viewedReceipt.timestamp.joinToString(", ")}")
return
}
log(envelope.timestamp, "Processing viewed receipts. Sender: $senderRecipientId, Device: ${metadata.sourceDeviceId}, Only Stories: ${!readReceipts}, Timestamps: ${viewedReceipt.timestampList.joinToString(", ")}")
log(envelope.timestamp!!, "Processing viewed receipts. Sender: $senderRecipientId, Device: ${metadata.sourceDeviceId}, Only Stories: ${!readReceipts}, Timestamps: ${viewedReceipt.timestamp.joinToString(", ")}")
val missingTargetTimestamps: Set<Long> = if (readReceipts && storyViewedReceipts) {
SignalDatabase.messages.incrementViewedReceiptCounts(viewedReceipt.timestampList, senderRecipientId, envelope.timestamp)
SignalDatabase.messages.incrementViewedReceiptCounts(viewedReceipt.timestamp, senderRecipientId, envelope.timestamp!!)
} else if (readReceipts) {
SignalDatabase.messages.incrementViewedNonStoryReceiptCounts(viewedReceipt.timestampList, senderRecipientId, envelope.timestamp)
SignalDatabase.messages.incrementViewedNonStoryReceiptCounts(viewedReceipt.timestamp, senderRecipientId, envelope.timestamp!!)
} else {
SignalDatabase.messages.incrementViewedStoryReceiptCounts(viewedReceipt.timestampList, senderRecipientId, envelope.timestamp)
SignalDatabase.messages.incrementViewedStoryReceiptCounts(viewedReceipt.timestamp, senderRecipientId, envelope.timestamp!!)
}
val foundTargetTimestamps: Set<Long> = viewedReceipt.timestampList.toSet() - missingTargetTimestamps.toSet()
val foundTargetTimestamps: Set<Long> = viewedReceipt.timestamp.toSet() - missingTargetTimestamps.toSet()
SignalDatabase.messages.updateViewedStories(foundTargetTimestamps)
if (missingTargetTimestamps.isNotEmpty()) {
val selfId = Recipient.self().id
for (targetTimestamp in missingTargetTimestamps) {
warn(envelope.timestamp, "[handleViewedReceipt] Could not find matching message! targetTimestamp: $targetTimestamp, receiptAuthor: $senderRecipientId | Receipt so associating with message from self ($selfId)")
warn(envelope.timestamp!!, "[handleViewedReceipt] Could not find matching message! targetTimestamp: $targetTimestamp, receiptAuthor: $senderRecipientId | Receipt so associating with message from self ($selfId)")
if (earlyMessageCacheEntry != null) {
ApplicationDependencies.getEarlyMessageCache().store(selfId, targetTimestamp, earlyMessageCacheEntry)
}

View File

@@ -1,7 +1,8 @@
package org.thoughtcrime.securesms.messages
import com.google.protobuf.ByteString
import com.google.protobuf.GeneratedMessageLite
import ProtoUtil.isNotEmpty
import com.squareup.wire.Message
import okio.ByteString
import org.signal.core.util.orNull
import org.signal.libsignal.protocol.message.DecryptionErrorMessage
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
@@ -18,79 +19,78 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPoin
import org.whispersystems.signalservice.api.payments.Money
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.util.AttachmentPointerUtil
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage.Payment
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.StoryMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.Sent
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.TypingMessage
import org.whispersystems.signalservice.internal.push.AttachmentPointer
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.DataMessage.Payment
import org.whispersystems.signalservice.internal.push.GroupContextV2
import org.whispersystems.signalservice.internal.push.StoryMessage
import org.whispersystems.signalservice.internal.push.SyncMessage.Sent
import org.whispersystems.signalservice.internal.push.TypingMessage
import java.util.Optional
private val ByteString.isNotEmpty: Boolean
get() = !this.isEmpty
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
object SignalServiceProtoUtil {
/** Contains some user data that affects the conversation */
val DataMessage.hasRenderableContent: Boolean
get() {
return attachmentsList.isNotEmpty() ||
hasBody() ||
hasQuote() ||
contactList.isNotEmpty() ||
previewList.isNotEmpty() ||
bodyRangesList.isNotEmpty() ||
hasSticker() ||
hasReaction() ||
return attachments.isNotEmpty() ||
body != null ||
quote != null ||
contact.isNotEmpty() ||
preview.isNotEmpty() ||
bodyRanges.isNotEmpty() ||
sticker != null ||
reaction != null ||
hasRemoteDelete
}
val DataMessage.hasDisallowedAnnouncementOnlyContent: Boolean
get() {
return hasBody() ||
attachmentsList.isNotEmpty() ||
hasQuote() ||
previewList.isNotEmpty() ||
bodyRangesList.isNotEmpty() ||
hasSticker()
return body != null ||
attachments.isNotEmpty() ||
quote != null ||
preview.isNotEmpty() ||
bodyRanges.isNotEmpty() ||
sticker != null
}
val DataMessage.isExpirationUpdate: Boolean
get() = flags and DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE != 0
get() = flags != null && flags!! and DataMessage.Flags.EXPIRATION_TIMER_UPDATE.value != 0
val DataMessage.hasRemoteDelete: Boolean
get() = hasDelete() && delete.hasTargetSentTimestamp()
get() = delete != null && delete!!.targetSentTimestamp != null
val DataMessage.isGroupV2Update: Boolean
get() = !hasRenderableContent && hasSignedGroupChange
val DataMessage.hasGroupContext: Boolean
get() = hasGroupV2() && groupV2.hasMasterKey() && groupV2.masterKey.isNotEmpty
val DataMessage?.hasGroupContext: Boolean
get() = this?.groupV2?.masterKey.isNotEmpty()
val DataMessage.hasSignedGroupChange: Boolean
get() = hasGroupContext && groupV2.hasSignedGroupChange
get() = hasGroupContext && groupV2!!.hasSignedGroupChange
val DataMessage.isMediaMessage: Boolean
get() = attachmentsList.isNotEmpty() || hasQuote() || contactList.isNotEmpty() || hasSticker() || bodyRangesList.isNotEmpty() || previewList.isNotEmpty()
get() = attachments.isNotEmpty() || quote != null || contact.isNotEmpty() || sticker != null || bodyRanges.isNotEmpty() || preview.isNotEmpty()
val DataMessage.isEndSession: Boolean
get() = flags and DataMessage.Flags.END_SESSION_VALUE != 0
get() = flags != null && flags!! and DataMessage.Flags.END_SESSION.value != 0
val DataMessage.isStoryReaction: Boolean
get() = hasReaction() && hasStoryContext()
get() = reaction != null && storyContext != null
val DataMessage.isPaymentActivationRequest: Boolean
get() = hasPayment() && payment.hasActivation() && payment.activation.type == Payment.Activation.Type.REQUEST
get() = payment?.activation?.type == Payment.Activation.Type.REQUEST
val DataMessage.isPaymentActivated: Boolean
get() = hasPayment() && payment.hasActivation() && payment.activation.type == Payment.Activation.Type.ACTIVATED
get() = payment?.activation?.type == Payment.Activation.Type.ACTIVATED
val DataMessage.isInvalid: Boolean
get() {
if (isViewOnce) {
val contentType = attachmentsList[0].contentType.lowercase()
return attachmentsList.size != 1 || !MediaUtil.isImageOrVideoType(contentType)
if (isViewOnce == true) {
val contentType = attachments[0].contentType?.lowercase()
return attachments.size != 1 || !MediaUtil.isImageOrVideoType(contentType)
}
return false
}
@@ -98,31 +98,34 @@ object SignalServiceProtoUtil {
val DataMessage.isEmptyGroupV2Message: Boolean
get() = hasGroupContext && !isGroupV2Update && !hasRenderableContent
val DataMessage.expireTimerDuration: Duration
get() = (expireTimer ?: 0).seconds
val GroupContextV2.hasSignedGroupChange: Boolean
get() = hasGroupChange() && groupChange.isNotEmpty
get() = groupChange.isNotEmpty()
val GroupContextV2.signedGroupChange: ByteArray
get() = groupChange.toByteArray()
get() = groupChange!!.toByteArray()
val GroupContextV2.groupMasterKey: GroupMasterKey
get() = GroupMasterKey(masterKey.toByteArray())
get() = GroupMasterKey(masterKey!!.toByteArray())
val GroupContextV2?.isValid: Boolean
get() = this != null && masterKey.isNotEmpty
get() = this?.masterKey.isNotEmpty()
val GroupContextV2.groupId: GroupId.V2?
get() = if (isValid) GroupId.v2(groupMasterKey) else null
val StoryMessage.type: StoryType
get() {
return if (allowsReplies) {
if (hasTextAttachment()) {
return if (allowsReplies == true) {
if (textAttachment != null) {
StoryType.TEXT_STORY_WITH_REPLIES
} else {
StoryType.STORY_WITH_REPLIES
}
} else {
if (hasTextAttachment()) {
if (textAttachment != null) {
StoryType.TEXT_STORY_WITHOUT_REPLIES
} else {
StoryType.STORY_WITHOUT_REPLIES
@@ -131,16 +134,16 @@ object SignalServiceProtoUtil {
}
fun Sent.isUnidentified(serviceId: ServiceId?): Boolean {
return serviceId != null && unidentifiedStatusList.firstOrNull { ServiceId.parseOrNull(it.destinationServiceId) == serviceId }?.unidentified ?: false
return serviceId != null && unidentifiedStatus.firstOrNull { ServiceId.parseOrNull(it.destinationServiceId) == serviceId }?.unidentified ?: false
}
val Sent.serviceIdsToUnidentifiedStatus: Map<ServiceId, Boolean>
get() {
return unidentifiedStatusList
return unidentifiedStatus
.mapNotNull { status ->
val serviceId = ServiceId.parseOrNull(status.destinationServiceId)
if (serviceId != null) {
serviceId to status.unidentified
serviceId to (status.unidentified ?: false)
} else {
null
}
@@ -149,7 +152,7 @@ object SignalServiceProtoUtil {
}
val TypingMessage.hasStarted: Boolean
get() = hasAction() && action == TypingMessage.Action.STARTED
get() = action == TypingMessage.Action.STARTED
fun ByteString.toDecryptionErrorMessage(metadata: EnvelopeMetadata): DecryptionErrorMessage {
try {
@@ -180,7 +183,7 @@ object SignalServiceProtoUtil {
}
@Suppress("UNCHECKED_CAST")
inline fun <reified MessageType : GeneratedMessageLite<MessageType, BuilderType>, BuilderType : GeneratedMessageLite.Builder<MessageType, BuilderType>> GeneratedMessageLite.Builder<MessageType, BuilderType>.buildWith(block: BuilderType.() -> Unit): MessageType {
inline fun <reified MessageType : Message<MessageType, BuilderType>, BuilderType : Message.Builder<MessageType, BuilderType>> Message.Builder<MessageType, BuilderType>.buildWith(block: BuilderType.() -> Unit): MessageType {
block(this as BuilderType)
return build()
}

View File

@@ -20,28 +20,30 @@ import org.thoughtcrime.securesms.stories.Stories
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.FeatureFlags
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.StoryMessage
import org.whispersystems.signalservice.internal.push.TextAttachment
object StoryMessageProcessor {
fun process(envelope: Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, senderRecipient: Recipient, threadRecipient: Recipient) {
val storyMessage = content.storyMessage
fun process(envelope: Envelope, content: Content, metadata: EnvelopeMetadata, senderRecipient: Recipient, threadRecipient: Recipient) {
val storyMessage = content.storyMessage!!
log(envelope.timestamp, "Story message.")
log(envelope.timestamp!!, "Story message.")
if (threadRecipient.isInactiveGroup) {
warn(envelope.timestamp, "Dropping a group story from a group we're no longer in.")
warn(envelope.timestamp!!, "Dropping a group story from a group we're no longer in.")
return
}
if (threadRecipient.isGroup && !SignalDatabase.groups.isCurrentMember(threadRecipient.requireGroupId().requirePush(), senderRecipient.id)) {
warn(envelope.timestamp, "Dropping a group story from a user who's no longer a member.")
warn(envelope.timestamp!!, "Dropping a group story from a user who's no longer a member.")
return
}
if (!threadRecipient.isGroup && !(senderRecipient.isProfileSharing || senderRecipient.isSystemContact)) {
warn(envelope.timestamp, "Dropping story from an untrusted source.")
warn(envelope.timestamp!!, "Dropping story from an untrusted source.")
return
}
@@ -50,29 +52,29 @@ object StoryMessageProcessor {
SignalDatabase.messages.beginTransaction()
try {
val storyType: StoryType = if (storyMessage.hasAllowsReplies() && storyMessage.allowsReplies) {
StoryType.withReplies(storyMessage.hasTextAttachment())
val storyType: StoryType = if (storyMessage.allowsReplies == true) {
StoryType.withReplies(isTextStory = storyMessage.textAttachment != null)
} else {
StoryType.withoutReplies(storyMessage.hasTextAttachment())
StoryType.withoutReplies(isTextStory = storyMessage.textAttachment != null)
}
val mediaMessage = IncomingMediaMessage(
from = senderRecipient.id,
sentTimeMillis = envelope.timestamp,
serverTimeMillis = envelope.serverTimestamp,
sentTimeMillis = envelope.timestamp!!,
serverTimeMillis = envelope.serverTimestamp!!,
receivedTimeMillis = System.currentTimeMillis(),
storyType = storyType,
isUnidentified = metadata.sealedSender,
body = serializeTextAttachment(storyMessage),
groupId = storyMessage.group.groupId,
attachments = if (storyMessage.hasFileAttachment()) listOfNotNull(storyMessage.fileAttachment.toPointer()) else emptyList(),
groupId = storyMessage.group?.groupId,
attachments = listOfNotNull(storyMessage.fileAttachment?.toPointer()),
linkPreviews = DataMessageProcessor.getLinkPreviews(
previews = if (storyMessage.textAttachment.hasPreview()) listOf(storyMessage.textAttachment.preview) else emptyList(),
previews = listOfNotNull(storyMessage.textAttachment?.preview),
body = "",
isStoryEmbed = true
),
serverGuid = envelope.serverGuid,
messageRanges = storyMessage.bodyRangesList.filterNot { it.hasMentionAci() }.toBodyRangeList()
messageRanges = storyMessage.bodyRanges.filter { it.mentionAci == null }.toBodyRangeList()
)
insertResult = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mediaMessage, -1).orNull()
@@ -91,66 +93,60 @@ object StoryMessageProcessor {
}
}
fun serializeTextAttachment(story: SignalServiceProtos.StoryMessage): String? {
if (!story.hasTextAttachment()) {
return null
}
val textAttachment = story.textAttachment
val builder = StoryTextPost.newBuilder()
fun serializeTextAttachment(story: StoryMessage): String? {
val textAttachment = story.textAttachment ?: return null
val builder = StoryTextPost.Builder()
if (textAttachment.hasText()) {
builder.body = textAttachment.text
if (textAttachment.text != null) {
builder.body = textAttachment.text!!
}
if (textAttachment.hasTextStyle()) {
when (textAttachment.textStyle) {
SignalServiceProtos.TextAttachment.Style.DEFAULT -> builder.style = StoryTextPost.Style.DEFAULT
SignalServiceProtos.TextAttachment.Style.REGULAR -> builder.style = StoryTextPost.Style.REGULAR
SignalServiceProtos.TextAttachment.Style.BOLD -> builder.style = StoryTextPost.Style.BOLD
SignalServiceProtos.TextAttachment.Style.SERIF -> builder.style = StoryTextPost.Style.SERIF
SignalServiceProtos.TextAttachment.Style.SCRIPT -> builder.style = StoryTextPost.Style.SCRIPT
SignalServiceProtos.TextAttachment.Style.CONDENSED -> builder.style = StoryTextPost.Style.CONDENSED
else -> Unit
}
when (textAttachment.textStyle) {
TextAttachment.Style.DEFAULT -> builder.style = StoryTextPost.Style.DEFAULT
TextAttachment.Style.REGULAR -> builder.style = StoryTextPost.Style.REGULAR
TextAttachment.Style.BOLD -> builder.style = StoryTextPost.Style.BOLD
TextAttachment.Style.SERIF -> builder.style = StoryTextPost.Style.SERIF
TextAttachment.Style.SCRIPT -> builder.style = StoryTextPost.Style.SCRIPT
TextAttachment.Style.CONDENSED -> builder.style = StoryTextPost.Style.CONDENSED
null -> Unit
}
if (textAttachment.hasTextBackgroundColor()) {
builder.textBackgroundColor = textAttachment.textBackgroundColor
if (textAttachment.textBackgroundColor != null) {
builder.textBackgroundColor = textAttachment.textBackgroundColor!!
}
if (textAttachment.hasTextForegroundColor()) {
builder.textForegroundColor = textAttachment.textForegroundColor
if (textAttachment.textForegroundColor != null) {
builder.textForegroundColor = textAttachment.textForegroundColor!!
}
val chatColorBuilder = ChatColor.newBuilder()
val chatColorBuilder = ChatColor.Builder()
if (textAttachment.hasColor()) {
chatColorBuilder.setSingleColor(ChatColor.SingleColor.newBuilder().setColor(textAttachment.color))
} else if (textAttachment.hasGradient()) {
val gradient = textAttachment.gradient
val linearGradientBuilder = ChatColor.LinearGradient.newBuilder()
linearGradientBuilder.rotation = gradient.angle.toFloat()
if (textAttachment.color != null) {
chatColorBuilder.singleColor(ChatColor.SingleColor.Builder().color(textAttachment.color!!).build())
} else if (textAttachment.gradient != null) {
val gradient = textAttachment.gradient!!
val linearGradientBuilder = ChatColor.LinearGradient.Builder()
linearGradientBuilder.rotation = (gradient.angle ?: 0).toFloat()
if (gradient.positionsList.size > 1 && gradient.colorsList.size == gradient.positionsList.size) {
val positions = ArrayList(gradient.positionsList)
if (gradient.positions.size > 1 && gradient.colors.size == gradient.positions.size) {
val positions = ArrayList(gradient.positions)
positions[0] = 0f
positions[positions.size - 1] = 1f
linearGradientBuilder.addAllColors(ArrayList(gradient.colorsList))
linearGradientBuilder.addAllPositions(positions)
} else if (gradient.colorsList.isNotEmpty()) {
linearGradientBuilder.colors(ArrayList(gradient.colors))
linearGradientBuilder.positions(positions)
} else if (gradient.colors.isNotEmpty()) {
warn("Incoming text story has color / position mismatch. Defaulting to start and end colors.")
linearGradientBuilder.addColors(gradient.colorsList[0])
linearGradientBuilder.addColors(gradient.colorsList[gradient.colorsList.size - 1])
linearGradientBuilder.addAllPositions(listOf(0f, 1f))
linearGradientBuilder.colors(listOf(gradient.colors[0], gradient.colors[gradient.colors.size - 1]))
linearGradientBuilder.positions(listOf(0f, 1f))
} else {
warn("Incoming text story did not have a valid linear gradient.")
linearGradientBuilder.addAllColors(listOf(Color.BLACK, Color.BLACK))
linearGradientBuilder.addAllPositions(listOf(0f, 1f))
linearGradientBuilder.colors(listOf(Color.BLACK, Color.BLACK))
linearGradientBuilder.positions(listOf(0f, 1f))
}
chatColorBuilder.setLinearGradient(linearGradientBuilder)
chatColorBuilder.linearGradient(linearGradientBuilder.build())
}
builder.setBackground(chatColorBuilder)
builder.background(chatColorBuilder.build())
return Base64.encodeBytes(builder.build().toByteArray())
return Base64.encodeBytes(builder.build().encode())
}
}

View File

@@ -1,26 +1,26 @@
package org.thoughtcrime.securesms.messages
import com.google.protobuf.InvalidProtocolBufferException
import org.thoughtcrime.securesms.database.model.databaseprotos.StoryTextPost
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.util.Base64
import org.whispersystems.signalservice.api.messages.SignalServicePreview
import org.whispersystems.signalservice.api.messages.SignalServiceTextAttachment
import java.io.IOException
import java.util.Optional
import kotlin.math.roundToInt
object StorySendUtil {
@JvmStatic
@Throws(InvalidProtocolBufferException::class)
@Throws(IOException::class)
fun deserializeBodyToStoryTextAttachment(message: OutgoingMessage, getPreviewsFor: (OutgoingMessage) -> List<SignalServicePreview>): SignalServiceTextAttachment {
val storyTextPost = StoryTextPost.parseFrom(Base64.decode(message.body))
val storyTextPost = StoryTextPost.ADAPTER.decode(Base64.decode(message.body))
val preview = if (message.linkPreviews.isEmpty()) {
Optional.empty()
} else {
Optional.of(getPreviewsFor(message)[0])
}
return if (storyTextPost.background.hasLinearGradient()) {
return if (storyTextPost.background!!.linearGradient != null) {
SignalServiceTextAttachment.forGradientBackground(
Optional.ofNullable(storyTextPost.body),
Optional.ofNullable(getStyle(storyTextPost.style)),
@@ -28,9 +28,9 @@ object StorySendUtil {
Optional.of(storyTextPost.textBackgroundColor),
preview,
SignalServiceTextAttachment.Gradient(
Optional.of(storyTextPost.background.linearGradient.rotation.roundToInt()),
ArrayList(storyTextPost.background.linearGradient.colorsList),
ArrayList(storyTextPost.background.linearGradient.positionsList)
Optional.of(storyTextPost.background.linearGradient!!.rotation.roundToInt()),
ArrayList(storyTextPost.background.linearGradient.colors),
ArrayList(storyTextPost.background.linearGradient.positions)
)
)
} else {
@@ -40,7 +40,7 @@ object StorySendUtil {
Optional.of(storyTextPost.textForegroundColor),
Optional.of(storyTextPost.textBackgroundColor),
preview,
storyTextPost.background.singleColor.color
storyTextPost.background.singleColor!!.color
)
}
}

View File

@@ -1,8 +1,9 @@
package org.thoughtcrime.securesms.messages
import ProtoUtil.isNotEmpty
import android.content.Context
import com.google.protobuf.ByteString
import com.mobilecoin.lib.exceptions.SerializationException
import okio.ByteString
import org.signal.core.util.Hex
import org.signal.core.util.orNull
import org.signal.libsignal.protocol.util.Pair
@@ -54,6 +55,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.messages.MessageContentProcessor.Companion.log
import org.thoughtcrime.securesms.messages.MessageContentProcessor.Companion.warn
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.expireTimerDuration
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.groupId
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.groupMasterKey
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.hasGroupContext
@@ -100,27 +102,28 @@ 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.storage.StorageKey
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.StoryMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.Blocked
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.CallLinkUpdate
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.CallLogEvent
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.Configuration
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.FetchLatest
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.MessageRequestResponse
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.Read
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.Request
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.Sent
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.StickerPackOperation
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.ViewOnceOpen
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.EditMessage
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.StoryMessage
import org.whispersystems.signalservice.internal.push.SyncMessage
import org.whispersystems.signalservice.internal.push.SyncMessage.Blocked
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLinkUpdate
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLogEvent
import org.whispersystems.signalservice.internal.push.SyncMessage.Configuration
import org.whispersystems.signalservice.internal.push.SyncMessage.FetchLatest
import org.whispersystems.signalservice.internal.push.SyncMessage.MessageRequestResponse
import org.whispersystems.signalservice.internal.push.SyncMessage.Read
import org.whispersystems.signalservice.internal.push.SyncMessage.Request
import org.whispersystems.signalservice.internal.push.SyncMessage.Sent
import org.whispersystems.signalservice.internal.push.SyncMessage.StickerPackOperation
import org.whispersystems.signalservice.internal.push.SyncMessage.ViewOnceOpen
import org.whispersystems.signalservice.internal.push.Verified
import java.io.IOException
import java.util.Optional
import java.util.UUID
import kotlin.time.Duration.Companion.seconds
import kotlin.time.Duration
object SyncMessageProcessor {
@@ -132,27 +135,27 @@ object SyncMessageProcessor {
metadata: EnvelopeMetadata,
earlyMessageCacheEntry: EarlyMessageCacheEntry?
) {
val syncMessage = content.syncMessage
val syncMessage = content.syncMessage!!
when {
syncMessage.hasSent() -> handleSynchronizeSentMessage(context, envelope, content, metadata, syncMessage.sent, senderRecipient, earlyMessageCacheEntry)
syncMessage.hasRequest() -> handleSynchronizeRequestMessage(context, syncMessage.request, envelope.timestamp)
syncMessage.readList.isNotEmpty() -> handleSynchronizeReadMessage(context, syncMessage.readList, envelope.timestamp, earlyMessageCacheEntry)
syncMessage.viewedList.isNotEmpty() -> handleSynchronizeViewedMessage(context, syncMessage.viewedList, envelope.timestamp)
syncMessage.hasViewOnceOpen() -> handleSynchronizeViewOnceOpenMessage(context, syncMessage.viewOnceOpen, envelope.timestamp, earlyMessageCacheEntry)
syncMessage.hasVerified() -> handleSynchronizeVerifiedMessage(context, syncMessage.verified)
syncMessage.stickerPackOperationList.isNotEmpty() -> handleSynchronizeStickerPackOperation(syncMessage.stickerPackOperationList, envelope.timestamp)
syncMessage.hasConfiguration() -> handleSynchronizeConfigurationMessage(context, syncMessage.configuration, envelope.timestamp)
syncMessage.hasBlocked() -> handleSynchronizeBlockedListMessage(syncMessage.blocked)
syncMessage.hasFetchLatest() && syncMessage.fetchLatest.hasType() -> handleSynchronizeFetchMessage(syncMessage.fetchLatest.type, envelope.timestamp)
syncMessage.hasMessageRequestResponse() -> handleSynchronizeMessageRequestResponse(syncMessage.messageRequestResponse, envelope.timestamp)
syncMessage.hasOutgoingPayment() -> handleSynchronizeOutgoingPayment(syncMessage.outgoingPayment, envelope.timestamp)
syncMessage.hasKeys() && syncMessage.keys.hasStorageService() -> handleSynchronizeKeys(syncMessage.keys.storageService, envelope.timestamp)
syncMessage.hasContacts() -> handleSynchronizeContacts(syncMessage.contacts, envelope.timestamp)
syncMessage.hasCallEvent() -> handleSynchronizeCallEvent(syncMessage.callEvent, envelope.timestamp)
syncMessage.hasCallLinkUpdate() -> handleSynchronizeCallLink(syncMessage.callLinkUpdate, envelope.timestamp)
syncMessage.hasCallLogEvent() -> handleSynchronizeCallLogEvent(syncMessage.callLogEvent, envelope.timestamp)
else -> warn(envelope.timestamp, "Contains no known sync types...")
syncMessage.sent != null -> handleSynchronizeSentMessage(context, envelope, content, metadata, syncMessage.sent!!, senderRecipient, earlyMessageCacheEntry)
syncMessage.request != null -> handleSynchronizeRequestMessage(context, syncMessage.request!!, envelope.timestamp!!)
syncMessage.read.isNotEmpty() -> handleSynchronizeReadMessage(context, syncMessage.read, envelope.timestamp!!, earlyMessageCacheEntry)
syncMessage.viewed.isNotEmpty() -> handleSynchronizeViewedMessage(context, syncMessage.viewed, envelope.timestamp!!)
syncMessage.viewOnceOpen != null -> handleSynchronizeViewOnceOpenMessage(context, syncMessage.viewOnceOpen!!, envelope.timestamp!!, earlyMessageCacheEntry)
syncMessage.verified != null -> handleSynchronizeVerifiedMessage(context, syncMessage.verified!!)
syncMessage.stickerPackOperation.isNotEmpty() -> handleSynchronizeStickerPackOperation(syncMessage.stickerPackOperation, envelope.timestamp!!)
syncMessage.configuration != null -> handleSynchronizeConfigurationMessage(context, syncMessage.configuration!!, envelope.timestamp!!)
syncMessage.blocked != null -> handleSynchronizeBlockedListMessage(syncMessage.blocked!!)
syncMessage.fetchLatest?.type != null -> handleSynchronizeFetchMessage(syncMessage.fetchLatest!!.type!!, envelope.timestamp!!)
syncMessage.messageRequestResponse != null -> handleSynchronizeMessageRequestResponse(syncMessage.messageRequestResponse!!, envelope.timestamp!!)
syncMessage.outgoingPayment != null -> handleSynchronizeOutgoingPayment(syncMessage.outgoingPayment!!, envelope.timestamp!!)
syncMessage.keys?.storageService != null -> handleSynchronizeKeys(syncMessage.keys!!.storageService!!, envelope.timestamp!!)
syncMessage.contacts != null -> handleSynchronizeContacts(syncMessage.contacts!!, envelope.timestamp!!)
syncMessage.callEvent != null -> handleSynchronizeCallEvent(syncMessage.callEvent!!, envelope.timestamp!!)
syncMessage.callLinkUpdate != null -> handleSynchronizeCallLink(syncMessage.callLinkUpdate!!, envelope.timestamp!!)
syncMessage.callLogEvent != null -> handleSynchronizeCallLogEvent(syncMessage.callLogEvent!!, envelope.timestamp!!)
else -> warn(envelope.timestamp!!, "Contains no known sync types...")
}
}
@@ -166,54 +169,64 @@ object SyncMessageProcessor {
senderRecipient: Recipient,
earlyMessageCacheEntry: EarlyMessageCacheEntry?
) {
log(envelope.timestamp, "Processing sent transcript for message with ID ${sent.timestamp}")
log(envelope.timestamp!!, "Processing sent transcript for message with ID ${sent.timestamp!!}")
try {
if (sent.hasStoryMessage() || sent.storyMessageRecipientsList.isNotEmpty()) {
if (sent.storyMessage != null || sent.storyMessageRecipients.isNotEmpty()) {
handleSynchronizeSentStoryMessage(envelope, sent)
return
}
if (sent.hasEditMessage()) {
if (sent.editMessage != null) {
handleSynchronizeSentEditMessage(context, envelope, sent, senderRecipient, earlyMessageCacheEntry)
return
}
val dataMessage = sent.message
val groupId: GroupId.V2? = if (dataMessage.hasGroupContext) GroupId.v2(dataMessage.groupV2.groupMasterKey) else null
if (sent.isRecipientUpdate == true) {
handleGroupRecipientUpdate(sent, envelope.timestamp!!)
return
}
val dataMessage = if (sent.message != null) {
sent.message!!
} else {
warn(envelope.timestamp!!, "Sync message missing nested message to sync")
return
}
val groupId: GroupId.V2? = if (dataMessage.hasGroupContext) GroupId.v2(dataMessage.groupV2!!.groupMasterKey) else null
if (groupId != null) {
if (MessageContentProcessor.handleGv2PreProcessing(context, envelope.timestamp, content, metadata, groupId, dataMessage.groupV2, senderRecipient) == MessageContentProcessor.Gv2PreProcessResult.IGNORE) {
if (MessageContentProcessor.handleGv2PreProcessing(context, envelope.timestamp!!, content, metadata, groupId, dataMessage.groupV2!!, senderRecipient) == MessageContentProcessor.Gv2PreProcessResult.IGNORE) {
return
}
}
var threadId: Long = -1
when {
sent.isRecipientUpdate -> handleGroupRecipientUpdate(sent, envelope.timestamp)
dataMessage.isEndSession -> threadId = handleSynchronizeSentEndSessionMessage(context, sent, envelope.timestamp)
dataMessage.isEndSession -> threadId = handleSynchronizeSentEndSessionMessage(context, sent, envelope.timestamp!!)
dataMessage.isGroupV2Update -> {
handleSynchronizeSentGv2Update(context, envelope, sent)
threadId = SignalDatabase.threads.getOrCreateThreadIdFor(getSyncMessageDestination(sent))
}
dataMessage.hasGroupCallUpdate() -> DataMessageProcessor.handleGroupCallUpdateMessage(envelope, dataMessage, senderRecipient.id, groupId)
dataMessage.isEmptyGroupV2Message -> warn(envelope.timestamp, "Empty GV2 message! Doing nothing.")
dataMessage.groupCallUpdate != null -> DataMessageProcessor.handleGroupCallUpdateMessage(envelope, dataMessage, senderRecipient.id, groupId)
dataMessage.isEmptyGroupV2Message -> warn(envelope.timestamp!!, "Empty GV2 message! Doing nothing.")
dataMessage.isExpirationUpdate -> threadId = handleSynchronizeSentExpirationUpdate(sent)
dataMessage.hasStoryContext() -> threadId = handleSynchronizeSentStoryReply(sent, envelope.timestamp)
dataMessage.hasReaction() -> {
dataMessage.storyContext != null -> threadId = handleSynchronizeSentStoryReply(sent, envelope.timestamp!!)
dataMessage.reaction != null -> {
DataMessageProcessor.handleReaction(context, envelope, dataMessage, senderRecipient.id, earlyMessageCacheEntry)
threadId = SignalDatabase.threads.getOrCreateThreadIdFor(getSyncMessageDestination(sent))
}
dataMessage.hasRemoteDelete -> DataMessageProcessor.handleRemoteDelete(context, envelope, dataMessage, senderRecipient.id, earlyMessageCacheEntry)
dataMessage.isMediaMessage -> threadId = handleSynchronizeSentMediaMessage(context, sent, envelope.timestamp)
else -> threadId = handleSynchronizeSentTextMessage(sent, envelope.timestamp)
dataMessage.isMediaMessage -> threadId = handleSynchronizeSentMediaMessage(context, sent, envelope.timestamp!!)
else -> threadId = handleSynchronizeSentTextMessage(sent, envelope.timestamp!!)
}
if (groupId != null && SignalDatabase.groups.isUnknownGroup(groupId)) {
DataMessageProcessor.handleUnknownGroupMessage(envelope.timestamp, dataMessage.groupV2)
DataMessageProcessor.handleUnknownGroupMessage(envelope.timestamp!!, dataMessage.groupV2!!)
}
if (dataMessage.hasProfileKey()) {
if (dataMessage.profileKey.isNotEmpty()) {
val recipient: Recipient = getSyncMessageDestination(sent)
if (!recipient.isSystemContact && !recipient.isProfileSharing) {
SignalDatabase.recipients.setProfileSharing(recipient.id, true)
@@ -226,11 +239,11 @@ object SyncMessageProcessor {
}
if (SignalStore.rateLimit().needsRecaptcha()) {
log(envelope.timestamp, "Got a sent transcript while in reCAPTCHA mode. Assuming we're good to message again.")
log(envelope.timestamp!!, "Got a sent transcript while in reCAPTCHA mode. Assuming we're good to message again.")
RateLimitUtil.retryAllRateLimitedMessages(context)
}
ApplicationDependencies.getMessageNotifier().setLastDesktopActivityTimestamp(sent.timestamp)
ApplicationDependencies.getMessageNotifier().setLastDesktopActivityTimestamp(sent.timestamp!!)
} catch (e: MmsException) {
throw StorageFailedException(e, metadata.sourceServiceId.toString(), metadata.sourceDeviceId)
}
@@ -238,9 +251,9 @@ object SyncMessageProcessor {
private fun getSyncMessageDestination(message: Sent): Recipient {
return if (message.message.hasGroupContext) {
Recipient.externalPossiblyMigratedGroup(GroupId.v2(message.message.groupV2.groupMasterKey))
Recipient.externalPossiblyMigratedGroup(GroupId.v2(message.message!!.groupV2!!.groupMasterKey))
} else {
Recipient.externalPush(SignalServiceAddress(ServiceId.parseOrThrow(message.destinationServiceId), message.destinationE164))
Recipient.externalPush(SignalServiceAddress(ServiceId.parseOrThrow(message.destinationServiceId!!), message.destinationE164))
}
}
@@ -252,30 +265,32 @@ object SyncMessageProcessor {
senderRecipient: Recipient,
earlyMessageCacheEntry: EarlyMessageCacheEntry?
) {
val targetSentTimestamp: Long = sent.editMessage.targetSentTimestamp
val editMessage: EditMessage = sent.editMessage!!
val targetSentTimestamp: Long = editMessage.targetSentTimestamp!!
val targetMessage: MessageRecord? = SignalDatabase.messages.getMessageFor(targetSentTimestamp, senderRecipient.id)
val senderRecipientId = senderRecipient.id
if (targetMessage == null) {
warn(envelope.timestamp, "[handleSynchronizeSentEditMessage] Could not find matching message! targetTimestamp: $targetSentTimestamp author: $senderRecipientId")
warn(envelope.timestamp!!, "[handleSynchronizeSentEditMessage] Could not find matching message! targetTimestamp: $targetSentTimestamp author: $senderRecipientId")
if (earlyMessageCacheEntry != null) {
ApplicationDependencies.getEarlyMessageCache().store(senderRecipientId, targetSentTimestamp, earlyMessageCacheEntry)
PushProcessEarlyMessagesJob.enqueue()
}
} else if (MessageConstraintsUtil.isValidEditMessageReceive(targetMessage, senderRecipient, envelope.serverTimestamp)) {
val message = sent.editMessage.dataMessage
} else if (MessageConstraintsUtil.isValidEditMessageReceive(targetMessage, senderRecipient, envelope.serverTimestamp!!)) {
val message: DataMessage = editMessage.dataMessage!!
val toRecipient: Recipient = if (message.hasGroupContext) {
Recipient.externalPossiblyMigratedGroup(GroupId.v2(message.groupV2.groupMasterKey))
Recipient.externalPossiblyMigratedGroup(GroupId.v2(message.groupV2!!.groupMasterKey))
} else {
Recipient.externalPush(ServiceId.parseOrThrow(sent.destinationServiceId))
Recipient.externalPush(ServiceId.parseOrThrow(sent.destinationServiceId!!))
}
if (message.isMediaMessage) {
handleSynchronizeSentEditMediaMessage(context, targetMessage, toRecipient, sent, message, envelope.timestamp)
handleSynchronizeSentEditMediaMessage(context, targetMessage, toRecipient, sent, message, envelope.timestamp!!)
} else {
handleSynchronizeSentEditTextMessage(targetMessage, toRecipient, sent, message, envelope.timestamp)
handleSynchronizeSentEditTextMessage(targetMessage, toRecipient, sent, message, envelope.timestamp!!)
}
} else {
warn(envelope.timestamp, "[handleSynchronizeSentEditMessage] Invalid message edit! editTime: ${envelope.serverTimestamp}, targetTime: ${targetMessage.serverTimestamp}, sendAuthor: $senderRecipientId, targetAuthor: ${targetMessage.fromRecipient.id}")
warn(envelope.timestamp!!, "[handleSynchronizeSentEditMessage] Invalid message edit! editTime: ${envelope.serverTimestamp}, targetTime: ${targetMessage.serverTimestamp}, sendAuthor: $senderRecipientId, targetAuthor: ${targetMessage.fromRecipient.id}")
}
}
@@ -289,7 +304,7 @@ object SyncMessageProcessor {
log(envelopeTimestamp, "Synchronize sent edit text message for message: ${targetMessage.id}")
val body = message.body ?: ""
val bodyRanges = message.bodyRangesList.filterNot { it.hasMentionAci() }.toBodyRangeList()
val bodyRanges = message.bodyRanges.filter { it.mentionAci == null }.toBodyRangeList()
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(toRecipient)
val isGroup = toRecipient.isGroup
@@ -299,7 +314,7 @@ object SyncMessageProcessor {
val outgoingMessage = OutgoingMessage(
recipient = toRecipient,
body = body,
timestamp = sent.timestamp,
timestamp = sent.timestamp!!,
expiresIn = targetMessage.expiresIn,
isSecure = true,
bodyRanges = bodyRanges,
@@ -311,7 +326,7 @@ object SyncMessageProcessor {
} else {
val outgoingTextMessage = OutgoingMessage(
threadRecipient = toRecipient,
sentTimeMillis = sent.timestamp,
sentTimeMillis = sent.timestamp!!,
body = body,
expiresIn = targetMessage.expiresIn,
isUrgent = true,
@@ -330,8 +345,8 @@ object SyncMessageProcessor {
}
if (toRecipient.isSelf) {
SignalDatabase.messages.incrementDeliveryReceiptCount(sent.timestamp, toRecipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementReadReceiptCount(sent.timestamp, toRecipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementDeliveryReceiptCount(sent.timestamp!!, toRecipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementReadReceiptCount(sent.timestamp!!, toRecipient.id, System.currentTimeMillis())
}
}
@@ -347,12 +362,12 @@ object SyncMessageProcessor {
val quote: QuoteModel? = DataMessageProcessor.getValidatedQuote(context, envelopeTimestamp, message)
val sharedContacts: List<Contact> = DataMessageProcessor.getContacts(message)
val previews: List<LinkPreview> = DataMessageProcessor.getLinkPreviews(message.previewList, message.body ?: "", false)
val mentions: List<Mention> = DataMessageProcessor.getMentions(message.bodyRangesList)
val viewOnce: Boolean = message.isViewOnce
val bodyRanges: BodyRangeList? = message.bodyRangesList.toBodyRangeList()
val previews: List<LinkPreview> = DataMessageProcessor.getLinkPreviews(message.preview, message.body ?: "", false)
val mentions: List<Mention> = DataMessageProcessor.getMentions(message.bodyRanges)
val viewOnce: Boolean = message.isViewOnce == true
val bodyRanges: BodyRangeList? = message.bodyRanges.toBodyRangeList()
val syncAttachments = message.attachmentsList.toPointersWithinLimit().filter {
val syncAttachments = message.attachments.toPointersWithinLimit().filter {
MediaUtil.SlideType.LONG_TEXT == MediaUtil.getSlideTypeFromContentType(it.contentType)
}
@@ -363,7 +378,7 @@ object SyncMessageProcessor {
recipient = toRecipient,
body = message.body ?: "",
attachments = syncAttachments.ifEmpty { (targetMessage as? MediaMmsMessageRecord)?.slideDeck?.asAttachments() ?: emptyList() },
timestamp = sent.timestamp,
timestamp = sent.timestamp!!,
expiresIn = targetMessage.expiresIn,
viewOnce = viewOnce,
quote = quote,
@@ -394,8 +409,8 @@ object SyncMessageProcessor {
ApplicationDependencies.getExpiringMessageManager().scheduleDeletion(messageId, true, targetMessage.expireStarted, targetMessage.expireStarted)
}
if (toRecipient.isSelf) {
SignalDatabase.messages.incrementDeliveryReceiptCount(sent.timestamp, toRecipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementReadReceiptCount(sent.timestamp, toRecipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementDeliveryReceiptCount(sent.timestamp!!, toRecipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementReadReceiptCount(sent.timestamp!!, toRecipient.id, System.currentTimeMillis())
}
SignalDatabase.messages.setTransactionSuccessful()
} finally {
@@ -412,45 +427,45 @@ object SyncMessageProcessor {
@Throws(MmsException::class)
private fun handleSynchronizeSentStoryMessage(envelope: Envelope, sent: Sent) {
log(envelope.timestamp, "Synchronize sent story message for " + sent.timestamp)
log(envelope.timestamp!!, "Synchronize sent story message for " + sent.timestamp)
val manifest = SentStorySyncManifest.fromRecipientsSet(sent.storyMessageRecipientsList)
val manifest = SentStorySyncManifest.fromRecipientsSet(sent.storyMessageRecipients)
if (sent.isRecipientUpdate) {
log(envelope.timestamp, "Processing recipient update for story message and exiting...")
SignalDatabase.storySends.applySentStoryManifest(manifest, sent.timestamp)
if (sent.isRecipientUpdate == true) {
log(envelope.timestamp!!, "Processing recipient update for story message and exiting...")
SignalDatabase.storySends.applySentStoryManifest(manifest, sent.timestamp!!)
return
}
val storyMessage: StoryMessage = sent.storyMessage
val storyMessage: StoryMessage = sent.storyMessage!!
val distributionIds: Set<DistributionId> = manifest.getDistributionIdSet()
val groupId: GroupId.V2? = storyMessage.group.groupId
val groupId: GroupId.V2? = storyMessage.group?.groupId
val textStoryBody: String? = StoryMessageProcessor.serializeTextAttachment(storyMessage)
val bodyRanges: BodyRangeList? = storyMessage.bodyRangesList.toBodyRangeList()
val bodyRanges: BodyRangeList? = storyMessage.bodyRanges.toBodyRangeList()
val storyType: StoryType = storyMessage.type
val linkPreviews: List<LinkPreview> = DataMessageProcessor.getLinkPreviews(
previews = if (storyMessage.textAttachment.hasPreview()) listOf(storyMessage.textAttachment.preview) else emptyList(),
previews = listOfNotNull(storyMessage.textAttachment?.preview),
body = "",
isStoryEmbed = true
)
val attachments: List<Attachment> = if (storyMessage.hasFileAttachment()) listOfNotNull(storyMessage.fileAttachment.toPointer()) else emptyList()
val attachments: List<Attachment> = listOfNotNull(storyMessage.fileAttachment?.toPointer())
for (distributionId in distributionIds) {
val distributionRecipientId = SignalDatabase.distributionLists.getOrCreateByDistributionId(distributionId, manifest)
val distributionListRecipient = Recipient.resolved(distributionRecipientId)
insertSentStoryMessage(sent, distributionListRecipient, null, textStoryBody, attachments, sent.timestamp, storyType, linkPreviews, bodyRanges)
insertSentStoryMessage(sent, distributionListRecipient, null, textStoryBody, attachments, sent.timestamp!!, storyType, linkPreviews, bodyRanges)
}
if (groupId != null) {
val groupRecipient: Optional<RecipientId> = SignalDatabase.recipients.getByGroupId(groupId)
if (groupRecipient.isPresent) {
insertSentStoryMessage(sent, Recipient.resolved(groupRecipient.get()), groupId, textStoryBody, attachments, sent.timestamp, storyType, linkPreviews, bodyRanges)
insertSentStoryMessage(sent, Recipient.resolved(groupRecipient.get()), groupId, textStoryBody, attachments, sent.timestamp!!, storyType, linkPreviews, bodyRanges)
}
}
SignalDatabase.storySends.applySentStoryManifest(manifest, sent.timestamp)
SignalDatabase.storySends.applySentStoryManifest(manifest, sent.timestamp!!)
}
@Throws(MmsException::class)
@@ -503,8 +518,8 @@ object SyncMessageProcessor {
attachments = allAttachments.filterNot { it.isSticker }
if (recipient.isSelf) {
SignalDatabase.messages.incrementDeliveryReceiptCount(sent.timestamp, recipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementReadReceiptCount(sent.timestamp, recipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementDeliveryReceiptCount(sent.timestamp!!, recipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementReadReceiptCount(sent.timestamp!!, recipient.id, System.currentTimeMillis())
}
SignalDatabase.messages.setTransactionSuccessful()
@@ -527,7 +542,7 @@ object SyncMessageProcessor {
return
}
val record = SignalDatabase.messages.getMessageFor(sent.timestamp, Recipient.self().id)
val record = SignalDatabase.messages.getMessageFor(sent.timestamp!!, Recipient.self().id)
if (record == null) {
warn("Got recipient update for non-existing message! Skipping.")
return
@@ -543,9 +558,9 @@ object SyncMessageProcessor {
for (messageRecipientId in messageRecipientIds.keys) {
if ((localReceipts[messageRecipientId] ?: GroupReceiptTable.STATUS_UNKNOWN) < GroupReceiptTable.STATUS_UNDELIVERED) {
SignalDatabase.groupReceipts.update(messageRecipientId, messageId, GroupReceiptTable.STATUS_UNDELIVERED, sent.timestamp)
SignalDatabase.groupReceipts.update(messageRecipientId, messageId, GroupReceiptTable.STATUS_UNDELIVERED, sent.timestamp!!)
} else if (!localReceipts.containsKey(messageRecipientId)) {
SignalDatabase.groupReceipts.insert(listOf(messageRecipientId), messageId, GroupReceiptTable.STATUS_UNDELIVERED, sent.timestamp)
SignalDatabase.groupReceipts.insert(listOf(messageRecipientId), messageId, GroupReceiptTable.STATUS_UNDELIVERED, sent.timestamp!!)
}
}
@@ -561,9 +576,9 @@ object SyncMessageProcessor {
for (messageRecipientId in messageRecipientIds.keys) {
if ((localReceipts[messageRecipientId] ?: GroupReceiptTable.STATUS_UNKNOWN) < GroupReceiptTable.STATUS_UNDELIVERED) {
SignalDatabase.groupReceipts.update(messageRecipientId, messageId, GroupReceiptTable.STATUS_UNDELIVERED, sent.timestamp)
SignalDatabase.groupReceipts.update(messageRecipientId, messageId, GroupReceiptTable.STATUS_UNDELIVERED, sent.timestamp!!)
} else if (!localReceipts.containsKey(messageRecipientId)) {
SignalDatabase.groupReceipts.insert(listOf(messageRecipientId), messageId, GroupReceiptTable.STATUS_UNDELIVERED, sent.timestamp)
SignalDatabase.groupReceipts.insert(listOf(messageRecipientId), messageId, GroupReceiptTable.STATUS_UNDELIVERED, sent.timestamp!!)
}
}
@@ -577,7 +592,7 @@ object SyncMessageProcessor {
log(envelopeTimestamp, "Synchronize end session message.")
val recipient: Recipient = getSyncMessageDestination(sent)
val outgoingEndSessionMessage: OutgoingMessage = endSessionMessage(recipient, sent.timestamp)
val outgoingEndSessionMessage: OutgoingMessage = endSessionMessage(recipient, sent.timestamp!!)
val threadId: Long = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
if (!recipient.isGroup) {
@@ -599,48 +614,56 @@ object SyncMessageProcessor {
@Throws(IOException::class, GroupChangeBusyException::class)
private fun handleSynchronizeSentGv2Update(context: Context, envelope: Envelope, sent: Sent) {
log(envelope.timestamp, "Synchronize sent GV2 update for message with timestamp " + sent.timestamp)
log(envelope.timestamp!!, "Synchronize sent GV2 update for message with timestamp " + sent.timestamp!!)
val dataMessage: DataMessage = sent.message
val groupId: GroupId.V2? = dataMessage.groupV2.groupId
val dataMessage: DataMessage = sent.message!!
val groupId: GroupId.V2? = dataMessage.groupV2?.groupId
if (MessageContentProcessor.updateGv2GroupFromServerOrP2PChange(context, envelope.timestamp, dataMessage.groupV2, SignalDatabase.groups.getGroup(GroupId.v2(dataMessage.groupV2.groupMasterKey))) == null) {
log(envelope.timestamp, "Ignoring GV2 message for group we are not currently in $groupId")
if (groupId == null) {
warn(envelope.timestamp!!, "GV2 update missing group id")
return
}
if (MessageContentProcessor.updateGv2GroupFromServerOrP2PChange(context, envelope.timestamp!!, dataMessage.groupV2!!, SignalDatabase.groups.getGroup(groupId)) == null) {
log(envelope.timestamp!!, "Ignoring GV2 message for group we are not currently in $groupId")
}
}
@Throws(MmsException::class)
private fun handleSynchronizeSentExpirationUpdate(sent: Sent, sideEffect: Boolean = false): Long {
log(sent.timestamp, "Synchronize sent expiration update.")
log(sent.timestamp!!, "Synchronize sent expiration update.")
val groupId: GroupId? = getSyncMessageDestination(sent).groupId.orNull()
if (groupId != null && groupId.isV2) {
warn(sent.timestamp, "Expiration update received for GV2. Ignoring.")
warn(sent.timestamp!!, "Expiration update received for GV2. Ignoring.")
return -1
}
val recipient: Recipient = getSyncMessageDestination(sent)
val expirationUpdateMessage: OutgoingMessage = expirationUpdateMessage(recipient, if (sideEffect) sent.timestamp - 1 else sent.timestamp, sent.message.expireTimer.seconds.inWholeMilliseconds)
val expirationUpdateMessage: OutgoingMessage = expirationUpdateMessage(recipient, if (sideEffect) sent.timestamp!! - 1 else sent.timestamp!!, sent.message!!.expireTimerDuration.inWholeMilliseconds)
val threadId: Long = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
val messageId: Long = SignalDatabase.messages.insertMessageOutbox(expirationUpdateMessage, threadId, false, null)
SignalDatabase.messages.markAsSent(messageId, true)
SignalDatabase.recipients.setExpireMessages(recipient.id, sent.message.expireTimer)
SignalDatabase.recipients.setExpireMessages(recipient.id, sent.message!!.expireTimerDuration.inWholeSeconds.toInt())
return threadId
}
@Throws(MmsException::class, BadGroupIdException::class)
private fun handleSynchronizeSentStoryReply(sent: Sent, envelopeTimestamp: Long): Long {
log(envelopeTimestamp, "Synchronize sent story reply for " + sent.timestamp)
log(envelopeTimestamp, "Synchronize sent story reply for " + sent.timestamp!!)
try {
val reaction: DataMessage.Reaction = sent.message.reaction
val dataMessage: DataMessage = sent.message!!
val storyContext: DataMessage.StoryContext = dataMessage.storyContext!!
val reaction: DataMessage.Reaction? = dataMessage.reaction
val parentStoryId: ParentStoryId
val authorServiceId: ServiceId = ServiceId.parseOrThrow(sent.message.storyContext.authorAci)
val sentTimestamp: Long = sent.message.storyContext.sentTimestamp
val authorServiceId: ServiceId = ServiceId.parseOrThrow(storyContext.authorAci!!)
val sentTimestamp: Long = storyContext.sentTimestamp!!
val recipient: Recipient = getSyncMessageDestination(sent)
var quoteModel: QuoteModel? = null
var expiresInMillis = 0L
@@ -651,16 +674,16 @@ object SyncMessageProcessor {
val groupStory: Boolean = threadRecipientId != null && (SignalDatabase.groups.getGroup(threadRecipientId).orNull()?.isActive ?: false)
var bodyRanges: BodyRangeList? = null
val body: String? = if (sent.message.hasReaction() && EmojiUtil.isEmoji(reaction.emoji)) {
reaction.emoji
} else if (sent.message.hasBody()) {
bodyRanges = sent.message.bodyRangesList.toBodyRangeList()
sent.message.body
val body: String? = if (EmojiUtil.isEmoji(reaction?.emoji)) {
reaction!!.emoji
} else if (dataMessage.body != null) {
bodyRanges = dataMessage.bodyRanges.toBodyRangeList()
dataMessage.body
} else {
null
}
if (sent.message.hasGroupContext) {
if (dataMessage.hasGroupContext) {
parentStoryId = GroupReply(storyMessageId)
} else if (groupStory || story.storyType.isStoryWithReplies) {
parentStoryId = DirectReply(storyMessageId)
@@ -672,7 +695,7 @@ object SyncMessageProcessor {
bodyBodyRanges = story.messageRanges
}
quoteModel = QuoteModel(sentTimestamp, storyAuthorRecipient, quoteBody, false, story.slideDeck.asAttachments(), emptyList(), QuoteModel.Type.NORMAL, bodyBodyRanges)
expiresInMillis = sent.message.expireTimer.seconds.inWholeMilliseconds
expiresInMillis = dataMessage.expireTimerDuration.inWholeMilliseconds
} else {
warn(envelopeTimestamp, "Story has replies disabled. Dropping reply.")
return -1L
@@ -681,17 +704,17 @@ object SyncMessageProcessor {
val mediaMessage = OutgoingMessage(
recipient = recipient,
body = body,
timestamp = sent.timestamp,
timestamp = sent.timestamp!!,
expiresIn = expiresInMillis,
parentStoryId = parentStoryId,
isStoryReaction = sent.message.hasReaction(),
isStoryReaction = reaction != null,
quote = quoteModel,
mentions = DataMessageProcessor.getMentions(sent.message.bodyRangesList),
mentions = DataMessageProcessor.getMentions(dataMessage.bodyRanges),
bodyRanges = bodyRanges,
isSecure = true
)
if (recipient.expiresInSeconds != sent.message.expireTimer) {
if (recipient.expiresInSeconds != dataMessage.expireTimerDuration.inWholeSeconds.toInt()) {
handleSynchronizeSentExpirationUpdate(sent, sideEffect = true)
}
@@ -708,16 +731,16 @@ object SyncMessageProcessor {
}
SignalDatabase.messages.markAsSent(messageId, true)
if (sent.message.expireTimer > 0) {
SignalDatabase.messages.markExpireStarted(messageId, sent.expirationStartTimestamp)
if (dataMessage.expireTimerDuration > Duration.ZERO) {
SignalDatabase.messages.markExpireStarted(messageId, sent.expirationStartTimestamp ?: 0)
ApplicationDependencies
.getExpiringMessageManager()
.scheduleDeletion(messageId, true, sent.expirationStartTimestamp, sent.message.expireTimer.seconds.inWholeMilliseconds)
.scheduleDeletion(messageId, true, sent.expirationStartTimestamp ?: 0, dataMessage.expireTimerDuration.inWholeMilliseconds)
}
if (recipient.isSelf) {
SignalDatabase.messages.incrementDeliveryReceiptCount(sent.timestamp, recipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementReadReceiptCount(sent.timestamp, recipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementDeliveryReceiptCount(sent.timestamp!!, recipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementReadReceiptCount(sent.timestamp!!, recipient.id, System.currentTimeMillis())
}
SignalDatabase.messages.setTransactionSuccessful()
} finally {
@@ -733,25 +756,26 @@ object SyncMessageProcessor {
@Throws(MmsException::class, BadGroupIdException::class)
private fun handleSynchronizeSentMediaMessage(context: Context, sent: Sent, envelopeTimestamp: Long): Long {
log(envelopeTimestamp, "Synchronize sent media message for " + sent.timestamp)
log(envelopeTimestamp, "Synchronize sent media message for " + sent.timestamp!!)
val recipient: Recipient = getSyncMessageDestination(sent)
val quote: QuoteModel? = DataMessageProcessor.getValidatedQuote(context, envelopeTimestamp, sent.message)
val sticker: Attachment? = DataMessageProcessor.getStickerAttachment(envelopeTimestamp, sent.message)
val sharedContacts: List<Contact> = DataMessageProcessor.getContacts(sent.message)
val previews: List<LinkPreview> = DataMessageProcessor.getLinkPreviews(sent.message.previewList, sent.message.body ?: "", false)
val mentions: List<Mention> = DataMessageProcessor.getMentions(sent.message.bodyRangesList)
val giftBadge: GiftBadge? = if (sent.message.hasGiftBadge()) GiftBadge.newBuilder().setRedemptionToken(sent.message.giftBadge.receiptCredentialPresentation).build() else null
val viewOnce: Boolean = sent.message.isViewOnce
val bodyRanges: BodyRangeList? = sent.message.bodyRangesList.toBodyRangeList()
val syncAttachments: List<Attachment> = listOfNotNull(sticker) + if (viewOnce) listOf<Attachment>(TombstoneAttachment(MediaUtil.VIEW_ONCE, false)) else sent.message.attachmentsList.toPointersWithinLimit()
val dataMessage: DataMessage = sent.message!!
val quote: QuoteModel? = DataMessageProcessor.getValidatedQuote(context, envelopeTimestamp, dataMessage)
val sticker: Attachment? = DataMessageProcessor.getStickerAttachment(envelopeTimestamp, dataMessage)
val sharedContacts: List<Contact> = DataMessageProcessor.getContacts(dataMessage)
val previews: List<LinkPreview> = DataMessageProcessor.getLinkPreviews(dataMessage.preview, dataMessage.body ?: "", false)
val mentions: List<Mention> = DataMessageProcessor.getMentions(dataMessage.bodyRanges)
val giftBadge: GiftBadge? = if (dataMessage.giftBadge?.receiptCredentialPresentation != null) GiftBadge.Builder().redemptionToken(dataMessage.giftBadge!!.receiptCredentialPresentation!!).build() else null
val viewOnce: Boolean = dataMessage.isViewOnce == true
val bodyRanges: BodyRangeList? = dataMessage.bodyRanges.toBodyRangeList()
val syncAttachments: List<Attachment> = listOfNotNull(sticker) + if (viewOnce) listOf<Attachment>(TombstoneAttachment(MediaUtil.VIEW_ONCE, false)) else dataMessage.attachments.toPointersWithinLimit()
val mediaMessage = OutgoingMessage(
recipient = recipient,
body = sent.message.body ?: "",
body = dataMessage.body ?: "",
attachments = syncAttachments,
timestamp = sent.timestamp,
expiresIn = sent.message.expireTimer.seconds.inWholeMilliseconds,
timestamp = sent.timestamp!!,
expiresIn = dataMessage.expireTimerDuration.inWholeMilliseconds,
viewOnce = viewOnce,
quote = quote,
contacts = sharedContacts,
@@ -762,7 +786,7 @@ object SyncMessageProcessor {
isSecure = true
)
if (recipient.expiresInSeconds != sent.message.expireTimer) {
if (recipient.expiresInSeconds != dataMessage.expireTimerDuration.inWholeSeconds.toInt()) {
handleSynchronizeSentExpirationUpdate(sent, sideEffect = true)
}
@@ -784,14 +808,14 @@ object SyncMessageProcessor {
attachments = SignalDatabase.attachments.getAttachmentsForMessage(messageId)
if (sent.message.expireTimer > 0) {
SignalDatabase.messages.markExpireStarted(messageId, sent.expirationStartTimestamp)
if (dataMessage.expireTimerDuration > Duration.ZERO) {
SignalDatabase.messages.markExpireStarted(messageId, sent.expirationStartTimestamp ?: 0)
ApplicationDependencies.getExpiringMessageManager().scheduleDeletion(messageId, true, sent.expirationStartTimestamp, sent.message.expireTimer.seconds.inWholeMilliseconds)
ApplicationDependencies.getExpiringMessageManager().scheduleDeletion(messageId, true, sent.expirationStartTimestamp ?: 0, dataMessage.expireTimerDuration.inWholeMilliseconds)
}
if (recipient.isSelf) {
SignalDatabase.messages.incrementDeliveryReceiptCount(sent.timestamp, recipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementReadReceiptCount(sent.timestamp, recipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementDeliveryReceiptCount(sent.timestamp!!, recipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementReadReceiptCount(sent.timestamp!!, recipient.id, System.currentTimeMillis())
}
SignalDatabase.messages.setTransactionSuccessful()
} finally {
@@ -809,14 +833,15 @@ object SyncMessageProcessor {
@Throws(MmsException::class, BadGroupIdException::class)
private fun handleSynchronizeSentTextMessage(sent: Sent, envelopeTimestamp: Long): Long {
log(envelopeTimestamp, "Synchronize sent text message for " + sent.timestamp)
log(envelopeTimestamp, "Synchronize sent text message for " + sent.timestamp!!)
val recipient = getSyncMessageDestination(sent)
val body = sent.message.body ?: ""
val expiresInMillis = sent.message.expireTimer.seconds.inWholeMilliseconds
val bodyRanges = sent.message.bodyRangesList.filterNot { it.hasMentionAci() }.toBodyRangeList()
val dataMessage: DataMessage = sent.message!!
val body = dataMessage.body ?: ""
val expiresInMillis = dataMessage.expireTimerDuration.inWholeMilliseconds
val bodyRanges = dataMessage.bodyRanges.filter { it.mentionAci == null }.toBodyRangeList()
if (recipient.expiresInSeconds != sent.message.expireTimer) {
if (recipient.expiresInSeconds != dataMessage.expireTimerDuration.inWholeSeconds.toInt()) {
handleSynchronizeSentExpirationUpdate(sent, sideEffect = true)
}
@@ -828,7 +853,7 @@ object SyncMessageProcessor {
val outgoingMessage = OutgoingMessage(
recipient = recipient,
body = body,
timestamp = sent.timestamp,
timestamp = sent.timestamp!!,
expiresIn = expiresInMillis,
isSecure = true,
bodyRanges = bodyRanges
@@ -837,20 +862,20 @@ object SyncMessageProcessor {
messageId = SignalDatabase.messages.insertMessageOutbox(outgoingMessage, threadId, false, GroupReceiptTable.STATUS_UNKNOWN, null)
updateGroupReceiptStatus(sent, messageId, recipient.requireGroupId())
} else {
val outgoingTextMessage = text(threadRecipient = recipient, body = body, expiresIn = expiresInMillis, sentTimeMillis = sent.timestamp, bodyRanges = bodyRanges)
val outgoingTextMessage = text(threadRecipient = recipient, body = body, expiresIn = expiresInMillis, sentTimeMillis = sent.timestamp!!, bodyRanges = bodyRanges)
messageId = SignalDatabase.messages.insertMessageOutbox(outgoingTextMessage, threadId, false, null)
SignalDatabase.messages.markUnidentified(messageId, sent.isUnidentified(recipient.serviceId.orNull()))
}
SignalDatabase.threads.update(threadId, true)
SignalDatabase.messages.markAsSent(messageId, true)
if (expiresInMillis > 0) {
SignalDatabase.messages.markExpireStarted(messageId, sent.expirationStartTimestamp)
ApplicationDependencies.getExpiringMessageManager().scheduleDeletion(messageId, isGroup, sent.expirationStartTimestamp, expiresInMillis)
SignalDatabase.messages.markExpireStarted(messageId, sent.expirationStartTimestamp ?: 0)
ApplicationDependencies.getExpiringMessageManager().scheduleDeletion(messageId, isGroup, sent.expirationStartTimestamp ?: 0, expiresInMillis)
}
if (recipient.isSelf) {
SignalDatabase.messages.incrementDeliveryReceiptCount(sent.timestamp, recipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementReadReceiptCount(sent.timestamp, recipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementDeliveryReceiptCount(sent.timestamp!!, recipient.id, System.currentTimeMillis())
SignalDatabase.messages.incrementReadReceiptCount(sent.timestamp!!, recipient.id, System.currentTimeMillis())
}
return threadId
@@ -920,13 +945,18 @@ object SyncMessageProcessor {
}
}
private fun handleSynchronizeViewedMessage(context: Context, viewedMessages: MutableList<SyncMessage.Viewed>, envelopeTimestamp: Long) {
private fun handleSynchronizeViewedMessage(context: Context, viewedMessages: List<SyncMessage.Viewed>, envelopeTimestamp: Long) {
log(envelopeTimestamp, "Synchronize view message. Count: ${viewedMessages.size}, Timestamps: ${viewedMessages.map { it.timestamp }}")
val records = viewedMessages
.mapNotNull { message ->
val author = Recipient.externalPush(ServiceId.parseOrThrow(message.senderAci)).id
SignalDatabase.messages.getMessageFor(message.timestamp, author)
val author = Recipient.externalPush(ServiceId.parseOrThrow(message.senderAci!!)).id
if (message.timestamp != null) {
SignalDatabase.messages.getMessageFor(message.timestamp!!, author)
} else {
warn(envelopeTimestamp, "Message timestamp null")
null
}
}
val toMarkViewed = records.map { it.id }
@@ -952,8 +982,13 @@ object SyncMessageProcessor {
private fun handleSynchronizeViewOnceOpenMessage(context: Context, openMessage: ViewOnceOpen, envelopeTimestamp: Long, earlyMessageCacheEntry: EarlyMessageCacheEntry?) {
log(envelopeTimestamp, "Handling a view-once open for message: " + openMessage.timestamp)
val author: RecipientId = Recipient.externalPush(ServiceId.parseOrThrow(openMessage.senderAci)).id
val timestamp: Long = openMessage.timestamp
val author: RecipientId = Recipient.externalPush(ServiceId.parseOrThrow(openMessage.senderAci!!)).id
val timestamp: Long = if (openMessage.timestamp != null) {
openMessage.timestamp!!
} else {
warn(envelopeTimestamp, "Open message missing timestamp")
return
}
val record: MessageRecord? = SignalDatabase.messages.getMessageFor(timestamp, author)
if (record != null) {
@@ -973,7 +1008,7 @@ object SyncMessageProcessor {
}
}
private fun handleSynchronizeVerifiedMessage(context: Context, verifiedMessage: SignalServiceProtos.Verified) {
private fun handleSynchronizeVerifiedMessage(context: Context, verifiedMessage: Verified) {
log("Synchronize verified message.")
IdentityUtil.processVerifiedMessage(context, verifiedMessage)
@@ -985,14 +1020,13 @@ object SyncMessageProcessor {
val jobManager = ApplicationDependencies.getJobManager()
for (operation in stickerPackOperations) {
if (operation.hasPackId() && operation.hasPackKey() && operation.hasType()) {
val packId = Hex.toStringCondensed(operation.packId.toByteArray())
val packKey = Hex.toStringCondensed(operation.packKey.toByteArray())
if (operation.packId != null && operation.packKey != null && operation.type != null) {
val packId = Hex.toStringCondensed(operation.packId!!.toByteArray())
val packKey = Hex.toStringCondensed(operation.packKey!!.toByteArray())
when (operation.type) {
when (operation.type!!) {
StickerPackOperation.Type.INSTALL -> jobManager.add(StickerPackDownloadJob.forInstall(packId, packKey, false))
StickerPackOperation.Type.REMOVE -> SignalDatabase.stickers.uninstallPack(packId)
else -> warn("Unknown sticker operation: ${operation.type}")
}
} else {
warn("Received incomplete sticker pack operation sync.")
@@ -1003,26 +1037,26 @@ object SyncMessageProcessor {
private fun handleSynchronizeConfigurationMessage(context: Context, configurationMessage: Configuration, envelopeTimestamp: Long) {
log(envelopeTimestamp, "Synchronize configuration message.")
if (configurationMessage.hasReadReceipts()) {
TextSecurePreferences.setReadReceiptsEnabled(context, configurationMessage.readReceipts)
if (configurationMessage.readReceipts != null) {
TextSecurePreferences.setReadReceiptsEnabled(context, configurationMessage.readReceipts!!)
}
if (configurationMessage.hasUnidentifiedDeliveryIndicators()) {
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, configurationMessage.unidentifiedDeliveryIndicators)
if (configurationMessage.unidentifiedDeliveryIndicators != null) {
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, configurationMessage.unidentifiedDeliveryIndicators!!)
}
if (configurationMessage.hasTypingIndicators()) {
TextSecurePreferences.setTypingIndicatorsEnabled(context, configurationMessage.typingIndicators)
if (configurationMessage.typingIndicators != null) {
TextSecurePreferences.setTypingIndicatorsEnabled(context, configurationMessage.typingIndicators!!)
}
if (configurationMessage.hasLinkPreviews()) {
SignalStore.settings().isLinkPreviewsEnabled = configurationMessage.linkPreviews
if (configurationMessage.linkPreviews != null) {
SignalStore.settings().isLinkPreviewsEnabled = configurationMessage.linkPreviews!!
}
}
private fun handleSynchronizeBlockedListMessage(blockMessage: Blocked) {
val addresses: List<SignalServiceAddress> = blockMessage.acisList.mapNotNull { SignalServiceAddress.fromRaw(it, null).orNull() }
val groupIds: List<ByteArray> = blockMessage.groupIdsList.mapNotNull { it.toByteArray() }
val addresses: List<SignalServiceAddress> = blockMessage.acis.mapNotNull { SignalServiceAddress.fromRaw(it, null).orNull() }
val groupIds: List<ByteArray> = blockMessage.groupIds.map { it.toByteArray() }
SignalDatabase.recipients.applyBlockedUpdate(addresses, groupIds)
}
@@ -1041,10 +1075,10 @@ object SyncMessageProcessor {
private fun handleSynchronizeMessageRequestResponse(response: MessageRequestResponse, envelopeTimestamp: Long) {
log(envelopeTimestamp, "Synchronize message request response.")
val recipient: Recipient = if (response.hasThreadAci()) {
Recipient.externalPush(ServiceId.parseOrThrow(response.threadAci))
} else if (response.hasGroupId()) {
val groupId: GroupId = GroupId.push(response.groupId)
val recipient: Recipient = if (response.threadAci != null) {
Recipient.externalPush(ServiceId.parseOrThrow(response.threadAci!!))
} else if (response.groupId != null) {
val groupId: GroupId = GroupId.push(response.groupId!!)
Recipient.externalPossiblyMigratedGroup(groupId)
} else {
warn("Message request response was missing a thread recipient! Skipping.")
@@ -1080,20 +1114,24 @@ object SyncMessageProcessor {
}
private fun handleSynchronizeOutgoingPayment(outgoingPayment: SyncMessage.OutgoingPayment, envelopeTimestamp: Long) {
if (!outgoingPayment.hasMobileCoin()) {
log("Unknown outgoing payment, ignoring.")
log(envelopeTimestamp, "Synchronize outgoing payment.")
val mobileCoin = if (outgoingPayment.mobileCoin != null) {
outgoingPayment.mobileCoin!!
} else {
log(envelopeTimestamp, "Unknown outgoing payment, ignoring.")
return
}
var recipientId: RecipientId? = ServiceId.parseOrNull(outgoingPayment.recipientServiceId)?.let { RecipientId.from(it) }
var timestamp = outgoingPayment.mobileCoin.ledgerBlockTimestamp
var timestamp: Long = mobileCoin.ledgerBlockTimestamp ?: 0L
if (timestamp == 0L) {
timestamp = System.currentTimeMillis()
}
var address: MobileCoinPublicAddress? = if (outgoingPayment.mobileCoin.hasRecipientAddress()) {
MobileCoinPublicAddress.fromBytes(outgoingPayment.mobileCoin.recipientAddress.toByteArray())
var address: MobileCoinPublicAddress? = if (mobileCoin.recipientAddress != null) {
MobileCoinPublicAddress.fromBytes(mobileCoin.recipientAddress!!.toByteArray())
} else {
null
}
@@ -1112,12 +1150,12 @@ object SyncMessageProcessor {
recipientId,
address!!,
timestamp,
outgoingPayment.mobileCoin.ledgerBlockIndex,
mobileCoin.ledgerBlockIndex!!,
outgoingPayment.note ?: "",
outgoingPayment.mobileCoin.amountPicoMob.toMobileCoinMoney(),
outgoingPayment.mobileCoin.feePicoMob.toMobileCoinMoney(),
outgoingPayment.mobileCoin.receipt.toByteArray(),
PaymentMetaDataUtil.fromKeysAndImages(outgoingPayment.mobileCoin.outputPublicKeysList, outgoingPayment.mobileCoin.spentKeyImagesList)
mobileCoin.amountPicoMob!!.toMobileCoinMoney(),
mobileCoin.feePicoMob!!.toMobileCoinMoney(),
mobileCoin.receipt!!.toByteArray(),
PaymentMetaDataUtil.fromKeysAndImages(mobileCoin.outputPublicKeys, mobileCoin.spentKeyImages)
)
} catch (e: SerializationException) {
warn(envelopeTimestamp, "Ignoring synchronized outgoing payment with bad data.", e)
@@ -1146,13 +1184,18 @@ object SyncMessageProcessor {
return
}
val attachment: SignalServiceAttachmentPointer = contactsMessage.blob.toSignalServiceAttachmentPointer()
if (contactsMessage.blob == null) {
log(envelopeTimestamp, "Contact blob is null")
return
}
val attachment: SignalServiceAttachmentPointer = contactsMessage.blob!!.toSignalServiceAttachmentPointer()
ApplicationDependencies.getJobManager().add(MultiDeviceContactSyncJob(attachment))
}
private fun handleSynchronizeCallEvent(callEvent: SyncMessage.CallEvent, envelopeTimestamp: Long) {
if (!callEvent.hasId()) {
if (callEvent.id == null) {
log(envelopeTimestamp, "Synchronize call event missing call id, ignoring. type: ${callEvent.type}")
return
}
@@ -1168,20 +1211,23 @@ object SyncMessageProcessor {
if (callLogEvent.type != CallLogEvent.Type.CLEAR) {
log(envelopeTimestamp, "Synchronize call log event has an invalid type ${callLogEvent.type}, ignoring.")
return
} else if (callLogEvent.timestamp == null) {
log(envelopeTimestamp, "Synchronize call log event has null timestamp")
return
}
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(callLogEvent.timestamp)
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(callLogEvent.timestamp)
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(callLogEvent.timestamp!!)
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(callLogEvent.timestamp!!)
}
private fun handleSynchronizeCallLink(callLinkUpdate: CallLinkUpdate, envelopeTimestamp: Long) {
if (!callLinkUpdate.hasRootKey()) {
if (callLinkUpdate.rootKey == null) {
log(envelopeTimestamp, "Synchronize call link missing root key, ignoring.")
return
}
val callLinkRootKey = try {
CallLinkRootKey(callLinkUpdate.rootKey.toByteArray())
CallLinkRootKey(callLinkUpdate.rootKey!!.toByteArray())
} catch (e: CallException) {
log(envelopeTimestamp, "Synchronize call link has invalid root key, ignoring.")
return
@@ -1193,7 +1239,7 @@ object SyncMessageProcessor {
SignalDatabase.callLinks.updateCallLinkCredentials(
roomId,
CallLinkCredentials(
callLinkUpdate.rootKey.toByteArray(),
callLinkUpdate.rootKey!!.toByteArray(),
callLinkUpdate.adminPassKey?.toByteArray()
)
)
@@ -1216,18 +1262,18 @@ object SyncMessageProcessor {
}
private fun handleSynchronizeOneToOneCallEvent(callEvent: SyncMessage.CallEvent, envelopeTimestamp: Long) {
val callId: Long = callEvent.id
val timestamp: Long = callEvent.timestamp
val callId: Long = callEvent.id!!
val timestamp: Long = callEvent.timestamp ?: 0L
val type: CallTable.Type? = CallTable.Type.from(callEvent.type)
val direction: CallTable.Direction? = CallTable.Direction.from(callEvent.direction)
val event: CallTable.Event? = CallTable.Event.from(callEvent.event)
if (timestamp == 0L || type == null || direction == null || event == null || !callEvent.hasConversationId()) {
warn(envelopeTimestamp, "Call event sync message is not valid, ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
if (timestamp == 0L || type == null || direction == null || event == null || callEvent.conversationId == null) {
warn(envelopeTimestamp, "Call event sync message is not valid, ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + (callEvent.conversationId != null))
return
}
val aci = ACI.parseOrThrow(callEvent.conversationId)
val aci = ACI.parseOrThrow(callEvent.conversationId!!)
val recipientId = RecipientId.from(aci)
log(envelopeTimestamp, "Synchronize call event call: $callId")
@@ -1256,25 +1302,26 @@ object SyncMessageProcessor {
return
}
val callId: Long = callEvent.id
val timestamp: Long = callEvent.timestamp
val callId: Long = callEvent.id!!
val timestamp: Long = callEvent.timestamp ?: 0L
val type: CallTable.Type? = CallTable.Type.from(callEvent.type)
val direction: CallTable.Direction? = CallTable.Direction.from(callEvent.direction)
val event: CallTable.Event? = CallTable.Event.from(callEvent.event)
val hasConversationId: Boolean = callEvent.conversationId != null
if (timestamp == 0L || type == null || direction == null || event == null || !callEvent.hasConversationId()) {
warn(envelopeTimestamp, "Group/Ad-hoc call event sync message is not valid, ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
if (timestamp == 0L || type == null || direction == null || event == null || !hasConversationId) {
warn(envelopeTimestamp, "Group/Ad-hoc call event sync message is not valid, ignoring. timestamp: $timestamp type: $type direction: $direction event: $event hasPeer: $hasConversationId")
return
}
val recipient: Recipient? = when (type) {
CallTable.Type.AD_HOC_CALL -> {
val callLinkRoomId = CallLinkRoomId.fromBytes(callEvent.conversationId.toByteArray())
val callLinkRoomId = CallLinkRoomId.fromBytes(callEvent.conversationId!!.toByteArray())
val callLink = SignalDatabase.callLinks.getOrCreateCallLinkByRoomId(callLinkRoomId)
Recipient.resolved(callLink.recipientId)
}
CallTable.Type.GROUP_CALL -> {
val groupId: GroupId = GroupId.push(callEvent.conversationId.toByteArray())
val groupId: GroupId = GroupId.push(callEvent.conversationId!!.toByteArray())
Recipient.externalGroupExact(groupId)
}
else -> {
@@ -1292,14 +1339,14 @@ object SyncMessageProcessor {
if (call != null) {
if (call.type !== type) {
warn(envelopeTimestamp, "Group/Ad-hoc call event type mismatch, ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
warn(envelopeTimestamp, "Group/Ad-hoc call event type mismatch, ignoring. timestamp: $timestamp type: $type direction: $direction event: $event hasPeer: $hasConversationId")
return
}
when (event) {
CallTable.Event.DELETE -> SignalDatabase.calls.deleteGroupCall(call)
CallTable.Event.ACCEPTED -> {
if (call.timestamp < callEvent.timestamp) {
SignalDatabase.calls.setTimestamp(call.callId, recipient.id, callEvent.timestamp)
if (call.timestamp < timestamp) {
SignalDatabase.calls.setTimestamp(call.callId, recipient.id, timestamp)
}
if (callEvent.direction == SyncMessage.CallEvent.Direction.INCOMING) {
SignalDatabase.calls.acceptIncomingGroupCall(call)
@@ -1307,15 +1354,15 @@ object SyncMessageProcessor {
warn(envelopeTimestamp, "Invalid direction OUTGOING for event ACCEPTED")
}
}
CallTable.Event.NOT_ACCEPTED -> warn("Unsupported event type " + event + ". Ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
else -> warn("Unsupported event type " + event + ". Ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
CallTable.Event.NOT_ACCEPTED -> warn("Unsupported event type $event. Ignoring. timestamp: $timestamp type: $type direction: $direction event: $event hasPeer: $hasConversationId")
else -> warn("Unsupported event type $event. Ignoring. timestamp: $timestamp type: $type direction: $direction event: $event hasPeer: $hasConversationId")
}
} else {
when (event) {
CallTable.Event.DELETE -> SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(callEvent.id, recipient.id, direction, timestamp)
CallTable.Event.ACCEPTED -> SignalDatabase.calls.insertAcceptedGroupCall(callEvent.id, recipient.id, direction, timestamp)
CallTable.Event.NOT_ACCEPTED -> warn("Unsupported event type " + event + ". Ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
else -> warn("Unsupported event type " + event + ". Ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
CallTable.Event.DELETE -> SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(callEvent.id!!, recipient.id, direction, timestamp)
CallTable.Event.ACCEPTED -> SignalDatabase.calls.insertAcceptedGroupCall(callEvent.id!!, recipient.id, direction, timestamp)
CallTable.Event.NOT_ACCEPTED -> warn("Unsupported event type $event. Ignoring. timestamp: $timestamp type: $type direction: $direction event: $event hasPeer: $hasConversationId")
else -> warn("Unsupported event type $event. Ignoring. timestamp: $timestamp type: $type direction: $direction event: $event hasPeer: $hasConversationId")
}
}
}