Fix out-of-sync local state after rejoining a group via invite link.

This commit is contained in:
Cody Henthorne
2022-10-05 17:09:28 -04:00
committed by Greyson Parrelli
parent 3895578d51
commit 26709177d2
13 changed files with 487 additions and 76 deletions

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.groups;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.DatabaseId;
import org.signal.core.util.Hex;
import org.signal.libsignal.protocol.kdf.HKDFv3;
import org.signal.libsignal.zkgroup.InvalidInputException;
@@ -14,7 +15,7 @@ import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
import java.security.SecureRandom;
public abstract class GroupId {
public abstract class GroupId implements DatabaseId {
private static final String ENCODED_SIGNAL_GROUP_V1_PREFIX = "__textsecure_group__!";
private static final String ENCODED_SIGNAL_GROUP_V2_PREFIX = "__signal_group__v2__!";
@@ -173,6 +174,11 @@ public abstract class GroupId {
return encodedId;
}
@Override
public @NonNull String serialize() {
return encodedId;
}
public abstract boolean isMms();
public abstract boolean isV1();

View File

@@ -188,6 +188,17 @@ public final class GroupManager {
}
}
@WorkerThread
public static void forceSanityUpdateFromServer(@NonNull Context context,
@NonNull GroupMasterKey groupMasterKey,
long timestamp)
throws GroupChangeBusyException, IOException, GroupNotAMemberException
{
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
updater.forceSanityUpdateFromServer(timestamp);
}
}
@WorkerThread
public static V2GroupServerStatus v2GroupStatus(@NonNull Context context,
@NonNull ServiceId authServiceId,

View File

@@ -798,6 +798,14 @@ final class GroupManagerV2 {
.updateLocalGroupToRevision(revision, timestamp, getDecryptedGroupChange(signedGroupChange));
}
@WorkerThread
void forceSanityUpdateFromServer(long timestamp)
throws IOException, GroupNotAMemberException
{
new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey)
.forceSanityUpdateFromServer(timestamp);
}
private DecryptedGroupChange getDecryptedGroupChange(@Nullable byte[] signedGroupChange) {
if (signedGroupChange != null) {
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey));
@@ -928,24 +936,6 @@ final class GroupManagerV2 {
if (group.isPresent()) {
Log.i(TAG, "Group already present locally");
DecryptedGroup currentGroupState = group.get()
.requireV2GroupProperties()
.getDecryptedGroup();
DecryptedGroup updatedGroup = currentGroupState;
try {
if (decryptedChange != null) {
updatedGroup = DecryptedGroupUtil.applyWithoutRevisionCheck(updatedGroup, decryptedChange);
}
updatedGroup = resetRevision(updatedGroup, currentGroupState.getRevision());
} catch (NotAbleToApplyGroupV2ChangeException e) {
Log.w(TAG, e);
updatedGroup = decryptedGroup;
}
groupDatabase.update(groupId, updatedGroup);
} else {
groupDatabase.create(groupMasterKey, decryptedGroup);
Log.i(TAG, "Created local group with placeholder");

View File

@@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupHistoryEntry;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct;
import org.whispersystems.signalservice.api.groupsv2.GroupHistoryPage;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
@@ -170,6 +171,52 @@ public class GroupsV2StateProcessor {
this.profileAndMessageHelper = profileAndMessageHelper;
}
@WorkerThread
public GroupUpdateResult forceSanityUpdateFromServer(long timestamp)
throws IOException, GroupNotAMemberException
{
Optional<GroupRecord> localRecord = groupDatabase.getGroup(groupId);
DecryptedGroup localState = localRecord.map(g -> g.requireV2GroupProperties().getDecryptedGroup()).orElse(null);
DecryptedGroup serverState;
if (localState == null) {
info("No local state to force update");
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
}
try {
serverState = groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
} catch (NotInGroupException | GroupNotFoundException e) {
throw new GroupNotAMemberException(e);
} catch (VerificationFailedException | InvalidGroupStateException e) {
throw new IOException(e);
}
DecryptedGroupChange decryptedGroupChange = GroupChangeReconstruct.reconstructGroupChange(localState, serverState);
GlobalGroupState inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(serverState, decryptedGroupChange)));
AdvanceGroupStateResult advanceGroupStateResult = GroupStateMapper.partiallyAdvanceGroupState(inputGroupState, serverState.getRevision());
DecryptedGroup newLocalState = advanceGroupStateResult.getNewGlobalGroupState().getLocalState();
if (newLocalState == null || newLocalState == inputGroupState.getLocalState()) {
info("Local state and server state are equal");
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
} else {
info("Local state (revision: " + localState.getRevision() + ") does not match server state (revision: " + serverState.getRevision() + "), updating");
}
updateLocalDatabaseGroupState(inputGroupState, newLocalState);
if (localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
info("Inserting single update message for restore placeholder");
profileAndMessageHelper.insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(newLocalState, null)));
} else {
info("Inserting force update messages");
profileAndMessageHelper.insertUpdateMessages(timestamp, localState, advanceGroupStateResult.getProcessedLogEntries());
}
profileAndMessageHelper.persistLearnedProfileKeys(inputGroupState);
return new GroupUpdateResult(GroupState.GROUP_UPDATED, newLocalState);
}
/**
* Using network where required, will attempt to bring the local copy of the group up to the revision specified.
*
@@ -560,10 +607,12 @@ public class GroupsV2StateProcessor {
private final Context context;
private final ServiceId serviceId;
private final GroupMasterKey masterKey;
private final GroupId.V2 groupId;
private final RecipientDatabase recipientDatabase;
@VisibleForTesting
GroupMasterKey masterKey;
ProfileAndMessageHelper(@NonNull Context context, @NonNull ServiceId serviceId, @NonNull GroupMasterKey masterKey, @NonNull GroupId.V2 groupId, @NonNull RecipientDatabase recipientDatabase) {
this.context = context;
this.serviceId = serviceId;