]> nmode's Git Repositories - signal-cli/commitdiff
Update libsignal-service-java
authorAsamK <asamk@gmx.de>
Thu, 13 May 2021 15:15:14 +0000 (17:15 +0200)
committerAsamK <asamk@gmx.de>
Thu, 13 May 2021 15:30:29 +0000 (17:30 +0200)
CHANGELOG.md
graalvm-config-dir/jni-config.json
graalvm-config-dir/reflect-config.json
lib/build.gradle.kts
lib/src/main/java/org/asamk/signal/manager/Manager.java
lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java
lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java
lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java
lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java
src/main/java/org/asamk/signal/ReceiveMessageHandler.java

index 170440424fe84238fdbc5c0b0dd1ab7b7b326479..7515a9848779a58b89900f899229ac90c0247a91 100644 (file)
@@ -3,6 +3,8 @@
 ## [Unreleased]
 
 ## [0.8.3] - 2021-05-13
+**Attention**: Now requires native libsignal-client version 0.5.1
+
 ### Fixed
 - Upgrading from account files with older profiles
 - Building native image with graalvm
index aeebbec84a269547d625f95bc04dbc36142fa751..bb19e040d302d25c126dfdc96df857ccca7141e3 100644 (file)
   "name":"org.whispersystems.libsignal.UntrustedIdentityException",
   "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
 },
+{
+  "name":"org.whispersystems.libsignal.logging.Log",
+  "methods":[{"name":"log","parameterTypes":["int","java.lang.String","java.lang.String"] }]
+},
 {
   "name":"org.whispersystems.libsignal.protocol.PreKeySignalMessage",
-  "methods":[{"name":"<init>","parameterTypes":["long"] }]
+  "methods":[
+    {"name":"<init>","parameterTypes":["long"] }, 
+    {"name":"nativeHandle","parameterTypes":[] }
+  ]
 },
 {
   "name":"org.whispersystems.libsignal.protocol.SignalMessage",
-  "methods":[{"name":"<init>","parameterTypes":["long"] }]
+  "methods":[
+    {"name":"<init>","parameterTypes":["long"] }, 
+    {"name":"nativeHandle","parameterTypes":[] }
+  ]
 },
 {
   "name":"org.whispersystems.libsignal.state.IdentityKeyStore"
index 2c1aaf601f687185adef2b4bbee39ef5d26f5d74..db7e24f609d9d0aa4bbade4c525c8ae0954c75ef 100644 (file)
   "fields":[
     {"name":"accessControl_", "allowUnsafeAccess":true}, 
     {"name":"avatar_", "allowUnsafeAccess":true}, 
+    {"name":"description_", "allowUnsafeAccess":true}, 
     {"name":"disappearingMessagesTimer_", "allowUnsafeAccess":true}, 
     {"name":"inviteLinkPassword_", "allowUnsafeAccess":true}, 
     {"name":"members_", "allowUnsafeAccess":true}, 
     {"name":"modifyAddFromInviteLinkAccess_", "allowUnsafeAccess":true}, 
     {"name":"modifyAttributesAccess_", "allowUnsafeAccess":true}, 
     {"name":"modifyAvatar_", "allowUnsafeAccess":true}, 
+    {"name":"modifyDescription_", "allowUnsafeAccess":true}, 
     {"name":"modifyDisappearingMessagesTimer_", "allowUnsafeAccess":true}, 
     {"name":"modifyInviteLinkPassword_", "allowUnsafeAccess":true}, 
     {"name":"modifyMemberAccess_", "allowUnsafeAccess":true}, 
   "fields":[
     {"name":"accessControl_", "allowUnsafeAccess":true}, 
     {"name":"avatar_", "allowUnsafeAccess":true}, 
+    {"name":"description_", "allowUnsafeAccess":true}, 
     {"name":"disappearingMessagesTimer_", "allowUnsafeAccess":true}, 
     {"name":"inviteLinkPassword_", "allowUnsafeAccess":true}, 
     {"name":"members_", "allowUnsafeAccess":true}, 
     {"name":"modifyMemberRoles_", "allowUnsafeAccess":true}, 
     {"name":"newAttributeAccess_", "allowUnsafeAccess":true}, 
     {"name":"newAvatar_", "allowUnsafeAccess":true}, 
+    {"name":"newDescription_", "allowUnsafeAccess":true}, 
     {"name":"newInviteLinkAccess_", "allowUnsafeAccess":true}, 
     {"name":"newInviteLinkPassword_", "allowUnsafeAccess":true}, 
     {"name":"newMemberAccess_", "allowUnsafeAccess":true}, 
     {"name":"groupV2_", "allowUnsafeAccess":true}, 
     {"name":"group_", "allowUnsafeAccess":true}, 
     {"name":"isViewOnce_", "allowUnsafeAccess":true}, 
+    {"name":"payment_", "allowUnsafeAccess":true}, 
     {"name":"preview_", "allowUnsafeAccess":true}, 
     {"name":"profileKey_", "allowUnsafeAccess":true}, 
     {"name":"quote_", "allowUnsafeAccess":true}, 
     {"name":"groups_", "allowUnsafeAccess":true}, 
     {"name":"keys_", "allowUnsafeAccess":true}, 
     {"name":"messageRequestResponse_", "allowUnsafeAccess":true}, 
+    {"name":"outgoingPayment_", "allowUnsafeAccess":true}, 
     {"name":"padding_", "allowUnsafeAccess":true}, 
     {"name":"read_", "allowUnsafeAccess":true}, 
     {"name":"request_", "allowUnsafeAccess":true}, 
     {"name":"sent_", "allowUnsafeAccess":true}, 
     {"name":"stickerPackOperation_", "allowUnsafeAccess":true}, 
     {"name":"verified_", "allowUnsafeAccess":true}, 
-    {"name":"viewOnceOpen_", "allowUnsafeAccess":true}
+    {"name":"viewOnceOpen_", "allowUnsafeAccess":true}, 
+    {"name":"viewed_", "allowUnsafeAccess":true}
   ]
 },
 {
     {"name":"type_", "allowUnsafeAccess":true}
   ]
 },
+{
+  "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Viewed",
+  "fields":[
+    {"name":"bitField0_", "allowUnsafeAccess":true}, 
+    {"name":"senderE164_", "allowUnsafeAccess":true}, 
+    {"name":"senderUuid_", "allowUnsafeAccess":true}, 
+    {"name":"timestamp_", "allowUnsafeAccess":true}
+  ]
+},
 {
   "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$TypingMessage",
   "fields":[
index a9b5a95fca05effe0b71d99c1f88d413d42b6232..de3e9382fe128e7720d03eddf44bcf6bd1607071 100644 (file)
@@ -14,7 +14,7 @@ repositories {
 }
 
 dependencies {
-    api("com.github.turasa:signal-service-java:2.15.3_unofficial_21")
+    api("com.github.turasa:signal-service-java:2.15.3_unofficial_22")
     implementation("com.google.protobuf:protobuf-javalite:3.10.0")
     implementation("org.bouncycastle:bcprov-jdk15on:1.68")
     implementation("org.slf4j:slf4j-api:1.7.30")
index 4d519c51c58d08f20729e9f19afa9c0c8c85f7b6..096f1c076fbee7b736ec000039920a789722d778 100644 (file)
@@ -83,6 +83,7 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager;
 import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
 import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
 import org.whispersystems.signalservice.api.SignalServiceMessageSender;
+import org.whispersystems.signalservice.api.SignalSessionLock;
 import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
 import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
 import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
@@ -161,6 +162,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -193,6 +195,15 @@ public class Manager implements Closeable {
     private final PinHelper pinHelper;
     private final AvatarStore avatarStore;
     private final AttachmentStore attachmentStore;
+    private final SignalSessionLock sessionLock = new SignalSessionLock() {
+        private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
+
+        @Override
+        public Lock acquire() {
+            LEGACY_LOCK.lock();
+            return LEGACY_LOCK::unlock;
+        }
+    };
 
     Manager(
             SignalAccount account,
@@ -389,6 +400,7 @@ public class Manager implements Closeable {
                     newProfile.getInternalServiceName(),
                     newProfile.getAbout() == null ? "" : newProfile.getAbout(),
                     newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(),
+                    Optional.absent(),
                     streamDetails);
         }
 
@@ -534,6 +546,7 @@ public class Manager implements Closeable {
                 account.getPassword(),
                 account.getDeviceId(),
                 account.getSignalProtocolStore(),
+                sessionLock,
                 userAgent,
                 account.isMultiDevice(),
                 Optional.fromNullable(messagePipe),
@@ -1511,18 +1524,12 @@ public class Manager implements Closeable {
         }
     }
 
-    private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, org.whispersystems.libsignal.UntrustedIdentityException {
+    private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, ProtocolUntrustedIdentityException {
         var cipher = new SignalServiceCipher(account.getSelfAddress(),
                 account.getSignalProtocolStore(),
+                sessionLock,
                 certificateValidator);
-        try {
-            return cipher.decrypt(envelope);
-        } catch (ProtocolUntrustedIdentityException e) {
-            if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
-                throw (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause();
-            }
-            throw new AssertionError(e);
-        }
+        return cipher.decrypt(envelope);
     }
 
     private void handleEndSession(RecipientId recipientId) {
@@ -1766,9 +1773,9 @@ public class Manager implements Closeable {
         if (!envelope.isReceipt()) {
             try {
                 content = decryptMessage(envelope);
-            } catch (org.whispersystems.libsignal.UntrustedIdentityException e) {
+            } catch (ProtocolUntrustedIdentityException e) {
                 if (!envelope.hasSource()) {
-                    final var identifier = ((org.whispersystems.libsignal.UntrustedIdentityException) e).getName();
+                    final var identifier = e.getSender();
                     final var recipientId = resolveRecipient(identifier);
                     try {
                         account.getMessageCache().replaceSender(cachedMessage, recipientId);
@@ -1889,8 +1896,8 @@ public class Manager implements Closeable {
                 handler.handleMessage(envelope, content, exception);
             }
             if (cachedMessage[0] != null) {
-                if (exception instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
-                    final var identifier = ((org.whispersystems.libsignal.UntrustedIdentityException) exception).getName();
+                if (exception instanceof ProtocolUntrustedIdentityException) {
+                    final var identifier = ((ProtocolUntrustedIdentityException) exception).getSender();
                     final var recipientId = resolveRecipient(identifier);
                     queuedActions.add(new RetrieveProfileAction(recipientId));
                     if (!envelope.hasSource()) {
index 4cf8653786ca024bc7301f93499ab46fd31d36d9..c3314f75ddee9b0b3382444428280a5d9e84723a 100644 (file)
@@ -51,7 +51,7 @@ public class ServiceConfig {
 
     public static boolean isSignalClientAvailable() {
         try {
-            org.signal.client.internal.Native.DisplayableFingerprint_Format(new byte[30], new byte[30]);
+            org.signal.client.internal.Native.DeviceTransfer_GeneratePrivateKey();
             return true;
         } catch (UnsatisfiedLinkError ignored) {
             return false;
index 1872e356ce73e2497f67aeb69c4d8219d9b5df96..e308bb6660e93be5d4e78d2e4783e51b8c49d128 100644 (file)
@@ -4,6 +4,7 @@ import org.whispersystems.libsignal.IdentityKey;
 import org.whispersystems.libsignal.IdentityKeyPair;
 import org.whispersystems.libsignal.InvalidKeyIdException;
 import org.whispersystems.libsignal.SignalProtocolAddress;
+import org.whispersystems.libsignal.groups.state.SenderKeyRecord;
 import org.whispersystems.libsignal.state.IdentityKeyStore;
 import org.whispersystems.libsignal.state.PreKeyRecord;
 import org.whispersystems.libsignal.state.PreKeyStore;
@@ -14,6 +15,7 @@ import org.whispersystems.signalservice.api.SignalServiceProtocolStore;
 import org.whispersystems.signalservice.api.SignalServiceSessionStore;
 
 import java.util.List;
+import java.util.UUID;
 
 public class SignalProtocolStore implements SignalServiceProtocolStore {
 
@@ -138,4 +140,15 @@ public class SignalProtocolStore implements SignalServiceProtocolStore {
     public void removeSignedPreKey(int signedPreKeyId) {
         signedPreKeyStore.removeSignedPreKey(signedPreKeyId);
     }
+
+    @Override
+    public void storeSenderKey(
+            final SignalProtocolAddress sender, final UUID distributionId, final SenderKeyRecord record
+    ) {
+    }
+
+    @Override
+    public SenderKeyRecord loadSenderKey(final SignalProtocolAddress sender, final UUID distributionId) {
+        return null;
+    }
 }
index 1909711de6839a044608952679c6042e5f77544f..aadadf95b2922d35a8c88c91038d9ce744dc67d3 100644 (file)
@@ -49,6 +49,7 @@ public class AttachmentUtils {
                 name,
                 false,
                 false,
+                false,
                 preview,
                 0,
                 0,
index f865ab214d3da588d38f78119cf151796e6330e9..c2ab7a5e0cec209d38328c532a1f4ec43c51cb96 100644 (file)
@@ -18,9 +18,9 @@ public class ProfileUtils {
     ) {
         var profileCipher = new ProfileCipher(profileKey);
         try {
-            var name = decryptName(encryptedProfile.getName(), profileCipher);
-            var about = decryptName(encryptedProfile.getAbout(), profileCipher);
-            var aboutEmoji = decryptName(encryptedProfile.getAboutEmoji(), profileCipher);
+            var name = decrypt(encryptedProfile.getName(), profileCipher);
+            var about = decrypt(encryptedProfile.getAbout(), profileCipher);
+            var aboutEmoji = decrypt(encryptedProfile.getAboutEmoji(), profileCipher);
 
             final var nameParts = splitName(name);
             return new Profile(new Date().getTime(),
@@ -66,13 +66,13 @@ public class ProfileUtils {
         return capabilities;
     }
 
-    private static String decryptName(
+    private static String decrypt(
             final String encryptedName, final ProfileCipher profileCipher
     ) throws InvalidCiphertextException {
         try {
             return encryptedName == null
                     ? null
-                    : new String(profileCipher.decryptName(Base64.getDecoder().decode(encryptedName)));
+                    : new String(profileCipher.decrypt(Base64.getDecoder().decode(encryptedName)));
         } catch (IllegalArgumentException e) {
             return null;
         }
index ce4f10686e630077cca42b560b910e5081b9157c..e63ce548fa98029773833a15cb0e13df5741a2a3 100644 (file)
@@ -5,8 +5,8 @@ import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupUtils;
 import org.asamk.signal.util.DateUtils;
 import org.asamk.signal.util.Util;
+import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
 import org.slf4j.helpers.MessageFormatter;
-import org.whispersystems.libsignal.UntrustedIdentityException;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
@@ -20,7 +20,6 @@ import org.whispersystems.signalservice.api.messages.shared.SharedContact;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 
-import java.io.IOException;
 import java.util.Base64;
 import java.util.stream.Collectors;
 
@@ -34,16 +33,6 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
 
     @Override
     public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception) {
-        try {
-            printMessage(envelope, content, exception);
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-    }
-
-    private void printMessage(
-            SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception
-    ) throws IOException {
         PlainTextWriter writer = new PlainTextWriterImpl(System.out);
 
         if (envelope.hasSource()) {
@@ -64,11 +53,11 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
             writer.println("Got receipt.");
         } else if (envelope.isSignalMessage() || envelope.isPreKeySignalMessage() || envelope.isUnidentifiedSender()) {
             if (exception != null) {
-                if (exception instanceof UntrustedIdentityException) {
-                    var e = (UntrustedIdentityException) exception;
+                if (exception instanceof ProtocolUntrustedIdentityException) {
+                    var e = (ProtocolUntrustedIdentityException) exception;
                     writer.println(
                             "The user’s key is untrusted, either the user has reinstalled Signal or a third party sent this message.");
-                    final var recipientName = m.resolveSignalServiceAddress(e.getName()).getLegacyIdentifier();
+                    final var recipientName = m.resolveSignalServiceAddress(e.getSender()).getLegacyIdentifier();
                     writer.println(
                             "Use 'signal-cli -u {} listIdentities -n {}', verify the key and run 'signal-cli -u {} trust -v \"FINGER_PRINT\" {}' to mark it as trusted",
                             m.getUsername(),
@@ -127,7 +116,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
 
     private void printDataMessage(
             PlainTextWriter writer, SignalServiceDataMessage message
-    ) throws IOException {
+    ) {
         writer.println("Message timestamp: {}", DateUtils.formatTimestamp(message.getTimestamp()));
         if (message.isViewOnce()) {
             writer.println("=VIEW ONCE=");
@@ -210,7 +199,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
 
     private void printTypingMessage(
             final PlainTextWriter writer, final SignalServiceTypingMessage typingMessage
-    ) throws IOException {
+    ) {
         writer.println("Action: {}", typingMessage.getAction());
         writer.println("Timestamp: {}", DateUtils.formatTimestamp(typingMessage.getTimestamp()));
         if (typingMessage.getGroupId().isPresent()) {
@@ -222,7 +211,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
 
     private void printReceiptMessage(
             final PlainTextWriter writer, final SignalServiceReceiptMessage receiptMessage
-    ) throws IOException {
+    ) {
         writer.println("When: {}", DateUtils.formatTimestamp(receiptMessage.getWhen()));
         if (receiptMessage.isDeliveryReceipt()) {
             writer.println("Is delivery receipt");
@@ -241,7 +230,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
 
     private void printCallMessage(
             final PlainTextWriter writer, final SignalServiceCallMessage callMessage
-    ) throws IOException {
+    ) {
         if (callMessage.getDestinationDeviceId().isPresent()) {
             final var deviceId = callMessage.getDestinationDeviceId().get();
             writer.println("Destination device id: {}", deviceId);
@@ -277,7 +266,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
 
     private void printSyncMessage(
             final PlainTextWriter writer, final SignalServiceSyncMessage syncMessage
-    ) throws IOException {
+    ) {
         if (syncMessage.getContacts().isPresent()) {
             final var contactsMessage = syncMessage.getContacts().get();
             var type = contactsMessage.isComplete() ? "complete" : "partial";
@@ -425,7 +414,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
 
     private void printPreview(
             final PlainTextWriter writer, final SignalServiceDataMessage.Preview preview
-    ) throws IOException {
+    ) {
         writer.println("Title: {}", preview.getTitle());
         writer.println("Description: {}", preview.getDescription());
         writer.println("Date: {}", DateUtils.formatTimestamp(preview.getDate()));
@@ -438,7 +427,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
 
     private void printSticker(
             final PlainTextWriter writer, final SignalServiceDataMessage.Sticker sticker
-    ) throws IOException {
+    ) {
         writer.println("Pack id: {}", Base64.getEncoder().encodeToString(sticker.getPackId()));
         writer.println("Pack key: {}", Base64.getEncoder().encodeToString(sticker.getPackKey()));
         writer.println("Sticker id: {}", sticker.getStickerId());
@@ -448,7 +437,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
 
     private void printReaction(
             final PlainTextWriter writer, final SignalServiceDataMessage.Reaction reaction
-    ) throws IOException {
+    ) {
         writer.println("Emoji: {}", reaction.getEmoji());
         writer.println("Target author: {}", formatContact(m.resolveSignalServiceAddress(reaction.getTargetAuthor())));
         writer.println("Target timestamp: {}", DateUtils.formatTimestamp(reaction.getTargetSentTimestamp()));
@@ -457,7 +446,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
 
     private void printQuote(
             final PlainTextWriter writer, final SignalServiceDataMessage.Quote quote
-    ) throws IOException {
+    ) {
         writer.println("Id: {}", quote.getId());
         writer.println("Author: {}", m.resolveSignalServiceAddress(quote.getAuthor()).getLegacyIdentifier());
         writer.println("Text: {}", quote.getText());
@@ -482,7 +471,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
         }
     }
 
-    private void printSharedContact(final PlainTextWriter writer, final SharedContact contact) throws IOException {
+    private void printSharedContact(final PlainTextWriter writer, final SharedContact contact) {
         writer.println("Name:");
         var name = contact.getName();
         writer.indent(w -> {
@@ -591,7 +580,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
 
     private void printGroupContext(
             final PlainTextWriter writer, final SignalServiceGroupContext groupContext
-    ) throws IOException {
+    ) {
         final var groupId = GroupUtils.getGroupId(groupContext);
         if (groupContext.getGroupV1().isPresent()) {
             var groupInfo = groupContext.getGroupV1().get();
@@ -616,7 +605,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
         }
     }
 
-    private void printGroupInfo(final PlainTextWriter writer, final GroupId groupId) throws IOException {
+    private void printGroupInfo(final PlainTextWriter writer, final GroupId groupId) {
         writer.println("Id: {}", groupId.toBase64());
 
         var group = m.getGroup(groupId);
@@ -629,7 +618,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
 
     private void printMention(
             PlainTextWriter writer, SignalServiceDataMessage.Mention mention
-    ) throws IOException {
+    ) {
         final var address = m.resolveSignalServiceAddress(new SignalServiceAddress(mention.getUuid(), null));
         writer.println("- {}: {} (length: {})", formatContact(address), mention.getStart(), mention.getLength());
     }