Store recipient unregistered state

This commit is contained in:
AsamK 2024-01-27 17:22:21 +01:00
parent 6a5dcd00b2
commit f92466f6be
5 changed files with 102 additions and 2 deletions

View file

@ -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;

View file

@ -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;
}

View file

@ -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());

View file

@ -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;
""");
}
}
}
}

View file

@ -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<StorageId> 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<String> 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);