Merge branch 'desktop-protocol-changes' into dev

This commit is contained in:
nielsandriesse 2020-07-13 11:44:35 +10:00
commit da8bbbc9cf
17 changed files with 202 additions and 113 deletions

View File

@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
@ -91,18 +92,20 @@ import org.webrtc.PeerConnectionFactory;
import org.webrtc.PeerConnectionFactory.InitializationOptions;
import org.webrtc.voiceengine.WebRtcAudioManager;
import org.webrtc.voiceengine.WebRtcAudioUtils;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.StreamDetails;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.loki.api.LokiAPI;
import org.whispersystems.signalservice.loki.api.LokiPoller;
import org.whispersystems.signalservice.loki.api.LokiPushNotificationAcknowledgement;
import org.whispersystems.signalservice.loki.api.LokiSwarmAPI;
import org.whispersystems.signalservice.loki.api.Poller;
import org.whispersystems.signalservice.loki.api.PushNotificationAcknowledgement;
import org.whispersystems.signalservice.loki.api.SnodeAPI;
import org.whispersystems.signalservice.loki.api.SwarmAPI;
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI;
import org.whispersystems.signalservice.loki.api.p2p.LokiP2PAPI;
import org.whispersystems.signalservice.loki.api.p2p.LokiP2PAPIDelegate;
import org.whispersystems.signalservice.loki.api.shelved.p2p.LokiP2PAPI;
import org.whispersystems.signalservice.loki.api.shelved.p2p.LokiP2PAPIDelegate;
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol;
import org.whispersystems.signalservice.loki.protocol.friendrequests.FriendRequestProtocol;
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
@ -151,7 +154,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
// Loki
public MessageNotifier messageNotifier = null;
public LokiPoller lokiPoller = null;
public Poller lokiPoller = null;
public LokiPublicChatManager lokiPublicChatManager = null;
private LokiPublicChatAPI lokiPublicChatAPI = null;
public Broadcaster broadcaster = null;
@ -184,8 +187,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
LokiSessionResetImplementation sessionResetImpl = new LokiSessionResetImplementation(this);
if (userPublicKey != null) {
LokiSwarmAPI.Companion.configureIfNeeded(apiDB);
LokiAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
SwarmAPI.Companion.configureIfNeeded(apiDB);
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
FriendRequestProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB);
SessionMetaProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
@ -194,7 +197,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
MultiDeviceProtocol.Companion.configureIfNeeded(apiDB);
SessionManagementProtocol.Companion.configureIfNeeded(sessionResetImpl, threadDB, this);
setUpP2PAPIIfNeeded();
LokiPushNotificationAcknowledgement.Companion.configureIfNeeded(BuildConfig.DEBUG);
PushNotificationAcknowledgement.Companion.configureIfNeeded(BuildConfig.DEBUG);
if (setUpStorageAPIIfNeeded()) {
if (userPublicKey != null) {
Set<DeviceLink> deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userPublicKey);
@ -493,15 +496,15 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null) return;
if (lokiPoller != null) {
LokiAPI.shared.setUserHexEncodedPublicKey(userPublicKey);
lokiPoller.setUserHexEncodedPublicKey(userPublicKey);
SnodeAPI.shared.setUserPublicKey(userPublicKey);
lokiPoller.setUserPublicKey(userPublicKey);
return;
}
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
Context context = this;
LokiSwarmAPI.Companion.configureIfNeeded(apiDB);
LokiAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
lokiPoller = new LokiPoller(userPublicKey, apiDB, protos -> {
SwarmAPI.Companion.configureIfNeeded(apiDB);
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
lokiPoller = new Poller(userPublicKey, apiDB, protos -> {
for (SignalServiceProtos.Envelope proto : protos) {
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(proto), false);
}
@ -588,6 +591,18 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
@Override
public void sendSessionRequest(@NotNull String publicKey) {
// It's never necessary to establish a session with self
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (publicKey.equals(userPublicKey)) { return; }
// Check that we don't already have a session
SignalProtocolAddress address = new SignalProtocolAddress(publicKey, SignalServiceAddress.DEFAULT_DEVICE_ID);
boolean hasSession = new TextSecureSessionStore(this).containsSession(address);
if (hasSession) { return; }
// Check that we didn't already send or process a session request
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
boolean hasSentOrProcessedSessionRequest = (apiDB.getSessionRequestTimestamp(publicKey) != null);
if (hasSentOrProcessedSessionRequest) { return; }
// Send the session request
DatabaseFactory.getLokiAPIDatabase(this).setSessionRequestTimestamp(publicKey, new Date().getTime());
EphemeralMessage sessionRequest = EphemeralMessage.createSessionRequest(publicKey);
jobManager.add(new PushEphemeralMessageSendJob(sessionRequest));

View File

@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.jobs.SmsSentJob;
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.jobs.UpdateApkJob;
import org.thoughtcrime.securesms.loki.protocol.PushNullMessageSendJob;
import java.util.HashMap;
import java.util.Map;
@ -74,6 +75,7 @@ public class WorkManagerFactoryMappings {
put(PushMediaSendJob.class.getName(), PushMediaSendJob.KEY);
put(PushNotificationReceiveJob.class.getName(), PushNotificationReceiveJob.KEY);
put(PushTextSendJob.class.getName(), PushTextSendJob.KEY);
put(PushNullMessageSendJob.class.getName(), PushNullMessageSendJob.KEY);
put(RefreshAttributesJob.class.getName(), RefreshAttributesJob.KEY);
put(RefreshPreKeysJob.class.getName(), RefreshPreKeysJob.KEY);
put(RefreshUnidentifiedDeliveryAbilityJob.class.getName(), RefreshUnidentifiedDeliveryAbilityJob.KEY);

View File

@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceOpenGroupUpdateJob;
import org.thoughtcrime.securesms.loki.protocol.PushEphemeralMessageSendJob;
import org.thoughtcrime.securesms.loki.protocol.PushNullMessageSendJob;
import java.util.Arrays;
import java.util.HashMap;
@ -50,6 +51,7 @@ public final class JobManagerFactories {
put(PushMediaSendJob.KEY, new PushMediaSendJob.Factory());
put(PushNotificationReceiveJob.KEY, new PushNotificationReceiveJob.Factory());
put(PushTextSendJob.KEY, new PushTextSendJob.Factory());
put(PushNullMessageSendJob.KEY, new PushNullMessageSendJob.Factory());
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
put(RefreshPreKeysJob.KEY, new RefreshPreKeysJob.Factory());
put(RefreshUnidentifiedDeliveryAbilityJob.KEY, new RefreshUnidentifiedDeliveryAbilityJob.Factory());

View File

@ -73,6 +73,7 @@ import org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol;
import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation;
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
import org.thoughtcrime.securesms.loki.protocol.PushEphemeralMessageSendJob;
import org.thoughtcrime.securesms.loki.protocol.PushNullMessageSendJob;
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
import org.thoughtcrime.securesms.loki.protocol.SyncMessagesProtocol;
@ -162,7 +163,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private MessageNotifier messageNotifier;
@Inject SignalServiceMessageSender messageSender;
private Address author;
public PushDecryptJob(Context context) {
this(context, -1);
@ -316,51 +316,13 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} else if (isMediaMessage) {
handleMediaMessage(content, message, smsMessageId, Optional.absent());
// Loki - This is needed for compatibility with refactored desktop clients
if (!message.isGroupMessage()) {
Recipient recipient = recipient(context, content.getSender());
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(context);
LokiThreadFriendRequestStatus threadFriendRequestStatus = threadDB.getFriendRequestStatus(threadID);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE || threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED);
} else if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
EphemeralMessage ephemeralMessage = EphemeralMessage.create(content.getSender());
ApplicationContext.getInstance(context).getJobManager().add(new PushEphemeralMessageSendJob(ephemeralMessage));
SyncMessagesProtocol.syncContact(context, Address.fromSerialized(content.getSender()));
}
// Loki - Handle friend request message if needed
FriendRequestProtocol.handleFriendRequestMessageIfNeeded(context, content.getSender(), content);
}
// Loki - Handle friend request message if needed
FriendRequestProtocol.handleFriendRequestMessageIfNeeded(context, content.getSender(), content);
} else if (message.getBody().isPresent()) {
handleTextMessage(content, message, smsMessageId, Optional.absent());
// Loki - This is needed for compatibility with refactored desktop clients
if (!message.isGroupMessage()) {
Recipient recipient = recipient(context, content.getSender());
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(context);
LokiThreadFriendRequestStatus threadFriendRequestStatus = threadDB.getFriendRequestStatus(threadID);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE || threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED);
} else if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
EphemeralMessage ephemeralMessage = EphemeralMessage.create(content.getSender());
ApplicationContext.getInstance(context).getJobManager().add(new PushEphemeralMessageSendJob(ephemeralMessage));
SyncMessagesProtocol.syncContact(context, Address.fromSerialized(content.getSender()));
}
// Loki - Handle friend request message if needed
FriendRequestProtocol.handleFriendRequestMessageIfNeeded(context, content.getSender(), content);
}
} else {
// Loki - This is needed for compatibility with refactored desktop clients
if (envelope.isFriendRequest()) {
EphemeralMessage ephemeralMessage = EphemeralMessage.create(content.getSender());
ApplicationContext.getInstance(context).getJobManager().add(new PushEphemeralMessageSendJob(ephemeralMessage));
}
// Loki - Handle friend request message if needed
FriendRequestProtocol.handleFriendRequestMessageIfNeeded(context, content.getSender(), content);
}
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
@ -406,7 +368,27 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} else if (content.getTypingMessage().isPresent()) {
handleTypingMessage(content, content.getTypingMessage().get());
} else {
Log.w(TAG, "Got unrecognized message...");
// Loki - This is needed for compatibility with refactored desktop clients
// ========
if (content.isFriendRequest()) {
ApplicationContext.getInstance(context).getJobManager().add(new PushNullMessageSendJob(content.getSender()));
} else {
Log.w(TAG, "Got unrecognized message...");
}
Recipient recipient = recipient(context, content.getSender());
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(context);
LokiThreadFriendRequestStatus threadFriendRequestStatus = threadDB.getFriendRequestStatus(threadID);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE || threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED);
} else if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
EphemeralMessage ephemeralMessage = EphemeralMessage.create(content.getSender());
ApplicationContext.getInstance(context).getJobManager().add(new PushEphemeralMessageSendJob(ephemeralMessage));
SyncMessagesProtocol.syncContact(context, Address.fromSerialized(content.getSender()));
}
// ========
}
resetRecipientToPush(Recipient.from(context, Address.fromSerialized(content.getSender()), false));

View File

@ -44,7 +44,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.loki.api.LokiAPI;
import org.whispersystems.signalservice.loki.api.SnodeAPI;
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
import java.io.FileNotFoundException;
@ -231,7 +231,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
database.addMismatchedIdentity(messageId, Address.fromSerialized(uie.getE164Number()), uie.getIdentityKey());
database.markAsSentFailed(messageId);
}
} catch (LokiAPI.Error e) {
} catch (SnodeAPI.Error e) {
Log.d("Loki", "Couldn't send message due to error: " + e.getDescription());
if (messageId >= 0) {
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
@ -257,7 +257,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
private boolean deliver(OutgoingMediaMessage message)
throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException,
UndeliverableMessageException, LokiAPI.Error
UndeliverableMessageException, SnodeAPI.Error
{
try {
Recipient recipient = Recipient.from(context, destination, false);

View File

@ -32,7 +32,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.loki.api.LokiAPI;
import org.whispersystems.signalservice.loki.api.SnodeAPI;
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
import java.io.IOException;
@ -173,7 +173,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
database.markAsSentFailed(record.getId());
database.markAsPush(record.getId());
}
} catch (LokiAPI.Error e) {
} catch (SnodeAPI.Error e) {
Log.d("Loki", "Couldn't send message due to error: " + e.getDescription());
if (messageId >= 0) {
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
@ -204,7 +204,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
}
private boolean deliver(SmsMessageRecord message)
throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException, LokiAPI.Error
throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException, SnodeAPI.Error
{
try {
Recipient recipient = Recipient.from(context, destination, false);

View File

@ -9,7 +9,7 @@ import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
import org.thoughtcrime.securesms.service.PersistentAlarmManagerListener
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
import org.whispersystems.signalservice.loki.api.LokiAPI
import org.whispersystems.signalservice.loki.api.SnodeAPI
import java.util.concurrent.TimeUnit
class BackgroundPollWorker : PersistentAlarmManagerListener() {
@ -35,8 +35,8 @@ class BackgroundPollWorker : PersistentAlarmManagerListener() {
try {
val applicationContext = context.applicationContext as ApplicationContext
val broadcaster = applicationContext.broadcaster
LokiAPI.configureIfNeeded(userPublicKey, lokiAPIDatabase, broadcaster)
LokiAPI.shared.getMessages().map { messages ->
SnodeAPI.configureIfNeeded(userPublicKey, lokiAPIDatabase, broadcaster)
SnodeAPI.shared.getMessages().map { messages ->
messages.forEach {
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
}

View File

@ -166,7 +166,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
}
val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.hexEncodedPublicKey
val serviceDataMessage = getDataMessage(message)
val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false, false, false, false, false)
val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false, false, false, false)
if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) {
PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
} else {

View File

@ -5,14 +5,14 @@ import okhttp3.*
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.logging.Log
import org.whispersystems.signalservice.internal.util.JsonUtil
import org.whispersystems.signalservice.loki.api.LokiPushNotificationAcknowledgement
import org.whispersystems.signalservice.loki.api.PushNotificationAcknowledgement
import java.io.IOException
object LokiPushNotificationManager {
private val connection = OkHttpClient()
private val server by lazy {
LokiPushNotificationAcknowledgement.shared.server
PushNotificationAcknowledgement.shared.server
}
private const val tokenExpirationInterval = 12 * 60 * 60 * 1000

View File

@ -7,7 +7,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.logging.Log
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
import org.whispersystems.signalservice.internal.util.Base64
import org.whispersystems.signalservice.loki.api.LokiMessageWrapper
import org.whispersystems.signalservice.loki.api.MessageWrapper
class PushNotificationService : FirebaseMessagingService() {
@ -23,7 +23,7 @@ class PushNotificationService : FirebaseMessagingService() {
val data = base64EncodedData?.let { Base64.decode(it) }
if (data != null) {
try {
val envelope = LokiMessageWrapper.unwrap(data)
val envelope = MessageWrapper.unwrap(data)
PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope), true)
} catch (e: Exception) {
Log.d("Loki", "Failed to unwrap data for message.")

View File

@ -8,7 +8,7 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.utilities.*
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.LokiAPITarget
import org.whispersystems.signalservice.loki.api.Snode
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
@ -77,7 +77,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
@JvmStatic val createSessionRequestTimestampCacheCommand = "CREATE TABLE $sessionRequestTimestampCache ($publicKey STRING PRIMARY KEY, $timestamp INTEGER DEFAULT 0);"
}
override fun getSnodePool(): Set<LokiAPITarget> {
override fun getSnodePool(): Set<Snode> {
val database = databaseHelper.readableDatabase
return database.get(snodePoolCache, "${Companion.dummyKey} = ?", wrap("dummy_key")) { cursor ->
val snodePoolAsString = cursor.getString(cursor.getColumnIndexOrThrow(snodePool))
@ -87,12 +87,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
LokiAPITarget(address, port, LokiAPITarget.KeySet(ed25519Key, x25519Key))
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key))
}
}?.toSet() ?: setOf()
}
override fun setSnodePool(newValue: Set<LokiAPITarget>) {
override fun setSnodePool(newValue: Set<Snode>) {
val database = databaseHelper.writableDatabase
val snodePoolAsString = newValue.joinToString(", ") { snode ->
var string = "${snode.address}-${snode.port}"
@ -106,9 +106,9 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
database.insertOrUpdate(snodePoolCache, row, "${Companion.dummyKey} = ?", wrap("dummy_key"))
}
override fun getOnionRequestPaths(): List<List<LokiAPITarget>> {
override fun getOnionRequestPaths(): List<List<Snode>> {
val database = databaseHelper.readableDatabase
fun get(indexPath: String): LokiAPITarget? {
fun get(indexPath: String): Snode? {
return database.get(onionRequestPathCache, "${Companion.indexPath} = ?", wrap(indexPath)) { cursor ->
val snodeAsString = cursor.getString(cursor.getColumnIndexOrThrow(snode))
val components = snodeAsString.split("-")
@ -117,7 +117,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
val ed25519Key = components.getOrNull(2)
val x25519Key = components.getOrNull(3)
if (port != null && ed25519Key != null && x25519Key != null) {
LokiAPITarget(address, port, LokiAPITarget.KeySet(ed25519Key, x25519Key))
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key))
} else {
null
}
@ -139,7 +139,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
delete("1-1"); delete("1-2")
}
override fun setOnionRequestPaths(newValue: List<List<LokiAPITarget>>) {
override fun setOnionRequestPaths(newValue: List<List<Snode>>) {
// FIXME: This is a bit of a dirty approach that assumes 2 paths of length 3 each. We should do better than this.
if (newValue.count() != 2) { return }
val path0 = newValue[0]
@ -147,7 +147,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
if (path0.count() != 3 || path1.count() != 3) { return }
Log.d("Loki", "Persisting onion request paths to database.")
val database = databaseHelper.writableDatabase
fun set(indexPath: String ,snode: LokiAPITarget) {
fun set(indexPath: String ,snode: Snode) {
var snodeAsString = "${snode.address}-${snode.port}"
val keySet = snode.publicKeySet
if (keySet != null) {
@ -161,7 +161,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
set("1-1", path1[1]); set("1-2", path1[2])
}
override fun getSwarm(hexEncodedPublicKey: String): Set<LokiAPITarget>? {
override fun getSwarm(hexEncodedPublicKey: String): Set<Snode>? {
val database = databaseHelper.readableDatabase
return database.get(swarmCache, "${Companion.hexEncodedPublicKey} = ?", wrap(hexEncodedPublicKey)) { cursor ->
val swarmAsString = cursor.getString(cursor.getColumnIndexOrThrow(swarm))
@ -171,12 +171,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
LokiAPITarget(address, port, LokiAPITarget.KeySet(ed25519Key, x25519Key))
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key))
}
}?.toSet()
}
override fun setSwarm(hexEncodedPublicKey: String, newValue: Set<LokiAPITarget>) {
override fun setSwarm(hexEncodedPublicKey: String, newValue: Set<Snode>) {
val database = databaseHelper.writableDatabase
val swarmAsString = newValue.joinToString(", ") { target ->
var string = "${target.address}-${target.port}"
@ -190,14 +190,14 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
database.insertOrUpdate(swarmCache, row, "${Companion.hexEncodedPublicKey} = ?", wrap(hexEncodedPublicKey))
}
override fun getLastMessageHashValue(target: LokiAPITarget): String? {
override fun getLastMessageHashValue(target: Snode): String? {
val database = databaseHelper.readableDatabase
return database.get(lastMessageHashValueCache, "${Companion.target} = ?", wrap(target.address)) { cursor ->
cursor.getString(cursor.getColumnIndexOrThrow(lastMessageHashValue))
}
}
override fun setLastMessageHashValue(target: LokiAPITarget, newValue: String) {
override fun setLastMessageHashValue(target: Snode, newValue: String) {
val database = databaseHelper.writableDatabase
val row = wrap(mapOf(Companion.target to target.address, lastMessageHashValue to newValue))
database.insertOrUpdate(lastMessageHashValueCache, row, "${Companion.target} = ?", wrap(target.address))

View File

@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.loki.utilities.QRCodeUtilities
import org.thoughtcrime.securesms.loki.utilities.toPx
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.LokiAPI
import org.whispersystems.signalservice.loki.api.SnodeAPI
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
@ -84,7 +84,7 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
contentView.cancelButton.visibility = View.GONE
contentView.authorizeButton.visibility = View.GONE
}
LokiFileServerAPI.shared.addDeviceLink(deviceLink).bind(LokiAPI.sharedContext) {
LokiFileServerAPI.shared.addDeviceLink(deviceLink).bind(SnodeAPI.sharedContext) {
MultiDeviceProtocol.signAndSendDeviceLinkMessage(context!!, deviceLink)
}.success {
TextSecurePreferences.setMultiDevice(context!!, true)

View File

@ -17,7 +17,7 @@ import org.whispersystems.libsignal.SignalProtocolAddress
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.loki.api.LokiAPI
import org.whispersystems.signalservice.loki.api.SnodeAPI
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
import java.util.*
@ -106,13 +106,7 @@ object ClosedGroupsProtocol {
allDevices.remove(userPublicKey)
}
for (device in allDevices) {
val deviceAsAddress = SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID)
val hasSession = TextSecureSessionStore(context).containsSession(deviceAsAddress)
if (hasSession) { continue }
if (DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(device) != null) { return }
DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestTimestamp(device, Date().time)
val sessionRequest = EphemeralMessage.createSessionRequest(device)
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRequest))
ApplicationContext.getInstance(context).sendSessionRequest(device)
}
}
}

View File

@ -0,0 +1,84 @@
package org.thoughtcrime.securesms.loki.protocol
import com.google.protobuf.ByteString
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.jobmanager.Data
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
import org.thoughtcrime.securesms.jobs.BaseJob
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.recipients.Recipient
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.loki.protocol.meta.TTLUtilities
import java.io.IOException
import java.security.SecureRandom
import java.util.*
import java.util.concurrent.TimeUnit
class PushNullMessageSendJob private constructor(parameters: Parameters, private val publicKey: String) : BaseJob(parameters) {
companion object {
const val KEY = "PushNullMessageSendJob"
}
constructor(publicKey: String) : this(Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setQueue(KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(1)
.build(),
publicKey)
override fun serialize(): Data {
return Data.Builder().putString("publicKey", publicKey).build()
}
override fun getFactoryKey(): String { return KEY }
public override fun onRun() {
val contentMessage = SignalServiceProtos.Content.newBuilder()
val nullMessage = SignalServiceProtos.NullMessage.newBuilder()
val sr = SecureRandom()
val paddingSize = sr.nextInt(512)
val padding = ByteArray(paddingSize)
sr.nextBytes(padding)
nullMessage.padding = ByteString.copyFrom(padding)
contentMessage.nullMessage = nullMessage.build()
val serializedContentMessage = contentMessage.build().toByteArray()
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(publicKey)
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient)
val ttl = TTLUtilities.getTTL(TTLUtilities.MessageType.Ephemeral)
try {
messageSender.sendMessage(0, address, udAccess.get().targetUnidentifiedAccess,
Date().time, serializedContentMessage, false, ttl, false,
false, false, false, false)
} catch (e: Exception) {
Log.d("Loki", "Failed to send null message to: $publicKey due to error: $e.")
throw e
}
}
public override fun onShouldRetry(e: Exception): Boolean {
// Disable since we have our own retrying
return false
}
override fun onCanceled() { }
class Factory : Job.Factory<PushNullMessageSendJob> {
override fun create(parameters: Parameters, data: Data): PushNullMessageSendJob {
try {
val publicKey = data.getString("publicKey")
return PushNullMessageSendJob(parameters, publicKey)
} catch (e: IOException) {
throw AssertionError(e)
}
}
}
}

View File

@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
import java.util.*
object SessionManagementProtocol {
@ -59,6 +59,14 @@ object SessionManagementProtocol {
val registrationID = TextSecurePreferences.getLocalRegistrationId(context) // TODO: It seems wrong to use the local registration ID for this?
val lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context)
Log.d("Loki", "Received a pre key bundle from: " + content.sender.toString() + ".")
if (content.dataMessage.isPresent && content.dataMessage.get().isSessionRequest) {
val sessionRequestTimestamp = DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(content.sender)
if (sessionRequestTimestamp != null && content.timestamp < sessionRequestTimestamp) {
// We sent or processed a session request after this one was sent
Log.d("Loki", "Ignoring session request from: ${content.sender}.")
return
}
}
val preKeyBundle = preKeyBundleMessage.getPreKeyBundle(registrationID)
lokiPreKeyBundleDatabase.setPreKeyBundle(content.sender, preKeyBundle)
}
@ -66,11 +74,13 @@ object SessionManagementProtocol {
@JvmStatic
fun handleSessionRequestIfNeeded(context: Context, content: SignalServiceContent): Boolean {
if (!content.dataMessage.isPresent || !content.dataMessage.get().isSessionRequest) { return false }
val sentSessionRequestTimestamp = DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(content.sender)
if (sentSessionRequestTimestamp != null && content.timestamp < sentSessionRequestTimestamp) {
// We sent a session request after this one was sent
val sessionRequestTimestamp = DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(content.sender)
if (sessionRequestTimestamp != null && content.timestamp < sessionRequestTimestamp) {
// We sent or processed a session request after this one was sent
Log.d("Loki", "Ignoring session request from: ${content.sender}.")
return false
}
DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestTimestamp(content.sender, Date().time)
val ephemeralMessage = EphemeralMessage.create(content.sender)
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
return true

View File

@ -14,8 +14,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.loki.api.rssfeeds.LokiRSSFeed
import org.whispersystems.signalservice.loki.api.rssfeeds.LokiRSSFeedProxy
import org.whispersystems.signalservice.loki.api.shelved.rssfeeds.LokiRSSFeed
import org.whispersystems.signalservice.loki.api.shelved.rssfeeds.LokiRSSFeedProxy
import java.text.SimpleDateFormat
import java.util.regex.Pattern
@ -66,7 +66,7 @@ class LokiRSSFeedPoller(private val context: Context, private val feed: LokiRSSF
val id = feed.id.toByteArray()
val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.RSS_FEED, null, null, null, null)
val x2 = SignalServiceDataMessage(timestamp, x1, null, body)
val x3 = SignalServiceContent(x2, "Loki", SignalServiceAddress.DEFAULT_DEVICE_ID, timestamp, false, false, false, false, false)
val x3 = SignalServiceContent(x2, "Loki", SignalServiceAddress.DEFAULT_DEVICE_ID, timestamp, false, false, false, false)
PushDecryptJob(context).handleTextMessage(x3, x2, Optional.absent(), Optional.absent())
}
}.fail { exception ->

View File

@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.loki.api.LokiPublicChatManager;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Debouncer;
import org.whispersystems.signalservice.loki.api.LokiPoller;
import org.whispersystems.signalservice.loki.api.Poller;
import java.util.concurrent.TimeUnit;
@ -40,9 +40,9 @@ public class OptimizedMessageNotifier implements MessageNotifier {
@Override
public void updateNotification(@NonNull Context context) {
LokiPoller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
Poller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
Boolean isCaughtUp = false;
boolean isCaughtUp = false;
if (lokiPoller != null && lokiPublicChatManager != null) {
isCaughtUp = lokiPoller.isCaughtUp() && lokiPublicChatManager.areAllCaughtUp();
}
@ -56,9 +56,9 @@ public class OptimizedMessageNotifier implements MessageNotifier {
@Override
public void updateNotification(@NonNull Context context, long threadId) {
LokiPoller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
Poller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
Boolean isCaughtUp = false;
boolean isCaughtUp = false;
if (lokiPoller != null && lokiPublicChatManager != null) {
isCaughtUp = lokiPoller.isCaughtUp() && lokiPublicChatManager.areAllCaughtUp();
}
@ -72,9 +72,9 @@ public class OptimizedMessageNotifier implements MessageNotifier {
@Override
public void updateNotification(@NonNull Context context, long threadId, boolean signal) {
LokiPoller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
Poller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
Boolean isCaughtUp = false;
boolean isCaughtUp = false;
if (lokiPoller != null && lokiPublicChatManager != null) {
isCaughtUp = lokiPoller.isCaughtUp() && lokiPublicChatManager.areAllCaughtUp();
}
@ -88,9 +88,9 @@ public class OptimizedMessageNotifier implements MessageNotifier {
@Override
public void updateNotification(@android.support.annotation.NonNull Context context, boolean signal, int reminderCount) {
LokiPoller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
Poller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
Boolean isCaughtUp = false;
boolean isCaughtUp = false;
if (lokiPoller != null && lokiPublicChatManager != null) {
isCaughtUp = lokiPoller.isCaughtUp() && lokiPublicChatManager.areAllCaughtUp();
}