From 57d532f4b804768bb32e1ee772eab8a20c315425 Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 8 Feb 2021 16:57:12 +1100 Subject: [PATCH 01/19] feat: add self sending syncTarget messages --- .../securesms/database/SmsDatabase.java | 19 +- .../securesms/jobs/PushDecryptJob.java | 167 ++++++++------ .../securesms/jobs/PushGroupSendJob.java | 78 ++++--- .../securesms/jobs/PushMediaSendJob.java | 35 ++- .../securesms/jobs/PushTextSendJob.java | 39 +++- .../ClosedGroupUpdateMessageSendJob.kt | 3 +- .../ClosedGroupUpdateMessageSendJobV2.kt | 3 +- .../loki/protocol/NullMessageSendJob.kt | 3 +- .../sms/IncomingEncryptedMessage.java | 7 +- .../sms/IncomingEndSessionMessage.java | 7 +- .../securesms/sms/IncomingGroupMessage.java | 7 +- .../sms/IncomingPreKeyBundleMessage.java | 7 +- .../securesms/sms/IncomingTextMessage.java | 5 - .../libsession/messaging/threads/Address.kt | 1 + .../api/SignalServiceMessageSender.java | 207 ++++-------------- .../api/crypto/SignalServiceCipher.java | 4 +- .../messages/SignalServiceDataMessage.java | 18 +- 17 files changed, 314 insertions(+), 296 deletions(-) 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 61a7af375..aaa14151d 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 0d23b6af7..081946041 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 1c7df45f5..e4c8b588d 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 19254a7d7..c89b44f13 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 00789a08e..acac2e3d9 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 7f2407803..28f4a5a86 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 3fcb203e7..8ae19749e 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 834c01ea2..9f0eff11c 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 63489bf7b..684329fc7 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 9277e989a..f5b7b5e40 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 546881ceb..af17d4b96 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 1910c366f..39f75a5da 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 36f01071d..eaf06f80d 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 72291a8a2..3b2556272 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 f0a9d2d0e..263b9276b 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 8f9521793..608dee89c 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 14853977c..151eac569 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); } } From 7c2b124ebc372151856cd2eb50b2dedd16ad4bf2 Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 8 Feb 2021 17:30:38 +1100 Subject: [PATCH 02/19] feat: adding the outbound attachment handling for handling media messages --- .../securesms/jobs/PushDecryptJob.java | 66 ++++++++++++++++--- 1 file changed, 57 insertions(+), 9 deletions(-) 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 081946041..7d3b72a97 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -28,7 +28,6 @@ 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; @@ -567,17 +566,17 @@ public class PushDecryptJob extends BaseJob implements InjectableType { @NonNull Optional messageServerIDOrNull) throws StorageFailedException { - Recipient originalRecipient = getMessageDestination(content, message); - Recipient masterRecipient = getMessageMasterDestination(content.getSender()); + Recipient originalRecipient = getMessageDestination(content, message); + Recipient masterRecipient = getMessageMasterDestination(content.getSender()); String syncTarget = message.getSyncTarget().orNull(); notifyTypingStoppedFromIncomingMessage(masterRecipient, content.getSender(), content.getSenderDevice()); - Optional quote = getValidatedQuote(message.getQuote()); - Optional> sharedContacts = getContacts(message.getSharedContacts()); - Optional> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or("")); - Optional sticker = getStickerAttachment(message.getSticker()); + Optional quote = getValidatedQuote(message.getQuote()); + Optional> sharedContacts = getContacts(message.getSharedContacts()); + Optional> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or("")); + Optional sticker = getStickerAttachment(message.getSticker()); Address masterAddress = masterRecipient.getAddress(); @@ -586,8 +585,57 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } if (syncTarget != null && !syncTarget.isEmpty()) { -// OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(masterAddress, message.getTimestamp(), -1, -// message.getExpiresInSeconds() * 1000L, false, ) + List attachments = PointerAttachment.forPointers(message.getAttachments()); + + OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(masterRecipient, message.getBody().orNull(), + attachments, + message.getTimestamp(), -1, + message.getExpiresInSeconds() * 1000, + ThreadDatabase.DistributionTypes.DEFAULT, quote.orNull(), + sharedContacts.or(Collections.emptyList()), + linkPreviews.or(Collections.emptyList()), + Collections.emptyList(), Collections.emptyList()); + + 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; + } + + Optional insertResult; + + try { + if (message.isGroupMessage()) { + insertResult = database.insertSecureDecryptedMessageOutbox(mediaMessage, -1, content.getTimestamp()); + } else { + insertResult = database.insertSecureDecryptedMessageOutbox(mediaMessage, -1); + } + + if (insertResult.isPresent()) { + List allAttachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(insertResult.get().getMessageId()); + List stickerAttachments = Stream.of(allAttachments).filter(Attachment::isSticker).toList(); + List dbAttachments = Stream.of(allAttachments).filterNot(Attachment::isSticker).toList(); + + forceStickerDownloadIfNecessary(stickerAttachments); + + for (DatabaseAttachment attachment : dbAttachments) { + 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(); + } + } else { IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterAddress, message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(), From 77eb460ba74bbc7ad0096857870be86a166730f6 Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 9 Feb 2021 13:45:38 +1100 Subject: [PATCH 03/19] feat: add image handling across device self-send. close an unclosed resource. remove unnecessary checks and SmsDatabase way of checking for existing message from ourselves --- app/build.gradle | 4 +-- .../securesms/database/MmsDatabase.java | 11 ++++++++ .../securesms/database/SmsDatabase.java | 9 ------- .../securesms/jobs/PushDecryptJob.java | 24 ++++++++++++++---- .../SingleRecipientNotificationBuilder.java | 2 +- .../loki/utilities/DownloadUtilities.kt | 25 ++++++++++--------- 6 files changed, 46 insertions(+), 29 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 64b407894..b83c5ae9c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -158,8 +158,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 121 -def canonicalVersionName = "1.6.4" +def canonicalVersionCode = 135 +def canonicalVersionName = "1.6.12" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 7b84e9975..2e6e2a9b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -902,6 +902,17 @@ public class MmsDatabase extends MessagingDatabase { return insertMessageInbox(retrieved, contentLocation, threadId, type, 0); } + public Optional insertSecureDecryptedMessageOutbox(OutgoingMediaMessage retrieved, long threadId, long serverTimestamp) + throws MmsException + { + long messageId = insertMessageOutbox(retrieved, threadId, false, null, serverTimestamp); + if (messageId == -1) { + return Optional.absent(); + } + markAsSent(messageId, true); + return Optional.fromNullable(new InsertResult(messageId, threadId)); + } + public Optional insertSecureDecryptedMessageInbox(IncomingMediaMessage retrieved, long threadId, long serverTimestamp) throws MmsException { 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 aaa14151d..0234dfae9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -725,15 +725,6 @@ 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) { 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 7d3b72a97..104708542 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -83,6 +83,7 @@ import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2; import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol; import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol; import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation; +import org.thoughtcrime.securesms.loki.utilities.DatabaseUtilitiesKt; import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException; @@ -584,9 +585,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType { masterAddress = getMessageMasterDestination(content.getSender()).getAddress(); } + // Handle sync message from ourselves if (syncTarget != null && !syncTarget.isEmpty()) { List attachments = PointerAttachment.forPointers(message.getAttachments()); + Address targetAddress = Address.fromSerialized(syncTarget); + OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(masterRecipient, message.getBody().orNull(), attachments, message.getTimestamp(), -1, @@ -596,6 +600,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType { linkPreviews.or(Collections.emptyList()), Collections.emptyList(), Collections.emptyList()); + if (DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(message.getTimestamp(), targetAddress) != null) { + Log.d("Loki","Message already exists, don't insert again"); + return; + } + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); database.beginTransaction(); @@ -607,11 +616,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType { Optional insertResult; try { - if (message.isGroupMessage()) { - insertResult = database.insertSecureDecryptedMessageOutbox(mediaMessage, -1, content.getTimestamp()); - } else { - insertResult = database.insertSecureDecryptedMessageOutbox(mediaMessage, -1); - } + + // Check if we have the thread already + long threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(syncTarget); + + insertResult = database.insertSecureDecryptedMessageOutbox(mediaMessage, threadID, content.getTimestamp()); if (insertResult.isPresent()) { List allAttachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(insertResult.get().getMessageId()); @@ -837,6 +846,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } else if (syncTarget != null && !syncTarget.isEmpty()) { Address targetAddress = Address.fromSerialized(syncTarget); + if (DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(message.getTimestamp(), targetAddress) != null) { + Log.d("Loki","Message already exists, don't insert again"); + return; + } + OutgoingTextMessage tm = new OutgoingTextMessage(Recipient.from(context, targetAddress, false), body, message.getExpiresInSeconds(), -1); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 62bfcc8b5..1ea22350a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -326,7 +326,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil private static Drawable getPlaceholderDrawable(Context context, Recipient recipient) { String publicKey = recipient.getAddress().serialize(); - String hepk = (recipient.isLocalNumber() && publicKey != null) + String hepk = (recipient.isLocalNumber() && publicKey == null) ? TextSecurePreferences.getMasterHexEncodedPublicKey(context) : publicKey; String displayName = recipient.getName(); diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/utilities/DownloadUtilities.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/utilities/DownloadUtilities.kt index 1986d2c2d..a582c0fa3 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/utilities/DownloadUtilities.kt +++ b/libsignal/src/main/java/org/session/libsignal/service/loki/utilities/DownloadUtilities.kt @@ -65,19 +65,20 @@ object DownloadUtilities { Log.d("Loki", "Attachment size limit exceeded.") throw PushNetworkException("Max response size exceeded.") } - val input = body.inputStream() - val buffer = ByteArray(32768) - var count = 0 - var bytes = input.read(buffer) - while (bytes >= 0) { - outputStream.write(buffer, 0, bytes) - count += bytes - if (count > maxSize) { - Log.d("Loki", "Attachment size limit exceeded.") - throw PushNetworkException("Max response size exceeded.") + body.inputStream().use { input -> + val buffer = ByteArray(32768) + var count = 0 + var bytes = input.read(buffer) + while (bytes >= 0) { + outputStream.write(buffer, 0, bytes) + count += bytes + if (count > maxSize) { + Log.d("Loki", "Attachment size limit exceeded.") + throw PushNetworkException("Max response size exceeded.") + } + listener?.onAttachmentProgress(body.size.toLong(), count.toLong()) + bytes = input.read(buffer) } - listener?.onAttachmentProgress(body.size.toLong(), count.toLong()) - bytes = input.read(buffer) } } catch (e: Exception) { Log.d("Loki", "Couldn't download attachment due to error: $e.") From 6b45cc683ee48c7cb6e6b4fd10f13e4bb03e8827 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Tue, 9 Feb 2021 17:00:17 +1100 Subject: [PATCH 04/19] only handle the first configuration message --- .../loki/activities/CreateClosedGroupActivity.kt | 2 -- .../securesms/loki/protocol/MultiDeviceProtocol.kt | 2 ++ .../libsession/utilities/TextSecurePreferences.kt | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt index 8521502d8..af0f5cc57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt @@ -111,8 +111,6 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM isLoading = true loaderContainer.fadeIn() ClosedGroupsProtocolV2.createClosedGroup(this, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID -> - // Force sync configuration message - MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this) loaderContainer.fadeOut() isLoading = false val threadID = DatabaseFactory.getThreadDatabase(this).getOrCreateThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt index bb1ce53fc..a248d6647 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt @@ -59,6 +59,7 @@ object MultiDeviceProtocol { @JvmStatic fun handleConfigurationMessage(context: Context, content: SignalServiceProtos.Content, senderPublicKey: String) { + if (TextSecurePreferences.getConfigurationMessageSynced(context)) return val configurationMessage = ConfigurationMessage.fromProto(content) ?: return val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return if (senderPublicKey != userPublicKey) return @@ -85,5 +86,6 @@ object MultiDeviceProtocol { if (allOpenGroups.contains(openGroup)) continue OpenGroupUtilities.addGroup(context, openGroup, 1) } + TextSecurePreferences.setConfigurationMessageSynced(context, true) } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index d697b0674..8d4581f18 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -131,6 +131,7 @@ object TextSecurePreferences { // region Multi Device private const val IS_USING_MULTI_DEVICE = "pref_is_using_multi_device" private const val LAST_CONFIGURATION_SYNC_TIME = "pref_last_configuration_sync_time" + private const val CONFIGURATION_SYNCED = "pref_configuration_synced" @JvmStatic fun isUsingMultiDevice(context: Context): Boolean { @@ -152,6 +153,16 @@ object TextSecurePreferences { setLongPreference(context, LAST_CONFIGURATION_SYNC_TIME, value) } + @JvmStatic + fun getConfigurationMessageSynced(context: Context): Boolean { + return getBooleanPreference(context, CONFIGURATION_SYNCED, false) + } + + @JvmStatic + fun setConfigurationMessageSynced(context: Context, value: Boolean) { + setBooleanPreference(context, CONFIGURATION_SYNCED, value) + } + @JvmStatic fun isUsingFCM(context: Context): Boolean { return getBooleanPreference(context, IS_USING_FCM, false) From e62eb819c917f1c63ece598dd65ef775e9634f2b Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 9 Feb 2021 17:04:56 +1100 Subject: [PATCH 05/19] refactor: GroupUtil functions to be JvmStatic --- .../src/main/java/org/session/libsession/utilities/GroupUtil.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libsession/src/main/java/org/session/libsession/utilities/GroupUtil.kt b/libsession/src/main/java/org/session/libsession/utilities/GroupUtil.kt index 24fcb4486..30b379601 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/GroupUtil.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/GroupUtil.kt @@ -58,10 +58,12 @@ object GroupUtil { return groupId.startsWith(MMS_GROUP_PREFIX) } + @JvmStatic fun isOpenGroup(groupId: String): Boolean { return groupId.startsWith(OPEN_GROUP_PREFIX) } + @JvmStatic fun isClosedGroup(groupId: String): Boolean { return groupId.startsWith(CLOSED_GROUP_PREFIX) } From fd0596f9eaab499055a223f06e0686c07b8d9c7c Mon Sep 17 00:00:00 2001 From: jubb Date: Wed, 10 Feb 2021 17:57:08 +1100 Subject: [PATCH 06/19] fix: closed groups now propagate properly without self-sends --- .../groups/GroupMessageProcessor.java | 9 ++++++++ .../securesms/jobs/PushDecryptJob.java | 23 +++++++++++++------ .../securesms/jobs/PushGroupSendJob.java | 18 +-------------- .../securesms/loki/api/SessionProtocolImpl.kt | 4 ++-- .../loki/protocol/ClosedGroupsProtocolV2.kt | 1 - .../loki/utilities/KeyPairUtilities.kt | 2 +- .../utilities/UnidentifiedAccessUtil.kt | 2 +- .../api/crypto/SignalServiceCipher.java | 1 - 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index ab5dede3e..e6d909002 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; +import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.jobs.AvatarDownloadJob; import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob; import org.session.libsignal.utilities.logging.Log; @@ -123,11 +124,19 @@ public class GroupMessageProcessor { { GroupDatabase database = DatabaseFactory.getGroupDatabase(context); + ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); String id = GroupUtil.getEncodedId(group); + Address address = Address.Companion.fromExternal(context, GroupUtil.getEncodedId(group)); + Recipient recipient = Recipient.from(context, address, false); String userMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); if (userMasterDevice == null) { userMasterDevice = TextSecurePreferences.getLocalNumber(context); } + if (content.getSender().equals(userMasterDevice)) { + long threadId = threadDatabase.getThreadIdIfExistsFor(recipient); + return threadId == -1 ? null : threadId; + } + if (group.getGroupType() == SignalServiceGroup.GroupType.SIGNAL) { // Loki - Only update the group if the group admin sent the message String masterDevice = MultiDeviceProtocol.shared.getMasterDevice(content.getSender()); 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 104708542..40cfb1fdc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -586,11 +586,15 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } // Handle sync message from ourselves - if (syncTarget != null && !syncTarget.isEmpty()) { + if (syncTarget != null && !syncTarget.isEmpty() || TextSecurePreferences.getLocalNumber(context).equals(content.getSender())) { + Address targetAddress = masterRecipient.getAddress(); + if (message.getGroupInfo().isPresent()) { + targetAddress = Address.Companion.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get())); + } else if (syncTarget != null && !syncTarget.isEmpty()) { + targetAddress = Address.fromSerialized(syncTarget); + } List attachments = PointerAttachment.forPointers(message.getAttachments()); - Address targetAddress = Address.fromSerialized(syncTarget); - OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(masterRecipient, message.getBody().orNull(), attachments, message.getTimestamp(), -1, @@ -618,7 +622,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { try { // Check if we have the thread already - long threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(syncTarget); + long threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(targetAddress.serialize()); insertResult = database.insertSecureDecryptedMessageOutbox(mediaMessage, threadID, content.getTimestamp()); @@ -843,8 +847,13 @@ 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); + } else if (syncTarget != null && !syncTarget.isEmpty() || TextSecurePreferences.getLocalNumber(context).equals(content.getSender())) { + Address targetAddress = masterRecipient.getAddress(); + if (message.getGroupInfo().isPresent()) { + targetAddress = Address.Companion.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get())); + } else if (syncTarget != null && !syncTarget.isEmpty()) { + targetAddress = Address.fromSerialized(syncTarget); + } if (DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(message.getTimestamp(), targetAddress) != null) { Log.d("Loki","Message already exists, don't insert again"); @@ -858,7 +867,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { if (tm.getMessageBody().length() == 0) { return; } // Check if we have the thread already - long threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(syncTarget); + long threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(targetAddress.serialize()); // Insert the message into the database 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 e4c8b588d..e5ef36cb0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -194,22 +194,6 @@ 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()); @@ -290,7 +274,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { } } - public SignalServiceDataMessage.Builder getDataMessage(Address address, OutgoingMediaMessage message) { + public SignalServiceDataMessage.Builder getDataMessage(Address address, OutgoingMediaMessage message) throws IOException { SignalServiceGroup.GroupType groupType = address.isOpenGroup() ? SignalServiceGroup.GroupType.PUBLIC_CHAT : SignalServiceGroup.GroupType.SIGNAL; diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/SessionProtocolImpl.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/SessionProtocolImpl.kt index 8a8c18565..e3c5b0aa6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/SessionProtocolImpl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/SessionProtocolImpl.kt @@ -18,10 +18,11 @@ import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities class SessionProtocolImpl(private val context: Context) : SessionProtocol { + private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } + override fun encrypt(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray { val userED25519KeyPair = KeyPairUtilities.getUserED25519KeyPair(context) ?: throw SessionProtocol.Exception.NoUserED25519KeyPair val recipientX25519PublicKey = Hex.fromStringCondensed(recipientHexEncodedX25519PublicKey.removing05PrefixIfNeeded()) - val sodium = LazySodiumAndroid(SodiumAndroid()) val verificationData = plaintext + userED25519KeyPair.publicKey.asBytes + recipientX25519PublicKey val signature = ByteArray(Sign.BYTES) @@ -47,7 +48,6 @@ class SessionProtocolImpl(private val context: Context) : SessionProtocol { val recipientX25519PrivateKey = x25519KeyPair.privateKey.serialize() val recipientX25519PublicKey = Hex.fromStringCondensed(x25519KeyPair.hexEncodedPublicKey.removing05PrefixIfNeeded()) Log.d("Test", "recipientX25519PublicKey: $recipientX25519PublicKey") - val sodium = LazySodiumAndroid(SodiumAndroid()) val signatureSize = Sign.BYTES val ed25519PublicKeySize = Sign.PUBLICKEYBYTES diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index 1adc93d44..c97591b87 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -70,7 +70,6 @@ object ClosedGroupsProtocolV2 { // Send a closed group update message to all members individually val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, encryptionKeyPair, membersAsData, adminsAsData) for (member in members) { - if (member == userPublicKey) { continue } val job = ClosedGroupUpdateMessageSendJobV2(member, closedGroupUpdateKind) job.setContext(context) job.onRun() // Run the job immediately to make all of this sync diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/KeyPairUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/KeyPairUtilities.kt index e965beeb1..8acd25559 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/KeyPairUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/KeyPairUtilities.kt @@ -14,7 +14,7 @@ import org.session.libsignal.libsignal.ecc.ECKeyPair object KeyPairUtilities { - private val sodium = LazySodiumAndroid(SodiumAndroid()) + private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } fun generate(): KeyPairGenerationResult { val seed = sodium.randomBytesBuf(16) diff --git a/libsession/src/main/java/org/session/libsession/messaging/utilities/UnidentifiedAccessUtil.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/UnidentifiedAccessUtil.kt index 671189778..56427bfb6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/UnidentifiedAccessUtil.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UnidentifiedAccessUtil.kt @@ -13,7 +13,7 @@ import org.session.libsignal.service.api.crypto.UnidentifiedAccessPair object UnidentifiedAccessUtil { private val TAG = UnidentifiedAccessUtil::class.simpleName - private val sodium = LazySodiumAndroid(SodiumAndroid()) + private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } fun getAccessFor(recipientPublicKey: String): UnidentifiedAccessPair? { try { 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 608dee89c..c5424d2cf 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 @@ -331,7 +331,6 @@ public class SignalServiceCipher { kotlin.Pair plaintextAndSenderPublicKey = SessionProtocolUtilities.INSTANCE.decryptClosedGroupCiphertext(ciphertext, groupPublicKey, apiDB, sessionProtocolImpl); paddedMessage = plaintextAndSenderPublicKey.getFirst(); String senderPublicKey = plaintextAndSenderPublicKey.getSecond(); - if (senderPublicKey.equals(localAddress.getNumber())) { throw new SelfSendException(); } // Will be caught and ignored in PushDecryptJob metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false); sessionVersion = sessionCipher.getSessionVersion(); } else if (envelope.isPreKeySignalMessage()) { From 34fab9681ceaa7d23a038bbf8b7c5c736fec8e26 Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 11 Feb 2021 14:01:31 +1100 Subject: [PATCH 07/19] fix: closed groups info messages work now --- .../securesms/database/GroupDatabase.java | 5 +- .../securesms/database/MmsSmsDatabase.java | 10 +- .../securesms/database/Storage.kt | 3 +- .../securesms/groups/GroupManager.java | 2 +- .../groups/GroupMessageProcessor.java | 12 +- .../securesms/jobs/PushDecryptJob.java | 113 +------------- .../loki/database/LokiMessageDatabase.kt | 2 +- .../ClosedGroupUpdateMessageSendJobV2.kt | 13 +- .../loki/protocol/ClosedGroupsProtocol.kt | 4 +- .../loki/protocol/ClosedGroupsProtocolV2.kt | 140 ++++++++++++------ .../mms/OutgoingGroupMediaMessage.java | 1 - .../api/SignalServiceMessageSender.java | 1 - 12 files changed, 130 insertions(+), 176 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index f17e6aca8..164172aba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -184,7 +184,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt } } - public void create(@NonNull String groupId, @Nullable String title, @NonNull List
members, + public long create(@NonNull String groupId, @Nullable String title, @NonNull List
members, @Nullable SignalServiceAttachmentPointer avatar, @Nullable String relay, @Nullable List
admins) { Collections.sort(members); @@ -211,7 +211,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt contentValues.put(ADMINS, Address.Companion.toSerializedList(admins, ',')); } - databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues); + long threadId = databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues); Recipient.applyCached(Address.Companion.fromSerialized(groupId), recipient -> { recipient.setName(title); @@ -220,6 +220,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt }); notifyConversationListListeners(); + return threadId; } public boolean delete(@NonNull String groupId) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 0ddf14286..159d96181 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -87,7 +87,7 @@ public class MmsSmsDatabase extends Database { } } - public @Nullable MessageRecord getMessageFor(long timestamp, Address author) { + public @Nullable MessageRecord getMessageFor(long timestamp, String serializedAuthor) { MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context); try (Cursor cursor = queryTables(PROJECTION, MmsSmsColumns.NORMALIZED_DATE_SENT + " = " + timestamp, null, null)) { @@ -96,8 +96,8 @@ public class MmsSmsDatabase extends Database { MessageRecord messageRecord; while ((messageRecord = reader.getNext()) != null) { - if ((Util.isOwnNumber(context, author.serialize()) && messageRecord.isOutgoing()) || - (!Util.isOwnNumber(context, author.serialize()) && messageRecord.getIndividualRecipient().getAddress().equals(author))) + if ((Util.isOwnNumber(context, serializedAuthor) && messageRecord.isOutgoing()) || + (!Util.isOwnNumber(context, serializedAuthor) && messageRecord.getIndividualRecipient().getAddress().equals(serializedAuthor))) { return messageRecord; } @@ -107,6 +107,10 @@ public class MmsSmsDatabase extends Database { return null; } + public @Nullable MessageRecord getMessageFor(long timestamp, Address author) { + return getMessageFor(timestamp, author.serialize()); + } + public Cursor getConversation(long threadId, long offset, long limit) { String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 3f34547fd..984f9fb82 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -8,7 +8,6 @@ import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageSendJob -import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.opengroups.OpenGroup @@ -351,7 +350,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, .setName(name) .addAllMembers(members) .addAllAdmins(admins) - val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, System.currentTimeMillis(), 0, null, listOf(), listOf()) + val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, 0, null, listOf(), listOf()) val mmsDB = DatabaseFactory.getMmsDatabase(context) val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null) mmsDB.markAsSent(infoMessageID, true) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java index 6d9ac5e29..af11e476c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java @@ -194,7 +194,7 @@ public class GroupManager { avatarAttachment = new UriAttachment(avatarUri, MediaTypes.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null); } - OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, null, Collections.emptyList(), Collections.emptyList()); + OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, 0, null, Collections.emptyList(), Collections.emptyList()); long threadId = MessageSender.send(context, outgoingMessage, -1, false, null); return new GroupActionResult(groupRecipient, threadId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index e6d909002..e6474ce4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; +import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.jobs.AvatarDownloadJob; import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob; import org.session.libsignal.utilities.logging.Log; @@ -269,9 +270,16 @@ public class GroupMessageProcessor { MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); Address address = Address.Companion.fromExternal(context, GroupUtil.getEncodedId(group)); Recipient recipient = Recipient.from(context, address, false); - OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, null, Collections.emptyList(), Collections.emptyList()); + OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, 0, null, Collections.emptyList(), Collections.emptyList()); long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient); - long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null); + Address senderAddress = Address.Companion.fromExternal(context, content.getSender()); + MessageRecord existingMessage = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(content.getTimestamp(), senderAddress); + long messageId; + if (existingMessage != null) { + messageId = existingMessage.getId(); + } else { + messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null); + } mmsDatabase.markAsSent(messageId, true); 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 40cfb1fdc..85b27d315 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -47,7 +47,6 @@ import org.session.libsession.utilities.TextSecurePreferences; import org.thoughtcrime.securesms.contactshare.ContactModelMapper; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; import org.thoughtcrime.securesms.database.AttachmentDatabase; @@ -83,7 +82,6 @@ import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2; import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol; import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol; import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation; -import org.thoughtcrime.securesms.loki.utilities.DatabaseUtilitiesKt; import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException; @@ -97,7 +95,6 @@ import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage; -import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.session.libsignal.utilities.Hex; import org.session.libsignal.libsignal.InvalidMessageException; @@ -399,33 +396,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } - private long handleSynchronizeSentEndSessionMessage(@NonNull SentTranscriptMessage message) - { - SmsDatabase database = DatabaseFactory.getSmsDatabase(context); - Recipient recipient = getSyncMessageDestination(message); - OutgoingTextMessage outgoingTextMessage = new OutgoingTextMessage(recipient, "", -1); - OutgoingEndSessionMessage outgoingEndSessionMessage = new OutgoingEndSessionMessage(outgoingTextMessage); - - long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient); - - if (!recipient.isGroupRecipient()) { - // TODO: Handle session reset on sync messages - /* - SessionStore sessionStore = new TextSecureSessionStore(context); - sessionStore.deleteAllSessions(recipient.getAddress().toPhoneString()); - */ - - SecurityEvent.broadcastSecurityUpdateEvent(context); - - long messageId = database.insertMessageOutbox(threadId, outgoingEndSessionMessage, - false, message.getTimestamp(), - null); - database.markAsSent(messageId, true); - } - - return threadId; - } - private void handleGroupMessage(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Optional smsMessageId) @@ -484,83 +454,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } - private void handleSynchronizeStickerPackOperation(@NonNull List stickerPackOperations) { - JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - - for (StickerPackOperationMessage operation : stickerPackOperations) { - if (operation.getPackId().isPresent() && operation.getPackKey().isPresent() && operation.getType().isPresent()) { - String packId = Hex.toStringCondensed(operation.getPackId().get()); - String packKey = Hex.toStringCondensed(operation.getPackKey().get()); - - switch (operation.getType().get()) { - case INSTALL: - jobManager.add(new StickerPackDownloadJob(packId, packKey, false)); - break; - case REMOVE: - DatabaseFactory.getStickerDatabase(context).uninstallPack(packId); - break; - } - } else { - Log.w(TAG, "Received incomplete sticker pack operation sync."); - } - } - } - - private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content, - @NonNull SentTranscriptMessage message) - throws StorageFailedException - - { - try { - GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - - Long threadId; - - if (message.getMessage().isEndSession()) { - threadId = handleSynchronizeSentEndSessionMessage(message); - } else if (message.getMessage().isGroupUpdate()) { - threadId = GroupMessageProcessor.process(context, content, message.getMessage(), true); - } else if (message.getMessage().isExpirationUpdate()) { - threadId = handleSynchronizeSentExpirationUpdate(message); - } else if (message.getMessage().getAttachments().isPresent() || message.getMessage().getQuote().isPresent() || message.getMessage().getPreviews().isPresent() || message.getMessage().getSticker().isPresent()) { - threadId = handleSynchronizeSentMediaMessage(message); - } else { - threadId = handleSynchronizeSentTextMessage(message); - } - - if (threadId == -1L) { threadId = null; } - - if (message.getMessage().getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get()))) { - handleUnknownGroupMessage(content, message.getMessage().getGroupInfo().get()); - } - - if (message.getMessage().getProfileKey().isPresent()) { - Recipient recipient = null; - - if (message.getDestination().isPresent()) recipient = Recipient.from(context, Address.Companion.fromSerialized(message.getDestination().get()), false); - else if (message.getMessage().getGroupInfo().isPresent()) recipient = Recipient.from(context, Address.Companion.fromSerialized(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get())), false); - - - if (recipient != null && !recipient.isSystemContact() && !recipient.isProfileSharing()) { - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); - } - - SessionMetaProtocol.handleProfileKeyUpdate(context, content); - } - - SessionMetaProtocol.handleProfileUpdateIfNeeded(context, content); - - if (threadId != null) { - DatabaseFactory.getThreadDatabase(context).setRead(threadId, true); - messageNotifier.updateNotification(context); - } - - messageNotifier.setLastDesktopActivityTimestamp(message.getTimestamp()); - } catch (MmsException e) { - throw new StorageFailedException(e, content.getSender(), content.getSenderDevice()); - } - } - public void handleMediaMessage(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Optional smsMessageId, @@ -1429,6 +1322,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType { return true; } + if (content.getSender().equals(TextSecurePreferences.getLocalNumber(context)) && + DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(content.getTimestamp(), content.getSender()) != null) { + Log.d("Loki", "Skipping message from self we already have"); + return true; + } + Recipient sender = Recipient.from(context, Address.Companion.fromSerialized(content.getSender()), false); if (content.getDeviceLink().isPresent()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt index 108783a51..66c190740 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt @@ -29,7 +29,7 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab } override fun getQuoteServerID(quoteID: Long, quoteePublicKey: String): Long? { - val message = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(quoteID, Address.fromSerialized(quoteePublicKey)) + val message = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(quoteID, quoteePublicKey) return if (message != null) getServerID(message.getId()) else null } 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 8ae19749e..6b4060aa1 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 @@ -27,7 +27,7 @@ import org.session.libsignal.utilities.Hex import java.util.* import java.util.concurrent.TimeUnit -class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Parameters, private val destination: String, private val kind: Kind) : BaseJob(parameters) { +class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Parameters, private val destination: String, private val kind: Kind, private val sentTime: Long) : BaseJob(parameters) { sealed class Kind { class New(val publicKey: ByteArray, val name: String, val encryptionKeyPair: ECKeyPair, val members: Collection, val admins: Collection) : Kind() @@ -61,20 +61,22 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete } } - constructor(destination: String, kind: Kind) : this(Parameters.Builder() + constructor(destination: String, kind: Kind, sentTime: Long) : this(Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setQueue(KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(20) .build(), destination, - kind) + kind, + sentTime) override fun getFactoryKey(): String { return KEY } override fun serialize(): Data { val builder = Data.Builder() builder.putString("destination", destination) + builder.putLong("sentTime", sentTime) when (kind) { is Kind.New -> { builder.putString("kind", "New") @@ -124,6 +126,7 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete override fun create(parameters: Parameters, data: Data): ClosedGroupUpdateMessageSendJobV2 { val destination = data.getString("destination") val rawKind = data.getString("kind") + val sentTime = data.getLong("sentTime") val kind: Kind when (rawKind) { "New" -> { @@ -162,7 +165,7 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete } else -> throw Exception("Invalid closed group update message kind: $rawKind.") } - return ClosedGroupUpdateMessageSendJobV2(parameters, destination, kind) + return ClosedGroupUpdateMessageSendJobV2(parameters, destination, kind, sentTime) } } @@ -221,7 +224,7 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete try { // 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, + sentTime, serializedContentMessage, false, ttl, 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/ClosedGroupsProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 3dd347472..6c4726950 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -285,7 +285,7 @@ object ClosedGroupsProtocol { .setName(name) .addAllMembers(members) .addAllAdmins(admins) - val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, System.currentTimeMillis(), 0, null, listOf(), listOf()) + val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, 0, null, listOf(), listOf()) val mmsDB = DatabaseFactory.getMmsDatabase(context) val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null) mmsDB.markAsSent(infoMessageID, true) @@ -324,6 +324,6 @@ object ClosedGroupsProtocol { .setId(decodedGroupId) .setType(GroupContext.Type.QUIT) .build() - return OutgoingGroupMediaMessage(groupRecipient, groupContext, null, System.currentTimeMillis(), 0, null, emptyList(), emptyList()) + return OutgoingGroupMediaMessage(groupRecipient, groupContext, null, 0, null, emptyList(), emptyList()) } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index c97591b87..7395fd2b9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -58,6 +58,7 @@ object ClosedGroupsProtocolV2 { val apiDB = DatabaseFactory.getLokiAPIDatabase(context) // Generate the group's public key val groupPublicKey = Curve.generateKeyPair().hexEncodedPublicKey // Includes the "05" prefix + val sentTime = System.currentTimeMillis() // Generate the key pair that'll be used for encryption and decryption val encryptionKeyPair = Curve.generateKeyPair() // Create the group @@ -68,19 +69,20 @@ object ClosedGroupsProtocolV2 { null, null, LinkedList(admins.map { Address.fromSerialized(it!!) })) DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.from(context, Address.fromSerialized(groupID), false), true) // Send a closed group update message to all members individually - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, encryptionKeyPair, membersAsData, adminsAsData) - for (member in members) { - val job = ClosedGroupUpdateMessageSendJobV2(member, closedGroupUpdateKind) - job.setContext(context) - job.onRun() // Run the job immediately to make all of this sync - } // Add the group to the user's set of public keys to poll for apiDB.addClosedGroupPublicKey(groupPublicKey) // Store the encryption key pair apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey) // Notify the user val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID) + insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTime) + + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, encryptionKeyPair, membersAsData, adminsAsData) + for (member in members) { + val job = ClosedGroupUpdateMessageSendJobV2(member, closedGroupUpdateKind, sentTime) + job.setContext(context) + job.onRun() // Run the job immediately to make all of this sync + } // Notify the PN server LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) // Fulfill the promise @@ -102,13 +104,14 @@ object ClosedGroupsProtocolV2 { val updatedMembers = group.members.map { it.serialize() }.toSet() - userPublicKey val admins = group.admins.map { it.serialize() } val name = group.title + val sentTime = System.currentTimeMillis() if (group == null) { Log.d("Loki", "Can't leave nonexistent closed group.") return@queue deferred.reject(Error.NoThread) } // Send the update to the group @Suppress("NAME_SHADOWING") - val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, ClosedGroupUpdateMessageSendJobV2.Kind.Leave) + val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, ClosedGroupUpdateMessageSendJobV2.Kind.Leave, sentTime) job.setContext(context) job.onRun() // Run the job immediately // Remove the group private key and unsubscribe from PNs @@ -116,7 +119,7 @@ object ClosedGroupsProtocolV2 { // Notify the user val infoType = GroupContext.Type.QUIT val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID) + insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) deferred.resolve(Unit) } return deferred.promise @@ -140,6 +143,7 @@ object ClosedGroupsProtocolV2 { val newMembersAsData = membersToAdd.map { Hex.fromStringCondensed(it) } val admins = group.admins.map { it.serialize() } val adminsAsData = admins.map { Hex.fromStringCondensed(it) } + val sentTime = System.currentTimeMillis() val encryptionKeyPair = apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) if (encryptionKeyPair == null) { Log.d("Loki", "Couldn't get encryption key pair for closed group.") @@ -148,7 +152,7 @@ object ClosedGroupsProtocolV2 { val name = group.title // Send the update to the group val memberUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.AddMembers(newMembersAsData) - val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, memberUpdateKind) + val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, memberUpdateKind, sentTime) job.setContext(context) job.onRun() // Run the job immediately // Send closed group update messages to any new members individually @@ -156,13 +160,13 @@ object ClosedGroupsProtocolV2 { @Suppress("NAME_SHADOWING") val closedGroupNewKind = ClosedGroupUpdateMessageSendJobV2.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, encryptionKeyPair, membersAsData, adminsAsData) @Suppress("NAME_SHADOWING") - val newMemberJob = ClosedGroupUpdateMessageSendJobV2(member, closedGroupNewKind) + val newMemberJob = ClosedGroupUpdateMessageSendJobV2(member, closedGroupNewKind, sentTime) ApplicationContext.getInstance(context).jobManager.add(newMemberJob) } // Notify the user val infoType = GroupContext.Type.UPDATE val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID) + insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) } } @@ -183,6 +187,7 @@ object ClosedGroupsProtocolV2 { groupDB.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) }) val removeMembersAsData = membersToRemove.map { Hex.fromStringCondensed(it) } val admins = group.admins.map { it.serialize() } + val sentTime = System.currentTimeMillis() val encryptionKeyPair = apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) if (encryptionKeyPair == null) { Log.d("Loki", "Couldn't get encryption key pair for closed group.") @@ -195,7 +200,7 @@ object ClosedGroupsProtocolV2 { val name = group.title // Send the update to the group val memberUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.RemoveMembers(removeMembersAsData) - val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, memberUpdateKind) + val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, memberUpdateKind, sentTime) job.setContext(context) job.onRun() // Run the job immediately val isCurrentUserAdmin = admins.contains(userPublicKey) @@ -205,7 +210,7 @@ object ClosedGroupsProtocolV2 { // Notify the user val infoType = GroupContext.Type.UPDATE val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID) + insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) } } @@ -218,13 +223,14 @@ object ClosedGroupsProtocolV2 { val group = groupDB.getGroup(groupID).orNull() val members = group.members.map { it.serialize() }.toSet() val admins = group.admins.map { it.serialize() } + val sentTime = System.currentTimeMillis() if (group == null) { Log.d("Loki", "Can't leave nonexistent closed group.") return@queue deferred.reject(Error.NoThread) } // Send the update to the group val kind = ClosedGroupUpdateMessageSendJobV2.Kind.NameChange(newName) - val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, kind) + val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, kind, sentTime) job.setContext(context) job.onRun() // Run the job immediately // Update the group @@ -232,7 +238,7 @@ object ClosedGroupsProtocolV2 { // Notify the user val infoType = GroupContext.Type.UPDATE val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, infoType, newName, members, admins, threadID) + insertOutgoingInfoMessage(context, groupID, infoType, newName, members, admins, threadID, sentTime) deferred.resolve(Unit) } return deferred.promise @@ -272,6 +278,7 @@ object ClosedGroupsProtocolV2 { Log.d("Loki", "Can't update nonexistent closed group.") return@queue deferred.reject(Error.NoThread) } + val sentTime = System.currentTimeMillis() val oldMembers = group.members.map { it.serialize() }.toSet() val newMembers = members.minus(oldMembers) val membersAsData = members.map { Hex.fromStringCondensed(it) } @@ -298,7 +305,7 @@ object ClosedGroupsProtocolV2 { @Suppress("NAME_SHADOWING") val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.Update(name, membersAsData) @Suppress("NAME_SHADOWING") - val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, closedGroupUpdateKind) + val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, closedGroupUpdateKind, sentTime) job.setContext(context) job.onRun() // Run the job immediately if (isUserLeaving) { @@ -322,7 +329,7 @@ object ClosedGroupsProtocolV2 { @Suppress("NAME_SHADOWING") val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, encryptionKeyPair, membersAsData, adminsAsData) @Suppress("NAME_SHADOWING") - val job = ClosedGroupUpdateMessageSendJobV2(member, closedGroupUpdateKind) + val job = ClosedGroupUpdateMessageSendJobV2(member, closedGroupUpdateKind, sentTime) ApplicationContext.getInstance(context).jobManager.add(job) } } @@ -335,7 +342,7 @@ object ClosedGroupsProtocolV2 { // Notify the user val infoType = if (isUserLeaving) GroupContext.Type.QUIT else GroupContext.Type.UPDATE val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID) + insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID, sentTime) deferred.resolve(Unit) } return deferred.promise @@ -367,7 +374,7 @@ object ClosedGroupsProtocolV2 { val ciphertext = SessionProtocolImpl(context).encrypt(plaintext, publicKey) ClosedGroupUpdateMessageSendJobV2.KeyPairWrapper(publicKey, ciphertext) } - val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, ClosedGroupUpdateMessageSendJobV2.Kind.EncryptionKeyPair(wrappers)) + val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, ClosedGroupUpdateMessageSendJobV2.Kind.EncryptionKeyPair(wrappers), System.currentTimeMillis()) job.setContext(context) job.onRun() // Run the job immediately // Store it * after * having sent out the message to the group @@ -376,9 +383,9 @@ object ClosedGroupsProtocolV2 { @JvmStatic fun handleMessage(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { - if (!isValid(closedGroupUpdate, senderPublicKey)) { return } + if (!isValid(context, closedGroupUpdate, senderPublicKey, sentTimestamp)) { return } when (closedGroupUpdate.type) { - SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate, senderPublicKey) + SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate, senderPublicKey, sentTimestamp) SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_REMOVED -> handleClosedGroupMembersRemoved(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey) SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_ADDED -> handleClosedGroupMembersAdded(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey) SignalServiceProtos.ClosedGroupUpdateV2.Type.NAME_CHANGE -> handleClosedGroupNameChange(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey) @@ -391,7 +398,10 @@ object ClosedGroupsProtocolV2 { } } - private fun isValid(closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, senderPublicKey: String): Boolean { + private fun isValid(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, senderPublicKey: String, sentTimestamp: Long): Boolean { + val record = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(sentTimestamp, senderPublicKey) + if (record != null) return false + return when (closedGroupUpdate.type) { SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW -> { (!closedGroupUpdate.publicKey.isEmpty && !closedGroupUpdate.name.isNullOrEmpty() && !(closedGroupUpdate.encryptionKeyPair.privateKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty @@ -413,7 +423,7 @@ object ClosedGroupsProtocolV2 { } } - public fun handleNewClosedGroup(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, senderPublicKey: String) { + public fun handleNewClosedGroup(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, senderPublicKey: String, sentTimestamp: Long) { // Prepare val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! val apiDB = DatabaseFactory.getLokiAPIDatabase(context) @@ -426,7 +436,8 @@ object ClosedGroupsProtocolV2 { // Create the group val groupID = doubleEncodeGroupID(groupPublicKey) val groupDB = DatabaseFactory.getGroupDatabase(context) - if (groupDB.getGroup(groupID).orNull() != null) { + val prevGroup = groupDB.getGroup(groupID).orNull() + if (prevGroup != null) { // Update the group groupDB.updateTitle(groupID, name) groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) @@ -440,8 +451,14 @@ object ClosedGroupsProtocolV2 { // Store the encryption key pair val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray().removing05PrefixIfNeeded()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray())) apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey) - // Notify the user - insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) + // Notify the user (if we didn't make the group) + if (userPublicKey != senderPublicKey) { + insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) + } else if (prevGroup == null) { + // only notify if we created this group + val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) + insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) + } // Notify the PN server LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) } @@ -451,8 +468,8 @@ object ClosedGroupsProtocolV2 { val groupDB = DatabaseFactory.getGroupDatabase(context) val groupID = doubleEncodeGroupID(groupPublicKey) val group = groupDB.getGroup(groupID).orNull() - if (group == null) { - Log.d("Loki", "Ignoring closed group info message for nonexistent group.") + if (group == null || !group.isActive) { + Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.") return } val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! @@ -491,16 +508,21 @@ object ClosedGroupsProtocolV2 { val (contextType, signalType) = if (senderLeft) GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT else GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE - - insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins) + if (userPublicKey == senderPublicKey) { + val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) + insertOutgoingInfoMessage(context, groupID, contextType, name, members, admins, threadID, sentTimestamp) + } else { + insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins) + } } fun handleClosedGroupMembersAdded(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { + val userPublicKey = TextSecurePreferences.getLocalNumber(context) val groupDB = DatabaseFactory.getGroupDatabase(context) val groupID = doubleEncodeGroupID(groupPublicKey) val group = groupDB.getGroup(groupID).orNull() - if (group == null) { - Log.d("Loki", "Ignoring closed group info message for nonexistent group.") + if (group == null || !group.isActive) { + Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.") return } if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) { @@ -517,16 +539,22 @@ object ClosedGroupsProtocolV2 { val newMembers = members + updateMembers groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) }) - insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) + if (userPublicKey == senderPublicKey) { + val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) + insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) + } else { + insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) + } } fun handleClosedGroupNameChange(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { // Check that the sender is a member of the group (before the update) + val userPublicKey = TextSecurePreferences.getLocalNumber(context) val groupDB = DatabaseFactory.getGroupDatabase(context) val groupID = doubleEncodeGroupID(groupPublicKey) val group = groupDB.getGroup(groupID).orNull() - if (group == null) { - Log.d("Loki", "Ignoring closed group info message for nonexistent group.") + if (group == null || !group.isActive) { + Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.") return } // Check common group update logic @@ -538,21 +566,23 @@ object ClosedGroupsProtocolV2 { val name = closedGroupUpdate.name groupDB.updateTitle(groupID, name) - insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) + if (userPublicKey == senderPublicKey) { + val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) + insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) + } else { + insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) + } } private fun handleClosedGroupMemberLeft(context: Context, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { // Check the user leaving isn't us, will already be handled val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! - if (senderPublicKey == userPublicKey) { - return - } val apiDB = DatabaseFactory.getLokiAPIDatabase(context) val groupDB = DatabaseFactory.getGroupDatabase(context) val groupID = doubleEncodeGroupID(groupPublicKey) val group = groupDB.getGroup(groupID).orNull() - if (group == null) { - Log.d("Loki", "Ignoring closed group info message for nonexistent group.") + if (group == null || !group.isActive) { + Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.") return } val name = group.title @@ -575,7 +605,12 @@ object ClosedGroupsProtocolV2 { generateAndSendNewEncryptionKeyPair(context, groupPublicKey, updatedMemberList) } } - insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.QUIT, SignalServiceGroup.Type.QUIT, name, members, admins) + if (userPublicKey == senderPublicKey) { + val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) + insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) + } else { + insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) + } } private fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { @@ -588,8 +623,8 @@ object ClosedGroupsProtocolV2 { val groupDB = DatabaseFactory.getGroupDatabase(context) val groupID = doubleEncodeGroupID(groupPublicKey) val group = groupDB.getGroup(groupID).orNull() - if (group == null) { - Log.d("Loki", "Ignoring closed group info message for nonexistent group.") + if (group == null || !group.isActive) { + Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.") return } val oldMembers = group.members.map { it.serialize() } @@ -623,7 +658,13 @@ object ClosedGroupsProtocolV2 { val wasSenderRemoved = !members.contains(senderPublicKey) val type0 = if (wasSenderRemoved) GroupContext.Type.QUIT else GroupContext.Type.UPDATE val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE - insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, group.admins.map { it.toString() }) + val admins = group.admins.map { it.toString() } + if (userPublicKey == senderPublicKey) { + val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) + insertOutgoingInfoMessage(context, groupID, type0, name, members, admins, threadID, sentTimestamp) + } else { + insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, admins) + } } private fun disableLocalGroupAndUnsubscribe(context: Context, apiDB: LokiAPIDatabase, groupPublicKey: String, groupDB: GroupDatabase, groupID: String, userPublicKey: String) { @@ -699,7 +740,8 @@ object ClosedGroupsProtocolV2 { } private fun insertOutgoingInfoMessage(context: Context, groupID: String, type: GroupContext.Type, name: String, - members: Collection, admins: Collection, threadID: Long) { + members: Collection, admins: Collection, threadID: Long, + sentTime: Long) { val recipient = Recipient.from(context, Address.fromSerialized(groupID), false) val groupContextBuilder = GroupContext.newBuilder() .setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID))) @@ -707,9 +749,9 @@ object ClosedGroupsProtocolV2 { .setName(name) .addAllMembers(members) .addAllAdmins(admins) - val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, System.currentTimeMillis(), 0, null, listOf(), listOf()) + val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, 0, null, listOf(), listOf()) val mmsDB = DatabaseFactory.getMmsDatabase(context) - val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null) + val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null, sentTime) mmsDB.markAsSent(infoMessageID, true) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java b/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java index d34a14909..4b1fbfb25 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java @@ -40,7 +40,6 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { public OutgoingGroupMediaMessage(@NonNull Recipient recipient, @NonNull GroupContext group, @Nullable final Attachment avatar, - long sentTimeMillis, long expireIn, @Nullable QuoteModel quote, @NonNull List contacts, 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 263b9276b..be28948a3 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 @@ -1052,7 +1052,6 @@ public class SignalServiceMessageSender { Optional syncTarget) throws IOException, UntrustedIdentityException { - 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 From 05fef1188904531962b281d6343b240f8d1e9c37 Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 11 Feb 2021 14:20:12 +1100 Subject: [PATCH 08/19] fix: non-compatible handle messages requiring timestamp --- .../org/thoughtcrime/securesms/jobs/PushDecryptJob.java | 2 +- .../securesms/loki/protocol/MultiDeviceProtocol.kt | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) 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 ca8bd7fdf..4510b1eef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -264,7 +264,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { SessionMetaProtocol.handleProfileUpdateIfNeeded(context, content); if (content.configurationMessageProto.isPresent()) { - MultiDeviceProtocol.handleConfigurationMessage(context, content.configurationMessageProto.get(), content.getSender()); + MultiDeviceProtocol.handleConfigurationMessage(context, content.configurationMessageProto.get(), content.getSender(), content.getTimestamp()); } else if (content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt index a248d6647..d0ba9c36e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt @@ -5,6 +5,7 @@ import com.google.protobuf.ByteString import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.utilities.TextSecurePreferences +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.utilities.removing05PrefixIfNeeded @@ -33,7 +34,7 @@ object MultiDeviceProtocol { try { messageSender.sendMessage(0, address, udAccess.get().targetUnidentifiedAccess, Date().time, serializedMessage, false, configurationMessage.ttl.toInt(), false, - true, false, true, false) + true, false, true, Optional.absent()) TextSecurePreferences.setLastConfigurationSyncTime(context, now) } catch (e: Exception) { Log.d("Loki", "Failed to send configuration message due to error: $e.") @@ -51,14 +52,14 @@ object MultiDeviceProtocol { try { messageSender.sendMessage(0, address, udAccess.get().targetUnidentifiedAccess, Date().time, serializedMessage, false, configurationMessage.ttl.toInt(), false, - true, false, true, false) + true, false, true, Optional.absent()) } catch (e: Exception) { Log.d("Loki", "Failed to send configuration message due to error: $e.") } } @JvmStatic - fun handleConfigurationMessage(context: Context, content: SignalServiceProtos.Content, senderPublicKey: String) { + fun handleConfigurationMessage(context: Context, content: SignalServiceProtos.Content, senderPublicKey: String, timestamp: Long) { if (TextSecurePreferences.getConfigurationMessageSynced(context)) return val configurationMessage = ConfigurationMessage.fromProto(content) ?: return val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return @@ -79,7 +80,7 @@ object MultiDeviceProtocol { closedGroupUpdate.addAllMembers(closedGroup.members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) closedGroupUpdate.addAllAdmins(closedGroup.admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) - ClosedGroupsProtocolV2.handleNewClosedGroup(context, closedGroupUpdate.build(), userPublicKey) + ClosedGroupsProtocolV2.handleNewClosedGroup(context, closedGroupUpdate.build(), userPublicKey, timestamp) } val allOpenGroups = storage.getAllOpenGroups().map { it.value.server } for (openGroup in configurationMessage.openGroups) { From 6a8e0ae195770689bf440b77c573d4df90751bb0 Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 11 Feb 2021 16:34:01 +1100 Subject: [PATCH 09/19] feat: use new explicit groups --- .../conversation/ConversationActivity.java | 2 +- .../activities/EditClosedGroupActivity.kt | 32 +++++++++---------- .../securesms/loki/activities/HomeActivity.kt | 2 +- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 0789f443a..010eef08a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -1109,7 +1109,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } try { if (isSSKBasedClosedGroup) { - ClosedGroupsProtocolV2.leave(this, groupPublicKey); + ClosedGroupsProtocolV2.explicitLeave(this, groupPublicKey); initializeEnabledCheck(); } else if (ClosedGroupsProtocol.leaveLegacyGroup(this, groupRecipient)) { initializeEnabledCheck(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt index e1039c854..80fd0281a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt @@ -277,24 +277,22 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { isLoading = true loaderContainer.fadeIn() val promise: Promise = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) { - ClosedGroupsProtocolV2.leave(this, groupPublicKey!!) + ClosedGroupsProtocolV2.explicitLeave(this, groupPublicKey!!) } else { -// TODO: uncomment when we switch to sending new explicit updates after clients update -// task { -// val name = -// if (hasNameChanged) ClosedGroupsProtocolV2.explicitNameChange(this@EditClosedGroupActivity,groupPublicKey!!,name) -// else Promise.of(Unit) -// name.get() -// members.filterNot { it in originalMembers }.let { adds -> -// if (adds.isNotEmpty()) ClosedGroupsProtocolV2.explicitAddMembers(this@EditClosedGroupActivity, groupPublicKey!!, adds.map { it.address.serialize() }) -// else Promise.of(Unit) -// }.get() -// originalMembers.filterNot { it in members }.let { removes -> -// if (removes.isNotEmpty()) ClosedGroupsProtocolV2.explicitRemoveMembers(this@EditClosedGroupActivity, groupPublicKey!!, removes.map { it.address.serialize() }) -// else Promise.of(Unit) -// }.get() -// } - ClosedGroupsProtocolV2.update(this, groupPublicKey!!, members.map { it.address.serialize() }, name) + task { + val name = + if (hasNameChanged) ClosedGroupsProtocolV2.explicitNameChange(this@EditClosedGroupActivity,groupPublicKey!!,name) + else Promise.of(Unit) + name.get() + members.filterNot { it in originalMembers }.let { adds -> + if (adds.isNotEmpty()) ClosedGroupsProtocolV2.explicitAddMembers(this@EditClosedGroupActivity, groupPublicKey!!, adds.map { it.address.serialize() }) + else Promise.of(Unit) + }.get() + originalMembers.filterNot { it in members }.let { removes -> + if (removes.isNotEmpty()) ClosedGroupsProtocolV2.explicitRemoveMembers(this@EditClosedGroupActivity, groupPublicKey!!, removes.map { it.address.serialize() }) + else Promise.of(Unit) + }.get() + } } promise.successUi { loaderContainer.fadeOut() diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 2da2a39ef..88c5eea00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -358,7 +358,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe isSSKBasedClosedGroup = false } if (isSSKBasedClosedGroup) { - ClosedGroupsProtocolV2.leave(context, groupPublicKey!!) + ClosedGroupsProtocolV2.explicitLeave(context, groupPublicKey!!) } else if (!ClosedGroupsProtocol.leaveLegacyGroup(context, recipient)) { Toast.makeText(context, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show() return@launch From b6951f09b4a08a7df9832c41a05dd820c1de316c Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 11 Feb 2021 18:58:38 +1100 Subject: [PATCH 10/19] feat: use cached keypair --- .../securesms/loki/protocol/ClosedGroupsProtocolV2.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index 7395fd2b9..b35e9e8d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -38,11 +38,14 @@ import org.session.libsession.utilities.TextSecurePreferences import java.io.IOException import java.util.* +import java.util.concurrent.atomic.AtomicReference import kotlin.jvm.Throws object ClosedGroupsProtocolV2 { const val groupSizeLimit = 100 + private val pendingKeyPair = AtomicReference(null) + sealed class Error(val description: String) : Exception() { object NoThread : Error("Couldn't find a thread associated with the given group public key") object NoKeyPair : Error("Couldn't find an encryption key pair associated with the given group public key.") @@ -364,7 +367,7 @@ object ClosedGroupsProtocolV2 { return } // Generate the new encryption key pair - val newKeyPair = Curve.generateKeyPair() + val newKeyPair = pendingKeyPair.getAndSet(Curve.generateKeyPair()) ?: Curve.generateKeyPair() // Distribute it val proto = SignalServiceProtos.KeyPair.newBuilder() proto.publicKey = ByteString.copyFrom(newKeyPair.publicKey.serialize().removing05PrefixIfNeeded()) From 2a50a09614fb22f8c7a767f9a872bc7c42b27b21 Mon Sep 17 00:00:00 2001 From: jubb Date: Fri, 12 Feb 2021 14:16:06 +1100 Subject: [PATCH 11/19] feat: share pending key pair between generate EC and add members --- .../loki/protocol/ClosedGroupsProtocolV2.kt | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index b35e9e8d9..5874c1d88 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.loki.protocol import android.content.Context import android.util.Log import com.google.protobuf.ByteString +import kotlinx.coroutines.delay import nl.komponents.kovenant.Promise import nl.komponents.kovenant.deferred import nl.komponents.kovenant.task @@ -44,7 +45,7 @@ import kotlin.jvm.Throws object ClosedGroupsProtocolV2 { const val groupSizeLimit = 100 - private val pendingKeyPair = AtomicReference(null) + private val pendingKeyPair = AtomicReference(null) sealed class Error(val description: String) : Exception() { object NoThread : Error("Couldn't find a thread associated with the given group public key") @@ -104,9 +105,6 @@ object ClosedGroupsProtocolV2 { val groupDB = DatabaseFactory.getGroupDatabase(context) val groupID = doubleEncodeGroupID(groupPublicKey) val group = groupDB.getGroup(groupID).orNull() - val updatedMembers = group.members.map { it.serialize() }.toSet() - userPublicKey - val admins = group.admins.map { it.serialize() } - val name = group.title val sentTime = System.currentTimeMillis() if (group == null) { Log.d("Loki", "Can't leave nonexistent closed group.") @@ -119,10 +117,6 @@ object ClosedGroupsProtocolV2 { job.onRun() // Run the job immediately // Remove the group private key and unsubscribe from PNs disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey) - // Notify the user - val infoType = GroupContext.Type.QUIT - val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) deferred.resolve(Unit) } return deferred.promise @@ -147,7 +141,7 @@ object ClosedGroupsProtocolV2 { val admins = group.admins.map { it.serialize() } val adminsAsData = admins.map { Hex.fromStringCondensed(it) } val sentTime = System.currentTimeMillis() - val encryptionKeyPair = apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) + val encryptionKeyPair = pendingKeyPair.get() ?: apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) if (encryptionKeyPair == null) { Log.d("Loki", "Couldn't get encryption key pair for closed group.") return@task Error.NoKeyPair @@ -166,10 +160,6 @@ object ClosedGroupsProtocolV2 { val newMemberJob = ClosedGroupUpdateMessageSendJobV2(member, closedGroupNewKind, sentTime) ApplicationContext.getInstance(context).jobManager.add(newMemberJob) } - // Notify the user - val infoType = GroupContext.Type.UPDATE - val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) } } @@ -210,10 +200,7 @@ object ClosedGroupsProtocolV2 { if (isCurrentUserAdmin) { generateAndSendNewEncryptionKeyPair(context, groupPublicKey, updatedMembers) } - // Notify the user - val infoType = GroupContext.Type.UPDATE - val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) + return@task Unit } } @@ -238,10 +225,6 @@ object ClosedGroupsProtocolV2 { job.onRun() // Run the job immediately // Update the group groupDB.updateTitle(groupID, newName) - // Notify the user - val infoType = GroupContext.Type.UPDATE - val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, infoType, newName, members, admins, threadID, sentTime) deferred.resolve(Unit) } return deferred.promise @@ -367,7 +350,11 @@ object ClosedGroupsProtocolV2 { return } // Generate the new encryption key pair - val newKeyPair = pendingKeyPair.getAndSet(Curve.generateKeyPair()) ?: Curve.generateKeyPair() + val newKeyPair = Curve.generateKeyPair() + do { + // make sure we set the pendingKeyPair or wait until it is not null + } while (!pendingKeyPair.compareAndSet(null, newKeyPair)) + // Distribute it val proto = SignalServiceProtos.KeyPair.newBuilder() proto.publicKey = ByteString.copyFrom(newKeyPair.publicKey.serialize().removing05PrefixIfNeeded()) @@ -382,6 +369,7 @@ object ClosedGroupsProtocolV2 { job.onRun() // Run the job immediately // Store it * after * having sent out the message to the group apiDB.addClosedGroupEncryptionKeyPair(newKeyPair, groupPublicKey) + pendingKeyPair.set(null) } @JvmStatic From 83d107cbce7bc29f44812f554ef39c6453c79e31 Mon Sep 17 00:00:00 2001 From: jubb Date: Fri, 12 Feb 2021 14:28:25 +1100 Subject: [PATCH 12/19] fix: compare serialized to serialized --- .../org/thoughtcrime/securesms/database/MmsSmsDatabase.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 159d96181..a27ae0c69 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -97,7 +97,9 @@ public class MmsSmsDatabase extends Database { while ((messageRecord = reader.getNext()) != null) { if ((Util.isOwnNumber(context, serializedAuthor) && messageRecord.isOutgoing()) || - (!Util.isOwnNumber(context, serializedAuthor) && messageRecord.getIndividualRecipient().getAddress().equals(serializedAuthor))) + (!Util.isOwnNumber(context, serializedAuthor) + && messageRecord.getIndividualRecipient().getAddress().serialize().equals(serializedAuthor) + )) { return messageRecord; } From 93f7d428cb3a5210c184a6022cbafd80431f2558 Mon Sep 17 00:00:00 2001 From: Jubb Date: Mon, 15 Feb 2021 12:05:04 +1100 Subject: [PATCH 13/19] fix: add in the encryption key send from current / pending for groupID in handleMembersAdded if admin and change pendingKeyPair implementation to keyed on groupPublicKey --- .../loki/protocol/ClosedGroupsProtocolV2.kt | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index 5874c1d88..0e5d0a8c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.loki.protocol import android.content.Context import android.util.Log import com.google.protobuf.ByteString -import kotlinx.coroutines.delay import nl.komponents.kovenant.Promise import nl.komponents.kovenant.deferred import nl.komponents.kovenant.task @@ -39,13 +38,13 @@ import org.session.libsession.utilities.TextSecurePreferences import java.io.IOException import java.util.* -import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.ConcurrentHashMap import kotlin.jvm.Throws object ClosedGroupsProtocolV2 { const val groupSizeLimit = 100 - private val pendingKeyPair = AtomicReference(null) + private val pendingKeyPair = ConcurrentHashMap>() sealed class Error(val description: String) : Exception() { object NoThread : Error("Couldn't find a thread associated with the given group public key") @@ -141,7 +140,9 @@ object ClosedGroupsProtocolV2 { val admins = group.admins.map { it.serialize() } val adminsAsData = admins.map { Hex.fromStringCondensed(it) } val sentTime = System.currentTimeMillis() - val encryptionKeyPair = pendingKeyPair.get() ?: apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) + val encryptionKeyPair = pendingKeyPair.getOrElse(groupPublicKey) { + Optional.fromNullable(apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey)) + }.orNull() if (encryptionKeyPair == null) { Log.d("Loki", "Couldn't get encryption key pair for closed group.") return@task Error.NoKeyPair @@ -190,7 +191,6 @@ object ClosedGroupsProtocolV2 { Log.d("Loki", "Can't remove admin from closed group unless the group is destroyed entirely.") return@task Error.InvalidUpdate } - val name = group.title // Send the update to the group val memberUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.RemoveMembers(removeMembersAsData) val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, memberUpdateKind, sentTime) @@ -351,11 +351,19 @@ object ClosedGroupsProtocolV2 { } // Generate the new encryption key pair val newKeyPair = Curve.generateKeyPair() + // replace call will not succeed if no value already set + pendingKeyPair.putIfAbsent(groupPublicKey,Optional.absent()) do { // make sure we set the pendingKeyPair or wait until it is not null - } while (!pendingKeyPair.compareAndSet(null, newKeyPair)) - + } while (!pendingKeyPair.replace(groupPublicKey,Optional.absent(),Optional.fromNullable(newKeyPair))) // Distribute it + sendEncryptionKeyPair(context, groupPublicKey, newKeyPair, targetMembers) + // Store it * after * having sent out the message to the group + apiDB.addClosedGroupEncryptionKeyPair(newKeyPair, groupPublicKey) + pendingKeyPair[groupPublicKey] = Optional.absent() + } + + private fun sendEncryptionKeyPair(context: Context, groupPublicKey: String, newKeyPair: ECKeyPair, targetMembers: Collection, force: Boolean = true) { val proto = SignalServiceProtos.KeyPair.newBuilder() proto.publicKey = ByteString.copyFrom(newKeyPair.publicKey.serialize().removing05PrefixIfNeeded()) proto.privateKey = ByteString.copyFrom(newKeyPair.privateKey.serialize()) @@ -365,11 +373,12 @@ object ClosedGroupsProtocolV2 { ClosedGroupUpdateMessageSendJobV2.KeyPairWrapper(publicKey, ciphertext) } val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, ClosedGroupUpdateMessageSendJobV2.Kind.EncryptionKeyPair(wrappers), System.currentTimeMillis()) - job.setContext(context) - job.onRun() // Run the job immediately - // Store it * after * having sent out the message to the group - apiDB.addClosedGroupEncryptionKeyPair(newKeyPair, groupPublicKey) - pendingKeyPair.set(null) + if (force) { + job.setContext(context) + job.onRun() // Run the job immediately + } else { + ApplicationContext.getInstance(context).jobManager.add(job) + } } @JvmStatic @@ -509,6 +518,7 @@ object ClosedGroupsProtocolV2 { fun handleClosedGroupMembersAdded(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { val userPublicKey = TextSecurePreferences.getLocalNumber(context) + val apiDB = DatabaseFactory.getLokiAPIDatabase(context) val groupDB = DatabaseFactory.getGroupDatabase(context) val groupID = doubleEncodeGroupID(groupPublicKey) val group = groupDB.getGroup(groupID).orNull() @@ -536,6 +546,17 @@ object ClosedGroupsProtocolV2 { } else { insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) } + if (userPublicKey in admins) { + // send current encryption key to the latest added members + val encryptionKeyPair = pendingKeyPair.getOrElse(groupPublicKey) { + Optional.fromNullable(apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey)) + }.orNull() + if (encryptionKeyPair == null) { + Log.d("Loki", "Couldn't get encryption key pair for closed group.") + } else { + sendEncryptionKeyPair(context, groupPublicKey, encryptionKeyPair, newMembers, false) + } + } } fun handleClosedGroupNameChange(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { From 8476e090e2ddde9c1994bf8b32329cce3c7fdd89 Mon Sep 17 00:00:00 2001 From: Jubb Date: Mon, 15 Feb 2021 12:08:08 +1100 Subject: [PATCH 14/19] fix: handle pendingKeyPair.getOrElse nullable after checking if key exists --- .../securesms/loki/protocol/ClosedGroupsProtocolV2.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index 0e5d0a8c8..ac66ec216 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -548,9 +548,8 @@ object ClosedGroupsProtocolV2 { } if (userPublicKey in admins) { // send current encryption key to the latest added members - val encryptionKeyPair = pendingKeyPair.getOrElse(groupPublicKey) { - Optional.fromNullable(apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey)) - }.orNull() + val encryptionKeyPair = pendingKeyPair[groupPublicKey]?.orNull() + ?: apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) if (encryptionKeyPair == null) { Log.d("Loki", "Couldn't get encryption key pair for closed group.") } else { From 069719d568aa2cf9bd1defa1a4cf5210dcb88aae Mon Sep 17 00:00:00 2001 From: Jubb Date: Mon, 15 Feb 2021 12:27:01 +1100 Subject: [PATCH 15/19] fix: re-add sync message send to self --- .../libsignal/service/api/SignalServiceMessageSender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1b854b97a..ab402c93c 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 @@ -347,7 +347,7 @@ 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, Optional.absent()); } } From a44a79e59f69c03eb54c837ab5dd5cd22ae1954b Mon Sep 17 00:00:00 2001 From: Jubb Date: Mon, 15 Feb 2021 16:38:50 +1100 Subject: [PATCH 16/19] fix: messages now filter properly for explicit group update messages --- .../loki/protocol/ClosedGroupsProtocolV2.kt | 25 ++++++++++++++++++- .../mms/OutgoingGroupMediaMessage.java | 19 +++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index ac66ec216..b5e3e4eb5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -104,6 +104,9 @@ object ClosedGroupsProtocolV2 { val groupDB = DatabaseFactory.getGroupDatabase(context) val groupID = doubleEncodeGroupID(groupPublicKey) val group = groupDB.getGroup(groupID).orNull() + val updatedMembers = group.members.map { it.serialize() }.toSet() - userPublicKey + val admins = group.admins.map { it.serialize() } + val name = group.title val sentTime = System.currentTimeMillis() if (group == null) { Log.d("Loki", "Can't leave nonexistent closed group.") @@ -114,6 +117,10 @@ object ClosedGroupsProtocolV2 { val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, ClosedGroupUpdateMessageSendJobV2.Kind.Leave, sentTime) job.setContext(context) job.onRun() // Run the job immediately + // Notify the user + val infoType = GroupContext.Type.QUIT + val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) + insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) // Remove the group private key and unsubscribe from PNs disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey) deferred.resolve(Unit) @@ -153,6 +160,10 @@ object ClosedGroupsProtocolV2 { val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, memberUpdateKind, sentTime) job.setContext(context) job.onRun() // Run the job immediately + // Notify the user + val infoType = GroupContext.Type.UPDATE + val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) + insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) // Send closed group update messages to any new members individually for (member in membersToAdd) { @Suppress("NAME_SHADOWING") @@ -191,11 +202,16 @@ object ClosedGroupsProtocolV2 { Log.d("Loki", "Can't remove admin from closed group unless the group is destroyed entirely.") return@task Error.InvalidUpdate } + val name = group.title // Send the update to the group val memberUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.RemoveMembers(removeMembersAsData) val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, memberUpdateKind, sentTime) job.setContext(context) job.onRun() // Run the job immediately + // Notify the user + val infoType = GroupContext.Type.UPDATE + val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) + insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) val isCurrentUserAdmin = admins.contains(userPublicKey) if (isCurrentUserAdmin) { generateAndSendNewEncryptionKeyPair(context, groupPublicKey, updatedMembers) @@ -223,6 +239,10 @@ object ClosedGroupsProtocolV2 { val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, kind, sentTime) job.setContext(context) job.onRun() // Run the job immediately + // Notify the user + val infoType = GroupContext.Type.UPDATE + val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) + insertOutgoingInfoMessage(context, groupID, infoType, newName, members, admins, threadID, sentTime) // Update the group groupDB.updateTitle(groupID, newName) deferred.resolve(Unit) @@ -753,6 +773,7 @@ object ClosedGroupsProtocolV2 { private fun insertOutgoingInfoMessage(context: Context, groupID: String, type: GroupContext.Type, name: String, members: Collection, admins: Collection, threadID: Long, sentTime: Long) { + val userPublicKey = TextSecurePreferences.getLocalNumber(context) val recipient = Recipient.from(context, Address.fromSerialized(groupID), false) val groupContextBuilder = GroupContext.newBuilder() .setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID))) @@ -760,8 +781,10 @@ object ClosedGroupsProtocolV2 { .setName(name) .addAllMembers(members) .addAllAdmins(admins) - val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, 0, null, listOf(), listOf()) + val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, sentTime, 0, null, listOf(), listOf()) val mmsDB = DatabaseFactory.getMmsDatabase(context) + val mmsSmsDB = DatabaseFactory.getMmsSmsDatabase(context) + if (mmsSmsDB.getMessageFor(sentTime,userPublicKey) != null) return val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null, sentTime) mmsDB.markAsSent(infoMessageID, true) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java b/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java index 4b1fbfb25..cf63d5a05 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java @@ -44,10 +44,27 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { @Nullable QuoteModel quote, @NonNull List contacts, @NonNull List previews) + { + super(recipient, Base64.encodeBytes(group.toByteArray()), + new LinkedList() {{if (avatar != null) add(avatar);}}, + System.currentTimeMillis(), + ThreadDatabase.DistributionTypes.CONVERSATION, expireIn, quote, contacts, previews); + + this.group = group; + } + + public OutgoingGroupMediaMessage(@NonNull Recipient recipient, + @NonNull GroupContext group, + @Nullable final Attachment avatar, + long sentTime, + long expireIn, + @Nullable QuoteModel quote, + @NonNull List contacts, + @NonNull List previews) { super(recipient, Base64.encodeBytes(group.toByteArray()), new LinkedList() {{if (avatar != null) add(avatar);}}, - System.currentTimeMillis(), + sentTime, ThreadDatabase.DistributionTypes.CONVERSATION, expireIn, quote, contacts, previews); this.group = group; From a63fce4ca66d7ac9ebe73c090bb616e124902d70 Mon Sep 17 00:00:00 2001 From: Jubb Date: Mon, 15 Feb 2021 17:35:56 +1100 Subject: [PATCH 17/19] fix: self-send messages now send to yourself only and aren't treated as sync messages --- .../thoughtcrime/securesms/jobs/PushMediaSendJob.java | 11 ++++++----- .../thoughtcrime/securesms/jobs/PushTextSendJob.java | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) 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 c89b44f13..e9d729f29 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -287,11 +287,12 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { if (SessionMetaProtocol.shared.isNoteToSelf(address.getNumber())) { // Loki - Device link messages don't go through here - Optional syncAccess = UnidentifiedAccessUtil.getAccessForSync(context); - SignalServiceSyncMessage syncMessage = buildSelfSendSyncMessage(context, mediaMessage, syncAccess); - - messageSender.sendMessage(syncMessage, syncAccess); - return syncAccess.isPresent(); + SendMessageResult result = messageSender.sendMessage(messageId, address, unidentifiedAccessPair, mediaMessage); + if (result.getLokiAPIError() != null) { + throw result.getLokiAPIError(); + } else { + return result.getSuccess().isUnidentified(); + } } else { SendMessageResult result = messageSender.sendMessage(messageId, address, unidentifiedAccessPair, mediaMessage); if (result.getLokiAPIError() != null) { 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 acac2e3d9..abec311d1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -226,11 +226,12 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { if (SessionMetaProtocol.shared.isNoteToSelf(address.getNumber())) { // Loki - Device link messages don't go through here - Optional syncAccess = UnidentifiedAccessUtil.getAccessForSync(context); - SignalServiceSyncMessage syncMessage = buildSelfSendSyncMessage(context, textSecureMessage, syncAccess); - - messageSender.sendMessage(syncMessage, syncAccess); - return syncAccess.isPresent(); + SendMessageResult result = messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage); + if (result.getLokiAPIError() != null) { + throw result.getLokiAPIError(); + } else { + return result.getSuccess().isUnidentified(); + } } else { SendMessageResult result = messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage); if (result.getLokiAPIError() != null) { From 9ccfb4702d3dff9a733b95018b40908f7e8d3879 Mon Sep 17 00:00:00 2001 From: Jubb Date: Mon, 15 Feb 2021 17:41:10 +1100 Subject: [PATCH 18/19] fix: self leave group is treated as QUIT instead of UPDATE --- .../securesms/loki/protocol/ClosedGroupsProtocolV2.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index b5e3e4eb5..c65494463 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -638,7 +638,7 @@ object ClosedGroupsProtocolV2 { } if (userPublicKey == senderPublicKey) { val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) + insertOutgoingInfoMessage(context, groupID, GroupContext.Type.QUIT, name, members, admins, threadID, sentTimestamp) } else { insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) } From 9f60a3ca365aab181bd6b925fd016732fb4d90cf Mon Sep 17 00:00:00 2001 From: Jubb Date: Mon, 15 Feb 2021 17:42:31 +1100 Subject: [PATCH 19/19] fix: other users treated as quit --- .../securesms/loki/protocol/ClosedGroupsProtocolV2.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index c65494463..bf12e073e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -640,7 +640,7 @@ object ClosedGroupsProtocolV2 { val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) insertOutgoingInfoMessage(context, groupID, GroupContext.Type.QUIT, name, members, admins, threadID, sentTimestamp) } else { - insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) + insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.QUIT, SignalServiceGroup.Type.QUIT, name, members, admins) } }