Signal-Android/app/src/main/java/org/thoughtcrime/securesms/mms/MessageGroupContext.java

196 lines
6.4 KiB
Java

package org.thoughtcrime.securesms.mms;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.groups.GroupMasterKey;
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Base64;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
/**
* Represents either a GroupV1 or GroupV2 encoded context.
*/
public final class MessageGroupContext {
@NonNull private final String encodedGroupContext;
@NonNull private final GroupProperties group;
@Nullable private final GroupV1Properties groupV1;
@Nullable private final GroupV2Properties groupV2;
public MessageGroupContext(@NonNull String encodedGroupContext, boolean v2)
throws IOException
{
this.encodedGroupContext = encodedGroupContext;
if (v2) {
this.groupV1 = null;
this.groupV2 = new GroupV2Properties(DecryptedGroupV2Context.parseFrom(Base64.decode(encodedGroupContext)));
this.group = groupV2;
} else {
this.groupV1 = new GroupV1Properties(GroupContext.parseFrom(Base64.decode(encodedGroupContext)));
this.groupV2 = null;
this.group = groupV1;
}
}
public MessageGroupContext(@NonNull GroupContext group) {
this.encodedGroupContext = Base64.encodeBytes(group.toByteArray());
this.groupV1 = new GroupV1Properties(group);
this.groupV2 = null;
this.group = groupV1;
}
public MessageGroupContext(@NonNull DecryptedGroupV2Context group) {
this.encodedGroupContext = Base64.encodeBytes(group.toByteArray());
this.groupV1 = null;
this.groupV2 = new GroupV2Properties(group);
this.group = groupV2;
}
public @NonNull GroupV1Properties requireGroupV1Properties() {
if (groupV1 == null) {
throw new AssertionError();
}
return groupV1;
}
public @NonNull GroupV2Properties requireGroupV2Properties() {
if (groupV2 == null) {
throw new AssertionError();
}
return groupV2;
}
public boolean isV2Group() {
return groupV2 != null;
}
public @NonNull String getEncodedGroupContext() {
return encodedGroupContext;
}
public String getName() {
return group.getName();
}
public List<RecipientId> getMembersListExcludingSelf() {
return group.getMembersListExcludingSelf();
}
interface GroupProperties {
@NonNull String getName();
@NonNull List<RecipientId> getMembersListExcludingSelf();
}
public static class GroupV1Properties implements GroupProperties {
private final GroupContext groupContext;
private GroupV1Properties(GroupContext groupContext) {
this.groupContext = groupContext;
}
public @NonNull GroupContext getGroupContext() {
return groupContext;
}
public boolean isQuit() {
return groupContext.getType().getNumber() == GroupContext.Type.QUIT_VALUE;
}
public boolean isUpdate() {
return groupContext.getType().getNumber() == GroupContext.Type.UPDATE_VALUE;
}
@Override
public @NonNull String getName() {
return groupContext.getName();
}
@Override
public @NonNull List<RecipientId> getMembersListExcludingSelf() {
RecipientId selfId = Recipient.self().getId();
return Stream.of(groupContext.getMembersList())
.map(GroupContext.Member::getE164)
.withoutNulls()
.map(e164 -> new SignalServiceAddress(null, e164))
.map(RecipientId::from)
.filterNot(selfId::equals)
.toList();
}
}
public static class GroupV2Properties implements GroupProperties {
private final DecryptedGroupV2Context decryptedGroupV2Context;
private final GroupContextV2 groupContext;
private final GroupMasterKey groupMasterKey;
private GroupV2Properties(DecryptedGroupV2Context decryptedGroupV2Context) {
this.decryptedGroupV2Context = decryptedGroupV2Context;
this.groupContext = decryptedGroupV2Context.getContext();
try {
groupMasterKey = new GroupMasterKey(groupContext.getMasterKey().toByteArray());
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public @NonNull GroupContextV2 getGroupContext() {
return groupContext;
}
public @NonNull GroupMasterKey getGroupMasterKey() {
return groupMasterKey;
}
public @NonNull List<UUID> getAllActivePendingAndRemovedMembers() {
LinkedList<UUID> memberUuids = new LinkedList<>();
memberUuids.addAll(DecryptedGroupUtil.membersToUuidList(decryptedGroupV2Context.getGroupState().getMembersList()));
memberUuids.addAll(DecryptedGroupUtil.pendingToUuidList(decryptedGroupV2Context.getGroupState().getPendingMembersList()));
memberUuids.addAll(DecryptedGroupUtil.removedMembersUuidList(decryptedGroupV2Context.getChange()));
memberUuids.addAll(DecryptedGroupUtil.removedPendingMembersUuidList(decryptedGroupV2Context.getChange()));
return UuidUtil.filterKnown(memberUuids);
}
@Override
public @NonNull String getName() {
return decryptedGroupV2Context.getGroupState().getTitle();
}
@Override
public @NonNull List<RecipientId> getMembersListExcludingSelf() {
List<RecipientId> members = new ArrayList<>(decryptedGroupV2Context.getGroupState().getMembersCount());
for (DecryptedMember member : decryptedGroupV2Context.getGroupState().getMembersList()) {
RecipientId recipient = RecipientId.from(UuidUtil.fromByteString(member.getUuid()), null);
if (!Recipient.self().getId().equals(recipient)) {
members.add(recipient);
}
}
return members;
}
}
}