diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt index 081b704f64..a1854222bf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt @@ -369,7 +369,7 @@ class StorageSyncJob private constructor(parameters: Parameters) : BaseJob(param private fun processKnownRecords(context: Context, records: StorageRecordCollection) { ContactRecordProcessor().process(records.contacts, StorageSyncHelper.KEY_GENERATOR) GroupV1RecordProcessor().process(records.gv1, StorageSyncHelper.KEY_GENERATOR) - GroupV2RecordProcessor(context).process(records.gv2, StorageSyncHelper.KEY_GENERATOR) + GroupV2RecordProcessor().process(records.gv2, StorageSyncHelper.KEY_GENERATOR) AccountRecordProcessor(context, freshSelf()).process(records.account, StorageSyncHelper.KEY_GENERATOR) StoryDistributionListRecordProcessor().process(records.storyDistributionLists, StorageSyncHelper.KEY_GENERATOR) CallLinkRecordProcessor().process(records.callLinkRecords, StorageSyncHelper.KEY_GENERATOR) diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV2RecordProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV2RecordProcessor.java deleted file mode 100644 index dde55f91d0..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV2RecordProcessor.java +++ /dev/null @@ -1,139 +0,0 @@ -package org.thoughtcrime.securesms.storage; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.signal.core.util.logging.Log; -import org.signal.libsignal.zkgroup.groups.GroupMasterKey; -import org.thoughtcrime.securesms.database.GroupTable; -import org.thoughtcrime.securesms.database.RecipientTable; -import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.groups.GroupId; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.whispersystems.signalservice.api.storage.SignalGroupV2Record; -import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record; - -import java.util.Arrays; -import java.util.Map; -import java.util.Optional; - -public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor { - - private static final String TAG = Log.tag(GroupV2RecordProcessor.class); - - private final Context context; - private final RecipientTable recipientTable; - private final GroupTable groupDatabase; - private final Map gv1GroupsByExpectedGv2Id; - - public GroupV2RecordProcessor(@NonNull Context context) { - this(context, SignalDatabase.recipients(), SignalDatabase.groups()); - } - - GroupV2RecordProcessor(@NonNull Context context, @NonNull RecipientTable recipientTable, @NonNull GroupTable groupDatabase) { - this.context = context; - this.recipientTable = recipientTable; - this.groupDatabase = groupDatabase; - this.gv1GroupsByExpectedGv2Id = groupDatabase.getAllExpectedV2Ids(); - } - - @Override - public boolean isInvalid(@NonNull SignalGroupV2Record remote) { - return remote.getMasterKeyBytes().length != GroupMasterKey.SIZE; - } - - @Override - public @NonNull Optional getMatching(@NonNull SignalGroupV2Record record, @NonNull StorageKeyGenerator keyGenerator) { - GroupId.V2 groupId = GroupId.v2(record.getMasterKeyOrThrow()); - - Optional recipientId = recipientTable.getByGroupId(groupId); - - return recipientId.map(recipientTable::getRecordForSync) - .map(settings -> { - if (settings.getSyncExtras().getGroupMasterKey() != null) { - return StorageSyncModels.localToRemoteRecord(settings); - } else { - Log.w(TAG, "No local master key. Assuming it matches remote since the groupIds match. Enqueuing a fetch to fix the bad state."); - groupDatabase.fixMissingMasterKey(record.getMasterKeyOrThrow()); - return StorageSyncModels.localToRemoteRecord(settings, record.getMasterKeyOrThrow()); - } - }) - .map(r -> r.getGroupV2().get()); - } - - @Override - public @NonNull SignalGroupV2Record merge(@NonNull SignalGroupV2Record remote, @NonNull SignalGroupV2Record local, @NonNull StorageKeyGenerator keyGenerator) { - byte[] unknownFields = remote.serializeUnknownFields(); - boolean blocked = remote.isBlocked(); - boolean profileSharing = remote.isProfileSharingEnabled(); - boolean archived = remote.isArchived(); - boolean forcedUnread = remote.isForcedUnread(); - long muteUntil = remote.getMuteUntil(); - boolean notifyForMentionsWhenMuted = remote.notifyForMentionsWhenMuted(); - boolean hideStory = remote.shouldHideStory(); - GroupV2Record.StorySendMode storySendMode = remote.getStorySendMode(); - - boolean matchesRemote = doParamsMatch(remote, unknownFields, blocked, profileSharing, archived, forcedUnread, muteUntil, notifyForMentionsWhenMuted, hideStory, storySendMode); - boolean matchesLocal = doParamsMatch(local, unknownFields, blocked, profileSharing, archived, forcedUnread, muteUntil, notifyForMentionsWhenMuted, hideStory, storySendMode); - - if (matchesRemote) { - return remote; - } else if (matchesLocal) { - return local; - } else { - return new SignalGroupV2Record.Builder(keyGenerator.generate(), remote.getMasterKeyBytes(), unknownFields) - .setBlocked(blocked) - .setProfileSharingEnabled(profileSharing) - .setArchived(archived) - .setForcedUnread(forcedUnread) - .setMuteUntil(muteUntil) - .setNotifyForMentionsWhenMuted(notifyForMentionsWhenMuted) - .setHideStory(hideStory) - .setStorySendMode(storySendMode) - .build(); - } - } - - @Override - public void insertLocal(@NonNull SignalGroupV2Record record) { - recipientTable.applyStorageSyncGroupV2Insert(record); - } - - @Override - public void updateLocal(@NonNull StorageRecordUpdate update) { - recipientTable.applyStorageSyncGroupV2Update(update); - } - - @Override - public int compare(@NonNull SignalGroupV2Record lhs, @NonNull SignalGroupV2Record rhs) { - if (Arrays.equals(lhs.getMasterKeyBytes(), rhs.getMasterKeyBytes())) { - return 0; - } else { - return 1; - } - } - - private boolean doParamsMatch(@NonNull SignalGroupV2Record group, - @Nullable byte[] unknownFields, - boolean blocked, - boolean profileSharing, - boolean archived, - boolean forcedUnread, - long muteUntil, - boolean notifyForMentionsWhenMuted, - boolean hideStory, - @NonNull GroupV2Record.StorySendMode storySendMode) - { - return Arrays.equals(unknownFields, group.serializeUnknownFields()) && - blocked == group.isBlocked() && - profileSharing == group.isProfileSharingEnabled() && - archived == group.isArchived() && - forcedUnread == group.isForcedUnread() && - muteUntil == group.getMuteUntil() && - notifyForMentionsWhenMuted == group.notifyForMentionsWhenMuted() && - hideStory == group.shouldHideStory() && - storySendMode == group.getStorySendMode(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV2RecordProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV2RecordProcessor.kt new file mode 100644 index 0000000000..0c1be8ec8c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV2RecordProcessor.kt @@ -0,0 +1,137 @@ +package org.thoughtcrime.securesms.storage + +import org.signal.core.util.logging.Log +import org.signal.libsignal.zkgroup.groups.GroupMasterKey +import org.thoughtcrime.securesms.database.GroupTable +import org.thoughtcrime.securesms.database.RecipientTable +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.model.RecipientRecord +import org.thoughtcrime.securesms.groups.GroupId +import org.whispersystems.signalservice.api.storage.SignalGroupV2Record +import org.whispersystems.signalservice.api.storage.SignalStorageRecord +import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record +import java.util.Optional + +class GroupV2RecordProcessor(private val recipientTable: RecipientTable, private val groupDatabase: GroupTable) : DefaultStorageRecordProcessor() { + companion object { + private val TAG = Log.tag(GroupV2RecordProcessor::class.java) + } + + constructor() : this(SignalDatabase.recipients, SignalDatabase.groups) + + override fun isInvalid(remote: SignalGroupV2Record): Boolean { + return remote.masterKeyBytes.size != GroupMasterKey.SIZE + } + + override fun getMatching(remote: SignalGroupV2Record, keyGenerator: StorageKeyGenerator): Optional { + val groupId = GroupId.v2(remote.masterKeyOrThrow) + + val recipientId = recipientTable.getByGroupId(groupId) + + return recipientId + .map { recipientTable.getRecordForSync(it)!! } + .map { settings: RecipientRecord -> + if (settings.syncExtras.groupMasterKey != null) { + StorageSyncModels.localToRemoteRecord(settings) + } else { + Log.w(TAG, "No local master key. Assuming it matches remote since the groupIds match. Enqueuing a fetch to fix the bad state.") + groupDatabase.fixMissingMasterKey(remote.masterKeyOrThrow) + StorageSyncModels.localToRemoteRecord(settings, remote.masterKeyOrThrow) + } + } + .map { record: SignalStorageRecord -> record.groupV2.get() } + } + + override fun merge(remote: SignalGroupV2Record, local: SignalGroupV2Record, keyGenerator: StorageKeyGenerator): SignalGroupV2Record { + val unknownFields = remote.serializeUnknownFields() + val blocked = remote.isBlocked + val profileSharing = remote.isProfileSharingEnabled + val archived = remote.isArchived + val forcedUnread = remote.isForcedUnread + val muteUntil = remote.muteUntil + val notifyForMentionsWhenMuted = remote.notifyForMentionsWhenMuted() + val hideStory = remote.shouldHideStory() + val storySendMode = remote.storySendMode + + val matchesRemote = doParamsMatch( + group = remote, + unknownFields = unknownFields, + blocked = blocked, + profileSharing = profileSharing, + archived = archived, + forcedUnread = forcedUnread, + muteUntil = muteUntil, + notifyForMentionsWhenMuted = notifyForMentionsWhenMuted, + hideStory = hideStory, + storySendMode = storySendMode + ) + val matchesLocal = doParamsMatch( + group = local, + unknownFields = unknownFields, + blocked = blocked, + profileSharing = profileSharing, + archived = archived, + forcedUnread = forcedUnread, + muteUntil = muteUntil, + notifyForMentionsWhenMuted = notifyForMentionsWhenMuted, + hideStory = hideStory, + storySendMode = storySendMode + ) + + return if (matchesRemote) { + remote + } else if (matchesLocal) { + local + } else { + SignalGroupV2Record.Builder(keyGenerator.generate(), remote.masterKeyBytes, unknownFields) + .setBlocked(blocked) + .setProfileSharingEnabled(profileSharing) + .setArchived(archived) + .setForcedUnread(forcedUnread) + .setMuteUntil(muteUntil) + .setNotifyForMentionsWhenMuted(notifyForMentionsWhenMuted) + .setHideStory(hideStory) + .setStorySendMode(storySendMode) + .build() + } + } + + override fun insertLocal(record: SignalGroupV2Record) { + recipientTable.applyStorageSyncGroupV2Insert(record) + } + + override fun updateLocal(update: StorageRecordUpdate) { + recipientTable.applyStorageSyncGroupV2Update(update) + } + + override fun compare(lhs: SignalGroupV2Record, rhs: SignalGroupV2Record): Int { + return if (lhs.masterKeyBytes.contentEquals(rhs.masterKeyBytes)) { + 0 + } else { + 1 + } + } + + private fun doParamsMatch( + group: SignalGroupV2Record, + unknownFields: ByteArray?, + blocked: Boolean, + profileSharing: Boolean, + archived: Boolean, + forcedUnread: Boolean, + muteUntil: Long, + notifyForMentionsWhenMuted: Boolean, + hideStory: Boolean, + storySendMode: GroupV2Record.StorySendMode + ): Boolean { + return unknownFields.contentEquals(group.serializeUnknownFields()) && + blocked == group.isBlocked && + profileSharing == group.isProfileSharingEnabled && + archived == group.isArchived && + forcedUnread == group.isForcedUnread && + muteUntil == group.muteUntil && + notifyForMentionsWhenMuted == group.notifyForMentionsWhenMuted() && + hideStory == group.shouldHideStory() && + storySendMode == group.storySendMode + } +}