diff --git a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java index c0a3e764ac..e1ec160dea 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java @@ -42,6 +42,7 @@ 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; @@ -121,7 +122,7 @@ public class TextSecureMessageSender { SendMessageResponse response = sendMessage(recipient, timestamp, content); if (response != null && response.getNeedsSync()) { - byte[] syncMessage = createSyncMessageContent(content, recipient, timestamp); + byte[] syncMessage = createSyncMessageContent(content, Optional.of(recipient), timestamp); sendMessage(syncAddress, timestamp, syncMessage); } @@ -145,8 +146,18 @@ public class TextSecureMessageSender { public void sendMessage(List recipients, TextSecureMessage message) throws IOException, EncapsulatedExceptions { - byte[] content = createMessageContent(message); - sendMessage(recipients, message.getTimestamp(), content); + byte[] content = createMessageContent(message); + long timestamp = message.getTimestamp(); + SendMessageResponse response = sendMessage(recipients, timestamp, content); + + try { + if (response != null && response.getNeedsSync()) { + byte[] syncMessage = createSyncMessageContent(content, Optional.absent(), timestamp); + sendMessage(syncAddress, timestamp, syncMessage); + } + } catch (UntrustedIdentityException e) { + throw new EncapsulatedExceptions(e); + } } private byte[] createMessageContent(TextSecureMessage message) throws IOException { @@ -172,13 +183,17 @@ public class TextSecureMessageSender { return builder.build().toByteArray(); } - private byte[] createSyncMessageContent(byte[] content, TextSecureAddress recipient, long timestamp) { + private byte[] createSyncMessageContent(byte[] content, Optional recipient, long timestamp) { try { + SyncMessageContext.Builder syncMessageContext = SyncMessageContext.newBuilder(); + syncMessageContext.setTimestamp(timestamp); + + if (recipient.isPresent()) { + syncMessageContext.setDestination(recipient.get().getNumber()); + } + PushMessageContent.Builder builder = PushMessageContent.parseFrom(content).toBuilder(); - builder.setSync(PushMessageContent.SyncMessageContext.newBuilder() - .setDestination(recipient.getNumber()) - .setTimestamp(timestamp) - .build()); + builder.setSync(syncMessageContext.build()); return builder.build().toByteArray(); } catch (InvalidProtocolBufferException e) { @@ -209,16 +224,18 @@ public class TextSecureMessageSender { return builder.build(); } - private void sendMessage(List recipients, long timestamp, byte[] content) + private SendMessageResponse sendMessage(List recipients, long timestamp, byte[] content) throws IOException, EncapsulatedExceptions { List untrustedIdentities = new LinkedList<>(); List unregisteredUsers = new LinkedList<>(); List networkExceptions = new LinkedList<>(); + SendMessageResponse response = null; + for (TextSecureAddress recipient : recipients) { try { - sendMessage(recipient, timestamp, content); + response = sendMessage(recipient, timestamp, content); } catch (UntrustedIdentityException e) { Log.w(TAG, e); untrustedIdentities.add(e); @@ -234,6 +251,8 @@ public class TextSecureMessageSender { if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty() || !networkExceptions.isEmpty()) { throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers, networkExceptions); } + + return response; } private SendMessageResponse sendMessage(TextSecureAddress recipient, long timestamp, byte[] content) diff --git a/java/src/main/java/org/whispersystems/textsecure/api/crypto/TextSecureCipher.java b/java/src/main/java/org/whispersystems/textsecure/api/crypto/TextSecureCipher.java index cb94f0998b..3c0b6d261c 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/crypto/TextSecureCipher.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/crypto/TextSecureCipher.java @@ -37,6 +37,7 @@ import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer; 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.internal.push.OutgoingPushMessage; import org.whispersystems.textsecure.internal.push.PushMessageProtos; import org.whispersystems.textsecure.internal.push.PushTransportDetails; @@ -126,6 +127,7 @@ public class TextSecureCipher { private TextSecureMessage createTextSecureMessage(TextSecureEnvelope envelope, PushMessageContent content) { TextSecureGroup groupInfo = createGroupInfo(envelope, content); + TextSecureSyncContext syncContext = createSyncContext(content); List attachments = new LinkedList<>(); boolean endSession = ((content.getFlags() & PushMessageContent.Flags.END_SESSION_VALUE) != 0); boolean secure = envelope.isWhisperMessage() || envelope.isPreKeyWhisperMessage(); @@ -138,7 +140,14 @@ public class TextSecureCipher { } return new TextSecureMessage(envelope.getTimestamp(), groupInfo, attachments, - content.getBody(), secure, endSession); + content.getBody(), syncContext, secure, endSession); + } + + private TextSecureSyncContext createSyncContext(PushMessageContent content) { + if (!content.hasSync()) return null; + + return new TextSecureSyncContext(content.getSync().getDestination(), + content.getSync().getTimestamp()); } private TextSecureGroup createGroupInfo(TextSecureEnvelope envelope, PushMessageContent content) { diff --git a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureMessage.java b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureMessage.java index f6044a3d2c..d5ef352fcb 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureMessage.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureMessage.java @@ -30,6 +30,7 @@ public class TextSecureMessage { private final Optional> attachments; private final Optional body; private final Optional group; + private final Optional syncContext; private final boolean secure; private final boolean endSession; @@ -67,7 +68,7 @@ public class TextSecureMessage { * @param body The message contents. */ public TextSecureMessage(long timestamp, TextSecureGroup group, List attachments, String body) { - this(timestamp, group, attachments, body, true, false); + this(timestamp, group, attachments, body, null, true, false); } /** @@ -80,10 +81,11 @@ public class TextSecureMessage { * @param secure Flag indicating whether this message is to be encrypted. * @param endSession Flag indicating whether this message should close a session. */ - public TextSecureMessage(long timestamp, TextSecureGroup group, List attachments, String body, boolean secure, boolean endSession) { + public TextSecureMessage(long timestamp, TextSecureGroup group, List attachments, String body, TextSecureSyncContext syncContext, boolean secure, boolean endSession) { this.timestamp = timestamp; this.body = Optional.fromNullable(body); this.group = Optional.fromNullable(group); + this.syncContext = Optional.fromNullable(syncContext); this.secure = secure; this.endSession = endSession; @@ -126,6 +128,10 @@ public class TextSecureMessage { return group; } + public Optional getSyncContext() { + return syncContext; + } + public boolean isSecure() { return secure; } @@ -185,7 +191,7 @@ public class TextSecureMessage { public TextSecureMessage build() { if (timestamp == 0) timestamp = System.currentTimeMillis(); - return new TextSecureMessage(timestamp, group, attachments, body, true, endSession); + return new TextSecureMessage(timestamp, group, attachments, body, null, true, endSession); } } } diff --git a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureSyncContext.java b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureSyncContext.java new file mode 100644 index 0000000000..36589a0b1a --- /dev/null +++ b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureSyncContext.java @@ -0,0 +1,20 @@ +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; + } +} diff --git a/java/src/main/java/org/whispersystems/textsecure/api/push/exceptions/EncapsulatedExceptions.java b/java/src/main/java/org/whispersystems/textsecure/api/push/exceptions/EncapsulatedExceptions.java index dc0f15030b..1e148f4cda 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/push/exceptions/EncapsulatedExceptions.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/push/exceptions/EncapsulatedExceptions.java @@ -18,6 +18,7 @@ package org.whispersystems.textsecure.api.push.exceptions; import org.whispersystems.textsecure.api.crypto.UntrustedIdentityException; +import java.util.LinkedList; import java.util.List; public class EncapsulatedExceptions extends Throwable { @@ -35,6 +36,14 @@ public class EncapsulatedExceptions extends Throwable { this.networkExceptions = networkExceptions; } + public EncapsulatedExceptions(UntrustedIdentityException e) { + this.untrustedIdentityExceptions = new LinkedList<>(); + this.unregisteredUserExceptions = new LinkedList<>(); + this.networkExceptions = new LinkedList<>(); + + this.untrustedIdentityExceptions.add(e); + } + public List getUntrustedIdentityExceptions() { return untrustedIdentityExceptions; }