Introduce extra caching for group message processing.

This commit is contained in:
Clark
2023-05-17 13:54:37 -04:00
committed by Greyson Parrelli
parent 44ab1643fa
commit 2d6b16b2ce
15 changed files with 149 additions and 33 deletions

View File

@@ -12,6 +12,7 @@ import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupIdentifier;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.thoughtcrime.securesms.util.LRUCache;
import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
@@ -29,6 +30,8 @@ public abstract class GroupId implements DatabaseId {
private final String encodedId;
private static final LRUCache<GroupMasterKey, GroupIdentifier> groupIdentifierCache = new LRUCache<>(1000);
private GroupId(@NonNull String prefix, @NonNull byte[] bytes) {
this.encodedId = prefix + Hex.toStringCondensed(bytes);
}
@@ -80,9 +83,23 @@ public abstract class GroupId implements DatabaseId {
}
public static GroupId.V2 v2(@NonNull GroupMasterKey masterKey) {
return v2(GroupSecretParams.deriveFromMasterKey(masterKey)
.getPublicParams()
.getGroupIdentifier());
return v2(getIdentifierForMasterKey(masterKey));
}
public static GroupIdentifier getIdentifierForMasterKey(@NonNull GroupMasterKey masterKey) {
GroupIdentifier cachedIdentifier;
synchronized (groupIdentifierCache) {
cachedIdentifier = groupIdentifierCache.get(masterKey);
}
if (cachedIdentifier == null) {
cachedIdentifier = GroupSecretParams.deriveFromMasterKey(masterKey)
.getPublicParams()
.getGroupIdentifier();
synchronized (groupIdentifierCache) {
groupIdentifierCache.put(masterKey, cachedIdentifier);
}
}
return cachedIdentifier;
}
public static GroupId.Push push(ByteString bytes) throws BadGroupIdException {

View File

@@ -32,6 +32,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@@ -187,12 +188,15 @@ public final class GroupManager {
@Nullable byte[] signedGroupChange)
throws GroupChangeBusyException, IOException, GroupNotAMemberException
{
return updateGroupFromServer(context, groupMasterKey, null, revision, timestamp, signedGroupChange);
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
return updater.updateLocalToServerRevision(revision, timestamp, null, signedGroupChange);
}
}
@WorkerThread
public static GroupsV2StateProcessor.GroupUpdateResult updateGroupFromServer(@NonNull Context context,
@NonNull GroupMasterKey groupMasterKey,
@NonNull Optional<GroupRecord> groupRecord,
@Nullable GroupSecretParams groupSecretParams,
int revision,
long timestamp,
@@ -200,7 +204,7 @@ public final class GroupManager {
throws GroupChangeBusyException, IOException, GroupNotAMemberException
{
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
return updater.updateLocalToServerRevision(revision, timestamp, groupSecretParams, signedGroupChange);
return updater.updateLocalToServerRevision(revision, timestamp, groupRecord, groupSecretParams, signedGroupChange);
}
}

View File

@@ -804,6 +804,14 @@ final class GroupManagerV2 {
.updateLocalGroupToRevision(revision, timestamp, getDecryptedGroupChange(signedGroupChange));
}
@WorkerThread
GroupsV2StateProcessor.GroupUpdateResult updateLocalToServerRevision(int revision, long timestamp, @NonNull Optional<GroupRecord> localRecord, @Nullable GroupSecretParams groupSecretParams, @Nullable byte[] signedGroupChange)
throws IOException, GroupNotAMemberException
{
return new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey, groupSecretParams)
.updateLocalGroupToRevision(revision, timestamp, localRecord, getDecryptedGroupChange(signedGroupChange));
}
@WorkerThread
void forceSanityUpdateFromServer(long timestamp)
throws IOException, GroupNotAMemberException
@@ -929,11 +937,11 @@ final class GroupManagerV2 {
alreadyAMember = true;
}
Optional<GroupRecord> unmigratedV1Group = groupDatabase.getGroupV1ByExpectedV2(groupId);
GroupRecord unmigratedV1Group = GroupsV1MigratedCache.getV1GroupByV2Id(groupId);
if (unmigratedV1Group.isPresent()) {
if (unmigratedV1Group != null) {
Log.i(TAG, "Group link was for a migrated V1 group we know about! Migrating it and using that as the base.");
GroupsV1MigrationUtil.performLocalMigration(context, unmigratedV1Group.get().getId().requireV1());
GroupsV1MigrationUtil.performLocalMigration(context, unmigratedV1Group.getId().requireV1());
}
DecryptedGroup decryptedGroup = createPlaceholderGroup(joinInfo, requestToJoin);

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.groups
import androidx.annotation.WorkerThread
import org.signal.core.util.orNull
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.util.LRUCache
/**
* Cache to keep track of groups we know do not need a migration run on. This is to save time looking for a gv1 group
* with the expected v2 id.
*/
object GroupsV1MigratedCache {
private const val MAX_CACHE = 1000
private val noV1GroupCache = LRUCache<GroupId.V2, Boolean>(MAX_CACHE)
@JvmStatic
@WorkerThread
fun hasV1Group(groupId: GroupId.V2): Boolean {
return getV1GroupByV2Id(groupId) != null
}
@JvmStatic
@WorkerThread
fun getV1GroupByV2Id(groupId: GroupId.V2): GroupRecord? {
synchronized(noV1GroupCache) {
if (noV1GroupCache.containsKey(groupId)) {
return null
}
}
val v1Group = SignalDatabase.groups.getGroupV1ByExpectedV2(groupId)
if (!v1Group.isPresent) {
synchronized(noV1GroupCache) {
noV1GroupCache.put(groupId, true)
}
}
return v1Group.orNull()
}
}

View File

@@ -266,8 +266,21 @@ public class GroupsV2StateProcessor {
@Nullable DecryptedGroupChange signedGroupChange)
throws IOException, GroupNotAMemberException
{
Optional<GroupRecord> localRecord = groupDatabase.getGroup(groupId);
return updateLocalGroupToRevision(revision, timestamp, groupDatabase.getGroup(groupId), signedGroupChange);
}
/**
* Using network where required, will attempt to bring the local copy of the group up to the revision specified.
*
* @param revision use {@link #LATEST} to get latest.
*/
@WorkerThread
public GroupUpdateResult updateLocalGroupToRevision(final int revision,
final long timestamp,
@NonNull Optional<GroupRecord> localRecord,
@Nullable DecryptedGroupChange signedGroupChange)
throws IOException, GroupNotAMemberException
{
if (localIsAtLeast(localRecord, revision)) {
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
}