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); }