diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 61a7af375e..aaa14151d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -686,6 +686,15 @@ public class SmsDatabase extends MessagingDatabase { return insertMessageInbox(message, Types.BASE_INBOX_TYPE, serverTimestamp); } + public Optional insertMessageOutbox(long threadId, OutgoingTextMessage message, long serverTimestamp) { + long messageId = insertMessageOutbox(threadId, message, false, serverTimestamp, null); + if (messageId == -1) { + return Optional.absent(); + } + markAsSent(messageId, true); + return Optional.fromNullable(new InsertResult(messageId, threadId)); + } + public long insertMessageOutbox(long threadId, OutgoingTextMessage message, boolean forceSms, long date, InsertListener insertListener) { @@ -716,9 +725,17 @@ public class SmsDatabase extends MessagingDatabase { contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum()); contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum()); + SQLiteDatabase readDb = databaseHelper.getReadableDatabase(); + Cursor existingRecord = readDb.query(TABLE_NAME, null, String.format("%s = ? AND %s = ? AND %s = ?",ADDRESS, THREAD_ID, DATE_SENT), + new String[] { address.serialize(), Long.toString(threadId), Long.toString(date) }, null, null, null); + int existingRecordCount = existingRecord.getCount(); + if (existingRecordCount > 0) { + // return -1 because record exists from Address to ThreadID with the same date sent (probably sent from us) + return -1; + } + SQLiteDatabase db = databaseHelper.getWritableDatabase(); long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues); - if (insertListener != null) { insertListener.onComplete(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 0d23b6af7f..081946041c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -28,6 +28,7 @@ import org.session.libsignal.metadata.ProtocolNoSessionException; import org.session.libsignal.metadata.ProtocolUntrustedIdentityException; import org.session.libsignal.metadata.SelfSendException; import org.session.libsignal.service.loki.api.crypto.SessionProtocol; +import org.session.libsignal.service.loki.utilities.HexEncodingKt; import org.session.libsignal.utilities.PromiseUtilities; import org.thoughtcrime.securesms.ApplicationContext; @@ -568,6 +569,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType { { Recipient originalRecipient = getMessageDestination(content, message); Recipient masterRecipient = getMessageMasterDestination(content.getSender()); + String syncTarget = message.getSyncTarget().orNull(); + notifyTypingStoppedFromIncomingMessage(masterRecipient, content.getSender(), content.getSenderDevice()); @@ -582,75 +585,79 @@ public class PushDecryptJob extends BaseJob implements InjectableType { masterAddress = getMessageMasterDestination(content.getSender()).getAddress(); } - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterAddress, message.getTimestamp(), -1, - message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(), - quote, sharedContacts, linkPreviews, sticker); + if (syncTarget != null && !syncTarget.isEmpty()) { +// OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(masterAddress, message.getTimestamp(), -1, +// message.getExpiresInSeconds() * 1000L, false, ) + } else { + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterAddress, message.getTimestamp(), -1, + message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(), + quote, sharedContacts, linkPreviews, sticker); + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + database.beginTransaction(); - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - database.beginTransaction(); + // Ignore message if it has no body and no attachments + if (mediaMessage.getBody().isEmpty() && mediaMessage.getAttachments().isEmpty() && mediaMessage.getLinkPreviews().isEmpty()) { + return; + } - // Ignore message if it has no body and no attachments - if (mediaMessage.getBody().isEmpty() && mediaMessage.getAttachments().isEmpty() && mediaMessage.getLinkPreviews().isEmpty()) { - return; - } + Optional insertResult; - Optional insertResult; + try { + if (message.isGroupMessage()) { + insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1, content.getTimestamp()); + } else { + insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1); + } - try { - if (message.isGroupMessage()) { - insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1, content.getTimestamp()); - } else { - insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1); + if (insertResult.isPresent()) { + List allAttachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(insertResult.get().getMessageId()); + List stickerAttachments = Stream.of(allAttachments).filter(Attachment::isSticker).toList(); + List attachments = Stream.of(allAttachments).filterNot(Attachment::isSticker).toList(); + + forceStickerDownloadIfNecessary(stickerAttachments); + + for (DatabaseAttachment attachment : attachments) { + ApplicationContext.getInstance(context).getJobManager().add(new AttachmentDownloadJob(insertResult.get().getMessageId(), attachment.getAttachmentId(), false)); + } + + if (smsMessageId.isPresent()) { + DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); + } + + database.setTransactionSuccessful(); + } + } catch (MmsException e) { + throw new StorageFailedException(e, content.getSender(), content.getSenderDevice()); + } finally { + database.endTransaction(); } if (insertResult.isPresent()) { - List allAttachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(insertResult.get().getMessageId()); - List stickerAttachments = Stream.of(allAttachments).filter(Attachment::isSticker).toList(); - List attachments = Stream.of(allAttachments).filterNot(Attachment::isSticker).toList(); - - forceStickerDownloadIfNecessary(stickerAttachments); - - for (DatabaseAttachment attachment : attachments) { - ApplicationContext.getInstance(context).getJobManager().add(new AttachmentDownloadJob(insertResult.get().getMessageId(), attachment.getAttachmentId(), false)); - } - - if (smsMessageId.isPresent()) { - DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); - } - - database.setTransactionSuccessful(); - } - } catch (MmsException e) { - throw new StorageFailedException(e, content.getSender(), content.getSenderDevice()); - } finally { - database.endTransaction(); - } - - if (insertResult.isPresent()) { - messageNotifier.updateNotification(context, insertResult.get().getThreadId()); - } - - if (insertResult.isPresent()) { - InsertResult result = insertResult.get(); - - // Loki - Cache the user hex encoded public key (for mentions) - MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(result.getThreadId(), context); - MentionsManager.shared.cache(content.getSender(), result.getThreadId()); - - // Loki - Store message open group server ID if needed - if (messageServerIDOrNull.isPresent()) { - long messageID = result.getMessageId(); - long messageServerID = messageServerIDOrNull.get(); - LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context); - lokiMessageDatabase.setServerID(messageID, messageServerID); + messageNotifier.updateNotification(context, insertResult.get().getThreadId()); } - // Loki - Update mapping of message ID to original thread ID - if (result.getMessageId() > -1) { - ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); - LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context); - long originalThreadId = threadDatabase.getOrCreateThreadIdFor(originalRecipient); - lokiMessageDatabase.setOriginalThreadID(result.getMessageId(), originalThreadId); + if (insertResult.isPresent()) { + InsertResult result = insertResult.get(); + + // Loki - Cache the user hex encoded public key (for mentions) + MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(result.getThreadId(), context); + MentionsManager.shared.cache(content.getSender(), result.getThreadId()); + + // Loki - Store message open group server ID if needed + if (messageServerIDOrNull.isPresent()) { + long messageID = result.getMessageId(); + long messageServerID = messageServerIDOrNull.get(); + LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context); + lokiMessageDatabase.setServerID(messageID, messageServerID); + } + + // Loki - Update mapping of message ID to original thread ID + if (result.getMessageId() > -1) { + ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); + LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context); + long originalThreadId = threadDatabase.getOrCreateThreadIdFor(originalRecipient); + lokiMessageDatabase.setOriginalThreadID(result.getMessageId(), originalThreadId); + } } } } @@ -769,6 +776,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { String body = message.getBody().isPresent() ? message.getBody().get() : ""; Recipient originalRecipient = getMessageDestination(content, message); Recipient masterRecipient = getMessageMasterDestination(content.getSender()); + String syncTarget = message.getSyncTarget().orNull(); if (message.getExpiresInSeconds() != originalRecipient.getExpireMessages()) { handleExpirationUpdate(content, message, Optional.absent()); @@ -778,15 +786,46 @@ public class PushDecryptJob extends BaseJob implements InjectableType { if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) { threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second; + } else if (syncTarget != null && !syncTarget.isEmpty()) { + Address targetAddress = Address.fromSerialized(syncTarget); + + OutgoingTextMessage tm = new OutgoingTextMessage(Recipient.from(context, targetAddress, false), + body, message.getExpiresInSeconds(), -1); + + // Ignore the message if it has no body + if (tm.getMessageBody().length() == 0) { return; } + + // Check if we have the thread already + long threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(syncTarget); + + + // Insert the message into the database + Optional insertResult; + insertResult = database.insertMessageOutbox(threadID, tm, content.getTimestamp()); + + if (insertResult.isPresent()) { + threadId = insertResult.get().getThreadId(); + } + + if (smsMessageId.isPresent()) database.deleteMessage(smsMessageId.get()); + + if (threadId != null) { + messageNotifier.updateNotification(context, threadId); + } + + if (insertResult.isPresent()) { + InsertResult result = insertResult.get(); + + // Loki - Cache the user hex encoded public key (for mentions) + MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(result.getThreadId(), context); + MentionsManager.shared.cache(content.getSender(), result.getThreadId()); + } + } else { notifyTypingStoppedFromIncomingMessage(masterRecipient, content.getSender(), content.getSenderDevice()); Address masterAddress = masterRecipient.getAddress(); - if (message.isGroupMessage()) { - masterAddress = getMessageMasterDestination(content.getSender()).getAddress(); - } - IncomingTextMessage tm = new IncomingTextMessage(masterAddress, content.getSenderDevice(), message.getTimestamp(), body, diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index 1c7df45f52..e4c8b588df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -15,6 +15,7 @@ import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.Address; import org.session.libsession.utilities.GroupUtil; +import org.session.libsession.utilities.TextSecurePreferences; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -143,6 +144,9 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { List existingNetworkFailures = message.getNetworkFailures(); List existingIdentityMismatches = message.getIdentityKeyMismatches(); + String userPublicKey = TextSecurePreferences.getLocalNumber(context); + SignalServiceAddress localAddress = new SignalServiceAddress(userPublicKey); + if (database.isSent(messageId)) { log(TAG, "Message " + messageId + " was already sent. Ignoring."); return; @@ -190,6 +194,22 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { } if (existingNetworkFailures.isEmpty() && networkFailures.isEmpty() && identityMismatches.isEmpty() && existingIdentityMismatches.isEmpty()) { + Address address = message.getRecipient().getAddress(); + if (!address.isOpenGroup()) { + try { + SignalServiceDataMessage selfSend = getDataMessage(address, message) + .withSyncTarget(address.toGroupString()) + .build(); + // send to ourselves to sync multi-device + Optional syncAccess = UnidentifiedAccessUtil.getAccessForSync(context); + SendMessageResult selfSendResult = messageSender.sendMessage(messageId, localAddress, syncAccess, selfSend); + if (selfSendResult.getLokiAPIError() != null) { + throw selfSendResult.getLokiAPIError(); + } + } catch (Exception e) { + Log.e("Loki", "Error sending message to ourselves", e); + } + } database.markAsSent(messageId, true); markAttachmentsUploaded(messageId, message.getAttachments()); @@ -238,25 +258,18 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { // return results; // } - String groupId = address.toGroupString(); - Optional profileKey = getProfileKey(message.getRecipient()); - Optional quote = getQuoteFor(message); - Optional sticker = getStickerFor(message); - List sharedContacts = getSharedContactsFor(message); - List previews = getPreviewsFor(message); List addresses = Stream.of(destinations).map(this::getPushAddress).toList(); - List attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList(); - List attachmentPointers = getAttachmentPointersFor(attachments); - List> unidentifiedAccess = Stream.of(addresses) .map(a -> Address.Companion.fromSerialized(a.getNumber())) .map(a -> Recipient.from(context, a, false)) .map(recipient -> UnidentifiedAccessUtil.getAccessFor(context, recipient)) .toList(); - SignalServiceGroup.GroupType groupType = address.isOpenGroup() ? SignalServiceGroup.GroupType.PUBLIC_CHAT : SignalServiceGroup.GroupType.SIGNAL; - if (message.isGroup() && address.isClosedGroup()) { + SignalServiceGroup.GroupType groupType = address.isOpenGroup() ? SignalServiceGroup.GroupType.PUBLIC_CHAT : SignalServiceGroup.GroupType.SIGNAL; + String groupId = address.toGroupString(); + List attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList(); + List attachmentPointers = getAttachmentPointersFor(attachments); // Loki - Only send GroupUpdate or GroupQuit messages to closed groups OutgoingGroupMediaMessage groupMessage = (OutgoingGroupMediaMessage) message; GroupContext groupContext = groupMessage.getGroupContext(); @@ -271,25 +284,40 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { return messageSender.sendMessage(messageId, addresses, unidentifiedAccess, groupDataMessage); } else { - SignalServiceGroup group = new SignalServiceGroup(GroupUtil.getDecodedGroupIDAsData(groupId), groupType); - SignalServiceDataMessage groupMessage = SignalServiceDataMessage.newBuilder() - .withTimestamp(message.getSentTimeMillis()) - .asGroupMessage(group) - .withAttachments(attachmentPointers) - .withBody(message.getBody()) - .withExpiration((int)(message.getExpiresIn() / 1000)) - .asExpirationUpdate(message.isExpirationUpdate()) - .withProfileKey(profileKey.orNull()) - .withQuote(quote.orNull()) - .withSticker(sticker.orNull()) - .withSharedContacts(sharedContacts) - .withPreviews(previews) - .build(); + SignalServiceDataMessage groupMessage = getDataMessage(address, message).build(); return messageSender.sendMessage(messageId, addresses, unidentifiedAccess, groupMessage); } } + public SignalServiceDataMessage.Builder getDataMessage(Address address, OutgoingMediaMessage message) { + + SignalServiceGroup.GroupType groupType = address.isOpenGroup() ? SignalServiceGroup.GroupType.PUBLIC_CHAT : SignalServiceGroup.GroupType.SIGNAL; + + String groupId = address.toGroupString(); + Optional profileKey = getProfileKey(message.getRecipient()); + Optional quote = getQuoteFor(message); + Optional sticker = getStickerFor(message); + List sharedContacts = getSharedContactsFor(message); + List previews = getPreviewsFor(message); + List attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList(); + List attachmentPointers = getAttachmentPointersFor(attachments); + + SignalServiceGroup group = new SignalServiceGroup(GroupUtil.getDecodedGroupIDAsData(groupId), groupType); + return SignalServiceDataMessage.newBuilder() + .withTimestamp(message.getSentTimeMillis()) + .asGroupMessage(group) + .withAttachments(attachmentPointers) + .withBody(message.getBody()) + .withExpiration((int)(message.getExpiresIn() / 1000)) + .asExpirationUpdate(message.isExpirationUpdate()) + .withProfileKey(profileKey.orNull()) + .withQuote(quote.orNull()) + .withSticker(sticker.orNull()) + .withSharedContacts(sharedContacts) + .withPreviews(previews); + } + public static class Factory implements Job.Factory { @Override public @NonNull PushGroupSendJob create(@NonNull Parameters parameters, @NonNull Data data) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 19254a7d74..c89b44f131 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -245,7 +245,9 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { { try { Recipient recipient = Recipient.from(context, destination, false); + String userPublicKey = TextSecurePreferences.getLocalNumber(context); SignalServiceAddress address = getPushAddress(recipient.getAddress()); + SignalServiceAddress localAddress = new SignalServiceAddress(userPublicKey); List attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList(); List serviceAttachments = getAttachmentPointersFor(attachments); Optional profileKey = getProfileKey(message.getRecipient()); @@ -254,6 +256,8 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { List sharedContacts = getSharedContactsFor(message); List previews = getPreviewsFor(message); + Optional unidentifiedAccessPair = UnidentifiedAccessUtil.getAccessFor(context, recipient); + SignalServiceDataMessage mediaMessage = SignalServiceDataMessage.newBuilder() .withBody(message.getBody()) .withAttachments(serviceAttachments) @@ -267,6 +271,20 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { .asExpirationUpdate(message.isExpirationUpdate()) .build(); + SignalServiceDataMessage mediaSelfSendMessage = SignalServiceDataMessage.newBuilder() + .withBody(message.getBody()) + .withAttachments(serviceAttachments) + .withTimestamp(message.getSentTimeMillis()) + .withSyncTarget(destination.serialize()) + .withExpiration((int)(message.getExpiresIn() / 1000)) + .withProfileKey(profileKey.orNull()) + .withQuote(quote.orNull()) + .withSticker(sticker.orNull()) + .withSharedContacts(sharedContacts) + .withPreviews(previews) + .asExpirationUpdate(message.isExpirationUpdate()) + .build(); + if (SessionMetaProtocol.shared.isNoteToSelf(address.getNumber())) { // Loki - Device link messages don't go through here Optional syncAccess = UnidentifiedAccessUtil.getAccessForSync(context); @@ -275,11 +293,24 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { messageSender.sendMessage(syncMessage, syncAccess); return syncAccess.isPresent(); } else { - SendMessageResult result = messageSender.sendMessage(messageId, address, UnidentifiedAccessUtil.getAccessFor(context, recipient), mediaMessage); + SendMessageResult result = messageSender.sendMessage(messageId, address, unidentifiedAccessPair, mediaMessage); if (result.getLokiAPIError() != null) { throw result.getLokiAPIError(); } else { - return result.getSuccess().isUnidentified(); + boolean isUnidentified = result.getSuccess().isUnidentified(); + + try { + // send to ourselves to sync multi-device + Optional syncAccess = UnidentifiedAccessUtil.getAccessForSync(context); + SendMessageResult selfSendResult = messageSender.sendMessage(messageId, localAddress, syncAccess, mediaSelfSendMessage); + if (selfSendResult.getLokiAPIError() != null) { + throw selfSendResult.getLokiAPIError(); + } + } catch (Exception e) { + Log.e("Loki", "Error sending message to ourselves", e); + } + + return isUnidentified; } } } catch (UnregisteredUserException e) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 00789a08ef..acac2e3d98 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs; import androidx.annotation.NonNull; import org.session.libsession.messaging.jobs.Data; +import org.session.libsignal.service.api.crypto.UnidentifiedAccess; import org.session.libsignal.utilities.logging.Log; import org.session.libsession.messaging.threads.Address; @@ -192,8 +193,10 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException, SnodeAPI.Error { try { + String userPublicKey = TextSecurePreferences.getLocalNumber(context); Recipient recipient = Recipient.from(context, destination, false); SignalServiceAddress address = getPushAddress(recipient.getAddress()); + SignalServiceAddress localAddress = new SignalServiceAddress(userPublicKey); Optional profileKey = getProfileKey(recipient); Optional unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient); @@ -205,13 +208,21 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { // } SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder() - .withTimestamp(message.getDateSent()) - .withBody(message.getBody()) - .withExpiration((int)(message.getExpiresIn() / 1000)) - .withProfileKey(profileKey.orNull()) -// .withPreKeyBundle(preKeyBundle) - .asEndSessionMessage(message.isEndSession()) - .build(); + .withTimestamp(message.getDateSent()) + .withBody(message.getBody()) + .withExpiration((int)(message.getExpiresIn() / 1000)) + .withProfileKey(profileKey.orNull()) + .asEndSessionMessage(message.isEndSession()) + .build(); + + SignalServiceDataMessage textSecureSelfSendMessage = SignalServiceDataMessage.newBuilder() + .withTimestamp(message.getDateSent()) + .withBody(message.getBody()) + .withSyncTarget(destination.serialize()) + .withExpiration((int)(message.getExpiresIn() / 1000)) + .withProfileKey(profileKey.orNull()) + .asEndSessionMessage(message.isEndSession()) + .build(); if (SessionMetaProtocol.shared.isNoteToSelf(address.getNumber())) { // Loki - Device link messages don't go through here @@ -225,7 +236,19 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { if (result.getLokiAPIError() != null) { throw result.getLokiAPIError(); } else { - return result.getSuccess().isUnidentified(); + boolean isUnidentified = result.getSuccess().isUnidentified(); + + try { + // send to ourselves to sync multi-device + Optional syncAccess = UnidentifiedAccessUtil.getAccessForSync(context); + SendMessageResult selfSendResult = messageSender.sendMessage(messageId, localAddress, syncAccess, textSecureSelfSendMessage); + if (selfSendResult.getLokiAPIError() != null) { + throw selfSendResult.getLokiAPIError(); + } + } catch (Exception e) { + Log.e("Loki", "Error sending message to ourselves", e); + } + return isUnidentified; } } } catch (UnregisteredUserException e) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupUpdateMessageSendJob.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupUpdateMessageSendJob.kt index 7f24078034..28f4a5a864 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupUpdateMessageSendJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupUpdateMessageSendJob.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki.protocol import com.google.protobuf.ByteString import org.session.libsession.messaging.jobs.Data +import org.session.libsignal.libsignal.util.guava.Optional import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil import org.thoughtcrime.securesms.jobmanager.Job @@ -128,7 +129,7 @@ class ClosedGroupUpdateMessageSendJob private constructor(parameters: Parameters // isClosedGroup can always be false as it's only used in the context of legacy closed groups messageSender.sendMessage(0, address, udAccess.get().targetUnidentifiedAccess, Date().time, serializedContentMessage, false, ttl, false, - useFallbackEncryption, false, false, false) + useFallbackEncryption, false, false, Optional.absent()) } catch (e: Exception) { Log.d("Loki", "Failed to send closed group update message to: $destination due to error: $e.") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupUpdateMessageSendJobV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupUpdateMessageSendJobV2.kt index 3fcb203e74..8ae19749e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupUpdateMessageSendJobV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupUpdateMessageSendJobV2.kt @@ -9,6 +9,7 @@ import org.session.libsession.messaging.jobs.Data import org.session.libsignal.libsignal.ecc.DjbECPrivateKey import org.session.libsignal.libsignal.ecc.DjbECPublicKey import org.session.libsignal.libsignal.ecc.ECKeyPair +import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.push.SignalServiceAddress import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.loki.protocol.meta.TTLUtilities @@ -221,7 +222,7 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete // isClosedGroup can always be false as it's only used in the context of legacy closed groups messageSender.sendMessage(0, address, udAccess.get().targetUnidentifiedAccess, Date().time, serializedContentMessage, false, ttl, false, - true, false, false, false) + true, false, false, Optional.absent()) } catch (e: Exception) { Log.d("Loki", "Failed to send closed group update message to: $destination due to error: $e.") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/NullMessageSendJob.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/NullMessageSendJob.kt index 834c01ea23..9f0eff11c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/NullMessageSendJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/NullMessageSendJob.kt @@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint import org.thoughtcrime.securesms.jobs.BaseJob import org.session.libsignal.utilities.logging.Log import org.session.libsession.messaging.threads.recipients.Recipient +import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.push.SignalServiceAddress import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.loki.protocol.meta.TTLUtilities @@ -56,7 +57,7 @@ class NullMessageSendJob private constructor(parameters: Parameters, private val try { messageSender.sendMessage(0, address, udAccess.get().targetUnidentifiedAccess, Date().time, serializedContentMessage, false, ttl, false, - false, false, false, false) + false, false, false, Optional.absent()) } catch (e: Exception) { Log.d("Loki", "Failed to send null message to: $publicKey due to error: $e.") throw e diff --git a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingEncryptedMessage.java b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingEncryptedMessage.java index 63489bf7bc..684329fc77 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingEncryptedMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingEncryptedMessage.java @@ -6,12 +6,7 @@ public class IncomingEncryptedMessage extends IncomingTextMessage { super(base, newBody); } - @Override - public IncomingTextMessage withMessageBody(String body) { - return new IncomingEncryptedMessage(this, body); - } - - @Override + @Override public boolean isSecureMessage() { return true; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingEndSessionMessage.java b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingEndSessionMessage.java index 9277e989a7..f5b7b5e402 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingEndSessionMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingEndSessionMessage.java @@ -10,12 +10,7 @@ public class IncomingEndSessionMessage extends IncomingTextMessage { super(base, newBody); } - @Override - public IncomingEndSessionMessage withMessageBody(String messageBody) { - return new IncomingEndSessionMessage(this, messageBody); - } - - @Override + @Override public boolean isEndSession() { return true; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingGroupMessage.java b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingGroupMessage.java index 546881ceba..af17d4b963 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingGroupMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingGroupMessage.java @@ -11,12 +11,7 @@ public class IncomingGroupMessage extends IncomingTextMessage { this.groupContext = groupContext; } - @Override - public IncomingGroupMessage withMessageBody(String body) { - return new IncomingGroupMessage(this, groupContext, body); - } - - @Override + @Override public boolean isGroup() { return true; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingPreKeyBundleMessage.java b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingPreKeyBundleMessage.java index 1910c366f4..39f75a5da0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingPreKeyBundleMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingPreKeyBundleMessage.java @@ -9,12 +9,7 @@ public class IncomingPreKeyBundleMessage extends IncomingTextMessage { this.legacy = legacy; } - @Override - public IncomingPreKeyBundleMessage withMessageBody(String messageBody) { - return new IncomingPreKeyBundleMessage(this, messageBody, legacy); - } - - @Override + @Override public boolean isLegacyPreKeyBundle() { return legacy; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingTextMessage.java index 36f01071d9..eaf06f80d0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingTextMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingTextMessage.java @@ -175,10 +175,6 @@ public class IncomingTextMessage implements Parcelable { return message; } - public IncomingTextMessage withMessageBody(String message) { - return new IncomingTextMessage(this, message); - } - public Address getSender() { return sender; } @@ -250,7 +246,6 @@ public class IncomingTextMessage implements Parcelable { public boolean isUnidentified() { return unidentified; } - @Override public int describeContents() { return 0; diff --git a/libsession/src/main/java/org/session/libsession/messaging/threads/Address.kt b/libsession/src/main/java/org/session/libsession/messaging/threads/Address.kt index 72291a8a2c..3b2556272a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/threads/Address.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/threads/Address.kt @@ -152,6 +152,7 @@ class Address private constructor(address: String) : Parcelable, Comparable>() + @JvmStatic fun fromSerialized(serialized: String): Address { return Address(serialized) } diff --git a/libsignal/src/main/java/org/session/libsignal/service/api/SignalServiceMessageSender.java b/libsignal/src/main/java/org/session/libsignal/service/api/SignalServiceMessageSender.java index f0a9d2d0ec..263b9276bf 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/api/SignalServiceMessageSender.java +++ b/libsignal/src/main/java/org/session/libsignal/service/api/SignalServiceMessageSender.java @@ -26,10 +26,6 @@ import org.session.libsignal.service.api.messages.SignalServiceDataMessage; import org.session.libsignal.service.api.messages.SignalServiceGroup; import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage; import org.session.libsignal.service.api.messages.SignalServiceTypingMessage; -import org.session.libsignal.service.api.messages.calls.AnswerMessage; -import org.session.libsignal.service.api.messages.calls.IceUpdateMessage; -import org.session.libsignal.service.api.messages.calls.OfferMessage; -import org.session.libsignal.service.api.messages.calls.SignalServiceCallMessage; import org.session.libsignal.service.api.messages.multidevice.BlockedListMessage; import org.session.libsignal.service.api.messages.multidevice.ConfigurationMessage; import org.session.libsignal.service.api.messages.multidevice.ReadMessage; @@ -51,7 +47,6 @@ import org.session.libsignal.service.internal.push.PushServiceSocket; import org.session.libsignal.service.internal.push.PushTransportDetails; import org.session.libsignal.service.internal.push.SignalServiceProtos; import org.session.libsignal.service.internal.push.SignalServiceProtos.AttachmentPointer; -import org.session.libsignal.service.internal.push.SignalServiceProtos.CallMessage; import org.session.libsignal.service.internal.push.SignalServiceProtos.Content; import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage; import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext; @@ -217,34 +212,14 @@ public class SignalServiceMessageSender { * @param recipient The sender of the received message you're acknowledging. * @param message The read receipt to deliver. * @throws IOException - * @throws UntrustedIdentityException */ public void sendReceipt(SignalServiceAddress recipient, Optional unidentifiedAccess, SignalServiceReceiptMessage message) - throws IOException, UntrustedIdentityException - { + throws IOException { byte[] content = createReceiptContent(message); boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(message, recipient.getNumber(), store); - sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), message.getWhen(), content, false, message.getTTL(), useFallbackEncryption, false); - } - - /** - * Send a typing indicator. - * - * @param recipient The destination - * @param message The typing indicator to deliver - * @throws IOException - * @throws UntrustedIdentityException - */ - public void sendTyping(SignalServiceAddress recipient, - Optional unidentifiedAccess, - SignalServiceTypingMessage message) - throws IOException, UntrustedIdentityException - { - byte[] content = createTypingContent(message); - boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(message, recipient.getNumber(), store); - sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), message.getTimestamp(), content, true, message.getTTL(), useFallbackEncryption, false); + sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), message.getWhen(), content, false, message.getTTL(), useFallbackEncryption); } public void sendTyping(List recipients, @@ -256,42 +231,24 @@ public class SignalServiceMessageSender { sendMessage(0, recipients, getTargetUnidentifiedAccess(unidentifiedAccess), message.getTimestamp(), content, true, message.getTTL(), false, false); } - /** - * Send a call setup message to a single recipient. - * - * @param recipient The message's destination. - * @param message The call message. - * @throws IOException - */ - public void sendCallMessage(SignalServiceAddress recipient, - Optional unidentifiedAccess, - SignalServiceCallMessage message) - throws IOException, UntrustedIdentityException - { - byte[] content = createCallContent(message); - boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(message, recipient.getNumber(), store); - sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), content, false, message.getTTL(), useFallbackEncryption, false); - } - /** * Send a message to a single recipient. * * @param recipient The message's destination. * @param message The message. - * @throws UntrustedIdentityException * @throws IOException */ public SendMessageResult sendMessage(long messageID, SignalServiceAddress recipient, Optional unidentifiedAccess, SignalServiceDataMessage message) - throws UntrustedIdentityException, IOException + throws IOException { byte[] content = createMessageContent(message, recipient); long timestamp = message.getTimestamp(); boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(message, recipient.getNumber(), store); boolean isClosedGroup = message.group.isPresent() && message.group.get().getGroupType() == SignalServiceGroup.GroupType.SIGNAL; - SendMessageResult result = sendMessage(messageID, recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, content, false, message.getTTL(), message.getDeviceLink().isPresent(), useFallbackEncryption, isClosedGroup, false, message.hasVisibleContent()); + SendMessageResult result = sendMessage(messageID, recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, content, false, message.getTTL(), message.getDeviceLink().isPresent(), useFallbackEncryption, isClosedGroup, message.hasVisibleContent(), message.getSyncTarget()); // // Loki - This shouldn't get invoked for note to self // boolean wouldSignalSendSyncMessage = (result.getSuccess() != null && result.getSuccess().isNeedsSync()) || unidentifiedAccess.isPresent(); @@ -325,8 +282,7 @@ public class SignalServiceMessageSender { List recipients, List> unidentifiedAccess, SignalServiceDataMessage message) - throws IOException, UntrustedIdentityException - { + throws IOException { // Loki - We only need the first recipient in the line below. This is because the recipient is only used to determine // whether an attachment is being sent to an open group or not. byte[] content = createMessageContent(message, recipients.get(0)); @@ -350,7 +306,7 @@ public class SignalServiceMessageSender { for (String device : linkedDevices) { SignalServiceAddress deviceAsAddress = new SignalServiceAddress(device); boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(syncMessage, device, store); - sendMessage(deviceAsAddress, Optional.absent(), timestamp, syncMessage, false, message.getTTL(), useFallbackEncryption, true); + sendMessage(deviceAsAddress, Optional.absent(), timestamp, syncMessage, false, message.getTTL(), useFallbackEncryption); } } @@ -392,18 +348,10 @@ public class SignalServiceMessageSender { for (String device : linkedDevices) { SignalServiceAddress deviceAsAddress = new SignalServiceAddress(device); boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(message, device, store); - sendMessageToPrivateChat(0, deviceAsAddress, Optional.absent(), timestamp, content, false, message.getTTL(), useFallbackEncryption, false, false); + // sendMessageToPrivateChat(0, deviceAsAddress, Optional.absent(), timestamp, content, false, message.getTTL(), useFallbackEncryption, false, false); } } - public void setSoTimeoutMillis(long soTimeoutMillis) { - socket.setSoTimeoutMillis(soTimeoutMillis); - } - - public void cancelInFlightRequests() { - socket.cancelInFlightRequests(); - } - public void setMessagePipe(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) { this.pipe.set(Optional.fromNullable(pipe)); this.unidentifiedPipe.set(Optional.fromNullable(unidentifiedPipe)); @@ -452,9 +400,7 @@ public class SignalServiceMessageSender { result.getUrl()); } - private void sendMessage(VerifiedMessage message, Optional unidentifiedAccess) - throws IOException, UntrustedIdentityException - { + private void sendMessage(VerifiedMessage message, Optional unidentifiedAccess) { } @@ -494,32 +440,6 @@ public class SignalServiceMessageSender { { Content.Builder container = Content.newBuilder(); -// if (message.getPreKeyBundle().isPresent()) { -// PreKeyBundle preKeyBundle = message.getPreKeyBundle().get(); -// PreKeyBundleMessage.Builder preKeyBundleMessageBuilder = PreKeyBundleMessage.newBuilder() -// .setDeviceId(preKeyBundle.getDeviceId()) -// .setIdentityKey(ByteString.copyFrom(preKeyBundle.getIdentityKey().serialize())) -// .setPreKeyId(preKeyBundle.getPreKeyId()) -// .setPreKey(ByteString.copyFrom(preKeyBundle.getPreKey().serialize())) -// .setSignedKeyId(preKeyBundle.getSignedPreKeyId()) -// .setSignedKey(ByteString.copyFrom(preKeyBundle.getSignedPreKey().serialize())) -// .setSignature(ByteString.copyFrom(preKeyBundle.getSignedPreKeySignature())) -// .setIdentityKey(ByteString.copyFrom(preKeyBundle.getIdentityKey().serialize())); -// container.setPreKeyBundleMessage(preKeyBundleMessageBuilder); -// } - -// if (message.getDeviceLink().isPresent()) { -// DeviceLink deviceLink = message.getDeviceLink().get(); -// SignalServiceProtos.DeviceLinkMessage.Builder deviceLinkMessageBuilder = SignalServiceProtos.DeviceLinkMessage.newBuilder() -// .setPrimaryPublicKey(deviceLink.getMasterPublicKey()) -// .setSecondaryPublicKey(deviceLink.getSlavePublicKey()) -// .setRequestSignature(ByteString.copyFrom(Objects.requireNonNull(deviceLink.getRequestSignature()))); -// if (deviceLink.getAuthorizationSignature() != null) { -// deviceLinkMessageBuilder.setAuthorizationSignature(ByteString.copyFrom(deviceLink.getAuthorizationSignature())); -// } -// container.setDeviceLinkMessage(deviceLinkMessageBuilder.build()); -// } - DataMessage.Builder builder = DataMessage.newBuilder(); List pointers = createAttachmentPointers(message.getAttachments(), recipient); @@ -559,6 +479,10 @@ public class SignalServiceMessageSender { builder.setProfileKey(ByteString.copyFrom(message.getProfileKey().get())); } + if (message.getSyncTarget().isPresent()) { + builder.setSyncTarget(message.getSyncTarget().get()); + } + if (message.getQuote().isPresent()) { DataMessage.Quote.Builder quoteBuilder = DataMessage.Quote.newBuilder() .setId(message.getQuote().get().getId()) @@ -636,40 +560,6 @@ public class SignalServiceMessageSender { return container.build().toByteArray(); } - private byte[] createCallContent(SignalServiceCallMessage callMessage) { - Content.Builder container = Content.newBuilder(); - CallMessage.Builder builder = CallMessage.newBuilder(); - - if (callMessage.getOfferMessage().isPresent()) { - OfferMessage offer = callMessage.getOfferMessage().get(); - builder.setOffer(CallMessage.Offer.newBuilder() - .setId(offer.getId()) - .setDescription(offer.getDescription())); - } else if (callMessage.getAnswerMessage().isPresent()) { - AnswerMessage answer = callMessage.getAnswerMessage().get(); - builder.setAnswer(CallMessage.Answer.newBuilder() - .setId(answer.getId()) - .setDescription(answer.getDescription())); - } else if (callMessage.getIceUpdateMessages().isPresent()) { - List updates = callMessage.getIceUpdateMessages().get(); - - for (IceUpdateMessage update : updates) { - builder.addIceUpdate(CallMessage.IceUpdate.newBuilder() - .setId(update.getId()) - .setSdp(update.getSdp()) - .setSdpMid(update.getSdpMid()) - .setSdpMLineIndex(update.getSdpMLineIndex())); - } - } else if (callMessage.getHangupMessage().isPresent()) { - builder.setHangup(CallMessage.Hangup.newBuilder().setId(callMessage.getHangupMessage().get().getId())); - } else if (callMessage.getBusyMessage().isPresent()) { - builder.setBusy(CallMessage.Busy.newBuilder().setId(callMessage.getBusyMessage().get().getId())); - } - - container.setCallMessage(builder); - return container.build().toByteArray(); - } - private byte[] createMultiDeviceContactsContent(SignalServiceAttachmentStream contacts, boolean complete) throws IOException { @@ -987,6 +877,7 @@ public class SignalServiceMessageSender { throws IOException { List results = new LinkedList<>(); + SignalServiceAddress ownAddress = localAddress; Iterator recipientIterator = recipients.iterator(); Iterator> unidentifiedAccessIterator = unidentifiedAccess.iterator(); @@ -995,7 +886,7 @@ public class SignalServiceMessageSender { try { boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(content, recipient.getNumber(), store); - SendMessageResult result = sendMessage(messageID, recipient, unidentifiedAccessIterator.next(), timestamp, content, online, ttl, false, useFallbackEncryption, isClosedGroup, false, notifyPNServer); + SendMessageResult result = sendMessage(messageID, recipient, unidentifiedAccessIterator.next(), timestamp, content, online, ttl, false, useFallbackEncryption, isClosedGroup, notifyPNServer, Optional.absent()); results.add(result); } catch (UnregisteredUserException e) { Log.w(TAG, e); @@ -1009,41 +900,46 @@ public class SignalServiceMessageSender { return results; } - private SendMessageResult sendMessage(SignalServiceAddress recipient, + private SendMessageResult sendMessage(SignalServiceAddress recipient, Optional unidentifiedAccess, - long timestamp, - byte[] content, - boolean online, - int ttl, - boolean useFallbackEncryption, - boolean isSyncMessage) + long timestamp, + byte[] content, + boolean online, + int ttl, + boolean useFallbackEncryption) throws IOException { // Loki - This method is only invoked for various types of control messages - return sendMessage(0, recipient, unidentifiedAccess, timestamp, content, online, ttl, false, false, useFallbackEncryption, isSyncMessage, false); + return sendMessage(0, recipient, unidentifiedAccess, timestamp, content, online, ttl, false, false, useFallbackEncryption, false,Optional.absent()); } - public SendMessageResult sendMessage(final long messageID, - final SignalServiceAddress recipient, + public SendMessageResult sendMessage(final long messageID, + final SignalServiceAddress recipient, Optional unidentifiedAccess, - long timestamp, - byte[] content, - boolean online, - int ttl, - boolean isDeviceLinkMessage, - boolean useFallbackEncryption, - boolean isClosedGroup, - boolean isSyncMessage, - boolean notifyPNServer) + long timestamp, + byte[] content, + boolean online, + int ttl, + boolean isDeviceLinkMessage, + boolean useFallbackEncryption, + boolean isClosedGroup, + boolean notifyPNServer, + Optional syncTarget) throws IOException { - long threadID = threadDatabase.getThreadID(recipient.getNumber()); + boolean isSelfSend = syncTarget.isPresent() && !syncTarget.get().isEmpty(); + long threadID; + if (isSelfSend) { + threadID = threadDatabase.getThreadID(syncTarget.get()); + } else { + threadID = threadDatabase.getThreadID(recipient.getNumber()); + } PublicChat publicChat = threadDatabase.getPublicChat(threadID); try { if (publicChat != null) { return sendMessageToPublicChat(messageID, recipient, timestamp, content, publicChat); } else { - return sendMessageToPrivateChat(messageID, recipient, unidentifiedAccess, timestamp, content, online, ttl, useFallbackEncryption, isClosedGroup, notifyPNServer); + return sendMessageToPrivateChat(messageID, recipient, unidentifiedAccess, timestamp, content, online, ttl, useFallbackEncryption, isClosedGroup, notifyPNServer, syncTarget); } } catch (PushNetworkException e) { return SendMessageResult.networkFailure(recipient); @@ -1152,10 +1048,11 @@ public class SignalServiceMessageSender { int ttl, boolean useFallbackEncryption, boolean isClosedGroup, - final boolean notifyPNServer) + final boolean notifyPNServer, + Optional syncTarget) throws IOException, UntrustedIdentityException { - if (recipient.getNumber().equals(userPublicKey)) { return SendMessageResult.success(recipient, false, false); } + if (recipient.getNumber().equals(userPublicKey) && !syncTarget.isPresent()) { return SendMessageResult.success(recipient, false, false); } final SettableFuture[] future = { new SettableFuture() }; OutgoingPushMessageList messages = getSessionProtocolEncryptedMessage(recipient, timestamp, content); // Loki - Remove this when we have shared sender keys @@ -1221,14 +1118,10 @@ public class SignalServiceMessageSender { } return Unit.INSTANCE; } - }).fail(new Function1() { - - @Override - public Unit invoke(Exception exception) { - @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture)future[0]; - f.setException(exception); - return Unit.INSTANCE; - } + }).fail(exception -> { + @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture)future[0]; + f.setException(exception); + return Unit.INSTANCE; }); @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture)future[0]; @@ -1304,12 +1197,6 @@ public class SignalServiceMessageSender { return builder.build(); } - private AttachmentPointer createAttachmentPointer(SignalServiceAttachmentStream attachment) - throws IOException - { - return createAttachmentPointer(attachment, false, null); - } - private AttachmentPointer createAttachmentPointer(SignalServiceAttachmentStream attachment, SignalServiceAddress recipient) throws IOException { diff --git a/libsignal/src/main/java/org/session/libsignal/service/api/crypto/SignalServiceCipher.java b/libsignal/src/main/java/org/session/libsignal/service/api/crypto/SignalServiceCipher.java index 8f9521793b..608dee89cd 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/api/crypto/SignalServiceCipher.java +++ b/libsignal/src/main/java/org/session/libsignal/service/api/crypto/SignalServiceCipher.java @@ -389,6 +389,7 @@ public class SignalServiceCipher { ClosedGroupUpdate closedGroupUpdate = content.getClosedGroupUpdate(); ClosedGroupUpdateV2 closedGroupUpdateV2 = content.getClosedGroupUpdateV2(); boolean isDeviceUnlinkingRequest = ((content.getFlags() & DataMessage.Flags.DEVICE_UNLINKING_REQUEST_VALUE) != 0); + String syncTarget = content.getSyncTarget(); for (AttachmentPointer pointer : content.getAttachmentsList()) { attachments.add(createAttachmentPointer(pointer)); @@ -417,7 +418,8 @@ public class SignalServiceCipher { null, closedGroupUpdate, closedGroupUpdateV2, - isDeviceUnlinkingRequest); + isDeviceUnlinkingRequest, + syncTarget); } private SignalServiceSyncMessage createSynchronizeMessage(Metadata metadata, SyncMessage content) diff --git a/libsignal/src/main/java/org/session/libsignal/service/api/messages/SignalServiceDataMessage.java b/libsignal/src/main/java/org/session/libsignal/service/api/messages/SignalServiceDataMessage.java index 14853977c5..151eac5696 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/api/messages/SignalServiceDataMessage.java +++ b/libsignal/src/main/java/org/session/libsignal/service/api/messages/SignalServiceDataMessage.java @@ -41,6 +41,7 @@ public class SignalServiceDataMessage { private final Optional closedGroupUpdate; private final Optional closedGroupUpdateV2; private final boolean isDeviceUnlinkingRequest; + private final Optional syncTarget; /** * Construct a SignalServiceDataMessage with a body and no attachments. @@ -134,7 +135,7 @@ public class SignalServiceDataMessage { Quote quote, List sharedContacts, List previews, Sticker sticker) { - this(timestamp, group, attachments, body, endSession, expiresInSeconds, expirationUpdate, profileKey, profileKeyUpdate, quote, sharedContacts, previews, sticker, null, null, null, null, false); + this(timestamp, group, attachments, body, endSession, expiresInSeconds, expirationUpdate, profileKey, profileKeyUpdate, quote, sharedContacts, previews, sticker, null, null, null, null, false, null); } /** @@ -155,7 +156,7 @@ public class SignalServiceDataMessage { Quote quote, List sharedContacts, List previews, Sticker sticker, PreKeyBundle preKeyBundle, DeviceLink deviceLink, ClosedGroupUpdate closedGroupUpdate, ClosedGroupUpdateV2 closedGroupUpdateV2, - boolean isDeviceUnlinkingRequest) + boolean isDeviceUnlinkingRequest, String syncTarget) { this.timestamp = timestamp; this.body = Optional.fromNullable(body); @@ -172,6 +173,7 @@ public class SignalServiceDataMessage { this.closedGroupUpdate = Optional.fromNullable(closedGroupUpdate); this.closedGroupUpdateV2 = Optional.fromNullable(closedGroupUpdateV2); this.isDeviceUnlinkingRequest = isDeviceUnlinkingRequest; + this.syncTarget = Optional.fromNullable(syncTarget); if (attachments != null && !attachments.isEmpty()) { this.attachments = Optional.of(attachments); @@ -250,6 +252,10 @@ public class SignalServiceDataMessage { return profileKey; } + public Optional getSyncTarget() { + return syncTarget; + } + public Optional getQuote() { return quote; } @@ -307,6 +313,7 @@ public class SignalServiceDataMessage { private Sticker sticker; private PreKeyBundle preKeyBundle; private DeviceLink deviceLink; + private String syncTarget; private boolean isDeviceUnlinkingRequest; private Builder() {} @@ -336,6 +343,11 @@ public class SignalServiceDataMessage { return this; } + public Builder withSyncTarget(String syncTarget) { + this.syncTarget = syncTarget; + return this; + } + public Builder asEndSessionMessage() { return asEndSessionMessage(true); } @@ -417,7 +429,7 @@ public class SignalServiceDataMessage { profileKeyUpdate, quote, sharedContacts, previews, sticker, preKeyBundle, deviceLink, null, null, - isDeviceUnlinkingRequest); + isDeviceUnlinkingRequest, syncTarget); } }