From cd58c09be3e226799d389523de3aaa97c47b7ea5 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 11 Nov 2020 07:35:39 -0500 Subject: [PATCH] Proper handling of GV1->GV2 migrations in storage service. --- .../securesms/database/GroupDatabase.java | 26 +++++- .../securesms/jobs/GroupV1MigrationJob.java | 79 +++++++++++-------- .../securesms/jobs/StorageSyncJob.java | 34 +++++++- .../storage/GroupV1ConflictMerger.java | 24 +++--- .../storage/GroupV2ExistenceChecker.java | 13 +++ .../StaticGroupV2ExistenceChecker.java | 26 ++++++ .../securesms/storage/StorageSyncHelper.java | 8 +- .../storage/GroupV1ConflictMergerTest.java | 29 ++++++- .../storage/StorageSyncHelperTest.java | 24 +++--- 9 files changed, 196 insertions(+), 67 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/storage/GroupV2ExistenceChecker.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/storage/StaticGroupV2ExistenceChecker.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index 4b2fa472d..a78a6ac38 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -41,10 +41,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.UUID; @Trace @@ -677,7 +679,7 @@ public final class GroupDatabase extends Database { return RecipientId.toSerializedList(groupMembers); } - public List getAllGroupV2Ids() { + public @NonNull List getAllGroupV2Ids() { List result = new LinkedList<>(); try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[]{ GROUP_ID }, null, null, null, null, null)) { @@ -692,6 +694,28 @@ public final class GroupDatabase extends Database { return result; } + /** + * Key: The 'expected' V2 ID (i.e. what a V1 ID would map to when migrated) + * Value: The matching V1 ID + */ + public @NonNull Map getAllExpectedV2Ids() { + Map result = new HashMap<>(); + + String[] projection = new String[]{ GROUP_ID, EXPECTED_V2_ID }; + String query = EXPECTED_V2_ID + " NOT NULL"; + + try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, projection, query, null, null, null, null)) { + while (cursor.moveToNext()) { + GroupId.V1 groupId = GroupId.parseOrThrow(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID))).requireV1(); + GroupId.V2 expectedId = GroupId.parseOrThrow(cursor.getString(cursor.getColumnIndexOrThrow(EXPECTED_V2_ID))).requireV2(); + + result.put(expectedId, groupId); + } + } + + return result; + } + public static class Reader implements Closeable { private final Cursor cursor; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV1MigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV1MigrationJob.java index f1b0842dd..b7d52df96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV1MigrationJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV1MigrationJob.java @@ -11,6 +11,7 @@ import com.annimon.stream.Stream; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.zkgroup.groups.GroupMasterKey; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupAlreadyExistsException; @@ -61,6 +62,8 @@ public class GroupV1MigrationJob extends BaseJob { private static final int ROUTINE_LIMIT = 50; private static final long REFRESH_INTERVAL = TimeUnit.HOURS.toMillis(3); + private static final Object MIGRATION_LOCK = new Object(); + private final RecipientId recipientId; private final boolean forced; @@ -159,8 +162,9 @@ public class GroupV1MigrationJob extends BaseJob { @Override protected void onRun() throws IOException, RetryLaterException { - Recipient groupRecipient = Recipient.resolved(recipientId); - Long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipientId); + Recipient groupRecipient = Recipient.resolved(recipientId); + Long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipientId); + GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); if (threadId == null) { warn(TAG, "No thread found!"); @@ -182,6 +186,11 @@ public class GroupV1MigrationJob extends BaseJob { GroupMasterKey gv2MasterKey = gv1Id.deriveV2MigrationMasterKey(); boolean newlyCreated = false; + if (groupDatabase.groupExists(gv2Id)) { + warn(TAG, "We already have a V2 group for this V1 group! Must have been added before we were migration-capable."); + return; + } + switch (GroupManager.v2GroupStatus(context, gv2MasterKey)) { case DOES_NOT_EXIST: log(TAG, "Group does not exist on the service."); @@ -259,42 +268,46 @@ public class GroupV1MigrationJob extends BaseJob { } public static void performLocalMigration(@NonNull Context context, @NonNull GroupId.V1 gv1Id) throws IOException { - Recipient recipient = Recipient.externalGroupExact(context, gv1Id); - long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); + synchronized (MIGRATION_LOCK) { + Recipient recipient = Recipient.externalGroupExact(context, gv1Id); + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); - performLocalMigration(context, gv1Id, threadId, recipient); + performLocalMigration(context, gv1Id, threadId, recipient); + } } private static @Nullable DecryptedGroup performLocalMigration(@NonNull Context context, @NonNull GroupId.V1 gv1Id, long threadId, @NonNull Recipient groupRecipient) throws IOException { - DecryptedGroup decryptedGroup; - try { - decryptedGroup = GroupManager.addedGroupVersion(context, gv1Id.deriveV2MigrationMasterKey()); - } catch (GroupDoesNotExistException e) { - throw new IOException("[Local] The group should exist already!"); - } catch (GroupNotAMemberException e) { - Log.w(TAG, "[Local] We are not in the group. Doing a local leave."); - handleLeftBehind(context, gv1Id, groupRecipient, threadId); - return null; + synchronized (MIGRATION_LOCK) { + DecryptedGroup decryptedGroup; + try { + decryptedGroup = GroupManager.addedGroupVersion(context, gv1Id.deriveV2MigrationMasterKey()); + } catch (GroupDoesNotExistException e) { + throw new IOException("[Local] The group should exist already!"); + } catch (GroupNotAMemberException e) { + Log.w(TAG, "[Local] We are not in the group. Doing a local leave."); + handleLeftBehind(context, gv1Id, groupRecipient, threadId); + return null; + } + + List pendingRecipients = Stream.of(DecryptedGroupUtil.pendingToUuidList(decryptedGroup.getPendingMembersList())) + .map(uuid -> Recipient.externalPush(context, uuid, null, false)) + .filterNot(Recipient::isSelf) + .map(Recipient::getId) + .toList(); + + Log.i(TAG, "[Local] Migrating group over to the version we were added to: V" + decryptedGroup.getRevision()); + DatabaseFactory.getGroupDatabase(context).migrateToV2(gv1Id, decryptedGroup); + DatabaseFactory.getSmsDatabase(context).insertGroupV1MigrationEvents(groupRecipient.getId(), threadId, pendingRecipients); + + Log.i(TAG, "[Local] Applying all changes since V" + decryptedGroup.getRevision()); + try { + GroupManager.updateGroupFromServer(context, gv1Id.deriveV2MigrationMasterKey(), LATEST, System.currentTimeMillis(), null); + } catch (GroupChangeBusyException | GroupNotAMemberException e) { + Log.w(TAG, e); + } + + return decryptedGroup; } - - List pendingRecipients = Stream.of(DecryptedGroupUtil.pendingToUuidList(decryptedGroup.getPendingMembersList())) - .map(uuid -> Recipient.externalPush(context, uuid, null, false)) - .filterNot(Recipient::isSelf) - .map(Recipient::getId) - .toList(); - - Log.i(TAG, "[Local] Migrating group over to the version we were added to: V" + decryptedGroup.getRevision()); - DatabaseFactory.getGroupDatabase(context).migrateToV2(gv1Id, decryptedGroup); - DatabaseFactory.getSmsDatabase(context).insertGroupV1MigrationEvents(groupRecipient.getId(), threadId, pendingRecipients); - - Log.i(TAG, "[Local] Applying all changes since V" + decryptedGroup.getRevision()); - try { - GroupManager.updateGroupFromServer(context, gv1Id.deriveV2MigrationMasterKey(), LATEST, System.currentTimeMillis(), null); - } catch (GroupChangeBusyException | GroupNotAMemberException e) { - Log.w(TAG, e); - } - - return decryptedGroup; } private static void handleLeftBehind(@NonNull Context context, @NonNull GroupId.V1 gv1Id, @NonNull Recipient groupRecipient, long threadId) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java index af749b493..8c6f65731 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java @@ -6,11 +6,13 @@ import androidx.annotation.NonNull; import com.annimon.stream.Stream; +import org.signal.zkgroup.groups.GroupMasterKey; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.database.StorageKeyDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; @@ -18,6 +20,8 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.storage.GroupV2ExistenceChecker; +import org.thoughtcrime.securesms.storage.StaticGroupV2ExistenceChecker; import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.storage.StorageSyncHelper.KeyDifferenceResult; import org.thoughtcrime.securesms.storage.StorageSyncHelper.LocalWriteResult; @@ -28,6 +32,7 @@ import org.thoughtcrime.securesms.storage.StorageSyncValidations; import org.thoughtcrime.securesms.tracing.Trace; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.util.FeatureFlags; +import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.InvalidKeyException; @@ -35,6 +40,7 @@ import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.storage.SignalAccountRecord; +import org.whispersystems.signalservice.api.storage.SignalGroupV2Record; import org.whispersystems.signalservice.api.storage.SignalStorageManifest; import org.whispersystems.signalservice.api.storage.SignalStorageRecord; import org.whispersystems.signalservice.api.storage.StorageId; @@ -44,9 +50,12 @@ import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -156,7 +165,8 @@ public class StorageSyncJob extends BaseJob { List localOnly = buildLocalStorageRecords(context, keyDifference.getLocalOnlyKeys()); List remoteOnly = accountManager.readStorageRecords(storageServiceKey, keyDifference.getRemoteOnlyKeys()); - MergeResult mergeResult = StorageSyncHelper.resolveConflict(remoteOnly, localOnly); + GroupV2ExistenceChecker gv2ExistenceChecker = new StaticGroupV2ExistenceChecker(DatabaseFactory.getGroupDatabase(context).getAllGroupV2Ids()); + MergeResult mergeResult = StorageSyncHelper.resolveConflict(remoteOnly, localOnly, gv2ExistenceChecker); WriteOperationResult writeOperationResult = StorageSyncHelper.createWriteOperation(remoteManifest.get().getVersion(), allLocalStorageKeys, mergeResult); if (remoteOnly.size() != keyDifference.getRemoteOnlyKeys().size()) { @@ -191,6 +201,7 @@ public class StorageSyncJob extends BaseJob { Log.i(TAG, "[Remote Newer] After resolving the conflict, all changes are local. No remote writes needed."); } + migrateToGv2IfNecessary(context, mergeResult.getLocalGroupV2Inserts()); recipientDatabase.applyStorageSyncUpdates(mergeResult.getLocalContactInserts(), mergeResult.getLocalContactUpdates(), mergeResult.getLocalGroupV1Inserts(), mergeResult.getLocalGroupV1Updates(), mergeResult.getLocalGroupV2Inserts(), mergeResult.getLocalGroupV2Updates()); storageKeyDatabase.applyStorageSyncUpdates(mergeResult.getLocalUnknownInserts(), mergeResult.getLocalUnknownDeletes()); StorageSyncHelper.applyAccountStorageSyncUpdates(context, mergeResult.getLocalAccountUpdate()); @@ -267,6 +278,27 @@ public class StorageSyncJob extends BaseJob { return needsMultiDeviceSync; } + /** + * Migrates any of the provided V2 IDs that map a local V1 ID. If a match is found, we remove the + * record from the collection of V2 IDs. + */ + private static void migrateToGv2IfNecessary(@NonNull Context context, @NonNull Collection inserts) + throws IOException + { + Map idMap = DatabaseFactory.getGroupDatabase(context).getAllExpectedV2Ids(); + Iterator recordIterator = inserts.iterator(); + + while (recordIterator.hasNext()) { + GroupId.V2 id = GroupId.v2(GroupUtil.requireMasterKey(recordIterator.next().getMasterKeyBytes())); + + if (idMap.containsKey(id)) { + Log.i(TAG, "Discovered a new GV2 ID that is actually a migrated V1 group! Migrating now."); + GroupV1MigrationJob.performLocalMigration(context, idMap.get(id)); + recordIterator.remove(); + } + } + } + private static @NonNull List getAllLocalStorageIds(@NonNull Context context, @NonNull Recipient self) { return Util.concatenatedList(DatabaseFactory.getRecipientDatabase(context).getContactStorageSyncIds(), Collections.singletonList(StorageId.forAccount(self.getStorageServiceId())), diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1ConflictMerger.java b/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1ConflictMerger.java index e961c884a..c19839e58 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1ConflictMerger.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1ConflictMerger.java @@ -17,9 +17,12 @@ import java.util.Map; final class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger { private final Map localByGroupId; + private final GroupV2ExistenceChecker groupExistenceChecker; - GroupV1ConflictMerger(@NonNull Collection localOnly) { + GroupV1ConflictMerger(@NonNull Collection localOnly, @NonNull GroupV2ExistenceChecker groupExistenceChecker) { localByGroupId = Stream.of(localOnly).collect(Collectors.toMap(g -> GroupId.v1orThrow(g.getGroupId()), g -> g)); + + this.groupExistenceChecker = groupExistenceChecker; } @Override @@ -30,8 +33,14 @@ final class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger getInvalidEntries(@NonNull Collection remoteRecords) { return Stream.of(remoteRecords) - .filterNot(GroupV1ConflictMerger::isValidGroupId) - .toList(); + .filter(record -> { + try { + GroupId.V1 id = GroupId.v1Exact(record.getGroupId()); + return groupExistenceChecker.exists(id.deriveV2MigrationGroupId()); + } catch (BadGroupIdException e) { + return true; + } + }).toList(); } @Override @@ -58,13 +67,4 @@ final class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger ids; + + public StaticGroupV2ExistenceChecker(@NonNull Collection ids) { + this.ids = new HashSet<>(ids); + } + + @Override + public boolean exists(@NonNull GroupId.V2 groupId) { + return ids.contains(groupId); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java index 080de2c63..5e39501d1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java @@ -12,7 +12,6 @@ import com.annimon.stream.Stream; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; -import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob; import org.thoughtcrime.securesms.jobs.StorageSyncJob; @@ -36,7 +35,6 @@ import org.whispersystems.signalservice.api.storage.SignalStorageManifest; import org.whispersystems.signalservice.api.storage.SignalStorageRecord; import org.whispersystems.signalservice.api.storage.StorageId; import org.whispersystems.signalservice.api.util.OptionalUtil; -import org.whispersystems.signalservice.internal.storage.protos.AccountRecord; import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord; import java.nio.ByteBuffer; @@ -45,7 +43,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; @@ -256,7 +253,8 @@ public final class StorageSyncHelper { * @return A set of actions that should be applied to resolve the conflict. */ public static @NonNull MergeResult resolveConflict(@NonNull Collection remoteOnlyRecords, - @NonNull Collection localOnlyRecords) + @NonNull Collection localOnlyRecords, + @NonNull GroupV2ExistenceChecker groupExistenceChecker) { List remoteOnlyContacts = Stream.of(remoteOnlyRecords).filter(r -> r.getContact().isPresent()).map(r -> r.getContact().get()).toList(); List localOnlyContacts = Stream.of(localOnlyRecords).filter(r -> r.getContact().isPresent()).map(r -> r.getContact().get()).toList(); @@ -280,7 +278,7 @@ public final class StorageSyncHelper { } RecordMergeResult contactMergeResult = resolveRecordConflict(remoteOnlyContacts, localOnlyContacts, new ContactConflictMerger(localOnlyContacts, Recipient.self())); - RecordMergeResult groupV1MergeResult = resolveRecordConflict(remoteOnlyGroupV1, localOnlyGroupV1, new GroupV1ConflictMerger(localOnlyGroupV1)); + RecordMergeResult groupV1MergeResult = resolveRecordConflict(remoteOnlyGroupV1, localOnlyGroupV1, new GroupV1ConflictMerger(localOnlyGroupV1, groupExistenceChecker)); RecordMergeResult groupV2MergeResult = resolveRecordConflict(remoteOnlyGroupV2, localOnlyGroupV2, new GroupV2ConflictMerger(localOnlyGroupV2)); RecordMergeResult accountMergeResult = resolveRecordConflict(remoteOnlyAccount, localOnlyAccount, new AccountConflictMerger(localOnlyAccount.isEmpty() ? Optional.absent() : Optional.of(localOnlyAccount.get(0)))); diff --git a/app/src/test/java/org/thoughtcrime/securesms/storage/GroupV1ConflictMergerTest.java b/app/src/test/java/org/thoughtcrime/securesms/storage/GroupV1ConflictMergerTest.java index 8f45cc968..bbe96e421 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/storage/GroupV1ConflictMergerTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/storage/GroupV1ConflictMergerTest.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.storage; import org.junit.Test; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.storage.StorageSyncHelper.KeyGenerator; import org.whispersystems.signalservice.api.storage.SignalGroupV1Record; @@ -39,7 +40,7 @@ public final class GroupV1ConflictMergerTest { .setForcedUnread(true) .build(); - SignalGroupV1Record merged = new GroupV1ConflictMerger(Collections.singletonList(local)).merge(remote, local, KEY_GENERATOR); + SignalGroupV1Record merged = new GroupV1ConflictMerger(Collections.singletonList(local), id -> false).merge(remote, local, KEY_GENERATOR); assertArrayEquals(remote.getId().getRaw(), merged.getId().getRaw()); assertArrayEquals(byteArray(100), merged.getGroupId()); @@ -62,7 +63,7 @@ public final class GroupV1ConflictMergerTest { .setArchived(false) .build(); - SignalGroupV1Record merged = new GroupV1ConflictMerger(Collections.singletonList(local)).merge(remote, local, mock(KeyGenerator.class)); + SignalGroupV1Record merged = new GroupV1ConflictMerger(Collections.singletonList(local), id -> false).merge(remote, local, mock(KeyGenerator.class)); assertEquals(remote, merged); } @@ -81,7 +82,29 @@ public final class GroupV1ConflictMergerTest { .setArchived(true) .build(); - Collection invalid = new GroupV1ConflictMerger(Collections.emptyList()).getInvalidEntries(Arrays.asList(badRemote, goodRemote)); + Collection invalid = new GroupV1ConflictMerger(Collections.emptyList(), id -> false).getInvalidEntries(Arrays.asList(badRemote, goodRemote)); + + assertEquals(Collections.singletonList(badRemote), invalid); + } + + @Test + public void merge_excludeMigratedGroupId() { + GroupId.V1 v1Id = GroupId.v1orThrow(groupKey(1)); + GroupId.V2 v2Id = v1Id.deriveV2MigrationGroupId(); + + SignalGroupV1Record badRemote = new SignalGroupV1Record.Builder(byteArray(1), v1Id.getDecodedId()) + .setBlocked(false) + .setProfileSharingEnabled(true) + .setArchived(true) + .build(); + + SignalGroupV1Record goodRemote = new SignalGroupV1Record.Builder(byteArray(1), groupKey(99)) + .setBlocked(false) + .setProfileSharingEnabled(true) + .setArchived(true) + .build(); + + Collection invalid = new GroupV1ConflictMerger(Collections.emptyList(), id -> id.equals(v2Id)).getInvalidEntries(Arrays.asList(badRemote, goodRemote)); assertEquals(Collections.singletonList(badRemote), invalid); } diff --git a/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.java b/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.java index c15c71209..81586d0cb 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.java @@ -6,8 +6,7 @@ import com.annimon.stream.Stream; import org.junit.Before; import org.junit.Test; -import org.signal.zkgroup.InvalidInputException; -import org.signal.zkgroup.groups.GroupMasterKey; +import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @@ -51,6 +50,7 @@ import static org.thoughtcrime.securesms.testutil.TestHelpers.setOf; @RunWith(PowerMockRunner.class) @PrepareForTest({ Recipient.class, FeatureFlags.class}) +@PowerMockIgnore("javax.crypto.*") public final class StorageSyncHelperTest { private static final UUID UUID_A = UuidUtil.parseOrThrow("ebef429e-695e-4f51-bcc4-526a60ac68c7"); @@ -145,7 +145,7 @@ public final class StorageSyncHelperTest { SignalContactRecord remote1 = contact(1, UUID_A, E164_A, "a"); SignalContactRecord local1 = contact(2, UUID_B, E164_B, "b"); - MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1)); + MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1), r -> false); assertEquals(setOf(remote1), result.getLocalContactInserts()); assertTrue(result.getLocalContactUpdates().isEmpty()); @@ -159,7 +159,7 @@ public final class StorageSyncHelperTest { SignalContactRecord remote1 = contact(1, UUID_SELF, E164_SELF, "self"); SignalContactRecord local1 = contact(2, UUID_A, E164_A, "a"); - MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1)); + MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1), r -> false); assertTrue(result.getLocalContactInserts().isEmpty()); assertTrue(result.getLocalContactUpdates().isEmpty()); @@ -173,7 +173,7 @@ public final class StorageSyncHelperTest { SignalGroupV1Record remote1 = badGroupV1(1, 1, true, false); SignalGroupV1Record local1 = groupV1(2, 1, true, true); - MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1)); + MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1), r -> false); assertTrue(result.getLocalContactInserts().isEmpty()); assertTrue(result.getLocalContactUpdates().isEmpty()); @@ -187,7 +187,7 @@ public final class StorageSyncHelperTest { SignalGroupV2Record remote1 = badGroupV2(1, 2, true, false); SignalGroupV2Record local1 = groupV2(2, 2, true, false); - MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1)); + MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1), r -> false); assertTrue(result.getLocalContactInserts().isEmpty()); assertTrue(result.getLocalContactUpdates().isEmpty()); @@ -201,7 +201,7 @@ public final class StorageSyncHelperTest { SignalContactRecord remote1 = contact(1, UUID_A, E164_A, "a"); SignalContactRecord local1 = contact(2, UUID_A, E164_A, "a"); - MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1)); + MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1), r -> false); SignalContactRecord expectedMerge = contact(1, UUID_A, E164_A, "a"); @@ -217,7 +217,7 @@ public final class StorageSyncHelperTest { SignalGroupV1Record remote1 = groupV1(1, 1, true, false); SignalGroupV1Record local1 = groupV1(2, 1, true, false); - MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1)); + MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1), r -> false); SignalGroupV1Record expectedMerge = groupV1(1, 1, true, false); @@ -233,7 +233,7 @@ public final class StorageSyncHelperTest { SignalGroupV2Record remote1 = groupV2(1, 2, true, false); SignalGroupV2Record local1 = groupV2(2, 2, true, false); - MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1)); + MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1), r -> false); SignalGroupV2Record expectedMerge = groupV2(1, 2, true, false); @@ -249,7 +249,7 @@ public final class StorageSyncHelperTest { SignalContactRecord remote1 = contact(1, UUID_A, E164_A, null); SignalContactRecord local1 = contact(2, UUID_A, E164_A, "a"); - MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1)); + MergeResult result = StorageSyncHelper.resolveConflict(recordSetOf(remote1), recordSetOf(local1), r -> false); SignalContactRecord expectedMerge = contact(2, UUID_A, E164_A, "a"); @@ -268,7 +268,7 @@ public final class StorageSyncHelperTest { SignalStorageRecord local1 = unknown(1); SignalStorageRecord local2 = unknown(2); - MergeResult result = StorageSyncHelper.resolveConflict(setOf(remote1, remote2, account), setOf(local1, local2, account)); + MergeResult result = StorageSyncHelper.resolveConflict(setOf(remote1, remote2, account), setOf(local1, local2, account), r -> false); assertTrue(result.getLocalContactInserts().isEmpty()); assertTrue(result.getLocalContactUpdates().isEmpty()); @@ -305,7 +305,7 @@ public final class StorageSyncHelperTest { Set remoteOnly = recordSetOf(remote1, remote2, remote3, remote4, remote5, remote6, unknownRemote); Set localOnly = recordSetOf(local1, local2, local3, local4, local5, local6, unknownLocal); - MergeResult result = StorageSyncHelper.resolveConflict(remoteOnly, localOnly); + MergeResult result = StorageSyncHelper.resolveConflict(remoteOnly, localOnly, r -> false); SignalContactRecord merge1 = contact(2, UUID_A, E164_A, "a"); SignalContactRecord merge2 = contact(111, UUID_B, E164_B, "b");