org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of signal-service-java Show documentation
Show all versions of signal-service-java Show documentation
Signal Service communication library for Java, unofficial fork
package org.whispersystems.signalservice.api.groupsv2;
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
import org.signal.storageservice.protos.groups.local.DecryptedBannedMember;
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.DecryptedRequestingMember;
import org.signal.storageservice.protos.groups.local.DecryptedString;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import okio.ByteString;
public final class GroupChangeReconstruct {
/**
* Given a {@param fromState} and a {@param toState} creates a {@link DecryptedGroupChange} that would take the {@param fromState} to the {@param toState}.
*/
public static DecryptedGroupChange reconstructGroupChange(DecryptedGroup fromState, DecryptedGroup toState) {
DecryptedGroupChange.Builder builder = new DecryptedGroupChange.Builder()
.revision(toState.revision);
if (!fromState.title.equals(toState.title)) {
builder.newTitle(new DecryptedString.Builder().value_(toState.title).build());
}
if (!fromState.description.equals(toState.description)) {
builder.newDescription(new DecryptedString.Builder().value_(toState.description).build());
}
if (!fromState.isAnnouncementGroup.equals(toState.isAnnouncementGroup)) {
builder.newIsAnnouncementGroup(toState.isAnnouncementGroup);
}
if (!fromState.avatar.equals(toState.avatar)) {
builder.newAvatar(new DecryptedString.Builder().value_(toState.avatar).build());
}
if (!Objects.equals(fromState.disappearingMessagesTimer, toState.disappearingMessagesTimer)) {
builder.newTimer(toState.disappearingMessagesTimer);
}
if (fromState.accessControl == null || (toState.accessControl != null && !fromState.accessControl.attributes.equals(toState.accessControl.attributes))) {
if (toState.accessControl != null) {
builder.newAttributeAccess(toState.accessControl.attributes);
}
}
if (fromState.accessControl == null || (toState.accessControl != null && !fromState.accessControl.members.equals(toState.accessControl.members))) {
if (toState.accessControl != null) {
builder.newMemberAccess(toState.accessControl.members);
}
}
Set fromStateMemberAcis = membersToSetOfAcis(fromState.members);
Set toStateMemberAcis = membersToSetOfAcis(toState.members);
Set pendingMembersListA = pendingMembersToSetOfServiceIds(fromState.pendingMembers);
Set pendingMembersListB = pendingMembersToSetOfServiceIds(toState.pendingMembers);
Set requestingMembersListA = requestingMembersToSetOfAcis(fromState.requestingMembers);
Set requestingMembersListB = requestingMembersToSetOfAcis(toState.requestingMembers);
Set bannedMembersListA = bannedMembersToSetOfServiceIds(fromState.bannedMembers);
Set bannedMembersListB = bannedMembersToSetOfServiceIds(toState.bannedMembers);
Set removedPendingMemberServiceIds = subtract(pendingMembersListA, pendingMembersListB);
Set removedRequestingMemberAcis = subtract(requestingMembersListA, requestingMembersListB);
Set newPendingMemberServiceIds = subtract(pendingMembersListB, pendingMembersListA);
Set newRequestingMemberAcis = subtract(requestingMembersListB, requestingMembersListA);
Set removedMemberAcis = subtract(fromStateMemberAcis, toStateMemberAcis);
Set newMemberAcis = subtract(toStateMemberAcis, fromStateMemberAcis);
Set removedBannedMemberServiceIds = subtract(bannedMembersListA, bannedMembersListB);
Set newBannedMemberServiceIds = subtract(bannedMembersListB, bannedMembersListA);
Set addedByInvitationAcis = intersect(newMemberAcis, removedPendingMemberServiceIds);
Set addedByRequestApprovalAcis = intersect(newMemberAcis, removedRequestingMemberAcis);
Set addedMembersByInvitation = intersectByAci(toState.members, addedByInvitationAcis);
Set addedMembersByRequestApproval = intersectByAci(toState.members, addedByRequestApprovalAcis);
Set addedMembers = intersectByAci(toState.members, subtract(newMemberAcis, addedByInvitationAcis, addedByRequestApprovalAcis));
Set uninvitedMembers = intersectPendingByServiceId(fromState.pendingMembers, subtract(removedPendingMemberServiceIds, addedByInvitationAcis));
Set rejectedRequestMembers = intersectRequestingByAci(fromState.requestingMembers, subtract(removedRequestingMemberAcis, addedByRequestApprovalAcis));
builder.deleteMembers(intersectByAci(fromState.members, removedMemberAcis).stream()
.map(m -> m.aciBytes)
.collect(Collectors.toList()));
builder.newMembers(new ArrayList<>(addedMembers));
builder.promotePendingMembers(new ArrayList<>(addedMembersByInvitation));
builder.deletePendingMembers(uninvitedMembers.stream()
.map(uninvitedMember -> new DecryptedPendingMemberRemoval.Builder()
.serviceIdBytes(uninvitedMember.serviceIdBytes)
.serviceIdCipherText(uninvitedMember.serviceIdCipherText)
.build())
.collect(Collectors.toList()));
builder.newPendingMembers(new ArrayList<>(intersectPendingByServiceId(toState.pendingMembers, newPendingMemberServiceIds)));
Set consistentMemberAcis = intersect(fromStateMemberAcis, toStateMemberAcis);
Set changedMembers = intersectByAci(subtract(toState.members, fromState.members), consistentMemberAcis);
Map membersAciMap = mapByAci(fromState.members);
Map bannedMembersServiceIdMap = bannedServiceIdMap(toState.bannedMembers);
List modifiedMemberRoles = new ArrayList<>(changedMembers.size());
List modifiedProfileKeys = new ArrayList<>(changedMembers.size());
for (DecryptedMember newState : changedMembers) {
DecryptedMember oldState = membersAciMap.get(newState.aciBytes);
if (oldState.role != newState.role) {
modifiedMemberRoles.add(new DecryptedModifyMemberRole.Builder()
.aciBytes(newState.aciBytes)
.role(newState.role)
.build());
}
if (!oldState.profileKey.equals(newState.profileKey)) {
modifiedProfileKeys.add(newState);
}
}
builder.modifyMemberRoles(modifiedMemberRoles);
builder.modifiedProfileKeys(modifiedProfileKeys);
if (fromState.accessControl == null || (toState.accessControl != null && !fromState.accessControl.addFromInviteLink.equals(toState.accessControl.addFromInviteLink))) {
if (toState.accessControl != null) {
builder.newInviteLinkAccess(toState.accessControl.addFromInviteLink);
}
}
builder.newRequestingMembers(new ArrayList<>(intersectRequestingByAci(toState.requestingMembers, newRequestingMemberAcis)));
builder.deleteRequestingMembers(rejectedRequestMembers.stream().map(requestingMember -> requestingMember.aciBytes).collect(Collectors.toList()));
builder.promoteRequestingMembers(addedMembersByRequestApproval.stream()
.map(member -> new DecryptedApproveMember.Builder()
.aciBytes(member.aciBytes)
.role(member.role)
.build())
.collect(Collectors.toList()));
if (!fromState.inviteLinkPassword.equals(toState.inviteLinkPassword)) {
builder.newInviteLinkPassword(toState.inviteLinkPassword);
}
builder.deleteBannedMembers(removedBannedMemberServiceIds.stream().map(serviceIdBinary -> new DecryptedBannedMember.Builder().serviceIdBytes(serviceIdBinary).build()).collect(Collectors.toList()));
builder.newBannedMembers(newBannedMemberServiceIds.stream()
.map(serviceIdBinary -> {
DecryptedBannedMember.Builder newBannedBuilder = new DecryptedBannedMember.Builder().serviceIdBytes(serviceIdBinary);
DecryptedBannedMember bannedMember = bannedMembersServiceIdMap.get(serviceIdBinary);
if (bannedMember != null) {
newBannedBuilder.timestamp(bannedMember.timestamp);
}
return newBannedBuilder.build();
})
.collect(Collectors.toList()));
return builder.build();
}
private static Map mapByAci(List membersList) {
Map map = new LinkedHashMap<>(membersList.size());
for (DecryptedMember member : membersList) {
map.put(member.aciBytes, member);
}
return map;
}
private static Map bannedServiceIdMap(List membersList) {
Map map = new LinkedHashMap<>(membersList.size());
for (DecryptedBannedMember member : membersList) {
map.put(member.serviceIdBytes, member);
}
return map;
}
private static Set intersectByAci(Collection members, Set acis) {
Set result = new LinkedHashSet<>(members.size());
for (DecryptedMember member : members) {
if (acis.contains(member.aciBytes))
result.add(member);
}
return result;
}
private static Set intersectPendingByServiceId(Collection members, Set serviceIds) {
Set result = new LinkedHashSet<>(members.size());
for (DecryptedPendingMember member : members) {
if (serviceIds.contains(member.serviceIdBytes))
result.add(member);
}
return result;
}
private static Set intersectRequestingByAci(Collection members, Set acis) {
Set result = new LinkedHashSet<>(members.size());
for (DecryptedRequestingMember member : members) {
if (acis.contains(member.aciBytes))
result.add(member);
}
return result;
}
private static Set pendingMembersToSetOfServiceIds(Collection pendingMembers) {
Set serviceIds = new LinkedHashSet<>(pendingMembers.size());
for (DecryptedPendingMember pendingMember : pendingMembers) {
serviceIds.add(pendingMember.serviceIdBytes);
}
return serviceIds;
}
private static Set requestingMembersToSetOfAcis(Collection requestingMembers) {
Set acis = new LinkedHashSet<>(requestingMembers.size());
for (DecryptedRequestingMember requestingMember : requestingMembers) {
acis.add(requestingMember.aciBytes);
}
return acis;
}
private static Set membersToSetOfAcis(Collection members) {
Set acis = new LinkedHashSet<>(members.size());
for (DecryptedMember member : members) {
acis.add(member.aciBytes);
}
return acis;
}
private static Set bannedMembersToSetOfServiceIds(Collection bannedMembers) {
Set serviceIds = new LinkedHashSet<>(bannedMembers.size());
for (DecryptedBannedMember bannedMember : bannedMembers) {
serviceIds.add(bannedMember.serviceIdBytes);
}
return serviceIds;
}
private static Set subtract(Collection a, Collection b) {
Set result = new LinkedHashSet<>(a);
result.removeAll(b);
return result;
}
private static Set subtract(Collection a, Collection b, Collection c) {
Set result = new LinkedHashSet<>(a);
result.removeAll(b);
result.removeAll(c);
return result;
}
private static Set intersect(Collection a, Collection b) {
Set result = new LinkedHashSet<>(a);
result.retainAll(b);
return result;
}
}