mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 17:29:32 +01:00
Add Group Send Endorsements support.
This commit is contained in:
@@ -172,6 +172,16 @@ public final class GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static void updateGroupSendEndorsements(@NonNull Context context,
|
||||
@NonNull GroupMasterKey groupMasterKey)
|
||||
throws GroupChangeBusyException, IOException, GroupNotAMemberException
|
||||
{
|
||||
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
|
||||
updater.updateGroupSendEndorsements();
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static void setMemberAdmin(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.protos.groups.GroupChange;
|
||||
import org.signal.storageservice.protos.groups.GroupChangeResponse;
|
||||
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||
import org.signal.storageservice.protos.groups.Member;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
@@ -49,6 +50,8 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupResponse;
|
||||
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct;
|
||||
@@ -219,16 +222,18 @@ final class GroupManagerV2 {
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception
|
||||
{
|
||||
GroupSecretParams groupSecretParams = GroupSecretParams.generate();
|
||||
DecryptedGroup decryptedGroup;
|
||||
DecryptedGroupResponse createGroupResponse;
|
||||
|
||||
try {
|
||||
decryptedGroup = createGroupOnServer(groupSecretParams, name, avatar, members, disappearingMessagesTimer);
|
||||
createGroupResponse = createGroupOnServer(groupSecretParams, name, avatar, members, disappearingMessagesTimer);
|
||||
} catch (GroupAlreadyExistsException e) {
|
||||
throw new GroupChangeFailedException(e);
|
||||
}
|
||||
|
||||
GroupMasterKey masterKey = groupSecretParams.getMasterKey();
|
||||
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup);
|
||||
DecryptedGroup decryptedGroup = createGroupResponse.getGroup();
|
||||
GroupMasterKey masterKey = groupSecretParams.getMasterKey();
|
||||
ReceivedGroupSendEndorsements groupSendEndorsements = groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroup, createGroupResponse.getGroupSendEndorsementsResponse());
|
||||
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup, groupSendEndorsements);
|
||||
|
||||
if (groupId == null) {
|
||||
throw new GroupChangeFailedException("Unable to create group, group already exists");
|
||||
@@ -670,7 +675,8 @@ final class GroupManagerV2 {
|
||||
|
||||
previousGroupState = v2GroupProperties.getDecryptedGroup();
|
||||
|
||||
GroupChange signedGroupChange = commitToServer(changeActions);
|
||||
GroupChangeResponse changeResponse = commitToServer(changeActions);
|
||||
GroupChange signedGroupChange = changeResponse.groupChange;
|
||||
try {
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
|
||||
@@ -680,7 +686,7 @@ final class GroupManagerV2 {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
groupDatabase.update(groupId, decryptedGroupState);
|
||||
groupDatabase.update(groupId, decryptedGroupState, groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroupState, changeResponse.groupSendEndorsementsResponse));
|
||||
|
||||
GroupMutation groupMutation = new GroupMutation(previousGroupState, decryptedChange, decryptedGroupState);
|
||||
RecipientAndThread recipientAndThread = sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange, sendToMembers);
|
||||
@@ -690,7 +696,7 @@ final class GroupManagerV2 {
|
||||
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient, recipientAndThread.threadId, newMembersCount, newPendingMembers);
|
||||
}
|
||||
|
||||
private @NonNull GroupChange commitToServer(@NonNull GroupChange.Actions change)
|
||||
private @NonNull GroupChangeResponse commitToServer(@NonNull GroupChange.Actions change)
|
||||
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
|
||||
{
|
||||
try {
|
||||
@@ -747,6 +753,14 @@ final class GroupManagerV2 {
|
||||
.forceSanityUpdateFromServer(timestamp);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
void updateGroupSendEndorsements()
|
||||
throws IOException, GroupNotAMemberException
|
||||
{
|
||||
GroupsV2StateProcessor.forGroup(serviceIds, groupMasterKey)
|
||||
.updateGroupSendEndorsements();
|
||||
}
|
||||
|
||||
private DecryptedGroupChange getDecryptedGroupChange(@Nullable byte[] signedGroupChange) {
|
||||
if (signedGroupChange != null && signedGroupChange.length > 0) {
|
||||
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey));
|
||||
@@ -764,11 +778,11 @@ final class GroupManagerV2 {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private @NonNull DecryptedGroup createGroupOnServer(@NonNull GroupSecretParams groupSecretParams,
|
||||
@Nullable String name,
|
||||
@Nullable byte[] avatar,
|
||||
@NonNull Collection<RecipientId> members,
|
||||
int disappearingMessageTimerSeconds)
|
||||
private @NonNull DecryptedGroupResponse createGroupOnServer(@NonNull GroupSecretParams groupSecretParams,
|
||||
@Nullable String name,
|
||||
@Nullable byte[] avatar,
|
||||
@NonNull Collection<RecipientId> members,
|
||||
int disappearingMessageTimerSeconds)
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException
|
||||
{
|
||||
if (!GroupsV2CapabilityChecker.allAndSelfHaveServiceId(members)) {
|
||||
@@ -797,15 +811,10 @@ final class GroupManagerV2 {
|
||||
disappearingMessageTimerSeconds);
|
||||
|
||||
try {
|
||||
groupsV2Api.putNewGroup(newGroup, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
|
||||
DecryptedGroupResponse groupResponse = groupsV2Api.putNewGroup(newGroup, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
|
||||
|
||||
DecryptedGroup decryptedGroup = groupsV2Api.getGroup(groupSecretParams, AppDependencies.getGroupsV2Authorization().getAuthorizationForToday(serviceIds, groupSecretParams));
|
||||
if (decryptedGroup == null) {
|
||||
throw new GroupChangeFailedException();
|
||||
}
|
||||
|
||||
return decryptedGroup;
|
||||
} catch (VerificationFailedException | InvalidGroupStateException e) {
|
||||
return groupResponse;
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | InvalidInputException e) {
|
||||
throw new GroupChangeFailedException(e);
|
||||
} catch (GroupExistsException e) {
|
||||
throw new GroupAlreadyExistsException(e);
|
||||
@@ -837,8 +846,9 @@ final class GroupManagerV2 {
|
||||
@Nullable byte[] avatar)
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupLinkNotActiveException
|
||||
{
|
||||
boolean requestToJoin = joinInfo.addFromInviteLink == AccessControl.AccessRequired.ADMINISTRATOR;
|
||||
boolean alreadyAMember = false;
|
||||
boolean requestToJoin = joinInfo.addFromInviteLink == AccessControl.AccessRequired.ADMINISTRATOR;
|
||||
boolean alreadyAMember = false;
|
||||
boolean groupAlreadyExists = false;
|
||||
|
||||
if (requestToJoin) {
|
||||
Log.i(TAG, "Requesting to join " + groupId);
|
||||
@@ -846,10 +856,13 @@ final class GroupManagerV2 {
|
||||
Log.i(TAG, "Joining " + groupId);
|
||||
}
|
||||
|
||||
GroupChange signedGroupChange = null;
|
||||
DecryptedGroupChange decryptedChange = null;
|
||||
GroupChangeResponse groupChangeResponse = null;
|
||||
GroupChange signedGroupChange = null;
|
||||
DecryptedGroupChange decryptedChange = null;
|
||||
|
||||
try {
|
||||
signedGroupChange = joinGroupOnServer(requestToJoin, joinInfo.revision);
|
||||
groupChangeResponse = joinGroupOnServer(requestToJoin, joinInfo.revision);
|
||||
signedGroupChange = groupChangeResponse.groupChange;
|
||||
|
||||
if (requestToJoin) {
|
||||
Log.i(TAG, String.format("Successfully requested to join %s on server", groupId));
|
||||
@@ -857,7 +870,7 @@ final class GroupManagerV2 {
|
||||
Log.i(TAG, String.format("Successfully added self to %s on server", groupId));
|
||||
}
|
||||
|
||||
decryptedChange = decryptChange(signedGroupChange);
|
||||
decryptedChange = decryptChange(Objects.requireNonNull(signedGroupChange));
|
||||
} catch (GroupJoinAlreadyAMemberException e) {
|
||||
Log.i(TAG, "Server reports that we are already a member of " + groupId);
|
||||
alreadyAMember = true;
|
||||
@@ -869,28 +882,37 @@ final class GroupManagerV2 {
|
||||
|
||||
if (group.isPresent()) {
|
||||
Log.i(TAG, "Group already present locally");
|
||||
if (decryptedChange != null) {
|
||||
try {
|
||||
GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
|
||||
.updateLocalGroupToRevision(decryptedChange.revision, System.currentTimeMillis(), decryptedChange);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Unable to apply join change to existing group", e);
|
||||
}
|
||||
}
|
||||
groupAlreadyExists = true;
|
||||
} else {
|
||||
GroupId.V2 groupId = groupDatabase.create(groupMasterKey, decryptedGroup);
|
||||
GroupId.V2 groupId = groupDatabase.create(groupMasterKey, decryptedGroup, null);
|
||||
|
||||
if (groupId != null) {
|
||||
Log.i(TAG, "Created local group with placeholder");
|
||||
} else {
|
||||
Log.i(TAG, "Create placeholder failed, group suddenly present locally, attempting to apply change");
|
||||
if (decryptedChange != null) {
|
||||
try {
|
||||
GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
|
||||
.updateLocalGroupToRevision(decryptedChange.revision, System.currentTimeMillis(), decryptedChange);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Unable to apply join change to existing group", e);
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Create placeholder failed, group suddenly present locally");
|
||||
groupAlreadyExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (groupAlreadyExists) {
|
||||
Log.i(TAG, "Attempting to update local group with change/server");
|
||||
try {
|
||||
GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
|
||||
.updateLocalGroupToRevision(decryptedChange != null ? decryptedChange.revision : GroupsV2StateProcessor.LATEST, System.currentTimeMillis(), decryptedChange);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Despite adding self to group, change/server says we are not a member, scheduling refresh of group info " + groupId, e);
|
||||
|
||||
AppDependencies.getJobManager()
|
||||
.add(new RequestGroupV2InfoJob(groupId));
|
||||
|
||||
throw new GroupChangeFailedException(e);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Group data fetch failed, scheduling refresh of group info " + groupId, e);
|
||||
|
||||
AppDependencies.getJobManager()
|
||||
.add(new RequestGroupV2InfoJob(groupId));
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1006,7 +1028,7 @@ final class GroupManagerV2 {
|
||||
return group.build();
|
||||
}
|
||||
|
||||
private @NonNull GroupChange joinGroupOnServer(boolean requestToJoin, int currentRevision)
|
||||
private @NonNull GroupChangeResponse joinGroupOnServer(boolean requestToJoin, int currentRevision)
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupLinkNotActiveException, GroupJoinAlreadyAMemberException
|
||||
{
|
||||
if (!GroupsV2CapabilityChecker.allAndSelfHaveServiceId(Collections.singleton(Recipient.self().getId()))) {
|
||||
@@ -1029,7 +1051,7 @@ final class GroupManagerV2 {
|
||||
return commitJoinChangeWithConflictResolution(currentRevision, change);
|
||||
}
|
||||
|
||||
private @NonNull GroupChange commitJoinChangeWithConflictResolution(int currentRevision, @NonNull GroupChange.Actions.Builder change)
|
||||
private @NonNull GroupChangeResponse commitJoinChangeWithConflictResolution(int currentRevision, @NonNull GroupChange.Actions.Builder change)
|
||||
throws GroupChangeFailedException, IOException, GroupLinkNotActiveException, GroupJoinAlreadyAMemberException
|
||||
{
|
||||
for (int attempt = 0; attempt < 5; attempt++) {
|
||||
@@ -1038,10 +1060,10 @@ final class GroupManagerV2 {
|
||||
.build();
|
||||
|
||||
Log.i(TAG, "Trying to join group at V" + changeActions.revision);
|
||||
GroupChange signedGroupChange = commitJoinToServer(changeActions);
|
||||
GroupChangeResponse changeResponse = commitJoinToServer(changeActions);
|
||||
|
||||
Log.i(TAG, "Successfully joined group at V" + changeActions.revision);
|
||||
return signedGroupChange;
|
||||
return changeResponse;
|
||||
} catch (GroupPatchNotAcceptedException e) {
|
||||
Log.w(TAG, "Patch not accepted", e);
|
||||
|
||||
@@ -1051,7 +1073,7 @@ final class GroupManagerV2 {
|
||||
} else {
|
||||
throw new GroupChangeFailedException(e);
|
||||
}
|
||||
} catch (VerificationFailedException | InvalidGroupStateException ex) {
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | InvalidInputException ex) {
|
||||
throw new GroupChangeFailedException(ex);
|
||||
}
|
||||
} catch (ConflictException e) {
|
||||
@@ -1064,7 +1086,7 @@ final class GroupManagerV2 {
|
||||
throw new GroupChangeFailedException("Unable to join group after conflicts");
|
||||
}
|
||||
|
||||
private @NonNull GroupChange commitJoinToServer(@NonNull GroupChange.Actions change)
|
||||
private @NonNull GroupChangeResponse commitJoinToServer(@NonNull GroupChange.Actions change)
|
||||
throws GroupChangeFailedException, IOException, GroupLinkNotActiveException
|
||||
{
|
||||
try {
|
||||
@@ -1109,7 +1131,7 @@ final class GroupManagerV2 {
|
||||
}
|
||||
|
||||
private boolean testGroupMembership()
|
||||
throws IOException, VerificationFailedException, InvalidGroupStateException
|
||||
throws IOException, VerificationFailedException, InvalidGroupStateException, InvalidInputException
|
||||
{
|
||||
try {
|
||||
groupsV2Api.getGroup(groupSecretParams, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
|
||||
@@ -1140,7 +1162,7 @@ final class GroupManagerV2 {
|
||||
DecryptedGroupChange decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.applyWithoutRevisionCheck(decryptedGroup, decryptedChange);
|
||||
|
||||
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.revision));
|
||||
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.revision), null);
|
||||
|
||||
sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, new GroupMutation(decryptedGroup, decryptedChange, newGroup), signedGroupChange, false);
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
|
||||
@@ -1165,7 +1187,7 @@ final class GroupManagerV2 {
|
||||
.build();
|
||||
|
||||
Log.i(TAG, "Trying to cancel request group at V" + changeActions.revision);
|
||||
GroupChange signedGroupChange = commitJoinToServer(changeActions);
|
||||
GroupChange signedGroupChange = commitJoinToServer(changeActions).groupChange;
|
||||
|
||||
Log.i(TAG, "Successfully cancelled group join at V" + changeActions.revision);
|
||||
return signedGroupChange;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.groups.v2.processing
|
||||
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog
|
||||
@@ -9,10 +10,15 @@ import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog
|
||||
*/
|
||||
class GroupStateDiff(
|
||||
val previousGroupState: DecryptedGroup?,
|
||||
val serverHistory: List<DecryptedGroupChangeLog>
|
||||
val serverHistory: List<DecryptedGroupChangeLog>,
|
||||
val groupSendEndorsementsResponse: GroupSendEndorsementsResponse?
|
||||
) {
|
||||
|
||||
constructor(previousGroupState: DecryptedGroup?, changedGroupState: DecryptedGroup?, change: DecryptedGroupChange?) : this(previousGroupState, listOf(DecryptedGroupChangeLog(changedGroupState, change)))
|
||||
constructor(
|
||||
previousGroupState: DecryptedGroup?,
|
||||
changedGroupState: DecryptedGroup?,
|
||||
change: DecryptedGroupChange?
|
||||
) : this(previousGroupState, listOf(DecryptedGroupChangeLog(changedGroupState, change)), null)
|
||||
|
||||
val earliestRevisionNumber: Int
|
||||
get() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.annotation.VisibleForTesting
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.signal.libsignal.zkgroup.groups.GroupSecretParams
|
||||
@@ -43,6 +44,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupHistoryPage
|
||||
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException
|
||||
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException
|
||||
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceIds
|
||||
@@ -95,8 +97,9 @@ class GroupsV2StateProcessor private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val groupsApi = AppDependencies.signalServiceAccountManager.groupsV2Api
|
||||
private val groupsApi = AppDependencies.signalServiceAccountManager.getGroupsV2Api()
|
||||
private val groupsV2Authorization = AppDependencies.groupsV2Authorization
|
||||
private val groupOperations = AppDependencies.groupsV2Operations.forGroup(groupSecretParams)
|
||||
private val groupId = GroupId.v2(groupSecretParams.getPublicParams().getGroupIdentifier())
|
||||
private val profileAndMessageHelper = ProfileAndMessageHelper.create(serviceIds.aci, groupMasterKey, groupId)
|
||||
|
||||
@@ -124,6 +127,36 @@ class GroupsV2StateProcessor private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and save the latest group send endorsements from the server. This endorsements returned may
|
||||
* not match our local view of the membership if the membership has changed on the server and we haven't updated the
|
||||
* group state yet. This is only an issue when trying to send to a group member that has been removed and should be handled
|
||||
* gracefully as a fallback in the sending flow.
|
||||
*/
|
||||
@WorkerThread
|
||||
@Throws(IOException::class, GroupNotAMemberException::class)
|
||||
fun updateGroupSendEndorsements() {
|
||||
val result = groupsApi.getGroupAsResult(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams))
|
||||
|
||||
val groupResponse = when (result) {
|
||||
is NetworkResult.Success -> result.result
|
||||
else -> when (val cause = result.getCause()!!) {
|
||||
is NotInGroupException, is GroupNotFoundException -> throw GroupNotAMemberException(cause)
|
||||
is IOException -> throw cause
|
||||
else -> throw IOException(cause)
|
||||
}
|
||||
}
|
||||
|
||||
val receivedGroupSendEndorsements = groupOperations.receiveGroupSendEndorsements(serviceIds.aci, groupResponse.group, groupResponse.groupSendEndorsementsResponse)
|
||||
|
||||
if (receivedGroupSendEndorsements != null) {
|
||||
Log.i(TAG, "$logPrefix Updating group send endorsements")
|
||||
SignalDatabase.groups.updateGroupSendEndorsements(groupId, receivedGroupSendEndorsements)
|
||||
} else {
|
||||
Log.w(TAG, "$logPrefix No group send endorsements on response")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Using network where required, will attempt to bring the local copy of the group up to the revision specified.
|
||||
*
|
||||
@@ -233,7 +266,8 @@ class GroupsV2StateProcessor private constructor(
|
||||
return saveGroupUpdate(
|
||||
timestamp = timestamp,
|
||||
serverGuid = serverGuid,
|
||||
groupStateDiff = groupStateDiff
|
||||
groupStateDiff = groupStateDiff,
|
||||
groupSendEndorsements = null
|
||||
)
|
||||
}
|
||||
|
||||
@@ -259,6 +293,8 @@ class GroupsV2StateProcessor private constructor(
|
||||
else -> return InternalUpdateResult.from(result.getCause()!!)
|
||||
}
|
||||
|
||||
val sendEndorsementExpiration = groupRecord.map { it.groupSendEndorsementExpiration }.orElse(0L)
|
||||
|
||||
var includeFirstState = currentLocalState == null ||
|
||||
currentLocalState.revision < 0 ||
|
||||
currentLocalState.revision == joinedAtRevision ||
|
||||
@@ -273,20 +309,30 @@ class GroupsV2StateProcessor private constructor(
|
||||
var hasRemainingRemoteChanges = false
|
||||
|
||||
while (hasMore) {
|
||||
Log.i(TAG, "$logPrefix Requesting change logs from server, currentRevision=${currentLocalState?.revision ?: "null"} logsNeededFrom=$logsNeededFrom includeFirstState=$includeFirstState")
|
||||
Log.i(TAG, "$logPrefix Requesting change logs from server, currentRevision=${currentLocalState?.revision ?: "null"} logsNeededFrom=$logsNeededFrom includeFirstState=$includeFirstState sendEndorsementExpiration=${sendEndorsementExpiration > 0}")
|
||||
|
||||
val (remoteGroupStateDiff, pagingData) = getGroupChangeLogs(currentLocalState, logsNeededFrom, includeFirstState)
|
||||
val (remoteGroupStateDiff, pagingData) = getGroupChangeLogs(currentLocalState, logsNeededFrom, includeFirstState, sendEndorsementExpiration)
|
||||
val applyGroupStateDiffResult: AdvanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(remoteGroupStateDiff, targetRevision)
|
||||
val updatedGroupState: DecryptedGroup? = applyGroupStateDiffResult.updatedGroupState
|
||||
|
||||
if (updatedGroupState == null || updatedGroupState == remoteGroupStateDiff.previousGroupState) {
|
||||
Log.i(TAG, "$logPrefix Local state is at or later than server revision: ${currentLocalState?.revision ?: "null"}")
|
||||
|
||||
if (currentLocalState != null) {
|
||||
val endorsements = groupOperations.receiveGroupSendEndorsements(serviceIds.aci, currentLocalState, remoteGroupStateDiff.groupSendEndorsementsResponse)
|
||||
|
||||
if (endorsements != null) {
|
||||
Log.i(TAG, "$logPrefix Received updated send endorsements, saving")
|
||||
SignalDatabase.groups.updateGroupSendEndorsements(groupId, endorsements)
|
||||
}
|
||||
}
|
||||
|
||||
return InternalUpdateResult.NoUpdateNeeded
|
||||
}
|
||||
|
||||
Log.i(TAG, "$logPrefix Saving updated group state at revision: ${updatedGroupState.revision}")
|
||||
|
||||
saveGroupState(remoteGroupStateDiff, updatedGroupState)
|
||||
saveGroupState(remoteGroupStateDiff, updatedGroupState, groupOperations.receiveGroupSendEndorsements(serviceIds.aci, updatedGroupState, remoteGroupStateDiff.groupSendEndorsementsResponse))
|
||||
|
||||
if (addMessagesForAllUpdates) {
|
||||
Log.d(TAG, "$logPrefix Inserting group changes into chat history")
|
||||
@@ -320,7 +366,7 @@ class GroupsV2StateProcessor private constructor(
|
||||
}
|
||||
|
||||
if (!addMessagesForAllUpdates) {
|
||||
Log.i(TAG, "Inserting single update message for restore placeholder")
|
||||
Log.i(TAG, "$logPrefix Inserting single update message for restore placeholder")
|
||||
profileAndMessageHelper.insertUpdateMessages(runningTimestamp, null, setOf(AppliedGroupChangeLog(currentLocalState!!, null)), serverGuid)
|
||||
}
|
||||
|
||||
@@ -341,19 +387,20 @@ class GroupsV2StateProcessor private constructor(
|
||||
private fun updateToLatestViaServer(timestamp: Long, currentLocalState: DecryptedGroup?, reconstructChange: Boolean): InternalUpdateResult {
|
||||
val result = groupsApi.getGroupAsResult(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams))
|
||||
|
||||
val serverState = if (result is NetworkResult.Success) {
|
||||
val groupResponse = if (result is NetworkResult.Success) {
|
||||
result.result
|
||||
} else {
|
||||
return InternalUpdateResult.from(result.getCause()!!)
|
||||
}
|
||||
|
||||
val completeGroupChange = if (reconstructChange) GroupChangeReconstruct.reconstructGroupChange(currentLocalState, serverState) else null
|
||||
val remoteGroupStateDiff = GroupStateDiff(currentLocalState, serverState, completeGroupChange)
|
||||
val completeGroupChange = if (reconstructChange) GroupChangeReconstruct.reconstructGroupChange(currentLocalState, groupResponse.group) else null
|
||||
val remoteGroupStateDiff = GroupStateDiff(currentLocalState, groupResponse.group, completeGroupChange)
|
||||
|
||||
return saveGroupUpdate(
|
||||
timestamp = timestamp,
|
||||
serverGuid = null,
|
||||
groupStateDiff = remoteGroupStateDiff
|
||||
groupStateDiff = remoteGroupStateDiff,
|
||||
groupSendEndorsements = groupOperations.receiveGroupSendEndorsements(serviceIds.aci, groupResponse.group, groupResponse.groupSendEndorsementsResponse)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -403,22 +450,30 @@ class GroupsV2StateProcessor private constructor(
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun getGroupChangeLogs(localState: DecryptedGroup?, logsNeededFromRevision: Int, includeFirstState: Boolean): Pair<GroupStateDiff, GroupHistoryPage.PagingData> {
|
||||
private fun getGroupChangeLogs(
|
||||
localState: DecryptedGroup?,
|
||||
logsNeededFromRevision: Int,
|
||||
includeFirstState: Boolean,
|
||||
sendEndorsementsExpirationMs: Long
|
||||
): Pair<GroupStateDiff, GroupHistoryPage.PagingData> {
|
||||
try {
|
||||
val groupHistoryPage = groupsApi.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), includeFirstState)
|
||||
val groupHistoryPage = groupsApi.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), includeFirstState, sendEndorsementsExpirationMs)
|
||||
|
||||
return GroupStateDiff(localState, groupHistoryPage.changeLogs) to groupHistoryPage.pagingData
|
||||
return GroupStateDiff(localState, groupHistoryPage.changeLogs, groupHistoryPage.groupSendEndorsementsResponse) to groupHistoryPage.pagingData
|
||||
} catch (e: InvalidGroupStateException) {
|
||||
throw IOException(e)
|
||||
} catch (e: VerificationFailedException) {
|
||||
throw IOException(e)
|
||||
} catch (e: InvalidInputException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveGroupUpdate(
|
||||
timestamp: Long,
|
||||
serverGuid: String?,
|
||||
groupStateDiff: GroupStateDiff
|
||||
groupStateDiff: GroupStateDiff,
|
||||
groupSendEndorsements: ReceivedGroupSendEndorsements?
|
||||
): InternalUpdateResult {
|
||||
val currentLocalState: DecryptedGroup? = groupStateDiff.previousGroupState
|
||||
val applyGroupStateDiffResult = GroupStatePatcher.applyGroupStateDiff(groupStateDiff, GroupStatePatcher.LATEST)
|
||||
@@ -426,12 +481,18 @@ class GroupsV2StateProcessor private constructor(
|
||||
|
||||
if (updatedGroupState == null || updatedGroupState == groupStateDiff.previousGroupState) {
|
||||
Log.i(TAG, "$logPrefix Local state and server state are equal")
|
||||
|
||||
if (groupSendEndorsements != null) {
|
||||
Log.i(TAG, "$logPrefix Saving new send endorsements")
|
||||
SignalDatabase.groups.updateGroupSendEndorsements(groupId, groupSendEndorsements)
|
||||
}
|
||||
|
||||
return InternalUpdateResult.NoUpdateNeeded
|
||||
} else {
|
||||
Log.i(TAG, "$logPrefix Local state (revision: ${currentLocalState?.revision}) does not match, updating to ${updatedGroupState.revision}")
|
||||
}
|
||||
|
||||
saveGroupState(groupStateDiff, updatedGroupState)
|
||||
saveGroupState(groupStateDiff, updatedGroupState, groupSendEndorsements)
|
||||
|
||||
if (currentLocalState == null || currentLocalState.revision == RESTORE_PLACEHOLDER_REVISION) {
|
||||
Log.i(TAG, "$logPrefix Inserting single update message for no local state or restore placeholder")
|
||||
@@ -453,20 +514,24 @@ class GroupsV2StateProcessor private constructor(
|
||||
return InternalUpdateResult.Updated(updatedGroupState)
|
||||
}
|
||||
|
||||
private fun saveGroupState(groupStateDiff: GroupStateDiff, updatedGroupState: DecryptedGroup) {
|
||||
private fun saveGroupState(groupStateDiff: GroupStateDiff, updatedGroupState: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?) {
|
||||
val previousGroupState = groupStateDiff.previousGroupState
|
||||
|
||||
if (groupSendEndorsements != null) {
|
||||
Log.i(TAG, "$logPrefix Updating send endorsements")
|
||||
}
|
||||
|
||||
val needsAvatarFetch = if (previousGroupState == null) {
|
||||
val groupId = SignalDatabase.groups.create(groupMasterKey, updatedGroupState)
|
||||
val groupId = SignalDatabase.groups.create(groupMasterKey, updatedGroupState, groupSendEndorsements)
|
||||
|
||||
if (groupId == null) {
|
||||
Log.w(TAG, "$logPrefix Group create failed, trying to update")
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState)
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements)
|
||||
}
|
||||
|
||||
updatedGroupState.avatar.isNotEmpty()
|
||||
} else {
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState)
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements)
|
||||
|
||||
updatedGroupState.avatar != previousGroupState.avatar
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user