From 0dd36c64a45c4463b2f1145d5d6726df7eff5074 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Sun, 8 Sep 2013 18:19:05 -0700 Subject: [PATCH] Basic support for encrypted push-based attachments. 1) Move the attachment structures into the encrypted message body. 2) Encrypt attachments with symmetric keys transmitted in the encryptd attachment pointer structure. 3) Correctly handle asynchronous decryption and categorization of encrypted push messages. TODO: Correct notification process and network/interruption retries. --- .../protobuf/IncomingPushMessageSignal.proto | 15 +- .../textsecure/crypto/AttachmentCipher.java | 156 ++ .../crypto/AttachmentCipherInputStream.java | 178 ++ .../textsecure/push/IncomingPushMessage.java | 73 +- .../textsecure/push/OutgoingPushMessage.java | 41 +- .../push/PushAttachmentPointer.java | 22 +- .../textsecure/push/PushMessageProtos.java | 1742 ++++++++++------- .../textsecure/push/PushServiceSocket.java | 70 +- .../whispersystems/textsecure/util/Util.java | 39 + res/drawable-hdpi/stat_sys_download_anim0.png | Bin 0 -> 501 bytes res/drawable-hdpi/stat_sys_download_anim1.png | Bin 0 -> 492 bytes res/drawable-hdpi/stat_sys_download_anim2.png | Bin 0 -> 511 bytes res/drawable-hdpi/stat_sys_download_anim3.png | Bin 0 -> 502 bytes res/drawable-hdpi/stat_sys_download_anim4.png | Bin 0 -> 473 bytes res/drawable-hdpi/stat_sys_download_anim5.png | Bin 0 -> 477 bytes res/drawable-mdpi/stat_sys_download_anim0.png | Bin 0 -> 382 bytes res/drawable-mdpi/stat_sys_download_anim1.png | Bin 0 -> 383 bytes res/drawable-mdpi/stat_sys_download_anim2.png | Bin 0 -> 389 bytes res/drawable-mdpi/stat_sys_download_anim3.png | Bin 0 -> 384 bytes res/drawable-mdpi/stat_sys_download_anim4.png | Bin 0 -> 365 bytes res/drawable-mdpi/stat_sys_download_anim5.png | Bin 0 -> 377 bytes .../stat_sys_download_anim0.png | Bin 0 -> 594 bytes .../stat_sys_download_anim1.png | Bin 0 -> 626 bytes .../stat_sys_download_anim2.png | Bin 0 -> 625 bytes .../stat_sys_download_anim3.png | Bin 0 -> 638 bytes .../stat_sys_download_anim4.png | Bin 0 -> 599 bytes .../stat_sys_download_anim5.png | Bin 0 -> 592 bytes res/drawable/stat_sys_download.xml | 30 + .../securesms/crypto/DecryptingQueue.java | 100 +- .../securesms/database/DatabaseFactory.java | 16 +- .../securesms/database/MmsDatabase.java | 31 +- .../securesms/database/PartDatabase.java | 178 +- .../securesms/database/PushDatabase.java | 42 + .../securesms/gcm/GcmIntentService.java | 29 +- .../securesms/mms/AttachmentManager.java | 6 +- .../securesms/mms/AudioSlide.java | 5 +- .../securesms/mms/ImageSlide.java | 49 +- .../securesms/mms/IncomingMediaMessage.java | 45 +- src/org/thoughtcrime/securesms/mms/Slide.java | 5 +- .../securesms/mms/VideoSlide.java | 5 +- .../securesms/service/MmsReceiver.java | 32 +- .../securesms/service/PushDownloader.java | 107 + .../securesms/service/PushReceiver.java | 211 ++ .../securesms/service/SendReceiveService.java | 43 +- .../securesms/transport/PushTransport.java | 95 +- .../com/google/android/mms/pdu/PduBody.java | 10 + .../com/google/android/mms/pdu/PduPart.java | 9 + 47 files changed, 2381 insertions(+), 1003 deletions(-) create mode 100644 library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java create mode 100644 library/src/org/whispersystems/textsecure/crypto/AttachmentCipherInputStream.java create mode 100644 res/drawable-hdpi/stat_sys_download_anim0.png create mode 100644 res/drawable-hdpi/stat_sys_download_anim1.png create mode 100644 res/drawable-hdpi/stat_sys_download_anim2.png create mode 100644 res/drawable-hdpi/stat_sys_download_anim3.png create mode 100644 res/drawable-hdpi/stat_sys_download_anim4.png create mode 100644 res/drawable-hdpi/stat_sys_download_anim5.png create mode 100644 res/drawable-mdpi/stat_sys_download_anim0.png create mode 100644 res/drawable-mdpi/stat_sys_download_anim1.png create mode 100644 res/drawable-mdpi/stat_sys_download_anim2.png create mode 100644 res/drawable-mdpi/stat_sys_download_anim3.png create mode 100644 res/drawable-mdpi/stat_sys_download_anim4.png create mode 100644 res/drawable-mdpi/stat_sys_download_anim5.png create mode 100644 res/drawable-xhdpi/stat_sys_download_anim0.png create mode 100644 res/drawable-xhdpi/stat_sys_download_anim1.png create mode 100644 res/drawable-xhdpi/stat_sys_download_anim2.png create mode 100644 res/drawable-xhdpi/stat_sys_download_anim3.png create mode 100644 res/drawable-xhdpi/stat_sys_download_anim4.png create mode 100644 res/drawable-xhdpi/stat_sys_download_anim5.png create mode 100644 res/drawable/stat_sys_download.xml create mode 100644 src/org/thoughtcrime/securesms/database/PushDatabase.java create mode 100644 src/org/thoughtcrime/securesms/service/PushDownloader.java create mode 100644 src/org/thoughtcrime/securesms/service/PushReceiver.java diff --git a/library/protobuf/IncomingPushMessageSignal.proto b/library/protobuf/IncomingPushMessageSignal.proto index 1e82872705..90465ae279 100644 --- a/library/protobuf/IncomingPushMessageSignal.proto +++ b/library/protobuf/IncomingPushMessageSignal.proto @@ -7,13 +7,18 @@ message IncomingPushMessageSignal { optional uint32 type = 1; optional string source = 2; repeated string destinations = 3; - optional bytes message = 4; + optional uint64 timestamp = 4; + optional bytes message = 5; // Contains an encrypted IncomingPushMessageContent +} + +message PushMessageContent { + optional string body = 1; message AttachmentPointer { - optional string contentType = 1; - optional string key = 2; + optional fixed64 id = 1; + optional string contentType = 2; + optional bytes key = 3; } - repeated AttachmentPointer attachments = 5; - optional uint64 timestamp = 6; + repeated AttachmentPointer attachments = 2; } \ No newline at end of file diff --git a/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java new file mode 100644 index 0000000000..4496f8653a --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java @@ -0,0 +1,156 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.whispersystems.textsecure.crypto; + +import android.util.Log; + +import org.whispersystems.textsecure.util.Hex; +import org.whispersystems.textsecure.util.Util; + +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.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +/** + * Encrypts push attachments. + * + * @author Moxie Marlinspike + */ +public class AttachmentCipher { + + static final int CIPHER_KEY_SIZE = 32; + static final int MAC_KEY_SIZE = 20; + + private final SecretKeySpec cipherKey; + private final SecretKeySpec macKey; + private final Cipher cipher; + private final Mac mac; + + public AttachmentCipher() { + this.cipherKey = initializeRandomCipherKey(); + this.macKey = initializeRandomMacKey(); + this.cipher = initializeCipher(); + this.mac = initializeMac(); + } + + public AttachmentCipher(byte[] combinedKeyMaterial) { + byte[][] parts = Util.split(combinedKeyMaterial, CIPHER_KEY_SIZE, MAC_KEY_SIZE); + this.cipherKey = new SecretKeySpec(parts[0], "AES"); + this.macKey = new SecretKeySpec(parts[1], "HmacSHA1"); + this.cipher = initializeCipher(); + this.mac = initializeMac(); + } + + public byte[] getCombinedKeyMaterial() { + return Util.combine(this.cipherKey.getEncoded(), this.macKey.getEncoded()); + } + + public byte[] encrypt(byte[] plaintext) { + try { + this.cipher.init(Cipher.ENCRYPT_MODE, this.cipherKey); + this.mac.init(this.macKey); + + byte[] ciphertext = this.cipher.doFinal(plaintext); + byte[] iv = this.cipher.getIV(); + byte[] mac = this.mac.doFinal(Util.combine(iv, ciphertext)); + + return Util.combine(iv, ciphertext, mac); + } catch (IllegalBlockSizeException e) { + throw new AssertionError(e); + } catch (BadPaddingException e) { + throw new AssertionError(e); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } + } + + public byte[] decrypt(byte[] ciphertext) + throws InvalidMacException, InvalidMessageException + { + try { + if (ciphertext.length <= cipher.getBlockSize() + mac.getMacLength()) { + throw new InvalidMessageException("Message too short!"); + } + + byte[][] ciphertextParts = Util.split(ciphertext, + this.cipher.getBlockSize(), + ciphertext.length - this.cipher.getBlockSize() - this.mac.getMacLength(), + this.mac.getMacLength()); + + this.mac.update(ciphertext, 0, ciphertext.length - mac.getMacLength()); + byte[] ourMac = this.mac.doFinal(); + + if (!Arrays.equals(ourMac, ciphertextParts[2])) { + throw new InvalidMacException("Mac doesn't match!"); + } + + this.cipher.init(Cipher.DECRYPT_MODE, this.cipherKey, + new IvParameterSpec(ciphertextParts[0])); + + return cipher.doFinal(ciphertextParts[1]); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } catch (InvalidAlgorithmParameterException e) { + throw new AssertionError(e); + } catch (IllegalBlockSizeException e) { + throw new AssertionError(e); + } catch (BadPaddingException e) { + throw new InvalidMessageException(e); + } + } + + private Mac initializeMac() { + try { + Mac mac = Mac.getInstance("HmacSHA1"); + return mac; + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + + private Cipher initializeCipher() { + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + return cipher; + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } catch (NoSuchPaddingException e) { + throw new AssertionError(e); + } + } + + private SecretKeySpec initializeRandomCipherKey() { + byte[] key = new byte[CIPHER_KEY_SIZE]; + Util.getSecureRandom().nextBytes(key); + return new SecretKeySpec(key, "AES"); + } + + private SecretKeySpec initializeRandomMacKey() { + byte[] key = new byte[MAC_KEY_SIZE]; + Util.getSecureRandom().nextBytes(key); + return new SecretKeySpec(key, "HmacSHA1"); + } + +} diff --git a/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherInputStream.java b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherInputStream.java new file mode 100644 index 0000000000..753e4c80fd --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherInputStream.java @@ -0,0 +1,178 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.whispersystems.textsecure.crypto; + +import android.util.Log; + +import org.whispersystems.textsecure.util.Hex; +import org.whispersystems.textsecure.util.Util; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +/** + * Class for streaming an encrypted push attachment off disk. + * + * @author Moxie Marlinspike + */ + +public class AttachmentCipherInputStream extends FileInputStream { + + private static final int BLOCK_SIZE = 16; + + private Cipher cipher; + private boolean done; + private long totalDataSize; + private long totalRead; + + public AttachmentCipherInputStream(File file, byte[] combinedKeyMaterial) + throws IOException, InvalidMessageException + { + super(file); + + try { + byte[][] parts = Util.split(combinedKeyMaterial, + AttachmentCipher.CIPHER_KEY_SIZE, + AttachmentCipher.MAC_KEY_SIZE); + + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(new SecretKeySpec(parts[1], "HmacSHA1")); + + if (file.length() <= BLOCK_SIZE + mac.getMacLength()) { + throw new InvalidMessageException("Message shorter than crypto overhead!"); + } + + verifyMac(file, mac); + + byte[] iv = new byte[BLOCK_SIZE]; + readFully(iv); + + this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + this.cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(parts[0], "AES"), new IvParameterSpec(iv)); + + this.done = false; + this.totalRead = 0; + this.totalDataSize = file.length() - cipher.getBlockSize() - mac.getMacLength(); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } catch (InvalidMacException e) { + throw new InvalidMessageException(e); + } catch (NoSuchPaddingException e) { + throw new AssertionError(e); + } catch (InvalidAlgorithmParameterException e) { + throw new AssertionError(e); + } + } + + @Override + public int read(byte[] buffer) throws IOException { + return read(buffer, 0, buffer.length); + } + + @Override + public int read(byte[] buffer, int offset, int length) throws IOException { + if (totalRead != totalDataSize) return readIncremental(buffer, offset, length); + else if (!done) return readFinal(buffer, offset, length); + else return -1; + } + + private int readFinal(byte[] buffer, int offset, int length) throws IOException { + try { + int flourish = cipher.doFinal(buffer, offset); + + done = true; + return flourish; + } catch (IllegalBlockSizeException e) { + Log.w("EncryptingPartInputStream", e); + throw new IOException("Illegal block size exception!"); + } catch (ShortBufferException e) { + Log.w("EncryptingPartInputStream", e); + throw new IOException("Short buffer exception!"); + } catch (BadPaddingException e) { + Log.w("EncryptingPartInputStream", e); + throw new IOException("Bad padding exception!"); + } + } + + private int readIncremental(byte[] buffer, int offset, int length) throws IOException { + if (length + totalRead > totalDataSize) + length = (int)(totalDataSize - totalRead); + + byte[] internalBuffer = new byte[length]; + int read = super.read(internalBuffer, 0, internalBuffer.length <= cipher.getBlockSize() ? internalBuffer.length : internalBuffer.length - cipher.getBlockSize()); + totalRead += read; + + try { + return cipher.update(internalBuffer, 0, read, buffer, offset); + } catch (ShortBufferException e) { + throw new AssertionError(e); + } + } + + private void verifyMac(File file, Mac mac) throws FileNotFoundException, InvalidMacException { + try { + FileInputStream fin = new FileInputStream(file); + int remainingData = (int) file.length() - mac.getMacLength(); + byte[] buffer = new byte[4096]; + + while (remainingData > 0) { + int read = fin.read(buffer, 0, Math.min(buffer.length, remainingData)); + mac.update(buffer, 0, read); + remainingData -= read; + } + + byte[] ourMac = mac.doFinal(); + byte[] theirMac = new byte[mac.getMacLength()]; + Util.readFully(fin, theirMac); + + if (!Arrays.equals(ourMac, theirMac)) { + throw new InvalidMacException("MAC doesn't match!"); + } + } catch (IOException e1) { + throw new InvalidMacException(e1); + } + } + + private void readFully(byte[] buffer) throws IOException { + int offset = 0; + + for (;;) { + int read = super.read(buffer, offset, buffer.length - offset); + + if (read + offset < buffer.length) offset += read; + else return; + } + } + + +} diff --git a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java index 57b2fd1427..8de52cd294 100644 --- a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java +++ b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java @@ -1,12 +1,26 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ 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 org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal; + import java.util.LinkedList; import java.util.List; @@ -24,12 +38,20 @@ public class IncomingPushMessage implements PushMessage, Parcelable { } }; - private int type; - private String source; - private List destinations; - private byte[] message; - private List attachments; - private long timestamp; + private int type; + private String source; + private List destinations; + private byte[] message; + private long timestamp; + + private IncomingPushMessage(IncomingPushMessage message, byte[] body) { + this.type = message.type; + this.source = message.source; + this.destinations = new LinkedList(); + this.destinations.addAll(message.destinations); + this.message = body; + this.timestamp = message.timestamp; + } public IncomingPushMessage(IncomingPushMessageSignal signal) { this.type = signal.getType(); @@ -37,25 +59,15 @@ public class IncomingPushMessage implements PushMessage, Parcelable { 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) { this.destinations = new LinkedList(); - this.attachments = new LinkedList(); - this.type = in.readInt(); this.source = in.readString(); in.readStringList(destinations); this.message = new byte[in.readInt()]; in.readByteArray(this.message); - in.readList(attachments, PushAttachmentPointer.class.getClassLoader()); this.timestamp = in.readLong(); } @@ -67,10 +79,6 @@ public class IncomingPushMessage implements PushMessage, Parcelable { return source; } - public List getAttachments() { - return attachments; - } - public byte[] getBody() { return message; } @@ -79,10 +87,6 @@ public class IncomingPushMessage implements PushMessage, Parcelable { return destinations; } - public boolean hasAttachments() { - return getAttachments() != null && !getAttachments().isEmpty(); - } - @Override public int describeContents() { return 0; @@ -95,11 +99,22 @@ public class IncomingPushMessage implements PushMessage, Parcelable { dest.writeStringList(destinations); dest.writeInt(message.length); dest.writeByteArray(message); - dest.writeList(attachments); dest.writeLong(timestamp); } + public IncomingPushMessage withBody(byte[] body) { + return new IncomingPushMessage(this, body); + } + public int getType() { return type; } + + public boolean isSecureMessage() { + return getType() == PushMessage.TYPE_MESSAGE_CIPHERTEXT; + } + + public boolean isPreKeyBundle() { + return getType() == PushMessage.TYPE_MESSAGE_PREKEY_BUNDLE; + } } diff --git a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java index 487a0da07d..1cfe139895 100644 --- a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java +++ b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java @@ -1,34 +1,35 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.whispersystems.textsecure.push; import org.whispersystems.textsecure.util.Base64; -import java.util.LinkedList; -import java.util.List; - public class OutgoingPushMessage implements PushMessage { - private int type; - private String destination; - private String body; - private List attachments; + private int type; + private String destination; + private String body; 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(String destination, byte[] body, - List attachments, - int type) - { - this.destination = destination; - this.body = Base64.encodeBytes(body); - this.attachments = attachments; - this.type = type; - } - public String getDestination() { return destination; } @@ -37,10 +38,6 @@ public class OutgoingPushMessage implements PushMessage { return body; } - public List getAttachments() { - return attachments; - } - public int getType() { return type; } diff --git a/library/src/org/whispersystems/textsecure/push/PushAttachmentPointer.java b/library/src/org/whispersystems/textsecure/push/PushAttachmentPointer.java index 8a3a81286e..30e893861b 100644 --- a/library/src/org/whispersystems/textsecure/push/PushAttachmentPointer.java +++ b/library/src/org/whispersystems/textsecure/push/PushAttachmentPointer.java @@ -18,23 +18,33 @@ public class PushAttachmentPointer implements Parcelable { }; private final String contentType; - private final String key; + private final long id; + private final byte[] key; - public PushAttachmentPointer(String contentType, String key) { + public PushAttachmentPointer(String contentType, long id, byte[] key) { this.contentType = contentType; + this.id = id; this.key = key; } public PushAttachmentPointer(Parcel in) { this.contentType = in.readString(); - this.key = in.readString(); + this.id = in.readLong(); + + int keyLength = in.readInt(); + this.key = new byte[keyLength]; + in.readByteArray(this.key); } public String getContentType() { return contentType; } - public String getKey() { + public long getId() { + return id; + } + + public byte[] getKey() { return key; } @@ -46,6 +56,8 @@ public class PushAttachmentPointer implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(contentType); - dest.writeString(key); + dest.writeLong(id); + dest.writeInt(this.key.length); + dest.writeByteArray(this.key); } } diff --git a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java index ff79ffa74e..ca0179643d 100644 --- a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java +++ b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java @@ -24,23 +24,13 @@ public final class PushMessageProtos { 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; + // optional uint64 timestamp = 4; boolean hasTimestamp(); long getTimestamp(); + + // optional bytes message = 5; + boolean hasMessage(); + com.google.protobuf.ByteString getMessage(); } public static final class IncomingPushMessageSignal extends com.google.protobuf.GeneratedMessage @@ -70,475 +60,6 @@ public final class PushMessageProtos { 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; @@ -596,54 +117,32 @@ public final class PushMessageProtos { 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; + // optional uint64 timestamp = 4; + public static final int TIMESTAMP_FIELD_NUMBER = 4; private long timestamp_; public boolean hasTimestamp() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000004) == 0x00000004); } public long getTimestamp() { return timestamp_; } + // optional bytes message = 5; + public static final int MESSAGE_FIELD_NUMBER = 5; + private com.google.protobuf.ByteString message_; + public boolean hasMessage() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getMessage() { + return message_; + } + 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; + message_ = com.google.protobuf.ByteString.EMPTY; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -667,13 +166,10 @@ public final class PushMessageProtos { 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)); + output.writeUInt64(4, timestamp_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeUInt64(6, timestamp_); + output.writeBytes(5, message_); } getUnknownFields().writeTo(output); } @@ -703,15 +199,11 @@ public final class PushMessageProtos { } 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)); + .computeUInt64Size(4, timestamp_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream - .computeUInt64Size(6, timestamp_); + .computeBytesSize(5, message_); } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; @@ -829,7 +321,6 @@ public final class PushMessageProtos { } private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getAttachmentsFieldBuilder(); } } private static Builder create() { @@ -844,16 +335,10 @@ public final class PushMessageProtos { 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); + bitField0_ = (bitField0_ & ~0x00000008); + message_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000010); return this; } @@ -909,20 +394,11 @@ public final class PushMessageProtos { 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)) { + result.timestamp_ = timestamp_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { to_bitField0_ |= 0x00000008; } - result.timestamp_ = timestamp_; + result.message_ = message_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -955,38 +431,12 @@ public final class PushMessageProtos { } 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()); } + if (other.hasMessage()) { + setMessage(other.getMessage()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -1033,20 +483,14 @@ public final class PushMessageProtos { destinations_.add(input.readBytes()); break; } - case 34: { + case 32: { bitField0_ |= 0x00000008; - message_ = input.readBytes(); + timestamp_ = input.readUInt64(); 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(); + bitField0_ |= 0x00000010; + message_ = input.readBytes(); break; } } @@ -1168,10 +612,31 @@ public final class PushMessageProtos { onChanged(); } - // optional bytes message = 4; + // optional uint64 timestamp = 4; + private long timestamp_ ; + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public long getTimestamp() { + return timestamp_; + } + public Builder setTimestamp(long value) { + bitField0_ |= 0x00000008; + timestamp_ = value; + onChanged(); + return this; + } + public Builder clearTimestamp() { + bitField0_ = (bitField0_ & ~0x00000008); + timestamp_ = 0L; + onChanged(); + return this; + } + + // optional bytes message = 5; private com.google.protobuf.ByteString message_ = com.google.protobuf.ByteString.EMPTY; public boolean hasMessage() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } public com.google.protobuf.ByteString getMessage() { return message_; @@ -1180,32 +645,986 @@ public final class PushMessageProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; message_ = value; onChanged(); return this; } public Builder clearMessage() { - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); message_ = getDefaultInstance().getMessage(); onChanged(); return this; } - // repeated .textsecure.IncomingPushMessageSignal.AttachmentPointer attachments = 5; - private java.util.List attachments_ = + // @@protoc_insertion_point(builder_scope:textsecure.IncomingPushMessageSignal) + } + + static { + defaultInstance = new IncomingPushMessageSignal(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.IncomingPushMessageSignal) + } + + public interface PushMessageContentOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional string body = 1; + boolean hasBody(); + String getBody(); + + // repeated .textsecure.PushMessageContent.AttachmentPointer attachments = 2; + java.util.List + getAttachmentsList(); + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer getAttachments(int index); + int getAttachmentsCount(); + java.util.List + getAttachmentsOrBuilderList(); + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder getAttachmentsOrBuilder( + int index); + } + public static final class PushMessageContent extends + com.google.protobuf.GeneratedMessage + implements PushMessageContentOrBuilder { + // Use PushMessageContent.newBuilder() to construct. + private PushMessageContent(Builder builder) { + super(builder); + } + private PushMessageContent(boolean noInit) {} + + private static final PushMessageContent defaultInstance; + public static PushMessageContent getDefaultInstance() { + return defaultInstance; + } + + public PushMessageContent getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_fieldAccessorTable; + } + + public interface AttachmentPointerOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional fixed64 id = 1; + boolean hasId(); + long getId(); + + // optional string contentType = 2; + boolean hasContentType(); + String getContentType(); + + // optional bytes key = 3; + boolean hasKey(); + com.google.protobuf.ByteString 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_PushMessageContent_AttachmentPointer_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_AttachmentPointer_fieldAccessorTable; + } + + private int bitField0_; + // optional fixed64 id = 1; + public static final int ID_FIELD_NUMBER = 1; + private long id_; + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public long getId() { + return id_; + } + + // optional string contentType = 2; + public static final int CONTENTTYPE_FIELD_NUMBER = 2; + private java.lang.Object contentType_; + public boolean hasContentType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + 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 bytes key = 3; + public static final int KEY_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString key_; + public boolean hasKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getKey() { + return key_; + } + + private void initFields() { + id_ = 0L; + contentType_ = ""; + key_ = com.google.protobuf.ByteString.EMPTY; + } + 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.writeFixed64(1, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getContentTypeBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, key_); + } + 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 + .computeFixed64Size(1, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getContentTypeBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, key_); + } + 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.PushMessageContent.AttachmentPointer parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.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.PushMessageContent.AttachmentPointer parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.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.PushMessageContent.AttachmentPointer parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.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.PushMessageContent.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.PushMessageContent.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.PushMessageContent.AttachmentPointer parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.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.PushMessageContent.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.PushMessageContent.AttachmentPointerOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_AttachmentPointer_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.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(); + id_ = 0L; + bitField0_ = (bitField0_ & ~0x00000001); + contentType_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + key_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDescriptor(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer getDefaultInstanceForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer build() { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer buildPartial() { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer result = new org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.id_ = id_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.contentType_ = contentType_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + 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.PushMessageContent.AttachmentPointer) { + return mergeFrom((org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer other) { + if (other == org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance()) return this; + if (other.hasId()) { + setId(other.getId()); + } + 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 9: { + bitField0_ |= 0x00000001; + id_ = input.readFixed64(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + contentType_ = input.readBytes(); + break; + } + case 26: { + bitField0_ |= 0x00000004; + key_ = input.readBytes(); + break; + } + } + } + } + + private int bitField0_; + + // optional fixed64 id = 1; + private long id_ ; + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public long getId() { + return id_; + } + public Builder setId(long value) { + bitField0_ |= 0x00000001; + id_ = value; + onChanged(); + return this; + } + public Builder clearId() { + bitField0_ = (bitField0_ & ~0x00000001); + id_ = 0L; + onChanged(); + return this; + } + + // optional string contentType = 2; + private java.lang.Object contentType_ = ""; + public boolean hasContentType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + 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_ |= 0x00000002; + contentType_ = value; + onChanged(); + return this; + } + public Builder clearContentType() { + bitField0_ = (bitField0_ & ~0x00000002); + contentType_ = getDefaultInstance().getContentType(); + onChanged(); + return this; + } + void setContentType(com.google.protobuf.ByteString value) { + bitField0_ |= 0x00000002; + contentType_ = value; + onChanged(); + } + + // optional bytes key = 3; + private com.google.protobuf.ByteString key_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getKey() { + return key_; + } + public Builder setKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + key_ = value; + onChanged(); + return this; + } + public Builder clearKey() { + bitField0_ = (bitField0_ & ~0x00000004); + key_ = getDefaultInstance().getKey(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.PushMessageContent.AttachmentPointer) + } + + static { + defaultInstance = new AttachmentPointer(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.PushMessageContent.AttachmentPointer) + } + + private int bitField0_; + // optional string body = 1; + public static final int BODY_FIELD_NUMBER = 1; + private java.lang.Object body_; + public boolean hasBody() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public String getBody() { + java.lang.Object ref = body_; + 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)) { + body_ = s; + } + return s; + } + } + private com.google.protobuf.ByteString getBodyBytes() { + java.lang.Object ref = body_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + body_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // repeated .textsecure.PushMessageContent.AttachmentPointer attachments = 2; + public static final int ATTACHMENTS_FIELD_NUMBER = 2; + 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.PushMessageContent.AttachmentPointer getAttachments(int index) { + return attachments_.get(index); + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder getAttachmentsOrBuilder( + int index) { + return attachments_.get(index); + } + + private void initFields() { + body_ = ""; + attachments_ = java.util.Collections.emptyList(); + } + 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, getBodyBytes()); + } + for (int i = 0; i < attachments_.size(); i++) { + output.writeMessage(2, attachments_.get(i)); + } + 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, getBodyBytes()); + } + for (int i = 0; i < attachments_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, attachments_.get(i)); + } + 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.PushMessageContent parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent 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.PushMessageContent parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent 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.PushMessageContent parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent 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.PushMessageContent 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.PushMessageContent 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.PushMessageContent parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent 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.PushMessageContent 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.PushMessageContentOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.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(); + body_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + if (attachmentsBuilder_ == null) { + attachments_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + } else { + attachmentsBuilder_.clear(); + } + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.getDescriptor(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent getDefaultInstanceForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.getDefaultInstance(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent build() { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent buildPartial() { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent result = new org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.body_ = body_; + if (attachmentsBuilder_ == null) { + if (((bitField0_ & 0x00000002) == 0x00000002)) { + attachments_ = java.util.Collections.unmodifiableList(attachments_); + bitField0_ = (bitField0_ & ~0x00000002); + } + result.attachments_ = attachments_; + } else { + result.attachments_ = attachmentsBuilder_.build(); + } + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent) { + return mergeFrom((org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent other) { + if (other == org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.getDefaultInstance()) return this; + if (other.hasBody()) { + setBody(other.getBody()); + } + if (attachmentsBuilder_ == null) { + if (!other.attachments_.isEmpty()) { + if (attachments_.isEmpty()) { + attachments_ = other.attachments_; + bitField0_ = (bitField0_ & ~0x00000002); + } else { + ensureAttachmentsIsMutable(); + attachments_.addAll(other.attachments_); + } + onChanged(); + } + } else { + if (!other.attachments_.isEmpty()) { + if (attachmentsBuilder_.isEmpty()) { + attachmentsBuilder_.dispose(); + attachmentsBuilder_ = null; + attachments_ = other.attachments_; + bitField0_ = (bitField0_ & ~0x00000002); + attachmentsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getAttachmentsFieldBuilder() : null; + } else { + attachmentsBuilder_.addAllMessages(other.attachments_); + } + } + } + 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; + body_ = input.readBytes(); + break; + } + case 18: { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder subBuilder = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.newBuilder(); + input.readMessage(subBuilder, extensionRegistry); + addAttachments(subBuilder.buildPartial()); + break; + } + } + } + } + + private int bitField0_; + + // optional string body = 1; + private java.lang.Object body_ = ""; + public boolean hasBody() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public String getBody() { + java.lang.Object ref = body_; + if (!(ref instanceof String)) { + String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + body_ = s; + return s; + } else { + return (String) ref; + } + } + public Builder setBody(String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + body_ = value; + onChanged(); + return this; + } + public Builder clearBody() { + bitField0_ = (bitField0_ & ~0x00000001); + body_ = getDefaultInstance().getBody(); + onChanged(); + return this; + } + void setBody(com.google.protobuf.ByteString value) { + bitField0_ |= 0x00000001; + body_ = value; + onChanged(); + } + + // repeated .textsecure.PushMessageContent.AttachmentPointer attachments = 2; + private java.util.List attachments_ = java.util.Collections.emptyList(); private void ensureAttachmentsIsMutable() { - if (!((bitField0_ & 0x00000010) == 0x00000010)) { - attachments_ = new java.util.ArrayList(attachments_); - bitField0_ |= 0x00000010; + if (!((bitField0_ & 0x00000002) == 0x00000002)) { + attachments_ = new java.util.ArrayList(attachments_); + bitField0_ |= 0x00000002; } } 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_; + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder> attachmentsBuilder_; - public java.util.List getAttachmentsList() { + public java.util.List getAttachmentsList() { if (attachmentsBuilder_ == null) { return java.util.Collections.unmodifiableList(attachments_); } else { @@ -1219,7 +1638,7 @@ public final class PushMessageProtos { return attachmentsBuilder_.getCount(); } } - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer getAttachments(int index) { + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer getAttachments(int index) { if (attachmentsBuilder_ == null) { return attachments_.get(index); } else { @@ -1227,7 +1646,7 @@ public final class PushMessageProtos { } } public Builder setAttachments( - int index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer value) { + int index, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer value) { if (attachmentsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -1241,7 +1660,7 @@ public final class PushMessageProtos { return this; } public Builder setAttachments( - int index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder builderForValue) { + int index, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder builderForValue) { if (attachmentsBuilder_ == null) { ensureAttachmentsIsMutable(); attachments_.set(index, builderForValue.build()); @@ -1251,7 +1670,7 @@ public final class PushMessageProtos { } return this; } - public Builder addAttachments(org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer value) { + public Builder addAttachments(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer value) { if (attachmentsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -1265,7 +1684,7 @@ public final class PushMessageProtos { return this; } public Builder addAttachments( - int index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer value) { + int index, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer value) { if (attachmentsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -1279,7 +1698,7 @@ public final class PushMessageProtos { return this; } public Builder addAttachments( - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder builderForValue) { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder builderForValue) { if (attachmentsBuilder_ == null) { ensureAttachmentsIsMutable(); attachments_.add(builderForValue.build()); @@ -1290,7 +1709,7 @@ public final class PushMessageProtos { return this; } public Builder addAttachments( - int index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder builderForValue) { + int index, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder builderForValue) { if (attachmentsBuilder_ == null) { ensureAttachmentsIsMutable(); attachments_.add(index, builderForValue.build()); @@ -1301,7 +1720,7 @@ public final class PushMessageProtos { return this; } public Builder addAllAttachments( - java.lang.Iterable values) { + java.lang.Iterable values) { if (attachmentsBuilder_ == null) { ensureAttachmentsIsMutable(); super.addAll(values, attachments_); @@ -1314,7 +1733,7 @@ public final class PushMessageProtos { public Builder clearAttachments() { if (attachmentsBuilder_ == null) { attachments_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000002); onChanged(); } else { attachmentsBuilder_.clear(); @@ -1331,18 +1750,18 @@ public final class PushMessageProtos { } return this; } - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder getAttachmentsBuilder( + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder getAttachmentsBuilder( int index) { return getAttachmentsFieldBuilder().getBuilder(index); } - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder getAttachmentsOrBuilder( + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder getAttachmentsOrBuilder( int index) { if (attachmentsBuilder_ == null) { return attachments_.get(index); } else { return attachmentsBuilder_.getMessageOrBuilder(index); } } - public java.util.List + public java.util.List getAttachmentsOrBuilderList() { if (attachmentsBuilder_ != null) { return attachmentsBuilder_.getMessageOrBuilderList(); @@ -1350,27 +1769,27 @@ public final class PushMessageProtos { return java.util.Collections.unmodifiableList(attachments_); } } - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder addAttachmentsBuilder() { + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder addAttachmentsBuilder() { return getAttachmentsFieldBuilder().addBuilder( - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.getDefaultInstance()); + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance()); } - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder addAttachmentsBuilder( + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder addAttachmentsBuilder( int index) { return getAttachmentsFieldBuilder().addBuilder( - index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.getDefaultInstance()); + index, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance()); } - public java.util.List + 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> + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.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>( + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder>( attachments_, - ((bitField0_ & 0x00000010) == 0x00000010), + ((bitField0_ & 0x00000002) == 0x00000002), getParentForChildren(), isClean()); attachments_ = null; @@ -1378,36 +1797,15 @@ public final class PushMessageProtos { 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) + // @@protoc_insertion_point(builder_scope:textsecure.PushMessageContent) } static { - defaultInstance = new IncomingPushMessageSignal(true); + defaultInstance = new PushMessageContent(true); defaultInstance.initFields(); } - // @@protoc_insertion_point(class_scope:textsecure.IncomingPushMessageSignal) + // @@protoc_insertion_point(class_scope:textsecure.PushMessageContent) } private static com.google.protobuf.Descriptors.Descriptor @@ -1416,10 +1814,15 @@ public final class PushMessageProtos { com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_textsecure_IncomingPushMessageSignal_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor - internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_descriptor; + internal_static_textsecure_PushMessageContent_descriptor; private static com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_fieldAccessorTable; + internal_static_textsecure_PushMessageContent_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_PushMessageContent_AttachmentPointer_fieldAccessorTable; public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { @@ -1430,14 +1833,15 @@ public final class PushMessageProtos { 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" + "ecure\"s\n\031IncomingPushMessageSignal\022\014\n\004ty" + + "pe\030\001 \001(\r\022\016\n\006source\030\002 \001(\t\022\024\n\014destinations" + + "\030\003 \003(\t\022\021\n\ttimestamp\030\004 \001(\004\022\017\n\007message\030\005 \001" + + "(\014\"\254\001\n\022PushMessageContent\022\014\n\004body\030\001 \001(\t\022" + + "E\n\013attachments\030\002 \003(\01320.textsecure.PushMe" + + "ssageContent.AttachmentPointer\032A\n\021Attach" + + "mentPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013contentType\030\002" + + " \001(\t\022\013\n\003key\030\003 \001(\014B7\n\"org.whispersystems." + + "textsecure.pushB\021PushMessageProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -1449,17 +1853,25 @@ public final class PushMessageProtos { 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", }, + new java.lang.String[] { "Type", "Source", "Destinations", "Timestamp", "Message", }, 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 + internal_static_textsecure_PushMessageContent_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_textsecure_PushMessageContent_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); + internal_static_textsecure_PushMessageContent_descriptor, + new java.lang.String[] { "Body", "Attachments", }, + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.class, + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.Builder.class); + internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor = + internal_static_textsecure_PushMessageContent_descriptor.getNestedTypes().get(0); + internal_static_textsecure_PushMessageContent_AttachmentPointer_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor, + new java.lang.String[] { "Id", "ContentType", "Key", }, + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.class, + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder.class); return null; } }; diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index acde1f67c1..bee4cd9235 100644 --- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java +++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java @@ -84,31 +84,21 @@ public class PushServiceSocket { sendMessage(new OutgoingPushMessageList(message)); } - public void sendMessage(List recipients, List bodies, - List> attachmentsList, int type) + public void sendMessage(List recipients, List bodies, List types) throws IOException { List messages = new LinkedList(); - Iterator recipientsIterator = recipients.iterator(); - Iterator bodiesIterator = bodies.iterator(); - Iterator> attachmentsIterator = attachmentsList.iterator(); + Iterator recipientsIterator = recipients.iterator(); + Iterator bodiesIterator = bodies.iterator(); + Iterator typesIterator = types.iterator(); while (recipientsIterator.hasNext()) { - String recipient = recipientsIterator.next(); - byte[] body = bodiesIterator.next(); - List attachments = attachmentsIterator.next(); + String recipient = recipientsIterator.next(); + byte[] body = bodiesIterator.next(); + int type = typesIterator.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); + messages.add(new OutgoingPushMessage(recipient, body, type)); } sendMessage(new OutgoingPushMessageList(messages)); @@ -149,20 +139,7 @@ public class PushServiceSocket { return PreKeyEntity.fromJson(responseText); } - private List sendAttachments(List attachments) - throws IOException - { - List attachmentIds = new LinkedList(); - - for (PushAttachmentData attachment : attachments) { - attachmentIds.add(new PushAttachmentPointer(attachment.getContentType(), - sendAttachment(attachment))); - } - - return attachmentIds; - } - - private String sendAttachment(PushAttachmentData attachment) throws IOException { + public long sendAttachment(PushAttachmentData attachment) throws IOException { Pair response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, ""), "GET", null, "Content-Location"); @@ -178,25 +155,18 @@ public class PushServiceSocket { return new Gson().fromJson(response.second, AttachmentKey.class).getId(); } - public List> retrieveAttachments(List attachmentIds) - throws IOException - { - List> attachments = new LinkedList>(); + public File retrieveAttachment(long attachmentId) throws IOException { + Pair response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, String.valueOf(attachmentId)), + "GET", null, "Content-Location"); - for (PushAttachmentPointer attachmentId : attachmentIds) { - Pair response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, attachmentId.getKey()), - "GET", null, "Content-Location"); + Log.w("PushServiceSocket", "Attachment: " + attachmentId + " is at: " + response.first); - Log.w("PushServiceSocket", "Attachment: " + attachmentId.getKey() + " is at: " + response.first); + File attachment = File.createTempFile("attachment", ".tmp", context.getFilesDir()); + attachment.deleteOnExit(); - File attachment = File.createTempFile("attachment", ".tmp", context.getFilesDir()); - attachment.deleteOnExit(); + downloadExternalFile(response.first, attachment); - downloadExternalFile(response.first, attachment); - attachments.add(new Pair(attachment, attachmentId.getContentType())); - } - - return attachments; + return attachment; } public Pair retrieveDirectory() { @@ -394,13 +364,13 @@ public class PushServiceSocket { } private static class AttachmentKey { - private String id; + private long id; - public AttachmentKey(String id) { + public AttachmentKey(long id) { this.id = id; } - public String getId() { + public long getId() { return id; } } diff --git a/library/src/org/whispersystems/textsecure/util/Util.java b/library/src/org/whispersystems/textsecure/util/Util.java index 379c250000..800050c631 100644 --- a/library/src/org/whispersystems/textsecure/util/Util.java +++ b/library/src/org/whispersystems/textsecure/util/Util.java @@ -45,6 +45,33 @@ public class Util { } + public static byte[][] split(byte[] input, int firstLength, int secondLength) { + byte[][] parts = new byte[2][]; + + parts[0] = new byte[firstLength]; + System.arraycopy(input, 0, parts[0], 0, firstLength); + + parts[1] = new byte[secondLength]; + System.arraycopy(input, firstLength, parts[1], 0, secondLength); + + return parts; + } + + public static byte[][] split(byte[] input, int firstLength, int secondLength, int thirdLength) { + byte[][] parts = new byte[3][]; + + parts[0] = new byte[firstLength]; + System.arraycopy(input, 0, parts[0], 0, firstLength); + + parts[1] = new byte[secondLength]; + System.arraycopy(input, firstLength, parts[1], 0, secondLength); + + parts[2] = new byte[thirdLength]; + System.arraycopy(input, firstLength + secondLength, parts[2], 0, thirdLength); + + return parts; + } + public static boolean isEmpty(String value) { return value == null || value.trim().length() == 0; } @@ -94,6 +121,18 @@ public class Util { return new String(bout.toByteArray()); } + public static void readFully(InputStream in, byte[] buffer) throws IOException { + int offset = 0; + + for (;;) { + int read = in.read(buffer, offset, buffer.length - offset); + + if (read + offset < buffer.length) offset += read; + else return; + } + } + + public static void copy(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[4096]; int read; diff --git a/res/drawable-hdpi/stat_sys_download_anim0.png b/res/drawable-hdpi/stat_sys_download_anim0.png new file mode 100644 index 0000000000000000000000000000000000000000..72d5f3fcb2a86fb8208dd11151c0d95f117b808e GIT binary patch literal 501 zcmVbZG(E*ORmaUBsI^x`I|^);X{-kdlzP z>&l4cfI=X0b%b>bgGDv_AM~d|(PzbjS)~r`V^+#9iApsr?_A8Ja`lof?axu%!ABEhB(vg9jics zP_WItb0L4Eq=-{Nh}EK8rbw{EJtFlmV=@ws8(<2kO>Pj$6eC_a5=0F!9x4>9aqUnk z;)#Pvibj}H)CkdI?~9mEs4K)BJKw+*Q0ttt{!NUBGS}Lm9%J<=Ct*WHkye6ox^G&u z=GO9qy5w%}Azo$R4+DzXVqiAuO8%h^uv-12kgLWR`?cpm rBVf>|BGF>te+^|lYTDggj}{j1J)GaLH=Qdt00000NkvXXu0mjfgS^cC literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/stat_sys_download_anim1.png b/res/drawable-hdpi/stat_sys_download_anim1.png new file mode 100644 index 0000000000000000000000000000000000000000..e7ba0a5d0a379c0450322fe23228ac0426dc8ffd GIT binary patch literal 492 zcmVt1fG6X~$RreCLQ? zB;N3yla7C8V3*S?kTa76%XY%@iej6dAIY-^>NX3P*Ks%6Q^eg_RUq@T5z zBQ{L?`?EWuMUeu50wHFNc>w$> zL1=$8u0g!A^ce_l^oh?r5@{;D05XB5>z#l0C+w4QkOI@b6*( zZvA~9(~*QN)EhqVwhg!mm|Oqo!gOYH3stg6C{s{#uU!! z=aePa{+-7R+*N}NGu|`X%=33w{q1`E1`T!ovFhr#KlQPmg4bft{RY8aaERaA#FhWdzP8Z{*GbGMr7+Cd6k*(PN?vL$bku(F! z2jqd2RFqV)pR{}fCaGuyjQXRV~_#k*zN!lAQamjKnq~boO$fO>59nKS58?V%pT{yl2Qrg-IA>Nc;SH( zVG3U86iO+Ox~bwB3hzmZGwLX190iPE~aM_R#5w# z@QEtUF#&Vw`#wzXZmysTW)WpFYJU5%ACov19aKrqf(t*TFv;_^hMMw)Ip=;(V+QrZ z7-X1m%w*YjQcE-}MdKG}ld6rKu3`VXKGwV7mX~9@$H4VJL@Ohb%^^pWBV!e{e!1?~ sqd+k-X~zd4A#4rhHW7VDjo!`v0P3u!7NyOY)c^nh07*qoM6N<$g03jj1^@s6 literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/stat_sys_download_anim4.png b/res/drawable-hdpi/stat_sys_download_anim4.png new file mode 100644 index 0000000000000000000000000000000000000000..98796a2606c321cf4e7aaa25fde2d374fce0be02 GIT binary patch literal 473 zcmV;~0Ve*5P)r0VUuoK&H6jH%R)~?pGXJ#LF)~GcB*1?wp zxdIq_wx$5C+%CCdhLdx|wQkJGw-3@4j1gb)8wkdczbeyt@NKph~7d32f_ z{kJyzAM~d|$-3f8G87;b+ZB(ZRFe%FHo&mhHmJNJBXgV_!+{j|AR*&~6!0h%Q&z@W z1*p%E9swt3z#y*1m@~%_knp6ZjUccCM)V07d7=I|jxe05rmK^yGBU{y7)F#8el9SN zdNz~VWVAqy=n+aF2PS%1$5b3b0X5))L$c^295K;r6{hku3#cxmh!PzFcY3SF)Le@- zR7%1Z51Q3sYVTJGHRPCg?)6!Zq3L-P0u%H(r@!cHMIx=4JCTr} zra_dXK_Ue`bV%HRmMhSvq2nx+)LbGI6m%#;LJ&nM^5fk{5tA5W8z&?c%*vLvUVZP) zNHc4ch5(Cj^Nk=6#)hLxz$w@%=L~TKwz<>2Il}TH4M7JnNuQ3OQL6d_%mWEilJvVY z;vYZ}5T`z(s)KG_#r_BVX;A#V|tfzc_kHhdx0a{p~;Adm^Yf4 zpG{zg1R=I~m8yn^6SR0@!W#qnjL>|I=rf?tJK|bks+@{WaLfxLWFU(b5I0)nSOyYA zf+Ko}X%3Q*Ag%-vR*6cLVnLf8N-Hv3o`C1F1}29(0C%13Up! zLs;l3DB=p;(wm4~y4sUdwh2>SsF<)%#__--JB*p>-W>VsNFcaDB-D69Tw^L}B5;Bk zq9P*_5pl7K57PyP7hEACkxb-V^qU?+g5VM%J~$PO>c17O1DW#9$q(rrh%sVM;D_n9 zrrlgmbt4fvL`tc=Z(FXYNp{2m%Wp~;OYT&)FRa(H>PXglMm#gF#S6{M%n7{Wi%fXl cSn@af1mPryxoTz~-T(jq07*qoM6N<$f?H9QegFUf literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/stat_sys_download_anim1.png b/res/drawable-mdpi/stat_sys_download_anim1.png new file mode 100644 index 0000000000000000000000000000000000000000..7e002201a7c001c00f53bd1db774350490a96ec5 GIT binary patch literal 383 zcmV-_0f7FAP)O1GP5bR4iLEePbwjhub}g|#d{G1H&~6}W-fhr)H6)uphsO;khBez;H(*`tPNQOD)03*KmL3DZZV%@5Wq)X07 z$yZ?q(dE_CP3soh3Nm6$TIR)zXWNZc2H860oD97IAg{R z*B(o_Jw;@=!ptz2hN%1;)QU17fx6uFf`B};))llwv!!%XOJTjGsDc<6F=WC%&($g_ z@<0MxefCHRdGs_#megoPX#{jR;F}IFIi{ux>JgJ-cG<>=PreW#PoDl!S1p}#OhWd1 z4G5GqzM3jw3k)d+22WQ% Jmvv4FO#lEHktP5D literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/stat_sys_download_anim5.png b/res/drawable-mdpi/stat_sys_download_anim5.png new file mode 100644 index 0000000000000000000000000000000000000000..6b64abd82bbb99c13aa95bd4dbccf72acac05a87 GIT binary patch literal 377 zcmV-<0fzpGP)SOtcam z4F%h93pymk6*vJsH9|t7gPNKXa0nV2Do#MT{Yq33vHd6#G#Cf+Do&UfxI;%GK|jl3j4imYQ?cBpe@nQF6qi-prA9ErH+#5mC1y;u%wJCIS;Y zA$~}RNI={OTxR5L(}myx5eOtA*J9lC6ygPU2vIfWYq(`&0J*2)QgASnK7be{CxT*{ zZoTYgqk$$yLQ47nw(N@vutUljvf^8mV{!Zf XUaWg_)Tnv900000NkvXXu0mjfJ+79H literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/stat_sys_download_anim0.png b/res/drawable-xhdpi/stat_sys_download_anim0.png new file mode 100644 index 0000000000000000000000000000000000000000..f465442a8951e208080c50594bad50f242676f7b GIT binary patch literal 594 zcmV-Y0zb>c`@ zWVW)sJKxM`*Z&S2*(_(0FmM!|mlaVN%d-LMSvnbjg2)-KiFFngjxW5UAWl32*|O04Kn80QfL5`1zKs6Zm=F2OAtAiSW zFih}a2O?6j4};3jqd57BH}H?i~P54Aab21fy0Q?vhelVc0H?ly;8il&O{cztr(O^&xE&sYw( zw|(8@*m0{H_O|FRSTx<_c>9y?-)+E*8MD1hK1-|91w`O8PpJ03@dZkdY=xaR2}S07*qoM6N<$g5_)tMDFNcn4mHH{isT0~8vmRjVQur3#{o>zy7FN4t*gjNRl!)~9u5=bu^s z`_F7(%R?+iihv`DUV3shEH?va#nO!dkdX6^59Et1JdQ7XA|c<*My9}fG- z_{SaD!sR_FH}=?UW59qxFduFbzyn|TNQPU?dwc`S7Z4+vm3>BJL0}UuE6LS)i=Hqf zTLh%^to*S9C^`8Oz*zy@wxwbRumjiu)STpb1ruphEJ5P;s#P^Etk>tzeVPh5PQkgA`6vKO%{s7^Gp9U zvzcZH06F1!g@beVr#h*Rer?eYAP#vQZIqHQ<5Z{X963wNA)?ou1-5hd-*sA?cIn=> z1mB{_LP_|`PrYm29CKe3K)m1$o`fksbY^jHZr=llx7;w+%;vsDQ&c=0kOs32wT_ia zXz>% M07*qoM6N<$g5zil%K!iX literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/stat_sys_download_anim2.png b/res/drawable-xhdpi/stat_sys_download_anim2.png new file mode 100644 index 0000000000000000000000000000000000000000..3b47b47d7e43e44e5c93deea726af4a18ee04e16 GIT binary patch literal 625 zcmV-%0*?KOP)JiEC#7q-4D2E!m2NC*eKsNXfccNC&(@qw5?P*j#O6 z%7XbS|I{9 zs3dKY2jX~$4`#m?`Fn=)IEtKbr;BEZ_K2gP=W38>i=tTqjP#qxQW9b>*P63XIjdlyBzQjQ zk9sv5YMY#LyrIEm_)8sE&wgve2_O!69xarTGUHMw+X5w)b|<2@TnDzx@E>)OXS=j- znZaJX=T>;Yg?|{-q(ct+I_A3XRj1ne_LbEGT=Nw0?IKJlAMFgCU3OLvHhtF^Uzv3E-Kv z29Qnet|T*t#S_mi02?Y|z&^h95-H<&4`lm)63uCBk?F2^5a0g+5^u`lYld*quD829`eBd1^#V~<%z-u&m-UB0>sEuyP z$x4+uj+xyy8znfj;&&ugu^rPrz&*e}0AMdPPflL;t{m_~1B`J&9mBzp03P_rTe32w zo)MqG(o1daT2S_xkOhX#_}(}sdWYx%M`Wcz%E&qiT>AtQiTqRwiV7gXBeDOgO*785 z4M|W9k5aeTtJVXg+k{9Q2l(KY_9A)DSdJpi2{)Q=cL$F+2_mgkfI#r4%x0*GUtM}bmO7F_6jlcL1Zs6_Od z%Yb*O{!HhqYM1`aF1U-87fQ-+KI?6FaLnB_fOyOcJSlU|bzxC&Uf%nWg0005@NklPrG5QV?$?#PRX zAQnUk5CXU0D%^u(a3MCVS+Y+8F%nV~q#y?)1$(-Sg(pK#=C7F85)v-2#;SU+tGmYU zwPC{{mLx^M;cH(eq8iH01lm%%HUd16SG*yXT_hYIc#9{lXCnjf8m)ca0Xw!*Tij5v zEFlX5GyB?X;RtAzA8^*P4ab&XORy!_68sAUp)LJZk1UNB(O&+Mb$~-2k$G%jCelEBfP{4*>SCfQImB=@SKB8 zbANGNX}_RM*sH1b1z)si0(KGr$TJQ|)i^Y4ONMfxU)uqD2t-unh{HkW;F#)V7oc<> za+z?Hb=jW=VryMqBa0P#+v+mUoI=hTwpX1s_ zDV_yqIv)aZ7GEi%mz-z0EBF(g-@ffK-kWA;aodIB`N=1}>E9f)`OPRE^MZtD&Z#ae z@Mim-QM}}esV;5s=J;c(c}U2f)^tB;{iU>(ngvJlsp5YM>6OYc<8VhfLtDz`?#Y6( z%ahS}MJ_IEI0pXKO5iA?Jg-6TjXMiWT0_t5fYOzM8;egusvVuuH$(Oaa<>SrLs l|4ICP8v|o{-}j*Y{2RhN$B!^DP_qC4002ovPDHLkV1h)Q3a9`8 literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/stat_sys_download_anim5.png b/res/drawable-xhdpi/stat_sys_download_anim5.png new file mode 100644 index 0000000000000000000000000000000000000000..e3d4730ee7948cd5ccd39a153d682f7b1f52c9b8 GIT binary patch literal 592 zcmV-W027@YhnOo&E>6TCsBj*5u* z&GU`muT)S8P7t9{5fJA*6Znnbdldgv1jKiq&H~?|aIFgQgD11W_f)(_eCE-t@Er;h z9C0AlAC>0Z*VS!XH#2y4p=dDnIgaP1{)G2>d6E))b2-)V*AZmVitly7qQ0!(&Qx)7x`~sH~%Mbdl@&h e>89U7{`?o4S2jXt#j7Cz0000 + + + + + + + + + + diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index 0be4d8a767..6bbefa0406 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java @@ -17,6 +17,7 @@ package org.thoughtcrime.securesms.crypto; import android.content.Context; +import android.content.Intent; import android.database.Cursor; import android.util.Log; @@ -33,22 +34,26 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; +import org.thoughtcrime.securesms.service.PushDownloader; +import org.thoughtcrime.securesms.service.PushReceiver; +import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.sms.SmsTransportDetails; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.IdentityKeyPair; import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.InvalidVersionException; -import org.whispersystems.textsecure.crypto.MessageCipher; -import org.whispersystems.textsecure.crypto.SessionCipher; -import org.whispersystems.textsecure.util.Hex; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.WorkerThread; import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.MessageCipher; +import org.whispersystems.textsecure.crypto.SessionCipher; +import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.push.PushTransportDetails; +import org.whispersystems.textsecure.util.Hex; import java.io.IOException; -import java.util.LinkedList; -import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.MmsException; @@ -64,21 +69,13 @@ import ws.com.google.android.mms.pdu.RetrieveConf; public class DecryptingQueue { - private static final List workQueue = new LinkedList(); - - static { - Thread workerThread = new WorkerThread(workQueue, "Async Decryption Thread"); - workerThread.start(); - } + private static final Executor executor = Executors.newSingleThreadExecutor(); public static void scheduleDecryption(Context context, MasterSecret masterSecret, long messageId, long threadId, MultimediaMessagePdu mms) { MmsDecryptionItem runnable = new MmsDecryptionItem(context, masterSecret, messageId, threadId, mms); - synchronized (workQueue) { - workQueue.add(runnable); - workQueue.notifyAll(); - } + executor.execute(runnable); } public static void scheduleDecryption(Context context, MasterSecret masterSecret, @@ -87,10 +84,15 @@ public class DecryptingQueue { { DecryptionWorkItem runnable = new DecryptionWorkItem(context, masterSecret, messageId, threadId, originator, body, isSecureMessage, isKeyExchange); - synchronized (workQueue) { - workQueue.add(runnable); - workQueue.notifyAll(); - } + executor.execute(runnable); + } + + public static void scheduleDecryption(Context context, MasterSecret masterSecret, + long messageId, IncomingPushMessage message) + { + PushDecryptionWorkItem runnable = new PushDecryptionWorkItem(context, masterSecret, + messageId, message); + executor.execute(runnable); } public static void schedulePendingDecrypts(Context context, MasterSecret masterSecret) { @@ -143,6 +145,59 @@ public class DecryptingQueue { originator, body, isSecureMessage, isKeyExchange); } + private static class PushDecryptionWorkItem implements Runnable { + + private Context context; + private MasterSecret masterSecret; + private long messageId; + private IncomingPushMessage message; + + public PushDecryptionWorkItem(Context context, MasterSecret masterSecret, + long messageId, IncomingPushMessage message) + { + this.context = context; + this.masterSecret = masterSecret; + this.messageId = messageId; + this.message = message; + } + + public void run() { + synchronized (SessionCipher.CIPHER_LOCK) { + try { + Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSource(), false); + Recipient recipient = recipients.getPrimaryRecipient(); + + if (!KeyUtil.isSessionFor(context, recipient)) { + sendResult(PushReceiver.RESULT_NO_SESSION); + return; + } + + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey, new PushTransportDetails()); + + byte[] plaintextBody = messageCipher.decrypt(recipient, message.getBody()); + message = message.withBody(plaintextBody); + sendResult(PushReceiver.RESULT_OK); + } catch (InvalidMessageException e) { + Log.w("DecryptionQueue", e); + sendResult(PushReceiver.RESULT_DECRYPT_FAILED); + } catch (RecipientFormattingException e) { + Log.w("DecryptionQueue", e); + sendResult(PushReceiver.RESULT_DECRYPT_FAILED); + } + } + } + + private void sendResult(int result) { + Intent intent = new Intent(context, SendReceiveService.class); + intent.setAction(SendReceiveService.DECRYPTED_PUSH_ACTION); + intent.putExtra("message", message); + intent.putExtra("message_id", messageId); + intent.putExtra("result", result); + context.startService(intent); + } + } + private static class MmsDecryptionItem implements Runnable { private long messageId; private long threadId; @@ -267,13 +322,10 @@ public class DecryptingQueue { synchronized (SessionCipher.CIPHER_LOCK) { try { - Log.w("DecryptingQueue", "Parsing recipient for originator: " + originator); Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false); Recipient recipient = recipients.getPrimaryRecipient(); - Log.w("DecryptingQueue", "Parsed Recipient: " + recipient.getNumber()); if (!KeyUtil.isSessionFor(context, recipient)) { - Log.w("DecryptingQueue", "No such recipient session..."); database.markAsNoSession(messageId); return; } diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index 4ef1cb5be4..3315f7ada0 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -51,7 +51,8 @@ public class DatabaseFactory { private static final int INTRODUCED_MMS_BODY_VERSION = 7; private static final int INTRODUCED_MMS_FROM_VERSION = 8; private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9; - private static final int DATABASE_VERSION = 9; + private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10; + private static final int DATABASE_VERSION = 10; private static final String DATABASE_NAME = "messages.db"; private static final Object lock = new Object(); @@ -71,6 +72,7 @@ public class DatabaseFactory { private final MmsSmsDatabase mmsSmsDatabase; private final IdentityDatabase identityDatabase; private final DraftDatabase draftDatabase; + private final PushDatabase pushDatabase; public static DatabaseFactory getInstance(Context context) { synchronized (lock) { @@ -132,6 +134,10 @@ public class DatabaseFactory { return getInstance(context).draftDatabase; } + public static PushDatabase getPushDatabase(Context context) { + return getInstance(context).pushDatabase; + } + private DatabaseFactory(Context context) { this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION); this.sms = new SmsDatabase(context, databaseHelper); @@ -144,6 +150,7 @@ public class DatabaseFactory { this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper); this.identityDatabase = new IdentityDatabase(context, databaseHelper); this.draftDatabase = new DraftDatabase(context, databaseHelper); + this.pushDatabase = new PushDatabase(context, databaseHelper); } public void reset(Context context) { @@ -425,6 +432,7 @@ public class DatabaseFactory { db.execSQL(MmsAddressDatabase.CREATE_TABLE); db.execSQL(IdentityDatabase.CREATE_TABLE); db.execSQL(DraftDatabase.CREATE_TABLE); + db.execSQL(PushDatabase.CREATE_TABLE); executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS); @@ -617,6 +625,12 @@ public class DatabaseFactory { db.execSQL("CREATE TABLE identities (_id INTEGER PRIMARY KEY, recipient INTEGER UNIQUE, key TEXT, mac TEXT);"); } + if (oldVersion < INTRODUCED_PUSH_DATABASE_VERSION) { + db.execSQL("CREATE TABLE push (_id INTEGER PRIMARY KEY, type INTEGER, source TEXT, destinations TEXT, body TEXT, TIMESTAMP INTEGER);"); + db.execSQL("ALTER TABLE part ADD COLUMN pending_push INTEGER;"); + db.execSQL("CREATE INDEX IF NOT EXISTS pending_push_index ON parts (pending_push);"); + } + db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 7d6b484d2c..0df05ae1f0 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -51,6 +51,7 @@ import java.io.UnsupportedEncodingException; import java.lang.ref.SoftReference; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; @@ -63,6 +64,7 @@ import ws.com.google.android.mms.pdu.EncodedStringValue; import ws.com.google.android.mms.pdu.NotificationInd; import ws.com.google.android.mms.pdu.PduBody; import ws.com.google.android.mms.pdu.PduHeaders; +import ws.com.google.android.mms.pdu.PduPart; import ws.com.google.android.mms.pdu.SendReq; // XXXX Clean up MMS efficiency: @@ -289,11 +291,11 @@ public class MmsDatabase extends Database implements MmsSmsColumns { public SendReq[] getOutgoingMessages(MasterSecret masterSecret, long messageId) throws MmsException { - MmsAddressDatabase addr = DatabaseFactory.getMmsAddressDatabase(context); - PartDatabase parts = getPartDatabase(masterSecret); - SQLiteDatabase database = databaseHelper.getReadableDatabase(); - MasterCipher masterCipher = masterSecret == null ? null : new MasterCipher(masterSecret); - Cursor cursor = null; + MmsAddressDatabase addr = DatabaseFactory.getMmsAddressDatabase(context); + PartDatabase partDatabase = getPartDatabase(masterSecret); + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + MasterCipher masterCipher = masterSecret == null ? null : new MasterCipher(masterSecret); + Cursor cursor = null; String selection; @@ -322,8 +324,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns { String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY)); PduHeaders headers = getHeadersFromCursor(cursor); addr.getAddressesForId(messageId, headers); - PduBody body = parts.getParts(messageId, true); + PduBody body = getPartsAsBody(partDatabase.getParts(messageId, true)); try { if (!Util.isEmpty(messageText) && Types.isSymmetricEncryption(outboxType)) { @@ -864,9 +866,12 @@ public class MmsDatabase extends Database implements MmsSmsColumns { if (masterSecret == null) return null; - PduBody body = getPartDatabase(masterSecret).getParts(id, false); + PduBody body = getPartsAsBody(getPartDatabase(masterSecret).getParts(id, false)); SlideDeck slideDeck = new SlideDeck(context, masterSecret, body); - slideCache.put(id, new SoftReference(slideDeck)); + + if (!body.containsPushInProgress()) { + slideCache.put(id, new SoftReference(slideDeck)); + } return slideDeck; } @@ -907,4 +912,14 @@ public class MmsDatabase extends Database implements MmsSmsColumns { } } + private PduBody getPartsAsBody(List> parts) { + PduBody body = new PduBody(); + + for (Pair part : parts) { + body.addPart(part.second); + } + + return body; + } + } diff --git a/src/org/thoughtcrime/securesms/database/PartDatabase.java b/src/org/thoughtcrime/securesms/database/PartDatabase.java index 9f981722ec..067a17b14a 100644 --- a/src/org/thoughtcrime/securesms/database/PartDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PartDatabase.java @@ -23,10 +23,12 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; +import android.util.Pair; import org.thoughtcrime.securesms.providers.PartProvider; import org.thoughtcrime.securesms.util.Util; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -34,6 +36,8 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.LinkedList; +import java.util.List; import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.MmsException; @@ -42,31 +46,34 @@ import ws.com.google.android.mms.pdu.PduPart; public class PartDatabase extends Database { - private static final String TABLE_NAME = "part"; - private static final String ID = "_id"; - private static final String MMS_ID = "mid"; - private static final String SEQUENCE = "seq"; - private static final String CONTENT_TYPE = "ct"; - private static final String NAME = "name"; - private static final String CHARSET = "chset"; - private static final String CONTENT_DISPOSITION = "cd"; - private static final String FILENAME = "fn"; - private static final String CONTENT_ID = "cid"; - private static final String CONTENT_LOCATION = "cl"; - private static final String CONTENT_TYPE_START = "ctt_s"; - private static final String CONTENT_TYPE_TYPE = "ctt_t"; - private static final String ENCRYPTED = "encrypted"; - private static final String DATA = "_data"; + private static final String TABLE_NAME = "part"; + private static final String ID = "_id"; + private static final String MMS_ID = "mid"; + private static final String SEQUENCE = "seq"; + private static final String CONTENT_TYPE = "ct"; + private static final String NAME = "name"; + private static final String CHARSET = "chset"; + private static final String CONTENT_DISPOSITION = "cd"; + private static final String FILENAME = "fn"; + private static final String CONTENT_ID = "cid"; + private static final String CONTENT_LOCATION = "cl"; + private static final String CONTENT_TYPE_START = "ctt_s"; + private static final String CONTENT_TYPE_TYPE = "ctt_t"; + private static final String ENCRYPTED = "encrypted"; + private static final String DATA = "_data"; + private static final String PENDING_PUSH_ATTACHMENT = "pending_push"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + MMS_ID + " INTEGER, " + SEQUENCE + " INTEGER DEFAULT 0, " + CONTENT_TYPE + " TEXT, " + NAME + " TEXT, " + CHARSET + " INTEGER, " + CONTENT_DISPOSITION + " TEXT, " + FILENAME + " TEXT, " + CONTENT_ID + " TEXT, " + CONTENT_LOCATION + " TEXT, " + CONTENT_TYPE_START + " INTEGER, " + - CONTENT_TYPE_TYPE + " TEXT, " + ENCRYPTED + " INTEGER, " + DATA + " TEXT);"; + CONTENT_TYPE_TYPE + " TEXT, " + ENCRYPTED + " INTEGER, " + + PENDING_PUSH_ATTACHMENT + " INTEGER, "+ DATA + " TEXT);"; public static final String[] CREATE_INDEXS = { - "CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");" + "CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");", + "CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + PENDING_PUSH_ATTACHMENT + ");", }; public PartDatabase(Context context, SQLiteOpenHelper databaseHelper) { @@ -113,6 +120,11 @@ public class PartDatabase extends Database { if (!cursor.isNull(encryptedColumn)) part.setEncrypted(cursor.getInt(encryptedColumn) == 1); + + int pendingPushColumn = cursor.getColumnIndexOrThrow(PENDING_PUSH_ATTACHMENT); + + if (!cursor.isNull(pendingPushColumn)) + part.setPendingPush(cursor.getInt(pendingPushColumn) == 1); } @@ -126,8 +138,9 @@ public class PartDatabase extends Database { if (part.getContentType() != null) { contentValues.put(CONTENT_TYPE, Util.toIsoString(part.getContentType())); - if (Util.toIsoString(part.getContentType()).equals(ContentType.APP_SMIL)) + if (Util.toIsoString(part.getContentType()).equals(ContentType.APP_SMIL)) { contentValues.put(SEQUENCE, -1); + } } else { throw new MmsException("There is no content type for this part."); } @@ -153,6 +166,7 @@ public class PartDatabase extends Database { } contentValues.put(ENCRYPTED, part.getEncrypted() ? 1 : 0); + contentValues.put(PENDING_PUSH_ATTACHMENT, part.isPendingPush() ? 1 : 0); return contentValues; } @@ -186,35 +200,42 @@ public class PartDatabase extends Database { } } - private File writePartData(PduPart part) throws MmsException { + private File writePartData(PduPart part, InputStream in) throws MmsException { try { File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE); File dataFile = File.createTempFile("part", ".mms", partsDirectory); FileOutputStream fout = getPartOutputStream(dataFile, part); + byte[] buf = new byte[512]; + int read; + + while ((read = in.read(buf)) != -1) { + fout.write(buf, 0, read); + } + + fout.close(); + in.close(); + + return dataFile; + } catch (IOException e) { + throw new AssertionError(e); + } + } + + private File writePartData(PduPart part) throws MmsException { + try { if (part.getData() != null) { Log.w("PartDatabase", "Writing part data from buffer"); - fout.write(part.getData()); - fout.close(); - return dataFile; + return writePartData(part, new ByteArrayInputStream(part.getData())); } else if (part.getDataUri() != null) { Log.w("PartDatabase", "Writing part dat from URI"); - byte[] buf = new byte[512]; InputStream in = context.getContentResolver().openInputStream(part.getDataUri()); - int read; - while ((read = in.read(buf)) != -1) - fout.write(buf, 0, read); - - fout.close(); - in.close(); - return dataFile; + return writePartData(part, in); } else { throw new MmsException("Part is empty!"); } } catch (FileNotFoundException e) { throw new AssertionError(e); - } catch (IOException e) { - throw new AssertionError(e); } } @@ -224,7 +245,7 @@ public class PartDatabase extends Database { long partId = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); getPartValues(part, cursor); - if (includeData) + if (includeData && !part.isPendingPush()) readPartData(part, dataLocation); part.setDataUri(ContentUris.withAppendedId(PartProvider.CONTENT_URI, partId)); @@ -232,14 +253,20 @@ public class PartDatabase extends Database { } private long insertPart(PduPart part, long mmsId) throws MmsException { - SQLiteDatabase database = databaseHelper.getWritableDatabase(); - File dataFile = writePartData(part); + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + File dataFile = null; + + if (!part.isPendingPush()) { + dataFile = writePartData(part); + Log.w("PartDatabase", "Wrote part to file: " + dataFile.getAbsolutePath()); + } - Log.w("PartDatabase", "Wrote part to file: " + dataFile.getAbsolutePath()); ContentValues contentValues = getContentValuesForPart(part); - contentValues.put(MMS_ID, mmsId); - contentValues.put(DATA, dataFile.getAbsolutePath()); + + if (dataFile != null) { + contentValues.put(DATA, dataFile.getAbsolutePath()); + } return database.insert(TABLE_NAME, null, contentValues); } @@ -256,6 +283,10 @@ public class PartDatabase extends Database { PduPart part = new PduPart(); part.setEncrypted(cursor.getInt(1) == 1); + if (cursor.isNull(0)) { + throw new FileNotFoundException("No part data for id: " + partId); + } + return getPartInputStream(new File(cursor.getString(0)), part); } else { throw new FileNotFoundException("No part for id: " + partId); @@ -273,6 +304,41 @@ public class PartDatabase extends Database { } } + public void updateDownloadedPart(long messageId, long partId, PduPart part, InputStream data) + throws MmsException + { + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + File partData = writePartData(part, data); + + part.setContentDisposition(new byte[0]); + part.setPendingPush(false); + + ContentValues values = getContentValuesForPart(part); + + if (partData != null) { + values.put(DATA, partData.getAbsolutePath()); + } + + database.update(TABLE_NAME, values, ID_WHERE, new String[] {partId+""}); + notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId)); + } + + public void updateFailedDownloadedPart(long messageId, long partId, PduPart part) + throws MmsException + { + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + + part.setContentDisposition(new byte[0]); + part.setPendingPush(false); + + ContentValues values = getContentValuesForPart(part); + + values.put(DATA, (String)null); + + database.update(TABLE_NAME, values, ID_WHERE, new String[] {partId+""}); + notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId)); + } + public PduPart getPart(long partId, boolean includeData) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); Cursor cursor = null; @@ -290,26 +356,50 @@ public class PartDatabase extends Database { } } - public PduBody getParts(long mmsId, boolean includeData) { - SQLiteDatabase database = databaseHelper.getReadableDatabase(); - PduBody body = new PduBody(); - Cursor cursor = null; + public List> getParts(long mmsId, boolean includeData) { + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + List> results = new LinkedList>(); + Cursor cursor = null; try { cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {mmsId+""}, null, null, null); while (cursor != null && cursor.moveToNext()) { PduPart part = getPart(cursor, includeData); - body.addPart(part); + results.add(new Pair(cursor.getLong(cursor.getColumnIndexOrThrow(ID)), + part)); } - return body; + return results; } finally { if (cursor != null) cursor.close(); } } + public List>> getPushPendingParts() { + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + List>> results = new LinkedList>>(); + Cursor cursor = null; + + try { + cursor = database.query(TABLE_NAME, null, PENDING_PUSH_ATTACHMENT + " = ?", new String[] {"1"}, null, null, null); + + while (cursor != null && cursor.moveToNext()) { + PduPart part = getPart(cursor, false); + results.add(new Pair>(cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)), + new Pair(cursor.getLong(cursor.getColumnIndexOrThrow(ID)), + part))); + } + + return results; + } finally { + if (cursor != null) + cursor.close(); + } + + } + public void deleteParts(long mmsId) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); Cursor cursor = null; diff --git a/src/org/thoughtcrime/securesms/database/PushDatabase.java b/src/org/thoughtcrime/securesms/database/PushDatabase.java new file mode 100644 index 0000000000..0ca927423e --- /dev/null +++ b/src/org/thoughtcrime/securesms/database/PushDatabase.java @@ -0,0 +1,42 @@ +package org.thoughtcrime.securesms.database; + +import android.content.ContentValues; +import android.content.Context; +import android.database.sqlite.SQLiteOpenHelper; + +import org.spongycastle.util.encoders.Base64; +import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.util.Util; + +public class PushDatabase extends Database { + + private static final String TABLE_NAME = "push"; + public static final String ID = "_id"; + public static final String TYPE = "type"; + public static final String SOURCE = "source"; + public static final String DESTINATIONS = "destinations"; + public static final String BODY = "body"; + public static final String TIMESTAMP = "timestamp"; + + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + + TYPE + " INTEGER, " + SOURCE + " TEXT, " + DESTINATIONS + " TEXT, " + BODY + " TEXT, " + TIMESTAMP + " INTEGER);"; + + public PushDatabase(Context context, SQLiteOpenHelper databaseHelper) { + super(context, databaseHelper); + } + + public long insert(IncomingPushMessage message) { + ContentValues values = new ContentValues(); + values.put(TYPE, message.getType()); + values.put(SOURCE, message.getSource()); + values.put(DESTINATIONS, Util.join(message.getDestinations(), ",")); + values.put(BODY, Base64.encode(message.getBody())); + values.put(TIMESTAMP, message.getTimestampMillis()); + + return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values); + } + + public void delete(long id) { + databaseHelper.getWritableDatabase().delete(TABLE_NAME, ID_WHERE, new String[] {id+""}); + } +} diff --git a/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java b/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java index dd437e29eb..397e34a529 100644 --- a/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java +++ b/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java @@ -7,8 +7,6 @@ import android.util.Log; import com.google.android.gcm.GCMBaseIntentService; import org.thoughtcrime.securesms.service.RegistrationService; import org.thoughtcrime.securesms.service.SendReceiveService; -import org.thoughtcrime.securesms.sms.IncomingTextMessage; -import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.push.IncomingEncryptedPushMessage; @@ -17,7 +15,6 @@ import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.util.Util; import java.io.IOException; -import java.util.ArrayList; public class GcmIntentService extends GCMBaseIntentService { @@ -61,8 +58,11 @@ public class GcmIntentService extends GCMBaseIntentService { IncomingEncryptedPushMessage encryptedMessage = new IncomingEncryptedPushMessage(data, sessionKey); IncomingPushMessage message = encryptedMessage.getIncomingPushMessage(); - if (!message.hasAttachments()) handleIncomingTextMessage(context, message); - else handleIncomingMediaMessage(context, message); + Intent service = new Intent(context, SendReceiveService.class); + service.setAction(SendReceiveService.RECEIVE_PUSH_ACTION); + service.putExtra("message", message); + + context.startService(service); } catch (IOException e) { Log.w("GcmIntentService", e); } catch (InvalidVersionException e) { @@ -75,25 +75,6 @@ public class GcmIntentService extends GCMBaseIntentService { Log.w("GcmIntentService", "GCM Error: " + s); } - private void handleIncomingTextMessage(Context context, IncomingPushMessage message) { - ArrayList messages = new ArrayList(); - String encodedBody = new String(new SmsTransportDetails().getEncodedMessage(message.getBody())); - messages.add(new IncomingTextMessage(message, encodedBody)); - - Intent receivedIntent = new Intent(context, SendReceiveService.class); - receivedIntent.setAction(SendReceiveService.RECEIVE_SMS_ACTION); - receivedIntent.putParcelableArrayListExtra("text_messages", messages); - receivedIntent.putExtra("push_type", message.getType()); - context.startService(receivedIntent); - } - - private void handleIncomingMediaMessage(Context context, IncomingPushMessage message) { - Intent receivedIntent = new Intent(context, SendReceiveService.class); - receivedIntent.setAction(SendReceiveService.RECEIVE_PUSH_MMS_ACTION); - receivedIntent.putExtra("media_message", message); - context.startService(receivedIntent); - } - private PushServiceSocket getGcmSocket(Context context) { String localNumber = TextSecurePreferences.getLocalNumber(context); String password = TextSecurePreferences.getPushServerPassword(context); diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index 451391b06d..559bb4132f 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -55,21 +55,21 @@ public class AttachmentManager { public void setImage(Uri image) throws IOException, BitmapDecodingException { ImageSlide slide = new ImageSlide(context, image); slideDeck.addSlide(slide); - thumbnail.setImageBitmap(slide.getThumbnail(345, 261)); + thumbnail.setImageDrawable(slide.getThumbnail(345, 261)); attachmentView.setVisibility(View.VISIBLE); } public void setVideo(Uri video) throws IOException, MediaTooLargeException { VideoSlide slide = new VideoSlide(context, video); slideDeck.addSlide(slide); - thumbnail.setImageBitmap(slide.getThumbnail(thumbnail.getWidth(), thumbnail.getHeight())); + thumbnail.setImageDrawable(slide.getThumbnail(thumbnail.getWidth(), thumbnail.getHeight())); attachmentView.setVisibility(View.VISIBLE); } public void setAudio(Uri audio)throws IOException, MediaTooLargeException { AudioSlide slide = new AudioSlide(context, audio); slideDeck.addSlide(slide); - thumbnail.setImageBitmap(slide.getThumbnail(thumbnail.getWidth(), thumbnail.getHeight())); + thumbnail.setImageDrawable(slide.getThumbnail(thumbnail.getWidth(), thumbnail.getHeight())); attachmentView.setVisibility(View.VISIBLE); } diff --git a/src/org/thoughtcrime/securesms/mms/AudioSlide.java b/src/org/thoughtcrime/securesms/mms/AudioSlide.java index aa62e527db..ee27e9e951 100644 --- a/src/org/thoughtcrime/securesms/mms/AudioSlide.java +++ b/src/org/thoughtcrime/securesms/mms/AudioSlide.java @@ -25,6 +25,7 @@ import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.MediaStore.Audio; import android.widget.ImageView; @@ -50,8 +51,8 @@ public class AudioSlide extends Slide { } @Override - public Bitmap getThumbnail(int maxWidth, int maxHeight) { - return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_menu_add_sound); + public Drawable getThumbnail(int maxWidth, int maxHeight) { + return context.getResources().getDrawable(R.drawable.ic_menu_add_sound); } public static PduPart constructPartFromUri(Context context, Uri uri) throws IOException, MediaTooLargeException { diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java index 0b4fc29a9b..16054180fc 100644 --- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java +++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java @@ -17,9 +17,8 @@ package org.thoughtcrime.securesms.mms; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Color; +import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -30,11 +29,11 @@ import android.util.Log; import android.widget.ImageView; import org.thoughtcrime.securesms.R; -import org.whispersystems.textsecure.crypto.MasterSecret; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.LRUCache; +import org.whispersystems.textsecure.crypto.MasterSecret; import java.io.FileNotFoundException; import java.io.IOException; @@ -50,8 +49,8 @@ import ws.com.google.android.mms.pdu.PduPart; public class ImageSlide extends Slide { private static final int MAX_CACHE_SIZE = 10; - private static final Map> thumbnailCache = - Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE)); + private static final Map> thumbnailCache = + Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE)); public ImageSlide(Context context, MasterSecret masterSecret, PduPart part) { super(context, masterSecret, part); @@ -62,32 +61,37 @@ public class ImageSlide extends Slide { } @Override - public Bitmap getThumbnail(int maxWidth, int maxHeight) { - Bitmap thumbnail = getCachedThumbnail(); + public Drawable getThumbnail(int maxWidth, int maxHeight) { + Drawable thumbnail = getCachedThumbnail(); - if (thumbnail != null) + if (thumbnail != null) { return thumbnail; + } + + if (part.isPendingPush()) { + return context.getResources().getDrawable(R.drawable.stat_sys_download); + } try { InputStream measureStream = getPartDataInputStream(); InputStream dataStream = getPartDataInputStream(); - thumbnail = BitmapUtil.createScaledBitmap(measureStream, dataStream, maxWidth, maxHeight); - thumbnailCache.put(part.getDataUri(), new SoftReference(thumbnail)); + thumbnail = new BitmapDrawable(context.getResources(), BitmapUtil.createScaledBitmap(measureStream, dataStream, maxWidth, maxHeight)); + thumbnailCache.put(part.getDataUri(), new SoftReference(thumbnail)); return thumbnail; } catch (FileNotFoundException e) { Log.w("ImageSlide", e); - return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_missing_thumbnail_picture); + return context.getResources().getDrawable(R.drawable.ic_missing_thumbnail_picture); } catch (BitmapDecodingException e) { Log.w("ImageSlide", e); - return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_missing_thumbnail_picture); + return context.getResources().getDrawable(R.drawable.ic_missing_thumbnail_picture); } } @Override public void setThumbnailOn(ImageView imageView) { - Bitmap thumbnail = getCachedThumbnail(); + Drawable thumbnail = getCachedThumbnail(); if (thumbnail != null) { Log.w("ImageSlide", "Setting cached thumbnail..."); @@ -109,8 +113,9 @@ public class ImageSlide extends Slide { MmsDatabase.slideResolver.execute(new Runnable() { @Override public void run() { - final Bitmap bitmap = getThumbnail(maxWidth, maxHeight); + final Drawable bitmap = getThumbnail(maxWidth, maxHeight); final ImageView destination = weakImageView.get(); + if (destination != null && destination.getDrawable() == temporaryDrawable) { handler.post(new Runnable() { @Override @@ -123,24 +128,26 @@ public class ImageSlide extends Slide { }); } - private void setThumbnailOn(ImageView imageView, Bitmap thumbnail, boolean fromMemory) { + private void setThumbnailOn(ImageView imageView, Drawable thumbnail, boolean fromMemory) { if (fromMemory) { - imageView.setImageBitmap(thumbnail); + imageView.setImageDrawable(thumbnail); + } else if (thumbnail instanceof AnimationDrawable) { + imageView.setImageDrawable(thumbnail); + ((AnimationDrawable)imageView.getDrawable()).start(); } else { - BitmapDrawable result = new BitmapDrawable(context.getResources(), thumbnail); - TransitionDrawable fadingResult = new TransitionDrawable(new Drawable[]{new ColorDrawable(Color.TRANSPARENT), result}); + TransitionDrawable fadingResult = new TransitionDrawable(new Drawable[]{new ColorDrawable(Color.TRANSPARENT), thumbnail}); imageView.setImageDrawable(fadingResult); fadingResult.startTransition(300); } } - private Bitmap getCachedThumbnail() { + private Drawable getCachedThumbnail() { synchronized (thumbnailCache) { - SoftReference bitmapReference = thumbnailCache.get(part.getDataUri()); + SoftReference bitmapReference = thumbnailCache.get(part.getDataUri()); Log.w("ImageSlide", "Got soft reference: " + bitmapReference); if (bitmapReference != null) { - Bitmap bitmap = bitmapReference.get(); + Drawable bitmap = bitmapReference.get(); Log.w("ImageSlide", "Got cached bitmap: " + bitmap); if (bitmap != null) return bitmap; else thumbnailCache.remove(part.getDataUri()); diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index 636b41bef6..29b23bdcec 100644 --- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -1,15 +1,13 @@ package org.thoughtcrime.securesms.mms; -import android.util.Log; -import android.util.Pair; - import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.textsecure.crypto.MasterCipher; +import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; +import org.whispersystems.textsecure.util.Base64; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.List; +import java.io.UnsupportedEncodingException; import ws.com.google.android.mms.pdu.CharacterSets; import ws.com.google.android.mms.pdu.EncodedStringValue; @@ -28,9 +26,9 @@ public class IncomingMediaMessage { this.body = retreived.getBody(); } - public IncomingMediaMessage(String localNumber, IncomingPushMessage message, - List> attachments) - throws IOException + public IncomingMediaMessage(MasterSecret masterSecret, String localNumber, + IncomingPushMessage message, + PushMessageContent messageContent) { this.headers = new PduHeaders(); this.body = new PduBody(); @@ -39,32 +37,29 @@ public class IncomingMediaMessage { this.headers.appendEncodedStringValue(new EncodedStringValue(localNumber), PduHeaders.TO); for (String destination : message.getDestinations()) { - if (!destination.equals(localNumber)) { - this.headers.appendEncodedStringValue(new EncodedStringValue(destination), PduHeaders.CC); - } + this.headers.appendEncodedStringValue(new EncodedStringValue(destination), PduHeaders.CC); } this.headers.setLongInteger(message.getTimestampMillis() / 1000, PduHeaders.DATE); - if (message.getBody() != null && message.getBody().length > 0) { + if (messageContent.getBody() != null && messageContent.getBody().length() > 0) { PduPart text = new PduPart(); - text.setData(message.getBody()); - text.setContentType("text/plain".getBytes(CharacterSets.MIMENAME_ISO_8859_1)); + text.setData(Util.toIsoBytes(messageContent.getBody())); + text.setContentType(Util.toIsoBytes("text/plain")); body.addPart(text); } - if (attachments != null) { - for (Pair attachment : attachments) { - PduPart media = new PduPart(); - FileInputStream fin = new FileInputStream(attachment.first); - byte[] data = Util.readFully(fin); + if (messageContent.getAttachmentsCount() > 0) { + for (PushMessageContent.AttachmentPointer attachment : messageContent.getAttachmentsList()) { + PduPart media = new PduPart(); + byte[] encryptedKey = new MasterCipher(masterSecret).encryptBytes(attachment.getKey().toByteArray()); - Log.w("IncomingMediaMessage", "Adding part: " + attachment.second + " with length: " + data.length); + media.setContentType(Util.toIsoBytes(attachment.getContentType())); + media.setContentLocation(Util.toIsoBytes(String.valueOf(attachment.getId()))); + media.setContentDisposition(Util.toIsoBytes(Base64.encodeBytes(encryptedKey))); + media.setPendingPush(true); - media.setContentType(attachment.second.getBytes(CharacterSets.MIMENAME_ISO_8859_1)); - media.setData(data); body.addPart(media); - attachment.first.delete(); } } } diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java index d3a0af680f..71467565b0 100644 --- a/src/org/thoughtcrime/securesms/mms/Slide.java +++ b/src/org/thoughtcrime/securesms/mms/Slide.java @@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.providers.PartProvider; import android.content.ContentUris; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.Log; import android.widget.ImageView; @@ -90,12 +91,12 @@ public abstract class Slide { return part.getDataUri(); } - public Bitmap getThumbnail(int maxWidth, int maxHeight) { + public Drawable getThumbnail(int maxWidth, int maxHeight) { throw new AssertionError("getThumbnail() called on non-thumbnail producing slide!"); } public void setThumbnailOn(ImageView imageView) { - imageView.setImageBitmap(getThumbnail(imageView.getWidth(), imageView.getHeight())); + imageView.setImageDrawable(getThumbnail(imageView.getWidth(), imageView.getHeight())); } public boolean hasImage() { diff --git a/src/org/thoughtcrime/securesms/mms/VideoSlide.java b/src/org/thoughtcrime/securesms/mms/VideoSlide.java index dfece90c48..5596cd90b5 100644 --- a/src/org/thoughtcrime/securesms/mms/VideoSlide.java +++ b/src/org/thoughtcrime/securesms/mms/VideoSlide.java @@ -26,6 +26,7 @@ import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.MediaStore; import android.util.Log; @@ -42,8 +43,8 @@ public class VideoSlide extends Slide { } @Override - public Bitmap getThumbnail(int width, int height) { - return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher_video_player); + public Drawable getThumbnail(int width, int height) { + return context.getResources().getDrawable(R.drawable.ic_launcher_video_player); } @Override diff --git a/src/org/thoughtcrime/securesms/service/MmsReceiver.java b/src/org/thoughtcrime/securesms/service/MmsReceiver.java index 871fdbae0d..e51cea39da 100644 --- a/src/org/thoughtcrime/securesms/service/MmsReceiver.java +++ b/src/org/thoughtcrime/securesms/service/MmsReceiver.java @@ -48,14 +48,8 @@ public class MmsReceiver { } public void process(MasterSecret masterSecret, Intent intent) { - try { - if (intent.getAction().equals(SendReceiveService.RECEIVE_MMS_ACTION)) { - handleMmsNotification(intent); - } else if (intent.getAction().equals(SendReceiveService.RECEIVE_PUSH_MMS_ACTION)) { - handlePushMedia(masterSecret, intent); - } - } catch (MmsException e) { - Log.w("MmsReceiver", e); + if (intent.getAction().equals(SendReceiveService.RECEIVE_MMS_ACTION)) { + handleMmsNotification(intent); } } @@ -73,28 +67,6 @@ public class MmsReceiver { } } - private void handlePushMedia(MasterSecret masterSecret, Intent intent) throws MmsException { - IncomingPushMessage pushMessage = intent.getParcelableExtra("media_message"); - String localNumber = TextSecurePreferences.getLocalNumber(context); - String password = TextSecurePreferences.getPushServerPassword(context); - PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); - - try { - List> attachments = socket.retrieveAttachments(pushMessage.getAttachments()); - IncomingMediaMessage message = new IncomingMediaMessage(localNumber, pushMessage, attachments); - - DatabaseFactory.getMmsDatabase(context).insertMessageInbox(masterSecret, message, "", -1); - } catch (IOException e) { - Log.w("MmsReceiver", e); - try { - IncomingMediaMessage message = new IncomingMediaMessage(localNumber, pushMessage, null); - DatabaseFactory.getMmsDatabase(context).insertMessageInbox(masterSecret, message, "", -1); - } catch (IOException e1) { - throw new MmsException(e1); - } - } - } - private void scheduleDownload(NotificationInd pdu, long messageId, long threadId) { Intent intent = new Intent(SendReceiveService.DOWNLOAD_MMS_ACTION, null, context, SendReceiveService.class); intent.putExtra("content_location", new String(pdu.getContentLocation())); diff --git a/src/org/thoughtcrime/securesms/service/PushDownloader.java b/src/org/thoughtcrime/securesms/service/PushDownloader.java new file mode 100644 index 0000000000..506a74c62c --- /dev/null +++ b/src/org/thoughtcrime/securesms/service/PushDownloader.java @@ -0,0 +1,107 @@ +package org.thoughtcrime.securesms.service; + + +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.util.Pair; + +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.EncryptingPartDatabase; +import org.thoughtcrime.securesms.database.PartDatabase; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.textsecure.crypto.AttachmentCipherInputStream; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.MasterCipher; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.push.PushServiceSocket; +import org.whispersystems.textsecure.util.Base64; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import ws.com.google.android.mms.MmsException; +import ws.com.google.android.mms.pdu.PduPart; + +public class PushDownloader { + + private final Context context; + + public PushDownloader(Context context) { + this.context = context.getApplicationContext(); + } + + public void process(MasterSecret masterSecret, Intent intent) { + if (!intent.getAction().equals(SendReceiveService.DOWNLOAD_PUSH_ACTION)) + return; + + long messageId = intent.getLongExtra("message_id", -1); + PartDatabase database = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret); + + Log.w("PushDownloader", "Downloading push parts for: " + messageId); + + if (messageId != -1) { + List> parts = database.getParts(messageId, false); + + for (Pair partPair : parts) { + retrievePart(masterSecret, partPair.second, messageId, partPair.first); + Log.w("PushDownloader", "Got part: " + partPair.first); + } + } else { + List>> parts = database.getPushPendingParts(); + + for (Pair> partPair : parts) { + retrievePart(masterSecret, partPair.second.second, partPair.first, partPair.second.first); + Log.w("PushDownloader", "Got part: " + partPair.second.first); + } + } + } + + private void retrievePart(MasterSecret masterSecret, PduPart part, long messageId, long partId) { + EncryptingPartDatabase database = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret); + File attachmentFile = null; + + try { + MasterCipher masterCipher = new MasterCipher(masterSecret); + long contentLocation = Long.parseLong(Util.toIsoString(part.getContentLocation())); + byte[] key = masterCipher.decryptBytes(Base64.decode(Util.toIsoString(part.getContentDisposition()))); + + attachmentFile = downloadAttachment(contentLocation); + InputStream attachmentInput = new AttachmentCipherInputStream(attachmentFile, key); + + database.updateDownloadedPart(messageId, partId, part, attachmentInput); + } catch (InvalidMessageException e) { + Log.w("PushDownloader", e); + try { + database.updateFailedDownloadedPart(messageId, partId, part); + } catch (MmsException mme) { + Log.w("PushDownloader", mme); + } + } catch (MmsException e) { + Log.w("PushDownloader", e); + try { + database.updateFailedDownloadedPart(messageId, partId, part); + } catch (MmsException mme) { + Log.w("PushDownloader", mme); + } + } catch (IOException e) { + Log.w("PushDownloader", e); + /// XXX schedule some kind of soft failure retry action + } finally { + if (attachmentFile != null) + attachmentFile.delete(); + } + } + + private File downloadAttachment(long contentLocation) throws IOException { + String localNumber = TextSecurePreferences.getLocalNumber(context); + String password = TextSecurePreferences.getPushServerPassword(context); + PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); + + return socket.retrieveAttachment(contentLocation); + } + +} diff --git a/src/org/thoughtcrime/securesms/service/PushReceiver.java b/src/org/thoughtcrime/securesms/service/PushReceiver.java new file mode 100644 index 0000000000..4750615d20 --- /dev/null +++ b/src/org/thoughtcrime/securesms/service/PushReceiver.java @@ -0,0 +1,211 @@ +package org.thoughtcrime.securesms.service; + +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.util.Pair; + +import com.google.protobuf.InvalidProtocolBufferException; +import org.thoughtcrime.securesms.crypto.DecryptingQueue; +import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; +import org.thoughtcrime.securesms.database.MmsDatabase; +import org.thoughtcrime.securesms.mms.IncomingMediaMessage; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; +import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; +import org.thoughtcrime.securesms.sms.IncomingTextMessage; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidVersionException; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; +import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; +import org.whispersystems.textsecure.storage.InvalidKeyIdException; + +import ws.com.google.android.mms.MmsException; + +public class PushReceiver { + + public static final int RESULT_OK = 0; + public static final int RESULT_NO_SESSION = 1; + public static final int RESULT_DECRYPT_FAILED = 2; + + private final Context context; + + public PushReceiver(Context context) { + this.context = context.getApplicationContext(); + } + + public void process(MasterSecret masterSecret, Intent intent) { + if (intent.getAction().equals(SendReceiveService.RECEIVE_PUSH_ACTION)) { + handleMessage(masterSecret, intent); + } else if (intent.getAction().equals(SendReceiveService.DECRYPTED_PUSH_ACTION)) { + handleDecrypt(masterSecret, intent); + } + } + + private void handleDecrypt(MasterSecret masterSecret, Intent intent) { + IncomingPushMessage message = intent.getParcelableExtra("message"); + long messageId = intent.getLongExtra("message_id", -1); + int result = intent.getIntExtra("result", 0); + + if (result == RESULT_OK) handleReceivedMessage(masterSecret, message, true); + else if (result == RESULT_NO_SESSION) handleReceivedMessageForNoSession(masterSecret, message); + else if (result == RESULT_DECRYPT_FAILED) handleReceivedCorruptedMessage(masterSecret, message, true); + + DatabaseFactory.getPushDatabase(context).delete(messageId); + } + + private void handleMessage(MasterSecret masterSecret, Intent intent) { + IncomingPushMessage message = intent.getExtras().getParcelable("message"); + + if (message.isSecureMessage()) handleReceivedSecureMessage(masterSecret, message); + else if (message.isPreKeyBundle()) handleReceivedPreKeyBundle(masterSecret, message); + else handleReceivedMessage(masterSecret, message, false); + } + + private void handleReceivedSecureMessage(MasterSecret masterSecret, IncomingPushMessage message) { + long id = DatabaseFactory.getPushDatabase(context).insert(message); + DecryptingQueue.scheduleDecryption(context, masterSecret, id, message); + } + + private void handleReceivedPreKeyBundle(MasterSecret masterSecret, IncomingPushMessage message) { + try { + Recipient recipient = new Recipient(null, message.getSource(), null, null); + KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); + PreKeyBundleMessage preKeyExchange = new PreKeyBundleMessage(message.getBody()); + + if (processor.isTrusted(preKeyExchange)) { + processor.processKeyExchangeMessage(preKeyExchange); + + IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getBundledMessage()); + handleReceivedSecureMessage(masterSecret, bundledMessage); + } else { + /// XXX + } + } catch (InvalidKeyException e) { + Log.w("SmsReceiver", e); + handleReceivedCorruptedKey(masterSecret, message, false); + } catch (InvalidVersionException e) { + Log.w("SmsReceiver", e); + handleReceivedCorruptedKey(masterSecret, message, true); + } catch (InvalidKeyIdException e) { + Log.w("SmsReceiver", e); + handleReceivedCorruptedKey(masterSecret, message, false); + } + } + + private void handleReceivedMessage(MasterSecret masterSecret, + IncomingPushMessage message, + boolean secure) + { + try { + PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody()); + + if (messageContent.getAttachmentsCount() > 0) { + Log.w("PushReceiver", "Received push media message..."); + handleReceivedMediaMessage(masterSecret, message, messageContent, secure); + } else { + Log.w("PushReceiver", "Received push text message..."); + handleReceivedTextMessage(masterSecret, message, messageContent, secure); + } + } catch (InvalidProtocolBufferException e) { + Log.w("PushReceiver", e); + handleReceivedCorruptedMessage(masterSecret, message, secure); + } + } + + private void handleReceivedMediaMessage(MasterSecret masterSecret, + IncomingPushMessage message, + PushMessageContent messageContent, + boolean secure) + { + + try { + String localNumber = TextSecurePreferences.getLocalNumber(context); + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, localNumber, + message, messageContent); + + Pair messageAndThreadId; + + if (secure) { + messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1); + } else { + messageAndThreadId = database.insertMessageInbox(masterSecret, mediaMessage, null, -1); + } + + Intent intent = new Intent(context, SendReceiveService.class); + intent.setAction(SendReceiveService.DOWNLOAD_PUSH_ACTION); + intent.putExtra("message_id", messageAndThreadId.first); + context.startService(intent); + + } catch (MmsException e) { + Log.w("PushReceiver", e); + // XXX + } + } + + private void handleReceivedTextMessage(MasterSecret masterSecret, + IncomingPushMessage message, + PushMessageContent messageContent, + boolean secure) + { + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + IncomingTextMessage textMessage = new IncomingTextMessage(message, ""); + + if (secure) { + textMessage = new IncomingEncryptedMessage(textMessage, ""); + } + + Pair messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage); + database.updateMessageBody(masterSecret, messageAndThreadId.first, messageContent.getBody()); + } + + private void handleReceivedCorruptedMessage(MasterSecret masterSecret, + IncomingPushMessage message, + boolean secure) + { + long messageId = insertMessagePlaceholder(masterSecret, message, secure); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptFailed(messageId); + } + + private void handleReceivedCorruptedKey(MasterSecret masterSecret, + IncomingPushMessage message, + boolean invalidVersion) + { + IncomingTextMessage corruptedMessage = new IncomingTextMessage(message, ""); + IncomingKeyExchangeMessage corruptedKeyMessage = new IncomingKeyExchangeMessage(corruptedMessage, ""); + + if (!invalidVersion) corruptedKeyMessage.setCorrupted(true); + else corruptedKeyMessage.setInvalidVersion(true); + + DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, corruptedKeyMessage); + } + + private void handleReceivedMessageForNoSession(MasterSecret masterSecret, + IncomingPushMessage message) + { + long messageId = insertMessagePlaceholder(masterSecret, message, true); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsNoSession(messageId); + } + + private long insertMessagePlaceholder(MasterSecret masterSecret, + IncomingPushMessage message, + boolean secure) + { + IncomingTextMessage placeholder = new IncomingTextMessage(message, ""); + + if (secure) { + placeholder = new IncomingEncryptedMessage(placeholder, ""); + } + + Pair messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context) + .insertMessageInbox(masterSecret, + placeholder); + return messageAndThreadId.first; + } +} diff --git a/src/org/thoughtcrime/securesms/service/SendReceiveService.java b/src/org/thoughtcrime/securesms/service/SendReceiveService.java index 28573124d8..c1759b3cfc 100644 --- a/src/org/thoughtcrime/securesms/service/SendReceiveService.java +++ b/src/org/thoughtcrime/securesms/service/SendReceiveService.java @@ -51,10 +51,12 @@ public class SendReceiveService extends Service { public static final String RECEIVE_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_SMS_ACTION"; public static final String SEND_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_MMS_ACTION"; public static final String RECEIVE_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_MMS_ACTION"; - public static final String RECEIVE_PUSH_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_PUSH_MMS_ACTION"; public static final String DOWNLOAD_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_ACTION"; public static final String DOWNLOAD_MMS_CONNECTIVITY_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION"; public static final String DOWNLOAD_MMS_PENDING_APN_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_PENDING_APN_ACTION"; + public static final String RECEIVE_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_PUSH_ACTION"; + public static final String DECRYPTED_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DECRYPTED_PUSH_ACTION"; + public static final String DOWNLOAD_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_PUSH_ACTION"; private static final int SEND_SMS = 0; private static final int RECEIVE_SMS = 1; @@ -62,14 +64,18 @@ public class SendReceiveService extends Service { private static final int RECEIVE_MMS = 3; private static final int DOWNLOAD_MMS = 4; private static final int DOWNLOAD_MMS_PENDING = 5; + private static final int RECEIVE_PUSH = 6; + private static final int DOWNLOAD_PUSH = 7; private ToastHandler toastHandler; - private SmsReceiver smsReceiver; - private SmsSender smsSender; - private MmsReceiver mmsReceiver; - private MmsSender mmsSender; - private MmsDownloader mmsDownloader; + private SmsReceiver smsReceiver; + private SmsSender smsSender; + private MmsReceiver mmsReceiver; + private MmsSender mmsSender; + private MmsDownloader mmsDownloader; + private PushReceiver pushReceiver; + private PushDownloader pushDownloader; private MasterSecret masterSecret; private boolean hasSecret; @@ -78,7 +84,6 @@ public class SendReceiveService extends Service { private ClearKeyReceiver clearKeyReceiver; private List workQueue; private List pendingSecretList; - private Thread workerThread; @Override public void onCreate() { @@ -105,12 +110,18 @@ public class SendReceiveService extends Service { scheduleIntent(SEND_SMS, intent); else if (action.equals(SEND_MMS_ACTION)) scheduleSecretRequiredIntent(SEND_MMS, intent); - else if (action.equals(RECEIVE_MMS_ACTION) || action.equals(RECEIVE_PUSH_MMS_ACTION)) + else if (action.equals(RECEIVE_MMS_ACTION)) scheduleIntent(RECEIVE_MMS, intent); else if (action.equals(DOWNLOAD_MMS_ACTION)) scheduleSecretRequiredIntent(DOWNLOAD_MMS, intent); else if (intent.getAction().equals(DOWNLOAD_MMS_PENDING_APN_ACTION)) scheduleSecretRequiredIntent(DOWNLOAD_MMS_PENDING, intent); + else if (action.equals(RECEIVE_PUSH_ACTION)) + scheduleIntent(RECEIVE_PUSH, intent); + else if (action.equals(DECRYPTED_PUSH_ACTION)) + scheduleSecretRequiredIntent(RECEIVE_PUSH, intent); + else if (action.equals(DOWNLOAD_PUSH_ACTION)) + scheduleSecretRequiredIntent(DOWNLOAD_PUSH, intent); else Log.w("SendReceiveService", "Received intent with unknown action: " + intent.getAction()); } @@ -142,13 +153,15 @@ public class SendReceiveService extends Service { mmsReceiver = new MmsReceiver(this); mmsSender = new MmsSender(this, toastHandler); mmsDownloader = new MmsDownloader(this, toastHandler); + pushReceiver = new PushReceiver(this); + pushDownloader = new PushDownloader(this); } private void initializeWorkQueue() { pendingSecretList = new LinkedList(); workQueue = new LinkedList(); - workerThread = new WorkerThread(workQueue, "SendReceveService-WorkerThread"); + Thread workerThread = new WorkerThread(workQueue, "SendReceveService-WorkerThread"); workerThread.start(); } @@ -222,12 +235,14 @@ public class SendReceiveService extends Service { @Override public void run() { switch (what) { - case RECEIVE_SMS: smsReceiver.process(masterSecret, intent); return; - case SEND_SMS: smsSender.process(masterSecret, intent); return; - case RECEIVE_MMS: mmsReceiver.process(masterSecret, intent); return; - case SEND_MMS: mmsSender.process(masterSecret, intent); return; - case DOWNLOAD_MMS: mmsDownloader.process(masterSecret, intent); return; + case RECEIVE_SMS: smsReceiver.process(masterSecret, intent); return; + case SEND_SMS: smsSender.process(masterSecret, intent); return; + case RECEIVE_MMS: mmsReceiver.process(masterSecret, intent); return; + case SEND_MMS: mmsSender.process(masterSecret, intent); return; + case DOWNLOAD_MMS: mmsDownloader.process(masterSecret, intent); return; case DOWNLOAD_MMS_PENDING: mmsDownloader.process(masterSecret, intent); return; + case RECEIVE_PUSH: pushReceiver.process(masterSecret, intent); return; + case DOWNLOAD_PUSH: pushDownloader.process(masterSecret, intent); return; } } } diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index 4f6623cf98..8fbb04e7d4 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -4,11 +4,18 @@ import android.content.Context; import android.util.Log; import android.util.Pair; +import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.mms.PartParser; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.RecipientFormattingException; +import org.thoughtcrime.securesms.recipients.Recipients; +import org.whispersystems.textsecure.crypto.AttachmentCipher; +import org.whispersystems.textsecure.push.PushAttachmentPointer; +import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; import org.whispersystems.textsecure.push.RawTransportDetails; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; @@ -24,6 +31,7 @@ import org.whispersystems.textsecure.push.PushAttachmentData; import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushTransportDetails; import org.whispersystems.textsecure.push.RateLimitException; +import org.whispersystems.textsecure.util.Hex; import org.whispersystems.textsecure.util.PhoneNumberFormatter; import java.io.IOException; @@ -50,10 +58,12 @@ public class PushTransport extends BaseTransport { String password = TextSecurePreferences.getPushServerPassword(context); PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); - Recipient recipient = message.getIndividualRecipient(); - String plaintext = message.getBody().getBody(); - String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(), - localNumber); + Recipient recipient = message.getIndividualRecipient(); + String plaintextBody = message.getBody().getBody(); + PushMessageContent.Builder builder = PushMessageContent.newBuilder(); + byte[] plaintext = builder.setBody(plaintextBody).build().toByteArray(); + String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(), + localNumber); Pair typeAndCiphertext = getEncryptedMessage(socket, recipient, recipientCanonicalNumber, plaintext); @@ -68,39 +78,70 @@ public class PushTransport extends BaseTransport { public void deliver(SendReq message, List destinations) throws IOException { try { - String localNumber = TextSecurePreferences.getLocalNumber(context); - String password = TextSecurePreferences.getPushServerPassword(context); - PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); - byte[] messageText = PartParser.getMessageText(message.getBody()).getBytes(); - List attachments = getAttachmentsFromBody(message.getBody()); + String localNumber = TextSecurePreferences.getLocalNumber(context); + String password = TextSecurePreferences.getPushServerPassword(context); + PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); + String messageBody = PartParser.getMessageText(message.getBody()); + List ciphertext = new LinkedList (); + List types = new LinkedList(); - List messagesList = new LinkedList(); - List> attachmentsList = new LinkedList>(); + for (String destination : destinations) { + Recipients recipients = RecipientFactory.getRecipientsFromString(context, destination, false); + List attachments = getPushAttachmentPointers(socket, message.getBody()); + PushMessageContent.Builder builder = PushMessageContent.newBuilder(); - for (String recipient : destinations) { - messagesList.add(messageText); - attachmentsList.add(attachments); + if (messageBody != null) { + builder.setBody(messageBody); + } + + for (PushAttachmentPointer attachment : attachments) { + PushMessageContent.AttachmentPointer.Builder attachmentBuilder = + PushMessageContent.AttachmentPointer.newBuilder(); + + attachmentBuilder.setId(attachment.getId()); + attachmentBuilder.setContentType(attachment.getContentType()); + attachmentBuilder.setKey(ByteString.copyFrom(attachment.getKey())); + + builder.addAttachments(attachmentBuilder.build()); + } + + byte[] plaintext = builder.build().toByteArray(); + Pair typeAndCiphertext = getEncryptedMessage(socket, recipients.getPrimaryRecipient(), + destination, plaintext); + + types.add(typeAndCiphertext.first); + ciphertext.add(typeAndCiphertext.second); } - socket.sendMessage(destinations, messagesList, attachmentsList, - OutgoingPushMessage.TYPE_MESSAGE_PLAINTEXT); + socket.sendMessage(destinations, ciphertext, types); + } catch (RateLimitException e) { Log.w("PushTransport", e); throw new IOException("Rate limit exceeded."); + } catch (RecipientFormattingException e) { + Log.w("PushTransport", e); + throw new IOException("Bad destination!"); } } - private List getAttachmentsFromBody(PduBody body) { - List attachments = new LinkedList(); + private List getPushAttachmentPointers(PushServiceSocket socket, PduBody body) + throws IOException + { + List attachments = new LinkedList(); for (int i=0;i getEncryptedMessage(PushServiceSocket socket, Recipient recipient, - String canonicalRecipientNumber, String plaintext) + String canonicalRecipientNumber, byte[] plaintext) throws IOException { if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) { @@ -127,13 +168,13 @@ public class PushTransport extends BaseTransport { } private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient, - String plaintext) + byte[] plaintext) { IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); IdentityKey identityKey = identityKeyPair.getPublicKey(); MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new RawTransportDetails()); - byte[] bundledMessage = message.encrypt(recipient, plaintext.getBytes()); + byte[] bundledMessage = message.encrypt(recipient, plaintext); PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage); return preKeyBundleMessage.serialize(); @@ -142,7 +183,7 @@ public class PushTransport extends BaseTransport { private byte[] getEncryptedPrekeyBundleMessageForNewSession(PushServiceSocket socket, Recipient recipient, String canonicalRecipientNumber, - String plaintext) + byte[] plaintext) throws IOException { IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); @@ -153,20 +194,20 @@ public class PushTransport extends BaseTransport { processor.processKeyExchangeMessage(preKey); MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new RawTransportDetails()); - byte[] bundledMessage = message.encrypt(recipient, plaintext.getBytes()); + byte[] bundledMessage = message.encrypt(recipient, plaintext); PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage); return preKeyBundleMessage.serialize(); } - private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext) + private byte[] getEncryptedMessageForExistingSession(Recipient recipient, byte[] plaintext) throws IOException { IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair, new PushTransportDetails()); - return messageCipher.encrypt(recipient, plaintext.getBytes()); + return messageCipher.encrypt(recipient, plaintext); } } diff --git a/src/ws/com/google/android/mms/pdu/PduBody.java b/src/ws/com/google/android/mms/pdu/PduBody.java index 1947dbe7d1..9cb5cb17b0 100644 --- a/src/ws/com/google/android/mms/pdu/PduBody.java +++ b/src/ws/com/google/android/mms/pdu/PduBody.java @@ -41,6 +41,16 @@ public class PduBody { mPartMapByFileName = new HashMap(); } + public boolean containsPushInProgress() { + for (int i=0;i