mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
WIP: clean up signal service protos
This commit is contained in:
parent
b34809f4d5
commit
04f140ee09
@ -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">
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
||||
}
|
@ -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) {}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -10,10 +10,6 @@ import java.io.IOException;
|
||||
|
||||
public class NonSuccessfulResponseCodeException extends IOException {
|
||||
|
||||
public NonSuccessfulResponseCodeException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NonSuccessfulResponseCodeException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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) { }
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user