kopia lustrzana https://github.com/ryukoposting/Signal-Android
499 wiersze
23 KiB
Kotlin
499 wiersze
23 KiB
Kotlin
package org.thoughtcrime.securesms.messages
|
|
|
|
import android.app.Notification
|
|
import android.app.PendingIntent
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import androidx.core.app.NotificationCompat
|
|
import androidx.core.app.NotificationManagerCompat
|
|
import com.squareup.wire.internal.toUnmodifiableList
|
|
import org.signal.core.util.PendingIntentFlags
|
|
import org.signal.core.util.logging.Log
|
|
import org.signal.libsignal.metadata.InvalidMetadataMessageException
|
|
import org.signal.libsignal.metadata.InvalidMetadataVersionException
|
|
import org.signal.libsignal.metadata.ProtocolDuplicateMessageException
|
|
import org.signal.libsignal.metadata.ProtocolException
|
|
import org.signal.libsignal.metadata.ProtocolInvalidKeyException
|
|
import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException
|
|
import org.signal.libsignal.metadata.ProtocolInvalidMessageException
|
|
import org.signal.libsignal.metadata.ProtocolInvalidVersionException
|
|
import org.signal.libsignal.metadata.ProtocolLegacyMessageException
|
|
import org.signal.libsignal.metadata.ProtocolNoSessionException
|
|
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException
|
|
import org.signal.libsignal.metadata.SelfSendException
|
|
import org.signal.libsignal.protocol.SignalProtocolAddress
|
|
import org.signal.libsignal.protocol.message.CiphertextMessage
|
|
import org.signal.libsignal.protocol.message.DecryptionErrorMessage
|
|
import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage
|
|
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
|
import org.thoughtcrime.securesms.R
|
|
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock
|
|
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
|
import org.thoughtcrime.securesms.groups.BadGroupIdException
|
|
import org.thoughtcrime.securesms.groups.GroupId
|
|
import org.thoughtcrime.securesms.jobs.AutomaticSessionResetJob
|
|
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob
|
|
import org.thoughtcrime.securesms.jobs.SendRetryReceiptJob
|
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
|
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity
|
|
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
|
import org.thoughtcrime.securesms.notifications.NotificationIds
|
|
import org.thoughtcrime.securesms.recipients.Recipient
|
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
|
import org.thoughtcrime.securesms.util.FeatureFlags
|
|
import org.whispersystems.signalservice.api.InvalidMessageStructureException
|
|
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
|
|
import org.whispersystems.signalservice.api.crypto.ContentHint
|
|
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
|
|
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher
|
|
import org.whispersystems.signalservice.api.crypto.SignalServiceCipherResult
|
|
import org.whispersystems.signalservice.api.messages.EnvelopeContentValidator
|
|
import org.whispersystems.signalservice.api.push.PNI
|
|
import org.whispersystems.signalservice.api.push.ServiceId
|
|
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.UnsupportedDataMessageException
|
|
import java.util.Optional
|
|
|
|
/**
|
|
* This class is designed to handle everything around the process of taking an [Envelope] and decrypting it into something
|
|
* that you can use (or provide an appropriate error if something goes wrong). We'll also use this space to go over some
|
|
* high-level concepts in message decryption.
|
|
*/
|
|
object MessageDecryptor {
|
|
|
|
private val TAG = Log.tag(MessageDecryptor::class.java)
|
|
|
|
/**
|
|
* Decrypts an envelope and provides a [Result]. This method has side effects, but all of them are limited to [SignalDatabase].
|
|
* That means that this operation should be atomic when performed within a transaction.
|
|
* To keep that property, there may be [Result.followUpOperations] you have to perform after your transaction is committed.
|
|
* These can vary from enqueueing jobs to inserting items into the [org.thoughtcrime.securesms.database.PendingRetryReceiptCache].
|
|
*/
|
|
fun decrypt(context: Context, envelope: Envelope, serverDeliveredTimestamp: Long): Result {
|
|
val selfAci: ServiceId = SignalStore.account().requireAci()
|
|
val selfPni: ServiceId = SignalStore.account().requirePni()
|
|
|
|
val destination: ServiceId = envelope.getDestination(selfAci, selfPni)
|
|
|
|
if (destination == selfPni && envelope.hasSourceUuid()) {
|
|
Log.i(TAG, "${logPrefix(envelope)} Received a message at our PNI. Marking as needing a PNI signature.")
|
|
|
|
val sourceServiceId = ServiceId.parseOrNull(envelope.sourceUuid)
|
|
|
|
if (sourceServiceId != null) {
|
|
val sender = RecipientId.from(sourceServiceId)
|
|
SignalDatabase.recipients.markNeedsPniSignature(sender)
|
|
} else {
|
|
Log.w(TAG, "${logPrefix(envelope)} Could not mark sender as needing a PNI signature because the sender serviceId was invalid!")
|
|
}
|
|
}
|
|
|
|
if (destination == selfPni && !envelope.hasSourceUuid()) {
|
|
Log.w(TAG, "${logPrefix(envelope)} Got a sealed sender message to our PNI? Invalid message, ignoring.")
|
|
return Result.Ignore(envelope, serverDeliveredTimestamp, emptyList())
|
|
}
|
|
|
|
val followUpOperations: MutableList<Runnable> = mutableListOf()
|
|
|
|
if (envelope.type == Envelope.Type.PREKEY_BUNDLE) {
|
|
followUpOperations += Runnable {
|
|
PreKeysSyncJob.enqueue()
|
|
}
|
|
}
|
|
|
|
val protocolStore: SignalServiceAccountDataStore = ApplicationDependencies.getProtocolStore().get(destination)
|
|
val localAddress = SignalServiceAddress(selfAci, SignalStore.account().e164)
|
|
val cipher = SignalServiceCipher(localAddress, SignalStore.account().deviceId, protocolStore, ReentrantSessionLock.INSTANCE, UnidentifiedAccessUtil.getCertificateValidator())
|
|
|
|
return try {
|
|
val cipherResult: SignalServiceCipherResult? = cipher.decrypt(envelope, serverDeliveredTimestamp)
|
|
|
|
if (cipherResult == null) {
|
|
Log.w(TAG, "${logPrefix(envelope)} Decryption resulted in a null result!", true)
|
|
return Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations)
|
|
}
|
|
|
|
Log.d(TAG, "${logPrefix(envelope, cipherResult)} Successfully decrypted the envelope.")
|
|
|
|
val validationResult: EnvelopeContentValidator.Result = EnvelopeContentValidator.validate(envelope, cipherResult.content)
|
|
|
|
if (validationResult is EnvelopeContentValidator.Result.Invalid) {
|
|
Log.w(TAG, "${logPrefix(envelope, cipherResult)} Invalid content! ${validationResult.reason}", validationResult.throwable)
|
|
return Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations)
|
|
}
|
|
|
|
if (validationResult is EnvelopeContentValidator.Result.UnsupportedDataMessage) {
|
|
Log.w(TAG, "${logPrefix(envelope, cipherResult)} Unsupported DataMessage! Our version: ${validationResult.ourVersion}, their version: ${validationResult.theirVersion}")
|
|
return Result.UnsupportedDataMessage(envelope, serverDeliveredTimestamp, cipherResult.toErrorMetadata(), followUpOperations)
|
|
}
|
|
|
|
// Must handle SKDM's immediately, because subsequent decryptions could rely on it
|
|
if (cipherResult.content.hasSenderKeyDistributionMessage()) {
|
|
handleSenderKeyDistributionMessage(
|
|
envelope,
|
|
cipherResult.metadata.sourceServiceId,
|
|
cipherResult.metadata.sourceDeviceId,
|
|
SenderKeyDistributionMessage(cipherResult.content.senderKeyDistributionMessage.toByteArray())
|
|
)
|
|
}
|
|
|
|
if (FeatureFlags.phoneNumberPrivacy() && cipherResult.content.hasPniSignatureMessage()) {
|
|
handlePniSignatureMessage(
|
|
envelope,
|
|
cipherResult.metadata.sourceServiceId,
|
|
cipherResult.metadata.sourceE164,
|
|
cipherResult.metadata.sourceDeviceId,
|
|
cipherResult.content.pniSignatureMessage
|
|
)
|
|
} else if (cipherResult.content.hasPniSignatureMessage()) {
|
|
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) {
|
|
val sender = RecipientId.from(cipherResult.metadata.sourceServiceId)
|
|
SignalDatabase.recipients.setReportingToken(sender, envelope.reportingToken.toByteArray())
|
|
}
|
|
|
|
Result.Success(envelope, serverDeliveredTimestamp, cipherResult.content, cipherResult.metadata, followUpOperations.toUnmodifiableList())
|
|
} catch (e: Exception) {
|
|
when (e) {
|
|
is ProtocolInvalidKeyIdException,
|
|
is ProtocolInvalidKeyException,
|
|
is ProtocolUntrustedIdentityException,
|
|
is ProtocolNoSessionException,
|
|
is ProtocolInvalidMessageException -> {
|
|
check(e is ProtocolException)
|
|
Log.w(TAG, "${logPrefix(envelope, e)} Decryption error!", e, true)
|
|
|
|
if (FeatureFlags.internalUser()) {
|
|
postErrorNotification(context)
|
|
}
|
|
|
|
if (FeatureFlags.retryReceipts()) {
|
|
buildResultForDecryptionError(context, envelope, serverDeliveredTimestamp, followUpOperations, e)
|
|
} else {
|
|
Log.w(TAG, "${logPrefix(envelope, e)} Retry receipts disabled! Enqueuing a session reset job, which will also insert an error message.", e, true)
|
|
|
|
followUpOperations += Runnable {
|
|
val sender: Recipient = Recipient.external(context, e.sender)
|
|
ApplicationDependencies.getJobManager().add(AutomaticSessionResetJob(sender.id, e.senderDevice, envelope.timestamp))
|
|
}
|
|
|
|
Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList())
|
|
}
|
|
}
|
|
|
|
is ProtocolDuplicateMessageException -> {
|
|
Log.w(TAG, "${logPrefix(envelope, e)} Duplicate message!", e)
|
|
Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList())
|
|
}
|
|
|
|
is InvalidMetadataVersionException,
|
|
is InvalidMetadataMessageException,
|
|
is InvalidMessageStructureException -> {
|
|
Log.w(TAG, "${logPrefix(envelope)} Invalid message structure!", e, true)
|
|
Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList())
|
|
}
|
|
|
|
is SelfSendException -> {
|
|
Log.i(TAG, "[${envelope.timestamp}] Dropping sealed sender message from self!", e)
|
|
Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList())
|
|
}
|
|
|
|
is ProtocolInvalidVersionException -> {
|
|
Log.w(TAG, "${logPrefix(envelope, e)} Invalid version!", e, true)
|
|
Result.InvalidVersion(envelope, serverDeliveredTimestamp, e.toErrorMetadata(), followUpOperations.toUnmodifiableList())
|
|
}
|
|
|
|
is ProtocolLegacyMessageException -> {
|
|
Log.w(TAG, "${logPrefix(envelope, e)} Legacy message!", e, true)
|
|
Result.LegacyMessage(envelope, serverDeliveredTimestamp, e.toErrorMetadata(), followUpOperations)
|
|
}
|
|
|
|
else -> {
|
|
Log.w(TAG, "Encountered an unexpected exception! Throwing!", e, true)
|
|
throw e
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun buildResultForDecryptionError(
|
|
context: Context,
|
|
envelope: Envelope,
|
|
serverDeliveredTimestamp: Long,
|
|
followUpOperations: MutableList<Runnable>,
|
|
protocolException: ProtocolException
|
|
): Result {
|
|
val contentHint: ContentHint = ContentHint.fromType(protocolException.contentHint)
|
|
val senderDevice: Int = protocolException.senderDevice
|
|
val receivedTimestamp: Long = System.currentTimeMillis()
|
|
val sender: Recipient = Recipient.external(context, protocolException.sender)
|
|
|
|
followUpOperations += Runnable {
|
|
ApplicationDependencies.getJobManager().add(buildSendRetryReceiptJob(envelope, protocolException, sender))
|
|
}
|
|
|
|
return when (contentHint) {
|
|
ContentHint.DEFAULT -> {
|
|
Log.w(TAG, "${logPrefix(envelope)} The content hint is $contentHint, so we need to insert an error right away.", true)
|
|
Result.DecryptionError(envelope, serverDeliveredTimestamp, protocolException.toErrorMetadata(), followUpOperations.toUnmodifiableList())
|
|
}
|
|
|
|
ContentHint.RESENDABLE -> {
|
|
Log.w(TAG, "${logPrefix(envelope)} The content hint is $contentHint, so we can try to resend the message.", true)
|
|
|
|
followUpOperations += Runnable {
|
|
val groupId: GroupId? = protocolException.parseGroupId(envelope)
|
|
val threadId: Long = if (groupId != null) {
|
|
val groupRecipient: Recipient = Recipient.externalPossiblyMigratedGroup(groupId)
|
|
SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
|
|
} else {
|
|
SignalDatabase.threads.getOrCreateThreadIdFor(sender)
|
|
}
|
|
|
|
ApplicationDependencies.getPendingRetryReceiptCache().insert(sender.id, senderDevice, envelope.timestamp, receivedTimestamp, threadId)
|
|
ApplicationDependencies.getPendingRetryReceiptManager().scheduleIfNecessary()
|
|
}
|
|
|
|
Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations)
|
|
}
|
|
|
|
ContentHint.IMPLICIT -> {
|
|
Log.w(TAG, "${logPrefix(envelope)} The content hint is $contentHint, so no error message is needed.", true)
|
|
Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun handleSenderKeyDistributionMessage(envelope: Envelope, serviceId: ServiceId, deviceId: Int, message: SenderKeyDistributionMessage) {
|
|
Log.i(TAG, "${logPrefix(envelope, serviceId)} Processing SenderKeyDistributionMessage")
|
|
val sender = ApplicationDependencies.getSignalServiceMessageSender()
|
|
sender.processSenderKeyDistributionMessage(SignalProtocolAddress(serviceId.toString(), deviceId), message)
|
|
}
|
|
|
|
private fun handlePniSignatureMessage(envelope: Envelope, serviceId: ServiceId, e164: String?, deviceId: Int, pniSignatureMessage: PniSignatureMessage) {
|
|
Log.i(TAG, "${logPrefix(envelope, serviceId)} Processing PniSignatureMessage")
|
|
|
|
val pni: PNI = PNI.parseOrThrow(pniSignatureMessage.pni.toByteArray())
|
|
|
|
if (SignalDatabase.recipients.isAssociated(serviceId, pni)) {
|
|
Log.i(TAG, "${logPrefix(envelope, serviceId)}[handlePniSignatureMessage] ACI ($serviceId) and PNI ($pni) are already associated.")
|
|
return
|
|
}
|
|
|
|
val identityStore = ApplicationDependencies.getProtocolStore().aci().identities()
|
|
val aciAddress = SignalProtocolAddress(serviceId.toString(), deviceId)
|
|
val pniAddress = SignalProtocolAddress(pni.toString(), deviceId)
|
|
val aciIdentity = identityStore.getIdentity(aciAddress)
|
|
val pniIdentity = identityStore.getIdentity(pniAddress)
|
|
|
|
if (aciIdentity == null) {
|
|
Log.w(TAG, "${logPrefix(envelope, serviceId)}[validatePniSignature] No identity found for ACI address $aciAddress")
|
|
return
|
|
}
|
|
|
|
if (pniIdentity == null) {
|
|
Log.w(TAG, "${logPrefix(envelope, serviceId)}[validatePniSignature] No identity found for PNI address $pniAddress")
|
|
return
|
|
}
|
|
|
|
if (pniIdentity.verifyAlternateIdentity(aciIdentity, pniSignatureMessage.signature.toByteArray())) {
|
|
Log.i(TAG, "${logPrefix(envelope, serviceId)}[validatePniSignature] PNI signature is valid. Associating ACI ($serviceId) with PNI ($pni)")
|
|
SignalDatabase.recipients.getAndPossiblyMergePnpVerified(serviceId, pni, e164)
|
|
} else {
|
|
Log.w(TAG, "${logPrefix(envelope, serviceId)}[validatePniSignature] Invalid PNI signature! Cannot associate ACI ($serviceId) with PNI ($pni)")
|
|
}
|
|
}
|
|
|
|
private fun postErrorNotification(context: Context) {
|
|
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
|
|
.setSmallIcon(R.drawable.ic_notification)
|
|
.setContentTitle(context.getString(R.string.MessageDecryptionUtil_failed_to_decrypt_message))
|
|
.setContentText(context.getString(R.string.MessageDecryptionUtil_tap_to_send_a_debug_log))
|
|
.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable()))
|
|
.build()
|
|
|
|
NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification)
|
|
}
|
|
|
|
private fun logPrefix(envelope: Envelope): String {
|
|
return logPrefix(envelope.timestamp, envelope.sourceUuid ?: "<sealed>", envelope.sourceDevice)
|
|
}
|
|
|
|
private fun logPrefix(envelope: Envelope, sender: ServiceId): String {
|
|
return logPrefix(envelope.timestamp, sender.toString(), envelope.sourceDevice)
|
|
}
|
|
|
|
private fun logPrefix(envelope: Envelope, cipherResult: SignalServiceCipherResult): String {
|
|
return logPrefix(envelope.timestamp, cipherResult.metadata.sourceServiceId.toString(), envelope.sourceDevice)
|
|
}
|
|
|
|
private fun logPrefix(envelope: Envelope, exception: ProtocolException): String {
|
|
return if (exception.sender != null) {
|
|
logPrefix(envelope.timestamp, exception.sender, exception.senderDevice)
|
|
} else {
|
|
logPrefix(envelope.timestamp, envelope.sourceUuid, envelope.sourceDevice)
|
|
}
|
|
}
|
|
|
|
private fun logPrefix(envelope: Envelope, exception: UnsupportedDataMessageException): String {
|
|
return if (exception.sender != null) {
|
|
logPrefix(envelope.timestamp, exception.sender, exception.senderDevice)
|
|
} else {
|
|
logPrefix(envelope.timestamp, envelope.sourceUuid, envelope.sourceDevice)
|
|
}
|
|
}
|
|
|
|
private fun logPrefix(timestamp: Long, sender: String?, deviceId: Int): String {
|
|
val senderString = sender ?: "null"
|
|
return "[$timestamp] $senderString:$deviceId |"
|
|
}
|
|
|
|
private fun buildSendRetryReceiptJob(envelope: Envelope, protocolException: ProtocolException, sender: Recipient): SendRetryReceiptJob {
|
|
val originalContent: ByteArray
|
|
val envelopeType: Int
|
|
|
|
if (protocolException.unidentifiedSenderMessageContent.isPresent) {
|
|
originalContent = protocolException.unidentifiedSenderMessageContent.get().content
|
|
envelopeType = protocolException.unidentifiedSenderMessageContent.get().type
|
|
} else {
|
|
originalContent = envelope.content.toByteArray()
|
|
envelopeType = envelope.type.number.toCiphertextMessageType()
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
private fun ProtocolException.parseGroupId(envelope: Envelope): GroupId? {
|
|
return if (this.groupId.isPresent) {
|
|
try {
|
|
GroupId.push(this.groupId.get())
|
|
} catch (e: BadGroupIdException) {
|
|
Log.w(TAG, "[${envelope.timestamp}] Bad groupId!", true)
|
|
null
|
|
}
|
|
} else {
|
|
null
|
|
}
|
|
}
|
|
|
|
private fun Envelope.getDestination(selfAci: ServiceId, selfPni: ServiceId): ServiceId {
|
|
return if (!FeatureFlags.phoneNumberPrivacy()) {
|
|
selfAci
|
|
} else if (this.hasDestinationUuid()) {
|
|
val serviceId = ServiceId.parseOrThrow(this.destinationUuid)
|
|
if (serviceId == selfAci || serviceId == selfPni) {
|
|
serviceId
|
|
} else {
|
|
Log.w(TAG, "Destination of $serviceId does not match our ACI ($selfAci) or PNI ($selfPni)! Defaulting to ACI.")
|
|
selfAci
|
|
}
|
|
} else {
|
|
Log.w(TAG, "No destinationUuid set! Defaulting to ACI.")
|
|
selfAci
|
|
}
|
|
}
|
|
|
|
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
|
|
else -> CiphertextMessage.WHISPER_TYPE
|
|
}
|
|
}
|
|
|
|
private fun ProtocolException.toErrorMetadata(): ErrorMetadata {
|
|
return ErrorMetadata(
|
|
sender = this.sender,
|
|
senderDevice = this.senderDevice,
|
|
groupId = if (this.groupId.isPresent) GroupId.v2(GroupMasterKey(this.groupId.get())) else null
|
|
)
|
|
}
|
|
|
|
private fun SignalServiceCipherResult.toErrorMetadata(): ErrorMetadata {
|
|
return ErrorMetadata(
|
|
sender = this.metadata.sourceServiceId.toString(),
|
|
senderDevice = this.metadata.sourceDeviceId,
|
|
groupId = null
|
|
)
|
|
}
|
|
|
|
sealed interface Result {
|
|
val envelope: Envelope
|
|
val serverDeliveredTimestamp: Long
|
|
val followUpOperations: List<Runnable>
|
|
|
|
/** Successfully decrypted the envelope content. The plaintext [Content] is available. */
|
|
class Success(
|
|
override val envelope: Envelope,
|
|
override val serverDeliveredTimestamp: Long,
|
|
val content: Content,
|
|
val metadata: EnvelopeMetadata,
|
|
override val followUpOperations: List<Runnable>
|
|
) : Result
|
|
|
|
/** We could not decrypt the message, and an error should be inserted into the user's chat history. */
|
|
class DecryptionError(
|
|
override val envelope: Envelope,
|
|
override val serverDeliveredTimestamp: Long,
|
|
override val errorMetadata: ErrorMetadata,
|
|
override val followUpOperations: List<Runnable>
|
|
) : Result, Error
|
|
|
|
/** The envelope used an invalid version of the Signal protocol. */
|
|
class InvalidVersion(
|
|
override val envelope: Envelope,
|
|
override val serverDeliveredTimestamp: Long,
|
|
override val errorMetadata: ErrorMetadata,
|
|
override val followUpOperations: List<Runnable>
|
|
) : Result, Error
|
|
|
|
/** The envelope used an old format that hasn't been used since 2015. This shouldn't be happening. */
|
|
class LegacyMessage(
|
|
override val envelope: Envelope,
|
|
override val serverDeliveredTimestamp: Long,
|
|
override val errorMetadata: ErrorMetadata,
|
|
override val followUpOperations: List<Runnable>
|
|
) : Result, Error
|
|
|
|
/**
|
|
* Indicates the that the [org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage.getRequiredProtocolVersion]
|
|
* is higher than we support.
|
|
*/
|
|
class UnsupportedDataMessage(
|
|
override val envelope: Envelope,
|
|
override val serverDeliveredTimestamp: Long,
|
|
override val errorMetadata: ErrorMetadata,
|
|
override val followUpOperations: List<Runnable>
|
|
) : Result, Error
|
|
|
|
/** There are no further results from this envelope that need to be processed. There may still be [followUpOperations]. */
|
|
class Ignore(
|
|
override val envelope: Envelope,
|
|
override val serverDeliveredTimestamp: Long,
|
|
override val followUpOperations: List<Runnable>
|
|
) : Result
|
|
|
|
interface Error {
|
|
val errorMetadata: ErrorMetadata
|
|
}
|
|
}
|
|
|
|
data class ErrorMetadata(
|
|
val sender: String,
|
|
val senderDevice: Int,
|
|
val groupId: GroupId?
|
|
)
|
|
}
|