]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java
Reduce log level of invalid sync contact address
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / SyncHelper.java
index 9fd5076def5dac80a667f8a6216836470482fa91..2d074014a9d8fe641256c5a34271b9b7914a067f 100644 (file)
@@ -2,13 +2,13 @@ package org.asamk.signal.manager.helper;
 
 import org.asamk.signal.manager.api.Contact;
 import org.asamk.signal.manager.api.GroupId;
+import org.asamk.signal.manager.api.MessageEnvelope.Sync.MessageRequestResponse;
 import org.asamk.signal.manager.api.TrustLevel;
 import org.asamk.signal.manager.storage.SignalAccount;
 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
 import org.asamk.signal.manager.storage.recipients.RecipientId;
 import org.asamk.signal.manager.storage.stickers.StickerPack;
-import org.asamk.signal.manager.util.AttachmentUtils;
 import org.asamk.signal.manager.util.IOUtils;
 import org.asamk.signal.manager.util.MimeUtils;
 import org.jetbrains.annotations.NotNull;
@@ -17,21 +17,26 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.messages.SendMessageResult;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
-import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
+import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact;
+import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactAvatar;
 import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream;
 import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsOutputStream;
 import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup;
 import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream;
 import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream;
 import org.whispersystems.signalservice.api.messages.multidevice.KeysMessage;
+import org.whispersystems.signalservice.api.messages.multidevice.MessageRequestResponseMessage;
+import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
+import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage;
+import org.whispersystems.signalservice.api.push.ServiceId;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.internal.push.SyncMessage;
 
@@ -86,6 +91,22 @@ public class SyncHelper {
                 .sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.STORAGE_MANIFEST));
     }
 
+    public void sendSyncReceiptMessage(ServiceId sender, SignalServiceReceiptMessage receiptMessage) {
+        if (receiptMessage.isReadReceipt()) {
+            final var readMessages = receiptMessage.getTimestamps()
+                    .stream()
+                    .map(t -> new ReadMessage(sender, t))
+                    .toList();
+            context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forRead(readMessages));
+        } else if (receiptMessage.isViewedReceipt()) {
+            final var viewedMessages = receiptMessage.getTimestamps()
+                    .stream()
+                    .map(t -> new ViewedMessage(sender, t))
+                    .toList();
+            context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forViewed(viewedMessages));
+        }
+    }
+
     public void sendGroups() throws IOException {
         var groupsFile = IOUtils.createTempFile();
 
@@ -113,10 +134,12 @@ public class SyncHelper {
 
             if (groupsFile.exists() && groupsFile.length() > 0) {
                 try (var groupsFileStream = new FileInputStream(groupsFile)) {
+                    final var uploadSpec = context.getDependencies().getMessageSender().getResumableUploadSpec();
                     var attachmentStream = SignalServiceAttachment.newStreamBuilder()
                             .withStream(groupsFileStream)
                             .withContentType(MimeUtils.OCTET_STREAM)
                             .withLength(groupsFile.length())
+                            .withResumableUploadSpec(uploadSpec)
                             .build();
 
                     context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forGroups(attachmentStream));
@@ -142,7 +165,14 @@ public class SyncHelper {
                     final var contact = contactPair.second();
                     final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId);
 
-                    out.write(getDeviceContact(address, recipientId, contact));
+                    final var deviceContact = getDeviceContact(address, recipientId, contact);
+                    out.write(deviceContact);
+                    deviceContact.getAvatar().ifPresent(a -> {
+                        try {
+                            a.getInputStream().close();
+                        } catch (IOException ignored) {
+                        }
+                    });
                 }
 
                 if (account.getProfileKey() != null) {
@@ -150,16 +180,25 @@ public class SyncHelper {
                     final var address = account.getSelfRecipientAddress();
                     final var recipientId = account.getSelfRecipientId();
                     final var contact = account.getContactStore().getContact(recipientId);
-                    out.write(getDeviceContact(address, recipientId, contact));
+                    final var deviceContact = getDeviceContact(address, recipientId, contact);
+                    out.write(deviceContact);
+                    deviceContact.getAvatar().ifPresent(a -> {
+                        try {
+                            a.getInputStream().close();
+                        } catch (IOException ignored) {
+                        }
+                    });
                 }
             }
 
             if (contactsFile.exists() && contactsFile.length() > 0) {
                 try (var contactsFileStream = new FileInputStream(contactsFile)) {
+                    final var uploadSpec = context.getDependencies().getMessageSender().getResumableUploadSpec();
                     var attachmentStream = SignalServiceAttachment.newStreamBuilder()
                             .withStream(contactsFileStream)
                             .withContentType(MimeUtils.OCTET_STREAM)
                             .withLength(contactsFile.length())
+                            .withResumableUploadSpec(uploadSpec)
                             .build();
 
                     context.getSendHelper()
@@ -178,7 +217,9 @@ public class SyncHelper {
 
     @NotNull
     private DeviceContact getDeviceContact(
-            final RecipientAddress address, final RecipientId recipientId, final Contact contact
+            final RecipientAddress address,
+            final RecipientId recipientId,
+            final Contact contact
     ) throws IOException {
         var currentIdentity = address.serviceId().isEmpty()
                 ? null
@@ -199,17 +240,21 @@ public class SyncHelper {
                 Optional.ofNullable(contact == null ? null : contact.color()),
                 Optional.ofNullable(verifiedMessage),
                 Optional.ofNullable(profileKey),
-                contact != null && contact.isBlocked(),
                 Optional.ofNullable(contact == null ? null : contact.messageExpirationTime()),
+                Optional.ofNullable(contact == null ? null : contact.messageExpirationTimeVersion()),
                 Optional.empty(),
                 contact != null && contact.isArchived());
     }
 
     public SendMessageResult sendBlockedList() {
-        var addresses = new ArrayList<SignalServiceAddress>();
+        var addresses = new ArrayList<BlockedListMessage.Individual>();
         for (var record : account.getContactStore().getContacts()) {
             if (record.second().isBlocked()) {
-                addresses.add(context.getRecipientHelper().resolveSignalServiceAddress(record.first()));
+                final var address = account.getRecipientAddressResolver().resolveRecipientAddress(record.first());
+                if (address.aci().isPresent() || address.number().isPresent()) {
+                    addresses.add(new BlockedListMessage.Individual(address.aci().orElse(null),
+                            address.number().orElse(null)));
+                }
             }
         }
         var groupIds = new ArrayList<byte[]>();
@@ -223,7 +268,9 @@ public class SyncHelper {
     }
 
     public SendMessageResult sendVerifiedMessage(
-            SignalServiceAddress destination, IdentityKey identityKey, TrustLevel trustLevel
+            SignalServiceAddress destination,
+            IdentityKey identityKey,
+            TrustLevel trustLevel
     ) {
         var verifiedMessage = new VerifiedMessage(destination,
                 identityKey,
@@ -233,13 +280,16 @@ public class SyncHelper {
     }
 
     public SendMessageResult sendKeysMessage() {
-        var keysMessage = new KeysMessage(Optional.ofNullable(account.getOrCreateStorageKey()),
-                Optional.ofNullable(account.getOrCreatePinMasterKey()));
+        var keysMessage = new KeysMessage(account.getOrCreateStorageKey(),
+                account.getOrCreatePinMasterKey(),
+                account.getOrCreateAccountEntropyPool(),
+                account.getOrCreateMediaRootBackupKey());
         return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forKeys(keysMessage));
     }
 
     public SendMessageResult sendStickerOperationsMessage(
-            List<StickerPack> installStickers, List<StickerPack> removeStickers
+            List<StickerPack> installStickers,
+            List<StickerPack> removeStickers
     ) {
         var installStickerMessages = installStickers.stream().map(s -> getStickerPackOperationMessage(s, true));
         var removeStickerMessages = removeStickers.stream().map(s -> getStickerPackOperationMessage(s, false));
@@ -249,7 +299,8 @@ public class SyncHelper {
     }
 
     private static StickerPackOperationMessage getStickerPackOperationMessage(
-            final StickerPack s, final boolean installed
+            final StickerPack s,
+            final boolean installed
     ) {
         return new StickerPackOperationMessage(s.packId().serialize(),
                 s.packKey(),
@@ -315,7 +366,7 @@ public class SyncHelper {
                 c = s.read();
             } catch (IOException e) {
                 if (e.getMessage() != null && e.getMessage().contains("Missing contact address!")) {
-                    logger.warn("Sync contacts contained invalid contact, ignoring: {}", e.getMessage());
+                    logger.debug("Sync contacts contained invalid contact, ignoring: {}", e.getMessage());
                     continue;
                 } else {
                     throw e;
@@ -353,40 +404,87 @@ public class SyncHelper {
                                 TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
             }
             if (c.getExpirationTimer().isPresent()) {
-                builder.withMessageExpirationTime(c.getExpirationTimer().get());
+                if (c.getExpirationTimerVersion().isPresent() && (
+                        contact == null || c.getExpirationTimerVersion().get() > contact.messageExpirationTimeVersion()
+                )) {
+                    builder.withMessageExpirationTime(c.getExpirationTimer().get());
+                    builder.withMessageExpirationTimeVersion(c.getExpirationTimerVersion().get());
+                } else {
+                    logger.debug(
+                            "[ContactSync] {} was synced with an old expiration timer. Ignoring. Received: {} Current: {}",
+                            recipientId,
+                            c.getExpirationTimerVersion(),
+                            contact == null ? 1 : contact.messageExpirationTimeVersion());
+                }
             }
-            builder.withIsBlocked(c.isBlocked());
             builder.withIsArchived(c.isArchived());
             account.getContactStore().storeContact(recipientId, builder.build());
 
             if (c.getAvatar().isPresent()) {
-                downloadContactAvatar(c.getAvatar().get(), address);
+                storeContactAvatar(c.getAvatar().get(), address);
             }
         }
     }
 
+    public SendMessageResult sendMessageRequestResponse(final MessageRequestResponse.Type type, final GroupId groupId) {
+        final var response = MessageRequestResponseMessage.forGroup(groupId.serialize(), localToRemoteType(type));
+        return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response));
+    }
+
+    public SendMessageResult sendMessageRequestResponse(
+            final MessageRequestResponse.Type type,
+            final RecipientId recipientId
+    ) {
+        final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId);
+        if (address.serviceId().isEmpty()) {
+            return null;
+        }
+        context.getContactHelper()
+                .setContactProfileSharing(recipientId,
+                        type == MessageRequestResponse.Type.ACCEPT
+                                || type == MessageRequestResponse.Type.UNBLOCK_AND_ACCEPT);
+        final var response = MessageRequestResponseMessage.forIndividual(address.serviceId().get(),
+                localToRemoteType(type));
+        return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response));
+    }
+
     private SendMessageResult requestSyncData(final SyncMessage.Request.Type type) {
         var r = new SyncMessage.Request.Builder().type(type).build();
         var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
         return context.getSendHelper().sendSyncMessage(message);
     }
 
-    private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(RecipientAddress address) throws IOException {
+    private Optional<DeviceContactAvatar> createContactAvatarAttachment(RecipientAddress address) throws IOException {
         final var streamDetails = context.getAvatarStore().retrieveContactAvatar(address);
         if (streamDetails == null) {
             return Optional.empty();
         }
 
-        return Optional.of(AttachmentUtils.createAttachmentStream(streamDetails, Optional.empty()));
+        return Optional.of(new DeviceContactAvatar(streamDetails.getStream(),
+                streamDetails.getLength(),
+                streamDetails.getContentType()));
     }
 
-    private void downloadContactAvatar(SignalServiceAttachment avatar, RecipientAddress address) {
+    private void storeContactAvatar(DeviceContactAvatar avatar, RecipientAddress address) {
         try {
             context.getAvatarStore()
                     .storeContactAvatar(address,
-                            outputStream -> context.getAttachmentHelper().retrieveAttachment(avatar, outputStream));
+                            outputStream -> IOUtils.copyStream(avatar.getInputStream(), outputStream));
         } catch (IOException e) {
             logger.warn("Failed to download avatar for contact {}, ignoring: {}", address, e.getMessage());
         }
     }
+
+    private MessageRequestResponseMessage.Type localToRemoteType(final MessageRequestResponse.Type type) {
+        return switch (type) {
+            case UNKNOWN -> MessageRequestResponseMessage.Type.UNKNOWN;
+            case ACCEPT -> MessageRequestResponseMessage.Type.ACCEPT;
+            case DELETE -> MessageRequestResponseMessage.Type.DELETE;
+            case BLOCK -> MessageRequestResponseMessage.Type.BLOCK;
+            case BLOCK_AND_DELETE -> MessageRequestResponseMessage.Type.BLOCK_AND_DELETE;
+            case UNBLOCK_AND_ACCEPT -> MessageRequestResponseMessage.Type.UNBLOCK_AND_ACCEPT;
+            case SPAM -> MessageRequestResponseMessage.Type.SPAM;
+            case BLOCK_AND_SPAM -> MessageRequestResponseMessage.Type.BLOCK_AND_SPAM;
+        };
+    }
 }