mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-29 19:30:47 +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 {
|
||||
classpath 'com.android.tools.build:gradle:1.1.0'
|
||||
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
subprojects {
|
||||
ext.version_number = "1.5.0"
|
||||
ext.version_number = "1.6.0-RC4"
|
||||
ext.group_info = "org.whispersystems"
|
||||
ext.axolotl_version = "1.3.1"
|
||||
|
||||
|
@ -19,6 +19,7 @@ package org.whispersystems.textsecure.api;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.api.crypto.AttachmentCipherInputStream;
|
||||
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.push.TrustStore;
|
||||
import org.whispersystems.textsecure.api.util.CredentialsProvider;
|
||||
@ -80,7 +81,7 @@ public class TextSecureMessageReceiver {
|
||||
* Retrieves a TextSecure attachment.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return An InputStream that streams the plaintext attachment contents.
|
||||
@ -119,7 +120,8 @@ public class TextSecureMessageReceiver {
|
||||
for (TextSecureEnvelopeEntity entity : entities) {
|
||||
TextSecureEnvelope envelope = new TextSecureEnvelope(entity.getType(), entity.getSource(),
|
||||
entity.getSourceDevice(), entity.getRelay(),
|
||||
entity.getTimestamp(), entity.getMessage());
|
||||
entity.getTimestamp(), entity.getMessage(),
|
||||
entity.getContent());
|
||||
|
||||
callback.onMessage(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.messages.TextSecureAttachment;
|
||||
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.TextSecureMessage;
|
||||
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
||||
import org.whispersystems.textsecure.api.push.TrustStore;
|
||||
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.OutgoingPushMessageList;
|
||||
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.SendMessageResponse;
|
||||
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.StaleDevicesException;
|
||||
import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider;
|
||||
@ -55,10 +59,6 @@ import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
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.
|
||||
*
|
||||
@ -114,16 +114,16 @@ public class TextSecureMessageSender {
|
||||
* @throws UntrustedIdentityException
|
||||
* @throws IOException
|
||||
*/
|
||||
public void sendMessage(TextSecureAddress recipient, TextSecureMessage message)
|
||||
public void sendMessage(TextSecureAddress recipient, TextSecureDataMessage message)
|
||||
throws UntrustedIdentityException, IOException
|
||||
{
|
||||
byte[] content = createMessageContent(message);
|
||||
long timestamp = message.getTimestamp();
|
||||
SendMessageResponse response = sendMessage(recipient, timestamp, content);
|
||||
SendMessageResponse response = sendMessage(recipient, timestamp, content, true);
|
||||
|
||||
if (response != null && response.getNeedsSync()) {
|
||||
byte[] syncMessage = createSyncMessageContent(content, Optional.of(recipient), timestamp);
|
||||
sendMessage(localAddress, timestamp, syncMessage);
|
||||
byte[] syncMessage = createSentTranscriptMessage(content, Optional.of(recipient), timestamp);
|
||||
sendMessage(localAddress, timestamp, syncMessage, false);
|
||||
}
|
||||
|
||||
if (message.isEndSession()) {
|
||||
@ -143,26 +143,33 @@ public class TextSecureMessageSender {
|
||||
* @throws IOException
|
||||
* @throws EncapsulatedExceptions
|
||||
*/
|
||||
public void sendMessage(List<TextSecureAddress> recipients, TextSecureMessage message)
|
||||
public void sendMessage(List<TextSecureAddress> recipients, TextSecureDataMessage message)
|
||||
throws IOException, EncapsulatedExceptions
|
||||
{
|
||||
byte[] content = createMessageContent(message);
|
||||
long timestamp = message.getTimestamp();
|
||||
SendMessageResponse response = sendMessage(recipients, timestamp, content);
|
||||
SendMessageResponse response = sendMessage(recipients, timestamp, content, true);
|
||||
|
||||
try {
|
||||
if (response != null && response.getNeedsSync()) {
|
||||
byte[] syncMessage = createSyncMessageContent(content, Optional.<TextSecureAddress>absent(), timestamp);
|
||||
sendMessage(localAddress, timestamp, syncMessage);
|
||||
byte[] syncMessage = createSentTranscriptMessage(content, Optional.<TextSecureAddress>absent(), timestamp);
|
||||
sendMessage(localAddress, timestamp, syncMessage, false);
|
||||
}
|
||||
} catch (UntrustedIdentityException e) {
|
||||
throw new EncapsulatedExceptions(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] createMessageContent(TextSecureMessage message) throws IOException {
|
||||
PushMessageContent.Builder builder = PushMessageContent.newBuilder();
|
||||
List<AttachmentPointer> pointers = createAttachmentPointers(message.getAttachments());
|
||||
public void sendMultiDeviceContactsUpdate(TextSecureAttachmentStream contacts)
|
||||
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());
|
||||
|
||||
if (!pointers.isEmpty()) {
|
||||
builder.addAllAttachments(pointers);
|
||||
@ -177,25 +184,34 @@ public class TextSecureMessageSender {
|
||||
}
|
||||
|
||||
if (message.isEndSession()) {
|
||||
builder.setFlags(PushMessageContent.Flags.END_SESSION_VALUE);
|
||||
builder.setFlags(DataMessage.Flags.END_SESSION_VALUE);
|
||||
}
|
||||
|
||||
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 {
|
||||
SyncMessageContext.Builder syncMessageContext = SyncMessageContext.newBuilder();
|
||||
syncMessageContext.setTimestamp(timestamp);
|
||||
Content.Builder container = Content.newBuilder();
|
||||
SyncMessage.Builder syncMessage = SyncMessage.newBuilder();
|
||||
SyncMessage.Sent.Builder sentMessage = SyncMessage.Sent.newBuilder();
|
||||
|
||||
sentMessage.setTimestamp(timestamp);
|
||||
sentMessage.setMessage(DataMessage.parseFrom(content));
|
||||
|
||||
if (recipient.isPresent()) {
|
||||
syncMessageContext.setDestination(recipient.get().getNumber());
|
||||
sentMessage.setDestination(recipient.get().getNumber());
|
||||
}
|
||||
|
||||
PushMessageContent.Builder builder = PushMessageContent.parseFrom(content).toBuilder();
|
||||
builder.setSync(syncMessageContext.build());
|
||||
|
||||
return builder.build().toByteArray();
|
||||
return container.setSyncMessage(syncMessage.setSent(sentMessage)).build().toByteArray();
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
@ -224,7 +240,7 @@ public class TextSecureMessageSender {
|
||||
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
|
||||
{
|
||||
List<UntrustedIdentityException> untrustedIdentities = new LinkedList<>();
|
||||
@ -235,7 +251,7 @@ public class TextSecureMessageSender {
|
||||
|
||||
for (TextSecureAddress recipient : recipients) {
|
||||
try {
|
||||
response = sendMessage(recipient, timestamp, content);
|
||||
response = sendMessage(recipient, timestamp, content, legacy);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w(TAG, e);
|
||||
untrustedIdentities.add(e);
|
||||
@ -255,12 +271,12 @@ public class TextSecureMessageSender {
|
||||
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
|
||||
{
|
||||
for (int i=0;i<3;i++) {
|
||||
try {
|
||||
OutgoingPushMessageList messages = getEncryptedMessages(socket, recipient, timestamp, content);
|
||||
OutgoingPushMessageList messages = getEncryptedMessages(socket, recipient, timestamp, content, legacy);
|
||||
return socket.sendMessage(messages);
|
||||
} catch (MismatchedDevicesException mde) {
|
||||
Log.w(TAG, mde);
|
||||
@ -314,23 +330,24 @@ public class TextSecureMessageSender {
|
||||
private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket,
|
||||
TextSecureAddress recipient,
|
||||
long timestamp,
|
||||
byte[] plaintext)
|
||||
byte[] plaintext,
|
||||
boolean legacy)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
List<OutgoingPushMessage> messages = new LinkedList<>();
|
||||
|
||||
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())) {
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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,
|
||||
|
@ -28,27 +28,33 @@ import org.whispersystems.libaxolotl.LegacyMessageException;
|
||||
import org.whispersystems.libaxolotl.NoSessionException;
|
||||
import org.whispersystems.libaxolotl.SessionCipher;
|
||||
import org.whispersystems.libaxolotl.UntrustedIdentityException;
|
||||
import org.whispersystems.libaxolotl.logging.Log;
|
||||
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
||||
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
|
||||
import org.whispersystems.libaxolotl.state.AxolotlStore;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
|
||||
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.TextSecureGroup;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureSyncContext;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
|
||||
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.internal.push.OutgoingPushMessage;
|
||||
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 java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.IncomingPushMessageSignal.Type;
|
||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent;
|
||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext.Type.DELIVER;
|
||||
import static org.whispersystems.textsecure.internal.push.TextSecureProtos.GroupContext.Type.DELIVER;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
private static final String TAG = TextSecureCipher.class.getSimpleName();
|
||||
|
||||
private final AxolotlStore axolotlStore;
|
||||
private final TextSecureAddress localAddress;
|
||||
|
||||
@ -65,7 +73,7 @@ public class TextSecureCipher {
|
||||
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);
|
||||
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
|
||||
CiphertextMessage message = sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage));
|
||||
@ -80,7 +88,8 @@ public class TextSecureCipher {
|
||||
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 NoSessionException
|
||||
*/
|
||||
public TextSecureMessage decrypt(TextSecureEnvelope envelope)
|
||||
public TextSecureContent decrypt(TextSecureEnvelope envelope)
|
||||
throws InvalidVersionException, InvalidMessageException, InvalidKeyException,
|
||||
DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException,
|
||||
LegacyMessageException, NoSessionException
|
||||
{
|
||||
try {
|
||||
AxolotlAddress sourceAddress = new AxolotlAddress(envelope.getSource(), envelope.getSourceDevice());
|
||||
SessionCipher sessionCipher = new SessionCipher(axolotlStore, sourceAddress);
|
||||
TextSecureContent content = new TextSecureContent();
|
||||
|
||||
byte[] paddedMessage;
|
||||
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 (envelope.isPreKeyWhisperMessage()) {
|
||||
paddedMessage = sessionCipher.decrypt(new PreKeyWhisperMessage(envelope.getMessage()));
|
||||
} else if (envelope.isWhisperMessage()) {
|
||||
paddedMessage = sessionCipher.decrypt(new WhisperMessage(envelope.getMessage()));
|
||||
} else {
|
||||
throw new InvalidMessageException("Unknown type: " + envelope.getType());
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
|
||||
PushMessageContent content = PushMessageContent.parseFrom(transportDetails.getStrippedPaddingMessageBody(paddedMessage));
|
||||
|
||||
return createTextSecureMessage(envelope, content);
|
||||
return content;
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private TextSecureMessage createTextSecureMessage(TextSecureEnvelope envelope, PushMessageContent content) {
|
||||
TextSecureGroup groupInfo = createGroupInfo(envelope, content);
|
||||
TextSecureSyncContext syncContext = createSyncContext(envelope, content);
|
||||
List<TextSecureAttachment> attachments = new LinkedList<>();
|
||||
boolean endSession = ((content.getFlags() & PushMessageContent.Flags.END_SESSION_VALUE) != 0);
|
||||
private byte[] decrypt(TextSecureEnvelope envelope, byte[] ciphertext)
|
||||
throws InvalidVersionException, InvalidMessageException, InvalidKeyException,
|
||||
DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException,
|
||||
LegacyMessageException, NoSessionException
|
||||
{
|
||||
AxolotlAddress sourceAddress = new AxolotlAddress(envelope.getSource(), envelope.getSourceDevice());
|
||||
SessionCipher sessionCipher = new SessionCipher(axolotlStore, sourceAddress);
|
||||
|
||||
for (PushMessageContent.AttachmentPointer pointer : content.getAttachmentsList()) {
|
||||
byte[] paddedMessage;
|
||||
|
||||
if (envelope.isPreKeyWhisperMessage()) {
|
||||
paddedMessage = sessionCipher.decrypt(new PreKeyWhisperMessage(ciphertext));
|
||||
} else if (envelope.isWhisperMessage()) {
|
||||
paddedMessage = sessionCipher.decrypt(new WhisperMessage(ciphertext));
|
||||
} else {
|
||||
throw new InvalidMessageException("Unknown type: " + envelope.getType());
|
||||
}
|
||||
|
||||
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
|
||||
return transportDetails.getStrippedPaddingMessageBody(paddedMessage);
|
||||
}
|
||||
|
||||
private TextSecureDataMessage createTextSecureMessage(TextSecureEnvelope envelope, DataMessage content) {
|
||||
TextSecureGroup groupInfo = createGroupInfo(envelope, content);
|
||||
List<TextSecureAttachment> attachments = new LinkedList<>();
|
||||
boolean endSession = ((content.getFlags() & DataMessage.Flags.END_SESSION_VALUE) != 0);
|
||||
|
||||
for (AttachmentPointer pointer : content.getAttachmentsList()) {
|
||||
attachments.add(new TextSecureAttachmentPointer(pointer.getId(),
|
||||
pointer.getContentType(),
|
||||
pointer.getKey().toByteArray(),
|
||||
envelope.getRelay()));
|
||||
}
|
||||
|
||||
return new TextSecureMessage(envelope.getTimestamp(), groupInfo, attachments,
|
||||
content.getBody(), syncContext, endSession);
|
||||
return new TextSecureDataMessage(envelope.getTimestamp(), groupInfo, attachments,
|
||||
content.getBody(), endSession);
|
||||
}
|
||||
|
||||
private TextSecureSyncContext createSyncContext(TextSecureEnvelope envelope, PushMessageContent content) {
|
||||
if (!content.hasSync()) return null;
|
||||
if (!envelope.getSource().equals(localAddress.getNumber())) return null;
|
||||
private TextSecureSyncMessage createSynchronizeMessage(TextSecureEnvelope envelope, SyncMessage content) {
|
||||
if (content.hasSent()) {
|
||||
SyncMessage.Sent sentContent = content.getSent();
|
||||
return new TextSecureSyncMessage(new SentTranscriptMessage(sentContent.getDestination(),
|
||||
sentContent.getTimestamp(),
|
||||
createTextSecureMessage(envelope, sentContent.getMessage())));
|
||||
}
|
||||
|
||||
return new TextSecureSyncContext(content.getSync().getDestination(),
|
||||
content.getSync().getTimestamp());
|
||||
return new TextSecureSyncMessage();
|
||||
}
|
||||
|
||||
private TextSecureGroup createGroupInfo(TextSecureEnvelope envelope, PushMessageContent content) {
|
||||
private TextSecureGroup createGroupInfo(TextSecureEnvelope envelope, DataMessage content) {
|
||||
if (!content.hasGroup()) return null;
|
||||
|
||||
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.
|
||||
*/
|
||||
public class TextSecureMessage {
|
||||
public class TextSecureDataMessage {
|
||||
|
||||
private final long timestamp;
|
||||
private final Optional<List<TextSecureAttachment>> attachments;
|
||||
private final Optional<String> body;
|
||||
private final Optional<TextSecureGroup> group;
|
||||
private final Optional<TextSecureSyncContext> syncContext;
|
||||
private final boolean endSession;
|
||||
|
||||
/**
|
||||
@ -39,11 +38,11 @@ public class TextSecureMessage {
|
||||
* @param timestamp The sent timestamp.
|
||||
* @param body The message contents.
|
||||
*/
|
||||
public TextSecureMessage(long timestamp, String body) {
|
||||
public TextSecureDataMessage(long timestamp, String 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);
|
||||
}
|
||||
|
||||
@ -54,7 +53,7 @@ public class TextSecureMessage {
|
||||
* @param attachments The attachments.
|
||||
* @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);
|
||||
}
|
||||
|
||||
@ -66,8 +65,8 @@ public class TextSecureMessage {
|
||||
* @param attachments The attachments.
|
||||
* @param body The message contents.
|
||||
*/
|
||||
public TextSecureMessage(long timestamp, TextSecureGroup group, List<TextSecureAttachment> attachments, String body) {
|
||||
this(timestamp, group, attachments, body, null, false);
|
||||
public TextSecureDataMessage(long timestamp, TextSecureGroup group, List<TextSecureAttachment> attachments, String body) {
|
||||
this(timestamp, group, attachments, body, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,11 +78,10 @@ public class TextSecureMessage {
|
||||
* @param body The message contents.
|
||||
* @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.body = Optional.fromNullable(body);
|
||||
this.group = Optional.fromNullable(group);
|
||||
this.syncContext = Optional.fromNullable(syncContext);
|
||||
this.endSession = endSession;
|
||||
|
||||
if (attachments != null && !attachments.isEmpty()) {
|
||||
@ -125,10 +123,6 @@ public class TextSecureMessage {
|
||||
return group;
|
||||
}
|
||||
|
||||
public Optional<TextSecureSyncContext> getSyncContext() {
|
||||
return syncContext;
|
||||
}
|
||||
|
||||
public boolean isEndSession() {
|
||||
return endSession;
|
||||
}
|
||||
@ -182,9 +176,9 @@ public class TextSecureMessage {
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextSecureMessage build() {
|
||||
public TextSecureDataMessage build() {
|
||||
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.util.guava.Optional;
|
||||
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.Hex;
|
||||
|
||||
@ -63,7 +63,7 @@ public class TextSecureEnvelope {
|
||||
private static final int IV_LENGTH = 16;
|
||||
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
|
||||
@ -99,42 +99,46 @@ public class TextSecureEnvelope {
|
||||
|
||||
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,
|
||||
String relay, long timestamp, byte[] message)
|
||||
String relay, long timestamp,
|
||||
byte[] legacyMessage, byte[] content)
|
||||
{
|
||||
this.signal = IncomingPushMessageSignal.newBuilder()
|
||||
.setType(IncomingPushMessageSignal.Type.valueOf(type))
|
||||
.setSource(source)
|
||||
.setSourceDevice(sourceDevice)
|
||||
.setRelay(relay)
|
||||
.setTimestamp(timestamp)
|
||||
.setMessage(ByteString.copyFrom(message))
|
||||
.build();
|
||||
Envelope.Builder builder = Envelope.newBuilder()
|
||||
.setType(Envelope.Type.valueOf(type))
|
||||
.setSource(source)
|
||||
.setSourceDevice(sourceDevice)
|
||||
.setRelay(relay)
|
||||
.setTimestamp(timestamp);
|
||||
|
||||
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.
|
||||
*/
|
||||
public String getSource() {
|
||||
return signal.getSource();
|
||||
return envelope.getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The envelope's sender device ID.
|
||||
*/
|
||||
public int getSourceDevice() {
|
||||
return signal.getSourceDevice();
|
||||
return envelope.getSourceDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The envelope's sender as a TextSecureAddress.
|
||||
*/
|
||||
public TextSecureAddress getSourceAddress() {
|
||||
return new TextSecureAddress(signal.getSource(),
|
||||
signal.hasRelay() ? Optional.fromNullable(signal.getRelay()) :
|
||||
return new TextSecureAddress(envelope.getSource(),
|
||||
envelope.hasRelay() ? Optional.fromNullable(envelope.getRelay()) :
|
||||
Optional.<String>absent());
|
||||
}
|
||||
|
||||
@ -142,49 +146,70 @@ public class TextSecureEnvelope {
|
||||
* @return The envelope content type.
|
||||
*/
|
||||
public int getType() {
|
||||
return signal.getType().getNumber();
|
||||
return envelope.getType().getNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The federated server this envelope came from.
|
||||
*/
|
||||
public String getRelay() {
|
||||
return signal.getRelay();
|
||||
return envelope.getRelay();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The timestamp this envelope was sent.
|
||||
*/
|
||||
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() {
|
||||
return signal.getMessage().toByteArray();
|
||||
public boolean hasLegacyMessage() {
|
||||
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}
|
||||
*/
|
||||
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}
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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 {
|
||||
|
@ -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;
|
||||
@JsonProperty
|
||||
private String body;
|
||||
@JsonProperty
|
||||
private String content;
|
||||
|
||||
public OutgoingPushMessage(int type,
|
||||
int destinationDeviceId,
|
||||
int destinationRegistrationId,
|
||||
String body)
|
||||
String legacyMessage, String content)
|
||||
{
|
||||
this.type = type;
|
||||
this.destinationDeviceId = destinationDeviceId;
|
||||
this.destinationRegistrationId = destinationRegistrationId;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public int getDestinationDeviceId() {
|
||||
return destinationDeviceId;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getDestinationRegistrationId() {
|
||||
return destinationRegistrationId;
|
||||
this.body = legacyMessage;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,9 @@ public class TextSecureEnvelopeEntity {
|
||||
@JsonProperty
|
||||
private byte[] message;
|
||||
|
||||
@JsonProperty
|
||||
private byte[] content;
|
||||
|
||||
public TextSecureEnvelopeEntity() {}
|
||||
|
||||
public int getType() {
|
||||
@ -47,4 +50,8 @@ public class TextSecureEnvelopeEntity {
|
||||
public byte[] getMessage() {
|
||||
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:
|
||||
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