]> nmode's Git Repositories - signal-cli/blobdiff - src/main/java/org/asamk/signal/manager/Manager.java
Prevent NullPointerException when destination is null for some reason
[signal-cli] / src / main / java / org / asamk / signal / manager / Manager.java
index 909100855784f08e1a3c1d0f17e065db6c80eeaa..d15d164b52a2ed957bb43b356840ea6ac75ab4d9 100644 (file)
@@ -43,6 +43,7 @@ import org.signal.libsignal.metadata.ProtocolLegacyMessageException;
 import org.signal.libsignal.metadata.ProtocolNoSessionException;
 import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
 import org.signal.libsignal.metadata.SelfSendException;
+import org.signal.storageservice.protos.groups.GroupChange;
 import org.signal.storageservice.protos.groups.local.DecryptedGroup;
 import org.signal.storageservice.protos.groups.local.DecryptedMember;
 import org.signal.zkgroup.InvalidInputException;
@@ -213,7 +214,9 @@ public class Manager implements Closeable {
         this.groupHelper = new GroupHelper(this::getRecipientProfileKeyCredential,
                 this::getRecipientProfile,
                 account::getSelfAddress,
-                groupsV2Operations);
+                groupsV2Operations,
+                groupsV2Api,
+                this::getGroupAuthForToday);
     }
 
     public String getUsername() {
@@ -736,52 +739,29 @@ public class Manager implements Closeable {
     }
 
     public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException {
-        SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT).withId(groupId).build();
 
-        SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group);
+        SignalServiceDataMessage.Builder messageBuilder;
 
         final GroupInfo g = getGroupForSending(groupId);
         if (g instanceof GroupInfoV1) {
             GroupInfoV1 groupInfoV1 = (GroupInfoV1) g;
+            SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
+                    .withId(groupId)
+                    .build();
+            messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group);
             groupInfoV1.removeMember(account.getSelfAddress());
             account.getGroupStore().updateGroup(groupInfoV1);
         } else {
-            throw new RuntimeException("TODO Not implemented!");
+            final GroupInfoV2 groupInfoV2 = (GroupInfoV2) g;
+            final Pair<DecryptedGroup, GroupChange> groupGroupChangePair = groupHelper.leaveGroup(groupInfoV2);
+            groupInfoV2.setGroup(groupGroupChangePair.first());
+            messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
+            account.getGroupStore().updateGroup(groupInfoV2);
         }
 
         return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
     }
 
-    private GroupInfoV2 createGroupV2(
-            String name, Collection<SignalServiceAddress> members, InputStream avatar
-    ) throws IOException {
-        byte[] avatarBytes = avatar == null ? null : IOUtils.readFully(avatar);
-        final GroupsV2Operations.NewGroup newGroup = groupHelper.createGroupV2(name, members, avatarBytes);
-        final GroupSecretParams groupSecretParams = newGroup.getGroupSecretParams();
-
-        final GroupsV2AuthorizationString groupAuthForToday;
-        final DecryptedGroup decryptedGroup;
-        try {
-            groupAuthForToday = getGroupAuthForToday(groupSecretParams);
-            groupsV2Api.putNewGroup(newGroup, groupAuthForToday);
-            decryptedGroup = groupsV2Api.getGroup(groupSecretParams, groupAuthForToday);
-        } catch (IOException | VerificationFailedException | InvalidGroupStateException e) {
-            System.err.println("Failed to create V2 group: " + e.getMessage());
-            return null;
-        }
-        if (decryptedGroup == null) {
-            System.err.println("Failed to create V2 group!");
-            return null;
-        }
-
-        final byte[] groupId = groupSecretParams.getPublicParams().getGroupIdentifier().serialize();
-        final GroupMasterKey masterKey = groupSecretParams.getMasterKey();
-        GroupInfoV2 g = new GroupInfoV2(groupId, masterKey);
-        g.setGroup(decryptedGroup);
-
-        return g;
-    }
-
     private Pair<byte[], List<SendMessageResult>> sendUpdateGroupMessage(
             byte[] groupId, String name, Collection<SignalServiceAddress> members, String avatarFile
     ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
@@ -789,8 +769,7 @@ public class Manager implements Closeable {
         SignalServiceDataMessage.Builder messageBuilder;
         if (groupId == null) {
             // Create new group
-            InputStream avatar = avatarFile == null ? null : new FileInputStream(avatarFile);
-            GroupInfoV2 gv2 = createGroupV2(name, members, avatar);
+            GroupInfoV2 gv2 = groupHelper.createGroupV2(name, members, avatarFile);
             if (gv2 == null) {
                 GroupInfoV1 gv1 = new GroupInfoV1(KeyUtils.createGroupId());
                 gv1.addMembers(Collections.singleton(account.getSelfAddress()));
@@ -798,18 +777,41 @@ public class Manager implements Closeable {
                 messageBuilder = getGroupUpdateMessageBuilder(gv1);
                 g = gv1;
             } else {
-                messageBuilder = getGroupUpdateMessageBuilder(gv2);
+                messageBuilder = getGroupUpdateMessageBuilder(gv2, null);
                 g = gv2;
             }
         } else {
             GroupInfo group = getGroupForSending(groupId);
-            if (!(group instanceof GroupInfoV1)) {
-                throw new RuntimeException("TODO Not implemented!");
+            if (group instanceof GroupInfoV2) {
+                Pair<DecryptedGroup, GroupChange> groupGroupChangePair = null;
+                if (members != null) {
+                    final Set<SignalServiceAddress> newMembers = new HashSet<>(members);
+                    newMembers.removeAll(group.getMembers());
+                    if (newMembers.size() > 0) {
+                        groupGroupChangePair = groupHelper.updateGroupV2((GroupInfoV2) group, newMembers);
+                    }
+                }
+                if (groupGroupChangePair == null || name != null || avatarFile != null) {
+                    if (groupGroupChangePair != null) {
+                        ((GroupInfoV2) group).setGroup(groupGroupChangePair.first());
+                        messageBuilder = getGroupUpdateMessageBuilder((GroupInfoV2) group,
+                                groupGroupChangePair.second().toByteArray());
+                        sendMessage(messageBuilder, group.getMembersWithout(account.getSelfAddress()));
+                    }
+
+                    groupGroupChangePair = groupHelper.updateGroupV2((GroupInfoV2) group, name, avatarFile);
+                }
+
+                ((GroupInfoV2) group).setGroup(groupGroupChangePair.first());
+                messageBuilder = getGroupUpdateMessageBuilder((GroupInfoV2) group,
+                        groupGroupChangePair.second().toByteArray());
+                g = group;
+            } else {
+                GroupInfoV1 gv1 = (GroupInfoV1) group;
+                updateGroupV1(gv1, name, members, avatarFile);
+                messageBuilder = getGroupUpdateMessageBuilder(gv1);
+                g = gv1;
             }
-            GroupInfoV1 gv1 = (GroupInfoV1) group;
-            updateGroupV1(gv1, name, members, avatarFile);
-            messageBuilder = getGroupUpdateMessageBuilder(gv1);
-            g = gv1;
         }
 
         account.getGroupStore().updateGroup(g);
@@ -899,11 +901,10 @@ public class Manager implements Closeable {
                 .withExpiration(g.getMessageExpirationTime());
     }
 
-    private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g) {
+    private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g, byte[] signedGroupChange) {
         SignalServiceGroupV2.Builder group = SignalServiceGroupV2.newBuilder(g.getMasterKey())
                 .withRevision(g.getGroup().getRevision())
-//                .withSignedGroupChange() // TODO
-                ;
+                .withSignedGroupChange(signedGroupChange);
         return SignalServiceDataMessage.newBuilder()
                 .asGroupMessage(group.build())
                 .withExpiration(g.getMessageExpirationTime());
@@ -1427,16 +1428,20 @@ public class Manager implements Closeable {
 
     private GroupsV2AuthorizationString getGroupAuthForToday(
             final GroupSecretParams groupSecretParams
-    ) throws IOException, VerificationFailedException {
+    ) throws IOException {
         final int today = currentTimeDays();
         // Returns credentials for the next 7 days
         final HashMap<Integer, AuthCredentialResponse> credentials = groupsV2Api.getCredentials(today);
         // TODO cache credentials until they expire
         AuthCredentialResponse authCredentialResponse = credentials.get(today);
-        return groupsV2Api.getGroupsV2AuthorizationString(account.getUuid(),
-                today,
-                groupSecretParams,
-                authCredentialResponse);
+        try {
+            return groupsV2Api.getGroupsV2AuthorizationString(account.getUuid(),
+                    today,
+                    groupSecretParams,
+                    authCredentialResponse);
+        } catch (VerificationFailedException e) {
+            throw new IOException(e);
+        }
     }
 
     private List<HandleAction> handleSignalServiceDataMessage(
@@ -1534,8 +1539,18 @@ public class Manager implements Closeable {
 
                     if (groupInfoV2.getGroup() == null
                             || groupInfoV2.getGroup().getRevision() < groupContext.getRevision()) {
-                        // TODO check if revision is only 1 behind and a signedGroupChange is available
-                        groupInfoV2.setGroup(getDecryptedGroup(groupSecretParams));
+                        DecryptedGroup group = null;
+                        if (groupContext.hasSignedGroupChange()
+                                && groupInfoV2.getGroup() != null
+                                && groupInfoV2.getGroup().getRevision() + 1 == groupContext.getRevision()) {
+                            group = groupHelper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(),
+                                    groupContext.getSignedGroupChange(),
+                                    groupMasterKey);
+                        }
+                        if (group == null) {
+                            group = getDecryptedGroup(groupSecretParams);
+                        }
+                        groupInfoV2.setGroup(group);
                         account.getGroupStore().updateGroup(groupInfoV2);
                     }
                 }
@@ -1853,7 +1868,7 @@ public class Manager implements Closeable {
     ) {
         List<HandleAction> actions = new ArrayList<>();
         if (content != null) {
-            SignalServiceAddress sender;
+            final SignalServiceAddress sender;
             if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
                 sender = envelope.getSourceAddress();
             } else {
@@ -1880,11 +1895,14 @@ public class Manager implements Closeable {
                 SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
                 if (syncMessage.getSent().isPresent()) {
                     SentTranscriptMessage message = syncMessage.getSent().get();
-                    actions.addAll(handleSignalServiceDataMessage(message.getMessage(),
-                            true,
-                            sender,
-                            message.getDestination().orNull(),
-                            ignoreAttachments));
+                    final SignalServiceAddress destination = message.getDestination().orNull();
+                    if (destination != null) {
+                        actions.addAll(handleSignalServiceDataMessage(message.getMessage(),
+                                true,
+                                sender,
+                                destination,
+                                ignoreAttachments));
+                    }
                 }
                 if (syncMessage.getRequest().isPresent()) {
                     RequestMessage rm = syncMessage.getRequest().get();