mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-30 03:20:45 +00:00
Support for multi-device message and contact sync.
// FREEBIE
This commit is contained in:
parent
1cdffebf6f
commit
0437bde205
@ -4,7 +4,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:1.1.0'
|
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
subprojects {
|
subprojects {
|
||||||
ext.version_number = "1.5.0"
|
ext.version_number = "1.6.0-RC4"
|
||||||
ext.group_info = "org.whispersystems"
|
ext.group_info = "org.whispersystems"
|
||||||
ext.axolotl_version = "1.3.1"
|
ext.axolotl_version = "1.3.1"
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ package org.whispersystems.textsecure.api;
|
|||||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||||
import org.whispersystems.textsecure.api.crypto.AttachmentCipherInputStream;
|
import org.whispersystems.textsecure.api.crypto.AttachmentCipherInputStream;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
|
||||||
|
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
|
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
|
||||||
import org.whispersystems.textsecure.api.push.TrustStore;
|
import org.whispersystems.textsecure.api.push.TrustStore;
|
||||||
import org.whispersystems.textsecure.api.util.CredentialsProvider;
|
import org.whispersystems.textsecure.api.util.CredentialsProvider;
|
||||||
@ -80,7 +81,7 @@ public class TextSecureMessageReceiver {
|
|||||||
* Retrieves a TextSecure attachment.
|
* Retrieves a TextSecure attachment.
|
||||||
*
|
*
|
||||||
* @param pointer The {@link org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer}
|
* @param pointer The {@link org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer}
|
||||||
* received in a {@link org.whispersystems.textsecure.api.messages.TextSecureMessage}.
|
* received in a {@link TextSecureDataMessage}.
|
||||||
* @param destination The download destination for this attachment.
|
* @param destination The download destination for this attachment.
|
||||||
*
|
*
|
||||||
* @return An InputStream that streams the plaintext attachment contents.
|
* @return An InputStream that streams the plaintext attachment contents.
|
||||||
@ -119,7 +120,8 @@ public class TextSecureMessageReceiver {
|
|||||||
for (TextSecureEnvelopeEntity entity : entities) {
|
for (TextSecureEnvelopeEntity entity : entities) {
|
||||||
TextSecureEnvelope envelope = new TextSecureEnvelope(entity.getType(), entity.getSource(),
|
TextSecureEnvelope envelope = new TextSecureEnvelope(entity.getType(), entity.getSource(),
|
||||||
entity.getSourceDevice(), entity.getRelay(),
|
entity.getSourceDevice(), entity.getRelay(),
|
||||||
entity.getTimestamp(), entity.getMessage());
|
entity.getTimestamp(), entity.getMessage(),
|
||||||
|
entity.getContent());
|
||||||
|
|
||||||
callback.onMessage(envelope);
|
callback.onMessage(envelope);
|
||||||
results.add(envelope);
|
results.add(envelope);
|
||||||
|
@ -30,8 +30,8 @@ import org.whispersystems.textsecure.api.crypto.TextSecureCipher;
|
|||||||
import org.whispersystems.textsecure.api.crypto.UntrustedIdentityException;
|
import org.whispersystems.textsecure.api.crypto.UntrustedIdentityException;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream;
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream;
|
||||||
|
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
|
|
||||||
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
||||||
import org.whispersystems.textsecure.api.push.TrustStore;
|
import org.whispersystems.textsecure.api.push.TrustStore;
|
||||||
import org.whispersystems.textsecure.api.push.exceptions.EncapsulatedExceptions;
|
import org.whispersystems.textsecure.api.push.exceptions.EncapsulatedExceptions;
|
||||||
@ -42,10 +42,14 @@ import org.whispersystems.textsecure.internal.push.MismatchedDevices;
|
|||||||
import org.whispersystems.textsecure.internal.push.OutgoingPushMessage;
|
import org.whispersystems.textsecure.internal.push.OutgoingPushMessage;
|
||||||
import org.whispersystems.textsecure.internal.push.OutgoingPushMessageList;
|
import org.whispersystems.textsecure.internal.push.OutgoingPushMessageList;
|
||||||
import org.whispersystems.textsecure.internal.push.PushAttachmentData;
|
import org.whispersystems.textsecure.internal.push.PushAttachmentData;
|
||||||
import org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext;
|
|
||||||
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
|
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
|
||||||
import org.whispersystems.textsecure.internal.push.SendMessageResponse;
|
import org.whispersystems.textsecure.internal.push.SendMessageResponse;
|
||||||
import org.whispersystems.textsecure.internal.push.StaleDevices;
|
import org.whispersystems.textsecure.internal.push.StaleDevices;
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos.AttachmentPointer;
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos.Content;
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos.DataMessage;
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos.GroupContext;
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos.SyncMessage;
|
||||||
import org.whispersystems.textsecure.internal.push.exceptions.MismatchedDevicesException;
|
import org.whispersystems.textsecure.internal.push.exceptions.MismatchedDevicesException;
|
||||||
import org.whispersystems.textsecure.internal.push.exceptions.StaleDevicesException;
|
import org.whispersystems.textsecure.internal.push.exceptions.StaleDevicesException;
|
||||||
import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider;
|
import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider;
|
||||||
@ -55,10 +59,6 @@ import java.io.IOException;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent;
|
|
||||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.AttachmentPointer;
|
|
||||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main interface for sending TextSecure messages.
|
* The main interface for sending TextSecure messages.
|
||||||
*
|
*
|
||||||
@ -114,16 +114,16 @@ public class TextSecureMessageSender {
|
|||||||
* @throws UntrustedIdentityException
|
* @throws UntrustedIdentityException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void sendMessage(TextSecureAddress recipient, TextSecureMessage message)
|
public void sendMessage(TextSecureAddress recipient, TextSecureDataMessage message)
|
||||||
throws UntrustedIdentityException, IOException
|
throws UntrustedIdentityException, IOException
|
||||||
{
|
{
|
||||||
byte[] content = createMessageContent(message);
|
byte[] content = createMessageContent(message);
|
||||||
long timestamp = message.getTimestamp();
|
long timestamp = message.getTimestamp();
|
||||||
SendMessageResponse response = sendMessage(recipient, timestamp, content);
|
SendMessageResponse response = sendMessage(recipient, timestamp, content, true);
|
||||||
|
|
||||||
if (response != null && response.getNeedsSync()) {
|
if (response != null && response.getNeedsSync()) {
|
||||||
byte[] syncMessage = createSyncMessageContent(content, Optional.of(recipient), timestamp);
|
byte[] syncMessage = createSentTranscriptMessage(content, Optional.of(recipient), timestamp);
|
||||||
sendMessage(localAddress, timestamp, syncMessage);
|
sendMessage(localAddress, timestamp, syncMessage, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.isEndSession()) {
|
if (message.isEndSession()) {
|
||||||
@ -143,25 +143,32 @@ public class TextSecureMessageSender {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws EncapsulatedExceptions
|
* @throws EncapsulatedExceptions
|
||||||
*/
|
*/
|
||||||
public void sendMessage(List<TextSecureAddress> recipients, TextSecureMessage message)
|
public void sendMessage(List<TextSecureAddress> recipients, TextSecureDataMessage message)
|
||||||
throws IOException, EncapsulatedExceptions
|
throws IOException, EncapsulatedExceptions
|
||||||
{
|
{
|
||||||
byte[] content = createMessageContent(message);
|
byte[] content = createMessageContent(message);
|
||||||
long timestamp = message.getTimestamp();
|
long timestamp = message.getTimestamp();
|
||||||
SendMessageResponse response = sendMessage(recipients, timestamp, content);
|
SendMessageResponse response = sendMessage(recipients, timestamp, content, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (response != null && response.getNeedsSync()) {
|
if (response != null && response.getNeedsSync()) {
|
||||||
byte[] syncMessage = createSyncMessageContent(content, Optional.<TextSecureAddress>absent(), timestamp);
|
byte[] syncMessage = createSentTranscriptMessage(content, Optional.<TextSecureAddress>absent(), timestamp);
|
||||||
sendMessage(localAddress, timestamp, syncMessage);
|
sendMessage(localAddress, timestamp, syncMessage, false);
|
||||||
}
|
}
|
||||||
} catch (UntrustedIdentityException e) {
|
} catch (UntrustedIdentityException e) {
|
||||||
throw new EncapsulatedExceptions(e);
|
throw new EncapsulatedExceptions(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] createMessageContent(TextSecureMessage message) throws IOException {
|
public void sendMultiDeviceContactsUpdate(TextSecureAttachmentStream contacts)
|
||||||
PushMessageContent.Builder builder = PushMessageContent.newBuilder();
|
throws IOException, UntrustedIdentityException
|
||||||
|
{
|
||||||
|
byte[] content = createMultiDeviceContactsContent(contacts);
|
||||||
|
sendMessage(localAddress, System.currentTimeMillis(), content, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] createMessageContent(TextSecureDataMessage message) throws IOException {
|
||||||
|
DataMessage.Builder builder = DataMessage.newBuilder();
|
||||||
List<AttachmentPointer> pointers = createAttachmentPointers(message.getAttachments());
|
List<AttachmentPointer> pointers = createAttachmentPointers(message.getAttachments());
|
||||||
|
|
||||||
if (!pointers.isEmpty()) {
|
if (!pointers.isEmpty()) {
|
||||||
@ -177,25 +184,34 @@ public class TextSecureMessageSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (message.isEndSession()) {
|
if (message.isEndSession()) {
|
||||||
builder.setFlags(PushMessageContent.Flags.END_SESSION_VALUE);
|
builder.setFlags(DataMessage.Flags.END_SESSION_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.build().toByteArray();
|
return builder.build().toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] createSyncMessageContent(byte[] content, Optional<TextSecureAddress> recipient, long timestamp) {
|
private byte[] createMultiDeviceContactsContent(TextSecureAttachmentStream contacts) throws IOException {
|
||||||
|
SyncMessage.Builder builder = SyncMessage.newBuilder();
|
||||||
|
builder.setContacts(SyncMessage.Contacts.newBuilder()
|
||||||
|
.setBlob(createAttachmentPointer(contacts)));
|
||||||
|
|
||||||
|
return builder.build().toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] createSentTranscriptMessage(byte[] content, Optional<TextSecureAddress> recipient, long timestamp) {
|
||||||
try {
|
try {
|
||||||
SyncMessageContext.Builder syncMessageContext = SyncMessageContext.newBuilder();
|
Content.Builder container = Content.newBuilder();
|
||||||
syncMessageContext.setTimestamp(timestamp);
|
SyncMessage.Builder syncMessage = SyncMessage.newBuilder();
|
||||||
|
SyncMessage.Sent.Builder sentMessage = SyncMessage.Sent.newBuilder();
|
||||||
|
|
||||||
|
sentMessage.setTimestamp(timestamp);
|
||||||
|
sentMessage.setMessage(DataMessage.parseFrom(content));
|
||||||
|
|
||||||
if (recipient.isPresent()) {
|
if (recipient.isPresent()) {
|
||||||
syncMessageContext.setDestination(recipient.get().getNumber());
|
sentMessage.setDestination(recipient.get().getNumber());
|
||||||
}
|
}
|
||||||
|
|
||||||
PushMessageContent.Builder builder = PushMessageContent.parseFrom(content).toBuilder();
|
return container.setSyncMessage(syncMessage.setSent(sentMessage)).build().toByteArray();
|
||||||
builder.setSync(syncMessageContext.build());
|
|
||||||
|
|
||||||
return builder.build().toByteArray();
|
|
||||||
} catch (InvalidProtocolBufferException e) {
|
} catch (InvalidProtocolBufferException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
@ -224,7 +240,7 @@ public class TextSecureMessageSender {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SendMessageResponse sendMessage(List<TextSecureAddress> recipients, long timestamp, byte[] content)
|
private SendMessageResponse sendMessage(List<TextSecureAddress> recipients, long timestamp, byte[] content, boolean legacy)
|
||||||
throws IOException, EncapsulatedExceptions
|
throws IOException, EncapsulatedExceptions
|
||||||
{
|
{
|
||||||
List<UntrustedIdentityException> untrustedIdentities = new LinkedList<>();
|
List<UntrustedIdentityException> untrustedIdentities = new LinkedList<>();
|
||||||
@ -235,7 +251,7 @@ public class TextSecureMessageSender {
|
|||||||
|
|
||||||
for (TextSecureAddress recipient : recipients) {
|
for (TextSecureAddress recipient : recipients) {
|
||||||
try {
|
try {
|
||||||
response = sendMessage(recipient, timestamp, content);
|
response = sendMessage(recipient, timestamp, content, legacy);
|
||||||
} catch (UntrustedIdentityException e) {
|
} catch (UntrustedIdentityException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
untrustedIdentities.add(e);
|
untrustedIdentities.add(e);
|
||||||
@ -255,12 +271,12 @@ public class TextSecureMessageSender {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SendMessageResponse sendMessage(TextSecureAddress recipient, long timestamp, byte[] content)
|
private SendMessageResponse sendMessage(TextSecureAddress recipient, long timestamp, byte[] content, boolean legacy)
|
||||||
throws UntrustedIdentityException, IOException
|
throws UntrustedIdentityException, IOException
|
||||||
{
|
{
|
||||||
for (int i=0;i<3;i++) {
|
for (int i=0;i<3;i++) {
|
||||||
try {
|
try {
|
||||||
OutgoingPushMessageList messages = getEncryptedMessages(socket, recipient, timestamp, content);
|
OutgoingPushMessageList messages = getEncryptedMessages(socket, recipient, timestamp, content, legacy);
|
||||||
return socket.sendMessage(messages);
|
return socket.sendMessage(messages);
|
||||||
} catch (MismatchedDevicesException mde) {
|
} catch (MismatchedDevicesException mde) {
|
||||||
Log.w(TAG, mde);
|
Log.w(TAG, mde);
|
||||||
@ -314,23 +330,24 @@ public class TextSecureMessageSender {
|
|||||||
private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket,
|
private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket,
|
||||||
TextSecureAddress recipient,
|
TextSecureAddress recipient,
|
||||||
long timestamp,
|
long timestamp,
|
||||||
byte[] plaintext)
|
byte[] plaintext,
|
||||||
|
boolean legacy)
|
||||||
throws IOException, UntrustedIdentityException
|
throws IOException, UntrustedIdentityException
|
||||||
{
|
{
|
||||||
List<OutgoingPushMessage> messages = new LinkedList<>();
|
List<OutgoingPushMessage> messages = new LinkedList<>();
|
||||||
|
|
||||||
if (!recipient.equals(localAddress)) {
|
if (!recipient.equals(localAddress)) {
|
||||||
messages.add(getEncryptedMessage(socket, recipient, TextSecureAddress.DEFAULT_DEVICE_ID, plaintext));
|
messages.add(getEncryptedMessage(socket, recipient, TextSecureAddress.DEFAULT_DEVICE_ID, plaintext, legacy));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int deviceId : store.getSubDeviceSessions(recipient.getNumber())) {
|
for (int deviceId : store.getSubDeviceSessions(recipient.getNumber())) {
|
||||||
messages.add(getEncryptedMessage(socket, recipient, deviceId, plaintext));
|
messages.add(getEncryptedMessage(socket, recipient, deviceId, plaintext, legacy));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OutgoingPushMessageList(recipient.getNumber(), timestamp, recipient.getRelay().orNull(), messages);
|
return new OutgoingPushMessageList(recipient.getNumber(), timestamp, recipient.getRelay().orNull(), messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OutgoingPushMessage getEncryptedMessage(PushServiceSocket socket, TextSecureAddress recipient, int deviceId, byte[] plaintext)
|
private OutgoingPushMessage getEncryptedMessage(PushServiceSocket socket, TextSecureAddress recipient, int deviceId, byte[] plaintext, boolean legacy)
|
||||||
throws IOException, UntrustedIdentityException
|
throws IOException, UntrustedIdentityException
|
||||||
{
|
{
|
||||||
AxolotlAddress axolotlAddress = new AxolotlAddress(recipient.getNumber(), deviceId);
|
AxolotlAddress axolotlAddress = new AxolotlAddress(recipient.getNumber(), deviceId);
|
||||||
@ -358,7 +375,7 @@ public class TextSecureMessageSender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cipher.encrypt(axolotlAddress, plaintext);
|
return cipher.encrypt(axolotlAddress, plaintext, legacy);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMismatchedDevices(PushServiceSocket socket, TextSecureAddress recipient,
|
private void handleMismatchedDevices(PushServiceSocket socket, TextSecureAddress recipient,
|
||||||
|
@ -28,27 +28,33 @@ import org.whispersystems.libaxolotl.LegacyMessageException;
|
|||||||
import org.whispersystems.libaxolotl.NoSessionException;
|
import org.whispersystems.libaxolotl.NoSessionException;
|
||||||
import org.whispersystems.libaxolotl.SessionCipher;
|
import org.whispersystems.libaxolotl.SessionCipher;
|
||||||
import org.whispersystems.libaxolotl.UntrustedIdentityException;
|
import org.whispersystems.libaxolotl.UntrustedIdentityException;
|
||||||
|
import org.whispersystems.libaxolotl.logging.Log;
|
||||||
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
||||||
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
|
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
|
||||||
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
|
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
|
||||||
import org.whispersystems.libaxolotl.state.AxolotlStore;
|
import org.whispersystems.libaxolotl.state.AxolotlStore;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
|
||||||
|
import org.whispersystems.textsecure.api.messages.TextSecureContent;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
|
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
|
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureSyncContext;
|
import org.whispersystems.textsecure.api.messages.multidevice.SentTranscriptMessage;
|
||||||
|
import org.whispersystems.textsecure.api.messages.multidevice.TextSecureSyncMessage;
|
||||||
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
||||||
import org.whispersystems.textsecure.internal.push.OutgoingPushMessage;
|
import org.whispersystems.textsecure.internal.push.OutgoingPushMessage;
|
||||||
import org.whispersystems.textsecure.internal.push.PushTransportDetails;
|
import org.whispersystems.textsecure.internal.push.PushTransportDetails;
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos.AttachmentPointer;
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos.Content;
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos.DataMessage;
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos.Envelope.Type;
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos.SyncMessage;
|
||||||
import org.whispersystems.textsecure.internal.util.Base64;
|
import org.whispersystems.textsecure.internal.util.Base64;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.IncomingPushMessageSignal.Type;
|
import static org.whispersystems.textsecure.internal.push.TextSecureProtos.GroupContext.Type.DELIVER;
|
||||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent;
|
|
||||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext.Type.DELIVER;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is used to decrypt received {@link org.whispersystems.textsecure.api.messages.TextSecureEnvelope}s.
|
* This is used to decrypt received {@link org.whispersystems.textsecure.api.messages.TextSecureEnvelope}s.
|
||||||
@ -57,6 +63,8 @@ import static org.whispersystems.textsecure.internal.push.PushMessageProtos.Push
|
|||||||
*/
|
*/
|
||||||
public class TextSecureCipher {
|
public class TextSecureCipher {
|
||||||
|
|
||||||
|
private static final String TAG = TextSecureCipher.class.getSimpleName();
|
||||||
|
|
||||||
private final AxolotlStore axolotlStore;
|
private final AxolotlStore axolotlStore;
|
||||||
private final TextSecureAddress localAddress;
|
private final TextSecureAddress localAddress;
|
||||||
|
|
||||||
@ -65,7 +73,7 @@ public class TextSecureCipher {
|
|||||||
this.localAddress = localAddress;
|
this.localAddress = localAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutgoingPushMessage encrypt(AxolotlAddress destination, byte[] unpaddedMessage) {
|
public OutgoingPushMessage encrypt(AxolotlAddress destination, byte[] unpaddedMessage, boolean legacy) {
|
||||||
SessionCipher sessionCipher = new SessionCipher(axolotlStore, destination);
|
SessionCipher sessionCipher = new SessionCipher(axolotlStore, destination);
|
||||||
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
|
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
|
||||||
CiphertextMessage message = sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage));
|
CiphertextMessage message = sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage));
|
||||||
@ -80,7 +88,8 @@ public class TextSecureCipher {
|
|||||||
default: throw new AssertionError("Bad type: " + message.getType());
|
default: throw new AssertionError("Bad type: " + message.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OutgoingPushMessage(type, destination.getDeviceId(), remoteRegistrationId, body);
|
return new OutgoingPushMessage(type, destination.getDeviceId(), remoteRegistrationId,
|
||||||
|
legacy ? body : null, legacy ? null : body);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,60 +106,83 @@ public class TextSecureCipher {
|
|||||||
* @throws LegacyMessageException
|
* @throws LegacyMessageException
|
||||||
* @throws NoSessionException
|
* @throws NoSessionException
|
||||||
*/
|
*/
|
||||||
public TextSecureMessage decrypt(TextSecureEnvelope envelope)
|
public TextSecureContent decrypt(TextSecureEnvelope envelope)
|
||||||
throws InvalidVersionException, InvalidMessageException, InvalidKeyException,
|
throws InvalidVersionException, InvalidMessageException, InvalidKeyException,
|
||||||
DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException,
|
DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException,
|
||||||
LegacyMessageException, NoSessionException
|
LegacyMessageException, NoSessionException
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
TextSecureContent content = new TextSecureContent();
|
||||||
|
|
||||||
|
if (envelope.hasLegacyMessage()) {
|
||||||
|
DataMessage message = DataMessage.parseFrom(decrypt(envelope, envelope.getLegacyMessage()));
|
||||||
|
content = new TextSecureContent(createTextSecureMessage(envelope, message));
|
||||||
|
} else if (envelope.hasContent()) {
|
||||||
|
Content message = Content.parseFrom(decrypt(envelope, envelope.getContent()));
|
||||||
|
|
||||||
|
if (message.hasDataMessage()) {
|
||||||
|
content = new TextSecureContent(createTextSecureMessage(envelope, message.getDataMessage()));
|
||||||
|
} else if (message.hasSyncMessage() && localAddress.getNumber().equals(envelope.getSource())) {
|
||||||
|
content = new TextSecureContent(createSynchronizeMessage(envelope, message.getSyncMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
} catch (InvalidProtocolBufferException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] decrypt(TextSecureEnvelope envelope, byte[] ciphertext)
|
||||||
|
throws InvalidVersionException, InvalidMessageException, InvalidKeyException,
|
||||||
|
DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException,
|
||||||
|
LegacyMessageException, NoSessionException
|
||||||
|
{
|
||||||
AxolotlAddress sourceAddress = new AxolotlAddress(envelope.getSource(), envelope.getSourceDevice());
|
AxolotlAddress sourceAddress = new AxolotlAddress(envelope.getSource(), envelope.getSourceDevice());
|
||||||
SessionCipher sessionCipher = new SessionCipher(axolotlStore, sourceAddress);
|
SessionCipher sessionCipher = new SessionCipher(axolotlStore, sourceAddress);
|
||||||
|
|
||||||
byte[] paddedMessage;
|
byte[] paddedMessage;
|
||||||
|
|
||||||
if (envelope.isPreKeyWhisperMessage()) {
|
if (envelope.isPreKeyWhisperMessage()) {
|
||||||
paddedMessage = sessionCipher.decrypt(new PreKeyWhisperMessage(envelope.getMessage()));
|
paddedMessage = sessionCipher.decrypt(new PreKeyWhisperMessage(ciphertext));
|
||||||
} else if (envelope.isWhisperMessage()) {
|
} else if (envelope.isWhisperMessage()) {
|
||||||
paddedMessage = sessionCipher.decrypt(new WhisperMessage(envelope.getMessage()));
|
paddedMessage = sessionCipher.decrypt(new WhisperMessage(ciphertext));
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidMessageException("Unknown type: " + envelope.getType());
|
throw new InvalidMessageException("Unknown type: " + envelope.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
|
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
|
||||||
PushMessageContent content = PushMessageContent.parseFrom(transportDetails.getStrippedPaddingMessageBody(paddedMessage));
|
return transportDetails.getStrippedPaddingMessageBody(paddedMessage);
|
||||||
|
|
||||||
return createTextSecureMessage(envelope, content);
|
|
||||||
} catch (InvalidProtocolBufferException e) {
|
|
||||||
throw new InvalidMessageException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextSecureMessage createTextSecureMessage(TextSecureEnvelope envelope, PushMessageContent content) {
|
private TextSecureDataMessage createTextSecureMessage(TextSecureEnvelope envelope, DataMessage content) {
|
||||||
TextSecureGroup groupInfo = createGroupInfo(envelope, content);
|
TextSecureGroup groupInfo = createGroupInfo(envelope, content);
|
||||||
TextSecureSyncContext syncContext = createSyncContext(envelope, content);
|
|
||||||
List<TextSecureAttachment> attachments = new LinkedList<>();
|
List<TextSecureAttachment> attachments = new LinkedList<>();
|
||||||
boolean endSession = ((content.getFlags() & PushMessageContent.Flags.END_SESSION_VALUE) != 0);
|
boolean endSession = ((content.getFlags() & DataMessage.Flags.END_SESSION_VALUE) != 0);
|
||||||
|
|
||||||
for (PushMessageContent.AttachmentPointer pointer : content.getAttachmentsList()) {
|
for (AttachmentPointer pointer : content.getAttachmentsList()) {
|
||||||
attachments.add(new TextSecureAttachmentPointer(pointer.getId(),
|
attachments.add(new TextSecureAttachmentPointer(pointer.getId(),
|
||||||
pointer.getContentType(),
|
pointer.getContentType(),
|
||||||
pointer.getKey().toByteArray(),
|
pointer.getKey().toByteArray(),
|
||||||
envelope.getRelay()));
|
envelope.getRelay()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TextSecureMessage(envelope.getTimestamp(), groupInfo, attachments,
|
return new TextSecureDataMessage(envelope.getTimestamp(), groupInfo, attachments,
|
||||||
content.getBody(), syncContext, endSession);
|
content.getBody(), endSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextSecureSyncContext createSyncContext(TextSecureEnvelope envelope, PushMessageContent content) {
|
private TextSecureSyncMessage createSynchronizeMessage(TextSecureEnvelope envelope, SyncMessage content) {
|
||||||
if (!content.hasSync()) return null;
|
if (content.hasSent()) {
|
||||||
if (!envelope.getSource().equals(localAddress.getNumber())) return null;
|
SyncMessage.Sent sentContent = content.getSent();
|
||||||
|
return new TextSecureSyncMessage(new SentTranscriptMessage(sentContent.getDestination(),
|
||||||
return new TextSecureSyncContext(content.getSync().getDestination(),
|
sentContent.getTimestamp(),
|
||||||
content.getSync().getTimestamp());
|
createTextSecureMessage(envelope, sentContent.getMessage())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextSecureGroup createGroupInfo(TextSecureEnvelope envelope, PushMessageContent content) {
|
return new TextSecureSyncMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextSecureGroup createGroupInfo(TextSecureEnvelope envelope, DataMessage content) {
|
||||||
if (!content.hasGroup()) return null;
|
if (!content.hasGroup()) return null;
|
||||||
|
|
||||||
TextSecureGroup.Type type;
|
TextSecureGroup.Type type;
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package org.whispersystems.textsecure.api.messages;
|
||||||
|
|
||||||
|
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||||
|
import org.whispersystems.textsecure.api.messages.multidevice.TextSecureSyncMessage;
|
||||||
|
|
||||||
|
public class TextSecureContent {
|
||||||
|
|
||||||
|
private final Optional<TextSecureDataMessage> message;
|
||||||
|
private final Optional<TextSecureSyncMessage> synchronizeMessage;
|
||||||
|
|
||||||
|
public TextSecureContent() {
|
||||||
|
this.message = Optional.absent();
|
||||||
|
this.synchronizeMessage = Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextSecureContent(TextSecureDataMessage message) {
|
||||||
|
this.message = Optional.fromNullable(message);
|
||||||
|
this.synchronizeMessage = Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextSecureContent(TextSecureSyncMessage synchronizeMessage) {
|
||||||
|
this.message = Optional.absent();
|
||||||
|
this.synchronizeMessage = Optional.fromNullable(synchronizeMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<TextSecureDataMessage> getDataMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<TextSecureSyncMessage> getSyncMessage() {
|
||||||
|
return synchronizeMessage;
|
||||||
|
}
|
||||||
|
}
|
@ -24,13 +24,12 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* Represents a decrypted text secure message.
|
* Represents a decrypted text secure message.
|
||||||
*/
|
*/
|
||||||
public class TextSecureMessage {
|
public class TextSecureDataMessage {
|
||||||
|
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
private final Optional<List<TextSecureAttachment>> attachments;
|
private final Optional<List<TextSecureAttachment>> attachments;
|
||||||
private final Optional<String> body;
|
private final Optional<String> body;
|
||||||
private final Optional<TextSecureGroup> group;
|
private final Optional<TextSecureGroup> group;
|
||||||
private final Optional<TextSecureSyncContext> syncContext;
|
|
||||||
private final boolean endSession;
|
private final boolean endSession;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,11 +38,11 @@ public class TextSecureMessage {
|
|||||||
* @param timestamp The sent timestamp.
|
* @param timestamp The sent timestamp.
|
||||||
* @param body The message contents.
|
* @param body The message contents.
|
||||||
*/
|
*/
|
||||||
public TextSecureMessage(long timestamp, String body) {
|
public TextSecureDataMessage(long timestamp, String body) {
|
||||||
this(timestamp, (List<TextSecureAttachment>)null, body);
|
this(timestamp, (List<TextSecureAttachment>)null, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextSecureMessage(final long timestamp, final TextSecureAttachment attachment, final String body) {
|
public TextSecureDataMessage(final long timestamp, final TextSecureAttachment attachment, final String body) {
|
||||||
this(timestamp, new LinkedList<TextSecureAttachment>() {{add(attachment);}}, body);
|
this(timestamp, new LinkedList<TextSecureAttachment>() {{add(attachment);}}, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +53,7 @@ public class TextSecureMessage {
|
|||||||
* @param attachments The attachments.
|
* @param attachments The attachments.
|
||||||
* @param body The message contents.
|
* @param body The message contents.
|
||||||
*/
|
*/
|
||||||
public TextSecureMessage(long timestamp, List<TextSecureAttachment> attachments, String body) {
|
public TextSecureDataMessage(long timestamp, List<TextSecureAttachment> attachments, String body) {
|
||||||
this(timestamp, null, attachments, body);
|
this(timestamp, null, attachments, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,8 +65,8 @@ public class TextSecureMessage {
|
|||||||
* @param attachments The attachments.
|
* @param attachments The attachments.
|
||||||
* @param body The message contents.
|
* @param body The message contents.
|
||||||
*/
|
*/
|
||||||
public TextSecureMessage(long timestamp, TextSecureGroup group, List<TextSecureAttachment> attachments, String body) {
|
public TextSecureDataMessage(long timestamp, TextSecureGroup group, List<TextSecureAttachment> attachments, String body) {
|
||||||
this(timestamp, group, attachments, body, null, false);
|
this(timestamp, group, attachments, body, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,11 +78,10 @@ public class TextSecureMessage {
|
|||||||
* @param body The message contents.
|
* @param body The message contents.
|
||||||
* @param endSession Flag indicating whether this message should close a session.
|
* @param endSession Flag indicating whether this message should close a session.
|
||||||
*/
|
*/
|
||||||
public TextSecureMessage(long timestamp, TextSecureGroup group, List<TextSecureAttachment> attachments, String body, TextSecureSyncContext syncContext, boolean endSession) {
|
public TextSecureDataMessage(long timestamp, TextSecureGroup group, List<TextSecureAttachment> attachments, String body, boolean endSession) {
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.body = Optional.fromNullable(body);
|
this.body = Optional.fromNullable(body);
|
||||||
this.group = Optional.fromNullable(group);
|
this.group = Optional.fromNullable(group);
|
||||||
this.syncContext = Optional.fromNullable(syncContext);
|
|
||||||
this.endSession = endSession;
|
this.endSession = endSession;
|
||||||
|
|
||||||
if (attachments != null && !attachments.isEmpty()) {
|
if (attachments != null && !attachments.isEmpty()) {
|
||||||
@ -125,10 +123,6 @@ public class TextSecureMessage {
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<TextSecureSyncContext> getSyncContext() {
|
|
||||||
return syncContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEndSession() {
|
public boolean isEndSession() {
|
||||||
return endSession;
|
return endSession;
|
||||||
}
|
}
|
||||||
@ -182,9 +176,9 @@ public class TextSecureMessage {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextSecureMessage build() {
|
public TextSecureDataMessage build() {
|
||||||
if (timestamp == 0) timestamp = System.currentTimeMillis();
|
if (timestamp == 0) timestamp = System.currentTimeMillis();
|
||||||
return new TextSecureMessage(timestamp, group, attachments, body, null, endSession);
|
return new TextSecureDataMessage(timestamp, group, attachments, body, endSession);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -22,7 +22,7 @@ import org.whispersystems.libaxolotl.InvalidVersionException;
|
|||||||
import org.whispersystems.libaxolotl.logging.Log;
|
import org.whispersystems.libaxolotl.logging.Log;
|
||||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||||
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
||||||
import org.whispersystems.textsecure.internal.push.PushMessageProtos.IncomingPushMessageSignal;
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos.Envelope;
|
||||||
import org.whispersystems.textsecure.internal.util.Base64;
|
import org.whispersystems.textsecure.internal.util.Base64;
|
||||||
import org.whispersystems.textsecure.internal.util.Hex;
|
import org.whispersystems.textsecure.internal.util.Hex;
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ public class TextSecureEnvelope {
|
|||||||
private static final int IV_LENGTH = 16;
|
private static final int IV_LENGTH = 16;
|
||||||
private static final int CIPHERTEXT_OFFSET = IV_OFFSET + IV_LENGTH;
|
private static final int CIPHERTEXT_OFFSET = IV_OFFSET + IV_LENGTH;
|
||||||
|
|
||||||
private final IncomingPushMessageSignal signal;
|
private final Envelope envelope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an envelope from a serialized, Base64 encoded TextSecureEnvelope, encrypted
|
* Construct an envelope from a serialized, Base64 encoded TextSecureEnvelope, encrypted
|
||||||
@ -99,42 +99,46 @@ public class TextSecureEnvelope {
|
|||||||
|
|
||||||
verifyMac(ciphertext, macKey);
|
verifyMac(ciphertext, macKey);
|
||||||
|
|
||||||
this.signal = IncomingPushMessageSignal.parseFrom(getPlaintext(ciphertext, cipherKey));
|
this.envelope = Envelope.parseFrom(getPlaintext(ciphertext, cipherKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextSecureEnvelope(int type, String source, int sourceDevice,
|
public TextSecureEnvelope(int type, String source, int sourceDevice,
|
||||||
String relay, long timestamp, byte[] message)
|
String relay, long timestamp,
|
||||||
|
byte[] legacyMessage, byte[] content)
|
||||||
{
|
{
|
||||||
this.signal = IncomingPushMessageSignal.newBuilder()
|
Envelope.Builder builder = Envelope.newBuilder()
|
||||||
.setType(IncomingPushMessageSignal.Type.valueOf(type))
|
.setType(Envelope.Type.valueOf(type))
|
||||||
.setSource(source)
|
.setSource(source)
|
||||||
.setSourceDevice(sourceDevice)
|
.setSourceDevice(sourceDevice)
|
||||||
.setRelay(relay)
|
.setRelay(relay)
|
||||||
.setTimestamp(timestamp)
|
.setTimestamp(timestamp);
|
||||||
.setMessage(ByteString.copyFrom(message))
|
|
||||||
.build();
|
if (legacyMessage != null) builder.setLegacyMessage(ByteString.copyFrom(legacyMessage));
|
||||||
|
if (content != null) builder.setContent(ByteString.copyFrom(content));
|
||||||
|
|
||||||
|
this.envelope = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The envelope's sender.
|
* @return The envelope's sender.
|
||||||
*/
|
*/
|
||||||
public String getSource() {
|
public String getSource() {
|
||||||
return signal.getSource();
|
return envelope.getSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The envelope's sender device ID.
|
* @return The envelope's sender device ID.
|
||||||
*/
|
*/
|
||||||
public int getSourceDevice() {
|
public int getSourceDevice() {
|
||||||
return signal.getSourceDevice();
|
return envelope.getSourceDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The envelope's sender as a TextSecureAddress.
|
* @return The envelope's sender as a TextSecureAddress.
|
||||||
*/
|
*/
|
||||||
public TextSecureAddress getSourceAddress() {
|
public TextSecureAddress getSourceAddress() {
|
||||||
return new TextSecureAddress(signal.getSource(),
|
return new TextSecureAddress(envelope.getSource(),
|
||||||
signal.hasRelay() ? Optional.fromNullable(signal.getRelay()) :
|
envelope.hasRelay() ? Optional.fromNullable(envelope.getRelay()) :
|
||||||
Optional.<String>absent());
|
Optional.<String>absent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,49 +146,70 @@ public class TextSecureEnvelope {
|
|||||||
* @return The envelope content type.
|
* @return The envelope content type.
|
||||||
*/
|
*/
|
||||||
public int getType() {
|
public int getType() {
|
||||||
return signal.getType().getNumber();
|
return envelope.getType().getNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The federated server this envelope came from.
|
* @return The federated server this envelope came from.
|
||||||
*/
|
*/
|
||||||
public String getRelay() {
|
public String getRelay() {
|
||||||
return signal.getRelay();
|
return envelope.getRelay();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The timestamp this envelope was sent.
|
* @return The timestamp this envelope was sent.
|
||||||
*/
|
*/
|
||||||
public long getTimestamp() {
|
public long getTimestamp() {
|
||||||
return signal.getTimestamp();
|
return envelope.getTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The envelope's containing message.
|
* @return Whether the envelope contains a TextSecureDataMessage
|
||||||
*/
|
*/
|
||||||
public byte[] getMessage() {
|
public boolean hasLegacyMessage() {
|
||||||
return signal.getMessage().toByteArray();
|
return envelope.hasLegacyMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The envelope's containing TextSecure message.
|
||||||
|
*/
|
||||||
|
public byte[] getLegacyMessage() {
|
||||||
|
return envelope.getLegacyMessage().toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Whether the envelope contains an encrypted TextSecureContent
|
||||||
|
*/
|
||||||
|
public boolean hasContent() {
|
||||||
|
return envelope.hasContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The envelope's encrypted TextSecureContent.
|
||||||
|
*/
|
||||||
|
public byte[] getContent() {
|
||||||
|
return envelope.getContent().toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the containing message is a {@link org.whispersystems.libaxolotl.protocol.WhisperMessage}
|
* @return true if the containing message is a {@link org.whispersystems.libaxolotl.protocol.WhisperMessage}
|
||||||
*/
|
*/
|
||||||
public boolean isWhisperMessage() {
|
public boolean isWhisperMessage() {
|
||||||
return signal.getType().getNumber() == IncomingPushMessageSignal.Type.CIPHERTEXT_VALUE;
|
return envelope.getType().getNumber() == Envelope.Type.CIPHERTEXT_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the containing message is a {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage}
|
* @return true if the containing message is a {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage}
|
||||||
*/
|
*/
|
||||||
public boolean isPreKeyWhisperMessage() {
|
public boolean isPreKeyWhisperMessage() {
|
||||||
return signal.getType().getNumber() == IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE;
|
return envelope.getType().getNumber() == Envelope.Type.PREKEY_BUNDLE_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the containing message is a delivery receipt.
|
* @return true if the containing message is a delivery receipt.
|
||||||
*/
|
*/
|
||||||
public boolean isReceipt() {
|
public boolean isReceipt() {
|
||||||
return signal.getType().getNumber() == IncomingPushMessageSignal.Type.RECEIPT_VALUE;
|
return envelope.getType().getNumber() == Envelope.Type.RECEIPT_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getPlaintext(byte[] ciphertext, SecretKeySpec cipherKey) throws IOException {
|
private byte[] getPlaintext(byte[] ciphertext, SecretKeySpec cipherKey) throws IOException {
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package org.whispersystems.textsecure.api.messages;
|
|
||||||
|
|
||||||
public class TextSecureSyncContext {
|
|
||||||
|
|
||||||
private final String destination;
|
|
||||||
private final long timestamp;
|
|
||||||
|
|
||||||
public TextSecureSyncContext(String destination, long timestamp) {
|
|
||||||
this.destination = destination;
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDestination() {
|
|
||||||
return destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,30 @@
|
|||||||
|
package org.whispersystems.textsecure.api.messages.multidevice;
|
||||||
|
|
||||||
|
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||||
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream;
|
||||||
|
|
||||||
|
public class DeviceContact {
|
||||||
|
|
||||||
|
private final String number;
|
||||||
|
private final Optional<String> name;
|
||||||
|
private final Optional<TextSecureAttachmentStream> avatar;
|
||||||
|
|
||||||
|
public DeviceContact(String number, Optional<String> name, Optional<TextSecureAttachmentStream> avatar) {
|
||||||
|
this.number = number;
|
||||||
|
this.name = name;
|
||||||
|
this.avatar = avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<TextSecureAttachmentStream> getAvatar() {
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNumber() {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
package org.whispersystems.textsecure.api.messages.multidevice;
|
||||||
|
|
||||||
|
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||||
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream;
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos;
|
||||||
|
import org.whispersystems.textsecure.internal.util.Util;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class DeviceContactsInputStream {
|
||||||
|
|
||||||
|
private final InputStream in;
|
||||||
|
|
||||||
|
public DeviceContactsInputStream(InputStream in) {
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceContact read() throws IOException {
|
||||||
|
long detailsLength = readRawVarint64();
|
||||||
|
byte[] detailsSerialized = new byte[(int)detailsLength];
|
||||||
|
Util.readFully(in, detailsSerialized);
|
||||||
|
|
||||||
|
TextSecureProtos.ContactDetails details = TextSecureProtos.ContactDetails.parseFrom(detailsSerialized);
|
||||||
|
String number = details.getNumber();
|
||||||
|
Optional<String> name = Optional.fromNullable(details.getName());
|
||||||
|
Optional<TextSecureAttachmentStream> avatar = Optional.absent();
|
||||||
|
|
||||||
|
if (details.hasAvatar()) {
|
||||||
|
long avatarLength = details.getAvatar().getLength();
|
||||||
|
InputStream avatarStream = new LimitedInputStream(in, avatarLength);
|
||||||
|
String avatarContentType = details.getAvatar().getContentType();
|
||||||
|
|
||||||
|
avatar = Optional.of(new TextSecureAttachmentStream(avatarStream, avatarContentType, avatarLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DeviceContact(number, name, avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long readRawVarint64() throws IOException {
|
||||||
|
int shift = 0;
|
||||||
|
long result = 0;
|
||||||
|
while (shift < 64) {
|
||||||
|
final byte b = (byte)in.read();
|
||||||
|
result |= (long)(b & 0x7F) << shift;
|
||||||
|
if ((b & 0x80) == 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
shift += 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IOException("Malformed varint!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class LimitedInputStream extends FilterInputStream {
|
||||||
|
|
||||||
|
private long left;
|
||||||
|
private long mark = -1;
|
||||||
|
|
||||||
|
LimitedInputStream(InputStream in, long limit) {
|
||||||
|
super(in);
|
||||||
|
left = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int available() throws IOException {
|
||||||
|
return (int) Math.min(in.available(), left);
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's okay to mark even if mark isn't supported, as reset won't work
|
||||||
|
@Override public synchronized void mark(int readLimit) {
|
||||||
|
in.mark(readLimit);
|
||||||
|
mark = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int read() throws IOException {
|
||||||
|
if (left == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = in.read();
|
||||||
|
if (result != -1) {
|
||||||
|
--left;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (left == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = (int) Math.min(len, left);
|
||||||
|
int result = in.read(b, off, len);
|
||||||
|
if (result != -1) {
|
||||||
|
left -= result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void reset() throws IOException {
|
||||||
|
if (!in.markSupported()) {
|
||||||
|
throw new IOException("Mark not supported");
|
||||||
|
}
|
||||||
|
if (mark == -1) {
|
||||||
|
throw new IOException("Mark not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
in.reset();
|
||||||
|
left = mark;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public long skip(long n) throws IOException {
|
||||||
|
n = Math.min(n, left);
|
||||||
|
long skipped = in.skip(n);
|
||||||
|
left -= skipped;
|
||||||
|
return skipped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package org.whispersystems.textsecure.api.messages.multidevice;
|
||||||
|
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos;
|
||||||
|
import org.whispersystems.textsecure.internal.util.Util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class DeviceContactsOutputStream {
|
||||||
|
|
||||||
|
private final OutputStream out;
|
||||||
|
|
||||||
|
public DeviceContactsOutputStream(OutputStream out) {
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(DeviceContact contact) throws IOException {
|
||||||
|
writeContactDetails(contact);
|
||||||
|
writeAvatarImage(contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeAvatarImage(DeviceContact contact) throws IOException {
|
||||||
|
if (contact.getAvatar().isPresent()) {
|
||||||
|
Util.copy(contact.getAvatar().get().getInputStream(), out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeContactDetails(DeviceContact contact) throws IOException {
|
||||||
|
TextSecureProtos.ContactDetails.Builder contactDetails = TextSecureProtos.ContactDetails.newBuilder();
|
||||||
|
contactDetails.setNumber(contact.getNumber());
|
||||||
|
|
||||||
|
if (contact.getName().isPresent()) {
|
||||||
|
contactDetails.setName(contact.getName().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contact.getAvatar().isPresent()) {
|
||||||
|
TextSecureProtos.ContactDetails.Avatar.Builder avatarBuilder = TextSecureProtos.ContactDetails.Avatar.newBuilder();
|
||||||
|
avatarBuilder.setContentType(contact.getAvatar().get().getContentType());
|
||||||
|
avatarBuilder.setLength(contact.getAvatar().get().getLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] serializedContactDetails = contactDetails.build().toByteArray();
|
||||||
|
|
||||||
|
writeVarint64(serializedContactDetails.length);
|
||||||
|
out.write(serializedContactDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeVarint64(long value) throws IOException {
|
||||||
|
while (true) {
|
||||||
|
if ((value & ~0x7FL) == 0) {
|
||||||
|
out.write((int) value);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
out.write(((int) value & 0x7F) | 0x80);
|
||||||
|
value >>>= 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package org.whispersystems.textsecure.api.messages.multidevice;
|
||||||
|
|
||||||
|
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||||
|
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
|
||||||
|
|
||||||
|
public class SentTranscriptMessage {
|
||||||
|
|
||||||
|
private final Optional<String> destination;
|
||||||
|
private final long timestamp;
|
||||||
|
private final TextSecureDataMessage message;
|
||||||
|
|
||||||
|
public SentTranscriptMessage(String destination, long timestamp, TextSecureDataMessage message) {
|
||||||
|
this.destination = Optional.of(destination);
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SentTranscriptMessage(long timestamp, TextSecureDataMessage message) {
|
||||||
|
this.destination = Optional.absent();
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> getDestination() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextSecureDataMessage getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package org.whispersystems.textsecure.api.messages.multidevice;
|
||||||
|
|
||||||
|
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||||
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
|
||||||
|
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
||||||
|
|
||||||
|
public class TextSecureSyncMessage {
|
||||||
|
|
||||||
|
private final Optional<SentTranscriptMessage> sent;
|
||||||
|
private final Optional<TextSecureAttachment> contacts;
|
||||||
|
private final Optional<TextSecureGroup> group;
|
||||||
|
|
||||||
|
public TextSecureSyncMessage() {
|
||||||
|
this.sent = Optional.absent();
|
||||||
|
this.contacts = Optional.absent();
|
||||||
|
this.group = Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextSecureSyncMessage(SentTranscriptMessage sent) {
|
||||||
|
this.sent = Optional.of(sent);
|
||||||
|
this.contacts = Optional.absent();
|
||||||
|
this.group = Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextSecureSyncMessage(TextSecureAttachment contacts) {
|
||||||
|
this.contacts = Optional.of(contacts);
|
||||||
|
this.sent = Optional.absent();
|
||||||
|
this.group = Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextSecureSyncMessage(TextSecureGroup group) {
|
||||||
|
this.group = Optional.of(group);
|
||||||
|
this.sent = Optional.absent();
|
||||||
|
this.contacts = Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<SentTranscriptMessage> getSent() {
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<TextSecureGroup> getGroup() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<TextSecureAttachment> getContacts() {
|
||||||
|
return contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -32,31 +32,18 @@ public class OutgoingPushMessage {
|
|||||||
private int destinationRegistrationId;
|
private int destinationRegistrationId;
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String body;
|
private String body;
|
||||||
|
@JsonProperty
|
||||||
|
private String content;
|
||||||
|
|
||||||
public OutgoingPushMessage(int type,
|
public OutgoingPushMessage(int type,
|
||||||
int destinationDeviceId,
|
int destinationDeviceId,
|
||||||
int destinationRegistrationId,
|
int destinationRegistrationId,
|
||||||
String body)
|
String legacyMessage, String content)
|
||||||
{
|
{
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.destinationDeviceId = destinationDeviceId;
|
this.destinationDeviceId = destinationDeviceId;
|
||||||
this.destinationRegistrationId = destinationRegistrationId;
|
this.destinationRegistrationId = destinationRegistrationId;
|
||||||
this.body = body;
|
this.body = legacyMessage;
|
||||||
}
|
this.content = content;
|
||||||
|
|
||||||
public int getDestinationDeviceId() {
|
|
||||||
return destinationDeviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBody() {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDestinationRegistrationId() {
|
|
||||||
return destinationRegistrationId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,9 @@ public class TextSecureEnvelopeEntity {
|
|||||||
@JsonProperty
|
@JsonProperty
|
||||||
private byte[] message;
|
private byte[] message;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private byte[] content;
|
||||||
|
|
||||||
public TextSecureEnvelopeEntity() {}
|
public TextSecureEnvelopeEntity() {}
|
||||||
|
|
||||||
public int getType() {
|
public int getType() {
|
||||||
@ -47,4 +50,8 @@ public class TextSecureEnvelopeEntity {
|
|||||||
public byte[] getMessage() {
|
public byte[] getMessage() {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -125,4 +125,18 @@ public class Util {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] toVarint64(long value) {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if ((value & ~0x7FL) == 0) {
|
||||||
|
out.write((int) value);
|
||||||
|
return out.toByteArray();
|
||||||
|
} else {
|
||||||
|
out.write(((int) value & 0x7F) | 0x80);
|
||||||
|
value >>>= 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
package textsecure;
|
|
||||||
|
|
||||||
option java_package = "org.whispersystems.textsecure.internal.push";
|
|
||||||
option java_outer_classname = "PushMessageProtos";
|
|
||||||
|
|
||||||
message IncomingPushMessageSignal {
|
|
||||||
enum Type {
|
|
||||||
UNKNOWN = 0;
|
|
||||||
CIPHERTEXT = 1;
|
|
||||||
KEY_EXCHANGE = 2;
|
|
||||||
PREKEY_BUNDLE = 3;
|
|
||||||
// PLAINTEXT = 4; // No longer supported
|
|
||||||
RECEIPT = 5;
|
|
||||||
}
|
|
||||||
optional Type type = 1;
|
|
||||||
optional string source = 2;
|
|
||||||
optional uint32 sourceDevice = 7;
|
|
||||||
optional string relay = 3;
|
|
||||||
optional uint64 timestamp = 5;
|
|
||||||
optional bytes message = 6; // Contains an encrypted PushMessageContent
|
|
||||||
// repeated string destinations = 4; // No longer supported
|
|
||||||
}
|
|
||||||
|
|
||||||
message PushMessageContent {
|
|
||||||
message AttachmentPointer {
|
|
||||||
optional fixed64 id = 1;
|
|
||||||
optional string contentType = 2;
|
|
||||||
optional bytes key = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GroupContext {
|
|
||||||
enum Type {
|
|
||||||
UNKNOWN = 0;
|
|
||||||
UPDATE = 1;
|
|
||||||
DELIVER = 2;
|
|
||||||
QUIT = 3;
|
|
||||||
}
|
|
||||||
optional bytes id = 1;
|
|
||||||
optional Type type = 2;
|
|
||||||
optional string name = 3;
|
|
||||||
repeated string members = 4;
|
|
||||||
optional AttachmentPointer avatar = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SyncMessageContext {
|
|
||||||
optional string destination = 1;
|
|
||||||
optional uint64 timestamp = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Flags {
|
|
||||||
END_SESSION = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
optional string body = 1;
|
|
||||||
repeated AttachmentPointer attachments = 2;
|
|
||||||
optional GroupContext group = 3;
|
|
||||||
optional uint32 flags = 4;
|
|
||||||
optional SyncMessageContext sync = 5;
|
|
||||||
}
|
|
@ -1,3 +1,3 @@
|
|||||||
|
|
||||||
all:
|
all:
|
||||||
protoc --java_out=../java/src/main/java/ IncomingPushMessageSignal.proto Provisioning.proto WebSocketResources.proto
|
protoc --java_out=../java/src/main/java/ TextSecure.proto Provisioning.proto WebSocketResources.proto
|
||||||
|
89
protobuf/TextSecure.proto
Normal file
89
protobuf/TextSecure.proto
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package textsecure;
|
||||||
|
|
||||||
|
option java_package = "org.whispersystems.textsecure.internal.push";
|
||||||
|
option java_outer_classname = "TextSecureProtos";
|
||||||
|
|
||||||
|
message Envelope {
|
||||||
|
enum Type {
|
||||||
|
UNKNOWN = 0;
|
||||||
|
CIPHERTEXT = 1;
|
||||||
|
KEY_EXCHANGE = 2;
|
||||||
|
PREKEY_BUNDLE = 3;
|
||||||
|
RECEIPT = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional Type type = 1;
|
||||||
|
optional string source = 2;
|
||||||
|
optional uint32 sourceDevice = 7;
|
||||||
|
optional string relay = 3;
|
||||||
|
optional uint64 timestamp = 5;
|
||||||
|
optional bytes legacyMessage = 6; // Contains an encrypted DataMessage
|
||||||
|
optional bytes content = 8; // Contains an encrypted Content
|
||||||
|
}
|
||||||
|
|
||||||
|
message Content {
|
||||||
|
optional DataMessage dataMessage = 1;
|
||||||
|
optional SyncMessage syncMessage = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DataMessage {
|
||||||
|
enum Flags {
|
||||||
|
END_SESSION = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional string body = 1;
|
||||||
|
repeated AttachmentPointer attachments = 2;
|
||||||
|
optional GroupContext group = 3;
|
||||||
|
optional uint32 flags = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SyncMessage {
|
||||||
|
message Sent {
|
||||||
|
optional string destination = 1;
|
||||||
|
optional uint64 timestamp = 2;
|
||||||
|
optional DataMessage message = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Contacts {
|
||||||
|
optional AttachmentPointer blob = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Group {
|
||||||
|
optional GroupContext group = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional Sent sent = 1;
|
||||||
|
optional Contacts contacts = 2;
|
||||||
|
optional Group group = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AttachmentPointer {
|
||||||
|
optional fixed64 id = 1;
|
||||||
|
optional string contentType = 2;
|
||||||
|
optional bytes key = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GroupContext {
|
||||||
|
enum Type {
|
||||||
|
UNKNOWN = 0;
|
||||||
|
UPDATE = 1;
|
||||||
|
DELIVER = 2;
|
||||||
|
QUIT = 3;
|
||||||
|
}
|
||||||
|
optional bytes id = 1;
|
||||||
|
optional Type type = 2;
|
||||||
|
optional string name = 3;
|
||||||
|
repeated string members = 4;
|
||||||
|
optional AttachmentPointer avatar = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ContactDetails {
|
||||||
|
message Avatar {
|
||||||
|
optional string contentType = 1;
|
||||||
|
optional uint64 length = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional string number = 1;
|
||||||
|
optional string name = 2;
|
||||||
|
optional Avatar avatar = 3;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user