From 0cc5837d7f8445faef9f7db7961bca165de1e8c7 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 29 Aug 2013 17:01:30 -0700 Subject: [PATCH] Support encrypted transport, properly handle multiple recipients. 1) Add encryption support for the transport layer. This obscures metadata from the push messaging provider. 2) Better support the direction multiple destination messages is headed (one unique message per recipient). --- .../protobuf/IncomingPushMessageSignal.proto | 19 + library/protobuf/Makefile | 2 +- library/protobuf/PreKeyEntity.proto | 10 - .../crypto/protocol/PreKeyBundleMessage.java | 4 +- .../push/IncomingEncryptedPushMessage.java | 136 ++ .../textsecure/push/IncomingPushMessage.java | 45 +- .../textsecure/push/OutgoingPushMessage.java | 52 +- .../push/OutgoingPushMessageList.java | 22 + .../textsecure/push/PushMessage.java | 9 + .../textsecure/push/PushMessageProtos.java | 1473 +++++++++++++++++ .../textsecure/push/PushServiceSocket.java | 52 +- .../textsecure/push/PushTransportDetails.java | 4 +- .../securesms/gcm/GcmIntentService.java | 25 +- .../securesms/service/SmsReceiver.java | 7 +- .../securesms/transport/PushTransport.java | 38 +- .../securesms/transport/SmsTransport.java | 3 +- .../securesms/util/TextSecurePreferences.java | 4 + 17 files changed, 1798 insertions(+), 107 deletions(-) create mode 100644 library/protobuf/IncomingPushMessageSignal.proto delete mode 100644 library/protobuf/PreKeyEntity.proto create mode 100644 library/src/org/whispersystems/textsecure/push/IncomingEncryptedPushMessage.java create mode 100644 library/src/org/whispersystems/textsecure/push/OutgoingPushMessageList.java create mode 100644 library/src/org/whispersystems/textsecure/push/PushMessage.java create mode 100644 library/src/org/whispersystems/textsecure/push/PushMessageProtos.java diff --git a/library/protobuf/IncomingPushMessageSignal.proto b/library/protobuf/IncomingPushMessageSignal.proto new file mode 100644 index 0000000000..1e82872705 --- /dev/null +++ b/library/protobuf/IncomingPushMessageSignal.proto @@ -0,0 +1,19 @@ +package textsecure; + +option java_package = "org.whispersystems.textsecure.push"; +option java_outer_classname = "PushMessageProtos"; + +message IncomingPushMessageSignal { + optional uint32 type = 1; + optional string source = 2; + repeated string destinations = 3; + optional bytes message = 4; + + message AttachmentPointer { + optional string contentType = 1; + optional string key = 2; + } + + repeated AttachmentPointer attachments = 5; + optional uint64 timestamp = 6; +} \ No newline at end of file diff --git a/library/protobuf/Makefile b/library/protobuf/Makefile index 836c360af6..55640f5437 100644 --- a/library/protobuf/Makefile +++ b/library/protobuf/Makefile @@ -1,3 +1,3 @@ all: - protoc --java_out=../src/ PreKeyEntity.proto + protoc --java_out=../src/ IncomingPushMessageSignal.proto diff --git a/library/protobuf/PreKeyEntity.proto b/library/protobuf/PreKeyEntity.proto deleted file mode 100644 index 490c1cc5ba..0000000000 --- a/library/protobuf/PreKeyEntity.proto +++ /dev/null @@ -1,10 +0,0 @@ -package textsecure; - -option java_package = "org.whispersystems.textsecure.encoded"; -option java_outer_classname = "PreKeyProtos"; - -message PreKeyEntity { - optional uint64 id = 1; - optional bytes public_key = 2; - optional bytes identity_key = 3; -} diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java index 02641c1cff..48e17fdedf 100644 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java @@ -96,8 +96,8 @@ public class PreKeyBundleMessage { } } - public String serialize() { - return Base64.encodeBytesWithoutPadding(this.messageBytes); + public byte[] serialize() { + return this.messageBytes; } public int getSupportedVersion() { diff --git a/library/src/org/whispersystems/textsecure/push/IncomingEncryptedPushMessage.java b/library/src/org/whispersystems/textsecure/push/IncomingEncryptedPushMessage.java new file mode 100644 index 0000000000..45d173a643 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/push/IncomingEncryptedPushMessage.java @@ -0,0 +1,136 @@ +package org.whispersystems.textsecure.push; + +import android.util.Log; + +import org.whispersystems.textsecure.crypto.InvalidVersionException; +import org.whispersystems.textsecure.util.Base64; +import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal; +import org.whispersystems.textsecure.util.Hex; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +public class IncomingEncryptedPushMessage { + + private static final int SUPPORTED_VERSION = 1; + private static final int CIPHER_KEY_SIZE = 32; + private static final int MAC_KEY_SIZE = 20; + private static final int MAC_SIZE = 10; + + private static final int VERSION_OFFSET = 0; + private static final int VERSION_LENGTH = 1; + private static final int IV_OFFSET = VERSION_OFFSET + VERSION_LENGTH; + private static final int IV_LENGTH = 16; + private static final int CIPHERTEXT_OFFSET = IV_OFFSET + IV_LENGTH; + + private final IncomingPushMessage incomingPushMessage; + + public IncomingEncryptedPushMessage(String message, String signalingKey) + throws IOException, InvalidVersionException + { + byte[] ciphertext = Base64.decode(message); + + if (ciphertext.length < VERSION_LENGTH || ciphertext[VERSION_OFFSET] != SUPPORTED_VERSION) + throw new InvalidVersionException("Unsupported version!"); + + SecretKeySpec cipherKey = getCipherKey(signalingKey); + SecretKeySpec macKey = getMacKey(signalingKey); + + verifyMac(ciphertext, macKey); + + byte[] plaintext = getPlaintext(ciphertext, cipherKey); + IncomingPushMessageSignal signal = IncomingPushMessageSignal.parseFrom(plaintext); + + this.incomingPushMessage = new IncomingPushMessage(signal); + } + + public IncomingPushMessage getIncomingPushMessage() { + return incomingPushMessage; + } + + private byte[] getPlaintext(byte[] ciphertext, SecretKeySpec cipherKey) throws IOException { + try { + byte[] ivBytes = new byte[IV_LENGTH]; + System.arraycopy(ciphertext, IV_OFFSET, ivBytes, 0, ivBytes.length); + IvParameterSpec iv = new IvParameterSpec(ivBytes); + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, cipherKey, iv); + + return cipher.doFinal(ciphertext, CIPHERTEXT_OFFSET, + ciphertext.length - VERSION_LENGTH - IV_LENGTH - MAC_SIZE); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } catch (NoSuchPaddingException e) { + throw new AssertionError(e); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } catch (InvalidAlgorithmParameterException e) { + throw new AssertionError(e); + } catch (IllegalBlockSizeException e) { + throw new AssertionError(e); + } catch (BadPaddingException e) { + Log.w("IncomingEncryptedPushMessage", e); + throw new IOException("Bad padding?"); + } + } + + private void verifyMac(byte[] ciphertext, SecretKeySpec macKey) throws IOException { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(macKey); + + if (ciphertext.length < MAC_SIZE + 1) + throw new IOException("Invalid MAC!"); + + mac.update(ciphertext, 0, ciphertext.length - MAC_SIZE); + + byte[] ourMacFull = mac.doFinal(); + byte[] ourMacBytes = new byte[MAC_SIZE]; + System.arraycopy(ourMacFull, 0, ourMacBytes, 0, ourMacBytes.length); + + byte[] theirMacBytes = new byte[MAC_SIZE]; + System.arraycopy(ciphertext, ciphertext.length-MAC_SIZE, theirMacBytes, 0, theirMacBytes.length); + + Log.w("IncomingEncryptedPushMessage", "Our MAC: " + Hex.toString(ourMacBytes)); + Log.w("IncomingEncryptedPushMessage", "Thr MAC: " + Hex.toString(theirMacBytes)); + + if (!Arrays.equals(ourMacBytes, theirMacBytes)) { + throw new IOException("Invalid MAC compare!"); + } + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } + } + + + private SecretKeySpec getCipherKey(String signalingKey) throws IOException { + byte[] signalingKeyBytes = Base64.decode(signalingKey); + byte[] cipherKey = new byte[CIPHER_KEY_SIZE]; + System.arraycopy(signalingKeyBytes, 0, cipherKey, 0, cipherKey.length); + + return new SecretKeySpec(cipherKey, "AES"); + } + + + private SecretKeySpec getMacKey(String signalingKey) throws IOException { + byte[] signalingKeyBytes = Base64.decode(signalingKey); + byte[] macKey = new byte[MAC_KEY_SIZE]; + System.arraycopy(signalingKeyBytes, CIPHER_KEY_SIZE, macKey, 0, macKey.length); + + return new SecretKeySpec(macKey, "HmacSHA256"); + } + +} diff --git a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java index 8a146a68bc..cb2427cd65 100644 --- a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java +++ b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java @@ -1,12 +1,16 @@ package org.whispersystems.textsecure.push; +import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal; +import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer; +import org.whispersystems.textsecure.util.Base64; + import android.os.Parcel; import android.os.Parcelable; import java.util.LinkedList; import java.util.List; -public class IncomingPushMessage implements Parcelable { +public class IncomingPushMessage implements PushMessage, Parcelable { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override @@ -23,19 +27,23 @@ public class IncomingPushMessage implements Parcelable { private int type; private String source; private List destinations; - private String messageText; + private byte[] message; private List attachments; private long timestamp; - public IncomingPushMessage(String source, List destinations, String messageText, - int type, List attachments, long timestamp) - { - this.type = type; - this.source = source; - this.destinations = destinations; - this.messageText = messageText; - this.attachments = attachments; - this.timestamp = timestamp; + public IncomingPushMessage(IncomingPushMessageSignal signal) { + this.type = signal.getType(); + this.source = signal.getSource(); + this.destinations = signal.getDestinationsList(); + this.message = signal.getMessage().toByteArray(); + this.timestamp = signal.getTimestamp(); + this.attachments = new LinkedList(); + + List attachmentPointers = signal.getAttachmentsList(); + + for (AttachmentPointer pointer : attachmentPointers) { + this.attachments.add(new PushAttachmentPointer(pointer.getContentType(), pointer.getKey())); + } } public IncomingPushMessage(Parcel in) { @@ -44,7 +52,8 @@ public class IncomingPushMessage implements Parcelable { this.source = in.readString(); in.readStringList(destinations); - this.messageText = in.readString(); + this.message = new byte[in.readInt()]; + in.readByteArray(this.message); in.readList(attachments, PushAttachmentPointer.class.getClassLoader()); this.timestamp = in.readLong(); } @@ -62,7 +71,14 @@ public class IncomingPushMessage implements Parcelable { } public String getMessageText() { - return messageText; + if (type == TYPE_MESSAGE_CIPHERTEXT || + type == TYPE_MESSAGE_KEY_EXCHANGE || + type == TYPE_MESSAGE_PREKEY_BUNDLE) + { + return Base64.encodeBytesWithoutPadding(message); + } + + return new String(message); } public List getDestinations() { @@ -82,7 +98,8 @@ public class IncomingPushMessage implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(source); dest.writeStringList(destinations); - dest.writeString(messageText); + dest.writeInt(message.length); + dest.writeByteArray(message); dest.writeList(attachments); dest.writeLong(timestamp); } diff --git a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java index 097d2dd01d..487a0da07d 100644 --- a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java +++ b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java @@ -1,50 +1,40 @@ package org.whispersystems.textsecure.push; +import org.whispersystems.textsecure.util.Base64; + import java.util.LinkedList; import java.util.List; -public class OutgoingPushMessage { - - public static final int TYPE_MESSAGE_PLAINTEXT = 0; - public static final int TYPE_MESSAGE_CIPHERTEXT = 1; - public static final int TYPE_MESSAGE_KEY_EXCHANGE = 2; - public static final int TYPE_MESSAGE_PREKEY_BUNDLE = 3; +public class OutgoingPushMessage implements PushMessage { private int type; - private List destinations; - private String messageText; + private String destination; + private String body; private List attachments; - public OutgoingPushMessage(String destination, String messageText, int type) { - this.destinations = new LinkedList(); - this.attachments = new LinkedList(); - this.messageText = messageText; - this.destinations.add(destination); - this.type = type; + public OutgoingPushMessage(String destination, byte[] body, int type) { + this.attachments = new LinkedList(); + this.destination = destination; + this.body = Base64.encodeBytes(body); + this.type = type; } - public OutgoingPushMessage(List destinations, String messageText, int type) { - this.destinations = destinations; - this.messageText = messageText; - this.attachments = new LinkedList(); - this.type = type; - } - - public OutgoingPushMessage(List destinations, String messageText, - List attachments, int type) + public OutgoingPushMessage(String destination, byte[] body, + List attachments, + int type) { - this.destinations = destinations; - this.messageText = messageText; - this.attachments = attachments; - this.type = type; + this.destination = destination; + this.body = Base64.encodeBytes(body); + this.attachments = attachments; + this.type = type; } - public List getDestinations() { - return destinations; + public String getDestination() { + return destination; } - public String getMessageText() { - return messageText; + public String getBody() { + return body; } public List getAttachments() { diff --git a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessageList.java b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessageList.java new file mode 100644 index 0000000000..6504ba5b7a --- /dev/null +++ b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessageList.java @@ -0,0 +1,22 @@ +package org.whispersystems.textsecure.push; + +import java.util.LinkedList; +import java.util.List; + +public class OutgoingPushMessageList { + + private List messages; + + public OutgoingPushMessageList(OutgoingPushMessage message) { + this.messages = new LinkedList(); + this.messages.add(message); + } + + public OutgoingPushMessageList(List messages) { + this.messages = messages; + } + + public List getMessages() { + return messages; + } +} diff --git a/library/src/org/whispersystems/textsecure/push/PushMessage.java b/library/src/org/whispersystems/textsecure/push/PushMessage.java new file mode 100644 index 0000000000..2cec4f4153 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/push/PushMessage.java @@ -0,0 +1,9 @@ +package org.whispersystems.textsecure.push; + + +public interface PushMessage { + public static final int TYPE_MESSAGE_PLAINTEXT = 0; + public static final int TYPE_MESSAGE_CIPHERTEXT = 1; + public static final int TYPE_MESSAGE_KEY_EXCHANGE = 2; + public static final int TYPE_MESSAGE_PREKEY_BUNDLE = 3; +} diff --git a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java new file mode 100644 index 0000000000..ff79ffa74e --- /dev/null +++ b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java @@ -0,0 +1,1473 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: IncomingPushMessageSignal.proto + +package org.whispersystems.textsecure.push; + +public final class PushMessageProtos { + private PushMessageProtos() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + } + public interface IncomingPushMessageSignalOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional uint32 type = 1; + boolean hasType(); + int getType(); + + // optional string source = 2; + boolean hasSource(); + String getSource(); + + // repeated string destinations = 3; + java.util.List getDestinationsList(); + int getDestinationsCount(); + String getDestinations(int index); + + // optional bytes message = 4; + boolean hasMessage(); + com.google.protobuf.ByteString getMessage(); + + // repeated .textsecure.IncomingPushMessageSignal.AttachmentPointer attachments = 5; + java.util.List + getAttachmentsList(); + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer getAttachments(int index); + int getAttachmentsCount(); + java.util.List + getAttachmentsOrBuilderList(); + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder getAttachmentsOrBuilder( + int index); + + // optional uint64 timestamp = 6; + boolean hasTimestamp(); + long getTimestamp(); + } + public static final class IncomingPushMessageSignal extends + com.google.protobuf.GeneratedMessage + implements IncomingPushMessageSignalOrBuilder { + // Use IncomingPushMessageSignal.newBuilder() to construct. + private IncomingPushMessageSignal(Builder builder) { + super(builder); + } + private IncomingPushMessageSignal(boolean noInit) {} + + private static final IncomingPushMessageSignal defaultInstance; + public static IncomingPushMessageSignal getDefaultInstance() { + return defaultInstance; + } + + public IncomingPushMessageSignal getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_fieldAccessorTable; + } + + public interface AttachmentPointerOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional string contentType = 1; + boolean hasContentType(); + String getContentType(); + + // optional string key = 2; + boolean hasKey(); + String getKey(); + } + public static final class AttachmentPointer extends + com.google.protobuf.GeneratedMessage + implements AttachmentPointerOrBuilder { + // Use AttachmentPointer.newBuilder() to construct. + private AttachmentPointer(Builder builder) { + super(builder); + } + private AttachmentPointer(boolean noInit) {} + + private static final AttachmentPointer defaultInstance; + public static AttachmentPointer getDefaultInstance() { + return defaultInstance; + } + + public AttachmentPointer getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_fieldAccessorTable; + } + + private int bitField0_; + // optional string contentType = 1; + public static final int CONTENTTYPE_FIELD_NUMBER = 1; + private java.lang.Object contentType_; + public boolean hasContentType() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public String getContentType() { + java.lang.Object ref = contentType_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (com.google.protobuf.Internal.isValidUtf8(bs)) { + contentType_ = s; + } + return s; + } + } + private com.google.protobuf.ByteString getContentTypeBytes() { + java.lang.Object ref = contentType_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + contentType_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // optional string key = 2; + public static final int KEY_FIELD_NUMBER = 2; + private java.lang.Object key_; + public boolean hasKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public String getKey() { + java.lang.Object ref = key_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (com.google.protobuf.Internal.isValidUtf8(bs)) { + key_ = s; + } + return s; + } + } + private com.google.protobuf.ByteString getKeyBytes() { + java.lang.Object ref = key_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + key_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private void initFields() { + contentType_ = ""; + key_ = ""; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, getContentTypeBytes()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getKeyBytes()); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, getContentTypeBytes()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getKeyBytes()); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + contentType_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + key_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.getDescriptor(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer getDefaultInstanceForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.getDefaultInstance(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer build() { + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer buildPartial() { + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer result = new org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.contentType_ = contentType_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.key_ = key_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer) { + return mergeFrom((org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer other) { + if (other == org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.getDefaultInstance()) return this; + if (other.hasContentType()) { + setContentType(other.getContentType()); + } + if (other.hasKey()) { + setKey(other.getKey()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + contentType_ = input.readBytes(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + key_ = input.readBytes(); + break; + } + } + } + } + + private int bitField0_; + + // optional string contentType = 1; + private java.lang.Object contentType_ = ""; + public boolean hasContentType() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public String getContentType() { + java.lang.Object ref = contentType_; + if (!(ref instanceof String)) { + String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + contentType_ = s; + return s; + } else { + return (String) ref; + } + } + public Builder setContentType(String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + contentType_ = value; + onChanged(); + return this; + } + public Builder clearContentType() { + bitField0_ = (bitField0_ & ~0x00000001); + contentType_ = getDefaultInstance().getContentType(); + onChanged(); + return this; + } + void setContentType(com.google.protobuf.ByteString value) { + bitField0_ |= 0x00000001; + contentType_ = value; + onChanged(); + } + + // optional string key = 2; + private java.lang.Object key_ = ""; + public boolean hasKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public String getKey() { + java.lang.Object ref = key_; + if (!(ref instanceof String)) { + String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + key_ = s; + return s; + } else { + return (String) ref; + } + } + public Builder setKey(String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + key_ = value; + onChanged(); + return this; + } + public Builder clearKey() { + bitField0_ = (bitField0_ & ~0x00000002); + key_ = getDefaultInstance().getKey(); + onChanged(); + return this; + } + void setKey(com.google.protobuf.ByteString value) { + bitField0_ |= 0x00000002; + key_ = value; + onChanged(); + } + + // @@protoc_insertion_point(builder_scope:textsecure.IncomingPushMessageSignal.AttachmentPointer) + } + + static { + defaultInstance = new AttachmentPointer(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.IncomingPushMessageSignal.AttachmentPointer) + } + + private int bitField0_; + // optional uint32 type = 1; + public static final int TYPE_FIELD_NUMBER = 1; + private int type_; + public boolean hasType() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getType() { + return type_; + } + + // optional string source = 2; + public static final int SOURCE_FIELD_NUMBER = 2; + private java.lang.Object source_; + public boolean hasSource() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public String getSource() { + java.lang.Object ref = source_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (com.google.protobuf.Internal.isValidUtf8(bs)) { + source_ = s; + } + return s; + } + } + private com.google.protobuf.ByteString getSourceBytes() { + java.lang.Object ref = source_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + source_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // repeated string destinations = 3; + public static final int DESTINATIONS_FIELD_NUMBER = 3; + private com.google.protobuf.LazyStringList destinations_; + public java.util.List + getDestinationsList() { + return destinations_; + } + public int getDestinationsCount() { + return destinations_.size(); + } + public String getDestinations(int index) { + return destinations_.get(index); + } + + // optional bytes message = 4; + public static final int MESSAGE_FIELD_NUMBER = 4; + private com.google.protobuf.ByteString message_; + public boolean hasMessage() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getMessage() { + return message_; + } + + // repeated .textsecure.IncomingPushMessageSignal.AttachmentPointer attachments = 5; + public static final int ATTACHMENTS_FIELD_NUMBER = 5; + private java.util.List attachments_; + public java.util.List getAttachmentsList() { + return attachments_; + } + public java.util.List + getAttachmentsOrBuilderList() { + return attachments_; + } + public int getAttachmentsCount() { + return attachments_.size(); + } + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer getAttachments(int index) { + return attachments_.get(index); + } + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder getAttachmentsOrBuilder( + int index) { + return attachments_.get(index); + } + + // optional uint64 timestamp = 6; + public static final int TIMESTAMP_FIELD_NUMBER = 6; + private long timestamp_; + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public long getTimestamp() { + return timestamp_; + } + + private void initFields() { + type_ = 0; + source_ = ""; + destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; + message_ = com.google.protobuf.ByteString.EMPTY; + attachments_ = java.util.Collections.emptyList(); + timestamp_ = 0L; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt32(1, type_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getSourceBytes()); + } + for (int i = 0; i < destinations_.size(); i++) { + output.writeBytes(3, destinations_.getByteString(i)); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(4, message_); + } + for (int i = 0; i < attachments_.size(); i++) { + output.writeMessage(5, attachments_.get(i)); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeUInt64(6, timestamp_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(1, type_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getSourceBytes()); + } + { + int dataSize = 0; + for (int i = 0; i < destinations_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeBytesSizeNoTag(destinations_.getByteString(i)); + } + size += dataSize; + size += 1 * getDestinationsList().size(); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(4, message_); + } + for (int i = 0; i < attachments_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(5, attachments_.get(i)); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(6, timestamp_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignalOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getAttachmentsFieldBuilder(); + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + type_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + source_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); + message_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + if (attachmentsBuilder_ == null) { + attachments_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000010); + } else { + attachmentsBuilder_.clear(); + } + timestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000020); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.getDescriptor(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal getDefaultInstanceForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.getDefaultInstance(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal build() { + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal buildPartial() { + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal result = new org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.type_ = type_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.source_ = source_; + if (((bitField0_ & 0x00000004) == 0x00000004)) { + destinations_ = new com.google.protobuf.UnmodifiableLazyStringList( + destinations_); + bitField0_ = (bitField0_ & ~0x00000004); + } + result.destinations_ = destinations_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000004; + } + result.message_ = message_; + if (attachmentsBuilder_ == null) { + if (((bitField0_ & 0x00000010) == 0x00000010)) { + attachments_ = java.util.Collections.unmodifiableList(attachments_); + bitField0_ = (bitField0_ & ~0x00000010); + } + result.attachments_ = attachments_; + } else { + result.attachments_ = attachmentsBuilder_.build(); + } + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000008; + } + result.timestamp_ = timestamp_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal) { + return mergeFrom((org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal other) { + if (other == org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.getDefaultInstance()) return this; + if (other.hasType()) { + setType(other.getType()); + } + if (other.hasSource()) { + setSource(other.getSource()); + } + if (!other.destinations_.isEmpty()) { + if (destinations_.isEmpty()) { + destinations_ = other.destinations_; + bitField0_ = (bitField0_ & ~0x00000004); + } else { + ensureDestinationsIsMutable(); + destinations_.addAll(other.destinations_); + } + onChanged(); + } + if (other.hasMessage()) { + setMessage(other.getMessage()); + } + if (attachmentsBuilder_ == null) { + if (!other.attachments_.isEmpty()) { + if (attachments_.isEmpty()) { + attachments_ = other.attachments_; + bitField0_ = (bitField0_ & ~0x00000010); + } else { + ensureAttachmentsIsMutable(); + attachments_.addAll(other.attachments_); + } + onChanged(); + } + } else { + if (!other.attachments_.isEmpty()) { + if (attachmentsBuilder_.isEmpty()) { + attachmentsBuilder_.dispose(); + attachmentsBuilder_ = null; + attachments_ = other.attachments_; + bitField0_ = (bitField0_ & ~0x00000010); + attachmentsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getAttachmentsFieldBuilder() : null; + } else { + attachmentsBuilder_.addAllMessages(other.attachments_); + } + } + } + if (other.hasTimestamp()) { + setTimestamp(other.getTimestamp()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + type_ = input.readUInt32(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + source_ = input.readBytes(); + break; + } + case 26: { + ensureDestinationsIsMutable(); + destinations_.add(input.readBytes()); + break; + } + case 34: { + bitField0_ |= 0x00000008; + message_ = input.readBytes(); + break; + } + case 42: { + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder subBuilder = org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.newBuilder(); + input.readMessage(subBuilder, extensionRegistry); + addAttachments(subBuilder.buildPartial()); + break; + } + case 48: { + bitField0_ |= 0x00000020; + timestamp_ = input.readUInt64(); + break; + } + } + } + } + + private int bitField0_; + + // optional uint32 type = 1; + private int type_ ; + public boolean hasType() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getType() { + return type_; + } + public Builder setType(int value) { + bitField0_ |= 0x00000001; + type_ = value; + onChanged(); + return this; + } + public Builder clearType() { + bitField0_ = (bitField0_ & ~0x00000001); + type_ = 0; + onChanged(); + return this; + } + + // optional string source = 2; + private java.lang.Object source_ = ""; + public boolean hasSource() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public String getSource() { + java.lang.Object ref = source_; + if (!(ref instanceof String)) { + String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + source_ = s; + return s; + } else { + return (String) ref; + } + } + public Builder setSource(String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + source_ = value; + onChanged(); + return this; + } + public Builder clearSource() { + bitField0_ = (bitField0_ & ~0x00000002); + source_ = getDefaultInstance().getSource(); + onChanged(); + return this; + } + void setSource(com.google.protobuf.ByteString value) { + bitField0_ |= 0x00000002; + source_ = value; + onChanged(); + } + + // repeated string destinations = 3; + private com.google.protobuf.LazyStringList destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureDestinationsIsMutable() { + if (!((bitField0_ & 0x00000004) == 0x00000004)) { + destinations_ = new com.google.protobuf.LazyStringArrayList(destinations_); + bitField0_ |= 0x00000004; + } + } + public java.util.List + getDestinationsList() { + return java.util.Collections.unmodifiableList(destinations_); + } + public int getDestinationsCount() { + return destinations_.size(); + } + public String getDestinations(int index) { + return destinations_.get(index); + } + public Builder setDestinations( + int index, String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureDestinationsIsMutable(); + destinations_.set(index, value); + onChanged(); + return this; + } + public Builder addDestinations(String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureDestinationsIsMutable(); + destinations_.add(value); + onChanged(); + return this; + } + public Builder addAllDestinations( + java.lang.Iterable values) { + ensureDestinationsIsMutable(); + super.addAll(values, destinations_); + onChanged(); + return this; + } + public Builder clearDestinations() { + destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + void addDestinations(com.google.protobuf.ByteString value) { + ensureDestinationsIsMutable(); + destinations_.add(value); + onChanged(); + } + + // optional bytes message = 4; + private com.google.protobuf.ByteString message_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasMessage() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getMessage() { + return message_; + } + public Builder setMessage(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + message_ = value; + onChanged(); + return this; + } + public Builder clearMessage() { + bitField0_ = (bitField0_ & ~0x00000008); + message_ = getDefaultInstance().getMessage(); + onChanged(); + return this; + } + + // repeated .textsecure.IncomingPushMessageSignal.AttachmentPointer attachments = 5; + private java.util.List attachments_ = + java.util.Collections.emptyList(); + private void ensureAttachmentsIsMutable() { + if (!((bitField0_ & 0x00000010) == 0x00000010)) { + attachments_ = new java.util.ArrayList(attachments_); + bitField0_ |= 0x00000010; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder> attachmentsBuilder_; + + public java.util.List getAttachmentsList() { + if (attachmentsBuilder_ == null) { + return java.util.Collections.unmodifiableList(attachments_); + } else { + return attachmentsBuilder_.getMessageList(); + } + } + public int getAttachmentsCount() { + if (attachmentsBuilder_ == null) { + return attachments_.size(); + } else { + return attachmentsBuilder_.getCount(); + } + } + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer getAttachments(int index) { + if (attachmentsBuilder_ == null) { + return attachments_.get(index); + } else { + return attachmentsBuilder_.getMessage(index); + } + } + public Builder setAttachments( + int index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer value) { + if (attachmentsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAttachmentsIsMutable(); + attachments_.set(index, value); + onChanged(); + } else { + attachmentsBuilder_.setMessage(index, value); + } + return this; + } + public Builder setAttachments( + int index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder builderForValue) { + if (attachmentsBuilder_ == null) { + ensureAttachmentsIsMutable(); + attachments_.set(index, builderForValue.build()); + onChanged(); + } else { + attachmentsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + public Builder addAttachments(org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer value) { + if (attachmentsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAttachmentsIsMutable(); + attachments_.add(value); + onChanged(); + } else { + attachmentsBuilder_.addMessage(value); + } + return this; + } + public Builder addAttachments( + int index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer value) { + if (attachmentsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAttachmentsIsMutable(); + attachments_.add(index, value); + onChanged(); + } else { + attachmentsBuilder_.addMessage(index, value); + } + return this; + } + public Builder addAttachments( + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder builderForValue) { + if (attachmentsBuilder_ == null) { + ensureAttachmentsIsMutable(); + attachments_.add(builderForValue.build()); + onChanged(); + } else { + attachmentsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + public Builder addAttachments( + int index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder builderForValue) { + if (attachmentsBuilder_ == null) { + ensureAttachmentsIsMutable(); + attachments_.add(index, builderForValue.build()); + onChanged(); + } else { + attachmentsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + public Builder addAllAttachments( + java.lang.Iterable values) { + if (attachmentsBuilder_ == null) { + ensureAttachmentsIsMutable(); + super.addAll(values, attachments_); + onChanged(); + } else { + attachmentsBuilder_.addAllMessages(values); + } + return this; + } + public Builder clearAttachments() { + if (attachmentsBuilder_ == null) { + attachments_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000010); + onChanged(); + } else { + attachmentsBuilder_.clear(); + } + return this; + } + public Builder removeAttachments(int index) { + if (attachmentsBuilder_ == null) { + ensureAttachmentsIsMutable(); + attachments_.remove(index); + onChanged(); + } else { + attachmentsBuilder_.remove(index); + } + return this; + } + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder getAttachmentsBuilder( + int index) { + return getAttachmentsFieldBuilder().getBuilder(index); + } + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder getAttachmentsOrBuilder( + int index) { + if (attachmentsBuilder_ == null) { + return attachments_.get(index); } else { + return attachmentsBuilder_.getMessageOrBuilder(index); + } + } + public java.util.List + getAttachmentsOrBuilderList() { + if (attachmentsBuilder_ != null) { + return attachmentsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(attachments_); + } + } + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder addAttachmentsBuilder() { + return getAttachmentsFieldBuilder().addBuilder( + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.getDefaultInstance()); + } + public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder addAttachmentsBuilder( + int index) { + return getAttachmentsFieldBuilder().addBuilder( + index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.getDefaultInstance()); + } + public java.util.List + getAttachmentsBuilderList() { + return getAttachmentsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder> + getAttachmentsFieldBuilder() { + if (attachmentsBuilder_ == null) { + attachmentsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder>( + attachments_, + ((bitField0_ & 0x00000010) == 0x00000010), + getParentForChildren(), + isClean()); + attachments_ = null; + } + return attachmentsBuilder_; + } + + // optional uint64 timestamp = 6; + private long timestamp_ ; + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + public long getTimestamp() { + return timestamp_; + } + public Builder setTimestamp(long value) { + bitField0_ |= 0x00000020; + timestamp_ = value; + onChanged(); + return this; + } + public Builder clearTimestamp() { + bitField0_ = (bitField0_ & ~0x00000020); + timestamp_ = 0L; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.IncomingPushMessageSignal) + } + + static { + defaultInstance = new IncomingPushMessageSignal(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.IncomingPushMessageSignal) + } + + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_IncomingPushMessageSignal_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_IncomingPushMessageSignal_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\037IncomingPushMessageSignal.proto\022\ntexts" + + "ecure\"\370\001\n\031IncomingPushMessageSignal\022\014\n\004t" + + "ype\030\001 \001(\r\022\016\n\006source\030\002 \001(\t\022\024\n\014destination" + + "s\030\003 \003(\t\022\017\n\007message\030\004 \001(\014\022L\n\013attachments\030" + + "\005 \003(\01327.textsecure.IncomingPushMessageSi" + + "gnal.AttachmentPointer\022\021\n\ttimestamp\030\006 \001(" + + "\004\0325\n\021AttachmentPointer\022\023\n\013contentType\030\001 " + + "\001(\t\022\013\n\003key\030\002 \001(\tB7\n\"org.whispersystems.t" + + "extsecure.pushB\021PushMessageProtos" + }; + com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = + new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { + public com.google.protobuf.ExtensionRegistry assignDescriptors( + com.google.protobuf.Descriptors.FileDescriptor root) { + descriptor = root; + internal_static_textsecure_IncomingPushMessageSignal_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_textsecure_IncomingPushMessageSignal_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_IncomingPushMessageSignal_descriptor, + new java.lang.String[] { "Type", "Source", "Destinations", "Message", "Attachments", "Timestamp", }, + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.class, + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.Builder.class); + internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_descriptor = + internal_static_textsecure_IncomingPushMessageSignal_descriptor.getNestedTypes().get(0); + internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_descriptor, + new java.lang.String[] { "ContentType", "Key", }, + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.class, + org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder.class); + return null; + } + }; + com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }, assigner); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index c363dae0f2..acde1f67c1 100644 --- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java +++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java @@ -4,13 +4,10 @@ import android.content.Context; import android.util.Log; import android.util.Pair; -import com.google.protobuf.ByteString; import com.google.thoughtcrimegson.Gson; import org.whispersystems.textsecure.R; import org.whispersystems.textsecure.Release; import org.whispersystems.textsecure.crypto.IdentityKey; -import org.whispersystems.textsecure.crypto.PreKeyPair; -import org.whispersystems.textsecure.crypto.PreKeyPublic; import org.whispersystems.textsecure.directory.DirectoryDescriptor; import org.whispersystems.textsecure.storage.PreKeyRecord; import org.whispersystems.textsecure.util.Base64; @@ -33,6 +30,7 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -79,31 +77,45 @@ public class PushServiceSocket { makeRequest(REGISTER_GCM_PATH, "DELETE", null); } - public void sendMessage(String recipient, String messageText, int type) + public void sendMessage(String recipient, byte[] body, int type) throws IOException { - OutgoingPushMessage message = new OutgoingPushMessage(recipient, messageText, type); - sendMessage(message); + OutgoingPushMessage message = new OutgoingPushMessage(recipient, body, type); + sendMessage(new OutgoingPushMessageList(message)); } - public void sendMessage(List recipients, String messageText, int type) + public void sendMessage(List recipients, List bodies, + List> attachmentsList, int type) throws IOException { - OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, type); - sendMessage(message); + List messages = new LinkedList(); + + Iterator recipientsIterator = recipients.iterator(); + Iterator bodiesIterator = bodies.iterator(); + Iterator> attachmentsIterator = attachmentsList.iterator(); + + while (recipientsIterator.hasNext()) { + String recipient = recipientsIterator.next(); + byte[] body = bodiesIterator.next(); + List attachments = attachmentsIterator.next(); + + OutgoingPushMessage message; + + if (!attachments.isEmpty()) { + List attachmentIds = sendAttachments(attachments); + message = new OutgoingPushMessage(recipient, body, attachmentIds, type); + } else { + message = new OutgoingPushMessage(recipient, body, type); + } + + messages.add(message); + } + + sendMessage(new OutgoingPushMessageList(messages)); } - public void sendMessage(List recipients, String messageText, - List attachments, int type) - throws IOException - { - List attachmentIds = sendAttachments(attachments); - OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, attachmentIds, type); - sendMessage(message); - } - - private void sendMessage(OutgoingPushMessage message) throws IOException { - String responseText = makeRequest(MESSAGE_PATH, "POST", new Gson().toJson(message)); + private void sendMessage(OutgoingPushMessageList messages) throws IOException { + String responseText = makeRequest(MESSAGE_PATH, "POST", new Gson().toJson(messages)); PushMessageResponse response = new Gson().fromJson(responseText, PushMessageResponse.class); if (response.getFailure().size() != 0) diff --git a/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java b/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java index 9920066f52..f8a87d5539 100644 --- a/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java +++ b/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java @@ -34,11 +34,11 @@ public class PushTransportDetails implements TransportDetails { @Override public byte[] getEncodedMessage(byte[] messageWithMac) { - return Base64.encodeBytesWithoutPadding(messageWithMac).getBytes(); + return messageWithMac; } @Override public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException { - return Base64.decodeWithoutPadding(new String(encodedMessageBytes)); + return encodedMessageBytes; } } diff --git a/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java b/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java index f14cd95848..4e29af7b90 100644 --- a/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java +++ b/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java @@ -5,11 +5,12 @@ import android.content.Intent; import android.util.Log; import com.google.android.gcm.GCMBaseIntentService; -import com.google.thoughtcrimegson.Gson; import org.thoughtcrime.securesms.service.RegistrationService; import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.textsecure.crypto.InvalidVersionException; +import org.whispersystems.textsecure.push.IncomingEncryptedPushMessage; import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.util.Util; @@ -48,16 +49,24 @@ public class GcmIntentService extends GCMBaseIntentService { @Override protected void onMessage(Context context, Intent intent) { - String data = intent.getStringExtra("message"); - Log.w("GcmIntentService", "GCM message: " + data); + try { + String data = intent.getStringExtra("message"); + Log.w("GcmIntentService", "GCM message: " + data); - if (Util.isEmpty(data)) - return; + if (Util.isEmpty(data)) + return; - IncomingPushMessage message = new Gson().fromJson(data, IncomingPushMessage.class); + String sessionKey = TextSecurePreferences.getSignalingKey(context); + IncomingEncryptedPushMessage encryptedMessage = new IncomingEncryptedPushMessage(data, sessionKey); + IncomingPushMessage message = encryptedMessage.getIncomingPushMessage(); - if (!message.hasAttachments()) handleIncomingTextMessage(context, message); - else handleIncomingMediaMessage(context, message); + if (!message.hasAttachments()) handleIncomingTextMessage(context, message); + else handleIncomingMediaMessage(context, message); + } catch (IOException e) { + Log.w("GcmIntentService", e); + } catch (InvalidVersionException e) { + Log.w("GcmIntentService", e); + } } @Override diff --git a/src/org/thoughtcrime/securesms/service/SmsReceiver.java b/src/org/thoughtcrime/securesms/service/SmsReceiver.java index 395467fb1f..643b901c4f 100644 --- a/src/org/thoughtcrime/securesms/service/SmsReceiver.java +++ b/src/org/thoughtcrime/securesms/service/SmsReceiver.java @@ -42,6 +42,7 @@ import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.push.OutgoingPushMessage; +import org.whispersystems.textsecure.push.PushMessage; import org.whispersystems.textsecure.storage.InvalidKeyIdException; import java.util.List; @@ -62,11 +63,11 @@ public class SmsReceiver { IncomingTextMessage message = messages.get(0); switch (pushType) { - case OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT: + case PushMessage.TYPE_MESSAGE_CIPHERTEXT: return new IncomingEncryptedMessage(message, message.getMessageBody()); - case OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE: + case PushMessage.TYPE_MESSAGE_PREKEY_BUNDLE: return new IncomingPreKeyBundleMessage(message, message.getMessageBody()); - case OutgoingPushMessage.TYPE_MESSAGE_KEY_EXCHANGE: + case PushMessage.TYPE_MESSAGE_KEY_EXCHANGE: return new IncomingKeyExchangeMessage(message, message.getMessageBody()); } diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index 67fe597bb3..368202e2e7 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -55,7 +55,7 @@ public class PushTransport extends BaseTransport { String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(), localNumber); - Pair typeAndCiphertext = getEncryptedMessage(socket, recipient, recipientCanonicalNumber, plaintext); + Pair typeAndCiphertext = getEncryptedMessage(socket, recipient, recipientCanonicalNumber, plaintext); socket.sendMessage(recipientCanonicalNumber, typeAndCiphertext.second, typeAndCiphertext.first); @@ -71,11 +71,19 @@ public class PushTransport extends BaseTransport { String localNumber = TextSecurePreferences.getLocalNumber(context); String password = TextSecurePreferences.getPushServerPassword(context); PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); - String messageText = PartParser.getMessageText(message.getBody()); + byte[] messageText = PartParser.getMessageText(message.getBody()).getBytes(); List attachments = getAttachmentsFromBody(message.getBody()); - if (attachments.isEmpty()) socket.sendMessage(destinations, messageText, OutgoingPushMessage.TYPE_MESSAGE_PLAINTEXT); - else socket.sendMessage(destinations, messageText, attachments, OutgoingPushMessage.TYPE_MESSAGE_PLAINTEXT); + List messagesList = new LinkedList(); + List> attachmentsList = new LinkedList>(); + + for (String recipient : destinations) { + messagesList.add(messageText); + attachmentsList.add(attachments); + } + + socket.sendMessage(destinations, messagesList, attachmentsList, + OutgoingPushMessage.TYPE_MESSAGE_PLAINTEXT); } catch (RateLimitException e) { Log.w("PushTransport", e); throw new IOException("Rate limit exceeded."); @@ -99,26 +107,26 @@ public class PushTransport extends BaseTransport { return attachments; } - private Pair getEncryptedMessage(PushServiceSocket socket, Recipient recipient, + private Pair getEncryptedMessage(PushServiceSocket socket, Recipient recipient, String canonicalRecipientNumber, String plaintext) throws IOException { if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) { Log.w("PushTransport", "Sending standard ciphertext message..."); - String ciphertext = getEncryptedMessageForExistingSession(recipient, plaintext); - return new Pair(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, ciphertext); + byte[] ciphertext = getEncryptedMessageForExistingSession(recipient, plaintext); + return new Pair(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, ciphertext); } else if (KeyUtil.isSessionFor(context, recipient)) { Log.w("PushTransport", "Sending prekeybundle ciphertext message for existing session..."); - String ciphertext = getEncryptedPrekeyBundleMessageForExistingSession(recipient, plaintext); - return new Pair(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); + byte[] ciphertext = getEncryptedPrekeyBundleMessageForExistingSession(recipient, plaintext); + return new Pair(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); } else { Log.w("PushTransport", "Sending prekeybundle ciphertext message for new session..."); - String ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, recipient, canonicalRecipientNumber, plaintext); - return new Pair(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); + byte[] ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, recipient, canonicalRecipientNumber, plaintext); + return new Pair(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); } } - private String getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient, + private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient, String plaintext) { IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); @@ -131,7 +139,7 @@ public class PushTransport extends BaseTransport { return preKeyBundleMessage.serialize(); } - private String getEncryptedPrekeyBundleMessageForNewSession(PushServiceSocket socket, + private byte[] getEncryptedPrekeyBundleMessageForNewSession(PushServiceSocket socket, Recipient recipient, String canonicalRecipientNumber, String plaintext) @@ -151,14 +159,14 @@ public class PushTransport extends BaseTransport { return preKeyBundleMessage.serialize(); } - private String getEncryptedMessageForExistingSession(Recipient recipient, String plaintext) + private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext) throws IOException { IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair, new PushTransportDetails()); - return new String(messageCipher.encrypt(recipient, plaintext.getBytes())); + return messageCipher.encrypt(recipient, plaintext.getBytes()); } } diff --git a/src/org/thoughtcrime/securesms/transport/SmsTransport.java b/src/org/thoughtcrime/securesms/transport/SmsTransport.java index 2ee86ead58..39e6c46f53 100644 --- a/src/org/thoughtcrime/securesms/transport/SmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/SmsTransport.java @@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; +import org.whispersystems.textsecure.util.Base64; import java.util.ArrayList; @@ -160,7 +161,7 @@ public class SmsTransport extends BaseTransport { byte[] bundledMessage = messageCipher.encrypt(recipient, body.getBytes()); PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey.getPublicKey(), bundledMessage); - return new OutgoingPrekeyBundleMessage(message, preKeyBundleMessage.serialize()); + return new OutgoingPrekeyBundleMessage(message, Base64.encodeBytesWithoutPadding(preKeyBundleMessage.serialize())); } } } diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 856f658691..009d36f1e1 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -59,6 +59,10 @@ public class TextSecurePreferences { setStringPreference(context, SIGNALING_KEY_PREF, signalingKey); } + public static String getSignalingKey(Context context) { + return getStringPreference(context, SIGNALING_KEY_PREF, null); + } + public static boolean isEnterImeKeyEnabled(Context context) { return getBooleanPreference(context, ENTER_PRESENT_PREF, false); }