org.whispersystems.textsecure.api.TextSecureMessagePipe Maven / Gradle / Ivy
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;
/**
* A TextSecureMessagePipe represents a dedicated connection
* to the TextSecure server, which the server can push messages
* down.
*/
public class TextSecureMessagePipe {
private final WebSocketConnection websocket;
private final CredentialsProvider credentialsProvider;
TextSecureMessagePipe(WebSocketConnection websocket, CredentialsProvider credentialsProvider) {
this.websocket = websocket;
this.credentialsProvider = credentialsProvider;
this.websocket.connect();
}
/**
* A blocking call that reads a message off the pipe. When this
* call returns, the message has been acknowledged and will not
* be retransmitted.
*
* @param timeout The timeout to wait for.
* @param unit The timeout time unit.
* @return A new message.
*
* @throws InvalidVersionException
* @throws IOException
* @throws TimeoutException
*/
public TextSecureEnvelope read(long timeout, TimeUnit unit)
throws InvalidVersionException, IOException, TimeoutException
{
return read(timeout, unit, new NullMessagePipeCallback());
}
/**
* A blocking call that reads a message off the pipe (see {@link #read(long, java.util.concurrent.TimeUnit)}
*
* Unlike {@link #read(long, java.util.concurrent.TimeUnit)}, this method allows you
* to specify a callback that will be called before the received message is acknowledged.
* This allows you to write the received message to durable storage before acknowledging
* receipt of it to the server.
*
* @param timeout The timeout to wait for.
* @param unit The timeout time unit.
* @param callback A callback that will be called before the message receipt is
* acknowledged to the server.
* @return The message read (same as the message sent through the callback).
* @throws TimeoutException
* @throws IOException
* @throws InvalidVersionException
*/
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);
}
}
}
/**
* Close this connection to the server.
*/
public void shutdown() {
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();
}
}
/**
* For receiving a callback when a new message has been
* received.
*/
public static interface MessagePipeCallback {
public void onMessage(TextSecureEnvelope envelope);
}
private static class NullMessagePipeCallback implements MessagePipeCallback {
@Override
public void onMessage(TextSecureEnvelope envelope) {}
}
}