Update SignalService.proto to match shared one.

This commit is contained in:
Greyson Parrelli
2026-03-26 09:37:44 -04:00
committed by Alex Hart
parent 5ae51f844e
commit f04a0533cb
22 changed files with 413 additions and 345 deletions

View File

@@ -112,7 +112,6 @@ import org.whispersystems.signalservice.internal.push.OutgoingPushMessage;
import org.whispersystems.signalservice.internal.push.OutgoingPushMessageList;
import org.whispersystems.signalservice.internal.push.PniSignatureMessage;
import org.whispersystems.signalservice.internal.push.Preview;
import org.whispersystems.signalservice.internal.push.ProvisioningVersion;
import org.whispersystems.signalservice.internal.push.PushAttachmentData;
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
import org.whispersystems.signalservice.internal.push.ReceiptMessage;
@@ -1602,8 +1601,6 @@ public class SignalServiceMessageSender {
configurationMessage.linkPreviews(configuration.getLinkPreviews().get());
}
configurationMessage.provisioningVersion(ProvisioningVersion.CURRENT.getValue());
return container.syncMessage(syncMessage.configuration(configurationMessage.build()).build()).build();
}

View File

@@ -104,8 +104,8 @@ public interface EnvelopeContent {
int type;
switch (message.getType()) {
case CiphertextMessage.PREKEY_TYPE: type = Type.PREKEY_BUNDLE.getValue(); break;
case CiphertextMessage.WHISPER_TYPE: type = Type.CIPHERTEXT.getValue(); break;
case CiphertextMessage.PREKEY_TYPE: type = Type.PREKEY_MESSAGE.getValue(); break;
case CiphertextMessage.WHISPER_TYPE: type = Type.DOUBLE_RATCHET.getValue(); break;
default: throw new AssertionError("Bad type: " + message.getType());
}

View File

@@ -187,25 +187,25 @@ public class SignalServiceCipher {
throw new InvalidMessageStructureException("Non-UD envelope is missing a UUID!");
}
if (envelope.type == Envelope.Type.PREKEY_BUNDLE) {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(sourceServiceId.toString(), envelope.sourceDevice);
if (envelope.type == Envelope.Type.PREKEY_MESSAGE) {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(sourceServiceId.toString(), envelope.sourceDeviceId);
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress));
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(envelope.content.toByteArray()));
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDeviceId, envelope.clientTimestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
ciphertextMessageType = CiphertextMessage.PREKEY_TYPE;
signalProtocolStore.clearSenderKeySharedWith(Collections.singleton(sourceAddress));
} else if (envelope.type == Envelope.Type.CIPHERTEXT) {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(sourceServiceId.toString(), envelope.sourceDevice);
} else if (envelope.type == Envelope.Type.DOUBLE_RATCHET) {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(sourceServiceId.toString(), envelope.sourceDeviceId);
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress));
paddedMessage = sessionCipher.decrypt(new SignalMessage(envelope.content.toByteArray()));
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDeviceId, envelope.clientTimestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
ciphertextMessageType = CiphertextMessage.WHISPER_TYPE;
} else if (envelope.type == Envelope.Type.PLAINTEXT_CONTENT) {
paddedMessage = new PlaintextContent(envelope.content.toByteArray()).getBody();
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDeviceId, envelope.clientTimestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
ciphertextMessageType = CiphertextMessage.PLAINTEXT_CONTENT_TYPE;
} else if (envelope.type == Envelope.Type.UNIDENTIFIED_SENDER) {
SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().getRawUuid(), localAddress.getNumber().orElse(null), localDeviceId));
@@ -215,7 +215,7 @@ public class SignalServiceCipher {
boolean needsReceipt = true;
if (sourceServiceId != null) {
Log.w(TAG, "[" + envelope.timestamp + "] Received a UD-encrypted message sent over an identified channel. Marking as needsReceipt=false");
Log.w(TAG, "[" + envelope.clientTimestamp + "] Received a UD-encrypted message sent over an identified channel. Marking as needsReceipt=false");
needsReceipt = false;
}
@@ -226,7 +226,7 @@ public class SignalServiceCipher {
}
paddedMessage = result.getPaddedMessage();
metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, needsReceipt, serverGuid, groupId, destinationStr);
metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.clientTimestamp, envelope.serverTimestamp, serverDeliveredTimestamp, needsReceipt, serverGuid, groupId, destinationStr);
} else {
throw new InvalidMetadataMessageException("Unknown type: " + envelope.type);
}
@@ -236,21 +236,21 @@ public class SignalServiceCipher {
return new Plaintext(metadata, data, ciphertextMessageType);
} catch (DuplicateMessageException e) {
throw new ProtocolDuplicateMessageException(e, sourceServiceId.toString(), envelope.sourceDevice);
throw new ProtocolDuplicateMessageException(e, sourceServiceId.toString(), envelope.sourceDeviceId);
} catch (LegacyMessageException e) {
throw new ProtocolLegacyMessageException(e, sourceServiceId.toString(), envelope.sourceDevice);
throw new ProtocolLegacyMessageException(e, sourceServiceId.toString(), envelope.sourceDeviceId);
} catch (InvalidMessageException e) {
throw new ProtocolInvalidMessageException(e, sourceServiceId.toString(), envelope.sourceDevice);
throw new ProtocolInvalidMessageException(e, sourceServiceId.toString(), envelope.sourceDeviceId);
} catch (InvalidKeyIdException e) {
throw new ProtocolInvalidKeyIdException(e, sourceServiceId.toString(), envelope.sourceDevice);
throw new ProtocolInvalidKeyIdException(e, sourceServiceId.toString(), envelope.sourceDeviceId);
} catch (InvalidKeyException e) {
throw new ProtocolInvalidKeyException(e, sourceServiceId.toString(), envelope.sourceDevice);
throw new ProtocolInvalidKeyException(e, sourceServiceId.toString(), envelope.sourceDeviceId);
} catch (UntrustedIdentityException e) {
throw new ProtocolUntrustedIdentityException(e, sourceServiceId.toString(), envelope.sourceDevice);
throw new ProtocolUntrustedIdentityException(e, sourceServiceId.toString(), envelope.sourceDeviceId);
} catch (InvalidVersionException e) {
throw new ProtocolInvalidVersionException(e, sourceServiceId.toString(), envelope.sourceDevice);
throw new ProtocolInvalidVersionException(e, sourceServiceId.toString(), envelope.sourceDeviceId);
} catch (NoSessionException e) {
throw new ProtocolNoSessionException(e, sourceServiceId.toString(), envelope.sourceDevice);
throw new ProtocolNoSessionException(e, sourceServiceId.toString(), envelope.sourceDeviceId);
}
}

View File

@@ -83,8 +83,8 @@ object EnvelopeContentValidator {
return Result.Invalid("[DataMessage] Missing timestamp!")
}
if (dataMessage.timestamp != envelope.timestamp) {
return Result.Invalid("[DataMessage] Timestamps don't match! envelope: ${envelope.timestamp}, content: ${dataMessage.timestamp}")
if (dataMessage.timestamp != envelope.clientTimestamp) {
return Result.Invalid("[DataMessage] Timestamps don't match! envelope: ${envelope.clientTimestamp}, content: ${dataMessage.timestamp}")
}
if (dataMessage.quote != null && ACI.parseOrNull(dataMessage.quote.authorAci, dataMessage.quote.authorAciBinary).isNullOrInvalidServiceId()) {
@@ -276,8 +276,8 @@ object EnvelopeContentValidator {
private fun validateTypingMessage(envelope: Envelope, typingMessage: TypingMessage): Result {
return if (typingMessage.timestamp == null) {
return Result.Invalid("[TypingMessage] Missing timestamp!")
} else if (typingMessage.timestamp != envelope.timestamp) {
Result.Invalid("[TypingMessage] Timestamps don't match! envelope: ${envelope.timestamp}, content: ${typingMessage.timestamp}")
} else if (typingMessage.timestamp != envelope.clientTimestamp) {
Result.Invalid("[TypingMessage] Timestamps don't match! envelope: ${envelope.clientTimestamp}, content: ${typingMessage.timestamp}")
} else if (typingMessage.action == null) {
Result.Invalid("[TypingMessage] Missing action!")
} else {

View File

@@ -13,23 +13,79 @@ option java_outer_classname = "SignalServiceProtos";
message Envelope {
enum Type {
UNKNOWN = 0;
CIPHERTEXT = 1; // content => (version byte | SignalMessage{Content})
/**
* A double-ratchet message represents a "normal," "unsealed-sender" message
* encrypted using the Double Ratchet within an established Signal session.
* Double-ratchet messages include sender information in the plaintext
* portion of the `Envelope`.
*/
DOUBLE_RATCHET = 1; // content => (version byte | SignalMessage{Content})
reserved 2;
reserved "KEY_EXCHANGE";
PREKEY_BUNDLE = 3; // content => (version byte | PreKeySignalMessage{Content})
SERVER_DELIVERY_RECEIPT = 5; // legacyMessage => [] AND content => []
UNIDENTIFIED_SENDER = 6; // legacyMessage => [] AND content => ((version byte | UnidentifiedSenderMessage) OR (version byte | Multi-Recipient Sealed Sender Format))
SENDERKEY_MESSAGE = 7; // legacyMessage => [] AND content => (version byte | SenderKeyMessage)
PLAINTEXT_CONTENT = 8; // legacyMessage => [] AND content => (marker byte | Content)
/**
* A prekey message begins a new Signal session. The `content` of a prekey
* message is a superset of a double-ratchet message's `content` and
* contains the sender's identity public key and information identifying the
* pre-keys used in the message's ciphertext. Like double-ratchet messages,
* prekey messages contain sender information in the plaintext portion of
* the `Envelope`.
*/
PREKEY_MESSAGE = 3; // content => (version byte | PreKeySignalMessage{Content})
/**
* Server delivery receipts are generated by the server when
* "unsealed-sender" messages are delivered to and acknowledged by the
* destination device. Server delivery receipts identify the sender in the
* plaintext portion of the `Envelope` and have no `content`. Note that
* receipts for sealed-sender messages are generated by clients as
* `UNIDENTIFIED_SENDER` messages.
*
* Note that, with server delivery receipts, the "client timestamp" on
* the envelope refers to the timestamp of the original message (i.e. the
* message the server just delivered) and not to the time of delivery. The
* "server timestamp" refers to the time of delivery.
*/
SERVER_DELIVERY_RECEIPT = 5; // content => []
/**
* An unidentified sender message represents a message with no sender
* information in the plaintext portion of the `Envelope`. Unidentified
* sender messages always contain an additional `subtype` in their
* `content`. They may or may not be part of an existing Signal session
* (i.e. an unidentified sender message may have a "prekey message"
* subtype or may indicate an encryption error).
*/
UNIDENTIFIED_SENDER = 6; // content => ((version byte | UnidentifiedSenderMessage) OR (version byte | Multi-Recipient Sealed Sender Format))
reserved 7;
reserved "SENDERKEY_MESSAGE";
/**
* A plaintext message is used solely to convey encryption error receipts
* and never contains encrypted message content. Encryption error receipts
* must be delivered in plaintext because, encryption/decryption of a prior
* message failed and there is no reason to believe that
* encryption/decryption of subsequent messages with the same key material
* would succeed.
*
* Critically, plaintext messages never have "real" message content
* generated by users. Plaintext messages include sender information.
*/
PLAINTEXT_CONTENT = 8; // content => (marker byte | Content)
// next: 9
}
optional Type type = 1;
reserved 2; // formerly optional string sourceE164 = 2;
optional string sourceServiceId = 11;
optional uint32 sourceDevice = 7;
optional uint32 sourceDeviceId = 7;
optional string destinationServiceId = 13;
reserved 3; // formerly optional string relay = 3;
optional uint64 timestamp = 5;
optional uint64 clientTimestamp = 5;
reserved 6; // formerly optional bytes legacyMessage = 6; // Contains an encrypted DataMessage; this field could have been set historically for type 1 or 3 messages; no longer in use
optional bytes content = 8; // Contains an encrypted Content
optional string serverGuid = 9;
@@ -48,17 +104,20 @@ message Envelope {
}
message Content {
optional DataMessage dataMessage = 1;
optional SyncMessage syncMessage = 2;
optional CallMessage callMessage = 3;
optional NullMessage nullMessage = 4;
optional ReceiptMessage receiptMessage = 5;
optional TypingMessage typingMessage = 6;
oneof content {
DataMessage dataMessage = 1;
SyncMessage syncMessage = 2;
CallMessage callMessage = 3;
NullMessage nullMessage = 4;
ReceiptMessage receiptMessage = 5;
TypingMessage typingMessage = 6;
bytes /* DecryptionErrorMessage */ decryptionErrorMessage = 8;
StoryMessage storyMessage = 9;
EditMessage editMessage = 11;
}
optional bytes /* SenderKeyDistributionMessage */ senderKeyDistributionMessage = 7;
optional bytes /* DecryptionErrorMessage */ decryptionErrorMessage = 8;
optional StoryMessage storyMessage = 9;
optional PniSignatureMessage pniSignatureMessage = 10;
optional EditMessage editMessage = 11;
}
message CallMessage {
@@ -331,8 +390,8 @@ message DataMessage {
message PollVote {
optional bytes targetAuthorAciBinary = 1;
optional uint64 targetSentTimestamp = 2;
repeated uint32 optionIndexes = 3; // must be in the range [0, options.length) from the PollCreate
optional uint32 voteCount = 4; // increment this by 1 each time you vote on a given poll
repeated uint32 optionIndexes = 3;
optional uint32 voteCount = 4;
}
message PinMessage {
@@ -441,6 +500,12 @@ message TextAttachment {
}
message Gradient {
// Color ordering:
// 0 degrees: bottom-to-top
// 90 degrees: left-to-right
// 180 degrees: top-to-bottom
// 270 degrees: right-to-left
optional uint32 startColor = 1; // deprecated: this field will be removed in a future release.
optional uint32 endColor = 2; // deprecated: this field will be removed in a future release.
optional uint32 angle = 3; // degrees
@@ -553,7 +618,7 @@ message SyncMessage {
optional bool unidentifiedDeliveryIndicators = 2;
optional bool typingIndicators = 3;
reserved /* linkPreviews */ 4;
optional uint32 provisioningVersion = 5;
reserved /* provisioningVersion */ 5;
optional bool linkPreviews = 6;
}
@@ -688,7 +753,7 @@ message SyncMessage {
optional bytes rootKey = 1;
optional bytes adminPasskey = 2;
optional Type type = 3; // defaults to UPDATE
reserved 4; // was epoch field, never used
reserved /*epoch*/ 4;
}
message CallLogEvent {
@@ -785,31 +850,40 @@ message SyncMessage {
}
}
optional Sent sent = 1;
optional Contacts contacts = 2;
oneof content {
Sent sent = 1;
Contacts contacts = 2;
Request request = 4;
Blocked blocked = 6;
Verified verified = 7;
Configuration configuration = 9;
ViewOnceOpen viewOnceOpen = 11;
FetchLatest fetchLatest = 12;
Keys keys = 13;
MessageRequestResponse messageRequestResponse = 14;
OutgoingPayment outgoingPayment = 15;
PniChangeNumber pniChangeNumber = 18;
CallEvent callEvent = 19;
CallLinkUpdate callLinkUpdate = 20;
CallLogEvent callLogEvent = 21;
DeleteForMe deleteForMe = 22;
DeviceNameChange deviceNameChange = 23;
AttachmentBackfillRequest attachmentBackfillRequest = 24;
AttachmentBackfillResponse attachmentBackfillResponse = 25;
}
reserved /*groups*/ 3;
optional Request request = 4;
// Protobufs don't allow `repeated` fields to be inside of `oneof` so while
// the fields below are mutually exclusive with the rest of the values above
// we have to place them outside of `oneof`.
repeated Read read = 5;
optional Blocked blocked = 6;
optional Verified verified = 7;
optional Configuration configuration = 9;
optional bytes padding = 8;
repeated StickerPackOperation stickerPackOperation = 10;
optional ViewOnceOpen viewOnceOpen = 11;
optional FetchLatest fetchLatest = 12;
optional Keys keys = 13;
optional MessageRequestResponse messageRequestResponse = 14;
optional OutgoingPayment outgoingPayment = 15;
repeated Viewed viewed = 16;
reserved /*pniIdentity*/ 17;
optional PniChangeNumber pniChangeNumber = 18;
optional CallEvent callEvent = 19;
optional CallLinkUpdate callLinkUpdate = 20;
optional CallLogEvent callLogEvent = 21;
optional DeleteForMe deleteForMe = 22;
optional DeviceNameChange deviceNameChange = 23;
optional AttachmentBackfillRequest attachmentBackfillRequest = 24;
optional AttachmentBackfillResponse attachmentBackfillResponse = 25;
optional bytes padding = 8;
}
message AttachmentPointer {

View File

@@ -16,7 +16,7 @@ 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.GroupContextV2
import org.whispersystems.signalservice.internal.push.SyncMessage
import org.whispersystems.signalservice.internal.push.PniSignatureMessage
class BuildSizeTreeTest {
@@ -136,12 +136,12 @@ class BuildSizeTreeTest {
fun `multiple top-level fields are all included`() {
val msg = Content(
dataMessage = DataMessage(body = "hi"),
syncMessage = SyncMessage()
pniSignatureMessage = PniSignatureMessage()
)
val tree = msg.buildSizeTree("Content")
assertThat(tree).contains("dataMessage(")
assertThat(tree).contains("syncMessage(")
assertThat(tree).contains("pniSignatureMessage(")
}
@Test

View File

@@ -23,7 +23,7 @@ class EnvelopeContentValidatorTest {
@Test
fun `validate - ensure mismatched timestamps are marked invalid`() {
val envelope = Envelope(
timestamp = 1234
clientTimestamp = 1234
)
val content = Content(
@@ -172,11 +172,10 @@ class EnvelopeContentValidatorTest {
fun `validate - plaintext content via envelope type with unexpected DataMessage is invalid`() {
val envelope = Envelope(
type = Envelope.Type.PLAINTEXT_CONTENT,
timestamp = 1234
clientTimestamp = 1234
)
val content = Content(
decryptionErrorMessage = createValidDecryptionErrorMessage(),
dataMessage = DataMessage(timestamp = 1234)
)
@@ -188,11 +187,10 @@ class EnvelopeContentValidatorTest {
fun `validate - plaintext content via ciphertext message type (sealed sender) with unexpected DataMessage is invalid`() {
val envelope = Envelope(
type = Envelope.Type.UNIDENTIFIED_SENDER,
timestamp = 1234
clientTimestamp = 1234
)
val content = Content(
decryptionErrorMessage = createValidDecryptionErrorMessage(),
dataMessage = DataMessage(timestamp = 1234)
)
@@ -231,7 +229,6 @@ class EnvelopeContentValidatorTest {
)
val content = Content(
decryptionErrorMessage = createValidDecryptionErrorMessage(),
syncMessage = org.whispersystems.signalservice.internal.push.SyncMessage()
)
@@ -242,8 +239,8 @@ class EnvelopeContentValidatorTest {
@Test
fun `validate - regular encrypted message is not subject to plaintext validation`() {
val envelope = Envelope(
type = Envelope.Type.CIPHERTEXT,
timestamp = 1234
type = Envelope.Type.DOUBLE_RATCHET,
clientTimestamp = 1234
)
val content = Content(