From 011f6e6cf434eab31ffd2242fd4de05bf5eb7488 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 5 Nov 2021 15:46:38 -0400 Subject: [PATCH] Repair groups with remapped recipients. --- .../securesms/database/GroupDatabase.java | 43 ++++++++++++++----- .../securesms/database/RemappedRecords.java | 36 ++++++++++++++++ 2 files changed, 69 insertions(+), 10 deletions(-) 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 a8f06b8261..43ee81a69b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -139,11 +139,28 @@ private static final String[] GROUP_PROJECTION = { } public Optional getGroup(@NonNull GroupId groupId) { - try (Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?", - new String[] {groupId.toString()}, - null, null, null)) - { + SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); + + try (Cursor cursor = db.query(TABLE_NAME, null, GROUP_ID + " = ?", SqlUtil.buildArgs(groupId.toString()), null, null, null)) { if (cursor != null && cursor.moveToNext()) { + Optional groupRecord = getGroup(cursor); + + if (groupRecord.isPresent() && RemappedRecords.getInstance().areAnyRemapped(context, groupRecord.get().getMembers())) { + String remaps = RemappedRecords.getInstance().buildRemapDescription(context, groupRecord.get().getMembers()); + Log.w(TAG, "Found a group with remapped recipients in it's membership list! Updating the list. GroupId: " + groupId + ", Remaps: " + remaps, true); + + Collection remapped = RemappedRecords.getInstance().remap(context, groupRecord.get().getMembers()); + + ContentValues values = new ContentValues(); + values.put(MEMBERS, RecipientId.toSerializedList(remapped)); + + if (db.update(TABLE_NAME, values, GROUP_ID + " = ?", SqlUtil.buildArgs(groupId)) > 0) { + return getGroup(groupId); + } else { + throw new IllegalStateException("Failed to update group with remapped recipients!"); + } + } + return getGroup(cursor); } @@ -565,7 +582,7 @@ private static final String[] GROUP_PROJECTION = { throw new AssertionError("V2 master key but no group state"); } groupId.requireV2(); - groupMembers = getV2GroupMembers(groupState); + groupMembers = getV2GroupMembers(context, groupState); contentValues.put(V2_MASTER_KEY, groupMasterKey.serialize()); contentValues.put(V2_REVISION, groupState.getRevision()); contentValues.put(V2_DECRYPTED_GROUP, groupState.toByteArray()); @@ -704,7 +721,7 @@ private static final String[] GROUP_PROJECTION = { contentValues.put(UNMIGRATED_V1_MEMBERS, unmigratedV1Members.isEmpty() ? null : RecipientId.toSerializedList(unmigratedV1Members)); } - List groupMembers = getV2GroupMembers(decryptedGroup); + List groupMembers = getV2GroupMembers(context, decryptedGroup); contentValues.put(TITLE, title); contentValues.put(V2_REVISION, decryptedGroup.getRevision()); contentValues.put(V2_DECRYPTED_GROUP, decryptedGroup.toByteArray()); @@ -863,7 +880,7 @@ private static final String[] GROUP_PROJECTION = { private static @NonNull List uuidsToRecipientIds(@NonNull List uuids) { - List groupMembers = new ArrayList<>(uuids.size()); + List groupMembers = new ArrayList<>(uuids.size()); for (UUID uuid : uuids) { if (UuidUtil.UNKNOWN_UUID.equals(uuid)) { @@ -878,9 +895,15 @@ private static final String[] GROUP_PROJECTION = { return groupMembers; } - private static @NonNull List getV2GroupMembers(@NonNull DecryptedGroup decryptedGroup) { - List uuids = DecryptedGroupUtil.membersToUuidList(decryptedGroup.getMembersList()); - return uuidsToRecipientIds(uuids); + private static @NonNull List getV2GroupMembers(@NonNull Context context, @NonNull DecryptedGroup decryptedGroup) { + List uuids = DecryptedGroupUtil.membersToUuidList(decryptedGroup.getMembersList()); + List ids = uuidsToRecipientIds(uuids); + + if (RemappedRecords.getInstance().areAnyRemapped(context, ids)) { + throw new IllegalStateException("Remapped records in group membership!"); + } else { + return ids; + } } public @NonNull List getAllGroupV2Ids() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RemappedRecords.java b/app/src/main/java/org/thoughtcrime/securesms/database/RemappedRecords.java index f6aa72d01e..c19a54e0d1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RemappedRecords.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RemappedRecords.java @@ -8,7 +8,10 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.recipients.RecipientId; import org.whispersystems.libsignal.util.guava.Optional; +import java.util.Collection; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; /** * Merging together recipients and threads is messy business. We can easily replace *almost* all of @@ -47,6 +50,39 @@ class RemappedRecords { return Optional.fromNullable(threadMap.get(oldId)); } + boolean areAnyRemapped(@NonNull Context context, @NonNull Collection recipientIds) { + ensureRecipientMapIsPopulated(context); + return recipientIds.stream().anyMatch(id -> recipientMap.containsKey(id)); + } + + @NonNull Set remap(@NonNull Context context, @NonNull Collection recipientIds) { + ensureRecipientMapIsPopulated(context); + + Set remapped = new LinkedHashSet<>(); + + for (RecipientId original : recipientIds) { + if (recipientMap.containsKey(original)) { + remapped.add(recipientMap.get(original)); + } else { + remapped.add(original); + } + } + + return remapped; + } + + @NonNull String buildRemapDescription(@NonNull Context context, @NonNull Collection recipientIds) { + StringBuilder builder = new StringBuilder(); + + for (RecipientId original : recipientIds) { + if (recipientMap.containsKey(original)) { + builder.append(original).append(" -> ").append(recipientMap.get(original)).append(" "); + } + } + + return builder.toString(); + } + /** * Can only be called inside of a transaction. */