From f92466f6be7d598bc75b6769c4d020d268eb1646 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 27 Jan 2024 17:22:21 +0100 Subject: [PATCH] Store recipient unregistered state --- .../signal/manager/config/ServiceConfig.java | 1 + .../manager/helper/RecipientHelper.java | 4 + .../signal/manager/helper/StorageHelper.java | 11 +++ .../manager/storage/AccountDatabase.java | 11 ++- .../storage/recipients/RecipientStore.java | 77 ++++++++++++++++++- 5 files changed, 102 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java index d46b6988..ea47b8a6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java @@ -24,6 +24,7 @@ public class ServiceConfig { public static final boolean AUTOMATIC_NETWORK_RETRY = true; public static final int GROUP_MAX_SIZE = 1001; public static final int MAXIMUM_ONE_OFF_REQUEST_SIZE = 3; + public static final long UNREGISTERED_LIFESPAN = TimeUnit.DAYS.toMillis(30); public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) { final var giftBadges = !isPrimaryDevice; diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java index 4a0bc9c4..83646052 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java @@ -164,6 +164,10 @@ public class RecipientHelper { registeredUsers.forEach((number, u) -> account.getRecipientTrustedResolver() .resolveRecipientTrusted(u.aci, u.pni, Optional.of(number))); + final var unregisteredUsers = new HashSet<>(numbers); + unregisteredUsers.removeAll(registeredUsers.keySet()); + account.getRecipientStore().markUnregistered(unregisteredUsers); + return registeredUsers; } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java index 8c8ac6fc..5bccfd05 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java @@ -149,6 +149,17 @@ public class StorageHelper { logger.debug("Pre-Merge ID Difference :: " + idDifference); + if (!idDifference.localOnlyIds().isEmpty()) { + final var updated = account.getRecipientStore() + .removeStorageIdsFromLocalOnlyUnregisteredRecipients(connection, idDifference.localOnlyIds()); + + if (updated > 0) { + logger.warn( + "Found {} records that were deleted remotely but only marked unregistered locally. Removed those from local store. Recalculating diff.", + updated); + } + } + if (!idDifference.isEmpty()) { final var remoteOnlyRecords = getSignalStorageRecords(storageKey, idDifference.remoteOnlyIds()); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java index c49e56ab..9e9eebf8 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java @@ -32,7 +32,7 @@ import java.util.UUID; public class AccountDatabase extends Database { private static final Logger logger = LoggerFactory.getLogger(AccountDatabase.class); - private static final long DATABASE_VERSION = 20; + private static final long DATABASE_VERSION = 21; private AccountDatabase(final HikariDataSource dataSource) { super(logger, DATABASE_VERSION, dataSource); @@ -558,5 +558,14 @@ public class AccountDatabase extends Database { """); } } + if (oldVersion < 21) { + logger.debug("Updating database: Create unregistered column"); + try (final var statement = connection.createStatement()) { + statement.executeUpdate(""" + ALTER TABLE recipient ADD unregistered_timestamp INTEGER; + UPDATE recipient SET pni = NULL WHERE uuid IS NOT NULL; + """); + } + } } } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java index 926f09bd..46f8652c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java @@ -37,6 +37,8 @@ import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; +import static org.asamk.signal.manager.config.ServiceConfig.UNREGISTERED_LIFESPAN; + public class RecipientStore implements RecipientIdCreator, RecipientResolver, RecipientTrustedResolver, ContactsStore, ProfileStore { private static final Logger logger = LoggerFactory.getLogger(RecipientStore.class); @@ -65,6 +67,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re username TEXT UNIQUE, uuid BLOB UNIQUE, pni BLOB UNIQUE, + unregistered_timestamp INTEGER, profile_key BLOB, profile_key_credential BLOB, @@ -521,7 +524,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re """ SELECT r._id FROM %s r - WHERE r.storage_id IS NULL + WHERE r.storage_id IS NULL AND (r.unregistered_timestamp IS NULL OR r.unregistered_timestamp > ?) """ ).formatted(TABLE_RECIPIENT); final var updateSql = ( @@ -534,6 +537,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re try (final var connection = database.getConnection()) { connection.setAutoCommit(false); try (final var selectStmt = connection.prepareStatement(selectSql)) { + selectStmt.setLong(1, System.currentTimeMillis() - UNREGISTERED_LIFESPAN); final var recipientIds = Utils.executeQueryForStream(selectStmt, this::getRecipientIdFromResultSet) .toList(); try (final var updateStmt = connection.prepareStatement(updateSql)) { @@ -835,6 +839,76 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re rotateStorageId(connection, recipientId); } + public int removeStorageIdsFromLocalOnlyUnregisteredRecipients( + final Connection connection, final List storageIds + ) throws SQLException { + final var sql = ( + """ + UPDATE %s + SET storage_id = NULL + WHERE storage_id = ? AND storage_id IS NOT NULL AND unregistered_timestamp IS NOT NULL + """ + ).formatted(TABLE_RECIPIENT); + var count = 0; + try (final var statement = connection.prepareStatement(sql)) { + for (final var storageId : storageIds) { + statement.setBytes(1, storageId.getRaw()); + count += statement.executeUpdate(); + } + } + return count; + } + + public void markUnregistered(final Set unregisteredUsers) { + logger.debug("Marking {} numbers as unregistered", unregisteredUsers.size()); + try (final var connection = database.getConnection()) { + connection.setAutoCommit(false); + for (final var number : unregisteredUsers) { + final var recipient = findByNumber(connection, number); + if (recipient.isPresent()) { + markUnregistered(connection, recipient.get().id()); + } + } + connection.commit(); + } catch (SQLException e) { + throw new RuntimeException("Failed update recipient store", e); + } + } + + private void markRegistered( + final Connection connection, final RecipientId recipientId + ) throws SQLException { + final var sql = ( + """ + UPDATE %s + SET unregistered_timestamp = ? + WHERE _id = ? + """ + ).formatted(TABLE_RECIPIENT); + try (final var statement = connection.prepareStatement(sql)) { + statement.setNull(1, Types.INTEGER); + statement.setLong(2, recipientId.id()); + statement.executeUpdate(); + } + } + + private void markUnregistered( + final Connection connection, final RecipientId recipientId + ) throws SQLException { + final var sql = ( + """ + UPDATE %s + SET unregistered_timestamp = ? + WHERE _id = ? AND unregistered_timestamp IS NULL + """ + ).formatted(TABLE_RECIPIENT); + try (final var statement = connection.prepareStatement(sql)) { + statement.setLong(1, System.currentTimeMillis()); + statement.setLong(2, recipientId.id()); + statement.executeUpdate(); + } + } + private void storeExpiringProfileKeyCredential( final Connection connection, final RecipientId recipientId, @@ -948,6 +1022,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re return new Pair<>(resolveRecipientLocked(connection, address), List.of()); } else { final var pair = MergeRecipientHelper.resolveRecipientTrustedLocked(new HelperStore(connection), address); + markRegistered(connection, pair.first()); for (final var toBeMergedRecipientId : pair.second()) { mergeRecipientsLocked(connection, pair.first(), toBeMergedRecipientId); -- 2.50.1