]> nmode's Git Repositories - signal-cli/commitdiff
Refactor to use GroupId class to wrap the byte array
authorAsamK <asamk@gmx.de>
Thu, 24 Dec 2020 15:36:47 +0000 (16:36 +0100)
committerAsamK <asamk@gmx.de>
Thu, 24 Dec 2020 15:36:47 +0000 (16:36 +0100)
Helps distinguish between group v1 and v2 ids

31 files changed:
src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java
src/main/java/org/asamk/signal/ReceiveMessageHandler.java
src/main/java/org/asamk/signal/commands/BlockCommand.java
src/main/java/org/asamk/signal/commands/JoinGroupCommand.java
src/main/java/org/asamk/signal/commands/ListGroupsCommand.java
src/main/java/org/asamk/signal/commands/QuitGroupCommand.java
src/main/java/org/asamk/signal/commands/SendCommand.java
src/main/java/org/asamk/signal/commands/SendReactionCommand.java
src/main/java/org/asamk/signal/commands/UnblockCommand.java
src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java
src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java
src/main/java/org/asamk/signal/json/JsonGroupInfo.java
src/main/java/org/asamk/signal/manager/GroupId.java [new file with mode: 0644]
src/main/java/org/asamk/signal/manager/GroupIdFormatException.java [new file with mode: 0644]
src/main/java/org/asamk/signal/manager/GroupIdV1.java [new file with mode: 0644]
src/main/java/org/asamk/signal/manager/GroupIdV2.java [new file with mode: 0644]
src/main/java/org/asamk/signal/manager/GroupNotFoundException.java
src/main/java/org/asamk/signal/manager/GroupUtils.java
src/main/java/org/asamk/signal/manager/HandleAction.java
src/main/java/org/asamk/signal/manager/KeyUtils.java
src/main/java/org/asamk/signal/manager/Manager.java
src/main/java/org/asamk/signal/manager/NotAGroupMemberException.java
src/main/java/org/asamk/signal/manager/helper/GroupHelper.java
src/main/java/org/asamk/signal/storage/SignalAccount.java
src/main/java/org/asamk/signal/storage/groups/GroupInfo.java
src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java
src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java
src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java
src/main/java/org/asamk/signal/util/ErrorUtils.java
src/main/java/org/asamk/signal/util/GroupIdFormatException.java [deleted file]
src/main/java/org/asamk/signal/util/Util.java

index 41b91a4869714ff7ea8733a635c101c1fee2d556..50eb9f9b5ffb1b21c5ed5f79df86798f3b027eb6 100644 (file)
@@ -65,7 +65,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler {
             } else if (content.getDataMessage().isPresent()) {
                 SignalServiceDataMessage message = content.getDataMessage().get();
 
-                byte[] groupId = getGroupId(m, message);
+                byte[] groupId = getGroupId(message);
                 if (!message.isEndSession() && (
                         groupId == null
                                 || message.getGroupContext().get().getGroupV1Type() == null
@@ -91,7 +91,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler {
                             .getGroupContext()
                             .isPresent()) {
                         SignalServiceDataMessage message = transcript.getMessage();
-                        byte[] groupId = getGroupId(m, message);
+                        byte[] groupId = getGroupId(message);
 
                         try {
                             conn.sendMessage(new Signal.SyncMessageReceived(objectPath,
@@ -112,20 +112,9 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler {
         }
     }
 
-    private static byte[] getGroupId(final Manager m, final SignalServiceDataMessage message) {
-        byte[] groupId;
-        if (message.getGroupContext().isPresent()) {
-            if (message.getGroupContext().get().getGroupV1().isPresent()) {
-                groupId = message.getGroupContext().get().getGroupV1().get().getGroupId();
-            } else if (message.getGroupContext().get().getGroupV2().isPresent()) {
-                groupId = GroupUtils.getGroupId(message.getGroupContext().get().getGroupV2().get().getMasterKey());
-            } else {
-                groupId = null;
-            }
-        } else {
-            groupId = null;
-        }
-        return groupId;
+    private static byte[] getGroupId(final SignalServiceDataMessage message) {
+        return message.getGroupContext().isPresent() ? GroupUtils.getGroupId(message.getGroupContext().get())
+                .serialize() : null;
     }
 
     static private List<String> getAttachments(SignalServiceDataMessage message, Manager m) {
index 711f503dbc3e8e543d2b6700889fe949acce128c..99010e13be19cd86e82126202fb2c08786f32695 100644 (file)
@@ -1,5 +1,6 @@
 package org.asamk.signal;
 
+import org.asamk.signal.manager.GroupId;
 import org.asamk.signal.manager.GroupUtils;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.storage.contacts.ContactInfo;
@@ -328,8 +329,9 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
                     System.out.println(" - Timestamp: " + DateUtils.formatTimestamp(typingMessage.getTimestamp()));
                     if (typingMessage.getGroupId().isPresent()) {
                         System.out.println(" - Group Info:");
-                        System.out.println("   Id: " + Base64.encodeBytes(typingMessage.getGroupId().get()));
-                        GroupInfo group = m.getGroup(typingMessage.getGroupId().get());
+                        final GroupId groupId = GroupId.unknownVersion(typingMessage.getGroupId().get());
+                        System.out.println("   Id: " + groupId.toBase64());
+                        GroupInfo group = m.getGroup(groupId);
                         if (group != null) {
                             System.out.println("   Name: " + group.getTitle());
                         } else {
@@ -356,13 +358,14 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
         if (message.getGroupContext().isPresent()) {
             System.out.println("Group info:");
             final SignalServiceGroupContext groupContext = message.getGroupContext().get();
+            final GroupId groupId = GroupUtils.getGroupId(groupContext);
             if (groupContext.getGroupV1().isPresent()) {
                 SignalServiceGroup groupInfo = groupContext.getGroupV1().get();
-                System.out.println("  Id: " + Base64.encodeBytes(groupInfo.getGroupId()));
+                System.out.println("  Id: " + groupId.toBase64());
                 if (groupInfo.getType() == SignalServiceGroup.Type.UPDATE && groupInfo.getName().isPresent()) {
                     System.out.println("  Name: " + groupInfo.getName().get());
                 } else {
-                    GroupInfo group = m.getGroup(groupInfo.getGroupId());
+                    GroupInfo group = m.getGroup(groupId);
                     if (group != null) {
                         System.out.println("  Name: " + group.getTitle());
                     } else {
@@ -381,8 +384,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
                 }
             } else if (groupContext.getGroupV2().isPresent()) {
                 final SignalServiceGroupV2 groupInfo = groupContext.getGroupV2().get();
-                byte[] groupId = GroupUtils.getGroupId(groupInfo.getMasterKey());
-                System.out.println("  Id: " + Base64.encodeBytes(groupId));
+                System.out.println("  Id: " + groupId.toBase64());
                 GroupInfo group = m.getGroup(groupId);
                 if (group != null) {
                     System.out.println("  Name: " + group.getTitle());
index 95c3738c1168c7671c1f7774ce7268a58e9fe747..2a9bc4e939c1822d728a01a7484057cbcfb85707 100644 (file)
@@ -3,9 +3,10 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.manager.GroupId;
+import org.asamk.signal.manager.GroupIdFormatException;
 import org.asamk.signal.manager.GroupNotFoundException;
 import org.asamk.signal.manager.Manager;
-import org.asamk.signal.util.GroupIdFormatException;
 import org.asamk.signal.util.Util;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 
@@ -36,7 +37,7 @@ public class BlockCommand implements LocalCommand {
         if (ns.<String>getList("group") != null) {
             for (String groupIdString : ns.<String>getList("group")) {
                 try {
-                    byte[] groupId = Util.decodeGroupId(groupIdString);
+                    GroupId groupId = Util.decodeGroupId(groupIdString);
                     m.setGroupBlocked(groupId, true);
                 } catch (GroupIdFormatException | GroupNotFoundException e) {
                     System.err.println(e.getMessage());
index 62b996cfda8c982cbdff1fb033cf917f70f5159b..8438e1fad635a6a5bada22df8c85b13a2356fba4 100644 (file)
@@ -4,6 +4,7 @@ import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.Signal;
+import org.asamk.signal.manager.GroupId;
 import org.asamk.signal.manager.GroupInviteLinkUrl;
 import org.asamk.signal.manager.Manager;
 import org.freedesktop.dbus.exceptions.DBusExecutionException;
@@ -11,7 +12,6 @@ import org.whispersystems.libsignal.util.Pair;
 import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
 import org.whispersystems.signalservice.api.messages.SendMessageResult;
 import org.whispersystems.signalservice.internal.push.exceptions.GroupPatchNotAcceptedException;
-import org.whispersystems.util.Base64;
 
 import java.io.IOException;
 import java.util.List;
@@ -52,12 +52,12 @@ public class JoinGroupCommand implements LocalCommand {
         }
 
         try {
-            final Pair<byte[], List<SendMessageResult>> results = m.joinGroup(linkUrl);
-            byte[] newGroupId = results.first();
+            final Pair<GroupId, List<SendMessageResult>> results = m.joinGroup(linkUrl);
+            GroupId newGroupId = results.first();
             if (!m.getGroup(newGroupId).isMember(m.getSelfAddress())) {
-                System.out.println("Requested to join group \"" + Base64.encodeBytes(newGroupId) + "\"");
+                System.out.println("Requested to join group \"" + newGroupId.toBase64() + "\"");
             } else {
-                System.out.println("Joined group \"" + Base64.encodeBytes(newGroupId) + "\"");
+                System.out.println("Joined group \"" + newGroupId.toBase64() + "\"");
             }
             return handleTimestampAndSendMessageResults(0, results.second());
         } catch (AssertionError e) {
index e7844fdad119853b5aefe81f8203bca727d7c179..4d1032a2a7b9382dac36683d3796a9cb8efffa6f 100644 (file)
@@ -8,7 +8,6 @@ import org.asamk.signal.manager.GroupInviteLinkUrl;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.storage.groups.GroupInfo;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.util.Base64;
 
 import java.util.List;
 import java.util.Set;
@@ -40,7 +39,7 @@ public class ListGroupsCommand implements LocalCommand {
 
             System.out.println(String.format(
                     "Id: %s Name: %s  Active: %s Blocked: %b Members: %s Pending members: %s Requesting members: %s Link: %s",
-                    Base64.encodeBytes(group.groupId),
+                    group.getGroupId().toBase64(),
                     group.getTitle(),
                     group.isMember(m.getSelfAddress()),
                     group.isBlocked(),
@@ -50,7 +49,7 @@ public class ListGroupsCommand implements LocalCommand {
                     groupInviteLink == null ? '-' : groupInviteLink.getUrl()));
         } else {
             System.out.println(String.format("Id: %s Name: %s  Active: %s Blocked: %b",
-                    Base64.encodeBytes(group.groupId),
+                    group.getGroupId().toBase64(),
                     group.getTitle(),
                     group.isMember(m.getSelfAddress()),
                     group.isBlocked()));
index 20d06eba9d5ce111cd2173aa42e4a20715ad4f0f..efc63f8fa2cf389d7f31884565695091d6afcb02 100644 (file)
@@ -3,10 +3,11 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.manager.GroupId;
+import org.asamk.signal.manager.GroupIdFormatException;
 import org.asamk.signal.manager.GroupNotFoundException;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.NotAGroupMemberException;
-import org.asamk.signal.util.GroupIdFormatException;
 import org.asamk.signal.util.Util;
 import org.whispersystems.libsignal.util.Pair;
 import org.whispersystems.signalservice.api.messages.SendMessageResult;
@@ -36,7 +37,7 @@ public class QuitGroupCommand implements LocalCommand {
         }
 
         try {
-            final byte[] groupId = Util.decodeGroupId(ns.getString("group"));
+            final GroupId groupId = Util.decodeGroupId(ns.getString("group"));
             final Pair<Long, List<SendMessageResult>> results = m.sendQuitGroupMessage(groupId);
             return handleTimestampAndSendMessageResults(results.first(), results.second());
         } catch (IOException e) {
index 551cf9384e6d2bf2c0a5ffe0988d1038979a11cb..04b064340c188cb4a90687c3638191db29ba3b06 100644 (file)
@@ -5,7 +5,7 @@ import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.Signal;
-import org.asamk.signal.util.GroupIdFormatException;
+import org.asamk.signal.manager.GroupIdFormatException;
 import org.asamk.signal.util.IOUtils;
 import org.asamk.signal.util.Util;
 import org.freedesktop.dbus.exceptions.DBusExecutionException;
@@ -79,7 +79,7 @@ public class SendCommand implements DbusCommand {
             if (ns.getString("group") != null) {
                 byte[] groupId;
                 try {
-                    groupId = Util.decodeGroupId(ns.getString("group"));
+                    groupId = Util.decodeGroupId(ns.getString("group")).serialize();
                 } catch (GroupIdFormatException e) {
                     handleGroupIdFormatException(e);
                     return 1;
index 000a1349517e77a883d1a93f41b5fa2a7db397fa..345c918014077658a00de2a77ab3f26755991560 100644 (file)
@@ -4,10 +4,11 @@ import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.manager.GroupId;
+import org.asamk.signal.manager.GroupIdFormatException;
 import org.asamk.signal.manager.GroupNotFoundException;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.NotAGroupMemberException;
-import org.asamk.signal.util.GroupIdFormatException;
 import org.asamk.signal.util.Util;
 import org.whispersystems.libsignal.util.Pair;
 import org.whispersystems.signalservice.api.messages.SendMessageResult;
@@ -65,7 +66,7 @@ public class SendReactionCommand implements LocalCommand {
         try {
             final Pair<Long, List<SendMessageResult>> results;
             if (ns.getString("group") != null) {
-                byte[] groupId = Util.decodeGroupId(ns.getString("group"));
+                GroupId groupId = Util.decodeGroupId(ns.getString("group"));
                 results = m.sendGroupMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, groupId);
             } else {
                 results = m.sendMessageReaction(emoji,
index b4f6cc3b11025d5414c260e8cf28050b74ef2352..73e578ace26c176b354a9ca27f9bc04578d4f6c1 100644 (file)
@@ -3,9 +3,10 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.manager.GroupId;
+import org.asamk.signal.manager.GroupIdFormatException;
 import org.asamk.signal.manager.GroupNotFoundException;
 import org.asamk.signal.manager.Manager;
-import org.asamk.signal.util.GroupIdFormatException;
 import org.asamk.signal.util.Util;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 
@@ -36,7 +37,7 @@ public class UnblockCommand implements LocalCommand {
         if (ns.<String>getList("group") != null) {
             for (String groupIdString : ns.<String>getList("group")) {
                 try {
-                    byte[] groupId = Util.decodeGroupId(groupIdString);
+                    GroupId groupId = Util.decodeGroupId(groupIdString);
                     m.setGroupBlocked(groupId, false);
                 } catch (GroupIdFormatException | GroupNotFoundException e) {
                     System.err.println(e.getMessage());
index 4216fd9baad30013257dcd6295f2458e7882006c..dae06b86404630153f103ecad7c69e0e5f94964a 100644 (file)
@@ -4,7 +4,7 @@ import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.Signal;
-import org.asamk.signal.util.GroupIdFormatException;
+import org.asamk.signal.manager.GroupIdFormatException;
 import org.asamk.signal.util.Util;
 import org.freedesktop.dbus.exceptions.DBusExecutionException;
 import org.whispersystems.util.Base64;
@@ -35,7 +35,7 @@ public class UpdateGroupCommand implements DbusCommand {
         byte[] groupId = null;
         if (ns.getString("group") != null) {
             try {
-                groupId = Util.decodeGroupId(ns.getString("group"));
+                groupId = Util.decodeGroupId(ns.getString("group")).serialize();
             } catch (GroupIdFormatException e) {
                 handleGroupIdFormatException(e);
                 return 1;
index 396063f2b582ecc7de1588fe6856db407d9deab4..cbb7283504b6ace1efb6875f7a621a11625cf5d5 100644 (file)
@@ -2,6 +2,7 @@ package org.asamk.signal.dbus;
 
 import org.asamk.Signal;
 import org.asamk.signal.manager.AttachmentInvalidException;
+import org.asamk.signal.manager.GroupId;
 import org.asamk.signal.manager.GroupNotFoundException;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.NotAGroupMemberException;
@@ -92,7 +93,9 @@ public class DbusSignalImpl implements Signal {
     @Override
     public long sendGroupMessage(final String message, final List<String> attachments, final byte[] groupId) {
         try {
-            Pair<Long, List<SendMessageResult>> results = m.sendGroupMessage(message, attachments, groupId);
+            Pair<Long, List<SendMessageResult>> results = m.sendGroupMessage(message,
+                    attachments,
+                    GroupId.unknownVersion(groupId));
             checkSendMessageResults(results.first(), results.second());
             return results.first();
         } catch (IOException e) {
@@ -134,7 +137,7 @@ public class DbusSignalImpl implements Signal {
     @Override
     public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
         try {
-            m.setGroupBlocked(groupId, blocked);
+            m.setGroupBlocked(GroupId.unknownVersion(groupId), blocked);
         } catch (GroupNotFoundException e) {
             throw new Error.GroupNotFound(e.getMessage());
         }
@@ -145,14 +148,14 @@ public class DbusSignalImpl implements Signal {
         List<GroupInfo> groups = m.getGroups();
         List<byte[]> ids = new ArrayList<>(groups.size());
         for (GroupInfo group : groups) {
-            ids.add(group.groupId);
+            ids.add(group.getGroupId().serialize());
         }
         return ids;
     }
 
     @Override
     public String getGroupName(final byte[] groupId) {
-        GroupInfo group = m.getGroup(groupId);
+        GroupInfo group = m.getGroup(GroupId.unknownVersion(groupId));
         if (group == null) {
             return "";
         } else {
@@ -162,7 +165,7 @@ public class DbusSignalImpl implements Signal {
 
     @Override
     public List<String> getGroupMembers(final byte[] groupId) {
-        GroupInfo group = m.getGroup(groupId);
+        GroupInfo group = m.getGroup(GroupId.unknownVersion(groupId));
         if (group == null) {
             return Collections.emptyList();
         } else {
@@ -189,9 +192,11 @@ public class DbusSignalImpl implements Signal {
             if (avatar.isEmpty()) {
                 avatar = null;
             }
-            final Pair<byte[], List<SendMessageResult>> results = m.updateGroup(groupId, name, members, avatar);
+            final Pair<GroupId, List<SendMessageResult>> results = m.updateGroup(groupId == null
+                    ? null
+                    : GroupId.unknownVersion(groupId), name, members, avatar);
             checkSendMessageResults(0, results.second());
-            return results.first();
+            return results.first().serialize();
         } catch (IOException e) {
             throw new Error.Failure(e.getMessage());
         } catch (GroupNotFoundException | NotAGroupMemberException e) {
index 970cde52d7011434c2a932985447fc1c38874d08..9709be2025e48716f08d883e95f547260e324b4a 100644 (file)
@@ -31,7 +31,7 @@ class JsonGroupInfo {
     }
 
     JsonGroupInfo(SignalServiceGroupV2 groupInfo) {
-        this.groupId = Base64.encodeBytes(GroupUtils.getGroupId(groupInfo.getMasterKey()));
+        this.groupId = GroupUtils.getGroupIdV2(groupInfo.getMasterKey()).toBase64();
         this.type = groupInfo.hasSignedGroupChange() ? "UPDATE" : "DELIVER";
     }
 
diff --git a/src/main/java/org/asamk/signal/manager/GroupId.java b/src/main/java/org/asamk/signal/manager/GroupId.java
new file mode 100644 (file)
index 0000000..34e18e8
--- /dev/null
@@ -0,0 +1,63 @@
+package org.asamk.signal.manager;
+
+import org.whispersystems.util.Base64;
+
+import java.util.Arrays;
+
+public abstract class GroupId {
+
+    private final byte[] id;
+
+    public static GroupIdV1 v1(byte[] id) {
+        return new GroupIdV1(id);
+    }
+
+    public static GroupIdV2 v2(byte[] id) {
+        return new GroupIdV2(id);
+    }
+
+    public static GroupId unknownVersion(byte[] id) {
+        if (id.length == 16) {
+            return new GroupIdV1(id);
+        } else if (id.length == 32) {
+            return new GroupIdV2(id);
+        }
+
+        throw new AssertionError("Invalid group id of size " + id.length);
+    }
+
+    public static GroupId fromBase64(String id) throws GroupIdFormatException {
+        try {
+            return unknownVersion(java.util.Base64.getDecoder().decode(id));
+        } catch (Throwable e) {
+            throw new GroupIdFormatException(id, e);
+        }
+    }
+
+    public GroupId(final byte[] id) {
+        this.id = id;
+    }
+
+    public byte[] serialize() {
+        return id;
+    }
+
+    public String toBase64() {
+        return Base64.encodeBytes(id);
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        final GroupId groupId = (GroupId) o;
+
+        return Arrays.equals(id, groupId.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(id);
+    }
+}
diff --git a/src/main/java/org/asamk/signal/manager/GroupIdFormatException.java b/src/main/java/org/asamk/signal/manager/GroupIdFormatException.java
new file mode 100644 (file)
index 0000000..83afd15
--- /dev/null
@@ -0,0 +1,8 @@
+package org.asamk.signal.manager;
+
+public class GroupIdFormatException extends Exception {
+
+    public GroupIdFormatException(String groupId, Throwable e) {
+        super("Failed to decode groupId (must be base64) \"" + groupId + "\": " + e.getMessage(), e);
+    }
+}
diff --git a/src/main/java/org/asamk/signal/manager/GroupIdV1.java b/src/main/java/org/asamk/signal/manager/GroupIdV1.java
new file mode 100644 (file)
index 0000000..40862f0
--- /dev/null
@@ -0,0 +1,14 @@
+package org.asamk.signal.manager;
+
+import static org.asamk.signal.manager.KeyUtils.getSecretBytes;
+
+public class GroupIdV1 extends GroupId {
+
+    public static GroupIdV1 createRandom() {
+        return new GroupIdV1(getSecretBytes(16));
+    }
+
+    public GroupIdV1(final byte[] id) {
+        super(id);
+    }
+}
diff --git a/src/main/java/org/asamk/signal/manager/GroupIdV2.java b/src/main/java/org/asamk/signal/manager/GroupIdV2.java
new file mode 100644 (file)
index 0000000..b329be1
--- /dev/null
@@ -0,0 +1,14 @@
+package org.asamk.signal.manager;
+
+import java.util.Base64;
+
+public class GroupIdV2 extends GroupId {
+
+    public static GroupIdV2 fromBase64(String groupId) {
+        return new GroupIdV2(Base64.getDecoder().decode(groupId));
+    }
+
+    public GroupIdV2(final byte[] id) {
+        super(id);
+    }
+}
index 0c0d6d2d016120ccf300f77caba9b688fbbb405a..d7efa923ee20ad5ad7011df4bc5f2eaaaf6a928d 100644 (file)
@@ -1,10 +1,8 @@
 package org.asamk.signal.manager;
 
-import org.whispersystems.util.Base64;
-
 public class GroupNotFoundException extends Exception {
 
-    public GroupNotFoundException(byte[] groupId) {
-        super("Group not found: " + Base64.encodeBytes(groupId));
+    public GroupNotFoundException(GroupId groupId) {
+        super("Group not found: " + groupId.toBase64());
     }
 }
index a0e95c7aff21086d05aa7ae5f0f7e879f11f5226..d86dfbe9cc179932a95a0143a1b7f4397b593b19 100644 (file)
@@ -9,6 +9,7 @@ import org.signal.zkgroup.groups.GroupSecretParams;
 import org.whispersystems.libsignal.kdf.HKDFv3;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
+import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
 import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
 
 public class GroupUtils {
@@ -18,7 +19,7 @@ public class GroupUtils {
     ) {
         if (groupInfo instanceof GroupInfoV1) {
             SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.DELIVER)
-                    .withId(groupInfo.groupId)
+                    .withId(groupInfo.getGroupId().serialize())
                     .build();
             messageBuilder.asGroupMessage(group);
         } else {
@@ -30,14 +31,34 @@ public class GroupUtils {
         }
     }
 
-    public static byte[] getGroupId(GroupMasterKey groupMasterKey) {
+    public static GroupId getGroupId(SignalServiceGroupContext context) {
+        if (context.getGroupV1().isPresent()) {
+            return GroupId.v1(context.getGroupV1().get().getGroupId());
+        } else if (context.getGroupV2().isPresent()) {
+            return getGroupIdV2(context.getGroupV2().get().getMasterKey());
+        } else {
+            return null;
+        }
+    }
+
+    public static GroupIdV2 getGroupIdV2(GroupSecretParams groupSecretParams) {
+        return GroupId.v2(groupSecretParams.getPublicParams().getGroupIdentifier().serialize());
+    }
+
+    public static GroupIdV2 getGroupIdV2(GroupMasterKey groupMasterKey) {
         final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
-        return groupSecretParams.getPublicParams().getGroupIdentifier().serialize();
+        return getGroupIdV2(groupSecretParams);
+    }
+
+    public static GroupIdV2 getGroupIdV2(GroupIdV1 groupIdV1) {
+        final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(deriveV2MigrationMasterKey(
+                groupIdV1));
+        return getGroupIdV2(groupSecretParams);
     }
 
-    public static GroupMasterKey deriveV2MigrationMasterKey(byte[] groupIdV1) {
+    private static GroupMasterKey deriveV2MigrationMasterKey(GroupIdV1 groupIdV1) {
         try {
-            return new GroupMasterKey(new HKDFv3().deriveSecrets(groupIdV1,
+            return new GroupMasterKey(new HKDFv3().deriveSecrets(groupIdV1.serialize(),
                     "GV2 Migration".getBytes(),
                     GroupMasterKey.SIZE));
         } catch (InvalidInputException e) {
index 9bdd3885a83c9c79b744061275ca1a437a17e819..aa25d8c5ae398d85a829ef11d408b226d364ba5f 100644 (file)
@@ -2,7 +2,6 @@ package org.asamk.signal.manager;
 
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 
-import java.util.Arrays;
 import java.util.Objects;
 
 interface HandleAction {
@@ -93,9 +92,9 @@ class SendSyncBlockedListAction implements HandleAction {
 class SendGroupInfoRequestAction implements HandleAction {
 
     private final SignalServiceAddress address;
-    private final byte[] groupId;
+    private final GroupIdV1 groupId;
 
-    public SendGroupInfoRequestAction(final SignalServiceAddress address, final byte[] groupId) {
+    public SendGroupInfoRequestAction(final SignalServiceAddress address, final GroupIdV1 groupId) {
         this.address = address;
         this.groupId = groupId;
     }
@@ -109,14 +108,17 @@ class SendGroupInfoRequestAction implements HandleAction {
     public boolean equals(final Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
+
         final SendGroupInfoRequestAction that = (SendGroupInfoRequestAction) o;
-        return address.equals(that.address) && Arrays.equals(groupId, that.groupId);
+
+        if (!address.equals(that.address)) return false;
+        return groupId.equals(that.groupId);
     }
 
     @Override
     public int hashCode() {
-        int result = Objects.hash(address);
-        result = 31 * result + Arrays.hashCode(groupId);
+        int result = address.hashCode();
+        result = 31 * result + groupId.hashCode();
         return result;
     }
 }
@@ -124,9 +126,9 @@ class SendGroupInfoRequestAction implements HandleAction {
 class SendGroupUpdateAction implements HandleAction {
 
     private final SignalServiceAddress address;
-    private final byte[] groupId;
+    private final GroupIdV1 groupId;
 
-    public SendGroupUpdateAction(final SignalServiceAddress address, final byte[] groupId) {
+    public SendGroupUpdateAction(final SignalServiceAddress address, final GroupIdV1 groupId) {
         this.address = address;
         this.groupId = groupId;
     }
@@ -140,14 +142,17 @@ class SendGroupUpdateAction implements HandleAction {
     public boolean equals(final Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
+
         final SendGroupUpdateAction that = (SendGroupUpdateAction) o;
-        return address.equals(that.address) && Arrays.equals(groupId, that.groupId);
+
+        if (!address.equals(that.address)) return false;
+        return groupId.equals(that.groupId);
     }
 
     @Override
     public int hashCode() {
-        int result = Objects.hash(address);
-        result = 31 * result + Arrays.hashCode(groupId);
+        int result = address.hashCode();
+        result = 31 * result + groupId.hashCode();
         return result;
     }
 }
index d6f332c0f7745d75599f72f1dfa7bdcec537f4fc..21f6037fe240fa8d960f66191f551e3d91b03736 100644 (file)
@@ -26,10 +26,6 @@ class KeyUtils {
         return getSecret(18);
     }
 
-    static byte[] createGroupId() {
-        return getSecretBytes(16);
-    }
-
     static byte[] createStickerUploadKey() {
         return getSecretBytes(32);
     }
index 8a46846fe556a5f71868b3a21ef32e3d8e1e4b27..b5d425d8d4b8881e63a55091139f3badfcb5cbaa 100644 (file)
@@ -679,7 +679,7 @@ public class Manager implements Closeable {
         }
     }
 
-    private Optional<SignalServiceAttachmentStream> createGroupAvatarAttachment(byte[] groupId) throws IOException {
+    private Optional<SignalServiceAttachmentStream> createGroupAvatarAttachment(GroupId groupId) throws IOException {
         File file = getGroupAvatarFile(groupId);
         if (!file.exists()) {
             return Optional.absent();
@@ -697,7 +697,7 @@ public class Manager implements Closeable {
         return Optional.of(Utils.createAttachment(file));
     }
 
-    private GroupInfo getGroupForSending(byte[] groupId) throws GroupNotFoundException, NotAGroupMemberException {
+    private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
         GroupInfo g = account.getGroupStore().getGroup(groupId);
         if (g == null) {
             throw new GroupNotFoundException(groupId);
@@ -708,7 +708,7 @@ public class Manager implements Closeable {
         return g;
     }
 
-    private GroupInfo getGroupForUpdating(byte[] groupId) throws GroupNotFoundException, NotAGroupMemberException {
+    private GroupInfo getGroupForUpdating(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
         GroupInfo g = account.getGroupStore().getGroup(groupId);
         if (g == null) {
             throw new GroupNotFoundException(groupId);
@@ -724,7 +724,7 @@ public class Manager implements Closeable {
     }
 
     public Pair<Long, List<SendMessageResult>> sendGroupMessage(
-            SignalServiceDataMessage.Builder messageBuilder, byte[] groupId
+            SignalServiceDataMessage.Builder messageBuilder, GroupId groupId
     ) throws IOException, GroupNotFoundException, NotAGroupMemberException {
         final GroupInfo g = getGroupForSending(groupId);
 
@@ -735,7 +735,7 @@ public class Manager implements Closeable {
     }
 
     public Pair<Long, List<SendMessageResult>> sendGroupMessage(
-            String messageText, List<String> attachments, byte[] groupId
+            String messageText, List<String> attachments, GroupId groupId
     ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
                 .withBody(messageText);
@@ -747,7 +747,7 @@ public class Manager implements Closeable {
     }
 
     public Pair<Long, List<SendMessageResult>> sendGroupMessageReaction(
-            String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, byte[] groupId
+            String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, GroupId groupId
     ) throws IOException, InvalidNumberException, NotAGroupMemberException, GroupNotFoundException {
         SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji,
                 remove,
@@ -759,7 +759,7 @@ public class Manager implements Closeable {
         return sendGroupMessage(messageBuilder, groupId);
     }
 
-    public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException {
+    public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(GroupId groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException {
 
         SignalServiceDataMessage.Builder messageBuilder;
 
@@ -767,7 +767,7 @@ public class Manager implements Closeable {
         if (g instanceof GroupInfoV1) {
             GroupInfoV1 groupInfoV1 = (GroupInfoV1) g;
             SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
-                    .withId(groupId)
+                    .withId(groupId.serialize())
                     .build();
             messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group);
             groupInfoV1.removeMember(account.getSelfAddress());
@@ -783,8 +783,8 @@ public class Manager implements Closeable {
         return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
     }
 
-    private Pair<byte[], List<SendMessageResult>> sendUpdateGroupMessage(
-            byte[] groupId, String name, Collection<SignalServiceAddress> members, String avatarFile
+    private Pair<GroupId, List<SendMessageResult>> sendUpdateGroupMessage(
+            GroupId groupId, String name, Collection<SignalServiceAddress> members, String avatarFile
     ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
         GroupInfo g;
         SignalServiceDataMessage.Builder messageBuilder;
@@ -792,7 +792,7 @@ public class Manager implements Closeable {
             // Create new group
             GroupInfoV2 gv2 = groupHelper.createGroupV2(name, members, avatarFile);
             if (gv2 == null) {
-                GroupInfoV1 gv1 = new GroupInfoV1(KeyUtils.createGroupId());
+                GroupInfoV1 gv1 = new GroupInfoV1(GroupIdV1.createRandom());
                 gv1.addMembers(Collections.singleton(account.getSelfAddress()));
                 updateGroupV1(gv1, name, members, avatarFile);
                 messageBuilder = getGroupUpdateMessageBuilder(gv1);
@@ -834,7 +834,7 @@ public class Manager implements Closeable {
                             groupGroupChangePair.second());
                 }
 
-                return new Pair<>(group.groupId, result.second());
+                return new Pair<>(group.getGroupId(), result.second());
             } else {
                 GroupInfoV1 gv1 = (GroupInfoV1) group;
                 updateGroupV1(gv1, name, members, avatarFile);
@@ -847,16 +847,16 @@ public class Manager implements Closeable {
 
         final Pair<Long, List<SendMessageResult>> result = sendMessage(messageBuilder,
                 g.getMembersIncludingPendingWithout(account.getSelfAddress()));
-        return new Pair<>(g.groupId, result.second());
+        return new Pair<>(g.getGroupId(), result.second());
     }
 
-    public Pair<byte[], List<SendMessageResult>> joinGroup(
+    public Pair<GroupId, List<SendMessageResult>> joinGroup(
             GroupInviteLinkUrl inviteLinkUrl
     ) throws IOException, GroupLinkNotActiveException {
         return sendJoinGroupMessage(inviteLinkUrl);
     }
 
-    private Pair<byte[], List<SendMessageResult>> sendJoinGroupMessage(
+    private Pair<GroupId, List<SendMessageResult>> sendJoinGroupMessage(
             GroupInviteLinkUrl inviteLinkUrl
     ) throws IOException, GroupLinkNotActiveException {
         final DecryptedGroupJoinInfo groupJoinInfo = groupHelper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(),
@@ -870,12 +870,12 @@ public class Manager implements Closeable {
 
         if (group.getGroup() == null) {
             // Only requested member, can't send update to group members
-            return new Pair<>(group.groupId, List.of());
+            return new Pair<>(group.getGroupId(), List.of());
         }
 
         final Pair<Long, List<SendMessageResult>> result = sendUpdateGroupMessage(group, group.getGroup(), groupChange);
 
-        return new Pair<>(group.groupId, result.second());
+        return new Pair<>(group.getGroupId(), result.second());
     }
 
     private Pair<Long, List<SendMessageResult>> sendUpdateGroupMessage(
@@ -923,13 +923,13 @@ public class Manager implements Closeable {
 
         if (avatarFile != null) {
             IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
-            File aFile = getGroupAvatarFile(g.groupId);
+            File aFile = getGroupAvatarFile(g.getGroupId());
             Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
         }
     }
 
     Pair<Long, List<SendMessageResult>> sendUpdateGroupMessage(
-            byte[] groupId, SignalServiceAddress recipient
+            GroupIdV1 groupId, SignalServiceAddress recipient
     ) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException {
         GroupInfoV1 g;
         GroupInfo group = getGroupForSending(groupId);
@@ -950,11 +950,11 @@ public class Manager implements Closeable {
 
     private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException {
         SignalServiceGroup.Builder group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.UPDATE)
-                .withId(g.groupId)
+                .withId(g.getGroupId().serialize())
                 .withName(g.name)
                 .withMembers(new ArrayList<>(g.getMembers()));
 
-        File aFile = getGroupAvatarFile(g.groupId);
+        File aFile = getGroupAvatarFile(g.getGroupId());
         if (aFile.exists()) {
             try {
                 group.withAvatar(Utils.createAttachment(aFile));
@@ -978,10 +978,10 @@ public class Manager implements Closeable {
     }
 
     Pair<Long, List<SendMessageResult>> sendGroupInfoRequest(
-            byte[] groupId, SignalServiceAddress recipient
+            GroupIdV1 groupId, SignalServiceAddress recipient
     ) throws IOException {
         SignalServiceGroup.Builder group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.REQUEST_INFO)
-                .withId(groupId);
+                .withId(groupId.serialize());
 
         SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
                 .asGroupMessage(group.build());
@@ -1087,7 +1087,7 @@ public class Manager implements Closeable {
         account.save();
     }
 
-    public void setGroupBlocked(final byte[] groupId, final boolean blocked) throws GroupNotFoundException {
+    public void setGroupBlocked(final GroupId groupId, final boolean blocked) throws GroupNotFoundException {
         GroupInfo group = getGroup(groupId);
         if (group == null) {
             throw new GroupNotFoundException(groupId);
@@ -1098,8 +1098,8 @@ public class Manager implements Closeable {
         account.save();
     }
 
-    public Pair<byte[], List<SendMessageResult>> updateGroup(
-            byte[] groupId, String name, List<String> members, String avatar
+    public Pair<GroupId, List<SendMessageResult>> updateGroup(
+            GroupId groupId, String name, List<String> members, String avatar
     ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
         return sendUpdateGroupMessage(groupId,
                 name,
@@ -1137,7 +1137,7 @@ public class Manager implements Closeable {
     /**
      * Change the expiration timer for a group
      */
-    public void setExpirationTimer(byte[] groupId, int messageExpirationTimer) {
+    public void setExpirationTimer(GroupId groupId, int messageExpirationTimer) {
         GroupInfo g = account.getGroupStore().getGroup(groupId);
         if (g instanceof GroupInfoV1) {
             GroupInfoV1 groupInfoV1 = (GroupInfoV1) g;
@@ -1551,20 +1551,21 @@ public class Manager implements Closeable {
         if (message.getGroupContext().isPresent()) {
             if (message.getGroupContext().get().getGroupV1().isPresent()) {
                 SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get();
-                GroupInfo group = account.getGroupStore().getGroupByV1Id(groupInfo.getGroupId());
+                GroupIdV1 groupId = GroupId.v1(groupInfo.getGroupId());
+                GroupInfo group = account.getGroupStore().getGroup(groupId);
                 if (group == null || group instanceof GroupInfoV1) {
                     GroupInfoV1 groupV1 = (GroupInfoV1) group;
                     switch (groupInfo.getType()) {
                         case UPDATE: {
                             if (groupV1 == null) {
-                                groupV1 = new GroupInfoV1(groupInfo.getGroupId());
+                                groupV1 = new GroupInfoV1(groupId);
                             }
 
                             if (groupInfo.getAvatar().isPresent()) {
                                 SignalServiceAttachment avatar = groupInfo.getAvatar().get();
                                 if (avatar.isPointer()) {
                                     try {
-                                        retrieveGroupAvatarAttachment(avatar.asPointer(), groupV1.groupId);
+                                        retrieveGroupAvatarAttachment(avatar.asPointer(), groupV1.getGroupId());
                                     } catch (IOException | InvalidMessageException | MissingConfigurationException e) {
                                         System.err.println("Failed to retrieve group avatar (" + avatar.asPointer()
                                                 .getRemoteId() + "): " + e.getMessage());
@@ -1589,7 +1590,7 @@ public class Manager implements Closeable {
                         }
                         case DELIVER:
                             if (groupV1 == null && !isSync) {
-                                actions.add(new SendGroupInfoRequestAction(source, groupInfo.getGroupId()));
+                                actions.add(new SendGroupInfoRequestAction(source, groupV1.getGroupId()));
                             }
                             break;
                         case QUIT: {
@@ -1601,7 +1602,7 @@ public class Manager implements Closeable {
                         }
                         case REQUEST_INFO:
                             if (groupV1 != null && !isSync) {
-                                actions.add(new SendGroupUpdateAction(source, groupV1.groupId));
+                                actions.add(new SendGroupUpdateAction(source, groupV1.getGroupId()));
                             }
                             break;
                     }
@@ -1627,7 +1628,7 @@ public class Manager implements Closeable {
             if (message.getGroupContext().isPresent()) {
                 if (message.getGroupContext().get().getGroupV1().isPresent()) {
                     SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get();
-                    GroupInfoV1 group = account.getGroupStore().getOrCreateGroupV1(groupInfo.getGroupId());
+                    GroupInfoV1 group = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(groupInfo.getGroupId()));
                     if (group != null) {
                         if (group.messageExpirationTime != message.getExpiresInSeconds()) {
                             group.messageExpirationTime = message.getExpiresInSeconds();
@@ -1723,17 +1724,17 @@ public class Manager implements Closeable {
     ) {
         final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
 
-        byte[] groupId = groupSecretParams.getPublicParams().getGroupIdentifier().serialize();
-        GroupInfo groupInfo = account.getGroupStore().getGroupByV2Id(groupId);
+        GroupIdV2 groupId = GroupUtils.getGroupIdV2(groupSecretParams);
+        GroupInfo groupInfo = account.getGroupStore().getGroup(groupId);
         final GroupInfoV2 groupInfoV2;
         if (groupInfo instanceof GroupInfoV1) {
             // Received a v2 group message for a v1 group, we need to locally migrate the group
-            account.getGroupStore().deleteGroup(groupInfo.groupId);
+            account.getGroupStore().deleteGroup(groupInfo.getGroupId());
             groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey);
             System.err.println("Locally migrated group "
-                    + Base64.encodeBytes(groupInfo.groupId)
+                    + groupInfo.getGroupId().toBase64()
                     + " to group v2, id: "
-                    + Base64.encodeBytes(groupInfoV2.groupId)
+                    + groupInfoV2.getGroupId().toBase64()
                     + " !!!");
         } else if (groupInfo instanceof GroupInfoV2) {
             groupInfoV2 = (GroupInfoV2) groupInfo;
@@ -1970,19 +1971,14 @@ public class Manager implements Closeable {
         if (content != null && content.getDataMessage().isPresent()) {
             SignalServiceDataMessage message = content.getDataMessage().get();
             if (message.getGroupContext().isPresent()) {
-                GroupInfo group = null;
                 if (message.getGroupContext().get().getGroupV1().isPresent()) {
                     SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get();
-                    if (groupInfo.getType() == SignalServiceGroup.Type.DELIVER) {
-                        group = getGroup(groupInfo.getGroupId());
+                    if (groupInfo.getType() != SignalServiceGroup.Type.DELIVER) {
+                        return false;
                     }
                 }
-                if (message.getGroupContext().get().getGroupV2().isPresent()) {
-                    SignalServiceGroupV2 groupContext = message.getGroupContext().get().getGroupV2().get();
-                    final GroupMasterKey groupMasterKey = groupContext.getMasterKey();
-                    byte[] groupId = GroupUtils.getGroupId(groupMasterKey);
-                    group = account.getGroupStore().getGroupByV2Id(groupId);
-                }
+                GroupId groupId = GroupUtils.getGroupId(message.getGroupContext().get());
+                GroupInfo group = account.getGroupStore().getGroup(groupId);
                 if (group != null && group.isBlocked()) {
                     return true;
                 }
@@ -2055,7 +2051,8 @@ public class Manager implements Closeable {
                             DeviceGroupsInputStream s = new DeviceGroupsInputStream(attachmentAsStream);
                             DeviceGroup g;
                             while ((g = s.read()) != null) {
-                                GroupInfoV1 syncGroup = account.getGroupStore().getOrCreateGroupV1(g.getId());
+                                GroupInfoV1 syncGroup = account.getGroupStore()
+                                        .getOrCreateGroupV1(GroupId.v1(g.getId()));
                                 if (syncGroup != null) {
                                     if (g.getName().isPresent()) {
                                         syncGroup.name = g.getName().get();
@@ -2076,7 +2073,7 @@ public class Manager implements Closeable {
                                     }
 
                                     if (g.getAvatar().isPresent()) {
-                                        retrieveGroupAvatarAttachment(g.getAvatar().get(), syncGroup.groupId);
+                                        retrieveGroupAvatarAttachment(g.getAvatar().get(), syncGroup.getGroupId());
                                     }
                                     syncGroup.inboxPosition = g.getInboxPosition().orNull();
                                     syncGroup.archived = g.isArchived();
@@ -2104,12 +2101,15 @@ public class Manager implements Closeable {
                     for (SignalServiceAddress address : blockedListMessage.getAddresses()) {
                         setContactBlocked(resolveSignalServiceAddress(address), true);
                     }
-                    for (byte[] groupId : blockedListMessage.getGroupIds()) {
+                    for (GroupId groupId : blockedListMessage.getGroupIds()
+                            .stream()
+                            .map(GroupId::unknownVersion)
+                            .collect(Collectors.toSet())) {
                         try {
                             setGroupBlocked(groupId, true);
                         } catch (GroupNotFoundException e) {
                             System.err.println("BlockedListMessage contained groupID that was not found in GroupStore: "
-                                    + Base64.encodeBytes(groupId));
+                                    + groupId.toBase64());
                         }
                     }
                 }
@@ -2229,12 +2229,12 @@ public class Manager implements Closeable {
         }
     }
 
-    private File getGroupAvatarFile(byte[] groupId) {
-        return new File(pathConfig.getAvatarsPath(), "group-" + Base64.encodeBytes(groupId).replace("/", "_"));
+    private File getGroupAvatarFile(GroupId groupId) {
+        return new File(pathConfig.getAvatarsPath(), "group-" + groupId.toBase64().replace("/", "_"));
     }
 
     private File retrieveGroupAvatarAttachment(
-            SignalServiceAttachment attachment, byte[] groupId
+            SignalServiceAttachment attachment, GroupId groupId
     ) throws IOException, InvalidMessageException, MissingConfigurationException {
         IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
         if (attachment.isPointer()) {
@@ -2333,10 +2333,10 @@ public class Manager implements Closeable {
                 for (GroupInfo record : account.getGroupStore().getGroups()) {
                     if (record instanceof GroupInfoV1) {
                         GroupInfoV1 groupInfo = (GroupInfoV1) record;
-                        out.write(new DeviceGroup(groupInfo.groupId,
+                        out.write(new DeviceGroup(groupInfo.getGroupId().serialize(),
                                 Optional.fromNullable(groupInfo.name),
                                 new ArrayList<>(groupInfo.getMembers()),
-                                createGroupAvatarAttachment(groupInfo.groupId),
+                                createGroupAvatarAttachment(groupInfo.getGroupId()),
                                 groupInfo.isMember(account.getSelfAddress()),
                                 Optional.of(groupInfo.messageExpirationTime),
                                 Optional.fromNullable(groupInfo.color),
@@ -2442,7 +2442,7 @@ public class Manager implements Closeable {
         List<byte[]> groupIds = new ArrayList<>();
         for (GroupInfo record : account.getGroupStore().getGroups()) {
             if (record.isBlocked()) {
-                groupIds.add(record.groupId);
+                groupIds.add(record.getGroupId().serialize());
             }
         }
         sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds)));
@@ -2466,7 +2466,7 @@ public class Manager implements Closeable {
         return account.getContactStore().getContact(Util.getSignalServiceAddressFromIdentifier(number));
     }
 
-    public GroupInfo getGroup(byte[] groupId) {
+    public GroupInfo getGroup(GroupId groupId) {
         return account.getGroupStore().getGroup(groupId);
     }
 
index 8c0e9be04e887c1ca3fb2331fdbf89970566e071..2c9b3f330db5e2db353b8676fb81a2864a21378c 100644 (file)
@@ -1,10 +1,8 @@
 package org.asamk.signal.manager;
 
-import org.whispersystems.util.Base64;
-
 public class NotAGroupMemberException extends Exception {
 
-    public NotAGroupMemberException(byte[] groupId, String groupName) {
-        super("User is not a member in group: " + groupName + " (" + Base64.encodeBytes(groupId) + ")");
+    public NotAGroupMemberException(GroupId groupId, String groupName) {
+        super("User is not a member in group: " + groupName + " (" + groupId.toBase64() + ")");
     }
 }
index d66831bba0775fed65217319d97377500b2045f1..6fd3500365177c5f0859095d9b10c86595f5bfe2 100644 (file)
@@ -2,7 +2,9 @@ package org.asamk.signal.manager.helper;
 
 import com.google.protobuf.InvalidProtocolBufferException;
 
+import org.asamk.signal.manager.GroupIdV2;
 import org.asamk.signal.manager.GroupLinkPassword;
+import org.asamk.signal.manager.GroupUtils;
 import org.asamk.signal.storage.groups.GroupInfoV2;
 import org.asamk.signal.util.IOUtils;
 import org.signal.storageservice.protos.groups.AccessControl;
@@ -117,7 +119,7 @@ public class GroupHelper {
             return null;
         }
 
-        final byte[] groupId = groupSecretParams.getPublicParams().getGroupIdentifier().serialize();
+        final GroupIdV2 groupId = GroupUtils.getGroupIdV2(groupSecretParams);
         final GroupMasterKey masterKey = groupSecretParams.getMasterKey();
         GroupInfoV2 g = new GroupInfoV2(groupId, masterKey);
         g.setGroup(decryptedGroup);
index 4f9d862858417cc219e1cc8457d5dfd2c9539029..d3145ecd33c250e8f490e0b0b4a12ac4beafc5ef 100644 (file)
@@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 
+import org.asamk.signal.manager.GroupId;
 import org.asamk.signal.storage.contacts.ContactInfo;
 import org.asamk.signal.storage.contacts.JsonContactsStore;
 import org.asamk.signal.storage.groups.GroupInfo;
@@ -309,7 +310,7 @@ public class SignalAccount implements Closeable {
                         contactInfo.messageExpirationTime = thread.messageExpirationTime;
                         contactStore.updateContact(contactInfo);
                     } else {
-                        GroupInfo groupInfo = groupStore.getGroup(Base64.decode(thread.id));
+                        GroupInfo groupInfo = groupStore.getGroup(GroupId.fromBase64(thread.id));
                         if (groupInfo instanceof GroupInfoV1) {
                             ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime;
                             groupStore.updateGroup(groupInfo);
index 3571985b70bcf64c65476ddaa9683d00363baf79..40b8c8847c9204162dc610d3508fddd811f12c1f 100644 (file)
@@ -1,8 +1,8 @@
 package org.asamk.signal.storage.groups;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonProperty;
 
+import org.asamk.signal.manager.GroupId;
 import org.asamk.signal.manager.GroupInviteLinkUrl;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 
@@ -12,12 +12,8 @@ import java.util.stream.Stream;
 
 public abstract class GroupInfo {
 
-    @JsonProperty
-    public final byte[] groupId;
-
-    public GroupInfo(byte[] groupId) {
-        this.groupId = groupId;
-    }
+    @JsonIgnore
+    public abstract GroupId getGroupId();
 
     @JsonIgnore
     public abstract String getTitle();
index b06e2436b7e4a79f313b80b449be1a74aaa6c165..90b26b81c4408a08a4d63ed7bcd62eba178e4535 100644 (file)
@@ -13,7 +13,11 @@ import com.fasterxml.jackson.databind.SerializerProvider;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
+import org.asamk.signal.manager.GroupId;
+import org.asamk.signal.manager.GroupIdV1;
+import org.asamk.signal.manager.GroupIdV2;
 import org.asamk.signal.manager.GroupInviteLinkUrl;
+import org.asamk.signal.manager.GroupUtils;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 
 import java.io.IOException;
@@ -26,8 +30,9 @@ public class GroupInfoV1 extends GroupInfo {
 
     private static final ObjectMapper jsonProcessor = new ObjectMapper();
 
-    @JsonProperty
-    public byte[] expectedV2Id;
+    private final GroupIdV1 groupId;
+
+    private GroupIdV2 expectedV2Id;
 
     @JsonProperty
     public String name;
@@ -47,18 +52,8 @@ public class GroupInfoV1 extends GroupInfo {
     @JsonProperty(defaultValue = "false")
     public boolean archived;
 
-    public GroupInfoV1(byte[] groupId) {
-        super(groupId);
-    }
-
-    @Override
-    public String getTitle() {
-        return name;
-    }
-
-    @Override
-    public GroupInviteLinkUrl getGroupInviteLink() {
-        return null;
+    public GroupInfoV1(GroupIdV1 groupId) {
+        this.groupId = groupId;
     }
 
     public GroupInfoV1(
@@ -74,8 +69,8 @@ public class GroupInfoV1 extends GroupInfo {
             @JsonProperty("messageExpirationTime") int messageExpirationTime,
             @JsonProperty("active") boolean _ignored_active
     ) {
-        super(groupId);
-        this.expectedV2Id = expectedV2Id;
+        this.groupId = GroupId.v1(groupId);
+        this.expectedV2Id = GroupId.v2(expectedV2Id);
         this.name = name;
         this.members.addAll(members);
         this.color = color;
@@ -85,6 +80,40 @@ public class GroupInfoV1 extends GroupInfo {
         this.messageExpirationTime = messageExpirationTime;
     }
 
+    @Override
+    @JsonIgnore
+    public GroupIdV1 getGroupId() {
+        return groupId;
+    }
+
+    @JsonProperty("groupId")
+    private byte[] getGroupIdJackson() {
+        return groupId.serialize();
+    }
+
+    @JsonIgnore
+    public GroupIdV2 getExpectedV2Id() {
+        if (expectedV2Id == null) {
+            expectedV2Id = GroupUtils.getGroupIdV2(groupId);
+        }
+        return expectedV2Id;
+    }
+
+    @JsonProperty("expectedV2Id")
+    private byte[] getExpectedV2IdJackson() {
+        return expectedV2Id.serialize();
+    }
+
+    @Override
+    public String getTitle() {
+        return name;
+    }
+
+    @Override
+    public GroupInviteLinkUrl getGroupInviteLink() {
+        return null;
+    }
+
     @JsonIgnore
     public Set<SignalServiceAddress> getMembers() {
         return members;
index 65f8c9a49ae0f14cfbf6b9fd036dca39bc41843c..1b00caaaa68ee0262965f0ab84a090296f77eafb 100644 (file)
@@ -1,5 +1,6 @@
 package org.asamk.signal.storage.groups;
 
+import org.asamk.signal.manager.GroupIdV2;
 import org.asamk.signal.manager.GroupInviteLinkUrl;
 import org.signal.storageservice.protos.groups.AccessControl;
 import org.signal.storageservice.protos.groups.local.DecryptedGroup;
@@ -13,16 +14,22 @@ import java.util.stream.Collectors;
 
 public class GroupInfoV2 extends GroupInfo {
 
+    private final GroupIdV2 groupId;
     private final GroupMasterKey masterKey;
 
     private boolean blocked;
     private DecryptedGroup group; // stored as a file with hexadecimal groupId as name
 
-    public GroupInfoV2(final byte[] groupId, final GroupMasterKey masterKey) {
-        super(groupId);
+    public GroupInfoV2(final GroupIdV2 groupId, final GroupMasterKey masterKey) {
+        this.groupId = groupId;
         this.masterKey = masterKey;
     }
 
+    @Override
+    public GroupIdV2 getGroupId() {
+        return groupId;
+    }
+
     public GroupMasterKey getMasterKey() {
         return masterKey;
     }
index 2175e293cd1c55f99012ff7299bc7ed1c4b5a663..d6a99f1c278752e9972437ce97720baea240f74b 100644 (file)
@@ -12,6 +12,9 @@ import com.fasterxml.jackson.databind.SerializerProvider;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
+import org.asamk.signal.manager.GroupId;
+import org.asamk.signal.manager.GroupIdV1;
+import org.asamk.signal.manager.GroupIdV2;
 import org.asamk.signal.manager.GroupUtils;
 import org.asamk.signal.util.Hex;
 import org.asamk.signal.util.IOUtils;
@@ -25,7 +28,6 @@ import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -39,7 +41,7 @@ public class JsonGroupStore {
     @JsonProperty("groups")
     @JsonSerialize(using = GroupsSerializer.class)
     @JsonDeserialize(using = GroupsDeserializer.class)
-    private final Map<String, GroupInfo> groups = new HashMap<>();
+    private final Map<GroupId, GroupInfo> groups = new HashMap<>();
 
     private JsonGroupStore() {
     }
@@ -49,11 +51,11 @@ public class JsonGroupStore {
     }
 
     public void updateGroup(GroupInfo group) {
-        groups.put(Base64.encodeBytes(group.groupId), group);
+        groups.put(group.getGroupId(), group);
         if (group instanceof GroupInfoV2 && ((GroupInfoV2) group).getGroup() != null) {
             try {
                 IOUtils.createPrivateDirectories(groupCachePath);
-                try (FileOutputStream stream = new FileOutputStream(getGroupFile(group.groupId))) {
+                try (FileOutputStream stream = new FileOutputStream(getGroupFile(group.getGroupId()))) {
                     ((GroupInfoV2) group).getGroup().writeTo(stream);
                 }
             } catch (IOException e) {
@@ -62,57 +64,50 @@ public class JsonGroupStore {
         }
     }
 
-    public void deleteGroup(byte[] groupId) {
-        groups.remove(Base64.encodeBytes(groupId));
+    public void deleteGroup(GroupId groupId) {
+        groups.remove(groupId);
     }
 
-    public GroupInfo getGroup(byte[] groupId) {
-        final GroupInfo group = groups.get(Base64.encodeBytes(groupId));
-        if (group == null & groupId.length == 16) {
-            return getGroupByV1Id(groupId);
-        }
-        loadDecryptedGroup(group);
-        return group;
-    }
-
-    public GroupInfo getGroupByV1Id(byte[] groupIdV1) {
-        GroupInfo group = groups.get(Base64.encodeBytes(groupIdV1));
+    public GroupInfo getGroup(GroupId groupId) {
+        GroupInfo group = groups.get(groupId);
         if (group == null) {
-            group = groups.get(Base64.encodeBytes(GroupUtils.getGroupId(GroupUtils.deriveV2MigrationMasterKey(groupIdV1))));
+            if (groupId instanceof GroupIdV1) {
+                group = groups.get(GroupUtils.getGroupIdV2((GroupIdV1) groupId));
+            } else if (groupId instanceof GroupIdV2) {
+                group = getGroupV1ByV2Id((GroupIdV2) groupId);
+            }
         }
         loadDecryptedGroup(group);
         return group;
     }
 
-    public GroupInfo getGroupByV2Id(byte[] groupIdV2) {
-        GroupInfo group = groups.get(Base64.encodeBytes(groupIdV2));
-        if (group == null) {
-            for (GroupInfo g : groups.values()) {
-                if (g instanceof GroupInfoV1 && Arrays.equals(groupIdV2, ((GroupInfoV1) g).expectedV2Id)) {
-                    group = g;
-                    break;
+    private GroupInfoV1 getGroupV1ByV2Id(GroupIdV2 groupIdV2) {
+        for (GroupInfo g : groups.values()) {
+            if (g instanceof GroupInfoV1) {
+                final GroupInfoV1 gv1 = (GroupInfoV1) g;
+                if (groupIdV2.equals(gv1.getExpectedV2Id())) {
+                    return gv1;
                 }
             }
         }
-        loadDecryptedGroup(group);
-        return group;
+        return null;
     }
 
     private void loadDecryptedGroup(final GroupInfo group) {
         if (group instanceof GroupInfoV2 && ((GroupInfoV2) group).getGroup() == null) {
-            try (FileInputStream stream = new FileInputStream(getGroupFile(group.groupId))) {
+            try (FileInputStream stream = new FileInputStream(getGroupFile(group.getGroupId()))) {
                 ((GroupInfoV2) group).setGroup(DecryptedGroup.parseFrom(stream));
             } catch (IOException ignored) {
             }
         }
     }
 
-    private File getGroupFile(final byte[] groupId) {
-        return new File(groupCachePath, Hex.toStringCondensed(groupId));
+    private File getGroupFile(final GroupId groupId) {
+        return new File(groupCachePath, Hex.toStringCondensed(groupId.serialize()));
     }
 
-    public GroupInfoV1 getOrCreateGroupV1(byte[] groupId) {
-        GroupInfo group = groups.get(Base64.encodeBytes(groupId));
+    public GroupInfoV1 getOrCreateGroupV1(GroupIdV1 groupId) {
+        GroupInfo group = getGroup(groupId);
         if (group instanceof GroupInfoV1) {
             return (GroupInfoV1) group;
         }
@@ -146,7 +141,7 @@ public class JsonGroupStore {
                 } else if (group instanceof GroupInfoV2) {
                     final GroupInfoV2 groupV2 = (GroupInfoV2) group;
                     jgen.writeStartObject();
-                    jgen.writeStringField("groupId", Base64.encodeBytes(groupV2.groupId));
+                    jgen.writeStringField("groupId", groupV2.getGroupId().toBase64());
                     jgen.writeStringField("masterKey", Base64.encodeBytes(groupV2.getMasterKey().serialize()));
                     jgen.writeBooleanField("blocked", groupV2.isBlocked());
                     jgen.writeEndObject();
@@ -158,34 +153,31 @@ public class JsonGroupStore {
         }
     }
 
-    private static class GroupsDeserializer extends JsonDeserializer<Map<String, GroupInfo>> {
+    private static class GroupsDeserializer extends JsonDeserializer<Map<GroupId, GroupInfo>> {
 
         @Override
-        public Map<String, GroupInfo> deserialize(
+        public Map<GroupId, GroupInfo> deserialize(
                 JsonParser jsonParser, DeserializationContext deserializationContext
         ) throws IOException {
-            Map<String, GroupInfo> groups = new HashMap<>();
+            Map<GroupId, GroupInfo> groups = new HashMap<>();
             JsonNode node = jsonParser.getCodec().readTree(jsonParser);
             for (JsonNode n : node) {
                 GroupInfo g;
                 if (n.has("masterKey")) {
                     // a v2 group
-                    byte[] groupId = Base64.decode(n.get("groupId").asText());
+                    GroupIdV2 groupId = GroupIdV2.fromBase64(n.get("groupId").asText());
                     try {
                         GroupMasterKey masterKey = new GroupMasterKey(Base64.decode(n.get("masterKey").asText()));
                         g = new GroupInfoV2(groupId, masterKey);
                     } catch (InvalidInputException e) {
-                        throw new AssertionError("Invalid master key for group " + Base64.encodeBytes(groupId));
+                        throw new AssertionError("Invalid master key for group " + groupId.toBase64());
                     }
                     g.setBlocked(n.get("blocked").asBoolean(false));
                 } else {
                     GroupInfoV1 gv1 = jsonProcessor.treeToValue(n, GroupInfoV1.class);
-                    if (gv1.expectedV2Id == null) {
-                        gv1.expectedV2Id = GroupUtils.getGroupId(GroupUtils.deriveV2MigrationMasterKey(gv1.groupId));
-                    }
                     g = gv1;
                 }
-                groups.put(Base64.encodeBytes(g.groupId), g);
+                groups.put(g.getGroupId(), g);
             }
 
             return groups;
index 8e65d440e894e8e6eb27279a789c8a706af3f0d3..44d505bee56c4a01b0eb6968c6c02ecb4e32a205 100644 (file)
@@ -1,5 +1,6 @@
 package org.asamk.signal.util;
 
+import org.asamk.signal.manager.GroupIdFormatException;
 import org.asamk.signal.manager.GroupNotFoundException;
 import org.asamk.signal.manager.NotAGroupMemberException;
 import org.whispersystems.signalservice.api.messages.SendMessageResult;
diff --git a/src/main/java/org/asamk/signal/util/GroupIdFormatException.java b/src/main/java/org/asamk/signal/util/GroupIdFormatException.java
deleted file mode 100644 (file)
index 5a5c457..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.asamk.signal.util;
-
-import java.io.IOException;
-
-public class GroupIdFormatException extends Exception {
-
-    public GroupIdFormatException(String groupId, IOException e) {
-        super("Failed to decode groupId (must be base64) \"" + groupId + "\": " + e.getMessage());
-    }
-}
index bc2d3377cef892d2e082e3014c123372f23fe98f..3cd5619a382904b170f576c649ec6b1199ed0b5c 100644 (file)
@@ -2,13 +2,13 @@ package org.asamk.signal.util;
 
 import com.fasterxml.jackson.databind.JsonNode;
 
+import org.asamk.signal.manager.GroupId;
+import org.asamk.signal.manager.GroupIdFormatException;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
 import org.whispersystems.signalservice.api.util.UuidUtil;
-import org.whispersystems.util.Base64;
 
-import java.io.IOException;
 import java.io.InvalidObjectException;
 
 public class Util {
@@ -48,12 +48,8 @@ public class Util {
         return node;
     }
 
-    public static byte[] decodeGroupId(String groupId) throws GroupIdFormatException {
-        try {
-            return Base64.decode(groupId);
-        } catch (IOException e) {
-            throw new GroupIdFormatException(groupId, e);
-        }
+    public static GroupId decodeGroupId(String groupId) throws GroupIdFormatException {
+        return GroupId.fromBase64(groupId);
     }
 
     public static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException {