Simplify PushDecryptJob a bit

This commit is contained in:
Niels Andriesse 2020-02-13 10:18:05 +11:00
parent 27fdfe4ee8
commit 9c71a4c3cd

View File

@ -290,13 +290,14 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
// Loki - Handle friend request acceptance if needed // Loki - Handle friend request acceptance if needed
acceptFriendRequestIfNeeded(content); if (!content.isFriendRequest() && !isGroupChatMessage(content)) {
becomeFriendsWithContactIfNeeded(content.getSender(), true, false);
}
// Loki - Session requests // Loki - Handle session request if needed
handleSessionRequestIfNeeded(content); handleSessionRequestIfNeeded(content);
// Loki - Store pre key bundle // Loki - Store pre key bundle if needed
// We shouldn't store it if it's a pairing message
if (!content.getDeviceLink().isPresent()) { if (!content.getDeviceLink().isPresent()) {
storePreKeyBundleIfNeeded(content); storePreKeyBundleIfNeeded(content);
} }
@ -311,29 +312,29 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
// Loki - Store the sender display name if needed // Loki - Store the sender display name if needed
Optional<String> rawSenderDisplayName = content.senderDisplayName; Optional<String> rawSenderDisplayName = content.senderDisplayName;
if (rawSenderDisplayName.isPresent() && rawSenderDisplayName.get().length() > 0) { if (rawSenderDisplayName.isPresent() && rawSenderDisplayName.get().length() > 0) {
// If we got a name from our primary device then we set our profile name to match it // If we got a name from our master device then set our display name to match
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
if (ourPrimaryDevice != null && content.getSender().equals(ourPrimaryDevice)) { if (ourMasterDevice != null && content.getSender().equals(ourMasterDevice)) {
TextSecurePreferences.setProfileName(context, rawSenderDisplayName.get()); TextSecurePreferences.setProfileName(context, rawSenderDisplayName.get());
} }
// If we receive a message from our device then don't set the display name in the database (as we probably have a alias set for them) // If we receive a message from our device then don't set the display name in the database (as we probably have a alias set for them)
MultiDeviceUtilities.isOneOfOurDevices(context, Address.fromSerialized(content.getSender())).success(isOneOfOurDevice -> { MultiDeviceUtilities.isOneOfOurDevices(context, Address.fromSerialized(content.getSender())).success( isOneOfOurDevices -> {
if (!isOneOfOurDevice) { setDisplayName(content.getSender(), rawSenderDisplayName.get()); } if (!isOneOfOurDevices) { setDisplayName(content.getSender(), rawSenderDisplayName.get()); }
return Unit.INSTANCE; return Unit.INSTANCE;
}); });
} }
if (content.getDeviceLink().isPresent()) { if (content.getDeviceLink().isPresent()) {
handlePairingMessage(content.getDeviceLink().get(), content); handleDeviceLinkMessage(content.getDeviceLink().get(), content);
} else if (content.getDataMessage().isPresent()) { } else if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get(); SignalServiceDataMessage message = content.getDataMessage().get();
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent(); boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
if (!content.isFriendRequest() && message.isUnlinkingRequest()) { if (!content.isFriendRequest() && message.isUnlinkingRequest()) {
// Make sure we got the request from our primary device // Make sure we got the request from our master device
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
if (ourPrimaryDevice != null && ourPrimaryDevice.equals(content.getSender())) { if (ourMasterDevice != null && ourMasterDevice.equals(content.getSender())) {
TextSecurePreferences.setDatabaseResetFromUnpair(context, true); TextSecurePreferences.setDatabaseResetFromUnpair(context, true);
MultiDeviceUtilities.checkForRevocation(context); MultiDeviceUtilities.checkForRevocation(context);
} }
@ -376,7 +377,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp()); else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp());
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get()); else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get()); else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get());
else if (syncMessage.getContacts().isPresent()) handleSynchronizeContactMessage(syncMessage.getContacts().get()); else if (syncMessage.getContacts().isPresent()) handleContactSyncMessage(syncMessage.getContacts().get());
else Log.w(TAG, "Contains no known sync types..."); else Log.w(TAG, "Contains no known sync types...");
} else if (content.getCallMessage().isPresent()) { } else if (content.getCallMessage().isPresent()) {
Log.i(TAG, "Got call message..."); Log.i(TAG, "Got call message...");
@ -594,7 +595,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{ {
GroupMessageProcessor.process(context, content, message, false); GroupMessageProcessor.process(context, content, message, false);
if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getMessageDestination(content, message).getExpireMessages()) { if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getRecipientForMessage(content, message).getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent()); handleExpirationUpdate(content, message, Optional.absent());
} }
@ -620,7 +621,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{ {
try { try {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient recipient = getMessageDestination(content, message); Recipient recipient = getRecipientForMessage(content, message);
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromSerialized(content.getSender()), IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromSerialized(content.getSender()),
message.getTimestamp(), -1, message.getTimestamp(), -1,
message.getExpiresInSeconds() * 1000L, true, message.getExpiresInSeconds() * 1000L, true,
@ -671,45 +672,44 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
} }
private void handleSynchronizeContactMessage(@NonNull ContactsMessage contactsMessage) { private void handleContactSyncMessage(@NonNull ContactsMessage contactsMessage) {
if (contactsMessage.getContactsStream().isStream()) { if (!contactsMessage.getContactsStream().isStream()) { return; }
Log.d("Loki", "Received contact sync message"); Log.d("Loki", "Received contact sync message.");
try { try {
InputStream in = contactsMessage.getContactsStream().asStream().getInputStream(); InputStream in = contactsMessage.getContactsStream().asStream().getInputStream();
DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(in); DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(in);
List<DeviceContact> devices = contactsInputStream.readAll(); List<DeviceContact> deviceContacts = contactsInputStream.readAll();
for (DeviceContact deviceContact : devices) { for (DeviceContact deviceContact : deviceContacts) {
// Check if we have the contact as a friend and that we're not trying to sync our own device // Check if we have the contact as a friend and that we're not trying to sync our own device
String pubKey = deviceContact.getNumber(); String hexEncodedPublicKey = deviceContact.getNumber();
Address address = Address.fromSerialized(pubKey); Address address = Address.fromSerialized(hexEncodedPublicKey);
if (!address.isPhone() || address.toPhoneString().equals(TextSecurePreferences.getLocalNumber(context))) { continue; } if (!address.isPhone() || address.toPhoneString().equals(TextSecurePreferences.getLocalNumber(context))) { continue; }
/* /*
If we're not friends with the contact we received or our friend request expired then we should send them a friend request If we're not friends with the contact we received or our friend request expired then we should send them a friend request.
otherwise if we have received a friend request with from them then we should automatically accept the friend request Otherwise, if we have received a friend request from them, automatically accept the friend request.
*/ */
Recipient recipient = Recipient.from(context, address, false); Recipient recipient = Recipient.from(context, address, false);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId); LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) { if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
MessageSender.sendBackgroundFriendRequest(context, pubKey, "Please accept to enable messages to be synced across devices"); MessageSender.sendBackgroundFriendRequest(context, hexEncodedPublicKey, "Please accept to enable messages to be synced across devices");
Log.d("Loki", "Sent friend request to " + pubKey); Log.d("Loki", "Sent friend request to " + hexEncodedPublicKey);
} else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { } else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
// Accept the incoming friend request // Accept the incoming friend request
becomeFriendsWithContact(pubKey, false, false); becomeFriendsWithContactIfNeeded(hexEncodedPublicKey, false, false);
// Send them an accept message back // Send them an accept message back
MessageSender.sendBackgroundMessage(context, pubKey); MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey);
Log.d("Loki", "Became friends with " + deviceContact.getNumber()); Log.d("Loki", "Became friends with " + deviceContact.getNumber());
}
// TODO: Handle blocked - If user is not blocked then we should do the friend request logic otherwise add them to our block list
// TODO: Handle expiration timer - Update expiration timer?
// TODO: Handle avatar - Download and set avatar?
} }
} catch (Exception e) {
Log.d("Loki", "Failed to sync contact: " + e); // TODO: Handle blocked - If user is not blocked then we should do the friend request logic otherwise add them to our block list
// TODO: Handle expiration timer - Update expiration timer?
// TODO: Handle avatar - Download and set avatar?
} }
} catch (Exception e) {
Log.d("Loki", "Failed to sync contact: " + e + ".");
} }
} }
@ -752,7 +752,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
} }
// Loki - If we received a sync message from our master device then we need to extract the avatar url // Loki - If we received a sync message from our master device then we need to extract the profile picture url
if (isSenderMasterDevice) { if (isSenderMasterDevice) {
handleProfileKey(content, message.getMessage()); handleProfileKey(content, message.getMessage());
} }
@ -842,21 +842,21 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
@NonNull Optional<Long> messageServerIDOrNull) @NonNull Optional<Long> messageServerIDOrNull)
throws StorageFailedException throws StorageFailedException
{ {
Recipient originalRecipient = getMessageDestination(content, message); Recipient originalRecipient = getRecipientForMessage(content, message);
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); Recipient masterRecipient = getMasterRecipientForMessage(content, message);
notifyTypingStoppedFromIncomingMessage(primaryDeviceRecipient, content.getSender(), content.getSenderDevice()); notifyTypingStoppedFromIncomingMessage(masterRecipient, content.getSender(), content.getSenderDevice());
Optional<QuoteModel> quote = getValidatedQuote(message.getQuote()); Optional<QuoteModel> quote = getValidatedQuote(message.getQuote());
Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts()); Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts());
Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or("")); Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or(""));
Optional<Attachment> sticker = getStickerAttachment(message.getSticker()); Optional<Attachment> sticker = getStickerAttachment(message.getSticker());
Address sender = primaryDeviceRecipient.getAddress(); Address sender = masterRecipient.getAddress();
// If message is from group then we need to map it to get the sender of the message // If message is from group then we need to map it to get the sender of the message
if (message.isGroupMessage()) { if (message.isGroupMessage()) {
sender = getPrimaryDeviceRecipient(content.getSender()).getAddress(); sender = getMasterRecipient(content.getSender()).getAddress();
} }
// Ignore messages from ourselves // Ignore messages from ourselves
@ -1031,8 +1031,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{ {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context); SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
String body = message.getBody().isPresent() ? message.getBody().get() : ""; String body = message.getBody().isPresent() ? message.getBody().get() : "";
Recipient originalRecipient = getMessageDestination(content, message); Recipient originalRecipient = getRecipientForMessage(content, message);
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); Recipient masterRecipient = getMasterRecipientForMessage(content, message);
if (message.getExpiresInSeconds() != originalRecipient.getExpireMessages()) { if (message.getExpiresInSeconds() != originalRecipient.getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent()); handleExpirationUpdate(content, message, Optional.absent());
@ -1043,13 +1043,13 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) { if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) {
threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second; threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second;
} else { } else {
notifyTypingStoppedFromIncomingMessage(primaryDeviceRecipient, content.getSender(), content.getSenderDevice()); notifyTypingStoppedFromIncomingMessage(masterRecipient, content.getSender(), content.getSenderDevice());
Address sender = primaryDeviceRecipient.getAddress(); Address sender = masterRecipient.getAddress();
// If message is from group then we need to map it to get the sender of the message // If message is from group then we need to map it to get the sender of the message
if (message.isGroupMessage()) { if (message.isGroupMessage()) {
sender = getPrimaryDeviceRecipient(content.getSender()).getAddress(); sender = getMasterRecipient(content.getSender()).getAddress();
} }
// Ignore messages from ourselves // Ignore messages from ourselves
@ -1103,7 +1103,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
} }
private boolean isValidPairingMessage(@NonNull DeviceLink authorisation) { private boolean isValidDeviceLinkMessage(@NonNull DeviceLink authorisation) {
boolean isSecondaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null; boolean isSecondaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
boolean isRequest = (authorisation.getType() == DeviceLink.Type.REQUEST); boolean isRequest = (authorisation.getType() == DeviceLink.Type.REQUEST);
@ -1123,74 +1123,66 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return authorisation.verify(); return authorisation.verify();
} }
private void handleProfileAvatar(SignalServiceContent content, String url) { private void handleDeviceLinkMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender());
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileAvatarJob(primaryDevice, url));
}
private void handlePairingMessage(@NonNull DeviceLink authorisation, @NonNull SignalServiceContent content) {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
if (authorisation.getType() == DeviceLink.Type.REQUEST) { if (deviceLink.getType() == DeviceLink.Type.REQUEST) {
handlePairingRequestMessage(authorisation, content); handleDeviceLinkRequestMessage(deviceLink, content);
} else if (authorisation.getSlaveHexEncodedPublicKey().equals(userHexEncodedPublicKey)) { } else if (deviceLink.getSlaveHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
handlePairingAuthorisationMessage(authorisation, content); handleDeviceLinkAuthorizedMessage(deviceLink, content);
} }
} }
private void handlePairingRequestMessage(@NonNull DeviceLink authorisation, @NonNull SignalServiceContent content) { private void handleDeviceLinkRequestMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
boolean isValid = isValidPairingMessage(authorisation); boolean isValid = isValidDeviceLinkMessage(deviceLink);
DeviceLinkingSession linkingSession = DeviceLinkingSession.Companion.getShared(); DeviceLinkingSession linkingSession = DeviceLinkingSession.Companion.getShared();
if (isValid && linkingSession.isListeningForLinkingRequests()) { if (!isValid || !linkingSession.isListeningForLinkingRequests()) { return; }
// Loki - If we successfully received a request then we should store the PreKeyBundle storePreKeyBundleIfNeeded(content);
storePreKeyBundleIfNeeded(content); linkingSession.processLinkingRequest(deviceLink);
linkingSession.processLinkingRequest(authorisation);
}
} }
private void handlePairingAuthorisationMessage(@NonNull DeviceLink authorisation, @NonNull SignalServiceContent content) { private void handleDeviceLinkAuthorizedMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
// Prepare // Check preconditions
boolean isSecondaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null; boolean hasExistingDeviceLink = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
if (isSecondaryDevice) { if (hasExistingDeviceLink) {
Log.d("Loki", "Ignoring unexpected pairing authorisation message (the device is already paired as a secondary device)."); Log.d("Loki", "Ignoring unexpected device link message (the device is already linked as a slave device).");
return; return;
} }
boolean isValid = isValidPairingMessage(authorisation); boolean isValid = isValidDeviceLinkMessage(deviceLink);
if (!isValid) { if (!isValid) {
Log.d("Loki", "Ignoring invalid pairing authorisation message."); Log.d("Loki", "Ignoring invalid device link message.");
return; return;
} }
if (!DeviceLinkingSession.Companion.getShared().isListeningForLinkingRequests()) { if (!DeviceLinkingSession.Companion.getShared().isListeningForLinkingRequests()) {
Log.d("Loki", "Ignoring pairing authorisation message."); Log.d("Loki", "Ignoring device link message.");
return; return;
} }
if (authorisation.getType() != DeviceLink.Type.AUTHORIZATION) { return; } if (deviceLink.getType() != DeviceLink.Type.AUTHORIZATION) { return; }
Log.d("Loki", "Received pairing authorisation message from: " + authorisation.getMasterHexEncodedPublicKey() + "."); Log.d("Loki", "Received device link authorized message from: " + deviceLink.getMasterHexEncodedPublicKey() + ".");
// Save PreKeyBundle if for whatever reason we got one // Save pre key bundle if we somehow got one
storePreKeyBundleIfNeeded(content); storePreKeyBundleIfNeeded(content);
// Process // Process
DeviceLinkingSession.Companion.getShared().processLinkingAuthorization(authorisation); DeviceLinkingSession.Companion.getShared().processLinkingAuthorization(deviceLink);
// Store the primary device's public key // Store the master device's ID
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(userHexEncodedPublicKey); DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(userHexEncodedPublicKey);
DatabaseFactory.getLokiAPIDatabase(context).addDeviceLink(authorisation); DatabaseFactory.getLokiAPIDatabase(context).addDeviceLink(deviceLink);
TextSecurePreferences.setMasterHexEncodedPublicKey(context, authorisation.getMasterHexEncodedPublicKey()); TextSecurePreferences.setMasterHexEncodedPublicKey(context, deviceLink.getMasterHexEncodedPublicKey());
TextSecurePreferences.setMultiDevice(context, true); TextSecurePreferences.setMultiDevice(context, true);
// Send a background message to the primary device // Send a background message to the master device
MessageSender.sendBackgroundMessage(context, authorisation.getMasterHexEncodedPublicKey()); MessageSender.sendBackgroundMessage(context, deviceLink.getMasterHexEncodedPublicKey());
// Propagate the updates to the file server // Propagate the updates to the file server
LokiFileServerAPI storageAPI = LokiFileServerAPI.Companion.getShared(); LokiFileServerAPI.Companion.getShared().updateUserDeviceLinks();
storageAPI.updateUserDeviceLinks(); // Update display name if needed
// Update display names
if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) { if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get()); TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
} }
// Profile avatar updates // Update profile picture if needed
if (content.getDataMessage().isPresent()) { if (content.getDataMessage().isPresent()) {
handleProfileKey(content, content.getDataMessage().get()); handleProfileKey(content, content.getDataMessage().get());
} }
// Contact sync // Handle contact sync if needed
if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getContacts().isPresent()) { if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getContacts().isPresent()) {
handleSynchronizeContactMessage(content.getSyncMessage().get().getContacts().get()); handleContactSyncMessage(content.getSyncMessage().get().getContacts().get());
} }
} }
@ -1200,95 +1192,79 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
private void updateGroupChatMessageServerID(Optional<Long> messageServerIDOrNull, Optional<InsertResult> insertResult) { private void updateGroupChatMessageServerID(Optional<Long> messageServerIDOrNull, Optional<InsertResult> insertResult) {
if (insertResult.isPresent() && messageServerIDOrNull.isPresent()) { if (!insertResult.isPresent() || !messageServerIDOrNull.isPresent()) { return; }
long messageID = insertResult.get().getMessageId(); long messageID = insertResult.get().getMessageId();
long messageServerID = messageServerIDOrNull.get(); long messageServerID = messageServerIDOrNull.get();
DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, messageServerID); DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, messageServerID);
}
} }
private void storePreKeyBundleIfNeeded(@NonNull SignalServiceContent content) { private void storePreKeyBundleIfNeeded(@NonNull SignalServiceContent content) {
Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false); Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
if (!sender.isGroupRecipient() && content.lokiServiceMessage.isPresent()) { if (sender.isGroupRecipient() || !content.lokiServiceMessage.isPresent()) { return; }
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get(); LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
if (lokiMessage.getPreKeyBundleMessage() != null) { if (lokiMessage.getPreKeyBundleMessage() == null) { return; }
int registrationID = TextSecurePreferences.getLocalRegistrationId(context); int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context); LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
if (registrationID <= 0) { return; }
// Loki - Store the latest pre key bundle Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + ".");
if (registrationID > 0) { PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + "."); lokiPreKeyBundleDatabase.setPreKeyBundle(content.getSender(), preKeyBundle);
PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID); // If we received a friend request, but we were already friends with the user, reset the session
lokiPreKeyBundleDatabase.setPreKeyBundle(content.getSender(), preKeyBundle); if (content.isFriendRequest()) {
long threadID = threadDatabase.getThreadIdIfExistsFor(sender);
// Loki - If we received a friend request, but we were already friends with this user, then reset the session if (lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) {
if (content.isFriendRequest()) { resetSession(content.getSender(), threadID);
long threadID = threadDatabase.getThreadIdIfExistsFor(sender); // Let our other devices know that we have reset the session
if (lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) { MessageSender.syncContact(context, sender.getAddress());
resetSession(content.getSender(), threadID);
// Let our other devices know that we have reset the session
MessageSender.syncContact(context, sender.getAddress());
}
}
}
} }
} }
} }
private void acceptFriendRequestIfNeeded(@NonNull SignalServiceContent content) {
// If we get anything other than a friend request, we can assume that we have a session with the other user
if (content.isFriendRequest() || isGroupChatMessage(content)) { return; }
becomeFriendsWithContact(content.getSender(), true, false);
}
private void handleSessionRequestIfNeeded(@NonNull SignalServiceContent content) { private void handleSessionRequestIfNeeded(@NonNull SignalServiceContent content) {
if (content.isFriendRequest() && isSessionRequest(content)) { if (!content.isFriendRequest() || !isSessionRequest(content)) { return; }
// Check if the session request from a member in one of our groups or our friend // Check if the session request came from a member in one of our groups or one of our friends
LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(content.getSender()).success(primaryDevicePublicKey -> { LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(content.getSender()).success( masterHexEncodedPublicKey -> {
String sender = primaryDevicePublicKey != null ? primaryDevicePublicKey : content.getSender(); String sender = masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : content.getSender();
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(sender), false)); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(sender), false));
LokiThreadFriendRequestStatus threadFriendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID); LokiThreadFriendRequestStatus threadFriendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
boolean isOurFriend = threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS; boolean isOurFriend = threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS;
boolean isInOneOfOurGroups = DatabaseFactory.getGroupDatabase(context).signalGroupsHaveMember(sender); boolean isInOneOfOurGroups = DatabaseFactory.getGroupDatabase(context).signalGroupsHaveMember(sender);
boolean shouldAcceptSessionRequest = isOurFriend || isInOneOfOurGroups; boolean shouldAcceptSessionRequest = isOurFriend || isInOneOfOurGroups;
if (shouldAcceptSessionRequest) { if (shouldAcceptSessionRequest) {
// Send a background message to acknowledge session request MessageSender.sendBackgroundMessage(context, content.getSender()); // Send a background message to acknowledge
MessageSender.sendBackgroundMessage(context, content.getSender()); }
} return Unit.INSTANCE;
return Unit.INSTANCE; });
});
}
} }
private void becomeFriendsWithContact(String pubKey, boolean syncContact, boolean force) { private void becomeFriendsWithContactIfNeeded(String hexEncodedPublicKey, boolean requiresContactSync, boolean canSkip) {
// Ignore friend requests to group recipients
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
Recipient contactID = Recipient.from(context, Address.fromSerialized(pubKey), false); Recipient contactID = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false);
if (contactID.isGroupRecipient()) return; if (contactID.isGroupRecipient()) return;
// Ignore friend requests to recipients we're already friends with
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(contactID); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(contactID);
LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID); LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS) { return; } if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS) { return; }
// We shouldn't be able to skip from NONE to FRIENDS under normal circumstances.
// We shouldn't be able to skip from None -> Friends in normal circumstances. // Multi-device is the one exception to this rule because we want to automatically become friends with slave devices.
// Multi-device is the exception to this rule because we want to automatically be friends with a secondary device if (!canSkip && threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE) { return; }
if (!force && threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE) { return; } // If the thread's friend request status is not `FRIENDS` or `NONE`, but we're receiving a message,
// If the thread's friend request status is not `FRIENDS`, but we're receiving a message,
// it must be a friend request accepted message. Declining a friend request doesn't send a message. // it must be a friend request accepted message. Declining a friend request doesn't send a message.
lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS); lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
// Send out a contact sync message // Send out a contact sync message if needed
if (syncContact) { if (requiresContactSync) {
MessageSender.syncContact(context, contactID.getAddress()); MessageSender.syncContact(context, contactID.getAddress());
} }
// Allow profile sharing with contact // Enable profile sharing with the recipient
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(contactID, true); DatabaseFactory.getRecipientDatabase(context).setProfileSharing(contactID, true);
// Update the last message if needed // Update the last message if needed
LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(pubKey).success(primaryDevice -> { LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey).success( masterHexEncodedPublicKey -> {
Util.runOnMain(() -> { Util.runOnMain(() -> {
long primaryDeviceThreadID = primaryDevice == null ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(primaryDevice), false)); long masterThreadID = (masterHexEncodedPublicKey == null) ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(masterHexEncodedPublicKey), false));
FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); FriendRequestHandler.updateLastFriendRequestMessage(context, masterThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
}); });
return Unit.INSTANCE; return Unit.INSTANCE;
}); });
@ -1296,25 +1272,24 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
if (!content.isFriendRequest() || message.isGroupMessage() || message.isSessionRequest()) { return; } if (!content.isFriendRequest() || message.isGroupMessage() || message.isSessionRequest()) { return; }
// This handles the case where another user sends us a regular message without authorisation
Promise<Boolean, Exception> promise = PromiseUtil.timeout(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), 8000); Promise<Boolean, Exception> promise = PromiseUtil.timeout(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), 8000);
boolean shouldBecomeFriends = PromiseUtil.get(promise, false); boolean shouldBecomeFriends = PromiseUtil.get(promise, false);
if (shouldBecomeFriends) { if (shouldBecomeFriends) {
// Become friends AND update the message they sent // Become friends AND update the message they sent
becomeFriendsWithContact(content.getSender(), true, true); becomeFriendsWithContactIfNeeded(content.getSender(), true, true);
// Send them an accept message back // Send them an accept message back
MessageSender.sendBackgroundMessage(context, content.getSender()); MessageSender.sendBackgroundMessage(context, content.getSender());
} else { } else {
// Do regular friend request logic checks // Do regular friend request logic checks
Recipient originalRecipient = getMessageDestination(content, message); Recipient originalRecipient = getRecipientForMessage(content, message);
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); Recipient masterRecipient = getMasterRecipientForMessage(content, message);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
// Loki - Friend requests only work in direct chats // Loki - Friend requests only work in direct chats
if (!originalRecipient.getAddress().isPhone()) { return; } if (!originalRecipient.getAddress().isPhone()) { return; }
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient);
long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDeviceRecipient); long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(masterRecipient);
LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID); LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) { if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
@ -1483,7 +1458,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
private void triggerSessionRestorePrompt(@NonNull String sender) { private void triggerSessionRestorePrompt(@NonNull String sender) {
Recipient primaryRecipient = getPrimaryDeviceRecipient(sender); Recipient primaryRecipient = getMasterRecipient(sender);
if (!primaryRecipient.isGroupRecipient()) { if (!primaryRecipient.isGroupRecipient()) {
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(primaryRecipient); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(primaryRecipient);
DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, sender); DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, sender);
@ -1560,11 +1535,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void handleDeliveryReceipt(@NonNull SignalServiceContent content, private void handleDeliveryReceipt(@NonNull SignalServiceContent content,
@NonNull SignalServiceReceiptMessage message) @NonNull SignalServiceReceiptMessage message)
{ {
// Redirect message to primary device conversation // Redirect message to master device conversation
Address sender = Address.fromSerialized(content.getSender()); Address sender = Address.fromSerialized(content.getSender());
if (sender.isPhone()) { if (sender.isPhone()) {
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender()); Recipient masterDevice = getMasterRecipient(content.getSender());
sender = primaryDevice.getAddress(); sender = masterDevice.getAddress();
} }
for (long timestamp : message.getTimestamps()) { for (long timestamp : message.getTimestamps()) {
@ -1580,11 +1555,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{ {
if (TextSecurePreferences.isReadReceiptsEnabled(context)) { if (TextSecurePreferences.isReadReceiptsEnabled(context)) {
// Redirect message to primary device conversation // Redirect message to master device conversation
Address sender = Address.fromSerialized(content.getSender()); Address sender = Address.fromSerialized(content.getSender());
if (sender.isPhone()) { if (sender.isPhone()) {
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender()); Recipient masterDevice = getMasterRecipient(content.getSender());
sender = primaryDevice.getAddress(); sender = masterDevice.getAddress();
} }
for (long timestamp : message.getTimestamps()) { for (long timestamp : message.getTimestamps()) {
@ -1615,7 +1590,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(groupRecipient); threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(groupRecipient);
} else { } else {
// See if we need to redirect the message // See if we need to redirect the message
author = getPrimaryDeviceRecipient(content.getSender()); author = getMasterRecipient(content.getSender());
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(author); threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(author);
} }
@ -1749,9 +1724,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
private Optional<InsertResult> insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp) { private Optional<InsertResult> insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp) {
Recipient primaryDevice = getPrimaryDeviceRecipient(sender); Recipient masterDevice = getMasterRecipient(sender);
SmsDatabase database = DatabaseFactory.getSmsDatabase(context); SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
IncomingTextMessage textMessage = new IncomingTextMessage(primaryDevice.getAddress(), IncomingTextMessage textMessage = new IncomingTextMessage(masterDevice.getAddress(),
senderDevice, timestamp, "", senderDevice, timestamp, "",
Optional.absent(), 0, false); Optional.absent(), 0, false);
@ -1771,11 +1746,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (message.getMessage().isGroupMessage()) { if (message.getMessage().isGroupMessage()) {
return getSyncMessageDestination(message); return getSyncMessageDestination(message);
} else { } else {
return getPrimaryDeviceRecipient(message.getDestination().get()); return getMasterRecipient(message.getDestination().get());
} }
} }
private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) { private Recipient getRecipientForMessage(SignalServiceContent content, SignalServiceDataMessage message) {
if (message.isGroupMessage()) { if (message.isGroupMessage()) {
return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get())), false); return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get())), false);
} else { } else {
@ -1783,34 +1758,34 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
} }
private Recipient getMessagePrimaryDestination(SignalServiceContent content, SignalServiceDataMessage message) { private Recipient getMasterRecipientForMessage(SignalServiceContent content, SignalServiceDataMessage message) {
if (message.isGroupMessage()) { if (message.isGroupMessage()) {
return getMessageDestination(content, message); return getRecipientForMessage(content, message);
} else { } else {
return getPrimaryDeviceRecipient(content.getSender()); return getMasterRecipient(content.getSender());
} }
} }
/** /**
* Get the primary device recipient of the passed in device. * Get the master device recipient of the provided device.
* *
* If the device doesn't have a primary device then it will return the same device. * If the device doesn't have a master device this will return the same device.
* If the device is our primary device then it will return our current device. * If the device is our master device then it will return our current device.
* Otherwise it will return the primary device. * Otherwise it will return the master device.
*/ */
private Recipient getPrimaryDeviceRecipient(String pubKey) { private Recipient getMasterRecipient(String hexEncodedPublicKey) {
try { try {
String primaryDevice = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(pubKey), 5000).get(); String masterHexEncodedPublicKey = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey), 5000).get();
String publicKey = (primaryDevice != null) ? primaryDevice : pubKey; String targetHexEncodedPublicKey = (masterHexEncodedPublicKey != null) ? masterHexEncodedPublicKey : hexEncodedPublicKey;
// If the public key matches our primary device then we need to forward the message to ourselves (Note to self) // If the public key matches our master device then we need to forward the message to ourselves (note to self)
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); String ourMasterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
if (ourPrimaryDevice != null && ourPrimaryDevice.equals(publicKey)) { if (ourMasterHexEncodedPublicKey != null && ourMasterHexEncodedPublicKey.equals(targetHexEncodedPublicKey)) {
publicKey = TextSecurePreferences.getLocalNumber(context); targetHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
} }
return Recipient.from(context, Address.fromSerialized(publicKey), false); return Recipient.from(context, Address.fromSerialized(targetHexEncodedPublicKey), false);
} catch (Exception e) { } catch (Exception e) {
Log.d("Loki", "Failed to get primary device public key for " + pubKey + ". " + e.getMessage()); Log.d("Loki", "Failed to get master device for: " + hexEncodedPublicKey + ". " + e.getMessage());
return Recipient.from(context, Address.fromSerialized(pubKey), false); return Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false);
} }
} }
@ -1836,7 +1811,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return false; return false;
} else if (content.getDataMessage().isPresent()) { } else if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get(); SignalServiceDataMessage message = content.getDataMessage().get();
Recipient conversation = getMessageDestination(content, message); Recipient conversation = getRecipientForMessage(content, message);
if (conversation.isGroupRecipient() && conversation.isBlocked()) { if (conversation.isGroupRecipient() && conversation.isBlocked()) {
return true; return true;