Groups V2 protobufs and local conflict resolution.

master
Alan Evans 2020-03-25 13:11:50 -03:00 committed by Greyson Parrelli
parent f449a45912
commit 0269a3eb6f
7 changed files with 1184 additions and 0 deletions

View File

@ -1,8 +1,13 @@
package org.whispersystems.signalservice.api.util;
import com.google.protobuf.ByteString;
import org.whispersystems.libsignal.util.guava.Optional;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
@ -43,4 +48,12 @@ public final class UuidUtil {
return buffer.array();
}
public static ByteString toByteString(UUID uuid) {
return ByteString.copyFrom(toByteArray(uuid));
}
public static UUID fromByteString(ByteString bytes) {
return parseOrThrow(bytes.toByteArray());
}
}

View File

@ -0,0 +1,212 @@
package org.whispersystems.signalservice.internal.groupsv2;
import com.google.protobuf.ByteString;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval;
import java.util.HashMap;
import java.util.List;
public final class GroupChangeUtil {
private GroupChangeUtil() {
}
/**
* The maximum field we know about here.
*/
static final int CHANGE_ACTION_MAX_FIELD = 14;
/**
* True iff there are no change actions.
*/
public static boolean changeIsEmpty(GroupChange.Actions change) {
return change.getAddMembersCount() == 0 && // field 3
change.getDeleteMembersCount() == 0 && // field 4
change.getModifyMemberRolesCount() == 0 && // field 5
change.getModifyMemberProfileKeysCount() == 0 && // field 6
change.getAddPendingMembersCount() == 0 && // field 7
change.getDeletePendingMembersCount() == 0 && // field 8
change.getPromotePendingMembersCount() == 0 && // field 9
!change.hasModifyTitle() && // field 10
!change.hasModifyAvatar() && // field 11
!change.hasModifyDisappearingMessagesTimer() && // field 12
!change.hasModifyAttributesAccess() && // field 13
!change.hasModifyMemberAccess(); // field 14
}
/**
* Given the latest group state and a conflicting change, decides which changes to carry forward
* and returns a new group change which could be empty.
* <p>
* Titles, avatars, and other settings are carried forward if they are different. Last writer wins.
* <p>
* Membership additions and removals also respect last writer wins and are removed if they have
* already been applied. e.g. you add someone but they are already added.
* <p>
* Membership additions will be altered to {@link GroupChange.Actions.PromotePendingMemberAction}
* if someone has invited them since.
*
* @param groupState Latest group state in plaintext.
* @param conflictingChange The potentially conflicting change in plaintext.
* @param encryptedChange Encrypted version of the {@param conflictingChange}.
* @return A new change builder.
*/
public static GroupChange.Actions.Builder resolveConflict(DecryptedGroup groupState,
DecryptedGroupChange conflictingChange,
GroupChange.Actions encryptedChange)
{
GroupChange.Actions.Builder result = GroupChange.Actions.newBuilder(encryptedChange);
HashMap<ByteString, DecryptedMember> fullMembersByUuid = new HashMap<>(groupState.getMembersCount());
HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid = new HashMap<>(groupState.getPendingMembersCount());
for (DecryptedMember member : groupState.getMembersList()) {
fullMembersByUuid.put(member.getUuid(), member);
}
for (DecryptedPendingMember member : groupState.getPendingMembersList()) {
pendingMembersByUuid.put(member.getUuid(), member);
}
resolveField3AddMembers (conflictingChange, result, fullMembersByUuid, pendingMembersByUuid);
resolveField4DeleteMembers (conflictingChange, result, fullMembersByUuid);
resolveField5ModifyMemberRoles (conflictingChange, result, fullMembersByUuid);
resolveField6ModifyProfileKeys (conflictingChange, result, fullMembersByUuid);
resolveField7AddPendingMembers (conflictingChange, result, fullMembersByUuid, pendingMembersByUuid);
resolveField8DeletePendingMembers (conflictingChange, result, pendingMembersByUuid);
resolveField9PromotePendingMembers (conflictingChange, result, pendingMembersByUuid);
resolveField10ModifyTitle (groupState, conflictingChange, result);
resolveField11ModifyAvatar (groupState, conflictingChange, result);
resolveField12modifyDisappearingMessagesTimer(groupState, conflictingChange, result);
resolveField13modifyAttributesAccess (groupState, conflictingChange, result);
resolveField14modifyAttributesAccess (groupState, conflictingChange, result);
return result;
}
private static void resolveField3AddMembers(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
List<DecryptedMember> newMembersList = conflictingChange.getNewMembersList();
for (int i = newMembersList.size() - 1; i >= 0; i--) {
DecryptedMember member = newMembersList.get(i);
if (fullMembersByUuid.containsKey(member.getUuid())) {
result.removeAddMembers(i);
} else if (pendingMembersByUuid.containsKey(member.getUuid())) {
GroupChange.Actions.AddMemberAction addMemberAction = result.getAddMembersList().get(i);
result.removeAddMembers(i);
result.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder().setPresentation(addMemberAction.getAdded().getPresentation()));
}
}
}
private static void resolveField4DeleteMembers(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedMember> fullMembersByUuid) {
List<ByteString> deletedMembersList = conflictingChange.getDeleteMembersList();
for (int i = deletedMembersList.size() - 1; i >= 0; i--) {
ByteString member = deletedMembersList.get(i);
if (!fullMembersByUuid.containsKey(member)) {
result.removeDeleteMembers(i);
}
}
}
private static void resolveField5ModifyMemberRoles(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedMember> fullMembersByUuid) {
List<DecryptedModifyMemberRole> modifyRolesList = conflictingChange.getModifyMemberRolesList();
for (int i = modifyRolesList.size() - 1; i >= 0; i--) {
DecryptedModifyMemberRole modifyRoleAction = modifyRolesList.get(i);
DecryptedMember memberInGroup = fullMembersByUuid.get(modifyRoleAction.getUuid());
if (memberInGroup == null || memberInGroup.getRole() == modifyRoleAction.getRole()) {
result.removeModifyMemberRoles(i);
}
}
}
private static void resolveField6ModifyProfileKeys(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedMember> fullMembersByUuid) {
List<DecryptedMember> modifyProfileKeysList = conflictingChange.getModifiedProfileKeysList();
for (int i = modifyProfileKeysList.size() - 1; i >= 0; i--) {
DecryptedMember member = modifyProfileKeysList.get(i);
DecryptedMember memberInGroup = fullMembersByUuid.get(member.getUuid());
if (memberInGroup == null || member.getProfileKey().equals(memberInGroup.getProfileKey())) {
result.removeModifyMemberProfileKeys(i);
}
}
}
private static void resolveField7AddPendingMembers(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
List<DecryptedPendingMember> newPendingMembersList = conflictingChange.getNewPendingMembersList();
for (int i = newPendingMembersList.size() - 1; i >= 0; i--) {
DecryptedPendingMember member = newPendingMembersList.get(i);
if (fullMembersByUuid.containsKey(member.getUuid()) || pendingMembersByUuid.containsKey(member.getUuid())) {
result.removeAddPendingMembers(i);
}
}
}
private static void resolveField8DeletePendingMembers(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
List<DecryptedPendingMemberRemoval> deletePendingMembersList = conflictingChange.getDeletePendingMembersList();
for (int i = deletePendingMembersList.size() - 1; i >= 0; i--) {
DecryptedPendingMemberRemoval member = deletePendingMembersList.get(i);
if (!pendingMembersByUuid.containsKey(member.getUuid())) {
result.removeDeletePendingMembers(i);
}
}
}
private static void resolveField9PromotePendingMembers(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
List<ByteString> promotePendingMembersList = conflictingChange.getPromotePendingMembersList();
for (int i = promotePendingMembersList.size() - 1; i >= 0; i--) {
ByteString member = promotePendingMembersList.get(i);
if (!pendingMembersByUuid.containsKey(member)) {
result.removePromotePendingMembers(i);
}
}
}
private static void resolveField10ModifyTitle(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result) {
if (conflictingChange.hasNewTitle() && conflictingChange.getNewTitle().getValue().equals(groupState.getTitle())) {
result.clearModifyTitle();
}
}
private static void resolveField11ModifyAvatar(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result) {
if (conflictingChange.hasNewAvatar() && conflictingChange.getNewAvatar().getValue().equals(groupState.getAvatar())) {
result.clearModifyAvatar();
}
}
private static void resolveField12modifyDisappearingMessagesTimer(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result) {
if (conflictingChange.hasNewTimer() && conflictingChange.getNewTimer().getDuration() == groupState.getDisappearingMessagesTimer().getDuration()) {
result.clearModifyDisappearingMessagesTimer();
}
}
private static void resolveField13modifyAttributesAccess(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result) {
if (conflictingChange.getNewAttributeAccess() == groupState.getAccessControl().getAttributes()) {
result.clearModifyAttributesAccess();
}
}
private static void resolveField14modifyAttributesAccess(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result) {
if (conflictingChange.getNewMemberAccess() == groupState.getAccessControl().getMembers()) {
result.clearModifyMemberAccess();
}
}
}

View File

@ -0,0 +1,73 @@
/**
* Copyright (C) 2019 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
syntax = "proto3";
option java_package = "org.signal.storageservice.protos.groups.local";
option java_multiple_files = true;
import "Groups.proto";
// Decrypted version of Member
// Keep field numbers in step
message DecryptedMember {
bytes uuid = 1;
Member.Role role = 2;
bytes profileKey = 3;
uint32 joinedAtVersion = 5;
}
message DecryptedPendingMember {
bytes uuid = 1;
Member.Role role = 2;
bytes addedByUuid = 3;
uint64 timestamp = 4;
bytes uuidCipherText = 5;
}
message DecryptedPendingMemberRemoval {
bytes uuid = 1;
bytes uuidCipherText = 2;
}
message DecryptedModifyMemberRole {
bytes uuid = 1;
Member.Role role = 2;
}
// Decrypted version of message Group
// Keep field numbers in step
message DecryptedGroup {
string title = 2;
string avatar = 3;
DisappearingMessagesTimer disappearingMessagesTimer = 4;
AccessControl accessControl = 5;
uint32 version = 6;
repeated DecryptedMember members = 7;
repeated DecryptedPendingMember pendingMembers = 8;
}
// Decrypted version of message GroupChange.Actions
// Keep field numbers in step
message DecryptedGroupChange {
bytes editor = 1;
uint32 version = 2;
repeated DecryptedMember newMembers = 3;
repeated bytes deleteMembers = 4;
repeated DecryptedModifyMemberRole modifyMemberRoles = 5;
repeated DecryptedMember modifiedProfileKeys = 6;
repeated DecryptedPendingMember newPendingMembers = 7;
repeated DecryptedPendingMemberRemoval deletePendingMembers = 8;
repeated bytes promotePendingMembers = 9;
DecryptedString newTitle = 10;
DecryptedString newAvatar = 11;
DisappearingMessagesTimer newTimer = 12;
AccessControl.AccessRequired newAttributeAccess = 13;
AccessControl.AccessRequired newMemberAccess = 14;
}
message DecryptedString {
string value = 1;
}

View File

@ -0,0 +1,148 @@
/**
* Copyright (C) 2019 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
syntax = "proto3";
option java_package = "org.signal.storageservice.protos.groups";
option java_multiple_files = true;
message AvatarUploadAttributes {
string key = 1;
string credential = 2;
string acl = 3;
string algorithm = 4;
string date = 5;
string policy = 6;
string signature = 7;
}
message Member {
enum Role {
UNKNOWN = 0;
DEFAULT = 1;
ADMINISTRATOR = 2;
}
bytes userId = 1;
Role role = 2;
bytes profileKey = 3;
bytes presentation = 4;
uint32 joinedAtVersion = 5;
}
message PendingMember {
Member member = 1;
bytes addedByUserId = 2;
uint64 timestamp = 3;
}
message AccessControl {
enum AccessRequired {
UNKNOWN = 0;
ANY = 1;
MEMBER = 2;
ADMINISTRATOR = 3;
}
AccessRequired attributes = 1;
AccessRequired members = 2;
}
message Group {
bytes publicKey = 1;
bytes title = 2;
string avatar = 3;
bytes disappearingMessagesTimer = 4;
AccessControl accessControl = 5;
uint32 version = 6;
repeated Member members = 7;
repeated PendingMember pendingMembers = 8;
}
message GroupChange {
message Actions {
message AddMemberAction {
Member added = 1;
}
message DeleteMemberAction {
bytes deletedUserId = 1;
}
message ModifyMemberRoleAction {
bytes userId = 1;
Member.Role role = 2;
}
message ModifyMemberProfileKeyAction {
bytes presentation = 1;
}
message AddPendingMemberAction {
PendingMember added = 1;
}
message DeletePendingMemberAction {
bytes deletedUserId = 1;
}
message PromotePendingMemberAction {
bytes presentation = 1;
}
message ModifyTitleAction {
bytes title = 1;
}
message ModifyAvatarAction {
string avatar = 1;
}
message ModifyDisappearingMessagesTimerAction {
bytes timer = 1;
}
message ModifyAttributesAccessControlAction {
AccessControl.AccessRequired attributesAccess = 1;
}
message ModifyMembersAccessControlAction {
AccessControl.AccessRequired membersAccess = 1;
}
bytes sourceUuid = 1;
uint32 version = 2;
repeated AddMemberAction addMembers = 3;
repeated DeleteMemberAction deleteMembers = 4;
repeated ModifyMemberRoleAction modifyMemberRoles = 5;
repeated ModifyMemberProfileKeyAction modifyMemberProfileKeys = 6;
repeated AddPendingMemberAction addPendingMembers = 7;
repeated DeletePendingMemberAction deletePendingMembers = 8;
repeated PromotePendingMemberAction promotePendingMembers = 9;
ModifyTitleAction modifyTitle = 10;
ModifyAvatarAction modifyAvatar = 11;
ModifyDisappearingMessagesTimerAction modifyDisappearingMessagesTimer = 12;
ModifyAttributesAccessControlAction modifyAttributesAccess = 13;
ModifyMembersAccessControlAction modifyMemberAccess = 14;
}
bytes actions = 1;
bytes serverSignature = 2;
}
message GroupChanges {
message GroupChangeState {
GroupChange groupChange = 1;
Group groupState = 2;
}
repeated GroupChangeState groupChanges = 1;
}
message DisappearingMessagesTimer {
uint32 duration = 1;
}

View File

@ -1,6 +1,9 @@
package org.whispersystems.signalservice.api.util;
import com.google.protobuf.ByteString;
import org.junit.Test;
import org.signal.zkgroup.util.UUIDUtil;
import org.whispersystems.libsignal.util.Hex;
import java.io.IOException;
@ -46,4 +49,31 @@ public final class UuidUtilTest {
assertEquals("b83dfb0b-67f1-41aa-992e-030c167cd011", uuid.toString());
}
@Test
public void byte_array_compatibility_with_zk_group_uuid_util() {
UUID uuid = UUID.fromString("67dfd496-ea02-4720-b13d-83a462168b1d");
UUID result = UUIDUtil.deserialize(UuidUtil.toByteArray(uuid));
assertEquals(uuid, result);
}
@Test
public void byte_string_compatibility_with_zk_group_uuid_util() {
UUID uuid = UUID.fromString("67dfd496-ea02-4720-b13d-83a462168b1d");
UUID result = UuidUtil.fromByteString(ByteString.copyFrom(UUIDUtil.serialize(uuid)));
assertEquals(uuid, result);
}
@Test
public void byte_string_round_trip() {
UUID uuid = UUID.fromString("67dfd496-ea02-4720-b13d-83a462168b1d");
UUID result = UuidUtil.fromByteString(ByteString.copyFrom(UuidUtil.toByteArray(uuid)));
assertEquals(uuid, result);
}
}

View File

@ -0,0 +1,149 @@
package org.whispersystems.signalservice.internal.groupsv2;
import org.junit.Test;
import org.signal.storageservice.protos.groups.GroupChange;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public final class GroupChangeUtilTest {
/**
* Reflects over the generated protobuf class and ensures that no new fields have been added since we wrote this.
* <p>
* If we didn't, newly added fields would easily affect {@link GroupChangeUtil}'s ability to detect empty change states and resolve conflicts.
*/
@Test
public void ensure_GroupChangeUtil_knows_about_all_fields_of_GroupChange_Actions() {
int maxFieldFound = Stream.of(GroupChange.Actions.class.getFields())
.filter(f -> f.getType() == int.class)
.mapToInt(f -> {
try {
return (int) f.get(null);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
})
.max()
.orElse(0);
assertEquals("GroupChangeUtil and its tests need updating to account for new fields on " + GroupChange.Actions.class.getName(),
GroupChangeUtil.CHANGE_ACTION_MAX_FIELD, maxFieldFound);
}
@Test
public void empty_change_set() {
assertTrue(GroupChangeUtil.changeIsEmpty(GroupChange.Actions.newBuilder().build()));
}
@Test
public void not_empty_with_add_member_field_3() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.addAddMembers(GroupChange.Actions.AddMemberAction.newBuilder().getDefaultInstanceForType())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_delete_member_field_4() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.addDeleteMembers(GroupChange.Actions.DeleteMemberAction.newBuilder().getDefaultInstanceForType())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_modify_member_roles_field_5() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().getDefaultInstanceForType())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_modify_profile_keys_field_6() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.addModifyMemberProfileKeys(GroupChange.Actions.ModifyMemberProfileKeyAction.newBuilder().getDefaultInstanceForType())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_add_pending_members_field_7() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.addAddPendingMembers(GroupChange.Actions.AddPendingMemberAction.newBuilder().getDefaultInstanceForType())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_delete_pending_members_field_8() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.addDeletePendingMembers(GroupChange.Actions.DeletePendingMemberAction.newBuilder().getDefaultInstanceForType())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_promote_delete_pending_members_field_9() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder().getDefaultInstanceForType())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_modify_title_field_10() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.setModifyTitle(GroupChange.Actions.ModifyTitleAction.newBuilder().getDefaultInstanceForType())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_modify_avatar_field_11() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder().getDefaultInstanceForType())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_modify_disappearing_message_timer_field_12() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.setModifyDisappearingMessagesTimer(GroupChange.Actions.ModifyDisappearingMessagesTimerAction.newBuilder().getDefaultInstanceForType())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_modify_attributes_field_13() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.setModifyAttributesAccess(GroupChange.Actions.ModifyAttributesAccessControlAction.newBuilder().getDefaultInstanceForType())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_modify_member_access_field_14() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.setModifyMemberAccess(GroupChange.Actions.ModifyMembersAccessControlAction.newBuilder().getDefaultInstanceForType())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
}

View File

@ -0,0 +1,559 @@
package org.whispersystems.signalservice.internal.groupsv2;
import com.google.protobuf.ByteString;
import org.junit.Test;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.DisappearingMessagesTimer;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.PendingMember;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval;
import org.signal.storageservice.protos.groups.local.DecryptedString;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.profiles.ProfileKey;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public final class GroupChangeUtil_resolveConflict_Test {
@Test
public void empty_actions() {
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(DecryptedGroup.newBuilder().build(),
DecryptedGroupChange.newBuilder().build(),
GroupChange.Actions.newBuilder().build())
.build();
assertTrue(GroupChangeUtil.changeIsEmpty(resolvedActions));
}
@Test
public void field_3__changes_to_add_existing_members_are_excluded() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
ProfileKey profileKey2 = randomProfileKey();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addMembers(member(member3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addNewMembers(member(member1))
.addNewMembers(member(member2))
.addNewMembers(member(member3))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.addAddMembers(GroupChange.Actions.AddMemberAction.newBuilder().setAdded(encryptedMember(member1, randomProfileKey())))
.addAddMembers(GroupChange.Actions.AddMemberAction.newBuilder().setAdded(encryptedMember(member2, profileKey2)))
.addAddMembers(GroupChange.Actions.AddMemberAction.newBuilder().setAdded(encryptedMember(member3, randomProfileKey())))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
.addAddMembers(GroupChange.Actions.AddMemberAction.newBuilder().setAdded(encryptedMember(member2, profileKey2)))
.build();
assertEquals(expected, resolvedActions);
}
@Test
public void field_4__changes_to_remove_missing_members_are_excluded() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member2))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addDeleteMembers(UuidUtil.toByteString(member1))
.addDeleteMembers(UuidUtil.toByteString(member2))
.addDeleteMembers(UuidUtil.toByteString(member3))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.addDeleteMembers(GroupChange.Actions.DeleteMemberAction.newBuilder().setDeletedUserId(encrypt(member1)))
.addDeleteMembers(GroupChange.Actions.DeleteMemberAction.newBuilder().setDeletedUserId(encrypt(member2)))
.addDeleteMembers(GroupChange.Actions.DeleteMemberAction.newBuilder().setDeletedUserId(encrypt(member3)))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
.addDeleteMembers(GroupChange.Actions.DeleteMemberAction.newBuilder().setDeletedUserId(encrypt(member2)))
.build();
assertEquals(expected, resolvedActions);
}
@Test
public void field_5__role_change_is_preserved() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(admin(member1))
.addMembers(member(member2))
.addMembers(member(member3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addModifyMemberRoles(demoteAdmin(member1))
.addModifyMemberRoles(promoteAdmin(member2))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().setUserId(encrypt(member1)).setRole(Member.Role.DEFAULT))
.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().setUserId(encrypt(member2)).setRole(Member.Role.ADMINISTRATOR))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertEquals(change, resolvedActions);
}
@Test
public void field_5__unnecessary_role_changes_removed() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
UUID memberNotInGroup = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(admin(member1))
.addMembers(member(member2))
.addMembers(member(member3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addModifyMemberRoles(promoteAdmin(member1))
.addModifyMemberRoles(promoteAdmin(member2))
.addModifyMemberRoles(demoteAdmin(member3))
.addModifyMemberRoles(promoteAdmin(memberNotInGroup))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().setUserId(encrypt(member1)).setRole(Member.Role.ADMINISTRATOR))
.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().setUserId(encrypt(member2)).setRole(Member.Role.ADMINISTRATOR))
.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().setUserId(encrypt(member3)).setRole(Member.Role.DEFAULT))
.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().setUserId(encrypt(memberNotInGroup)).setRole(Member.Role.ADMINISTRATOR))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().setUserId(encrypt(member2)).setRole(Member.Role.ADMINISTRATOR))
.build();
assertEquals(expected, resolvedActions);
}
@Test
public void field_6__profile_key_changes() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
UUID memberNotInGroup = UUID.randomUUID();
ProfileKey profileKey1 = randomProfileKey();
ProfileKey profileKey2 = randomProfileKey();
ProfileKey profileKey3 = randomProfileKey();
ProfileKey profileKey4 = randomProfileKey();
ProfileKey profileKey2b = randomProfileKey();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1, profileKey1))
.addMembers(member(member2, profileKey2))
.addMembers(member(member3, profileKey3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addModifiedProfileKeys(member(member1, profileKey1))
.addModifiedProfileKeys(member(member2, profileKey2b))
.addModifiedProfileKeys(member(member3, profileKey3))
.addModifiedProfileKeys(member(memberNotInGroup, profileKey4))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.addModifyMemberProfileKeys(GroupChange.Actions.ModifyMemberProfileKeyAction.newBuilder().setPresentation(presentation(member1, profileKey1)))
.addModifyMemberProfileKeys(GroupChange.Actions.ModifyMemberProfileKeyAction.newBuilder().setPresentation(presentation(member2, profileKey2b)))
.addModifyMemberProfileKeys(GroupChange.Actions.ModifyMemberProfileKeyAction.newBuilder().setPresentation(presentation(member3, profileKey3)))
.addModifyMemberProfileKeys(GroupChange.Actions.ModifyMemberProfileKeyAction.newBuilder().setPresentation(presentation(memberNotInGroup, profileKey4)))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
.addModifyMemberProfileKeys(GroupChange.Actions.ModifyMemberProfileKeyAction.newBuilder().setPresentation(presentation(member2, profileKey2b)))
.build();
assertEquals(expected, resolvedActions);
}
@Test
public void field_7__add_pending_members() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
ProfileKey profileKey2 = randomProfileKey();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addPendingMembers(pendingMember(member3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addNewPendingMembers(pendingMember(member1))
.addNewPendingMembers(pendingMember(member2))
.addNewPendingMembers(pendingMember(member3))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.addAddPendingMembers(GroupChange.Actions.AddPendingMemberAction.newBuilder().setAdded(PendingMember.newBuilder().setMember(encryptedMember(member1, randomProfileKey()))))
.addAddPendingMembers(GroupChange.Actions.AddPendingMemberAction.newBuilder().setAdded(PendingMember.newBuilder().setMember(encryptedMember(member2, profileKey2))))
.addAddPendingMembers(GroupChange.Actions.AddPendingMemberAction.newBuilder().setAdded(PendingMember.newBuilder().setMember(encryptedMember(member3, randomProfileKey()))))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
.addAddPendingMembers(GroupChange.Actions.AddPendingMemberAction.newBuilder().setAdded(PendingMember.newBuilder().setMember(encryptedMember(member2, profileKey2))))
.build();
assertEquals(expected, resolvedActions);
}
@Test
public void field_8__delete_pending_members() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addPendingMembers(pendingMember(member2))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addDeletePendingMembers(pendingMemberRemoval(member1))
.addDeletePendingMembers(pendingMemberRemoval(member2))
.addDeletePendingMembers(pendingMemberRemoval(member3))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.addDeletePendingMembers(GroupChange.Actions.DeletePendingMemberAction.newBuilder().setDeletedUserId(encrypt(member1)))
.addDeletePendingMembers(GroupChange.Actions.DeletePendingMemberAction.newBuilder().setDeletedUserId(encrypt(member2)))
.addDeletePendingMembers(GroupChange.Actions.DeletePendingMemberAction.newBuilder().setDeletedUserId(encrypt(member3)))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
.addDeletePendingMembers(GroupChange.Actions.DeletePendingMemberAction.newBuilder().setDeletedUserId(encrypt(member2)))
.build();
assertEquals(expected, resolvedActions);
}
@Test
public void field_9__promote_pending_members() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
ProfileKey profileKey2 = randomProfileKey();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addPendingMembers(pendingMember(member2))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addPromotePendingMembers(UuidUtil.toByteString(member1))
.addPromotePendingMembers(UuidUtil.toByteString(member2))
.addPromotePendingMembers(UuidUtil.toByteString(member3))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder().setPresentation(presentation(member1, randomProfileKey())))
.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder().setPresentation(presentation(member2, profileKey2)))
.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder().setPresentation(presentation(member3, randomProfileKey())))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder().setPresentation(presentation(member2, profileKey2)))
.build();
assertEquals(expected, resolvedActions);
}
@Test
public void field_3_to_9__add_of_pending_member_converted_to_a_promote() {
UUID member1 = UUID.randomUUID();
ProfileKey profileKey1 = randomProfileKey();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addPendingMembers(pendingMember(member1))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addNewMembers(member(member1))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.addAddMembers(GroupChange.Actions.AddMemberAction.newBuilder().setAdded(encryptedMember(member1, profileKey1)))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder().setPresentation(presentation(member1, profileKey1)))
.build();
assertEquals(expected, resolvedActions);
}
@Test
public void field_10__title_change_is_preserved() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setTitle("Existing title")
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewTitle(DecryptedString.newBuilder().setValue("New title").build())
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.setModifyTitle(GroupChange.Actions.ModifyTitleAction.newBuilder().setTitle(ByteString.copyFrom("New title encrypted".getBytes())))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertEquals(change, resolvedActions);
}
@Test
public void field_10__no_title_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setTitle("Existing title")
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewTitle(DecryptedString.newBuilder().setValue("Existing title").build())
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.setModifyTitle(GroupChange.Actions.ModifyTitleAction.newBuilder().setTitle(ByteString.copyFrom("Existing title encrypted".getBytes())))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertTrue(GroupChangeUtil.changeIsEmpty(resolvedActions));
}
@Test
public void field_11__avatar_change_is_preserved() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAvatar("Existing avatar")
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewAvatar(DecryptedString.newBuilder().setValue("New avatar").build())
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder().setAvatar("New avatar possibly encrypted"))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertEquals(change, resolvedActions);
}
@Test
public void field_11__no_avatar_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAvatar("Existing avatar")
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewAvatar(DecryptedString.newBuilder().setValue("Existing avatar").build())
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder().setAvatar("Existing avatar possibly encrypted"))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertTrue(GroupChangeUtil.changeIsEmpty(resolvedActions));
}
@Test
public void field_12__timer_change_is_preserved() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setDisappearingMessagesTimer(DisappearingMessagesTimer.newBuilder().setDuration(123))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewTimer(DisappearingMessagesTimer.newBuilder().setDuration(456))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.setModifyDisappearingMessagesTimer(GroupChange.Actions.ModifyDisappearingMessagesTimerAction.newBuilder().setTimer(ByteString.EMPTY))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertEquals(change, resolvedActions);
}
@Test
public void field_12__no_timer_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setDisappearingMessagesTimer(DisappearingMessagesTimer.newBuilder().setDuration(123))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewTimer(DisappearingMessagesTimer.newBuilder().setDuration(123))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.setModifyDisappearingMessagesTimer(GroupChange.Actions.ModifyDisappearingMessagesTimerAction.newBuilder().setTimer(ByteString.EMPTY))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertTrue(GroupChangeUtil.changeIsEmpty(resolvedActions));
}
@Test
public void field_13__attribute_access_change_is_preserved() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAccessControl(AccessControl.newBuilder().setAttributes(AccessControl.AccessRequired.ADMINISTRATOR))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewAttributeAccess(AccessControl.AccessRequired.MEMBER)
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.setModifyAttributesAccess(GroupChange.Actions.ModifyAttributesAccessControlAction.newBuilder().setAttributesAccess(AccessControl.AccessRequired.MEMBER))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertEquals(change, resolvedActions);
}
@Test
public void field_13__no_attribute_access_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAccessControl(AccessControl.newBuilder().setAttributes(AccessControl.AccessRequired.ADMINISTRATOR))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewAttributeAccess(AccessControl.AccessRequired.ADMINISTRATOR)
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.setModifyAttributesAccess(GroupChange.Actions.ModifyAttributesAccessControlAction.newBuilder().setAttributesAccess(AccessControl.AccessRequired.ADMINISTRATOR))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertTrue(GroupChangeUtil.changeIsEmpty(resolvedActions));
}
@Test
public void field_14__membership_access_change_is_preserved() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAccessControl(AccessControl.newBuilder().setMembers(AccessControl.AccessRequired.ADMINISTRATOR))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewMemberAccess(AccessControl.AccessRequired.MEMBER)
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.setModifyMemberAccess(GroupChange.Actions.ModifyMembersAccessControlAction.newBuilder().setMembersAccess(AccessControl.AccessRequired.MEMBER))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertEquals(change, resolvedActions);
}
@Test
public void field_14__no_membership_access_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAccessControl(AccessControl.newBuilder().setMembers(AccessControl.AccessRequired.ADMINISTRATOR))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewMemberAccess(AccessControl.AccessRequired.ADMINISTRATOR)
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.setModifyMemberAccess(GroupChange.Actions.ModifyMembersAccessControlAction.newBuilder().setMembersAccess(AccessControl.AccessRequired.ADMINISTRATOR))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertTrue(GroupChangeUtil.changeIsEmpty(resolvedActions));
}
private static ProfileKey randomProfileKey() {
byte[] contents = new byte[32];
new SecureRandom().nextBytes(contents);
try {
return new ProfileKey(contents);
} catch (InvalidInputException e) {
throw new AssertionError();
}
}
/**
* Emulates encryption by creating a unique {@link ByteString} that won't equal a byte string created from the {@link UUID}.
*/
private static ByteString encrypt(UUID uuid) {
byte[] uuidBytes = UuidUtil.toByteArray(uuid);
return ByteString.copyFrom(Arrays.copyOf(uuidBytes, uuidBytes.length + 1));
}
/**
* Emulates a presentation by concatenating the uuid and profile key which makes it suitable for
* equality assertions in these tests.
*/
private static ByteString presentation(UUID uuid, ProfileKey profileKey) {
byte[] uuidBytes = UuidUtil.toByteArray(uuid);
byte[] profileKeyBytes = profileKey.serialize();
byte[] concat = new byte[uuidBytes.length + profileKeyBytes.length];
System.arraycopy(uuidBytes, 0, concat, 0, uuidBytes.length);
System.arraycopy(profileKeyBytes, 0, concat, uuidBytes.length, profileKeyBytes.length);
return ByteString.copyFrom(concat);
}
private static DecryptedModifyMemberRole promoteAdmin(UUID member) {
return DecryptedModifyMemberRole.newBuilder()
.setUuid(UuidUtil.toByteString(member))
.setRole(Member.Role.ADMINISTRATOR)
.build();
}
private static DecryptedModifyMemberRole demoteAdmin(UUID member) {
return DecryptedModifyMemberRole.newBuilder()
.setUuid(UuidUtil.toByteString(member))
.setRole(Member.Role.DEFAULT)
.build();
}
private Member encryptedMember(UUID uuid, ProfileKey profileKey) {
return Member.newBuilder()
.setPresentation(presentation(uuid, profileKey))
.build();
}
private static DecryptedMember member(UUID uuid) {
return DecryptedMember.newBuilder()
.setUuid(UuidUtil.toByteString(uuid))
.setRole(Member.Role.DEFAULT)
.build();
}
private static DecryptedPendingMemberRemoval pendingMemberRemoval(UUID uuid) {
return DecryptedPendingMemberRemoval.newBuilder()
.setUuid(UuidUtil.toByteString(uuid))
.build();
}
private static DecryptedPendingMember pendingMember(UUID uuid) {
return DecryptedPendingMember.newBuilder()
.setUuid(UuidUtil.toByteString(uuid))
.setRole(Member.Role.DEFAULT)
.build();
}
private static DecryptedMember member(UUID uuid, ProfileKey profileKey) {
return DecryptedMember.newBuilder()
.setUuid(UuidUtil.toByteString(uuid))
.setRole(Member.Role.DEFAULT)
.setProfileKey(ByteString.copyFrom(profileKey.serialize()))
.build();
}
private static DecryptedMember admin(UUID uuid) {
return DecryptedMember.newBuilder()
.setUuid(UuidUtil.toByteString(uuid))
.setRole(Member.Role.ADMINISTRATOR)
.build();
}
}