mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-02 08:23:00 +01:00
Add send and receive support for group member labels.
This commit is contained in:
committed by
Greyson Parrelli
parent
ce46c44b5d
commit
0a572153f0
@@ -46,4 +46,6 @@ public interface ChangeSetModifier {
|
||||
void removeDeleteBannedMembers(int i);
|
||||
|
||||
void removePromotePendingPniAciMembers(int i);
|
||||
|
||||
void removeModifyMemberLabels(int i);
|
||||
}
|
||||
|
||||
@@ -110,6 +110,10 @@ internal class DecryptedGroupChangeActionsBuilderChangeSetModifier(private val r
|
||||
result.promotePendingPniAciMembers = result.promotePendingPniAciMembers.removeIndex(i)
|
||||
}
|
||||
|
||||
override fun removeModifyMemberLabels(i: Int) {
|
||||
result.modifyMemberLabel = result.modifyMemberLabel.removeIndex(i)
|
||||
}
|
||||
|
||||
private fun <T> List<T>.removeIndex(i: Int): List<T> {
|
||||
val modifiedList = this.toMutableList()
|
||||
modifiedList.removeAt(i)
|
||||
|
||||
@@ -7,7 +7,9 @@ package org.whispersystems.signalservice.api.groupsv2
|
||||
|
||||
import org.signal.core.models.ServiceId
|
||||
import org.signal.core.models.ServiceId.ACI
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberLabel
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMember
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedRequestingMember
|
||||
import java.util.Optional
|
||||
@@ -31,3 +33,24 @@ fun Collection<DecryptedRequestingMember>.findRequestingByAci(aci: ACI): Optiona
|
||||
fun Collection<DecryptedPendingMember>.findPendingByServiceId(serviceId: ServiceId): Optional<DecryptedPendingMember> {
|
||||
return DecryptedGroupUtil.findPendingByServiceId(this, serviceId)
|
||||
}
|
||||
|
||||
@Throws(NotAbleToApplyGroupV2ChangeException::class)
|
||||
fun DecryptedGroup.Builder.setModifyMemberLabelActions(
|
||||
actions: List<DecryptedModifyMemberLabel>
|
||||
) {
|
||||
val updatedMembers = members.toMutableList()
|
||||
actions.forEach { action ->
|
||||
val modifiedMemberIndex = updatedMembers.indexOfFirst { it.aciBytes == action.aciBytes }
|
||||
if (modifiedMemberIndex < 0) {
|
||||
throw NotAbleToApplyGroupV2ChangeException()
|
||||
}
|
||||
|
||||
updatedMembers[modifiedMemberIndex] = updatedMembers[modifiedMemberIndex]
|
||||
.newBuilder()
|
||||
.labelEmoji(action.labelEmoji)
|
||||
.labelString(action.labelString)
|
||||
.build()
|
||||
}
|
||||
|
||||
members = updatedMembers
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedBannedMemb
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberLabel;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberRole;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMemberRemoval;
|
||||
@@ -26,6 +27,8 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
public final class DecryptedGroupUtil {
|
||||
@@ -159,7 +162,7 @@ public final class DecryptedGroupUtil {
|
||||
return Optional.ofNullable(change != null ? ServiceId.parseOrNull(change.editorServiceIdBytes) : null);
|
||||
}
|
||||
|
||||
public static Optional<DecryptedMember> findMemberByAci(Collection<DecryptedMember> members, ACI aci) {
|
||||
public static Optional<DecryptedMember> findMemberByAci(@Nonnull Collection<DecryptedMember> members, @Nonnull ACI aci) {
|
||||
ByteString aciBytes = aci.toByteString();
|
||||
|
||||
for (DecryptedMember member : members) {
|
||||
@@ -335,6 +338,8 @@ public final class DecryptedGroupUtil {
|
||||
|
||||
applyPromotePendingPniAciMemberActions(builder, change.promotePendingPniAciMembers);
|
||||
|
||||
DecryptedGroupExtensionsKt.setModifyMemberLabelActions(builder, change.modifyMemberLabel);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -747,7 +752,8 @@ public final class DecryptedGroupUtil {
|
||||
isEmpty(change.newIsAnnouncementGroup) && // field 21
|
||||
change.newBannedMembers.size() == 0 && // field 22
|
||||
change.deleteBannedMembers.size() == 0 && // field 23
|
||||
change.promotePendingPniAciMembers.size() == 0; // field 24
|
||||
change.promotePendingPniAciMembers.size() == 0 && // field 24
|
||||
change.modifyMemberLabel.isEmpty(); // field 26
|
||||
}
|
||||
|
||||
public static boolean changeIsEmptyExceptForBanChangesAndOptionalProfileKeyChanges(DecryptedGroupChange change) {
|
||||
@@ -770,7 +776,8 @@ public final class DecryptedGroupUtil {
|
||||
change.newInviteLinkPassword.size() == 0 && // field 19
|
||||
change.newDescription == null && // field 20
|
||||
isEmpty(change.newIsAnnouncementGroup) && // field 21
|
||||
change.promotePendingPniAciMembers.size() == 0; // field 24
|
||||
change.promotePendingPniAciMembers.size() == 0 && // field 24
|
||||
change.modifyMemberLabel.isEmpty(); // field 26
|
||||
}
|
||||
|
||||
static boolean isEmpty(AccessControl.AccessRequired newAttributeAccess) {
|
||||
|
||||
@@ -101,6 +101,10 @@ internal class GroupChangeActionsBuilderChangeSetModifier(private val result: Gr
|
||||
result.promote_members_pending_pni_aci_profile_key = result.promote_members_pending_pni_aci_profile_key.removeIndex(i)
|
||||
}
|
||||
|
||||
override fun removeModifyMemberLabels(i: Int) {
|
||||
result.modifyMemberLabel = result.modifyMemberLabel.removeIndex(i)
|
||||
}
|
||||
|
||||
private fun <T> List<T>.removeIndex(i: Int): List<T> {
|
||||
val modifiedList = this.toMutableList()
|
||||
modifiedList.removeAt(i)
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedBannedMemb
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberLabel;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberRole;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMemberRemoval;
|
||||
@@ -116,8 +117,9 @@ public final class GroupChangeReconstruct {
|
||||
Map<ByteString, DecryptedMember> membersAciMap = mapByAci(fromState.members);
|
||||
Map<ByteString, DecryptedBannedMember> bannedMembersServiceIdMap = bannedServiceIdMap(toState.bannedMembers);
|
||||
|
||||
List<DecryptedModifyMemberRole> modifiedMemberRoles = new ArrayList<>(changedMembers.size());
|
||||
List<DecryptedMember> modifiedProfileKeys = new ArrayList<>(changedMembers.size());
|
||||
List<DecryptedModifyMemberRole> modifiedMemberRoles = new ArrayList<>(changedMembers.size());
|
||||
List<DecryptedMember> modifiedProfileKeys = new ArrayList<>(changedMembers.size());
|
||||
List<DecryptedModifyMemberLabel> modifiedMemberLabels = new ArrayList<>(changedMembers.size());
|
||||
for (DecryptedMember newState : changedMembers) {
|
||||
DecryptedMember oldState = membersAciMap.get(newState.aciBytes);
|
||||
if (oldState.role != newState.role) {
|
||||
@@ -130,9 +132,18 @@ public final class GroupChangeReconstruct {
|
||||
if (!oldState.profileKey.equals(newState.profileKey)) {
|
||||
modifiedProfileKeys.add(newState);
|
||||
}
|
||||
|
||||
if (!oldState.labelEmoji.equals(newState.labelEmoji) || !oldState.labelString.equals(newState.labelString)) {
|
||||
modifiedMemberLabels.add(new DecryptedModifyMemberLabel.Builder()
|
||||
.aciBytes(newState.aciBytes)
|
||||
.labelEmoji(newState.labelEmoji)
|
||||
.labelString(newState.labelString)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
builder.modifyMemberRoles(modifiedMemberRoles);
|
||||
builder.modifiedProfileKeys(modifiedProfileKeys);
|
||||
builder.modifyMemberLabel(modifiedMemberLabels);
|
||||
|
||||
if (fromState.accessControl == null || (toState.accessControl != null && !fromState.accessControl.addFromInviteLink.equals(toState.accessControl.addFromInviteLink))) {
|
||||
if (toState.accessControl != null) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedBannedMemb
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberLabel;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberRole;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMemberRemoval;
|
||||
@@ -13,6 +14,9 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedRequesting
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
@@ -46,7 +50,8 @@ public final class GroupChangeUtil {
|
||||
change.modify_announcements_only == null && // field 21
|
||||
change.add_members_banned.size() == 0 && // field 22
|
||||
change.delete_members_banned.size() == 0 && // field 23
|
||||
change.promote_members_pending_pni_aci_profile_key.size() == 0; // field 24
|
||||
change.promote_members_pending_pni_aci_profile_key.size() == 0 && // field 24
|
||||
change.modifyMemberLabel.isEmpty(); // field 26
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,6 +154,7 @@ public final class GroupChangeUtil {
|
||||
resolveField22AddBannedMembers (conflictingChange, changeSetModifier, bannedMembersByServiceId);
|
||||
resolveField23DeleteBannedMembers (conflictingChange, changeSetModifier, bannedMembersByServiceId);
|
||||
resolveField24PromotePendingPniAciMembers (conflictingChange, changeSetModifier, fullMembersByUuid);
|
||||
resolveField26ModifyMemberLabels (conflictingChange, changeSetModifier, fullMembersByUuid);
|
||||
}
|
||||
|
||||
private static void resolveField3AddMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByServiceId) {
|
||||
@@ -366,4 +372,22 @@ public final class GroupChangeUtil {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void resolveField26ModifyMemberLabels(
|
||||
@Nonnull DecryptedGroupChange conflictingChange,
|
||||
@Nonnull ChangeSetModifier result,
|
||||
@Nonnull Map<ByteString, DecryptedMember> fullMembersByAci
|
||||
)
|
||||
{
|
||||
List<DecryptedModifyMemberLabel> actions = conflictingChange.modifyMemberLabel;
|
||||
|
||||
for (int i = actions.size() - 1; i >= 0; i--) {
|
||||
DecryptedModifyMemberLabel action = actions.get(i);
|
||||
DecryptedMember member = fullMembersByAci.get(action.aciBytes);
|
||||
|
||||
if (member == null || (action.labelEmoji.equals(member.labelEmoji) && action.labelString.equals(member.labelString))) {
|
||||
result.removeModifyMemberLabels(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupJoinInfo;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberLabel;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberRole;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMemberRemoval;
|
||||
@@ -44,6 +45,7 @@ import org.signal.core.models.ServiceId.PNI;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -55,6 +57,7 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -72,7 +75,7 @@ public final class GroupsV2Operations {
|
||||
public static final UUID UNKNOWN_UUID = UuidUtil.UNKNOWN_UUID;
|
||||
|
||||
/** Highest change epoch this class knows now to decrypt */
|
||||
public static final int HIGHEST_KNOWN_EPOCH = 5;
|
||||
public static final int HIGHEST_KNOWN_EPOCH = 6;
|
||||
|
||||
private final ServerPublicParams serverPublicParams;
|
||||
private final ClientZkProfileOperations clientZkProfileOperations;
|
||||
@@ -754,6 +757,19 @@ public final class GroupsV2Operations {
|
||||
}
|
||||
builder.promotePendingPniAciMembers(promotePendingPniAciMembers);
|
||||
|
||||
// Field 26
|
||||
List<DecryptedModifyMemberLabel> modifyMemberLabels = new ArrayList<>(actions.modifyMemberLabel.size());
|
||||
for (GroupChange.Actions.ModifyMemberLabelAction action : actions.modifyMemberLabel) {
|
||||
modifyMemberLabels.add(
|
||||
new DecryptedModifyMemberLabel.Builder()
|
||||
.aciBytes(decryptAciToBinary(action.userId))
|
||||
.labelEmoji(Objects.requireNonNullElse(decryptString(action.labelEmoji), ""))
|
||||
.labelString(Objects.requireNonNullElse(decryptString(action.labelString), ""))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
builder.modifyMemberLabel(modifyMemberLabels);
|
||||
|
||||
if (editorServiceId instanceof ServiceId.PNI) {
|
||||
if (actions.addMembers.size() == 1 && builder.newMembers.size() == 1) {
|
||||
GroupChange.Actions.AddMemberAction addMemberAction = actions.addMembers.get(0);
|
||||
@@ -790,14 +806,19 @@ public final class GroupsV2Operations {
|
||||
private DecryptedMember.Builder decryptMember(Member member)
|
||||
throws InvalidGroupStateException, VerificationFailedException, InvalidInputException
|
||||
{
|
||||
String labelEmoji = Objects.requireNonNullElse(decryptString(member.labelEmoji), "");
|
||||
String labelString = Objects.requireNonNullElse(decryptString(member.labelString), "");
|
||||
|
||||
if (member.presentation.size() == 0) {
|
||||
ACI aci = decryptAci(member.userId);
|
||||
|
||||
return new DecryptedMember.Builder()
|
||||
.aciBytes(aci.toByteString())
|
||||
.joinedAtRevision(member.joinedAtVersion)
|
||||
.profileKey(decryptProfileKeyToByteString(member.profileKey, aci))
|
||||
.role(member.role);
|
||||
.aciBytes(aci.toByteString())
|
||||
.joinedAtRevision(member.joinedAtVersion)
|
||||
.profileKey(decryptProfileKeyToByteString(member.profileKey, aci))
|
||||
.role(member.role)
|
||||
.labelEmoji(labelEmoji)
|
||||
.labelString(labelString);
|
||||
} else {
|
||||
ProfileKeyCredentialPresentation profileKeyCredentialPresentation = new ProfileKeyCredentialPresentation(member.presentation.toByteArray());
|
||||
|
||||
@@ -810,10 +831,12 @@ public final class GroupsV2Operations {
|
||||
ProfileKey profileKey = clientZkGroupCipher.decryptProfileKey(profileKeyCredentialPresentation.getProfileKeyCiphertext(), aci.getLibSignalAci());
|
||||
|
||||
return new DecryptedMember.Builder()
|
||||
.aciBytes(aci.toByteString())
|
||||
.joinedAtRevision(member.joinedAtVersion)
|
||||
.profileKey(ByteString.of(profileKey.serialize()))
|
||||
.role(member.role);
|
||||
.aciBytes(aci.toByteString())
|
||||
.joinedAtRevision(member.joinedAtVersion)
|
||||
.profileKey(ByteString.of(profileKey.serialize()))
|
||||
.role(member.role)
|
||||
.labelEmoji(labelEmoji)
|
||||
.labelString(labelString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1010,6 +1033,34 @@ public final class GroupsV2Operations {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a string as raw UTF-8 bytes for member-specific attributes.
|
||||
*/
|
||||
private ByteString encryptString(@Nullable String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return ByteString.EMPTY;
|
||||
}
|
||||
|
||||
try {
|
||||
return ByteString.of(clientZkGroupCipher.encryptBlob(value.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (VerificationFailedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a string from raw UTF-8 bytes for member-specific attributes.
|
||||
*/
|
||||
@Nullable
|
||||
private String decryptString(@Nullable ByteString cipherText) throws VerificationFailedException {
|
||||
if (cipherText == null || cipherText.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] decryptedBytes = clientZkGroupCipher.decryptBlob(cipherText.toByteArray());
|
||||
return new String(decryptedBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies signature and parses actions on a group change.
|
||||
*/
|
||||
@@ -1046,6 +1097,18 @@ public final class GroupsV2Operations {
|
||||
));
|
||||
}
|
||||
|
||||
public GroupChange.Actions.Builder createChangeMemberLabel(@Nonnull ACI memberAci, @Nonnull String labelString, @Nullable String labelEmoji) {
|
||||
return new GroupChange.Actions.Builder().modifyMemberLabel(
|
||||
Collections.singletonList(
|
||||
new GroupChange.Actions.ModifyMemberLabelAction.Builder()
|
||||
.userId(encryptServiceId(memberAci))
|
||||
.labelEmoji(encryptString(labelEmoji))
|
||||
.labelString(encryptString(labelString))
|
||||
.build()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public List<ServiceId> decryptAddMembers(List<GroupChange.Actions.AddMemberAction> addMembers) throws InvalidGroupStateException, InvalidInputException, VerificationFailedException {
|
||||
List<ServiceId> ids = new ArrayList<>(addMembers.size());
|
||||
for (GroupChange.Actions.AddMemberAction addMember : addMembers) {
|
||||
|
||||
@@ -20,6 +20,8 @@ message DecryptedMember {
|
||||
bytes profileKey = 3;
|
||||
uint32 joinedAtRevision = 5;
|
||||
bytes pniBytes = 6;
|
||||
string labelEmoji = 7;
|
||||
string labelString = 8;
|
||||
}
|
||||
|
||||
message DecryptedPendingMember {
|
||||
@@ -56,6 +58,12 @@ message DecryptedModifyMemberRole {
|
||||
Member.Role role = 2;
|
||||
}
|
||||
|
||||
message DecryptedModifyMemberLabel {
|
||||
bytes aciBytes = 1;
|
||||
string labelEmoji = 2;
|
||||
string labelString = 3;
|
||||
}
|
||||
|
||||
// Decrypted version of message Group
|
||||
// Keep field numbers in step
|
||||
message DecryptedGroup {
|
||||
@@ -102,6 +110,7 @@ message DecryptedGroupChange {
|
||||
repeated DecryptedBannedMember newBannedMembers = 22;
|
||||
repeated DecryptedBannedMember deleteBannedMembers = 23;
|
||||
repeated DecryptedMember promotePendingPniAciMembers = 24;
|
||||
repeated DecryptedModifyMemberLabel modifyMemberLabel = 26;
|
||||
}
|
||||
|
||||
message DecryptedString {
|
||||
@@ -128,4 +137,3 @@ enum EnabledState {
|
||||
ENABLED = 1;
|
||||
DISABLED = 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ message Member {
|
||||
bytes profileKey = 3;
|
||||
bytes presentation = 4;
|
||||
uint32 joinedAtVersion = 5;
|
||||
bytes labelEmoji = 6; // decrypts to a UTF-8 string
|
||||
bytes labelString = 7; // decrypts to a UTF-8 string
|
||||
}
|
||||
|
||||
message MemberPendingProfileKey {
|
||||
@@ -141,6 +143,12 @@ message GroupChange {
|
||||
Member.Role role = 2;
|
||||
}
|
||||
|
||||
message ModifyMemberLabelAction {
|
||||
bytes userId = 1;
|
||||
bytes labelEmoji = 2; // decrypts to a UTF-8 string
|
||||
bytes labelString = 3; // decrypts to a UTF-8 string
|
||||
}
|
||||
|
||||
message ModifyMemberProfileKeyAction {
|
||||
bytes presentation = 1;
|
||||
bytes user_id = 2;
|
||||
@@ -253,7 +261,8 @@ message GroupChange {
|
||||
repeated AddMemberBannedAction add_members_banned = 22; // change epoch = 4
|
||||
repeated DeleteMemberBannedAction delete_members_banned = 23; // change epoch = 4
|
||||
repeated PromoteMemberPendingPniAciProfileKeyAction promote_members_pending_pni_aci_profile_key = 24; // change epoch = 5
|
||||
// next: 26
|
||||
repeated ModifyMemberLabelAction modifyMemberLabel = 26; // change epoch = 6;
|
||||
// next: 27
|
||||
}
|
||||
|
||||
bytes actions = 1;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.whispersystems.signalservice.api.groupsv2;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.storageservice.storage.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.storage.protos.groups.Member;
|
||||
@@ -9,6 +10,7 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedBannedMemb
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberLabel;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberRole;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMemberRemoval;
|
||||
@@ -16,7 +18,6 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedRequesting
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedString;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedTimer;
|
||||
import org.signal.storageservice.storage.protos.groups.local.EnabledState;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.util.List;
|
||||
@@ -38,8 +39,8 @@ import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.reque
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.withProfileKey;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
|
||||
|
||||
@SuppressWarnings("NewClassNamingConvention")
|
||||
public final class DecryptedGroupUtil_apply_Test {
|
||||
|
||||
/**
|
||||
* Reflects over the generated protobuf class and ensures that no new fields have been added since we wrote this.
|
||||
* <p>
|
||||
@@ -50,7 +51,7 @@ public final class DecryptedGroupUtil_apply_Test {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
|
||||
|
||||
assertEquals("DecryptedGroupUtil and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
|
||||
24, maxFieldFound);
|
||||
26, maxFieldFound);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -956,4 +957,99 @@ public final class DecryptedGroupUtil_apply_Test {
|
||||
.build(),
|
||||
newGroup);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void apply_modify_member_label() throws NotAbleToApplyGroupV2ChangeException {
|
||||
UUID memberUuid = UUID.fromString("d1d1d1d1-0000-4000-8000-000000000001");
|
||||
DecryptedMember existingMember = member(memberUuid);
|
||||
|
||||
DecryptedModifyMemberLabel modifyLabelAction = new DecryptedModifyMemberLabel.Builder()
|
||||
.aciBytes(UuidUtil.toByteString(memberUuid))
|
||||
.labelEmoji("🎉")
|
||||
.labelString("Test Label")
|
||||
.build();
|
||||
|
||||
DecryptedGroup actualResult = DecryptedGroupUtil.apply(
|
||||
new DecryptedGroup.Builder()
|
||||
.revision(10)
|
||||
.members(List.of(existingMember))
|
||||
.build(),
|
||||
|
||||
new DecryptedGroupChange.Builder()
|
||||
.revision(11)
|
||||
.modifyMemberLabel(List.of(modifyLabelAction))
|
||||
.build()
|
||||
);
|
||||
|
||||
List<DecryptedMember> expectedMembers = List.of(
|
||||
existingMember.newBuilder()
|
||||
.labelEmoji("🎉")
|
||||
.labelString("Test Label")
|
||||
.build()
|
||||
);
|
||||
|
||||
DecryptedGroup expectedResult = new DecryptedGroup.Builder()
|
||||
.revision(11)
|
||||
.members(expectedMembers)
|
||||
.build();
|
||||
|
||||
assertEquals(expectedResult, actualResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void apply_modify_member_label_clear() throws NotAbleToApplyGroupV2ChangeException {
|
||||
UUID memberUuid = UUID.fromString("d1d1d1d1-0000-4000-8000-000000000001");
|
||||
DecryptedMember member = member(memberUuid)
|
||||
.newBuilder()
|
||||
.labelEmoji("🎉")
|
||||
.labelString("Test Label")
|
||||
.build();
|
||||
|
||||
DecryptedModifyMemberLabel modifyLabelAction = new DecryptedModifyMemberLabel.Builder()
|
||||
.aciBytes(UuidUtil.toByteString(memberUuid))
|
||||
.labelEmoji("")
|
||||
.labelString("")
|
||||
.build();
|
||||
|
||||
DecryptedGroup actualResult = DecryptedGroupUtil.apply(
|
||||
new DecryptedGroup.Builder()
|
||||
.revision(10)
|
||||
.members(List.of(member))
|
||||
.build(),
|
||||
|
||||
new DecryptedGroupChange.Builder()
|
||||
.revision(11)
|
||||
.modifyMemberLabel(List.of(modifyLabelAction))
|
||||
.build());
|
||||
|
||||
DecryptedGroup expectedResult = new DecryptedGroup.Builder()
|
||||
.revision(11)
|
||||
.members(List.of(member(memberUuid)))
|
||||
.build();
|
||||
|
||||
assertEquals(expectedResult, actualResult);
|
||||
}
|
||||
|
||||
@Test(expected = NotAbleToApplyGroupV2ChangeException.class)
|
||||
public void apply_modify_member_label_for_non_member() throws NotAbleToApplyGroupV2ChangeException {
|
||||
UUID memberUuid = UUID.fromString("d1d1d1d1-0000-4000-8000-000000000001");
|
||||
UUID nonMemberUuid = UUID.fromString("d2d2d2d2-0000-4000-8000-000000000002");
|
||||
DecryptedMember member1 = member(memberUuid);
|
||||
|
||||
DecryptedModifyMemberLabel modifyLabelAction = new DecryptedModifyMemberLabel.Builder()
|
||||
.aciBytes(UuidUtil.toByteString(nonMemberUuid))
|
||||
.labelEmoji("🎉")
|
||||
.labelString("Test Label")
|
||||
.build();
|
||||
|
||||
DecryptedGroupUtil.apply(
|
||||
new DecryptedGroup.Builder()
|
||||
.revision(10)
|
||||
.members(List.of(member1))
|
||||
.build(),
|
||||
new DecryptedGroupChange.Builder()
|
||||
.revision(11)
|
||||
.modifyMemberLabel(List.of(modifyLabelAction))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package org.whispersystems.signalservice.api.groupsv2;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
import org.signal.storageservice.storage.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedApproveMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedBannedMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberLabel;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedRequestingMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedString;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedTimer;
|
||||
import org.signal.storageservice.storage.protos.groups.local.EnabledState;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@@ -27,8 +28,8 @@ import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.promo
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.randomProfileKey;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
|
||||
|
||||
@SuppressWarnings("NewClassNamingConvention")
|
||||
public final class DecryptedGroupUtil_empty_Test {
|
||||
|
||||
/**
|
||||
* Reflects over the generated protobuf class and ensures that no new fields have been added since we wrote this.
|
||||
* <p>
|
||||
@@ -39,7 +40,7 @@ public final class DecryptedGroupUtil_empty_Test {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
|
||||
|
||||
assertEquals("DecryptedGroupUtil and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
|
||||
24, maxFieldFound);
|
||||
26, maxFieldFound);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -266,4 +267,21 @@ public final class DecryptedGroupUtil_empty_Test {
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_empty_with_modify_member_label_field_26() {
|
||||
DecryptedModifyMemberLabel modifyLabelAction = new DecryptedModifyMemberLabel.Builder()
|
||||
.aciBytes(UuidUtil.toByteString(UUID.randomUUID()))
|
||||
.labelEmoji("🔥")
|
||||
.labelString("Test")
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange change = new DecryptedGroupChange.Builder()
|
||||
.modifyMemberLabel(List.of(modifyLabelAction))
|
||||
.build();
|
||||
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForBanChangesAndOptionalProfileKeyChanges(change));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package org.whispersystems.signalservice.api.groupsv2;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.storageservice.storage.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedString;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedTimer;
|
||||
import org.signal.storageservice.storage.protos.groups.local.EnabledState;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.util.List;
|
||||
@@ -408,4 +409,57 @@ public final class GroupChangeReconstructTest {
|
||||
|
||||
assertEquals(new DecryptedGroupChange.Builder().deleteBannedMembers(List.of(bannedMember(uuidOld))).build(), decryptedGroupChange);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_label_change() {
|
||||
UUID memberUuid = UUID.fromString("d1d1d1d1-0000-4000-8000-000000000001");
|
||||
|
||||
DecryptedMember existingMember = member(memberUuid);
|
||||
DecryptedMember updatedMember = member(memberUuid)
|
||||
.newBuilder()
|
||||
.labelEmoji("🎉")
|
||||
.labelString("New Label")
|
||||
.build();
|
||||
|
||||
DecryptedGroup from = new DecryptedGroup.Builder()
|
||||
.members(List.of(existingMember))
|
||||
.build();
|
||||
|
||||
DecryptedGroup to = new DecryptedGroup.Builder()
|
||||
.members(List.of(updatedMember))
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange change = GroupChangeReconstruct.reconstructGroupChange(from, to);
|
||||
|
||||
assertEquals(1, change.modifyMemberLabel.size());
|
||||
assertEquals(UuidUtil.toByteString(memberUuid), change.modifyMemberLabel.get(0).aciBytes);
|
||||
assertEquals("🎉", change.modifyMemberLabel.get(0).labelEmoji);
|
||||
assertEquals("New Label", change.modifyMemberLabel.get(0).labelString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_label_clear() {
|
||||
UUID memberUuid = UUID.fromString("d1d1d1d1-0000-4000-8000-000000000001");
|
||||
|
||||
DecryptedMember memberWithLabel = member(memberUuid)
|
||||
.newBuilder()
|
||||
.labelEmoji("🎉")
|
||||
.labelString("existing label")
|
||||
.build();
|
||||
|
||||
DecryptedGroup from = new DecryptedGroup.Builder()
|
||||
.members(List.of(memberWithLabel))
|
||||
.build();
|
||||
|
||||
DecryptedGroup to = new DecryptedGroup.Builder()
|
||||
.members(List.of(member(memberUuid)))
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange change = GroupChangeReconstruct.reconstructGroupChange(from, to);
|
||||
|
||||
assertEquals(1, change.modifyMemberLabel.size());
|
||||
assertEquals(UuidUtil.toByteString(memberUuid), change.modifyMemberLabel.get(0).aciBytes);
|
||||
assertEquals("", change.modifyMemberLabel.get(0).labelEmoji);
|
||||
assertEquals("", change.modifyMemberLabel.get(0).labelString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
|
||||
|
||||
@SuppressWarnings("NewClassNamingConvention")
|
||||
public final class GroupChangeUtil_changeIsEmpty_Test {
|
||||
|
||||
/**
|
||||
* Reflects over the generated protobuf class and ensures that no new fields have been added since we wrote this.
|
||||
* <p>
|
||||
@@ -22,7 +22,7 @@ public final class GroupChangeUtil_changeIsEmpty_Test {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(GroupChange.Actions.class);
|
||||
|
||||
assertEquals("GroupChangeUtil and its tests need updating to account for new fields on " + GroupChange.Actions.class.getName(),
|
||||
25, maxFieldFound);
|
||||
26, maxFieldFound);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -227,4 +227,13 @@ public final class GroupChangeUtil_changeIsEmpty_Test {
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_empty_with_modify_member_label_field_26() {
|
||||
GroupChange.Actions actions = new GroupChange.Actions.Builder()
|
||||
.modifyMemberLabel(List.of(new GroupChange.Actions.ModifyMemberLabelAction()))
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package org.whispersystems.signalservice.api.groupsv2;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.storageservice.storage.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.storage.protos.groups.MemberBanned;
|
||||
import org.signal.storageservice.storage.protos.groups.GroupChange;
|
||||
import org.signal.storageservice.storage.protos.groups.Member;
|
||||
import org.signal.storageservice.storage.protos.groups.MemberBanned;
|
||||
import org.signal.storageservice.storage.protos.groups.MemberPendingProfileKey;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberLabel;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedString;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedTimer;
|
||||
import org.signal.storageservice.storage.protos.groups.local.EnabledState;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.util.List;
|
||||
@@ -40,8 +41,8 @@ import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.rando
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.requestingMember;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
|
||||
|
||||
@SuppressWarnings("NewClassNamingConvention")
|
||||
public final class GroupChangeUtil_resolveConflict_Test {
|
||||
|
||||
/**
|
||||
* Reflects over the generated protobuf class and ensures that no new fields have been added since we wrote this.
|
||||
* <p>
|
||||
@@ -52,7 +53,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
|
||||
|
||||
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
|
||||
24, maxFieldFound);
|
||||
26, maxFieldFound);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +66,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
|
||||
|
||||
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + GroupChange.class.getName(),
|
||||
24, maxFieldFound);
|
||||
26, maxFieldFound);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -854,4 +855,57 @@ public final class GroupChangeUtil_resolveConflict_Test {
|
||||
.build();
|
||||
assertEquals(expected, resolvedActions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void field_26__modify_member_label__remove_if_label_already_matches() {
|
||||
UUID memberUuid = UUID.fromString("d1d1d1d1-0000-4000-8000-000000000001");
|
||||
|
||||
DecryptedMember existingMember = member(memberUuid)
|
||||
.newBuilder()
|
||||
.labelEmoji("🔥")
|
||||
.labelString("matching label")
|
||||
.build();
|
||||
|
||||
DecryptedGroup existingGroup = new DecryptedGroup.Builder()
|
||||
.revision(10)
|
||||
.members(List.of(existingMember))
|
||||
.build();
|
||||
|
||||
DecryptedModifyMemberLabel modifyLabelAction = new DecryptedModifyMemberLabel.Builder()
|
||||
.aciBytes(UuidUtil.toByteString(memberUuid))
|
||||
.labelEmoji("🔥")
|
||||
.labelString("matching label")
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange conflictingChange = new DecryptedGroupChange.Builder()
|
||||
.modifyMemberLabel(List.of(modifyLabelAction))
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange.Builder resolvedActions = GroupChangeUtil.resolveConflict(existingGroup, conflictingChange);
|
||||
assertTrue(resolvedActions.build().modifyMemberLabel.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void field_26__modify_member_label__remove_if_member_not_in_group() {
|
||||
UUID memberUuuid = UUID.fromString("d1d1d1d1-0000-4000-8000-000000000001");
|
||||
UUID nonMemberUuid = UUID.fromString("d2d2d2d2-0000-4000-8000-000000000002");
|
||||
|
||||
DecryptedGroup existingGroup = new DecryptedGroup.Builder()
|
||||
.revision(10)
|
||||
.members(List.of(member(memberUuuid)))
|
||||
.build();
|
||||
|
||||
DecryptedModifyMemberLabel modifyLabelAction = new org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberLabel.Builder()
|
||||
.aciBytes(UuidUtil.toByteString(nonMemberUuid))
|
||||
.labelEmoji("🔥")
|
||||
.labelString("foo bar")
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange conflictingChange = new DecryptedGroupChange.Builder()
|
||||
.modifyMemberLabel(List.of(modifyLabelAction))
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange.Builder resolved = GroupChangeUtil.resolveConflict(existingGroup, conflictingChange);
|
||||
assertTrue(resolved.build().modifyMemberLabel.isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
package org.whispersystems.signalservice.api.groupsv2;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.storageservice.storage.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberLabel;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedString;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedTimer;
|
||||
import org.signal.storageservice.storage.protos.groups.local.EnabledState;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.util.List;
|
||||
@@ -32,8 +33,8 @@ import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.rando
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.requestingMember;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
|
||||
|
||||
@SuppressWarnings("NewClassNamingConvention")
|
||||
public final class GroupChangeUtil_resolveConflict_decryptedOnly_Test {
|
||||
|
||||
/**
|
||||
* Reflects over the generated protobuf class and ensures that no new fields have been added since we wrote this.
|
||||
* <p>
|
||||
@@ -44,7 +45,7 @@ public final class GroupChangeUtil_resolveConflict_decryptedOnly_Test {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
|
||||
|
||||
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
|
||||
24, maxFieldFound);
|
||||
26, maxFieldFound);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -673,4 +674,57 @@ public final class GroupChangeUtil_resolveConflict_decryptedOnly_Test {
|
||||
|
||||
assertEquals(expected, resolvedChanges);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void field_26__modify_member_label__remove_if_label_already_matches() {
|
||||
UUID memberUuid = UUID.fromString("d1d1d1d1-0000-4000-8000-000000000001");
|
||||
|
||||
DecryptedMember existingMember = member(memberUuid)
|
||||
.newBuilder()
|
||||
.labelEmoji("🔥")
|
||||
.labelString("Already Set")
|
||||
.build();
|
||||
|
||||
DecryptedModifyMemberLabel modifyLabelAction = new DecryptedModifyMemberLabel.Builder()
|
||||
.aciBytes(UuidUtil.toByteString(memberUuid))
|
||||
.labelEmoji("🔥")
|
||||
.labelString("Already Set")
|
||||
.build();
|
||||
|
||||
DecryptedGroup existingGroup = new DecryptedGroup.Builder()
|
||||
.revision(10)
|
||||
.members(List.of(existingMember))
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange conflictingChange = new DecryptedGroupChange.Builder()
|
||||
.modifyMemberLabel(List.of(modifyLabelAction))
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange.Builder resolved = GroupChangeUtil.resolveConflict(existingGroup, conflictingChange);
|
||||
assertTrue(resolved.build().modifyMemberLabel.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void field_26__modify_member_label__remove_if_member_not_in_group() {
|
||||
UUID memberUuid = UUID.fromString("d1d1d1d1-0000-4000-8000-000000000001");
|
||||
UUID notInGroupUuid = UUID.fromString("d2d2d2d2-0000-4000-8000-000000000002");
|
||||
|
||||
DecryptedGroup existingGroup = new DecryptedGroup.Builder()
|
||||
.revision(10)
|
||||
.members(List.of(member(memberUuid)))
|
||||
.build();
|
||||
|
||||
DecryptedModifyMemberLabel modifyLabelAction = new DecryptedModifyMemberLabel.Builder()
|
||||
.aciBytes(UuidUtil.toByteString(notInGroupUuid))
|
||||
.labelEmoji("🔥")
|
||||
.labelString("Test")
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange conflictingChange = new DecryptedGroupChange.Builder()
|
||||
.modifyMemberLabel(List.of(modifyLabelAction))
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange.Builder resolved = GroupChangeUtil.resolveConflict(existingGroup, conflictingChange);
|
||||
assertTrue(resolved.build().modifyMemberLabel.isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@ package org.whispersystems.signalservice.api.groupsv2;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.signal.core.models.ServiceId.ACI;
|
||||
import org.signal.core.models.ServiceId.PNI;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
import org.signal.libsignal.zkgroup.groups.ClientZkGroupCipher;
|
||||
@@ -23,6 +26,7 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedApproveMem
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedBannedMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberLabel;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberRole;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMemberRemoval;
|
||||
@@ -30,9 +34,6 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedRequesting
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedString;
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedTimer;
|
||||
import org.signal.storageservice.storage.protos.groups.local.EnabledState;
|
||||
import org.signal.core.models.ServiceId.ACI;
|
||||
import org.signal.core.models.ServiceId.PNI;
|
||||
import org.signal.core.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
import org.whispersystems.signalservice.testutil.LibSignalLibraryUtil;
|
||||
|
||||
@@ -50,8 +51,8 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
|
||||
|
||||
@SuppressWarnings("NewClassNamingConvention")
|
||||
public final class GroupsV2Operations_decrypt_change_Test {
|
||||
|
||||
private GroupSecretParams groupSecretParams;
|
||||
private GroupsV2Operations.GroupOperations groupOperations;
|
||||
private ClientZkOperations clientZkOperations;
|
||||
@@ -72,7 +73,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
|
||||
|
||||
assertEquals("GroupV2Operations#decryptChange and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
|
||||
24,
|
||||
26,
|
||||
maxFieldFound);
|
||||
}
|
||||
|
||||
@@ -459,6 +460,22 @@ public final class GroupsV2Operations_decrypt_change_Test {
|
||||
.build())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_decrypt_modify_member_label_field26() {
|
||||
ACI aci = ACI.from(UUID.fromString("d1d1d1d1-0000-4000-8000-000000000001"));
|
||||
|
||||
DecryptedModifyMemberLabel modifyLabelAction = new DecryptedModifyMemberLabel.Builder()
|
||||
.aciBytes(aci.toByteString())
|
||||
.labelString("Label Text")
|
||||
.labelEmoji("🔥")
|
||||
.build();
|
||||
|
||||
assertDecryption(
|
||||
groupOperations.createChangeMemberLabel(aci, "Label Text", "🔥"),
|
||||
new DecryptedGroupChange.Builder().modifyMemberLabel(List.of(modifyLabelAction))
|
||||
);
|
||||
}
|
||||
|
||||
private static ProfileKey newProfileKey() {
|
||||
try {
|
||||
return new ProfileKey(Util.getSecretBytes(32));
|
||||
|
||||
Reference in New Issue
Block a user