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. // FREEBIEpull/1/head
parent
8a2caeef3d
commit
83d65228e9
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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…
Reference in New Issue