WIP: clean up signal service protos

This commit is contained in:
Ryan ZHAO 2021-02-22 09:31:29 +11:00
parent b34809f4d5
commit 04f140ee09
47 changed files with 4 additions and 4273 deletions

View File

@ -382,9 +382,6 @@
android:name="org.thoughtcrime.securesms.service.KeyCachingService"
android:enabled="true"
android:exported="false" />
<service
android:name="org.thoughtcrime.securesms.service.IncomingMessageObserver$ForegroundService"
android:enabled="true" />
<service
android:name="org.thoughtcrime.securesms.service.DirectShareService"
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">

View File

@ -84,7 +84,6 @@ import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.LocalBackupListener;
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
@ -206,7 +205,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
initializeProfileManager();
initializePeriodicTasks();
initializeWebRtc();
initializePendingMessages();
initializeBlobProvider();
SSKEnvironment.Companion.configure(getTypingStatusRepository(), getReadReceiptManager(), getProfileManager(), messageNotifier, getExpiringMessageManager());
}
@ -340,7 +338,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
}
private void initializeDependencyInjection() {
communicationModule = new SignalCommunicationModule(this, new SignalServiceNetworkAccess(this));
communicationModule = new SignalCommunicationModule(this);
this.objectGraph = ObjectGraph.create(communicationModule);
}
@ -409,14 +407,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
}
}
private void initializePendingMessages() {
if (TextSecurePreferences.getNeedsMessagePull(this)) {
Log.i(TAG, "Scheduling a message fetch.");
ApplicationContext.getInstance(this).getJobManager().add(new PushNotificationReceiveJob(this));
TextSecurePreferences.setNeedsMessagePull(this, false);
}
}
private void initializeBlobProvider() {
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
BlobProvider.getInstance().onSessionStart(this);

View File

@ -19,7 +19,6 @@ import org.thoughtcrime.securesms.jobs.PushDecryptJob;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob;
import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.PushTextSendJob;
import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
@ -32,9 +31,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.push.MessageSenderEventListener;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.stickers.StickerPackPreviewRepository;
import org.thoughtcrime.securesms.stickers.StickerRemoteUriLoader;
import org.thoughtcrime.securesms.util.RealtimeSleepTimer;
@ -48,8 +45,6 @@ import network.loki.messenger.BuildConfig;
PushTextSendJob.class,
PushMediaSendJob.class,
AttachmentDownloadJob.class,
IncomingMessageObserver.class,
PushNotificationReceiveJob.class,
RequestGroupInfoJob.class,
PushGroupUpdateJob.class,
AvatarDownloadJob.class,
@ -71,15 +66,12 @@ public class SignalCommunicationModule {
private static final String TAG = SignalCommunicationModule.class.getSimpleName();
private final Context context;
private final SignalServiceNetworkAccess networkAccess;
private SignalServiceMessageSender messageSender;
private SignalServiceMessageReceiver messageReceiver;
public SignalCommunicationModule(Context context, SignalServiceNetworkAccess networkAccess) {
public SignalCommunicationModule(Context context) {
this.context = context;
this.networkAccess = networkAccess;
}
@Provides
@ -87,8 +79,6 @@ public class SignalCommunicationModule {
if (this.messageSender == null) {
this.messageSender = new SignalServiceMessageSender(new DynamicCredentialsProvider(context),
new SignalProtocolStoreImpl(context),
Optional.fromNullable(IncomingMessageObserver.getPipe()),
Optional.fromNullable(IncomingMessageObserver.getUnidentifiedPipe()),
TextSecurePreferences.getLocalNumber(context),
DatabaseFactory.getLokiAPIDatabase(context),
DatabaseFactory.getLokiThreadDatabase(context),
@ -97,8 +87,6 @@ public class SignalCommunicationModule {
DatabaseFactory.getLokiUserDatabase(context),
DatabaseFactory.getGroupDatabase(context),
((ApplicationContext)context.getApplicationContext()).broadcaster);
} else {
this.messageSender.setMessagePipe(IncomingMessageObserver.getPipe(), IncomingMessageObserver.getUnidentifiedPipe());
}
return this.messageSender;
@ -109,8 +97,7 @@ public class SignalCommunicationModule {
if (this.messageReceiver == null) {
SleepTimer sleepTimer = TextSecurePreferences.isFcmDisabled(context) ? new RealtimeSleepTimer(context) : new UptimeSleepTimer();
this.messageReceiver = new SignalServiceMessageReceiver(networkAccess.getConfiguration(context),
new DynamicCredentialsProvider(context),
this.messageReceiver = new SignalServiceMessageReceiver(new DynamicCredentialsProvider(context),
BuildConfig.USER_AGENT,
new PipeConnectivityListener(),
sleepTimer);
@ -119,11 +106,6 @@ public class SignalCommunicationModule {
return this.messageReceiver;
}
@Provides
synchronized SignalServiceNetworkAccess provideSignalServiceNetworkAccess() {
return networkAccess;
}
private static class DynamicCredentialsProvider implements CredentialsProvider {
private final Context context;

View File

@ -1,90 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import org.session.libsession.utilities.TextSecurePreferences;
import java.io.IOException;
import javax.inject.Inject;
public class PushNotificationReceiveJob extends PushReceivedJob implements InjectableType {
public static final String KEY = "PushNotificationReceiveJob";
private static final String TAG = PushNotificationReceiveJob.class.getSimpleName();
@Inject SignalServiceMessageReceiver receiver;
public PushNotificationReceiveJob(Context context) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setQueue("__notification_received")
.setMaxAttempts(3)
.setMaxInstances(1)
.build());
setContext(context);
}
private PushNotificationReceiveJob(@NonNull Job.Parameters parameters) {
super(parameters);
}
@Override
public @NonNull
Data serialize() {
return Data.EMPTY;
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws IOException {
pullAndProcessMessages(receiver, TAG, System.currentTimeMillis());
}
public void pullAndProcessMessages(SignalServiceMessageReceiver receiver, String tag, long startTime) throws IOException {
synchronized (PushReceivedJob.RECEIVE_LOCK) {
receiver.retrieveMessages(envelope -> {
Log.i(tag, "Retrieved an envelope." + timeSuffix(startTime));
processEnvelope(envelope, false);
Log.i(tag, "Successfully processed an envelope." + timeSuffix(startTime));
});
TextSecurePreferences.setNeedsMessagePull(context, false);
}
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
Log.w(TAG, e);
return e instanceof PushNetworkException;
}
@Override
public void onCanceled() {
Log.w(TAG, "***** Failed to download pending message!");
// MessageNotifier.notifyMessagesPending(getContext());
}
private static String timeSuffix(long startTime) {
return " (" + (System.currentTimeMillis() - startTime) + " ms elapsed)";
}
public static final class Factory implements Job.Factory<PushNotificationReceiveJob> {
@Override
public @NonNull PushNotificationReceiveJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new PushNotificationReceiveJob(parameters);
}
}
}

View File

@ -1,210 +0,0 @@
package org.thoughtcrime.securesms.service;
import android.app.Service;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.ConstraintObserver;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver;
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.libsignal.InvalidVersionException;
import org.session.libsignal.service.api.SignalServiceMessagePipe;
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.inject.Inject;
import network.loki.messenger.R;
public class IncomingMessageObserver implements InjectableType, ConstraintObserver.Notifier {
private static final String TAG = IncomingMessageObserver.class.getSimpleName();
public static final int FOREGROUND_ID = 313399;
private static final long REQUEST_TIMEOUT_MINUTES = 1;
private static SignalServiceMessagePipe pipe = null;
private static SignalServiceMessagePipe unidentifiedPipe = null;
private final Context context;
private final NetworkConstraint networkConstraint;
private boolean appVisible;
@Inject SignalServiceMessageReceiver receiver;
@Inject SignalServiceNetworkAccess networkAccess;
public IncomingMessageObserver(@NonNull Context context) {
ApplicationContext.getInstance(context).injectDependencies(this);
this.context = context;
this.networkConstraint = new NetworkConstraint.Factory(ApplicationContext.getInstance(context)).create();
new NetworkConstraintObserver(ApplicationContext.getInstance(context)).register(this);
new MessageRetrievalThread().start();
if (TextSecurePreferences.isFcmDisabled(context)) {
ContextCompat.startForegroundService(context, new Intent(context, ForegroundService.class));
}
ProcessLifecycleOwner.get().getLifecycle().addObserver(new DefaultLifecycleObserver() {
@Override
public void onStart(@NonNull LifecycleOwner owner) {
onAppForegrounded();
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
onAppBackgrounded();
}
});
}
@Override
public void onConstraintMet(@NonNull String reason) {
synchronized (this) {
notifyAll();
}
}
private synchronized void onAppForegrounded() {
appVisible = true;
notifyAll();
}
private synchronized void onAppBackgrounded() {
appVisible = false;
notifyAll();
}
private synchronized boolean isConnectionNecessary() {
boolean isGcmDisabled = TextSecurePreferences.isFcmDisabled(context);
Log.d(TAG, String.format("Network requirement: %s, app visible: %s, gcm disabled: %b",
networkConstraint.isMet(), appVisible, isGcmDisabled));
return TextSecurePreferences.isPushRegistered(context) &&
TextSecurePreferences.isWebsocketRegistered(context) &&
(appVisible || isGcmDisabled) &&
networkConstraint.isMet() &&
!networkAccess.isCensored(context);
}
private synchronized void waitForConnectionNecessary() {
try {
while (!isConnectionNecessary()) wait();
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
private void shutdown(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) {
try {
pipe.shutdown();
unidentifiedPipe.shutdown();
} catch (Throwable t) {
Log.w(TAG, t);
}
}
public static @Nullable SignalServiceMessagePipe getPipe() {
return pipe;
}
public static @Nullable SignalServiceMessagePipe getUnidentifiedPipe() {
return unidentifiedPipe;
}
private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler {
MessageRetrievalThread() {
super("MessageRetrievalService");
setUncaughtExceptionHandler(this);
}
@Override
public void run() {
while (true) {
Log.i(TAG, "Waiting for websocket state change....");
waitForConnectionNecessary();
Log.i(TAG, "Making websocket connection....");
pipe = receiver.createMessagePipe();
unidentifiedPipe = receiver.createUnidentifiedMessagePipe();
SignalServiceMessagePipe localPipe = pipe;
SignalServiceMessagePipe unidentifiedLocalPipe = unidentifiedPipe;
try {
while (isConnectionNecessary()) {
try {
Log.i(TAG, "Reading message...");
localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
envelope -> {
Log.i(TAG, "Retrieved envelope! " + String.valueOf(envelope.getSource()));
new PushContentReceiveJob(context).processEnvelope(envelope, false);
});
} catch (TimeoutException e) {
Log.w(TAG, "Application level read timeout...");
} catch (InvalidVersionException e) {
Log.w(TAG, e);
}
}
} catch (Throwable e) {
Log.w(TAG, e);
} finally {
Log.w(TAG, "Shutting down pipe...");
shutdown(localPipe, unidentifiedLocalPipe);
}
Log.i(TAG, "Looping...");
}
}
@Override
public void uncaughtException(Thread t, Throwable e) {
Log.w(TAG, "*** Uncaught exception!");
Log.w(TAG, e);
}
}
public static class ForegroundService extends Service {
@Override
public @Nullable IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), NotificationChannels.OTHER);
builder.setContentTitle(getApplicationContext().getString(R.string.MessageRetrievalService_signal));
builder.setContentText(getApplicationContext().getString(R.string.MessageRetrievalService_background_connection_enabled));
builder.setPriority(NotificationCompat.PRIORITY_MIN);
builder.setWhen(0);
builder.setSmallIcon(R.drawable.ic_notification);
startForeground(FOREGROUND_ID, builder.build());
return Service.START_STICKY;
}
}
}

View File

@ -108,8 +108,6 @@ object TextSecurePreferences {
private const val NOTIFICATION_CHANNEL_VERSION = "pref_notification_channel_version"
private const val NOTIFICATION_MESSAGES_CHANNEL_VERSION = "pref_notification_messages_channel_version"
private const val NEEDS_MESSAGE_PULL = "pref_needs_message_pull"
const val UNIVERSAL_UNIDENTIFIED_ACCESS = "pref_universal_unidentified_access"
const val TYPING_INDICATORS = "pref_typing_indicators"
@ -766,16 +764,6 @@ object TextSecurePreferences {
setIntegerPrefrence(context, NOTIFICATION_MESSAGES_CHANNEL_VERSION, version)
}
@JvmStatic
fun getNeedsMessagePull(context: Context): Boolean {
return getBooleanPreference(context, NEEDS_MESSAGE_PULL, false)
}
@JvmStatic
fun setNeedsMessagePull(context: Context, needsMessagePull: Boolean) {
setBooleanPreference(context, NEEDS_MESSAGE_PULL, needsMessagePull)
}
@JvmStatic
fun hasSeenStickerIntroTooltip(context: Context): Boolean {
return getBooleanPreference(context, SEEN_STICKER_INTRO_TOOLTIP, false)

View File

@ -1,275 +0,0 @@
/*
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api;
import com.google.protobuf.ByteString;
import org.session.libsignal.libsignal.InvalidVersionException;
import org.session.libsignal.libsignal.util.Pair;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
import org.session.libsignal.service.api.profiles.SignalServiceProfile;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.util.CredentialsProvider;
import org.session.libsignal.service.internal.push.AttachmentUploadAttributes;
import org.session.libsignal.service.internal.push.OutgoingPushMessageList;
import org.session.libsignal.service.internal.push.SendMessageResponse;
import org.session.libsignal.utilities.Base64;
import org.session.libsignal.utilities.JsonUtil;
import org.session.libsignal.service.internal.util.Util;
import org.session.libsignal.service.internal.websocket.WebSocketConnection;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.session.libsignal.service.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
import static org.session.libsignal.service.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 Optional<CredentialsProvider> credentialsProvider;
SignalServiceMessagePipe(WebSocketConnection websocket, Optional<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, TimeUnit)}
*
* Unlike {@link #read(long, 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
{
if (!credentialsProvider.isPresent()) {
throw new IllegalArgumentException("You can't read messages if you haven't specified credentials");
}
while (true) {
WebSocketRequestMessage request = websocket.readRequest(unit.toMillis(timeout));
WebSocketResponseMessage response = createWebSocketResponse(request);
boolean signalKeyEncrypted = isSignalKeyEncrypted(request);
try {
if (isSignalServiceEnvelope(request)) {
SignalServiceEnvelope envelope = new SignalServiceEnvelope(request.getBody().toByteArray(),
credentialsProvider.get().getSignalingKey(),
signalKeyEncrypted);
callback.onMessage(envelope);
return envelope;
}
} finally {
websocket.sendResponse(response);
}
}
}
public SendMessageResponse send(OutgoingPushMessageList list, Optional<UnidentifiedAccess> unidentifiedAccess) throws IOException {
try {
List<String> headers = new LinkedList<String>() {{
add("content-type:application/json");
}};
if (unidentifiedAccess.isPresent()) {
headers.add("Unidentified-Access-Key:" + Base64.encodeBytes(unidentifiedAccess.get().getUnidentifiedAccessKey()));
}
WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
.setId(SecureRandom.getInstance("SHA1PRNG").nextLong())
.setVerb("PUT")
.setPath(String.format("/v1/messages/%s", list.getDestination()))
.addAllHeaders(headers)
.setBody(ByteString.copyFrom(JsonUtil.toJson(list).getBytes()))
.build();
Pair<Integer, String> 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 e) {
throw new IOException(e);
} catch (ExecutionException e) {
throw new IOException(e);
} catch (TimeoutException e) {
throw new IOException(e);
}
}
public SignalServiceProfile getProfile(SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess) throws IOException {
try {
List<String> headers = new LinkedList<String>();
if (unidentifiedAccess.isPresent()) {
headers.add("Unidentified-Access-Key:" + Base64.encodeBytes(unidentifiedAccess.get().getUnidentifiedAccessKey()));
}
WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
.setId(SecureRandom.getInstance("SHA1PRNG").nextLong())
.setVerb("GET")
.setPath(String.format("/v1/profile/%s", address.getNumber()))
.addAllHeaders(headers)
.build();
Pair<Integer, String> 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 e) {
throw new IOException(e);
} catch (ExecutionException e) {
throw new IOException(e);
} catch (TimeoutException e) {
throw new IOException(e);
}
}
public AttachmentUploadAttributes getAttachmentUploadAttributes() throws IOException {
try {
WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
.setId(new SecureRandom().nextLong())
.setVerb("GET")
.setPath("/v2/attachments/form/upload")
.build();
Pair<Integer, String> 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(), AttachmentUploadAttributes.class);
} catch (InterruptedException e) {
throw new IOException(e);
} catch (ExecutionException e) {
throw new IOException(e);
} catch (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 boolean isSignalKeyEncrypted(WebSocketRequestMessage message) {
List<String> headers = message.getHeadersList();
if (headers == null || headers.isEmpty()) {
return true;
}
for (String header : headers) {
String[] parts = header.split(":");
if (parts.length == 2 && parts[0] != null && parts[0].trim().equalsIgnoreCase("X-Signal-Key")) {
if (parts[1] != null && parts[1].trim().equalsIgnoreCase("false")) {
return false;
}
}
}
return true;
}
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) {}
}
}

View File

@ -7,37 +7,17 @@
package org.session.libsignal.service.api;
import org.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream;
import org.session.libsignal.service.api.crypto.ProfileCipherInputStream;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.session.libsignal.service.api.messages.SignalServiceAttachment.ProgressListener;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
import org.session.libsignal.service.api.messages.SignalServiceStickerManifest;
import org.session.libsignal.service.api.profiles.SignalServiceProfile;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.util.CredentialsProvider;
import org.session.libsignal.service.api.util.SleepTimer;
import org.session.libsignal.service.api.websocket.ConnectivityListener;
import org.session.libsignal.service.internal.configuration.SignalServiceConfiguration;
import org.session.libsignal.service.internal.push.PushServiceSocket;
import org.session.libsignal.service.internal.push.SignalServiceEnvelopeEntity;
import org.session.libsignal.service.internal.sticker.StickerProtos;
import org.session.libsignal.service.internal.util.StaticCredentialsProvider;
import org.session.libsignal.service.internal.util.Util;
import org.session.libsignal.service.internal.websocket.WebSocketConnection;
import org.session.libsignal.service.loki.utilities.DownloadUtilities;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* The primary interface for receiving Signal Service messages.
@ -46,51 +26,6 @@ import java.util.List;
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class SignalServiceMessageReceiver {
private final PushServiceSocket socket;
private final SignalServiceConfiguration urls;
private final CredentialsProvider credentialsProvider;
private final String userAgent;
private final ConnectivityListener connectivityListener;
private final SleepTimer sleepTimer;
/**
* Construct a SignalServiceMessageReceiver.
*
* @param urls The URL of the Signal Service.
* @param user The Signal Service username (eg. phone number).
* @param password The Signal Service user password.
* @param signalingKey The 52 byte signaling key assigned to this user at registration.
*/
public SignalServiceMessageReceiver(SignalServiceConfiguration urls,
String user, String password,
String signalingKey, String userAgent,
ConnectivityListener listener,
SleepTimer timer)
{
this(urls, new StaticCredentialsProvider(user, password, signalingKey), userAgent, listener, timer);
}
/**
* Construct a SignalServiceMessageReceiver.
*
* @param urls The URL of the Signal Service.
* @param credentials The Signal Service user's credentials.
*/
public SignalServiceMessageReceiver(SignalServiceConfiguration urls,
CredentialsProvider credentials,
String userAgent,
ConnectivityListener listener,
SleepTimer timer)
{
this.urls = urls;
this.credentialsProvider = credentials;
this.socket = new PushServiceSocket(urls, credentials, userAgent);
this.userAgent = userAgent;
this.connectivityListener = listener;
this.sleepTimer = timer;
}
/**
* Retrieves a SignalServiceAttachment.
*
@ -139,105 +74,4 @@ public class SignalServiceMessageReceiver {
return AttachmentCipherInputStream.createForAttachment(destination, pointer.getSize().or(0), pointer.getKey(), pointer.getDigest().get());
}
public InputStream retrieveSticker(byte[] packId, byte[] packKey, int stickerId)
throws IOException, InvalidMessageException
{
byte[] data = socket.retrieveSticker(packId, stickerId);
return AttachmentCipherInputStream.createForStickerData(data, packKey);
}
/**
* Retrieves a {@link SignalServiceStickerManifest}.
*
* @param packId The 16-byte packId that identifies the sticker pack.
* @param packKey The 32-byte packKey that decrypts the sticker pack.
* @return The {@link SignalServiceStickerManifest} representing the sticker pack.
* @throws IOException
* @throws InvalidMessageException
*/
public SignalServiceStickerManifest retrieveStickerManifest(byte[] packId, byte[] packKey)
throws IOException, InvalidMessageException
{
byte[] manifestBytes = socket.retrieveStickerManifest(packId);
InputStream cipherStream = AttachmentCipherInputStream.createForStickerData(manifestBytes, packKey);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Util.copy(cipherStream, outputStream);
StickerProtos.Pack pack = StickerProtos.Pack.parseFrom(outputStream.toByteArray());
List<SignalServiceStickerManifest.StickerInfo> stickers = new ArrayList<SignalServiceStickerManifest.StickerInfo>(pack.getStickersCount());
SignalServiceStickerManifest.StickerInfo cover = pack.hasCover() ? new SignalServiceStickerManifest.StickerInfo(pack.getCover().getId(), pack.getCover().getEmoji())
: null;
for (StickerProtos.Pack.Sticker sticker : pack.getStickersList()) {
stickers.add(new SignalServiceStickerManifest.StickerInfo(sticker.getId(), sticker.getEmoji()));
}
return new SignalServiceStickerManifest(pack.getTitle(), pack.getAuthor(), cover, stickers);
}
/**
* Creates a pipe for receiving SignalService messages.
*
* Callers must call {@link SignalServiceMessagePipe#shutdown()} when finished with the pipe.
*
* @return A SignalServiceMessagePipe for receiving Signal Service messages.
*/
public SignalServiceMessagePipe createMessagePipe() {
WebSocketConnection webSocket = new WebSocketConnection(urls.getSignalServiceUrls()[0].getUrl(),
urls.getSignalServiceUrls()[0].getTrustStore(),
Optional.of(credentialsProvider), userAgent, connectivityListener,
sleepTimer);
return new SignalServiceMessagePipe(webSocket, Optional.of(credentialsProvider));
}
public SignalServiceMessagePipe createUnidentifiedMessagePipe() {
WebSocketConnection webSocket = new WebSocketConnection(urls.getSignalServiceUrls()[0].getUrl(),
urls.getSignalServiceUrls()[0].getTrustStore(),
Optional.<CredentialsProvider>absent(), userAgent, connectivityListener,
sleepTimer);
return new SignalServiceMessagePipe(webSocket, Optional.of(credentialsProvider));
}
public List<SignalServiceEnvelope> retrieveMessages(MessageReceivedCallback callback)
throws IOException
{
List<SignalServiceEnvelope> results = new LinkedList<SignalServiceEnvelope>();
List<SignalServiceEnvelopeEntity> entities = socket.getMessages();
for (SignalServiceEnvelopeEntity entity : entities) {
SignalServiceEnvelope envelope;
if (entity.getSource() != null && entity.getSourceDevice() > 0) {
envelope = new SignalServiceEnvelope(entity.getType(), entity.getSource(),
entity.getSourceDevice(), entity.getTimestamp(),
entity.getContent(), entity.getServerTimestamp());
} else {
envelope = new SignalServiceEnvelope(entity.getType(), entity.getTimestamp(),
entity.getContent(), entity.getServerTimestamp());
}
callback.onMessage(envelope);
results.add(envelope);
socket.acknowledgeMessage(entity.getSource(), entity.getTimestamp());
}
return results;
}
public interface MessageReceivedCallback {
public void onMessage(SignalServiceEnvelope envelope);
}
public static class NullMessageReceivedCallback implements MessageReceivedCallback {
@Override
public void onMessage(SignalServiceEnvelope envelope) {}
}
}

View File

@ -93,9 +93,6 @@ public class SignalServiceMessageSender {
private final IdentityKeyStore store;
private final SignalServiceAddress localAddress;
private final AtomicReference<Optional<SignalServiceMessagePipe>> pipe;
private final AtomicReference<Optional<SignalServiceMessagePipe>> unidentifiedPipe;
// Loki
private final String userPublicKey;
private final LokiAPIDatabaseProtocol apiDatabase;
@ -115,8 +112,6 @@ public class SignalServiceMessageSender {
*/
public SignalServiceMessageSender(String user, String password,
IdentityKeyStore store,
Optional<SignalServiceMessagePipe> pipe,
Optional<SignalServiceMessagePipe> unidentifiedPipe,
String userPublicKey,
LokiAPIDatabaseProtocol apiDatabase,
LokiThreadDatabaseProtocol threadDatabase,
@ -126,13 +121,11 @@ public class SignalServiceMessageSender {
LokiOpenGroupDatabaseProtocol openGroupDatabase,
Broadcaster broadcaster)
{
this(new StaticCredentialsProvider(user, password, null), store, pipe, unidentifiedPipe, userPublicKey, apiDatabase, threadDatabase, messageDatabase, sessionProtocolImpl, userDatabase, openGroupDatabase, broadcaster);
this(new StaticCredentialsProvider(user, password, null), store, userPublicKey, apiDatabase, threadDatabase, messageDatabase, sessionProtocolImpl, userDatabase, openGroupDatabase, broadcaster);
}
public SignalServiceMessageSender(CredentialsProvider credentialsProvider,
IdentityKeyStore store,
Optional<SignalServiceMessagePipe> pipe,
Optional<SignalServiceMessagePipe> unidentifiedPipe,
String userPublicKey,
LokiAPIDatabaseProtocol apiDatabase,
LokiThreadDatabaseProtocol threadDatabase,
@ -144,8 +137,6 @@ public class SignalServiceMessageSender {
{
this.store = store;
this.localAddress = new SignalServiceAddress(credentialsProvider.getUser());
this.pipe = new AtomicReference<>(pipe);
this.unidentifiedPipe = new AtomicReference<>(unidentifiedPipe);
this.userPublicKey = userPublicKey;
this.apiDatabase = apiDatabase;
this.threadDatabase = threadDatabase;
@ -223,11 +214,6 @@ public class SignalServiceMessageSender {
return sendMessage(messageID, recipients, unidentifiedAccess, timestamp, content, false, message.getTTL(), isClosedGroup, message.hasVisibleContent());
}
public void setMessagePipe(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) {
this.pipe.set(Optional.fromNullable(pipe));
this.unidentifiedPipe.set(Optional.fromNullable(unidentifiedPipe));
}
public SignalServiceAttachmentPointer uploadAttachment(SignalServiceAttachmentStream attachment, boolean usePadding, @Nullable SignalServiceAddress recipient)
throws IOException
{

View File

@ -1,69 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.push;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* A class that represents a contact's registration state.
*/
public class ContactTokenDetails {
@JsonProperty
private String token;
@JsonProperty
private String relay;
@JsonProperty
private String number;
@JsonProperty
private boolean voice;
@JsonProperty
private boolean video;
public ContactTokenDetails() {}
/**
* @return The "anonymized" token (truncated hash) that's transmitted to the server.
*/
public String getToken() {
return token;
}
/**
* @return The federated server this contact is registered with, or null if on your server.
*/
public String getRelay() {
return relay;
}
/**
* @return Whether this contact supports secure voice calls.
*/
public boolean isVoice() {
return voice;
}
public boolean isVideo() {
return video;
}
public void setNumber(String number) {
this.number = number;
}
/**
* @return This contact's username (e164 formatted number).
*/
public String getNumber() {
return number;
}
}

View File

@ -1,57 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.session.libsignal.libsignal.ecc.ECPublicKey;
import org.session.libsignal.service.internal.push.PreKeyEntity;
import org.session.libsignal.utilities.Base64;
import java.io.IOException;
public class SignedPreKeyEntity extends PreKeyEntity {
@JsonProperty
@JsonSerialize(using = ByteArraySerializer.class)
@JsonDeserialize(using = ByteArrayDeserializer.class)
private byte[] signature;
public SignedPreKeyEntity() {}
public SignedPreKeyEntity(int keyId, ECPublicKey publicKey, byte[] signature) {
super(keyId, publicKey);
this.signature = signature;
}
public byte[] getSignature() {
return signature;
}
private static class ByteArraySerializer extends JsonSerializer<byte[]> {
@Override
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(Base64.encodeBytesWithoutPadding(value));
}
}
private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
@Override
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return Base64.decodeWithoutPadding(p.getValueAsString());
}
}
}

View File

@ -1,15 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.push.exceptions;
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
public class AuthorizationFailedException extends NonSuccessfulResponseCodeException {
public AuthorizationFailedException(String s) {
super(s);
}
}

View File

@ -1,6 +0,0 @@
package org.session.libsignal.service.api.push.exceptions;
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
public class CaptchaRequiredException extends NonSuccessfulResponseCodeException {
}

View File

@ -1,49 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.push.exceptions;
import org.session.libsignal.service.api.crypto.UntrustedIdentityException;
import org.session.libsignal.service.api.push.exceptions.NetworkFailureException;
import org.session.libsignal.service.api.push.exceptions.UnregisteredUserException;
import java.util.LinkedList;
import java.util.List;
public class EncapsulatedExceptions extends Throwable {
private final List<UntrustedIdentityException> untrustedIdentityExceptions;
private final List<UnregisteredUserException> unregisteredUserExceptions;
private final List<NetworkFailureException> networkExceptions;
public EncapsulatedExceptions(List<UntrustedIdentityException> untrustedIdentities,
List<UnregisteredUserException> unregisteredUsers,
List<NetworkFailureException> networkExceptions)
{
this.untrustedIdentityExceptions = untrustedIdentities;
this.unregisteredUserExceptions = unregisteredUsers;
this.networkExceptions = networkExceptions;
}
public EncapsulatedExceptions(UntrustedIdentityException e) {
this.untrustedIdentityExceptions = new LinkedList<UntrustedIdentityException>();
this.unregisteredUserExceptions = new LinkedList<UnregisteredUserException>();
this.networkExceptions = new LinkedList<NetworkFailureException>();
this.untrustedIdentityExceptions.add(e);
}
public List<UntrustedIdentityException> getUntrustedIdentityExceptions() {
return untrustedIdentityExceptions;
}
public List<UnregisteredUserException> getUnregisteredUserExceptions() {
return unregisteredUserExceptions;
}
public List<NetworkFailureException> getNetworkExceptions() {
return networkExceptions;
}
}

View File

@ -1,11 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.push.exceptions;
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
public class ExpectationFailedException extends NonSuccessfulResponseCodeException {
}

View File

@ -1,21 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.push.exceptions;
public class NetworkFailureException extends Exception {
private final String e164number;
public NetworkFailureException(String e164number, Exception nested) {
super(nested);
this.e164number = e164number;
}
public String getE164number() {
return e164number;
}
}

View File

@ -10,10 +10,6 @@ import java.io.IOException;
public class NonSuccessfulResponseCodeException extends IOException {
public NonSuccessfulResponseCodeException() {
super();
}
public NonSuccessfulResponseCodeException(String s) {
super(s);
}

View File

@ -1,13 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.push.exceptions;
public class NotFoundException extends NonSuccessfulResponseCodeException {
public NotFoundException(String s) {
super(s);
}
}

View File

@ -1,15 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.push.exceptions;
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
public class RateLimitException extends NonSuccessfulResponseCodeException {
public RateLimitException(String s) {
super(s);
}
}

View File

@ -1,13 +0,0 @@
/*
* Copyright (C) 2019 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.push.exceptions;
public class RemoteAttestationResponseExpiredException extends NonSuccessfulResponseCodeException {
public RemoteAttestationResponseExpiredException(String message) {
super(message);
}
}

View File

@ -1,81 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class AccountAttributes {
@JsonProperty
private String signalingKey;
@JsonProperty
private int registrationId;
@JsonProperty
private boolean voice;
@JsonProperty
private boolean video;
@JsonProperty
private boolean fetchesMessages;
@JsonProperty
private String pin;
@JsonProperty
private byte[] unidentifiedAccessKey;
@JsonProperty
private boolean unrestrictedUnidentifiedAccess;
public AccountAttributes(String signalingKey, int registrationId, boolean fetchesMessages, String pin, byte[] unidentifiedAccessKey, boolean unrestrictedUnidentifiedAccess) {
this.signalingKey = signalingKey;
this.registrationId = registrationId;
this.voice = true;
this.video = true;
this.fetchesMessages = fetchesMessages;
this.pin = pin;
this.unidentifiedAccessKey = unidentifiedAccessKey;
this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess;
}
public AccountAttributes() {}
public String getSignalingKey() {
return signalingKey;
}
public int getRegistrationId() {
return registrationId;
}
public boolean isVoice() {
return voice;
}
public boolean isVideo() {
return video;
}
public boolean isFetchesMessages() {
return fetchesMessages;
}
public String getPin() {
return pin;
}
public byte[] getUnidentifiedAccessKey() {
return unidentifiedAccessKey;
}
public boolean isUnrestrictedUnidentifiedAccess() {
return unrestrictedUnidentifiedAccess;
}
}

View File

@ -1,78 +0,0 @@
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class AttachmentUploadAttributes {
@JsonProperty
private String url;
@JsonProperty
private String key;
@JsonProperty
private String credential;
@JsonProperty
private String acl;
@JsonProperty
private String algorithm;
@JsonProperty
private String date;
@JsonProperty
private String policy;
@JsonProperty
private String signature;
@JsonProperty
private String attachmentId;
@JsonProperty
private String attachmentIdString;
public AttachmentUploadAttributes() {}
public String getUrl() {
return url;
}
public String getKey() {
return key;
}
public String getCredential() {
return credential;
}
public String getAcl() {
return acl;
}
public String getAlgorithm() {
return algorithm;
}
public String getDate() {
return date;
}
public String getPolicy() {
return policy;
}
public String getSignature() {
return signature;
}
public String getAttachmentId() {
return attachmentId;
}
public String getAttachmentIdString() {
return attachmentIdString;
}
}

View File

@ -1,28 +0,0 @@
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ContactDiscoveryCredentials {
@JsonProperty
private String username;
@JsonProperty
private String password;
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}

View File

@ -1,13 +0,0 @@
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ContactDiscoveryFailureReason {
@JsonProperty
private final String reason;
public ContactDiscoveryFailureReason(String reason) {
this.reason = reason == null ? "" : reason;
}
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.session.libsignal.service.api.push.ContactTokenDetails;
import java.util.List;
public class ContactTokenDetailsList {
@JsonProperty
private List<ContactTokenDetails> contacts;
public ContactTokenDetailsList() {}
public List<ContactTokenDetails> getContacts() {
return contacts;
}
}

View File

@ -1,24 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.internal.push;
import java.util.List;
public class ContactTokenList {
private List<String> contacts;
public ContactTokenList(List<String> contacts) {
this.contacts = contacts;
}
public ContactTokenList() {}
public List<String> getContacts() {
return contacts;
}
}

View File

@ -1,13 +0,0 @@
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DeviceCode {
@JsonProperty
private String verificationCode;
public String getVerificationCode() {
return verificationCode;
}
}

View File

@ -1,20 +0,0 @@
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DeviceLimit {
@JsonProperty
private int current;
@JsonProperty
private int max;
public int getCurrent() {
return current;
}
public int getMax() {
return max;
}
}

View File

@ -1,20 +0,0 @@
package org.session.libsignal.service.internal.push;
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
public class DeviceLimitExceededException extends NonSuccessfulResponseCodeException {
private final DeviceLimit deviceLimit;
public DeviceLimitExceededException(DeviceLimit deviceLimit) {
this.deviceLimit = deviceLimit;
}
public int getCurrent() {
return deviceLimit.getCurrent();
}
public int getMax() {
return deviceLimit.getMax();
}
}

View File

@ -1,23 +0,0 @@
package org.session.libsignal.service.internal.push;
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
public class LockedException extends NonSuccessfulResponseCodeException {
private int length;
private long timeRemaining;
LockedException(int length, long timeRemaining) {
this.length = length;
this.timeRemaining = timeRemaining;
}
public int getLength() {
return length;
}
public long getTimeRemaining() {
return timeRemaining;
}
}

View File

@ -1,27 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class MismatchedDevices {
@JsonProperty
private List<Integer> missingDevices;
@JsonProperty
private List<Integer> extraDevices;
public List<Integer> getMissingDevices() {
return missingDevices;
}
public List<Integer> getExtraDevices() {
return extraDevices;
}
}

View File

@ -1,68 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.session.libsignal.libsignal.InvalidKeyException;
import org.session.libsignal.libsignal.ecc.Curve;
import org.session.libsignal.libsignal.ecc.ECPublicKey;
import org.session.libsignal.utilities.Base64;
import java.io.IOException;
public class PreKeyEntity {
@JsonProperty
private int keyId;
@JsonProperty
@JsonSerialize(using = ECPublicKeySerializer.class)
@JsonDeserialize(using = ECPublicKeyDeserializer.class)
private ECPublicKey publicKey;
public PreKeyEntity() {}
public PreKeyEntity(int keyId, ECPublicKey publicKey) {
this.keyId = keyId;
this.publicKey = publicKey;
}
public int getKeyId() {
return keyId;
}
public ECPublicKey getPublicKey() {
return publicKey;
}
private static class ECPublicKeySerializer extends JsonSerializer<ECPublicKey> {
@Override
public void serialize(ECPublicKey value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(Base64.encodeBytesWithoutPadding(value.serialize()));
}
}
private static class ECPublicKeyDeserializer extends JsonDeserializer<ECPublicKey> {
@Override
public ECPublicKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
try {
return Curve.decodePoint(Base64.decodeWithoutPadding(p.getValueAsString()), 0);
} catch (InvalidKeyException e) {
throw new IOException(e);
}
}
}
}

View File

@ -1,37 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.utilities.JsonUtil;
import java.util.List;
public class PreKeyResponse {
@JsonProperty
@JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
private IdentityKey identityKey;
@JsonProperty
private List<PreKeyResponseItem> devices;
public IdentityKey getIdentityKey() {
return identityKey;
}
public List<PreKeyResponseItem> getDevices() {
return devices;
}
}

View File

@ -1,43 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.session.libsignal.service.api.push.SignedPreKeyEntity;
public class PreKeyResponseItem {
@JsonProperty
private int deviceId;
@JsonProperty
private int registrationId;
@JsonProperty
private SignedPreKeyEntity signedPreKey;
@JsonProperty
private PreKeyEntity preKey;
public int getDeviceId() {
return deviceId;
}
public int getRegistrationId() {
return registrationId;
}
public SignedPreKeyEntity getSignedPreKey() {
return signedPreKey;
}
public PreKeyEntity getPreKey() {
return preKey;
}
}

View File

@ -1,33 +0,0 @@
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.service.api.push.SignedPreKeyEntity;
import org.session.libsignal.utilities.JsonUtil;
import java.util.List;
public class PreKeyState {
@JsonProperty
@JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
private IdentityKey identityKey;
@JsonProperty
private List<PreKeyEntity> preKeys;
@JsonProperty
private SignedPreKeyEntity signedPreKey;
public PreKeyState(List<PreKeyEntity> preKeys, SignedPreKeyEntity signedPreKey, IdentityKey identityKey) {
this.preKeys = preKeys;
this.signedPreKey = signedPreKey;
this.identityKey = identityKey;
}
}

View File

@ -1,21 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class PreKeyStatus {
@JsonProperty
private int count;
public PreKeyStatus() {}
public int getCount() {
return count;
}
}

View File

@ -1,64 +0,0 @@
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ProfileAvatarUploadAttributes {
@JsonProperty
private String url;
@JsonProperty
private String key;
@JsonProperty
private String credential;
@JsonProperty
private String acl;
@JsonProperty
private String algorithm;
@JsonProperty
private String date;
@JsonProperty
private String policy;
@JsonProperty
private String signature;
public ProfileAvatarUploadAttributes() {}
public String getUrl() {
return url;
}
public String getKey() {
return key;
}
public String getCredential() {
return credential;
}
public String getAcl() {
return acl;
}
public String getAlgorithm() {
return algorithm;
}
public String getDate() {
return date;
}
public String getPolicy() {
return policy;
}
public String getSignature() {
return signature;
}
}

View File

@ -1,14 +0,0 @@
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ProvisioningMessage {
@JsonProperty
private String body;
public ProvisioningMessage(String body) {
this.body = body;
}
}

View File

@ -1,669 +0,0 @@
/*
* Copyright (C) 2014-2017 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.libsignal.ecc.ECPublicKey;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.libsignal.util.Pair;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.session.libsignal.service.api.messages.SignalServiceAttachment.ProgressListener;
import org.session.libsignal.service.api.profiles.SignalServiceProfile;
import org.session.libsignal.service.api.push.ContactTokenDetails;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.push.SignedPreKeyEntity;
import org.session.libsignal.service.api.push.exceptions.AuthorizationFailedException;
import org.session.libsignal.service.api.push.exceptions.CaptchaRequiredException;
import org.session.libsignal.service.api.push.exceptions.ExpectationFailedException;
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.session.libsignal.service.api.push.exceptions.NotFoundException;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import org.session.libsignal.service.api.push.exceptions.RateLimitException;
import org.session.libsignal.service.api.push.exceptions.RemoteAttestationResponseExpiredException;
import org.session.libsignal.service.api.push.exceptions.UnregisteredUserException;
import org.session.libsignal.service.api.util.CredentialsProvider;
import org.session.libsignal.service.api.util.Tls12SocketFactory;
import org.session.libsignal.service.internal.configuration.SignalServiceConfiguration;
import org.session.libsignal.service.internal.configuration.SignalUrl;
import org.session.libsignal.service.internal.contacts.entities.DiscoveryRequest;
import org.session.libsignal.service.internal.contacts.entities.DiscoveryResponse;
import org.session.libsignal.service.internal.contacts.entities.RemoteAttestationRequest;
import org.session.libsignal.service.internal.contacts.entities.RemoteAttestationResponse;
import org.session.libsignal.service.internal.push.exceptions.MismatchedDevicesException;
import org.session.libsignal.service.internal.push.exceptions.StaleDevicesException;
import org.session.libsignal.service.internal.push.http.DigestingRequestBody;
import org.session.libsignal.service.internal.push.http.OutputStreamFactory;
import org.session.libsignal.utilities.Base64;
import org.session.libsignal.service.internal.util.BlacklistingTrustManager;
import org.session.libsignal.utilities.Hex;
import org.session.libsignal.utilities.JsonUtil;
import org.session.libsignal.service.internal.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.Call;
import okhttp3.ConnectionSpec;
import okhttp3.Credentials;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
/**
* @author Moxie Marlinspike
*/
public class PushServiceSocket {
private static final String TAG = PushServiceSocket.class.getSimpleName();
private static final String CREATE_ACCOUNT_SMS_PATH = "/v1/accounts/sms/code/%s?client=%s";
private static final String CREATE_ACCOUNT_VOICE_PATH = "/v1/accounts/voice/code/%s";
private static final String VERIFY_ACCOUNT_CODE_PATH = "/v1/accounts/code/%s";
private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/";
private static final String TURN_SERVER_INFO = "/v1/accounts/turn";
private static final String SET_ACCOUNT_ATTRIBUTES = "/v1/accounts/attributes/";
private static final String PIN_PATH = "/v1/accounts/pin/";
private static final String PREKEY_METADATA_PATH = "/v2/keys/";
private static final String PREKEY_PATH = "/v2/keys/%s";
private static final String PREKEY_DEVICE_PATH = "/v2/keys/%s/%s";
private static final String SIGNED_PREKEY_PATH = "/v2/keys/signed";
private static final String PROVISIONING_CODE_PATH = "/v1/devices/provisioning/code";
private static final String PROVISIONING_MESSAGE_PATH = "/v1/provisioning/%s";
private static final String DEVICE_PATH = "/v1/devices/%s";
private static final String DIRECTORY_TOKENS_PATH = "/v1/directory/tokens";
private static final String DIRECTORY_VERIFY_PATH = "/v1/directory/%s";
private static final String DIRECTORY_AUTH_PATH = "/v1/directory/auth";
private static final String DIRECTORY_FEEDBACK_PATH = "/v1/directory/feedback-v3/%s";
private static final String MESSAGE_PATH = "/v1/messages/%s";
private static final String SENDER_ACK_MESSAGE_PATH = "/v1/messages/%s/%d";
private static final String UUID_ACK_MESSAGE_PATH = "/v1/messages/uuid/%s";
private static final String ATTACHMENT_PATH = "/v2/attachments/form/upload";
private static final String PROFILE_PATH = "/v1/profile/%s";
private static final String SENDER_CERTIFICATE_PATH = "/v1/certificate/delivery";
private static final String ATTACHMENT_DOWNLOAD_PATH = "attachments/%d";
private static final String ATTACHMENT_UPLOAD_PATH = "attachments/";
private static final String STICKER_MANIFEST_PATH = "stickers/%s/manifest.proto";
private static final String STICKER_PATH = "stickers/%s/full/%d";
private static final Map<String, String> NO_HEADERS = Collections.emptyMap();
private static final ResponseCodeHandler NO_HANDLER = new EmptyResponseCodeHandler();
private long soTimeoutMillis = TimeUnit.SECONDS.toMillis(30);
private final Set<Call> connections = new HashSet<Call>();
private final ServiceConnectionHolder[] serviceClients;
private final ConnectionHolder[] cdnClients;
private final ConnectionHolder[] contactDiscoveryClients;
private final OkHttpClient attachmentClient;
private final CredentialsProvider credentialsProvider;
private final String userAgent;
private final SecureRandom random;
public PushServiceSocket(SignalServiceConfiguration signalServiceConfiguration, CredentialsProvider credentialsProvider, String userAgent) {
this.credentialsProvider = credentialsProvider;
this.userAgent = userAgent;
this.serviceClients = createServiceConnectionHolders(signalServiceConfiguration.getSignalServiceUrls());
this.cdnClients = createConnectionHolders(signalServiceConfiguration.getSignalCdnUrls());
this.contactDiscoveryClients = createConnectionHolders(signalServiceConfiguration.getSignalContactDiscoveryUrls());
this.attachmentClient = createAttachmentClient();
this.random = new SecureRandom();
}
public List<SignalServiceEnvelopeEntity> getMessages() throws IOException {
String responseText = makeServiceRequest(String.format(MESSAGE_PATH, ""), "GET", null);
return JsonUtil.fromJson(responseText, SignalServiceEnvelopeEntityList.class).getMessages();
}
public void acknowledgeMessage(String sender, long timestamp) throws IOException {
makeServiceRequest(String.format(SENDER_ACK_MESSAGE_PATH, sender, timestamp), "DELETE", null);
}
public byte[] retrieveSticker(byte[] packId, int stickerId)
throws NonSuccessfulResponseCodeException, PushNetworkException
{
String hexPackId = Hex.toStringCondensed(packId);
ByteArrayOutputStream output = new ByteArrayOutputStream();
downloadFromCdn(output, String.format(STICKER_PATH, hexPackId, stickerId), 1024 * 1024, null);
return output.toByteArray();
}
public byte[] retrieveStickerManifest(byte[] packId)
throws NonSuccessfulResponseCodeException, PushNetworkException
{
String hexPackId = Hex.toStringCondensed(packId);
ByteArrayOutputStream output = new ByteArrayOutputStream();
downloadFromCdn(output, String.format(STICKER_MANIFEST_PATH, hexPackId), 1024 * 1024, null);
return output.toByteArray();
}
public SignalServiceProfile retrieveProfile(SignalServiceAddress target, Optional<UnidentifiedAccess> unidentifiedAccess)
throws NonSuccessfulResponseCodeException, PushNetworkException
{
try {
String response = makeServiceRequest(String.format(PROFILE_PATH, target.getNumber()), "GET", null, NO_HEADERS, unidentifiedAccess);
return JsonUtil.fromJson(response, SignalServiceProfile.class);
} catch (IOException e) {
Log.w(TAG, e);
throw new NonSuccessfulResponseCodeException("Unable to parse entity");
}
}
private void downloadFromCdn(OutputStream outputStream, String path, int maxSizeBytes, ProgressListener listener)
throws PushNetworkException, NonSuccessfulResponseCodeException
{
ConnectionHolder connectionHolder = getRandom(cdnClients, random);
OkHttpClient okHttpClient = connectionHolder.getClient()
.newBuilder()
.connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.build();
Request.Builder request = new Request.Builder().url(connectionHolder.getUrl() + "/" + path).get();
if (connectionHolder.getHostHeader().isPresent()) {
request.addHeader("Host", connectionHolder.getHostHeader().get());
}
Call call = okHttpClient.newCall(request.build());
synchronized (connections) {
connections.add(call);
}
Response response;
try {
response = call.execute();
if (response.isSuccessful()) {
ResponseBody body = response.body();
if (body == null) throw new PushNetworkException("No response body!");
if (body.contentLength() > maxSizeBytes) throw new PushNetworkException("Response exceeds max size!");
InputStream in = body.byteStream();
byte[] buffer = new byte[32768];
int read, totalRead = 0;
while ((read = in.read(buffer, 0, buffer.length)) != -1) {
outputStream.write(buffer, 0, read);
if ((totalRead += read) > maxSizeBytes) throw new PushNetworkException("Response exceeded max size!");
if (listener != null) {
listener.onAttachmentProgress(body.contentLength(), totalRead);
}
}
return;
}
} catch (IOException e) {
throw new PushNetworkException(e);
} finally {
synchronized (connections) {
connections.remove(call);
}
}
throw new NonSuccessfulResponseCodeException("Response: " + response);
}
private String makeServiceRequest(String urlFragment, String method, String body)
throws NonSuccessfulResponseCodeException, PushNetworkException
{
return makeServiceRequest(urlFragment, method, body, NO_HEADERS, NO_HANDLER, Optional.<UnidentifiedAccess>absent());
}
private String makeServiceRequest(String urlFragment, String method, String body, Map<String, String> headers)
throws NonSuccessfulResponseCodeException, PushNetworkException
{
return makeServiceRequest(urlFragment, method, body, headers, NO_HANDLER, Optional.<UnidentifiedAccess>absent());
}
private String makeServiceRequest(String urlFragment, String method, String body, Map<String, String> headers, ResponseCodeHandler responseCodeHandler)
throws NonSuccessfulResponseCodeException, PushNetworkException
{
return makeServiceRequest(urlFragment, method, body, headers, responseCodeHandler, Optional.<UnidentifiedAccess>absent());
}
private String makeServiceRequest(String urlFragment, String method, String body, Map<String, String> headers, Optional<UnidentifiedAccess> unidentifiedAccessKey)
throws NonSuccessfulResponseCodeException, PushNetworkException
{
return makeServiceRequest(urlFragment, method, body, headers, NO_HANDLER, unidentifiedAccessKey);
}
private String makeServiceRequest(String urlFragment, String method, String body, Map<String, String> headers, ResponseCodeHandler responseCodeHandler, Optional<UnidentifiedAccess> unidentifiedAccessKey)
throws NonSuccessfulResponseCodeException, PushNetworkException
{
Response response = getServiceConnection(urlFragment, method, body, headers, unidentifiedAccessKey);
int responseCode;
String responseMessage;
String responseBody;
try {
responseCode = response.code();
responseMessage = response.message();
responseBody = response.body().string();
} catch (IOException ioe) {
throw new PushNetworkException(ioe);
}
responseCodeHandler.handle(responseCode);
switch (responseCode) {
case 413:
throw new RateLimitException("Rate limit exceeded: " + responseCode);
case 401:
case 403:
throw new AuthorizationFailedException("Authorization failed!");
case 404:
throw new NotFoundException("Not found");
case 409:
MismatchedDevices mismatchedDevices;
try {
mismatchedDevices = JsonUtil.fromJson(responseBody, MismatchedDevices.class);
} catch (JsonProcessingException e) {
Log.w(TAG, e);
throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage);
} catch (IOException e) {
throw new PushNetworkException(e);
}
throw new MismatchedDevicesException(mismatchedDevices);
case 410:
StaleDevices staleDevices;
try {
staleDevices = JsonUtil.fromJson(responseBody, StaleDevices.class);
} catch (JsonProcessingException e) {
throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage);
} catch (IOException e) {
throw new PushNetworkException(e);
}
throw new StaleDevicesException(staleDevices);
case 411:
DeviceLimit deviceLimit;
try {
deviceLimit = JsonUtil.fromJson(responseBody, DeviceLimit.class);
} catch (JsonProcessingException e) {
throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage);
} catch (IOException e) {
throw new PushNetworkException(e);
}
throw new DeviceLimitExceededException(deviceLimit);
case 417:
throw new ExpectationFailedException();
case 423:
RegistrationLockFailure accountLockFailure;
try {
accountLockFailure = JsonUtil.fromJson(responseBody, RegistrationLockFailure.class);
} catch (JsonProcessingException e) {
Log.w(TAG, e);
throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage);
} catch (IOException e) {
throw new PushNetworkException(e);
}
throw new LockedException(accountLockFailure.length, accountLockFailure.timeRemaining);
}
if (responseCode != 200 && responseCode != 204) {
throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " +
responseMessage);
}
return responseBody;
}
private Response getServiceConnection(String urlFragment, String method, String body, Map<String, String> headers, Optional<UnidentifiedAccess> unidentifiedAccess)
throws PushNetworkException
{
try {
ServiceConnectionHolder connectionHolder = (ServiceConnectionHolder) getRandom(serviceClients, random);
OkHttpClient baseClient = unidentifiedAccess.isPresent() ? connectionHolder.getUnidentifiedClient() : connectionHolder.getClient();
OkHttpClient okHttpClient = baseClient.newBuilder()
.connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.build();
Log.w(TAG, "Push service URL: " + connectionHolder.getUrl());
Log.w(TAG, "Opening URL: " + String.format("%s%s", connectionHolder.getUrl(), urlFragment));
Request.Builder request = new Request.Builder();
request.url(String.format("%s%s", connectionHolder.getUrl(), urlFragment));
if (body != null) {
request.method(method, RequestBody.create(MediaType.parse("application/json"), body));
} else {
request.method(method, null);
}
for (Map.Entry<String, String> header : headers.entrySet()) {
request.addHeader(header.getKey(), header.getValue());
}
if (unidentifiedAccess.isPresent()) {
request.addHeader("Unidentified-Access-Key", Base64.encodeBytes(unidentifiedAccess.get().getUnidentifiedAccessKey()));
} else if (credentialsProvider.getPassword() != null) {
request.addHeader("Authorization", getAuthorizationHeader(credentialsProvider));
}
if (userAgent != null) {
request.addHeader("X-Signal-Agent", userAgent);
}
if (connectionHolder.getHostHeader().isPresent()) {
request.addHeader("Host", connectionHolder.getHostHeader().get());
}
Call call = okHttpClient.newCall(request.build());
synchronized (connections) {
connections.add(call);
}
try {
return call.execute();
} finally {
synchronized (connections) {
connections.remove(call);
}
}
} catch (IOException e) {
throw new PushNetworkException(e);
}
}
private Response makeContactDiscoveryRequest(String authorization, List<String> cookies, String path, String method, String body)
throws PushNetworkException, NonSuccessfulResponseCodeException
{
ConnectionHolder connectionHolder = getRandom(contactDiscoveryClients, random);
OkHttpClient okHttpClient = connectionHolder.getClient()
.newBuilder()
.connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.build();
Request.Builder request = new Request.Builder().url(connectionHolder.getUrl() + path);
if (body != null) {
request.method(method, RequestBody.create(MediaType.parse("application/json"), body));
} else {
request.method(method, null);
}
if (connectionHolder.getHostHeader().isPresent()) {
request.addHeader("Host", connectionHolder.getHostHeader().get());
}
if (authorization != null) {
request.addHeader("Authorization", authorization);
}
if (cookies != null && !cookies.isEmpty()) {
request.addHeader("Cookie", Util.join(cookies, "; "));
}
Call call = okHttpClient.newCall(request.build());
synchronized (connections) {
connections.add(call);
}
Response response;
try {
response = call.execute();
if (response.isSuccessful()) {
return response;
}
} catch (IOException e) {
throw new PushNetworkException(e);
} finally {
synchronized (connections) {
connections.remove(call);
}
}
switch (response.code()) {
case 401:
case 403:
throw new AuthorizationFailedException("Authorization failed!");
case 409:
throw new RemoteAttestationResponseExpiredException("Remote attestation response expired");
case 429:
throw new RateLimitException("Rate limit exceeded: " + response.code());
}
throw new NonSuccessfulResponseCodeException("Response: " + response);
}
private ServiceConnectionHolder[] createServiceConnectionHolders(SignalUrl[] urls) {
List<ServiceConnectionHolder> serviceConnectionHolders = new LinkedList<ServiceConnectionHolder>();
for (SignalUrl url : urls) {
serviceConnectionHolders.add(new ServiceConnectionHolder(createConnectionClient(url),
createConnectionClient(url),
url.getUrl(), url.getHostHeader()));
}
return serviceConnectionHolders.toArray(new ServiceConnectionHolder[0]);
}
private ConnectionHolder[] createConnectionHolders(SignalUrl[] urls) {
List<ConnectionHolder> connectionHolders = new LinkedList<ConnectionHolder>();
for (SignalUrl url : urls) {
connectionHolders.add(new ConnectionHolder(createConnectionClient(url), url.getUrl(), url.getHostHeader()));
}
return connectionHolders.toArray(new ConnectionHolder[0]);
}
private OkHttpClient createConnectionClient(SignalUrl url) {
try {
TrustManager[] trustManagers = BlacklistingTrustManager.createFor(url.getTrustStore());
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, null);
return new OkHttpClient.Builder()
.sslSocketFactory(new Tls12SocketFactory(context.getSocketFactory()), (X509TrustManager)trustManagers[0])
.connectionSpecs(url.getConnectionSpecs().or(Util.immutableList(ConnectionSpec.RESTRICTED_TLS)))
.build();
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (KeyManagementException e) {
throw new AssertionError(e);
}
}
private OkHttpClient createAttachmentClient() {
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore)null);
return new OkHttpClient.Builder()
.sslSocketFactory(new Tls12SocketFactory(context.getSocketFactory()),
(X509TrustManager)trustManagerFactory.getTrustManagers()[0])
.connectionSpecs(Util.immutableList(ConnectionSpec.RESTRICTED_TLS))
.build();
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (KeyManagementException e) {
throw new AssertionError(e);
} catch (KeyStoreException e) {
throw new AssertionError(e);
}
}
private String getAuthorizationHeader(CredentialsProvider credentialsProvider) {
try {
return "Basic " + Base64.encodeBytes((credentialsProvider.getUser() + ":" + credentialsProvider.getPassword()).getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
private ConnectionHolder getRandom(ConnectionHolder[] connections, SecureRandom random) {
return connections[random.nextInt(connections.length)];
}
private static class GcmRegistrationId {
@JsonProperty
private String gcmRegistrationId;
@JsonProperty
private boolean webSocketChannel;
public GcmRegistrationId() {}
public GcmRegistrationId(String gcmRegistrationId, boolean webSocketChannel) {
this.gcmRegistrationId = gcmRegistrationId;
this.webSocketChannel = webSocketChannel;
}
}
private static class RegistrationLock {
@JsonProperty
private String pin;
public RegistrationLock() {}
public RegistrationLock(String pin) {
this.pin = pin;
}
}
private static class RegistrationLockFailure {
@JsonProperty
private int length;
@JsonProperty
private long timeRemaining;
}
private static class AttachmentDescriptor {
@JsonProperty
private long id;
@JsonProperty
private String location;
public long getId() {
return id;
}
public String getLocation() {
return location;
}
}
private static class ConnectionHolder {
private final OkHttpClient client;
private final String url;
private final Optional<String> hostHeader;
private ConnectionHolder(OkHttpClient client, String url, Optional<String> hostHeader) {
this.client = client;
this.url = url;
this.hostHeader = hostHeader;
}
OkHttpClient getClient() {
return client;
}
public String getUrl() {
return url;
}
Optional<String> getHostHeader() {
return hostHeader;
}
}
private static class ServiceConnectionHolder extends ConnectionHolder {
private final OkHttpClient unidentifiedClient;
private ServiceConnectionHolder(OkHttpClient identifiedClient, OkHttpClient unidentifiedClient, String url, Optional<String> hostHeader) {
super(identifiedClient, url, hostHeader);
this.unidentifiedClient = unidentifiedClient;
}
OkHttpClient getUnidentifiedClient() {
return unidentifiedClient;
}
}
private interface ResponseCodeHandler {
void handle(int responseCode) throws NonSuccessfulResponseCodeException, PushNetworkException;
}
private static class EmptyResponseCodeHandler implements ResponseCodeHandler {
@Override
public void handle(int responseCode) { }
}
}

View File

@ -1,16 +0,0 @@
package org.session.libsignal.service.internal.push;
public class SendMessageResponse {
private boolean needsSync;
public SendMessageResponse() {}
public SendMessageResponse(boolean needsSync) {
this.needsSync = needsSync;
}
public boolean getNeedsSync() {
return needsSync;
}
}

View File

@ -1,45 +0,0 @@
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.session.libsignal.utilities.Base64;
import java.io.IOException;
public class SenderCertificate {
@JsonProperty
@JsonDeserialize(using = ByteArrayDesieralizer.class)
@JsonSerialize(using = ByteArraySerializer.class)
private byte[] certificate;
public SenderCertificate() {}
public byte[] getCertificate() {
return certificate;
}
public static class ByteArraySerializer extends JsonSerializer<byte[]> {
@Override
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(Base64.encodeBytes(value));
}
}
public static class ByteArrayDesieralizer extends JsonDeserializer<byte[]> {
@Override
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return Base64.decode(p.getValueAsString());
}
}
}

View File

@ -1,57 +0,0 @@
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class SignalServiceEnvelopeEntity {
@JsonProperty
private int type;
@JsonProperty
private String relay;
@JsonProperty
private long timestamp;
@JsonProperty
private String source;
@JsonProperty
private int sourceDevice;
@JsonProperty
private byte[] content;
@JsonProperty
private long serverTimestamp;
public SignalServiceEnvelopeEntity() {}
public int getType() {
return type;
}
public String getRelay() {
return relay;
}
public long getTimestamp() {
return timestamp;
}
public String getSource() {
return source;
}
public int getSourceDevice() {
return sourceDevice;
}
public byte[] getContent() {
return content;
}
public long getServerTimestamp() {
return serverTimestamp;
}
}

View File

@ -1,14 +0,0 @@
package org.session.libsignal.service.internal.push;
import java.util.List;
public class SignalServiceEnvelopeEntityList {
private List<SignalServiceEnvelopeEntity> messages;
public SignalServiceEnvelopeEntityList() {}
public List<SignalServiceEnvelopeEntity> getMessages() {
return messages;
}
}

View File

@ -1,21 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class StaleDevices {
@JsonProperty
private List<Integer> staleDevices;
public List<Integer> getStaleDevices() {
return staleDevices;
}
}

View File

@ -1,23 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.internal.push.exceptions;
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.session.libsignal.service.internal.push.MismatchedDevices;
public class MismatchedDevicesException extends NonSuccessfulResponseCodeException {
private final MismatchedDevices mismatchedDevices;
public MismatchedDevicesException(MismatchedDevices mismatchedDevices) {
this.mismatchedDevices = mismatchedDevices;
}
public MismatchedDevices getMismatchedDevices() {
return mismatchedDevices;
}
}

View File

@ -1,23 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.internal.push.exceptions;
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.session.libsignal.service.internal.push.StaleDevices;
public class StaleDevicesException extends NonSuccessfulResponseCodeException {
private final StaleDevices staleDevices;
public StaleDevicesException(StaleDevices staleDevices) {
this.staleDevices = staleDevices;
}
public StaleDevices getStaleDevices() {
return staleDevices;
}
}