From d3271f548c7b5e3d7866268c4976f6f4a73beef7 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Sun, 25 Jan 2015 17:43:24 -0800 Subject: [PATCH] Support for retrieving stored messages via websocket. 1) When registering with server, indicate that the server should store messages and send notifications. 2) Process notification GCM messages, and connect to the server to retrieve actual message content. --- AndroidManifest.xml | 1 + libtextsecure/build.gradle | 1 + libtextsecure/protobuf/Makefile | 2 +- .../protobuf/WebSocketResources.proto | 46 + .../api/TextSecureAccountManager.java | 3 +- .../textsecure/api/TextSecureMessagePipe.java | 87 + .../api/TextSecureMessageReceiver.java | 31 +- .../api/TextSecureMessageSender.java | 3 +- .../api/messages/TextSecureEnvelope.java | 6 +- .../api/util/CredentialsProvider.java | 7 + .../internal/push/PushServiceSocket.java | 62 +- .../util/BlacklistingTrustManager.java | 24 + .../util/StaticCredentialsProvider.java | 31 + .../textsecure/internal/util/Util.java | 16 + .../websocket/WebSocketConnection.java | 292 ++ .../internal/websocket/WebSocketProtos.java | 2471 +++++++++++++++++ .../securesms/PassphraseRequiredMixin.java | 3 + .../TextSecureCommunicationModule.java | 36 +- .../securesms/gcm/GcmBroadcastReceiver.java | 6 + .../securesms/jobs/AvatarDownloadJob.java | 6 +- .../securesms/jobs/PushDecryptJob.java | 8 +- .../securesms/jobs/PushReceiveJob.java | 39 +- .../service/MessageRetrievalService.java | 180 ++ .../service/RegistrationService.java | 4 +- .../securesms/util/TextSecurePreferences.java | 9 + 25 files changed, 3300 insertions(+), 74 deletions(-) create mode 100644 libtextsecure/protobuf/WebSocketResources.proto create mode 100644 libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessagePipe.java create mode 100644 libtextsecure/src/main/java/org/whispersystems/textsecure/api/util/CredentialsProvider.java create mode 100644 libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/StaticCredentialsProvider.java create mode 100644 libtextsecure/src/main/java/org/whispersystems/textsecure/internal/websocket/WebSocketConnection.java create mode 100644 libtextsecure/src/main/java/org/whispersystems/textsecure/internal/websocket/WebSocketProtos.java create mode 100644 src/org/thoughtcrime/securesms/service/MessageRetrievalService.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5e368ad096..08fe5c696e 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -229,6 +229,7 @@ + . + */ +package textsecure; + +option java_package = "org.whispersystems.textsecure.internal.websocket"; +option java_outer_classname = "WebSocketProtos"; + +message WebSocketRequestMessage { + optional string verb = 1; + optional string path = 2; + optional bytes body = 3; + optional uint64 id = 4; +} + +message WebSocketResponseMessage { + optional uint64 id = 1; + optional uint32 status = 2; + optional string message = 3; + optional bytes body = 4; +} + +message WebSocketMessage { + enum Type { + UNKNOWN = 0; + REQUEST = 1; + RESPONSE = 2; + } + + optional Type type = 1; + optional WebSocketRequestMessage request = 2; + optional WebSocketResponseMessage response = 3; +} \ No newline at end of file diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java index acd27015f0..4d2afb37ef 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java @@ -30,6 +30,7 @@ import org.whispersystems.textsecure.api.push.SignedPreKeyEntity; import org.whispersystems.textsecure.api.push.TrustStore; import org.whispersystems.textsecure.internal.crypto.ProvisioningCipher; import org.whispersystems.textsecure.internal.push.PushServiceSocket; +import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider; import java.io.IOException; import java.util.List; @@ -45,7 +46,7 @@ public class TextSecureAccountManager { public TextSecureAccountManager(String url, TrustStore trustStore, String user, String password) { - this.pushServiceSocket = new PushServiceSocket(url, trustStore, user, password); + this.pushServiceSocket = new PushServiceSocket(url, trustStore, new StaticCredentialsProvider(user, password, null)); this.user = user; } diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessagePipe.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessagePipe.java new file mode 100644 index 0000000000..64a7f7408c --- /dev/null +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessagePipe.java @@ -0,0 +1,87 @@ +package org.whispersystems.textsecure.api; + +import org.whispersystems.libaxolotl.InvalidVersionException; +import org.whispersystems.textsecure.api.messages.TextSecureEnvelope; +import org.whispersystems.textsecure.api.util.CredentialsProvider; +import org.whispersystems.textsecure.internal.websocket.WebSocketConnection; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage; +import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage; + +public class TextSecureMessagePipe { + + private final WebSocketConnection websocket; + private final CredentialsProvider credentialsProvider; + + public TextSecureMessagePipe(WebSocketConnection websocket, CredentialsProvider credentialsProvider) { + this.websocket = websocket; + this.credentialsProvider = credentialsProvider; + + this.websocket.connect(); + } + + public TextSecureEnvelope read(long timeout, TimeUnit unit) + throws InvalidVersionException, IOException, TimeoutException + { + return read(timeout, unit, new NullMessagePipeCallback()); + } + + public TextSecureEnvelope read(long timeout, TimeUnit unit, MessagePipeCallback callback) + throws TimeoutException, IOException, InvalidVersionException + { + while (true) { + WebSocketRequestMessage request = websocket.readRequest(unit.toMillis(timeout)); + WebSocketResponseMessage response = createWebSocketResponse(request); + + try { + if (isTextSecureEnvelope(request)) { + TextSecureEnvelope envelope = new TextSecureEnvelope(request.getBody().toByteArray(), + credentialsProvider.getSignalingKey()); + + callback.onMessage(envelope); + return envelope; + } + } finally { + websocket.sendResponse(response); + } + } + } + + public void shutdown() throws IOException { + websocket.disconnect(); + } + + private boolean isTextSecureEnvelope(WebSocketRequestMessage message) { + return "PUT".equals(message.getVerb()) && "/api/v1/message".equals(message.getPath()); + } + + private WebSocketResponseMessage createWebSocketResponse(WebSocketRequestMessage request) { + if (isTextSecureEnvelope(request)) { + return WebSocketResponseMessage.newBuilder() + .setId(request.getId()) + .setStatus(200) + .setMessage("OK") + .build(); + } else { + return WebSocketResponseMessage.newBuilder() + .setId(request.getId()) + .setStatus(400) + .setMessage("Unknown") + .build(); + } + } + + public static interface MessagePipeCallback { + public void onMessage(TextSecureEnvelope envelope); + } + + private static class NullMessagePipeCallback implements MessagePipeCallback { + @Override + public void onMessage(TextSecureEnvelope envelope) {} + } + +} diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java index 221a32e27b..a7e30be72a 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java @@ -17,23 +17,43 @@ package org.whispersystems.textsecure.api; import org.whispersystems.libaxolotl.InvalidMessageException; +import org.whispersystems.libaxolotl.InvalidVersionException; import org.whispersystems.textsecure.api.crypto.AttachmentCipherInputStream; import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer; +import org.whispersystems.textsecure.api.messages.TextSecureEnvelope; import org.whispersystems.textsecure.api.push.TrustStore; +import org.whispersystems.textsecure.api.util.CredentialsProvider; import org.whispersystems.textsecure.internal.push.PushServiceSocket; +import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider; +import org.whispersystems.textsecure.internal.websocket.WebSocketConnection; +import org.whispersystems.textsecure.internal.websocket.WebSocketProtos; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage; public class TextSecureMessageReceiver { - private final PushServiceSocket socket; + private final PushServiceSocket socket; + private final TrustStore trustStore; + private final String url; + private final CredentialsProvider credentialsProvider; public TextSecureMessageReceiver(String url, TrustStore trustStore, - String user, String password) + String user, String password, String signalingKey) { - this.socket = new PushServiceSocket(url, trustStore, user, password); + this(url, trustStore, new StaticCredentialsProvider(user, password, signalingKey)); + } + + public TextSecureMessageReceiver(String url, TrustStore trustStore, CredentialsProvider credentials) { + this.url = url; + this.trustStore = trustStore; + this.credentialsProvider = credentials; + this.socket = new PushServiceSocket(url, trustStore, credentials); } public InputStream retrieveAttachment(TextSecureAttachmentPointer pointer, File destination) @@ -43,4 +63,9 @@ public class TextSecureMessageReceiver { return new AttachmentCipherInputStream(destination, pointer.getKey()); } + public TextSecureMessagePipe createMessagePipe() { + WebSocketConnection webSocket = new WebSocketConnection(url, trustStore, credentialsProvider); + return new TextSecureMessagePipe(webSocket, credentialsProvider); + } + } diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java index c033e94dd3..1fed694abd 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java @@ -47,6 +47,7 @@ import org.whispersystems.textsecure.api.push.exceptions.UnregisteredUserExcepti import org.whispersystems.textsecure.api.push.exceptions.EncapsulatedExceptions; import org.whispersystems.textsecure.internal.push.exceptions.MismatchedDevicesException; import org.whispersystems.textsecure.internal.push.exceptions.StaleDevicesException; +import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider; import org.whispersystems.textsecure.internal.util.Util; import java.io.IOException; @@ -72,7 +73,7 @@ public class TextSecureMessageSender { long userId, AxolotlStore store, Optional eventListener) { - this.socket = new PushServiceSocket(url, trustStore, user, password); + this.socket = new PushServiceSocket(url, trustStore, new StaticCredentialsProvider(user, password, null)); this.store = store; this.syncAddress = new PushAddress(userId, user, null); this.eventListener = eventListener; diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureEnvelope.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureEnvelope.java index a796f1a062..09876d00fc 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureEnvelope.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureEnvelope.java @@ -59,8 +59,12 @@ public class TextSecureEnvelope { public TextSecureEnvelope(String message, String signalingKey) throws IOException, InvalidVersionException { - byte[] ciphertext = Base64.decode(message); + this(Base64.decode(message), signalingKey); + } + public TextSecureEnvelope(byte[] ciphertext, String signalingKey) + throws InvalidVersionException, IOException + { if (ciphertext.length < VERSION_LENGTH || ciphertext[VERSION_OFFSET] != SUPPORTED_VERSION) throw new InvalidVersionException("Unsupported version!"); diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/util/CredentialsProvider.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/util/CredentialsProvider.java new file mode 100644 index 0000000000..baa745adee --- /dev/null +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/util/CredentialsProvider.java @@ -0,0 +1,7 @@ +package org.whispersystems.textsecure.api.util; + +public interface CredentialsProvider { + public String getUser(); + public String getPassword(); + public String getSignalingKey(); +} diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java index 142dad4955..b303e99415 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java @@ -27,23 +27,24 @@ import org.whispersystems.libaxolotl.ecc.ECPublicKey; import org.whispersystems.libaxolotl.state.PreKeyBundle; import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; -import org.whispersystems.textsecure.api.push.PushAddress; import org.whispersystems.textsecure.api.crypto.AttachmentCipherOutputStream; import org.whispersystems.textsecure.api.push.ContactTokenDetails; +import org.whispersystems.textsecure.api.push.PushAddress; import org.whispersystems.textsecure.api.push.SignedPreKeyEntity; import org.whispersystems.textsecure.api.push.TrustStore; -import org.whispersystems.textsecure.api.push.exceptions.UnregisteredUserException; -import org.whispersystems.textsecure.internal.push.exceptions.MismatchedDevicesException; -import org.whispersystems.textsecure.internal.push.exceptions.StaleDevicesException; -import org.whispersystems.textsecure.internal.util.Base64; -import org.whispersystems.textsecure.internal.util.Util; import org.whispersystems.textsecure.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.textsecure.api.push.exceptions.ExpectationFailedException; import org.whispersystems.textsecure.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.textsecure.api.push.exceptions.NotFoundException; import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException; import org.whispersystems.textsecure.api.push.exceptions.RateLimitException; +import org.whispersystems.textsecure.api.push.exceptions.UnregisteredUserException; +import org.whispersystems.textsecure.api.util.CredentialsProvider; +import org.whispersystems.textsecure.internal.push.exceptions.MismatchedDevicesException; +import org.whispersystems.textsecure.internal.push.exceptions.StaleDevicesException; +import org.whispersystems.textsecure.internal.util.Base64; import org.whispersystems.textsecure.internal.util.BlacklistingTrustManager; +import org.whispersystems.textsecure.internal.util.Util; import java.io.File; import java.io.FileOutputStream; @@ -54,10 +55,7 @@ import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -65,7 +63,6 @@ import java.util.Set; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; /** * @@ -96,23 +93,20 @@ public class PushServiceSocket { private static final boolean ENFORCE_SSL = true; - private final String serviceUrl; - private final String localNumber; - private final String password; - private final TrustManager[] trustManagers; + private final String serviceUrl; + private final TrustManager[] trustManagers; + private final CredentialsProvider credentialsProvider; - public PushServiceSocket(String serviceUrl, TrustStore trustStore, - String localNumber, String password) + public PushServiceSocket(String serviceUrl, TrustStore trustStore, CredentialsProvider credentialsProvider) { - this.serviceUrl = serviceUrl; - this.localNumber = localNumber; - this.password = password; - this.trustManagers = initializeTrustManager(trustStore); + this.serviceUrl = serviceUrl; + this.credentialsProvider = credentialsProvider; + this.trustManagers = BlacklistingTrustManager.createFor(trustStore); } public void createAccount(boolean voice) throws IOException { String path = voice ? CREATE_ACCOUNT_VOICE_PATH : CREATE_ACCOUNT_SMS_PATH; - makeRequest(String.format(path, localNumber), "GET", null); + makeRequest(String.format(path, credentialsProvider.getUser()), "GET", null); } public void verifyAccount(String verificationCode, String signalingKey, @@ -145,7 +139,7 @@ public class PushServiceSocket { } public void registerGcmId(String gcmRegistrationId) throws IOException { - GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId); + GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId, true); makeRequest(REGISTER_GCM_PATH, "PUT", new Gson().toJson(registration)); } @@ -510,7 +504,7 @@ public class PushServiceSocket { connection.setRequestMethod(method); connection.setRequestProperty("Content-Type", "application/json"); - if (password != null) { + if (credentialsProvider.getPassword() != null) { connection.setRequestProperty("Authorization", getAuthorizationHeader()); } @@ -539,35 +533,21 @@ public class PushServiceSocket { private String getAuthorizationHeader() { try { - return "Basic " + Base64.encodeBytes((localNumber + ":" + password).getBytes("UTF-8")); + return "Basic " + Base64.encodeBytes((credentialsProvider.getUser() + ":" + credentialsProvider.getPassword()).getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new AssertionError(e); } } - private TrustManager[] initializeTrustManager(TrustStore trustStore) { - try { - InputStream keyStoreInputStream = trustStore.getKeyStoreInputStream(); - KeyStore keyStore = KeyStore.getInstance("BKS"); - - keyStore.load(keyStoreInputStream, trustStore.getKeyStorePassword().toCharArray()); - - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509"); - trustManagerFactory.init(keyStore); - - return BlacklistingTrustManager.createFor(trustManagerFactory.getTrustManagers()); - } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException kse) { - throw new AssertionError(kse); - } - } - private static class GcmRegistrationId { private String gcmRegistrationId; + private boolean webSocketChannel; public GcmRegistrationId() {} - public GcmRegistrationId(String gcmRegistrationId) { + public GcmRegistrationId(String gcmRegistrationId, boolean webSocketChannel) { this.gcmRegistrationId = gcmRegistrationId; + this.webSocketChannel = webSocketChannel; } } diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/BlacklistingTrustManager.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/BlacklistingTrustManager.java index 4c5b47defa..42a5ec92c4 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/BlacklistingTrustManager.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/BlacklistingTrustManager.java @@ -16,13 +16,21 @@ */ package org.whispersystems.textsecure.internal.util; +import org.whispersystems.textsecure.api.push.TrustStore; + +import java.io.IOException; +import java.io.InputStream; import java.math.BigInteger; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.LinkedList; import java.util.List; import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; /** @@ -51,6 +59,22 @@ public class BlacklistingTrustManager implements X509TrustManager { throw new AssertionError("No X509 Trust Managers!"); } + public static TrustManager[] createFor(TrustStore trustStore) { + try { + InputStream keyStoreInputStream = trustStore.getKeyStoreInputStream(); + KeyStore keyStore = KeyStore.getInstance("BKS"); + + keyStore.load(keyStoreInputStream, trustStore.getKeyStorePassword().toCharArray()); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509"); + trustManagerFactory.init(keyStore); + + return BlacklistingTrustManager.createFor(trustManagerFactory.getTrustManagers()); + } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + private final X509TrustManager trustManager; public BlacklistingTrustManager(X509TrustManager trustManager) { diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/StaticCredentialsProvider.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/StaticCredentialsProvider.java new file mode 100644 index 0000000000..281086d2b5 --- /dev/null +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/StaticCredentialsProvider.java @@ -0,0 +1,31 @@ +package org.whispersystems.textsecure.internal.util; + +import org.whispersystems.textsecure.api.util.CredentialsProvider; + +public class StaticCredentialsProvider implements CredentialsProvider { + + private final String user; + private final String password; + private final String signalingKey; + + public StaticCredentialsProvider(String user, String password, String signalingKey) { + this.user = user; + this.password = password; + this.signalingKey = signalingKey; + } + + @Override + public String getUser() { + return user; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getSignalingKey() { + return signalingKey; + } +} diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/Util.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/Util.java index 63eb5b7174..51e89c9a8f 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/Util.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/Util.java @@ -102,4 +102,20 @@ public class Util { out.close(); } + public static void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + } + + public static void wait(Object lock, long millis) { + try { + lock.wait(millis); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + } + } diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/websocket/WebSocketConnection.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/websocket/WebSocketConnection.java new file mode 100644 index 0000000000..87ceb8c300 --- /dev/null +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/websocket/WebSocketConnection.java @@ -0,0 +1,292 @@ +package org.whispersystems.textsecure.internal.websocket; + +import android.util.Log; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; +import com.squareup.okhttp.internal.ws.WebSocket; +import com.squareup.okhttp.internal.ws.WebSocketListener; + +import org.whispersystems.textsecure.api.push.TrustStore; +import org.whispersystems.textsecure.api.util.CredentialsProvider; +import org.whispersystems.textsecure.internal.util.BlacklistingTrustManager; +import org.whispersystems.textsecure.internal.util.Util; + +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.LinkedList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; + +import okio.Buffer; +import okio.BufferedSource; + +import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage; +import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage; +import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage; + +public class WebSocketConnection { + + private static final String TAG = WebSocketConnection.class.getSimpleName(); + + private final LinkedList incomingRequests = new LinkedList<>(); + + private final String wsUri; + private final TrustStore trustStore; + private final CredentialsProvider credentialsProvider; + + private Client client; + private KeepAliveSender keepAliveSender; + + public WebSocketConnection(String httpUri, TrustStore trustStore, CredentialsProvider credentialsProvider) { + this.trustStore = trustStore; + this.credentialsProvider = credentialsProvider; + this.wsUri = httpUri.replace("https://", "wss://") + .replace("http://", "ws://") + "/v1/websocket/?login=%s&password=%s"; + } + + public synchronized void connect() { + Log.w(TAG, "WSC connect()..."); + + if (client == null) { + client = new Client(wsUri, trustStore, credentialsProvider); + client.connect(); + } + } + + public synchronized void disconnect() throws IOException { + Log.w(TAG, "WSC disconnect()..."); + + if (client != null) { + client.disconnect(); + client = null; + } + + if (keepAliveSender != null) { + keepAliveSender.shutdown(); + keepAliveSender = null; + } + } + + public synchronized WebSocketRequestMessage readRequest(long timeoutMillis) + throws TimeoutException, IOException + { + if (client == null) { + throw new IOException("Connection closed!"); + } + + long startTime = System.currentTimeMillis(); + + while (client != null && incomingRequests.isEmpty() && elapsedTime(startTime) < timeoutMillis) { + Util.wait(this, Math.max(1, timeoutMillis - elapsedTime(startTime))); + } + + if (incomingRequests.isEmpty() && client == null) throw new IOException("Connection closed!"); + else if (incomingRequests.isEmpty()) throw new TimeoutException("Timeout exceeded"); + else return incomingRequests.removeFirst(); + } + + public synchronized void sendResponse(WebSocketResponseMessage response) throws IOException { + if (client == null) { + throw new IOException("Connection closed!"); + } + + WebSocketMessage message = WebSocketMessage.newBuilder() + .setType(WebSocketMessage.Type.RESPONSE) + .setResponse(response) + .build(); + + client.sendMessage(message.toByteArray()); + } + + private synchronized void sendKeepAlive() throws IOException { + if (keepAliveSender != null) { + client.sendMessage(WebSocketMessage.newBuilder() + .setType(WebSocketMessage.Type.REQUEST) + .setRequest(WebSocketRequestMessage.newBuilder() + .setId(System.currentTimeMillis()) + .setPath("/v1/keepalive") + .setVerb("GET") + .build()).build() + .toByteArray()); + } + } + + private synchronized void onMessage(byte[] payload) { + Log.w(TAG, "WSC onMessage()"); + try { + WebSocketMessage message = WebSocketMessage.parseFrom(payload); + + Log.w(TAG, "Message Type: " + message.getType().getNumber()); + + if (message.getType().getNumber() == WebSocketMessage.Type.REQUEST_VALUE) { + incomingRequests.add(message.getRequest()); + } + + notifyAll(); + } catch (InvalidProtocolBufferException e) { + Log.w(TAG, e); + } + } + + private synchronized void onClose() { + Log.w(TAG, "onClose()..."); + + if (client != null) { + client = null; + connect(); + } + + if (keepAliveSender != null) { + keepAliveSender.shutdown(); + keepAliveSender = null; + } + + notifyAll(); + } + + private synchronized void onConnected() { + if (client != null) { + keepAliveSender = new KeepAliveSender(); + keepAliveSender.start(); + } + } + + private long elapsedTime(long startTime) { + return System.currentTimeMillis() - startTime; + } + + private class Client implements WebSocketListener { + + private final String uri; + private final TrustStore trustStore; + private final CredentialsProvider credentialsProvider; + + private WebSocket webSocket; + private boolean closed; + + public Client(String uri, TrustStore trustStore, CredentialsProvider credentialsProvider) { + Log.w(TAG, "Connecting to: " + uri); + + this.uri = uri; + this.trustStore = trustStore; + this.credentialsProvider = credentialsProvider; + } + + public void connect() { + new Thread() { + @Override + public void run() { + int attempt = 0; + + while (newSocket()) { + try { + Response response = webSocket.connect(Client.this); + + if (response.code() == 101) { + onConnected(); + return; + } + + Log.w(TAG, "WebSocket Response: " + response.code()); + } catch (IOException e) { + Log.w(TAG, e); + } + + Util.sleep(Math.min(++attempt * 200, TimeUnit.SECONDS.toMillis(15))); + } + } + }.start(); + } + + public synchronized void disconnect() { + Log.w(TAG, "Calling disconnect()..."); + try { + closed = true; + if (webSocket != null) { + webSocket.close(1000, "OK"); + } + } catch (IOException e) { + Log.w(TAG, e); + } + } + + public void sendMessage(byte[] message) throws IOException { + webSocket.sendMessage(WebSocket.PayloadType.BINARY, new Buffer().write(message)); + } + + @Override + public void onMessage(BufferedSource payload, WebSocket.PayloadType type) throws IOException { + Log.w(TAG, "onMessage: " + type); + if (type.equals(WebSocket.PayloadType.BINARY)) { + WebSocketConnection.this.onMessage(payload.readByteArray()); + } + + payload.close(); + } + + @Override + public void onClose(int code, String reason) { + Log.w(TAG, String.format("onClose(%d, %s)", code, reason)); + WebSocketConnection.this.onClose(); + } + + @Override + public void onFailure(IOException e) { + Log.w(TAG, e); + WebSocketConnection.this.onClose(); + } + + private synchronized boolean newSocket() { + if (closed) return false; + + String filledUri = String.format(uri, credentialsProvider.getUser(), credentialsProvider.getPassword()); + SSLSocketFactory socketFactory = createTlsSocketFactory(trustStore); + + this.webSocket = WebSocket.newWebSocket(new OkHttpClient().setSslSocketFactory(socketFactory), + new Request.Builder().url(filledUri).build()); + + return true; + } + + private SSLSocketFactory createTlsSocketFactory(TrustStore trustStore) { + try { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, BlacklistingTrustManager.createFor(trustStore), null); + + return context.getSocketFactory(); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new AssertionError(e); + } + } + } + + private class KeepAliveSender extends Thread { + + private AtomicBoolean stop = new AtomicBoolean(false); + + public void run() { + while (!stop.get()) { + try { + Thread.sleep(TimeUnit.SECONDS.toMillis(15)); + + Log.w(TAG, "Sending keep alive..."); + sendKeepAlive(); + } catch (Throwable e) { + Log.w(TAG, e); + } + } + } + + public void shutdown() { + stop.set(true); + } + } +} diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/websocket/WebSocketProtos.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/websocket/WebSocketProtos.java new file mode 100644 index 0000000000..8e0a0f28f1 --- /dev/null +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/websocket/WebSocketProtos.java @@ -0,0 +1,2471 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: WebSocketResources.proto + +package org.whispersystems.textsecure.internal.websocket; + +public final class WebSocketProtos { + private WebSocketProtos() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + } + public interface WebSocketRequestMessageOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional string verb = 1; + /** + * optional string verb = 1; + */ + boolean hasVerb(); + /** + * optional string verb = 1; + */ + java.lang.String getVerb(); + /** + * optional string verb = 1; + */ + com.google.protobuf.ByteString + getVerbBytes(); + + // optional string path = 2; + /** + * optional string path = 2; + */ + boolean hasPath(); + /** + * optional string path = 2; + */ + java.lang.String getPath(); + /** + * optional string path = 2; + */ + com.google.protobuf.ByteString + getPathBytes(); + + // optional bytes body = 3; + /** + * optional bytes body = 3; + */ + boolean hasBody(); + /** + * optional bytes body = 3; + */ + com.google.protobuf.ByteString getBody(); + + // optional uint64 id = 4; + /** + * optional uint64 id = 4; + */ + boolean hasId(); + /** + * optional uint64 id = 4; + */ + long getId(); + } + /** + * Protobuf type {@code textsecure.WebSocketRequestMessage} + */ + public static final class WebSocketRequestMessage extends + com.google.protobuf.GeneratedMessage + implements WebSocketRequestMessageOrBuilder { + // Use WebSocketRequestMessage.newBuilder() to construct. + private WebSocketRequestMessage(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private WebSocketRequestMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final WebSocketRequestMessage defaultInstance; + public static WebSocketRequestMessage getDefaultInstance() { + return defaultInstance; + } + + public WebSocketRequestMessage getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private WebSocketRequestMessage( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + verb_ = input.readBytes(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + path_ = input.readBytes(); + break; + } + case 26: { + bitField0_ |= 0x00000004; + body_ = input.readBytes(); + break; + } + case 32: { + bitField0_ |= 0x00000008; + id_ = input.readUInt64(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketRequestMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketRequestMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.class, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public WebSocketRequestMessage parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new WebSocketRequestMessage(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // optional string verb = 1; + public static final int VERB_FIELD_NUMBER = 1; + private java.lang.Object verb_; + /** + * optional string verb = 1; + */ + public boolean hasVerb() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string verb = 1; + */ + public java.lang.String getVerb() { + java.lang.Object ref = verb_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + verb_ = s; + } + return s; + } + } + /** + * optional string verb = 1; + */ + public com.google.protobuf.ByteString + getVerbBytes() { + java.lang.Object ref = verb_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + verb_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // optional string path = 2; + public static final int PATH_FIELD_NUMBER = 2; + private java.lang.Object path_; + /** + * optional string path = 2; + */ + public boolean hasPath() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string path = 2; + */ + public java.lang.String getPath() { + java.lang.Object ref = path_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + path_ = s; + } + return s; + } + } + /** + * optional string path = 2; + */ + public com.google.protobuf.ByteString + getPathBytes() { + java.lang.Object ref = path_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + path_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // optional bytes body = 3; + public static final int BODY_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString body_; + /** + * optional bytes body = 3; + */ + public boolean hasBody() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional bytes body = 3; + */ + public com.google.protobuf.ByteString getBody() { + return body_; + } + + // optional uint64 id = 4; + public static final int ID_FIELD_NUMBER = 4; + private long id_; + /** + * optional uint64 id = 4; + */ + public boolean hasId() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional uint64 id = 4; + */ + public long getId() { + return id_; + } + + private void initFields() { + verb_ = ""; + path_ = ""; + body_ = com.google.protobuf.ByteString.EMPTY; + id_ = 0L; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, getVerbBytes()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getPathBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, body_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeUInt64(4, id_); + } + 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, getVerbBytes()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getPathBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, body_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(4, id_); + } + 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.internal.websocket.WebSocketProtos.WebSocketRequestMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage 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; + } + /** + * Protobuf type {@code textsecure.WebSocketRequestMessage} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketRequestMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketRequestMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.class, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.Builder.class); + } + + // Construct using org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.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(); + verb_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + path_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + body_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); + id_ = 0L; + bitField0_ = (bitField0_ & ~0x00000008); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketRequestMessage_descriptor; + } + + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage getDefaultInstanceForType() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.getDefaultInstance(); + } + + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage build() { + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage buildPartial() { + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage result = new org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.verb_ = verb_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.path_ = path_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.body_ = body_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.id_ = id_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage) { + return mergeFrom((org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage other) { + if (other == org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.getDefaultInstance()) return this; + if (other.hasVerb()) { + bitField0_ |= 0x00000001; + verb_ = other.verb_; + onChanged(); + } + if (other.hasPath()) { + bitField0_ |= 0x00000002; + path_ = other.path_; + onChanged(); + } + if (other.hasBody()) { + setBody(other.getBody()); + } + if (other.hasId()) { + setId(other.getId()); + } + 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 { + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // optional string verb = 1; + private java.lang.Object verb_ = ""; + /** + * optional string verb = 1; + */ + public boolean hasVerb() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string verb = 1; + */ + public java.lang.String getVerb() { + java.lang.Object ref = verb_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + verb_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string verb = 1; + */ + public com.google.protobuf.ByteString + getVerbBytes() { + java.lang.Object ref = verb_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + verb_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string verb = 1; + */ + public Builder setVerb( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + verb_ = value; + onChanged(); + return this; + } + /** + * optional string verb = 1; + */ + public Builder clearVerb() { + bitField0_ = (bitField0_ & ~0x00000001); + verb_ = getDefaultInstance().getVerb(); + onChanged(); + return this; + } + /** + * optional string verb = 1; + */ + public Builder setVerbBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + verb_ = value; + onChanged(); + return this; + } + + // optional string path = 2; + private java.lang.Object path_ = ""; + /** + * optional string path = 2; + */ + public boolean hasPath() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string path = 2; + */ + public java.lang.String getPath() { + java.lang.Object ref = path_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + path_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string path = 2; + */ + public com.google.protobuf.ByteString + getPathBytes() { + java.lang.Object ref = path_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + path_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string path = 2; + */ + public Builder setPath( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + path_ = value; + onChanged(); + return this; + } + /** + * optional string path = 2; + */ + public Builder clearPath() { + bitField0_ = (bitField0_ & ~0x00000002); + path_ = getDefaultInstance().getPath(); + onChanged(); + return this; + } + /** + * optional string path = 2; + */ + public Builder setPathBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + path_ = value; + onChanged(); + return this; + } + + // optional bytes body = 3; + private com.google.protobuf.ByteString body_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes body = 3; + */ + public boolean hasBody() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional bytes body = 3; + */ + public com.google.protobuf.ByteString getBody() { + return body_; + } + /** + * optional bytes body = 3; + */ + public Builder setBody(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + body_ = value; + onChanged(); + return this; + } + /** + * optional bytes body = 3; + */ + public Builder clearBody() { + bitField0_ = (bitField0_ & ~0x00000004); + body_ = getDefaultInstance().getBody(); + onChanged(); + return this; + } + + // optional uint64 id = 4; + private long id_ ; + /** + * optional uint64 id = 4; + */ + public boolean hasId() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional uint64 id = 4; + */ + public long getId() { + return id_; + } + /** + * optional uint64 id = 4; + */ + public Builder setId(long value) { + bitField0_ |= 0x00000008; + id_ = value; + onChanged(); + return this; + } + /** + * optional uint64 id = 4; + */ + public Builder clearId() { + bitField0_ = (bitField0_ & ~0x00000008); + id_ = 0L; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.WebSocketRequestMessage) + } + + static { + defaultInstance = new WebSocketRequestMessage(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.WebSocketRequestMessage) + } + + public interface WebSocketResponseMessageOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional uint64 id = 1; + /** + * optional uint64 id = 1; + */ + boolean hasId(); + /** + * optional uint64 id = 1; + */ + long getId(); + + // optional uint32 status = 2; + /** + * optional uint32 status = 2; + */ + boolean hasStatus(); + /** + * optional uint32 status = 2; + */ + int getStatus(); + + // optional string message = 3; + /** + * optional string message = 3; + */ + boolean hasMessage(); + /** + * optional string message = 3; + */ + java.lang.String getMessage(); + /** + * optional string message = 3; + */ + com.google.protobuf.ByteString + getMessageBytes(); + + // optional bytes body = 4; + /** + * optional bytes body = 4; + */ + boolean hasBody(); + /** + * optional bytes body = 4; + */ + com.google.protobuf.ByteString getBody(); + } + /** + * Protobuf type {@code textsecure.WebSocketResponseMessage} + */ + public static final class WebSocketResponseMessage extends + com.google.protobuf.GeneratedMessage + implements WebSocketResponseMessageOrBuilder { + // Use WebSocketResponseMessage.newBuilder() to construct. + private WebSocketResponseMessage(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private WebSocketResponseMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final WebSocketResponseMessage defaultInstance; + public static WebSocketResponseMessage getDefaultInstance() { + return defaultInstance; + } + + public WebSocketResponseMessage getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private WebSocketResponseMessage( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + id_ = input.readUInt64(); + break; + } + case 16: { + bitField0_ |= 0x00000002; + status_ = input.readUInt32(); + break; + } + case 26: { + bitField0_ |= 0x00000004; + message_ = input.readBytes(); + break; + } + case 34: { + bitField0_ |= 0x00000008; + body_ = input.readBytes(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketResponseMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketResponseMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.class, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public WebSocketResponseMessage parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new WebSocketResponseMessage(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // optional uint64 id = 1; + public static final int ID_FIELD_NUMBER = 1; + private long id_; + /** + * optional uint64 id = 1; + */ + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional uint64 id = 1; + */ + public long getId() { + return id_; + } + + // optional uint32 status = 2; + public static final int STATUS_FIELD_NUMBER = 2; + private int status_; + /** + * optional uint32 status = 2; + */ + public boolean hasStatus() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional uint32 status = 2; + */ + public int getStatus() { + return status_; + } + + // optional string message = 3; + public static final int MESSAGE_FIELD_NUMBER = 3; + private java.lang.Object message_; + /** + * optional string message = 3; + */ + public boolean hasMessage() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional string message = 3; + */ + public java.lang.String getMessage() { + java.lang.Object ref = message_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + message_ = s; + } + return s; + } + } + /** + * optional string message = 3; + */ + public com.google.protobuf.ByteString + getMessageBytes() { + java.lang.Object ref = message_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + message_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // optional bytes body = 4; + public static final int BODY_FIELD_NUMBER = 4; + private com.google.protobuf.ByteString body_; + /** + * optional bytes body = 4; + */ + public boolean hasBody() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional bytes body = 4; + */ + public com.google.protobuf.ByteString getBody() { + return body_; + } + + private void initFields() { + id_ = 0L; + status_ = 0; + message_ = ""; + body_ = 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.writeUInt64(1, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeUInt32(2, status_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, getMessageBytes()); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeBytes(4, body_); + } + 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 + .computeUInt64Size(1, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(2, status_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, getMessageBytes()); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(4, body_); + } + 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.internal.websocket.WebSocketProtos.WebSocketResponseMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage 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; + } + /** + * Protobuf type {@code textsecure.WebSocketResponseMessage} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketResponseMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketResponseMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.class, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.Builder.class); + } + + // Construct using org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.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); + status_ = 0; + bitField0_ = (bitField0_ & ~0x00000002); + message_ = ""; + bitField0_ = (bitField0_ & ~0x00000004); + body_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketResponseMessage_descriptor; + } + + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage getDefaultInstanceForType() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.getDefaultInstance(); + } + + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage build() { + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage buildPartial() { + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage result = new org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage(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.status_ = status_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.message_ = message_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.body_ = body_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage) { + return mergeFrom((org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage other) { + if (other == org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.getDefaultInstance()) return this; + if (other.hasId()) { + setId(other.getId()); + } + if (other.hasStatus()) { + setStatus(other.getStatus()); + } + if (other.hasMessage()) { + bitField0_ |= 0x00000004; + message_ = other.message_; + onChanged(); + } + if (other.hasBody()) { + setBody(other.getBody()); + } + 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 { + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // optional uint64 id = 1; + private long id_ ; + /** + * optional uint64 id = 1; + */ + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional uint64 id = 1; + */ + public long getId() { + return id_; + } + /** + * optional uint64 id = 1; + */ + public Builder setId(long value) { + bitField0_ |= 0x00000001; + id_ = value; + onChanged(); + return this; + } + /** + * optional uint64 id = 1; + */ + public Builder clearId() { + bitField0_ = (bitField0_ & ~0x00000001); + id_ = 0L; + onChanged(); + return this; + } + + // optional uint32 status = 2; + private int status_ ; + /** + * optional uint32 status = 2; + */ + public boolean hasStatus() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional uint32 status = 2; + */ + public int getStatus() { + return status_; + } + /** + * optional uint32 status = 2; + */ + public Builder setStatus(int value) { + bitField0_ |= 0x00000002; + status_ = value; + onChanged(); + return this; + } + /** + * optional uint32 status = 2; + */ + public Builder clearStatus() { + bitField0_ = (bitField0_ & ~0x00000002); + status_ = 0; + onChanged(); + return this; + } + + // optional string message = 3; + private java.lang.Object message_ = ""; + /** + * optional string message = 3; + */ + public boolean hasMessage() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional string message = 3; + */ + public java.lang.String getMessage() { + java.lang.Object ref = message_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + message_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string message = 3; + */ + public com.google.protobuf.ByteString + getMessageBytes() { + java.lang.Object ref = message_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + message_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string message = 3; + */ + public Builder setMessage( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + message_ = value; + onChanged(); + return this; + } + /** + * optional string message = 3; + */ + public Builder clearMessage() { + bitField0_ = (bitField0_ & ~0x00000004); + message_ = getDefaultInstance().getMessage(); + onChanged(); + return this; + } + /** + * optional string message = 3; + */ + public Builder setMessageBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + message_ = value; + onChanged(); + return this; + } + + // optional bytes body = 4; + private com.google.protobuf.ByteString body_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes body = 4; + */ + public boolean hasBody() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional bytes body = 4; + */ + public com.google.protobuf.ByteString getBody() { + return body_; + } + /** + * optional bytes body = 4; + */ + public Builder setBody(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + body_ = value; + onChanged(); + return this; + } + /** + * optional bytes body = 4; + */ + public Builder clearBody() { + bitField0_ = (bitField0_ & ~0x00000008); + body_ = getDefaultInstance().getBody(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.WebSocketResponseMessage) + } + + static { + defaultInstance = new WebSocketResponseMessage(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.WebSocketResponseMessage) + } + + public interface WebSocketMessageOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional .textsecure.WebSocketMessage.Type type = 1; + /** + * optional .textsecure.WebSocketMessage.Type type = 1; + */ + boolean hasType(); + /** + * optional .textsecure.WebSocketMessage.Type type = 1; + */ + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Type getType(); + + // optional .textsecure.WebSocketRequestMessage request = 2; + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + boolean hasRequest(); + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage getRequest(); + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessageOrBuilder getRequestOrBuilder(); + + // optional .textsecure.WebSocketResponseMessage response = 3; + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + boolean hasResponse(); + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage getResponse(); + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessageOrBuilder getResponseOrBuilder(); + } + /** + * Protobuf type {@code textsecure.WebSocketMessage} + */ + public static final class WebSocketMessage extends + com.google.protobuf.GeneratedMessage + implements WebSocketMessageOrBuilder { + // Use WebSocketMessage.newBuilder() to construct. + private WebSocketMessage(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private WebSocketMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final WebSocketMessage defaultInstance; + public static WebSocketMessage getDefaultInstance() { + return defaultInstance; + } + + public WebSocketMessage getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private WebSocketMessage( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + int rawValue = input.readEnum(); + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Type value = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Type.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(1, rawValue); + } else { + bitField0_ |= 0x00000001; + type_ = value; + } + break; + } + case 18: { + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.Builder subBuilder = null; + if (((bitField0_ & 0x00000002) == 0x00000002)) { + subBuilder = request_.toBuilder(); + } + request_ = input.readMessage(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(request_); + request_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000002; + break; + } + case 26: { + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.Builder subBuilder = null; + if (((bitField0_ & 0x00000004) == 0x00000004)) { + subBuilder = response_.toBuilder(); + } + response_ = input.readMessage(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(response_); + response_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000004; + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.class, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public WebSocketMessage parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new WebSocketMessage(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + /** + * Protobuf enum {@code textsecure.WebSocketMessage.Type} + */ + public enum Type + implements com.google.protobuf.ProtocolMessageEnum { + /** + * UNKNOWN = 0; + */ + UNKNOWN(0, 0), + /** + * REQUEST = 1; + */ + REQUEST(1, 1), + /** + * RESPONSE = 2; + */ + RESPONSE(2, 2), + ; + + /** + * UNKNOWN = 0; + */ + public static final int UNKNOWN_VALUE = 0; + /** + * REQUEST = 1; + */ + public static final int REQUEST_VALUE = 1; + /** + * RESPONSE = 2; + */ + public static final int RESPONSE_VALUE = 2; + + + public final int getNumber() { return value; } + + public static Type valueOf(int value) { + switch (value) { + case 0: return UNKNOWN; + case 1: return REQUEST; + case 2: return RESPONSE; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public Type findValueByNumber(int number) { + return Type.valueOf(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(index); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.getDescriptor().getEnumTypes().get(0); + } + + private static final Type[] VALUES = values(); + + public static Type valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int index; + private final int value; + + private Type(int index, int value) { + this.index = index; + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:textsecure.WebSocketMessage.Type) + } + + private int bitField0_; + // optional .textsecure.WebSocketMessage.Type type = 1; + public static final int TYPE_FIELD_NUMBER = 1; + private org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Type type_; + /** + * optional .textsecure.WebSocketMessage.Type type = 1; + */ + public boolean hasType() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional .textsecure.WebSocketMessage.Type type = 1; + */ + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Type getType() { + return type_; + } + + // optional .textsecure.WebSocketRequestMessage request = 2; + public static final int REQUEST_FIELD_NUMBER = 2; + private org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage request_; + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + public boolean hasRequest() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage getRequest() { + return request_; + } + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessageOrBuilder getRequestOrBuilder() { + return request_; + } + + // optional .textsecure.WebSocketResponseMessage response = 3; + public static final int RESPONSE_FIELD_NUMBER = 3; + private org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage response_; + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + public boolean hasResponse() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage getResponse() { + return response_; + } + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessageOrBuilder getResponseOrBuilder() { + return response_; + } + + private void initFields() { + type_ = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Type.UNKNOWN; + request_ = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.getDefaultInstance(); + response_ = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.getDefaultInstance(); + } + 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.writeEnum(1, type_.getNumber()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeMessage(2, request_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeMessage(3, response_); + } + 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 + .computeEnumSize(1, type_.getNumber()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, request_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(3, response_); + } + 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.internal.websocket.WebSocketProtos.WebSocketMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage 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; + } + /** + * Protobuf type {@code textsecure.WebSocketMessage} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.class, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Builder.class); + } + + // Construct using org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getRequestFieldBuilder(); + getResponseFieldBuilder(); + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + type_ = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Type.UNKNOWN; + bitField0_ = (bitField0_ & ~0x00000001); + if (requestBuilder_ == null) { + request_ = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.getDefaultInstance(); + } else { + requestBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000002); + if (responseBuilder_ == null) { + response_ = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.getDefaultInstance(); + } else { + responseBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.internal_static_textsecure_WebSocketMessage_descriptor; + } + + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage getDefaultInstanceForType() { + return org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.getDefaultInstance(); + } + + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage build() { + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage buildPartial() { + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage result = new org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.type_ = type_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + if (requestBuilder_ == null) { + result.request_ = request_; + } else { + result.request_ = requestBuilder_.build(); + } + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + if (responseBuilder_ == null) { + result.response_ = response_; + } else { + result.response_ = responseBuilder_.build(); + } + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage) { + return mergeFrom((org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage other) { + if (other == org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.getDefaultInstance()) return this; + if (other.hasType()) { + setType(other.getType()); + } + if (other.hasRequest()) { + mergeRequest(other.getRequest()); + } + if (other.hasResponse()) { + mergeResponse(other.getResponse()); + } + 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 { + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // optional .textsecure.WebSocketMessage.Type type = 1; + private org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Type type_ = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Type.UNKNOWN; + /** + * optional .textsecure.WebSocketMessage.Type type = 1; + */ + public boolean hasType() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional .textsecure.WebSocketMessage.Type type = 1; + */ + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Type getType() { + return type_; + } + /** + * optional .textsecure.WebSocketMessage.Type type = 1; + */ + public Builder setType(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Type value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + type_ = value; + onChanged(); + return this; + } + /** + * optional .textsecure.WebSocketMessage.Type type = 1; + */ + public Builder clearType() { + bitField0_ = (bitField0_ & ~0x00000001); + type_ = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketMessage.Type.UNKNOWN; + onChanged(); + return this; + } + + // optional .textsecure.WebSocketRequestMessage request = 2; + private org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage request_ = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.Builder, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessageOrBuilder> requestBuilder_; + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + public boolean hasRequest() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage getRequest() { + if (requestBuilder_ == null) { + return request_; + } else { + return requestBuilder_.getMessage(); + } + } + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + public Builder setRequest(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage value) { + if (requestBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + request_ = value; + onChanged(); + } else { + requestBuilder_.setMessage(value); + } + bitField0_ |= 0x00000002; + return this; + } + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + public Builder setRequest( + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.Builder builderForValue) { + if (requestBuilder_ == null) { + request_ = builderForValue.build(); + onChanged(); + } else { + requestBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000002; + return this; + } + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + public Builder mergeRequest(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage value) { + if (requestBuilder_ == null) { + if (((bitField0_ & 0x00000002) == 0x00000002) && + request_ != org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.getDefaultInstance()) { + request_ = + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.newBuilder(request_).mergeFrom(value).buildPartial(); + } else { + request_ = value; + } + onChanged(); + } else { + requestBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000002; + return this; + } + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + public Builder clearRequest() { + if (requestBuilder_ == null) { + request_ = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.getDefaultInstance(); + onChanged(); + } else { + requestBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.Builder getRequestBuilder() { + bitField0_ |= 0x00000002; + onChanged(); + return getRequestFieldBuilder().getBuilder(); + } + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessageOrBuilder getRequestOrBuilder() { + if (requestBuilder_ != null) { + return requestBuilder_.getMessageOrBuilder(); + } else { + return request_; + } + } + /** + * optional .textsecure.WebSocketRequestMessage request = 2; + */ + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.Builder, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessageOrBuilder> + getRequestFieldBuilder() { + if (requestBuilder_ == null) { + requestBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage.Builder, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessageOrBuilder>( + request_, + getParentForChildren(), + isClean()); + request_ = null; + } + return requestBuilder_; + } + + // optional .textsecure.WebSocketResponseMessage response = 3; + private org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage response_ = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.Builder, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessageOrBuilder> responseBuilder_; + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + public boolean hasResponse() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage getResponse() { + if (responseBuilder_ == null) { + return response_; + } else { + return responseBuilder_.getMessage(); + } + } + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + public Builder setResponse(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage value) { + if (responseBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + response_ = value; + onChanged(); + } else { + responseBuilder_.setMessage(value); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + public Builder setResponse( + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.Builder builderForValue) { + if (responseBuilder_ == null) { + response_ = builderForValue.build(); + onChanged(); + } else { + responseBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + public Builder mergeResponse(org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage value) { + if (responseBuilder_ == null) { + if (((bitField0_ & 0x00000004) == 0x00000004) && + response_ != org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.getDefaultInstance()) { + response_ = + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.newBuilder(response_).mergeFrom(value).buildPartial(); + } else { + response_ = value; + } + onChanged(); + } else { + responseBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + public Builder clearResponse() { + if (responseBuilder_ == null) { + response_ = org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.getDefaultInstance(); + onChanged(); + } else { + responseBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.Builder getResponseBuilder() { + bitField0_ |= 0x00000004; + onChanged(); + return getResponseFieldBuilder().getBuilder(); + } + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + public org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessageOrBuilder getResponseOrBuilder() { + if (responseBuilder_ != null) { + return responseBuilder_.getMessageOrBuilder(); + } else { + return response_; + } + } + /** + * optional .textsecure.WebSocketResponseMessage response = 3; + */ + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.Builder, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessageOrBuilder> + getResponseFieldBuilder() { + if (responseBuilder_ == null) { + responseBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage.Builder, org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessageOrBuilder>( + response_, + getParentForChildren(), + isClean()); + response_ = null; + } + return responseBuilder_; + } + + // @@protoc_insertion_point(builder_scope:textsecure.WebSocketMessage) + } + + static { + defaultInstance = new WebSocketMessage(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.WebSocketMessage) + } + + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_WebSocketRequestMessage_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_WebSocketRequestMessage_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_WebSocketResponseMessage_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_WebSocketResponseMessage_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_WebSocketMessage_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_WebSocketMessage_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\030WebSocketResources.proto\022\ntextsecure\"O" + + "\n\027WebSocketRequestMessage\022\014\n\004verb\030\001 \001(\t\022" + + "\014\n\004path\030\002 \001(\t\022\014\n\004body\030\003 \001(\014\022\n\n\002id\030\004 \001(\004\"" + + "U\n\030WebSocketResponseMessage\022\n\n\002id\030\001 \001(\004\022" + + "\016\n\006status\030\002 \001(\r\022\017\n\007message\030\003 \001(\t\022\014\n\004body" + + "\030\004 \001(\014\"\341\001\n\020WebSocketMessage\022/\n\004type\030\001 \001(" + + "\0162!.textsecure.WebSocketMessage.Type\0224\n\007" + + "request\030\002 \001(\0132#.textsecure.WebSocketRequ" + + "estMessage\0226\n\010response\030\003 \001(\0132$.textsecur" + + "e.WebSocketResponseMessage\".\n\004Type\022\013\n\007UN", + "KNOWN\020\000\022\013\n\007REQUEST\020\001\022\014\n\010RESPONSE\020\002BC\n0or" + + "g.whispersystems.textsecure.internal.web" + + "socketB\017WebSocketProtos" + }; + com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = + new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { + public com.google.protobuf.ExtensionRegistry assignDescriptors( + com.google.protobuf.Descriptors.FileDescriptor root) { + descriptor = root; + internal_static_textsecure_WebSocketRequestMessage_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_textsecure_WebSocketRequestMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_WebSocketRequestMessage_descriptor, + new java.lang.String[] { "Verb", "Path", "Body", "Id", }); + internal_static_textsecure_WebSocketResponseMessage_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_textsecure_WebSocketResponseMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_WebSocketResponseMessage_descriptor, + new java.lang.String[] { "Id", "Status", "Message", "Body", }); + internal_static_textsecure_WebSocketMessage_descriptor = + getDescriptor().getMessageTypes().get(2); + internal_static_textsecure_WebSocketMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_WebSocketMessage_descriptor, + new java.lang.String[] { "Type", "Request", "Response", }); + return null; + } + }; + com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }, assigner); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/src/org/thoughtcrime/securesms/PassphraseRequiredMixin.java b/src/org/thoughtcrime/securesms/PassphraseRequiredMixin.java index 67cf63713d..80aa544305 100644 --- a/src/org/thoughtcrime/securesms/PassphraseRequiredMixin.java +++ b/src/org/thoughtcrime/securesms/PassphraseRequiredMixin.java @@ -10,6 +10,7 @@ import android.view.WindowManager; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.service.MessageRetrievalService; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -27,11 +28,13 @@ public class PassphraseRequiredMixin { initializeNewKeyReceiver(activity); initializeFromMasterSecret(activity); KeyCachingService.registerPassphraseActivityStarted(activity); + MessageRetrievalService.registerActivityStarted(activity); } public void onPause(T activity) { removeNewKeyReceiver(activity); KeyCachingService.registerPassphraseActivityStopped(activity); + MessageRetrievalService.registerActivityStopped(activity); } public void onDestroy(T activity) { diff --git a/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java index 7d830a7230..9e27d09e68 100644 --- a/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java +++ b/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java @@ -6,7 +6,6 @@ import org.thoughtcrime.securesms.Release; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore; import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob; -import org.thoughtcrime.securesms.jobs.AvatarDownloadJob; import org.thoughtcrime.securesms.jobs.CleanPreKeysJob; import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.DeliveryReceiptJob; @@ -19,12 +18,13 @@ import org.thoughtcrime.securesms.push.TextSecurePushTrustStore; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; +import org.thoughtcrime.securesms.service.MessageRetrievalService; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.textsecure.api.TextSecureAccountManager; import org.whispersystems.textsecure.api.TextSecureMessageReceiver; import org.whispersystems.textsecure.api.TextSecureMessageSender; -import org.whispersystems.textsecure.api.push.PushAddress; +import org.whispersystems.textsecure.api.util.CredentialsProvider; import dagger.Module; import dagger.Provides; @@ -36,7 +36,8 @@ import dagger.Provides; PushTextSendJob.class, PushMediaSendJob.class, AttachmentDownloadJob.class, - RefreshPreKeysJob.class}) + RefreshPreKeysJob.class, + MessageRetrievalService.class}) public class TextSecureCommunicationModule { private final Context context; @@ -77,13 +78,36 @@ public class TextSecureCommunicationModule { @Provides TextSecureMessageReceiver provideTextSecureMessageReceiver() { return new TextSecureMessageReceiver(Release.PUSH_URL, - new TextSecurePushTrustStore(context), - TextSecurePreferences.getLocalNumber(context), - TextSecurePreferences.getPushServerPassword(context)); + new TextSecurePushTrustStore(context), + new DynamicCredentialsProvider(context)); } public static interface TextSecureMessageSenderFactory { public TextSecureMessageSender create(MasterSecret masterSecret); } + private static class DynamicCredentialsProvider implements CredentialsProvider { + + private final Context context; + + private DynamicCredentialsProvider(Context context) { + this.context = context.getApplicationContext(); + } + + @Override + public String getUser() { + return TextSecurePreferences.getLocalNumber(context); + } + + @Override + public String getPassword() { + return TextSecurePreferences.getPushServerPassword(context); + } + + @Override + public String getSignalingKey() { + return TextSecurePreferences.getSignalingKey(context); + } + } + } diff --git a/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java b/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java index 4975474521..843a2120ff 100644 --- a/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java +++ b/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java @@ -10,6 +10,7 @@ import com.google.android.gms.gcm.GoogleCloudMessaging; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.jobs.PushReceiveJob; +import org.thoughtcrime.securesms.service.MessageRetrievalService; import org.thoughtcrime.securesms.util.TextSecurePreferences; public class GcmBroadcastReceiver extends BroadcastReceiver { @@ -34,6 +35,7 @@ public class GcmBroadcastReceiver extends BroadcastReceiver { if (!TextUtils.isEmpty(messageData)) handleReceivedMessage(context, messageData); else if (!TextUtils.isEmpty(receiptData)) handleReceivedMessage(context, receiptData); + else if (intent.hasExtra("notification")) handleReceivedNotification(context); } } @@ -42,4 +44,8 @@ public class GcmBroadcastReceiver extends BroadcastReceiver { .getJobManager() .add(new PushReceiveJob(context, data)); } + + private void handleReceivedNotification(Context context) { + MessageRetrievalService.registerPushReceived(context); + } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java index 56c740f251..52cd6d4e66 100644 --- a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java @@ -23,6 +23,7 @@ import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.textsecure.api.crypto.AttachmentCipherInputStream; import org.whispersystems.textsecure.internal.push.PushServiceSocket; import org.whispersystems.textsecure.api.push.exceptions.NonSuccessfulResponseCodeException; +import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider; import java.io.File; import java.io.IOException; @@ -99,8 +100,9 @@ public class AvatarDownloadJob extends MasterSecretJob { private File downloadAttachment(String relay, long contentLocation) throws IOException { PushServiceSocket socket = new PushServiceSocket(Release.PUSH_URL, new TextSecurePushTrustStore(context), - TextSecurePreferences.getLocalNumber(context), - TextSecurePreferences.getPushServerPassword(context)); + new StaticCredentialsProvider(TextSecurePreferences.getLocalNumber(context), + TextSecurePreferences.getPushServerPassword(context), + null)); File destination = File.createTempFile("avatar", "tmp"); diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 5d95f88ccb..64ed7f366a 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -221,10 +221,12 @@ public class PushDecryptJob extends MasterSecretJob { } private void handleDuplicateMessage(MasterSecret masterSecret, TextSecureEnvelope envelope) { - Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); - DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptDuplicate(messageAndThreadId.first); + // Let's start ignoring these now. - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); +// Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); +// DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptDuplicate(messageAndThreadId.first); +// +// MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } private void handleUntrustedIdentityMessage(MasterSecret masterSecret, TextSecureEnvelope envelope) { diff --git a/src/org/thoughtcrime/securesms/jobs/PushReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/PushReceiveJob.java index 348760132f..cbe3b2668a 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushReceiveJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushReceiveJob.java @@ -22,6 +22,11 @@ public class PushReceiveJob extends ContextJob { private final String data; + public PushReceiveJob(Context context) { + super(context, JobParameters.newBuilder().create()); + this.data = null; + } + public PushReceiveJob(Context context, String data) { super(context, JobParameters.newBuilder() .withPersistence() @@ -39,16 +44,7 @@ public class PushReceiveJob extends ContextJob { String sessionKey = TextSecurePreferences.getSignalingKey(context); TextSecureEnvelope envelope = new TextSecureEnvelope(data, sessionKey); - if (!isActiveNumber(context, envelope.getSource())) { - TextSecureDirectory directory = TextSecureDirectory.getInstance(context); - ContactTokenDetails contactTokenDetails = new ContactTokenDetails(); - contactTokenDetails.setNumber(envelope.getSource()); - - directory.setNumber(contactTokenDetails, true); - } - - if (envelope.isReceipt()) handleReceipt(envelope); - else handleMessage(envelope); + handle(envelope, true); } catch (IOException | InvalidVersionException e) { Log.w(TAG, e); } @@ -64,13 +60,28 @@ public class PushReceiveJob extends ContextJob { return false; } - private void handleMessage(TextSecureEnvelope envelope) { + public void handle(TextSecureEnvelope envelope, boolean sendExplicitReceipt) { + if (!isActiveNumber(context, envelope.getSource())) { + TextSecureDirectory directory = TextSecureDirectory.getInstance(context); + ContactTokenDetails contactTokenDetails = new ContactTokenDetails(); + contactTokenDetails.setNumber(envelope.getSource()); + + directory.setNumber(contactTokenDetails, true); + } + + if (envelope.isReceipt()) handleReceipt(envelope); + else handleMessage(envelope, sendExplicitReceipt); + } + + private void handleMessage(TextSecureEnvelope envelope, boolean sendExplicitReceipt) { JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); long messageId = DatabaseFactory.getPushDatabase(context).insert(envelope); - jobManager.add(new DeliveryReceiptJob(context, envelope.getSource(), - envelope.getTimestamp(), - envelope.getRelay())); + if (sendExplicitReceipt) { + jobManager.add(new DeliveryReceiptJob(context, envelope.getSource(), + envelope.getTimestamp(), + envelope.getRelay())); + } jobManager.add(new PushDecryptJob(context, messageId)); } diff --git a/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java b/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java new file mode 100644 index 0000000000..5d866f9cd5 --- /dev/null +++ b/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java @@ -0,0 +1,180 @@ +package org.thoughtcrime.securesms.service; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.util.Log; + +import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.dependencies.InjectableType; +import org.thoughtcrime.securesms.jobs.PushReceiveJob; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.jobqueue.requirements.NetworkRequirement; +import org.whispersystems.jobqueue.requirements.NetworkRequirementProvider; +import org.whispersystems.jobqueue.requirements.RequirementListener; +import org.whispersystems.libaxolotl.InvalidVersionException; +import org.whispersystems.textsecure.api.TextSecureMessagePipe; +import org.whispersystems.textsecure.api.TextSecureMessageReceiver; +import org.whispersystems.textsecure.api.messages.TextSecureEnvelope; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.inject.Inject; + +public class MessageRetrievalService extends Service implements Runnable, InjectableType, RequirementListener { + + private static final String TAG = MessageRetrievalService.class.getSimpleName(); + + public static final String ACTION_ACTIVITY_STARTED = "ACTIVITY_STARTED"; + public static final String ACTION_ACTIVITY_FINISHED = "ACTIVITY_FINISHED"; + public static final String ACTION_PUSH_RECEIVED = "PUSH_RECEIVED"; + private static final long REQUEST_TIMEOUT_MINUTES = 1; + + private NetworkRequirement networkRequirement; + private NetworkRequirementProvider networkRequirementProvider; + + @Inject + public TextSecureMessageReceiver receiver; + + private int activeActivities = 0; + private boolean pushPending = false; + + @Override + public void onCreate() { + super.onCreate(); + ApplicationContext.getInstance(this).injectDependencies(this); + + networkRequirement = new NetworkRequirement(this); + networkRequirementProvider = new NetworkRequirementProvider(this); + + networkRequirementProvider.setListener(this); + new Thread(this, "MessageRetrievalService").start(); + } + + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent == null) return START_STICKY; + + if (ACTION_ACTIVITY_STARTED.equals(intent.getAction())) incrementActive(); + else if (ACTION_ACTIVITY_FINISHED.equals(intent.getAction())) decrementActive(); + else if (ACTION_PUSH_RECEIVED.equals(intent.getAction())) incrementPushReceived(); + + return START_STICKY; + } + + @Override + public void run() { + while (true) { + Log.w(TAG, "Waiting for websocket state change...."); + waitForConnectionNecessary(); + + Log.w(TAG, "Making websocket connection...."); + TextSecureMessagePipe pipe = receiver.createMessagePipe(); + + try { + while (isConnectionNecessary()) { + try { + Log.w(TAG, "Reading message..."); + pipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES, + new TextSecureMessagePipe.MessagePipeCallback() { + @Override + public void onMessage(TextSecureEnvelope envelope) { + Log.w(TAG, "Retrieved envelope! " + envelope.getSource()); + + PushReceiveJob receiveJob = new PushReceiveJob(MessageRetrievalService.this); + receiveJob.handle(envelope, false); + + decrementPushReceived(); + } + }); + } catch (TimeoutException | InvalidVersionException e) { + Log.w(TAG, e); + } + } + } catch (Throwable e) { + Log.w(TAG, e); + } finally { + Log.w(TAG, "Shutting down pipe..."); + shutdown(pipe); + } + + Log.w(TAG, "Looping..."); + } + } + + @Override + public void onRequirementStatusChanged() { + synchronized (this) { + notifyAll(); + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + private synchronized void incrementActive() { + activeActivities++; + Log.w(TAG, "Active Count: " + activeActivities); + notifyAll(); + } + + private synchronized void decrementActive() { + activeActivities--; + Log.w(TAG, "Active Count: " + activeActivities); + notifyAll(); + } + + private synchronized void incrementPushReceived() { + pushPending = true; + notifyAll(); + } + + private synchronized void decrementPushReceived() { + pushPending = false; + notifyAll(); + } + + private synchronized boolean isConnectionNecessary() { + Log.w(TAG, "Network requirement: " + networkRequirement.isPresent()); + return TextSecurePreferences.isWebsocketRegistered(this) && + (activeActivities > 0 || pushPending) && + networkRequirement.isPresent(); + } + + private synchronized void waitForConnectionNecessary() { + try { + while (!isConnectionNecessary()) wait(); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + } + + private void shutdown(TextSecureMessagePipe pipe) { + try { + pipe.shutdown(); + } catch (Throwable t) { + Log.w(TAG, t); + } + } + + public static void registerActivityStarted(Context activity) { + Intent intent = new Intent(activity, MessageRetrievalService.class); + intent.setAction(MessageRetrievalService.ACTION_ACTIVITY_STARTED); + activity.startService(intent); + } + + public static void registerActivityStopped(Context activity) { + Intent intent = new Intent(activity, MessageRetrievalService.class); + intent.setAction(MessageRetrievalService.ACTION_ACTIVITY_FINISHED); + activity.startService(intent); + } + + public static void registerPushReceived(Context context) { + Intent intent = new Intent(context, MessageRetrievalService.class); + intent.setAction(MessageRetrievalService.ACTION_PUSH_RECEIVED); + context.startService(intent); + } +} diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index 3f525b768a..b4e53c6ae5 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -245,9 +245,11 @@ public class RegistrationService extends Service { setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number)); String gcmRegistrationId = GoogleCloudMessaging.getInstance(this).register(GcmRefreshJob.REGISTRATION_ID); - TextSecurePreferences.setGcmRegistrationId(this, gcmRegistrationId); accountManager.setGcmId(Optional.of(gcmRegistrationId)); + TextSecurePreferences.setGcmRegistrationId(this, gcmRegistrationId); + TextSecurePreferences.setWebsocketRegistered(this, true); + DatabaseFactory.getIdentityDatabase(this).saveIdentity(masterSecret, self.getRecipientId(), identityKey.getPublicKey()); DirectoryHelper.refreshDirectory(this, accountManager, number); diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index dc743e61fa..6082af1d5a 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -61,10 +61,19 @@ public class TextSecurePreferences { private static final String GCM_REGISTRATION_ID_PREF = "pref_gcm_registration_id"; private static final String GCM_REGISTRATION_ID_VERSION_PREF = "pref_gcm_registration_id_version"; + private static final String WEBSOCKET_REGISTERED_PREF = "pref_websocket_registered"; private static final String PUSH_REGISTRATION_REMINDER_PREF = "pref_push_registration_reminder"; public static final String REPEAT_ALERTS_PREF = "pref_repeat_alerts"; + public static boolean isWebsocketRegistered(Context context) { + return getBooleanPreference(context, WEBSOCKET_REGISTERED_PREF, false); + } + + public static void setWebsocketRegistered(Context context, boolean registered) { + setBooleanPreference(context, WEBSOCKET_REGISTERED_PREF, registered); + } + public static boolean isWifiSmsEnabled(Context context) { return getBooleanPreference(context, WIFI_SMS_PREF, false); }