org.whispersystems.signalservice.api.SignalServiceMessagePipe Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of signal-service-java Show documentation
Show all versions of signal-service-java Show documentation
Signal Service communication library for Java
The newest version!
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.whispersystems.signalservice.api;
import com.google.protobuf.ByteString;
import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.internal.push.OutgoingPushMessageList;
import org.whispersystems.signalservice.internal.push.SendMessageResponse;
import org.whispersystems.signalservice.internal.util.JsonUtil;
import org.whispersystems.signalservice.internal.util.Util;
import org.whispersystems.signalservice.internal.websocket.WebSocketConnection;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
import static org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketResponseMessage;
/**
* A SignalServiceMessagePipe represents a dedicated connection
* to the Signal Service, which the server can push messages
* down through.
*/
public class SignalServiceMessagePipe {
private static final String TAG = SignalServiceMessagePipe.class.getName();
private final WebSocketConnection websocket;
private final CredentialsProvider credentialsProvider;
SignalServiceMessagePipe(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 SignalServiceEnvelope 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 SignalServiceEnvelope 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 (isSignalServiceEnvelope(request)) {
SignalServiceEnvelope envelope = new SignalServiceEnvelope(request.getBody().toByteArray(),
credentialsProvider.getSignalingKey());
callback.onMessage(envelope);
return envelope;
}
} finally {
websocket.sendResponse(response);
}
}
}
public SendMessageResponse send(OutgoingPushMessageList list) throws IOException {
try {
WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
.setId(SecureRandom.getInstance("SHA1PRNG").nextLong())
.setVerb("PUT")
.setPath(String.format("/v1/messages/%s", list.getDestination()))
.addHeaders("content-type:application/json")
.setBody(ByteString.copyFrom(JsonUtil.toJson(list).getBytes()))
.build();
Pair response = websocket.sendRequest(requestMessage).get(10, TimeUnit.SECONDS);
if (response.first() < 200 || response.first() >= 300) {
throw new IOException("Non-successful response: " + response.first());
}
if (Util.isEmpty(response.second())) return new SendMessageResponse(false);
else return JsonUtil.fromJson(response.second(), SendMessageResponse.class);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new IOException(e);
}
}
public SignalServiceProfile getProfile(SignalServiceAddress address) throws IOException {
try {
WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
.setId(SecureRandom.getInstance("SHA1PRNG").nextLong())
.setVerb("GET")
.setPath(String.format("/v1/profile/%s", address.getNumber()))
.build();
Pair response = websocket.sendRequest(requestMessage).get(10, TimeUnit.SECONDS);
if (response.first() < 200 || response.first() >= 300) {
throw new IOException("Non-successful response: " + response.first());
}
return JsonUtil.fromJson(response.second(), SignalServiceProfile.class);
} catch (NoSuchAlgorithmException nsae) {
throw new AssertionError(nsae);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new IOException(e);
}
}
/**
* Close this connection to the server.
*/
public void shutdown() {
websocket.disconnect();
}
private boolean isSignalServiceEnvelope(WebSocketRequestMessage message) {
return "PUT".equals(message.getVerb()) && "/api/v1/message".equals(message.getPath());
}
private WebSocketResponseMessage createWebSocketResponse(WebSocketRequestMessage request) {
if (isSignalServiceEnvelope(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(SignalServiceEnvelope envelope);
}
private static class NullMessagePipeCallback implements MessagePipeCallback {
@Override
public void onMessage(SignalServiceEnvelope envelope) {}
}
}