} 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
.getGroupContext()
.isPresent()) {
SignalServiceDataMessage message = transcript.getMessage();
- byte[] groupId = getGroupId(m, message);
+ byte[] groupId = getGroupId(message);
try {
conn.sendMessage(new Signal.SyncMessageReceived(objectPath,
}
}
- 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) {
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;
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 {
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 {
}
} 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());
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;
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());
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;
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;
}
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) {
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;
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(),
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()));
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;
}
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) {
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;
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;
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;
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,
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;
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());
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;
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;
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;
@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) {
@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());
}
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 {
@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 {
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) {
}
JsonGroupInfo(SignalServiceGroupV2 groupInfo) {
- this.groupId = Base64.encodeBytes(GroupUtils.getGroupId(groupInfo.getMasterKey()));
+ this.groupId = GroupUtils.getGroupIdV2(groupInfo.getMasterKey()).toBase64();
this.type = groupInfo.hasSignedGroupChange() ? "UPDATE" : "DELIVER";
}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
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());
}
}
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 {
) {
if (groupInfo instanceof GroupInfoV1) {
SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.DELIVER)
- .withId(groupInfo.groupId)
+ .withId(groupInfo.getGroupId().serialize())
.build();
messageBuilder.asGroupMessage(group);
} else {
}
}
- 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) {
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import java.util.Arrays;
import java.util.Objects;
interface 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;
}
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;
}
}
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;
}
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;
}
}
return getSecret(18);
}
- static byte[] createGroupId() {
- return getSecretBytes(16);
- }
-
static byte[] createStickerUploadKey() {
return getSecretBytes(32);
}
}
}
- 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();
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);
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);
}
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);
}
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);
}
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,
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;
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());
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;
// 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);
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);
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(),
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(
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);
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));
}
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());
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);
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,
/**
* 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;
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());
}
case DELIVER:
if (groupV1 == null && !isSync) {
- actions.add(new SendGroupInfoRequestAction(source, groupInfo.getGroupId()));
+ actions.add(new SendGroupInfoRequestAction(source, groupV1.getGroupId()));
}
break;
case QUIT: {
}
case REQUEST_INFO:
if (groupV1 != null && !isSync) {
- actions.add(new SendGroupUpdateAction(source, groupV1.groupId));
+ actions.add(new SendGroupUpdateAction(source, groupV1.getGroupId()));
}
break;
}
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();
) {
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;
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;
}
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();
}
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();
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());
}
}
}
}
}
- 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()) {
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),
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)));
return account.getContactStore().getContact(Util.getSignalServiceAddressFromIdentifier(number));
}
- public GroupInfo getGroup(byte[] groupId) {
+ public GroupInfo getGroup(GroupId groupId) {
return account.getGroupStore().getGroup(groupId);
}
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() + ")");
}
}
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;
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);
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;
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);
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;
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();
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;
private static final ObjectMapper jsonProcessor = new ObjectMapper();
- @JsonProperty
- public byte[] expectedV2Id;
+ private final GroupIdV1 groupId;
+
+ private GroupIdV2 expectedV2Id;
@JsonProperty
public String name;
@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(
@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;
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;
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;
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;
}
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;
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;
@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() {
}
}
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) {
}
}
- 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;
}
} 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();
}
}
- 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;
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;
+++ /dev/null
-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());
- }
-}
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 {
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 {