Initial refactor of the message decryption flow.

This commit is contained in:
Greyson Parrelli
2023-03-03 01:10:33 -05:00
parent c1a94be9cd
commit ec2565263e
14 changed files with 1077 additions and 696 deletions

View File

@@ -0,0 +1,12 @@
package org.whispersystems.signalservice.api.crypto
import org.whispersystems.signalservice.api.push.ServiceId
class EnvelopeMetadata(
val sourceServiceId: ServiceId,
val sourceE164: String?,
val sourceDeviceId: Int,
val sealedSender: Boolean,
val groupId: ByteArray?,
val destinationServiceId: ServiceId
)

View File

@@ -49,10 +49,12 @@ import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceMetadata;
import org.whispersystems.signalservice.api.push.ACI;
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.OutgoingPushMessage;
import org.whispersystems.signalservice.internal.push.PushTransportDetails;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope;
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer;
import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer;
@@ -144,7 +146,7 @@ public class SignalServiceCipher {
{
try {
if (envelope.hasContent()) {
Plaintext plaintext = decrypt(envelope, envelope.getContent());
Plaintext plaintext = decryptInternal(envelope.getProto(), envelope.getServerDeliveredTimestamp());
SignalServiceProtos.Content content = SignalServiceProtos.Content.parseFrom(plaintext.getData());
SignalServiceContentProto contentProto = SignalServiceContentProto.newBuilder()
@@ -162,7 +164,39 @@ public class SignalServiceCipher {
}
}
private Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext)
public SignalServiceCipherResult decrypt(Envelope envelope, long serverDeliveredTimestamp)
throws InvalidMetadataMessageException, InvalidMetadataVersionException,
ProtocolInvalidKeyIdException, ProtocolLegacyMessageException,
ProtocolUntrustedIdentityException, ProtocolNoSessionException,
ProtocolInvalidVersionException, ProtocolInvalidMessageException,
ProtocolInvalidKeyException, ProtocolDuplicateMessageException,
SelfSendException, InvalidMessageStructureException
{
try {
if (envelope.hasContent()) {
Plaintext plaintext = decryptInternal(envelope, serverDeliveredTimestamp);
SignalServiceProtos.Content content = SignalServiceProtos.Content.parseFrom(plaintext.getData());
return new SignalServiceCipherResult(
content,
new EnvelopeMetadata(
plaintext.metadata.getSender().getServiceId(),
plaintext.metadata.getSender().getNumber().orElse(null),
plaintext.metadata.getSenderDevice(),
plaintext.metadata.isNeedsReceipt(),
plaintext.metadata.getGroupId().orElse(null),
localAddress.getServiceId()
)
);
} else {
return null;
}
} catch (InvalidProtocolBufferException e) {
throw new InvalidMetadataMessageException(e);
}
}
private Plaintext decryptInternal(Envelope envelope, long serverDeliveredTimestamp)
throws InvalidMetadataMessageException, InvalidMetadataVersionException,
ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException,
ProtocolLegacyMessageException, ProtocolInvalidKeyException,
@@ -175,30 +209,30 @@ public class SignalServiceCipher {
byte[] paddedMessage;
SignalServiceMetadata metadata;
if (!envelope.hasSourceUuid() && !envelope.isUnidentifiedSender()) {
if (!envelope.hasSourceUuid() && envelope.getType().getNumber() != Envelope.Type.UNIDENTIFIED_SENDER_VALUE) {
throw new InvalidMessageStructureException("Non-UD envelope is missing a UUID!");
}
if (envelope.isPreKeySignalMessage()) {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSourceUuid().get(), envelope.getSourceDevice());
if (envelope.getType().getNumber() == Envelope.Type.PREKEY_BUNDLE_VALUE) {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSourceUuid(), envelope.getSourceDevice());
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress));
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext));
metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid());
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(envelope.getContent().toByteArray()));
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid());
signalProtocolStore.clearSenderKeySharedWith(Collections.singleton(sourceAddress));
} else if (envelope.isSignalMessage()) {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSourceUuid().get(), envelope.getSourceDevice());
} else if (envelope.getType().getNumber() == Envelope.Type.CIPHERTEXT_VALUE) {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSourceUuid(), envelope.getSourceDevice());
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress));
paddedMessage = sessionCipher.decrypt(new SignalMessage(ciphertext));
metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid());
} else if (envelope.isPlaintextContent()) {
paddedMessage = new PlaintextContent(ciphertext).getBody();
metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid());
} else if (envelope.isUnidentifiedSender()) {
paddedMessage = sessionCipher.decrypt(new SignalMessage(envelope.getContent().toByteArray()));
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid());
} else if (envelope.getType().getNumber() == Envelope.Type.PLAINTEXT_CONTENT_VALUE) {
paddedMessage = new PlaintextContent(envelope.getContent().toByteArray()).getBody();
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid());
} else if (envelope.getType().getNumber() == Envelope.Type.UNIDENTIFIED_SENDER_VALUE) {
SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().uuid(), localAddress.getNumber().orElse(null), localDeviceId));
DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerReceivedTimestamp());
DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, envelope.getContent().toByteArray(), envelope.getServerTimestamp());
SignalServiceAddress resultAddress = new SignalServiceAddress(ACI.parseOrThrow(result.getSenderUuid()), result.getSenderE164());
Optional<byte[]> groupId = result.getGroupId();
boolean needsReceipt = true;
@@ -213,7 +247,7 @@ public class SignalServiceCipher {
}
paddedMessage = result.getPaddedMessage();
metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), needsReceipt, envelope.getServerGuid(), groupId, envelope.getDestinationUuid());
metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, needsReceipt, envelope.getServerGuid(), groupId, envelope.getDestinationUuid());
} else {
throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType());
}
@@ -223,24 +257,28 @@ public class SignalServiceCipher {
return new Plaintext(metadata, data);
} catch (DuplicateMessageException e) {
throw new ProtocolDuplicateMessageException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
throw new ProtocolDuplicateMessageException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
} catch (LegacyMessageException e) {
throw new ProtocolLegacyMessageException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
throw new ProtocolLegacyMessageException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
} catch (InvalidMessageException e) {
throw new ProtocolInvalidMessageException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
throw new ProtocolInvalidMessageException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
} catch (InvalidKeyIdException e) {
throw new ProtocolInvalidKeyIdException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
throw new ProtocolInvalidKeyIdException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
} catch (InvalidKeyException e) {
throw new ProtocolInvalidKeyException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
throw new ProtocolInvalidKeyException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
} catch (UntrustedIdentityException e) {
throw new ProtocolUntrustedIdentityException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
throw new ProtocolUntrustedIdentityException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
} catch (InvalidVersionException e) {
throw new ProtocolInvalidVersionException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
throw new ProtocolInvalidVersionException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
} catch (NoSessionException e) {
throw new ProtocolNoSessionException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
throw new ProtocolNoSessionException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
}
}
private static SignalServiceAddress getSourceAddress(Envelope envelope) {
return new SignalServiceAddress(ServiceId.parseOrNull(envelope.getSourceUuid()));
}
private static class Plaintext {
private final SignalServiceMetadata metadata;
private final byte[] data;

View File

@@ -0,0 +1,15 @@
package org.whispersystems.signalservice.api.crypto
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
/**
* Represents the output of decrypting a [SignalServiceProtos.Envelope] via [SignalServiceCipher.decrypt]
*
* @param content The [SignalServiceProtos.Content] that was decrypted from the envelope.
* @param metadata The decrypted metadata of the envelope. Represents sender information that may have
* been encrypted with sealed sender.
*/
data class SignalServiceCipherResult(
val content: SignalServiceProtos.Content,
val metadata: EnvelopeMetadata
)

View File

@@ -0,0 +1,270 @@
package org.whispersystems.signalservice.api.messages
import org.signal.libsignal.protocol.message.DecryptionErrorMessage
import org.signal.libsignal.zkgroup.InvalidInputException
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation
import org.whispersystems.signalservice.api.InvalidMessageStructureException
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer
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.ReceiptMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.StoryMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.TypingMessage
/**
* Validates an [Envelope] and its decrypted [Content] so that we know the message can be processed safely
* down the line.
*
* Mostly makes sure that UUIDs are valid, required fields are presents, etc.
*/
object EnvelopeContentValidator {
fun validate(envelope: Envelope, content: Content): Result {
return when {
envelope.story && !content.meetsStoryFlagCriteria() -> Result.Invalid("Envelope was flagged as a story, but it did not have any story-related content!")
content.hasDataMessage() -> validateDataMessage(envelope, content.dataMessage)
content.hasSyncMessage() -> validateSyncMessage(envelope, content.syncMessage)
content.hasCallMessage() -> Result.Valid
content.hasReceiptMessage() -> validateReceiptMessage(content.receiptMessage)
content.hasTypingMessage() -> validateTypingMessage(envelope, content.typingMessage)
content.hasDecryptionErrorMessage() -> validateDecryptionErrorMessage(content.decryptionErrorMessage.toByteArray())
content.hasStoryMessage() -> validateStoryMessage(content.storyMessage)
content.hasPniSignatureMessage() -> Result.Valid
content.hasSenderKeyDistributionMessage() -> Result.Valid
else -> Result.Invalid("Content is empty!")
}
}
private fun validateDataMessage(envelope: Envelope, dataMessage: DataMessage): Result {
if (dataMessage.requiredProtocolVersion > DataMessage.ProtocolVersion.CURRENT_VALUE) {
return Result.UnsupportedDataMessage(
ourVersion = DataMessage.ProtocolVersion.CURRENT_VALUE,
theirVersion = dataMessage.requiredProtocolVersion
)
}
if (!dataMessage.hasTimestamp()) {
return Result.Invalid("[DataMessage] Missing timestamp!")
}
if (dataMessage.timestamp != envelope.timestamp) {
Result.Invalid("[DataMessage] Timestamps don't match! envelope: ${envelope.timestamp}, content: ${dataMessage.timestamp}")
}
if (dataMessage.hasQuote() && dataMessage.quote.authorUuid.isNullOrInvalidUuid()) {
return Result.Invalid("[DataMessage] Invalid UUID on quote!")
}
if (dataMessage.contactList.any { it.hasAvatar() && it.avatar.avatar.isPresentAndInvalid() }) {
return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.contactList.avatar!")
}
if (dataMessage.previewList.any { it.hasImage() && it.image.isPresentAndInvalid() }) {
return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.previewList.image!")
}
if (dataMessage.bodyRangesList.any { it.hasMentionUuid() && it.mentionUuid.isNullOrInvalidUuid() }) {
return Result.Invalid("[DataMessage] Invalid UUID on body range!")
}
if (dataMessage.hasSticker() && dataMessage.sticker.data.isNullOrInvalid()) {
return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.sticker!")
}
if (dataMessage.hasReaction()) {
if (!dataMessage.reaction.hasTargetSentTimestamp()) {
return Result.Invalid("[DataMessage] Missing timestamp on DataMessage.reaction!")
}
if (dataMessage.reaction.targetAuthorUuid.isNullOrInvalidUuid()) {
return Result.Invalid("[DataMessage] Invalid UUID on DataMessage.reaction!")
}
}
if (dataMessage.hasDelete() && !dataMessage.delete.hasTargetSentTimestamp()) {
return Result.Invalid("[DataMessage] Missing timestamp on DataMessage.delete!")
}
if (dataMessage.hasStoryContext() && dataMessage.storyContext.authorUuid.isNullOrInvalidUuid()) {
return Result.Invalid("[DataMessage] Invalid UUID on DataMessage.storyContext!")
}
if (dataMessage.hasGiftBadge()) {
if (!dataMessage.giftBadge.hasReceiptCredentialPresentation()) {
return Result.Invalid("[DataMessage] Missing DataMessage.giftBadge.receiptCredentialPresentation!")
}
if (!dataMessage.giftBadge.hasReceiptCredentialPresentation()) {
try {
ReceiptCredentialPresentation(dataMessage.giftBadge.receiptCredentialPresentation.toByteArray())
} catch (e: InvalidInputException) {
return Result.Invalid("[DataMessage] Invalid DataMessage.giftBadge.receiptCredentialPresentation!")
}
}
}
if (dataMessage.attachmentsList.any { it.isNullOrInvalid() }) {
return Result.Invalid("[DataMessage] Invalid attachments!")
}
return Result.Valid
}
private fun validateSyncMessage(envelope: Envelope, syncMessage: SyncMessage): Result {
if (syncMessage.hasSent()) {
val validAddress = syncMessage.sent.destinationUuid.isValidUuid()
val hasDataGroup = syncMessage.sent.message?.hasGroupV2() ?: false
val hasStoryGroup = syncMessage.sent.storyMessage?.hasGroup() ?: false
val hasStoryManifest = syncMessage.sent.storyMessageRecipientsList.isNotEmpty()
if (hasDataGroup) {
validateGroupContextV2(syncMessage.sent.message.groupV2, "[SyncMessage.Sent.Message]")?.let { return it }
}
if (hasStoryGroup) {
validateGroupContextV2(syncMessage.sent.storyMessage.group, "[SyncMessage.Sent.StoryMessage]")?.let { return it }
}
if (!validAddress && !hasDataGroup && !hasStoryGroup && !hasStoryManifest) {
return Result.Invalid("[SyncMessage] No valid destination! Checked the destination, DataMessage.group, StoryMessage.group, and storyMessageRecipientList")
}
for (status in syncMessage.sent.unidentifiedStatusList) {
if (status.destinationUuid.isNullOrInvalidUuid()) {
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.sent.unidentifiedStatusList!")
}
}
return if (syncMessage.sent.hasMessage()) {
validateDataMessage(envelope, syncMessage.sent.message)
} else if (syncMessage.sent.hasStoryMessage()) {
validateStoryMessage(syncMessage.sent.storyMessage)
} else {
Result.Invalid("[SyncMessage] Empty SyncMessage.sent!")
}
}
if (syncMessage.readList.any { it.senderUuid.isNullOrInvalidUuid() }) {
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.readList!")
}
if (syncMessage.viewedList.any { it.senderUuid.isNullOrInvalidUuid() }) {
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.viewList!")
}
if (syncMessage.hasViewOnceOpen() && syncMessage.viewOnceOpen.senderUuid.isNullOrInvalidUuid()) {
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.viewOnceOpen!")
}
if (syncMessage.hasVerified() && syncMessage.verified.destinationUuid.isNullOrInvalidUuid()) {
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.verified!")
}
if (syncMessage.stickerPackOperationList.any { !it.hasPackId() }) {
return Result.Invalid("[SyncMessage] Missing packId in stickerPackOperationList!")
}
if (syncMessage.hasBlocked() && syncMessage.blocked.uuidsList.any { it.isNullOrInvalidUuid() }) {
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.blocked!")
}
if (syncMessage.hasMessageRequestResponse() && !syncMessage.messageRequestResponse.hasGroupId() && syncMessage.messageRequestResponse.threadUuid.isNullOrInvalidUuid()) {
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.messageRequestResponse!")
}
return Result.Valid
}
private fun validateReceiptMessage(receiptMessage: ReceiptMessage): Result {
return if (!receiptMessage.hasType()) {
Result.Invalid("[ReceiptMessage] Missing type!")
} else {
Result.Valid
}
}
private fun validateTypingMessage(envelope: Envelope, typingMessage: TypingMessage): Result {
return if (!typingMessage.hasTimestamp()) {
return Result.Invalid("[TypingMessage] Missing timestamp!")
} else if (typingMessage.hasTimestamp() && typingMessage.timestamp != envelope.timestamp) {
Result.Invalid("[TypingMessage] Timestamps don't match! envelope: ${envelope.timestamp}, content: ${typingMessage.timestamp}")
} else if (!typingMessage.hasAction()) {
Result.Invalid("[TypingMessage] Missing action!")
} else {
Result.Valid
}
}
private fun validateDecryptionErrorMessage(serializedDecryptionErrorMessage: ByteArray): Result {
return try {
DecryptionErrorMessage(serializedDecryptionErrorMessage)
Result.Valid
} catch (e: InvalidMessageStructureException) {
Result.Invalid("[DecryptionErrorMessage] Bad decryption error message!", e)
}
}
private fun validateStoryMessage(storyMessage: StoryMessage): Result {
if (storyMessage.hasGroup()) {
validateGroupContextV2(storyMessage.group, "[StoryMessage]")?.let { return it }
}
return Result.Valid
}
private fun AttachmentPointer?.isNullOrInvalid(): Boolean {
return this == null || this.attachmentIdentifierCase == AttachmentPointer.AttachmentIdentifierCase.ATTACHMENTIDENTIFIER_NOT_SET
}
private fun AttachmentPointer?.isPresentAndInvalid(): Boolean {
return this != null && this.attachmentIdentifierCase == AttachmentPointer.AttachmentIdentifierCase.ATTACHMENTIDENTIFIER_NOT_SET
}
private fun String?.isValidUuid(): Boolean {
return UuidUtil.isUuid(this)
}
private fun String?.isNullOrInvalidUuid(): Boolean {
return !UuidUtil.isUuid(this)
}
private fun Content?.meetsStoryFlagCriteria(): Boolean {
return when {
this == null -> false
this.hasSenderKeyDistributionMessage() -> true
this.hasStoryMessage() -> true
this.hasDataMessage() && this.dataMessage.hasStoryContext() && this.dataMessage.hasGroupV2() -> true
this.hasDataMessage() && this.dataMessage.hasDelete() -> true
else -> false
}
}
private fun validateGroupContextV2(groupContext: GroupContextV2, prefix: String): Result.Invalid? {
return if (!groupContext.hasMasterKey()) {
Result.Invalid("$prefix Missing GV2 master key!")
} else if (!groupContext.hasRevision()) {
Result.Invalid("$prefix Missing GV2 revision!")
} else {
try {
GroupMasterKey(groupContext.masterKey.toByteArray())
null
} catch (e: InvalidInputException) {
Result.Invalid("$prefix Bad GV2 master key!", e)
}
}
}
sealed class Result {
/** Content is valid. */
object Valid : Result()
/** The [DataMessage.requiredProtocolVersion_] is newer than the one we support. */
class UnsupportedDataMessage(val ourVersion: Int, val theirVersion: Int) : Result()
/** The contents of the proto do not match our expectations, e.g. invalid UUIDs, missing required fields, etc. */
class Invalid(val reason: String, val throwable: Throwable = Throwable()) : Result()
}
}

View File

@@ -271,6 +271,10 @@ public class SignalServiceEnvelope {
return envelope.getReportingToken().toByteArray();
}
public Envelope getProto() {
return envelope;
}
private SignalServiceEnvelopeProto.Builder serializeToProto() {
SignalServiceEnvelopeProto.Builder builder = SignalServiceEnvelopeProto.newBuilder()
.setType(getType())

View File

@@ -23,6 +23,10 @@ public final class PNI extends ServiceId {
return from(UUID.fromString(raw));
}
public static PNI parseOrThrow(byte[] raw) {
return from(UuidUtil.parseOrThrow(raw));
}
public static PNI parseOrNull(byte[] raw) {
UUID uuid = UuidUtil.parseOrNull(raw);
return uuid != null ? from(uuid) : null;