From 2ee04bd1b62d75ef02ece8c8e87aa00b9dbee2a1 Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Wed, 9 Sep 2020 11:59:09 -0300 Subject: [PATCH] Insert placeholder group on GV2 storage service sync. --- .../securesms/database/RecipientDatabase.java | 16 ++- .../v2/processing/GroupStateMapper.java | 5 +- .../v2/processing/GroupsV2StateProcessor.java | 23 ++- .../securesms/jobs/JobManagerFactories.java | 2 +- .../securesms/jobs/WakeGroupV2Job.java | 134 ------------------ 5 files changed, 38 insertions(+), 142 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/WakeGroupV2Job.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index fe8893cc9..41cd65058 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -15,6 +15,7 @@ import com.google.android.gms.common.util.ArrayUtils; import net.sqlcipher.database.SQLiteConstraintException; import net.sqlcipher.database.SQLiteDatabase; +import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.groups.GroupMasterKey; import org.signal.zkgroup.profiles.ProfileKey; @@ -28,8 +29,9 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.v2.ProfileKeySet; +import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; -import org.thoughtcrime.securesms.jobs.WakeGroupV2Job; +import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.ProfileName; @@ -860,7 +862,17 @@ public class RecipientDatabase extends Database { GroupId.V2 groupId = GroupId.v2(insert.getMasterKey()); Recipient recipient = Recipient.externalGroup(context, groupId); - ApplicationDependencies.getJobManager().add(new WakeGroupV2Job(insert.getMasterKey())); + Log.i(TAG, "Creating restore placeholder for " + groupId); + + DatabaseFactory.getGroupDatabase(context) + .create(insert.getMasterKey(), + DecryptedGroup.newBuilder() + .setRevision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) + .build()); + + Log.i(TAG, "Scheduling request for latest group info for " + groupId); + + ApplicationDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId)); threadDatabase.setArchived(recipient.getId(), insert.isArchived()); needsRefresh.add(recipient.getId()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupStateMapper.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupStateMapper.java index 8b2901577..5b8b26a7a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupStateMapper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupStateMapper.java @@ -21,8 +21,9 @@ final class GroupStateMapper { private static final String TAG = Log.tag(GroupStateMapper.class); - static final int LATEST = Integer.MAX_VALUE; - static final int PLACEHOLDER_REVISION = -1; + static final int LATEST = Integer.MAX_VALUE; + static final int PLACEHOLDER_REVISION = -1; + static final int RESTORE_PLACEHOLDER_REVISION = -2; private static final Comparator BY_REVISION = (o1, o2) -> Integer.compare(o1.getRevision(), o2.getRevision()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java index 3af96db4a..38276b4cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java @@ -65,9 +65,20 @@ public final class GroupsV2StateProcessor { private static final String TAG = Log.tag(GroupsV2StateProcessor.class); - public static final int LATEST = GroupStateMapper.LATEST; + public static final int LATEST = GroupStateMapper.LATEST; + + /** + * Used to mark a group state as a placeholder when there is partial knowledge (title and avater) + * gathered from a group join link. + */ public static final int PLACEHOLDER_REVISION = GroupStateMapper.PLACEHOLDER_REVISION; + /** + * Used to mark a group state as a placeholder when you have no knowledge at all of the group + * e.g. from a group master key from a storage service restore. + */ + public static final int RESTORE_PLACEHOLDER_REVISION = GroupStateMapper.RESTORE_PLACEHOLDER_REVISION; + private final Context context; private final JobManager jobManager; private final RecipientDatabase recipientDatabase; @@ -176,7 +187,8 @@ public final class GroupsV2StateProcessor { if (inputGroupState == null) { try { - inputGroupState = queryServer(localState, revision == LATEST && localState == null); + boolean latestRevisionOnly = revision == LATEST && (localState == null || localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION); + inputGroupState = queryServer(localState, latestRevisionOnly); } catch (GroupNotAMemberException e) { if (localState != null && signedGroupChange != null) { try { @@ -212,7 +224,12 @@ public final class GroupsV2StateProcessor { updateLocalDatabaseGroupState(inputGroupState, newLocalState); determineProfileSharing(inputGroupState, newLocalState); - insertUpdateMessages(timestamp, advanceGroupStateResult.getProcessedLogEntries()); + if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) { + Log.i(TAG, "Inserting single update message for restore placeholder"); + insertUpdateMessages(timestamp, Collections.singleton(new LocalGroupLogEntry(newLocalState, null))); + } else { + insertUpdateMessages(timestamp, advanceGroupStateResult.getProcessedLogEntries()); + } persistLearnedProfileKeys(inputGroupState); GlobalGroupState remainingWork = advanceGroupStateResult.getNewGlobalGroupState(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 668b3578b..5e51c4b63 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -101,7 +101,6 @@ public final class JobManagerFactories { put(StorageAccountRestoreJob.KEY, new StorageAccountRestoreJob.Factory()); put(RequestGroupV2InfoWorkerJob.KEY, new RequestGroupV2InfoWorkerJob.Factory()); put(RequestGroupV2InfoJob.KEY, new RequestGroupV2InfoJob.Factory()); - put(WakeGroupV2Job.KEY, new WakeGroupV2Job.Factory()); put(GroupV2UpdateSelfProfileKeyJob.KEY, new GroupV2UpdateSelfProfileKeyJob.Factory()); put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory()); put(RetrieveProfileJob.KEY, new RetrieveProfileJob.Factory()); @@ -153,6 +152,7 @@ public final class JobManagerFactories { put("Argon2TestJob", new FailingJob.Factory()); put("Argon2TestMigrationJob", new PassingMigrationJob.Factory()); put("StorageKeyRotationMigrationJob", new PassingMigrationJob.Factory()); + put("WakeGroupV2Job", new FailingJob.Factory()); }}; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/WakeGroupV2Job.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/WakeGroupV2Job.java deleted file mode 100644 index 108795842..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/WakeGroupV2Job.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import androidx.annotation.NonNull; - -import org.signal.zkgroup.InvalidInputException; -import org.signal.zkgroup.groups.GroupMasterKey; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.GroupDatabase; -import org.thoughtcrime.securesms.database.MessageDatabase; -import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context; -import org.thoughtcrime.securesms.groups.GroupChangeBusyException; -import org.thoughtcrime.securesms.groups.GroupId; -import org.thoughtcrime.securesms.groups.GroupManager; -import org.thoughtcrime.securesms.groups.GroupNotAMemberException; -import org.thoughtcrime.securesms.groups.GroupProtoUtil; -import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor; -import org.thoughtcrime.securesms.jobmanager.Data; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.mms.MmsException; -import org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.util.Hex; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException; -import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; - -import java.io.IOException; -import java.util.Collections; -import java.util.concurrent.TimeUnit; - -/** - * Use to create and show a thread for an unknown GV2 group. - */ -public final class WakeGroupV2Job extends BaseJob { - - public static final String KEY = "WakeGroupV2Job"; - - private static final String TAG = Log.tag(WakeGroupV2Job.class); - - private static final String KEY_GROUP_MASTER_KEY = "group_id"; - - private final GroupMasterKey groupMasterKey; - - public WakeGroupV2Job(@NonNull GroupMasterKey groupMasterKey) { - this(new Parameters.Builder() - .setQueue("RequestGroupV2InfoJob::" + GroupId.v2(groupMasterKey)) - .addConstraint(NetworkConstraint.KEY) - .setLifespan(TimeUnit.DAYS.toMillis(1)) - .setMaxAttempts(Parameters.UNLIMITED) - .build(), - groupMasterKey); - } - - private WakeGroupV2Job(@NonNull Parameters parameters, @NonNull GroupMasterKey groupMasterKey) { - super(parameters); - - this.groupMasterKey = groupMasterKey; - } - - @Override - public @NonNull Data serialize() { - return new Data.Builder().putString(KEY_GROUP_MASTER_KEY, Hex.toStringCondensed(groupMasterKey.serialize())) - .build(); - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void onRun() throws IOException, GroupNotAMemberException, GroupChangeBusyException { - Log.i(TAG, "Waking group"); - - GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - GroupId.V2 groupId = GroupId.v2(groupMasterKey); - - if (groupDatabase.findGroup(groupId)) { - Log.w(TAG, "Group already exists " + groupId); - return; - } else { - GroupManager.updateGroupFromServer(context, groupMasterKey, GroupsV2StateProcessor.LATEST, System.currentTimeMillis(), null); - Log.i(TAG, "Group created " + groupId); - } - - Optional group = groupDatabase.getGroup(groupId); - if (!group.isPresent()) { - Log.w(TAG, "Failed to create group from server " + groupId); - return; - } - - Log.i(TAG, "Waking group " + groupId); - try { - Recipient groupRecipient = Recipient.externalGroup(context, groupId); - long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); - GroupDatabase.V2GroupProperties v2GroupProperties = group.get().requireV2GroupProperties(); - DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(v2GroupProperties.getGroupMasterKey(), v2GroupProperties.getDecryptedGroup(), null, null); - MessageDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); - OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(groupRecipient, decryptedGroupV2Context, null, System.currentTimeMillis(), 0, false, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - - long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null); - - mmsDatabase.markAsSent(messageId, true); - } catch (MmsException e) { - Log.w(TAG, e); - } - } - - @Override - public boolean onShouldRetry(@NonNull Exception e) { - return e instanceof PushNetworkException || - e instanceof NoCredentialForRedemptionTimeException || - e instanceof GroupChangeBusyException; - } - - @Override - public void onFailure() { - } - - public static final class Factory implements Job.Factory { - - @Override - public @NonNull WakeGroupV2Job create(@NonNull Parameters parameters, @NonNull Data data) { - try { - return new WakeGroupV2Job(parameters, - new GroupMasterKey(Hex.fromStringCondensed(data.getString(KEY_GROUP_MASTER_KEY)))); - } catch (InvalidInputException | IOException e) { - throw new AssertionError(e); - } - } - } -}