mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-12 02:33:40 +00:00
Fix for race conditioned caused by OkHttpClient NPE.
We catch OkHttpClient exceptions to deal with bugs in their code, but in some cases that was leaving our state information in a bad situation. // FREEBIE
This commit is contained in:
parent
8a2caeef3d
commit
83d65228e9
@ -51,7 +51,7 @@ public class TextSecureMessagePipe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() throws IOException {
|
public void shutdown() {
|
||||||
websocket.disconnect();
|
websocket.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
package org.whispersystems.textsecure.internal.websocket;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
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.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
import okio.Buffer;
|
||||||
|
import okio.BufferedSource;
|
||||||
|
|
||||||
|
public class OkHttpClientWrapper implements WebSocketListener {
|
||||||
|
|
||||||
|
private static final String TAG = OkHttpClientWrapper.class.getSimpleName();
|
||||||
|
|
||||||
|
private final String uri;
|
||||||
|
private final TrustStore trustStore;
|
||||||
|
private final CredentialsProvider credentialsProvider;
|
||||||
|
private final WebSocketEventListener listener;
|
||||||
|
|
||||||
|
private WebSocket webSocket;
|
||||||
|
private boolean closed;
|
||||||
|
private boolean connected;
|
||||||
|
|
||||||
|
public OkHttpClientWrapper(String uri, TrustStore trustStore,
|
||||||
|
CredentialsProvider credentialsProvider,
|
||||||
|
WebSocketEventListener listener)
|
||||||
|
{
|
||||||
|
Log.w(TAG, "Connecting to: " + uri);
|
||||||
|
|
||||||
|
this.uri = uri;
|
||||||
|
this.trustStore = trustStore;
|
||||||
|
this.credentialsProvider = credentialsProvider;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect() {
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
int attempt = 0;
|
||||||
|
|
||||||
|
while ((webSocket = newSocket()) != null) {
|
||||||
|
try {
|
||||||
|
Response response = webSocket.connect(OkHttpClientWrapper.this);
|
||||||
|
|
||||||
|
if (response.code() == 101) {
|
||||||
|
synchronized (OkHttpClientWrapper.this) {
|
||||||
|
if (closed) webSocket.close(1000, "OK");
|
||||||
|
else connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.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 && connected) {
|
||||||
|
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)) {
|
||||||
|
listener.onMessage(payload.readByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose(int code, String reason) {
|
||||||
|
Log.w(TAG, String.format("onClose(%d, %s)", code, reason));
|
||||||
|
listener.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
listener.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized WebSocket newSocket() {
|
||||||
|
if (closed) return null;
|
||||||
|
|
||||||
|
String filledUri = String.format(uri, credentialsProvider.getUser(), credentialsProvider.getPassword());
|
||||||
|
SSLSocketFactory socketFactory = createTlsSocketFactory(trustStore);
|
||||||
|
|
||||||
|
return WebSocket.newWebSocket(new OkHttpClient().setSslSocketFactory(socketFactory),
|
||||||
|
new Request.Builder().url(filledUri).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,36 +3,22 @@ package org.whispersystems.textsecure.internal.websocket;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
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.push.TrustStore;
|
||||||
import org.whispersystems.textsecure.api.util.CredentialsProvider;
|
import org.whispersystems.textsecure.api.util.CredentialsProvider;
|
||||||
import org.whispersystems.textsecure.internal.util.BlacklistingTrustManager;
|
|
||||||
import org.whispersystems.textsecure.internal.util.Util;
|
import org.whispersystems.textsecure.internal.util.Util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
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.WebSocketMessage;
|
||||||
import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
|
import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
|
||||||
import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage;
|
import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage;
|
||||||
|
|
||||||
public class WebSocketConnection {
|
public class WebSocketConnection implements WebSocketEventListener {
|
||||||
|
|
||||||
private static final String TAG = WebSocketConnection.class.getSimpleName();
|
private static final String TAG = WebSocketConnection.class.getSimpleName();
|
||||||
|
|
||||||
@ -42,7 +28,7 @@ public class WebSocketConnection {
|
|||||||
private final TrustStore trustStore;
|
private final TrustStore trustStore;
|
||||||
private final CredentialsProvider credentialsProvider;
|
private final CredentialsProvider credentialsProvider;
|
||||||
|
|
||||||
private Client client;
|
private OkHttpClientWrapper client;
|
||||||
private KeepAliveSender keepAliveSender;
|
private KeepAliveSender keepAliveSender;
|
||||||
|
|
||||||
public WebSocketConnection(String httpUri, TrustStore trustStore, CredentialsProvider credentialsProvider) {
|
public WebSocketConnection(String httpUri, TrustStore trustStore, CredentialsProvider credentialsProvider) {
|
||||||
@ -56,12 +42,12 @@ public class WebSocketConnection {
|
|||||||
Log.w(TAG, "WSC connect()...");
|
Log.w(TAG, "WSC connect()...");
|
||||||
|
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
client = new Client(wsUri, trustStore, credentialsProvider);
|
client = new OkHttpClientWrapper(wsUri, trustStore, credentialsProvider, this);
|
||||||
client.connect();
|
client.connect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void disconnect() throws IOException {
|
public synchronized void disconnect() {
|
||||||
Log.w(TAG, "WSC disconnect()...");
|
Log.w(TAG, "WSC disconnect()...");
|
||||||
|
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
@ -119,7 +105,7 @@ public class WebSocketConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void onMessage(byte[] payload) {
|
public synchronized void onMessage(byte[] payload) {
|
||||||
Log.w(TAG, "WSC onMessage()");
|
Log.w(TAG, "WSC onMessage()");
|
||||||
try {
|
try {
|
||||||
WebSocketMessage message = WebSocketMessage.parseFrom(payload);
|
WebSocketMessage message = WebSocketMessage.parseFrom(payload);
|
||||||
@ -136,10 +122,11 @@ public class WebSocketConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void onClose() {
|
public synchronized void onClose() {
|
||||||
Log.w(TAG, "onClose()...");
|
Log.w(TAG, "onClose()...");
|
||||||
|
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
|
client.disconnect();
|
||||||
client = null;
|
client = null;
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
@ -152,8 +139,8 @@ public class WebSocketConnection {
|
|||||||
notifyAll();
|
notifyAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void onConnected() {
|
public synchronized void onConnected() {
|
||||||
if (client != null) {
|
if (client != null && keepAliveSender == null) {
|
||||||
keepAliveSender = new KeepAliveSender();
|
keepAliveSender = new KeepAliveSender();
|
||||||
keepAliveSender.start();
|
keepAliveSender.start();
|
||||||
}
|
}
|
||||||
@ -163,117 +150,6 @@ public class WebSocketConnection {
|
|||||||
return System.currentTimeMillis() - 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;
|
|
||||||
|
|
||||||
try {
|
|
||||||
response = webSocket.connect(Client.this);
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 class KeepAliveSender extends Thread {
|
||||||
|
|
||||||
private AtomicBoolean stop = new AtomicBoolean(false);
|
private AtomicBoolean stop = new AtomicBoolean(false);
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.whispersystems.textsecure.internal.websocket;
|
||||||
|
|
||||||
|
public interface WebSocketEventListener {
|
||||||
|
|
||||||
|
public void onMessage(byte[] payload);
|
||||||
|
public void onClose();
|
||||||
|
public void onConnected();
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user