kopia lustrzana https://github.com/ryukoposting/Signal-Android
Improve GV2 state change processing speed.
rodzic
5080567ca9
commit
6f788ee3df
|
@ -17,7 +17,7 @@ import org.signal.zkgroup.groups.GroupMasterKey;
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup;
|
||||||
import org.whispersystems.signalservice.api.push.ACI;
|
import org.whispersystems.signalservice.api.push.ACI;
|
||||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||||
|
@ -30,19 +30,19 @@ public final class GroupProtoUtil {
|
||||||
private GroupProtoUtil() {
|
private GroupProtoUtil() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int findRevisionWeWereAdded(@NonNull DecryptedGroup group, @NonNull UUID uuid)
|
public static int findRevisionWeWereAdded(@NonNull PartialDecryptedGroup partialDecryptedGroup, @NonNull UUID uuid)
|
||||||
throws GroupNotAMemberException
|
throws GroupNotAMemberException
|
||||||
{
|
{
|
||||||
ByteString bytes = UuidUtil.toByteString(uuid);
|
ByteString bytes = UuidUtil.toByteString(uuid);
|
||||||
for (DecryptedMember decryptedMember : group.getMembersList()) {
|
for (DecryptedMember decryptedMember : partialDecryptedGroup.getMembersList()) {
|
||||||
if (decryptedMember.getUuid().equals(bytes)) {
|
if (decryptedMember.getUuid().equals(bytes)) {
|
||||||
return decryptedMember.getJoinedAtRevision();
|
return decryptedMember.getJoinedAtRevision();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (DecryptedPendingMember decryptedMember : group.getPendingMembersList()) {
|
for (DecryptedPendingMember decryptedMember : partialDecryptedGroup.getPendingMembersList()) {
|
||||||
if (decryptedMember.getUuid().equals(bytes)) {
|
if (decryptedMember.getUuid().equals(bytes)) {
|
||||||
// Assume latest, we don't have any information about when pending members were invited
|
// Assume latest, we don't have any information about when pending members were invited
|
||||||
return group.getRevision();
|
return partialDecryptedGroup.getRevision();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new GroupNotAMemberException();
|
throw new GroupNotAMemberException();
|
||||||
|
|
|
@ -51,6 +51,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupHistoryPage;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
|
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException;
|
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException;
|
||||||
|
import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup;
|
||||||
import org.whispersystems.signalservice.api.push.ACI;
|
import org.whispersystems.signalservice.api.push.ACI;
|
||||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||||
import org.whispersystems.signalservice.internal.push.exceptions.GroupNotFoundException;
|
import org.whispersystems.signalservice.internal.push.exceptions.GroupNotFoundException;
|
||||||
|
@ -302,11 +303,11 @@ public final class GroupsV2StateProcessor {
|
||||||
|
|
||||||
Log.i(TAG, "Paging from server revision: " + (revision == LATEST ? "latest" : revision) + ", latestOnly: " + latestRevisionOnly);
|
Log.i(TAG, "Paging from server revision: " + (revision == LATEST ? "latest" : revision) + ", latestOnly: " + latestRevisionOnly);
|
||||||
|
|
||||||
DecryptedGroup latestServerGroup;
|
PartialDecryptedGroup latestServerGroup;
|
||||||
GlobalGroupState inputGroupState;
|
GlobalGroupState inputGroupState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
latestServerGroup = groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(selfAci, groupSecretParams));
|
latestServerGroup = groupsV2Api.getPartialDecryptedGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(selfAci, groupSecretParams));
|
||||||
} catch (NotInGroupException | GroupNotFoundException e) {
|
} catch (NotInGroupException | GroupNotFoundException e) {
|
||||||
throw new GroupNotAMemberException(e);
|
throw new GroupNotAMemberException(e);
|
||||||
} catch (VerificationFailedException | InvalidGroupStateException e) {
|
} catch (VerificationFailedException | InvalidGroupStateException e) {
|
||||||
|
@ -315,12 +316,12 @@ public final class GroupsV2StateProcessor {
|
||||||
|
|
||||||
if (localState != null && localState.getRevision() >= latestServerGroup.getRevision()) {
|
if (localState != null && localState.getRevision() >= latestServerGroup.getRevision()) {
|
||||||
Log.i(TAG, "Local state is at or later than server");
|
Log.i(TAG, "Local state is at or later than server");
|
||||||
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, latestServerGroup);
|
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (latestRevisionOnly || !GroupProtoUtil.isMember(selfAci.uuid(), latestServerGroup.getMembersList())) {
|
if (latestRevisionOnly || !GroupProtoUtil.isMember(selfAci.uuid(), latestServerGroup.getMembersList())) {
|
||||||
Log.i(TAG, "Latest revision or not a member, use latest only");
|
Log.i(TAG, "Latest revision or not a member, use latest only");
|
||||||
inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(latestServerGroup, null)));
|
inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(latestServerGroup.getFullyDecryptedGroup(), null)));
|
||||||
} else {
|
} else {
|
||||||
int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, selfAci.uuid());
|
int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, selfAci.uuid());
|
||||||
int logsNeededFrom = localState != null ? Math.max(localState.getRevision(), revisionWeWereAdded) : revisionWeWereAdded;
|
int logsNeededFrom = localState != null ? Math.max(localState.getRevision(), revisionWeWereAdded) : revisionWeWereAdded;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import org.mockito.Mockito.verify
|
||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedMember
|
import org.signal.storageservice.protos.groups.local.DecryptedMember
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedTimer
|
import org.signal.storageservice.protos.groups.local.DecryptedTimer
|
||||||
|
@ -40,6 +41,7 @@ import org.thoughtcrime.securesms.testutil.SystemOutLogger
|
||||||
import org.thoughtcrime.securesms.util.Hex.fromStringCondensed
|
import org.thoughtcrime.securesms.util.Hex.fromStringCondensed
|
||||||
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider
|
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api
|
||||||
|
import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup
|
||||||
import org.whispersystems.signalservice.api.push.ACI
|
import org.whispersystems.signalservice.api.push.ACI
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
@ -92,8 +94,13 @@ class GroupsV2StateProcessorTest {
|
||||||
doReturn(data.groupRecord).`when`(groupDatabase).getGroup(any(GroupId.V2::class.java))
|
doReturn(data.groupRecord).`when`(groupDatabase).getGroup(any(GroupId.V2::class.java))
|
||||||
doReturn(!data.groupRecord.isPresent).`when`(groupDatabase).isUnknownGroup(any())
|
doReturn(!data.groupRecord.isPresent).`when`(groupDatabase).isUnknownGroup(any())
|
||||||
|
|
||||||
if (data.serverState != null) {
|
data.serverState?.let { serverState ->
|
||||||
doReturn(data.serverState).`when`(groupsV2API).getGroup(any(), any())
|
val testPartial = object : PartialDecryptedGroup(null, serverState, null, null) {
|
||||||
|
override fun getFullyDecryptedGroup(): DecryptedGroup {
|
||||||
|
return serverState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doReturn(testPartial).`when`(groupsV2API).getPartialDecryptedGroup(any(), any())
|
||||||
}
|
}
|
||||||
|
|
||||||
data.changeSet?.let { changeSet ->
|
data.changeSet?.let { changeSet ->
|
||||||
|
|
|
@ -20,7 +20,6 @@ import org.signal.zkgroup.auth.AuthCredentialResponse;
|
||||||
import org.signal.zkgroup.auth.ClientZkAuthOperations;
|
import org.signal.zkgroup.auth.ClientZkAuthOperations;
|
||||||
import org.signal.zkgroup.groups.ClientZkGroupCipher;
|
import org.signal.zkgroup.groups.ClientZkGroupCipher;
|
||||||
import org.signal.zkgroup.groups.GroupSecretParams;
|
import org.signal.zkgroup.groups.GroupSecretParams;
|
||||||
import org.whispersystems.libsignal.logging.Log;
|
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.push.ACI;
|
import org.whispersystems.signalservice.api.push.ACI;
|
||||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
||||||
|
@ -30,7 +29,6 @@ import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GroupsV2Api {
|
public class GroupsV2Api {
|
||||||
|
@ -85,6 +83,16 @@ public class GroupsV2Api {
|
||||||
socket.putNewGroupsV2Group(group, authorization);
|
socket.putNewGroupsV2Group(group, authorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PartialDecryptedGroup getPartialDecryptedGroup(GroupSecretParams groupSecretParams,
|
||||||
|
GroupsV2AuthorizationString authorization)
|
||||||
|
throws IOException, InvalidGroupStateException, VerificationFailedException
|
||||||
|
{
|
||||||
|
Group group = socket.getGroupsV2Group(authorization);
|
||||||
|
|
||||||
|
return groupsOperations.forGroup(groupSecretParams)
|
||||||
|
.partialDecryptGroup(group);
|
||||||
|
}
|
||||||
|
|
||||||
public DecryptedGroup getGroup(GroupSecretParams groupSecretParams,
|
public DecryptedGroup getGroup(GroupSecretParams groupSecretParams,
|
||||||
GroupsV2AuthorizationString authorization)
|
GroupsV2AuthorizationString authorization)
|
||||||
throws IOException, InvalidGroupStateException, VerificationFailedException
|
throws IOException, InvalidGroupStateException, VerificationFailedException
|
||||||
|
|
|
@ -369,6 +369,38 @@ public final class GroupsV2Operations {
|
||||||
.setMember(member);
|
.setMember(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PartialDecryptedGroup partialDecryptGroup(Group group)
|
||||||
|
throws VerificationFailedException, InvalidGroupStateException
|
||||||
|
{
|
||||||
|
List<Member> membersList = group.getMembersList();
|
||||||
|
List<PendingMember> pendingMembersList = group.getPendingMembersList();
|
||||||
|
List<DecryptedMember> decryptedMembers = new ArrayList<>(membersList.size());
|
||||||
|
List<DecryptedPendingMember> decryptedPendingMembers = new ArrayList<>(pendingMembersList.size());
|
||||||
|
|
||||||
|
for (Member member : membersList) {
|
||||||
|
UUID memberUuid = decryptUuid(member.getUserId());
|
||||||
|
decryptedMembers.add(DecryptedMember.newBuilder()
|
||||||
|
.setUuid(UuidUtil.toByteString(memberUuid))
|
||||||
|
.setJoinedAtRevision(member.getJoinedAtRevision())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PendingMember member : pendingMembersList) {
|
||||||
|
UUID pendingMemberUuid = decryptUuidOrUnknown(member.getMember().getUserId());
|
||||||
|
decryptedMembers.add(DecryptedMember.newBuilder()
|
||||||
|
.setUuid(UuidUtil.toByteString(pendingMemberUuid))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
DecryptedGroup decryptedGroup = DecryptedGroup.newBuilder()
|
||||||
|
.setRevision(group.getRevision())
|
||||||
|
.addAllMembers(decryptedMembers)
|
||||||
|
.addAllPendingMembers(decryptedPendingMembers)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return new PartialDecryptedGroup(group, decryptedGroup, GroupsV2Operations.this, groupSecretParams);
|
||||||
|
}
|
||||||
|
|
||||||
public DecryptedGroup decryptGroup(Group group)
|
public DecryptedGroup decryptGroup(Group group)
|
||||||
throws VerificationFailedException, InvalidGroupStateException
|
throws VerificationFailedException, InvalidGroupStateException
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.whispersystems.signalservice.api.groupsv2;
|
||||||
|
|
||||||
|
import org.signal.storageservice.protos.groups.Group;
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||||
|
import org.signal.zkgroup.VerificationFailedException;
|
||||||
|
import org.signal.zkgroup.groups.GroupSecretParams;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypting an entire group can be expensive for large groups. Since not every
|
||||||
|
* operation requires all data to be decrypted, this class can be populated with only
|
||||||
|
* the minimalist about of information need to perform an operation. Currently, only
|
||||||
|
* updating from the server utilizes it.
|
||||||
|
*/
|
||||||
|
public class PartialDecryptedGroup {
|
||||||
|
private final Group group;
|
||||||
|
private final DecryptedGroup decryptedGroup;
|
||||||
|
private final GroupsV2Operations groupsOperations;
|
||||||
|
private final GroupSecretParams groupSecretParams;
|
||||||
|
|
||||||
|
public PartialDecryptedGroup(Group group,
|
||||||
|
DecryptedGroup decryptedGroup,
|
||||||
|
GroupsV2Operations groupsOperations,
|
||||||
|
GroupSecretParams groupSecretParams)
|
||||||
|
{
|
||||||
|
this.group = group;
|
||||||
|
this.decryptedGroup = decryptedGroup;
|
||||||
|
this.groupsOperations = groupsOperations;
|
||||||
|
this.groupSecretParams = groupSecretParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRevision() {
|
||||||
|
return decryptedGroup.getRevision();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DecryptedMember> getMembersList() {
|
||||||
|
return decryptedGroup.getMembersList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DecryptedPendingMember> getPendingMembersList() {
|
||||||
|
return decryptedGroup.getPendingMembersList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecryptedGroup getFullyDecryptedGroup()
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return groupsOperations.forGroup(groupSecretParams)
|
||||||
|
.decryptGroup(group);
|
||||||
|
} catch (VerificationFailedException | InvalidGroupStateException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Ładowanie…
Reference in New Issue