From 5a4f4ba6db935d54757f4c6a79e544708a994d6f Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 26 Oct 2024 13:08:21 +0200 Subject: [PATCH] Implement message expiration timer version Fixes #1605 --- .../org/asamk/signal/manager/api/Contact.java | 8 ++++ .../signal/manager/config/ServiceConfig.java | 3 +- .../signal/manager/helper/ContactHelper.java | 28 ++++++++++++- .../helper/IncomingMessageHandler.java | 4 +- .../signal/manager/helper/SendHelper.java | 8 ++-- .../signal/manager/helper/SyncHelper.java | 15 ++++++- .../manager/storage/AccountDatabase.java | 10 ++++- .../signal/manager/storage/SignalAccount.java | 2 + .../recipients/LegacyRecipientStore2.java | 1 + .../storage/recipients/RecipientStore.java | 42 ++++++++++--------- .../asamk/signal/dbus/DbusManagerImpl.java | 1 + 11 files changed, 91 insertions(+), 31 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Contact.java b/lib/src/main/java/org/asamk/signal/manager/api/Contact.java index 3605b7c5..91cf82df 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Contact.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Contact.java @@ -11,6 +11,7 @@ public record Contact( String note, String color, int messageExpirationTime, + int messageExpirationTimeVersion, long muteUntil, boolean hideStory, boolean isBlocked, @@ -29,6 +30,7 @@ public record Contact( builder.note, builder.color, builder.messageExpirationTime, + builder.messageExpirationTimeVersion, builder.muteUntil, builder.hideStory, builder.isBlocked, @@ -84,6 +86,7 @@ public record Contact( private String note; private String color; private int messageExpirationTime; + private int messageExpirationTimeVersion = 1; private long muteUntil; private boolean hideStory; private boolean isBlocked; @@ -139,6 +142,11 @@ public record Contact( return this; } + public Builder withMessageExpirationTimeVersion(final int val) { + messageExpirationTimeVersion = val; + return this; + } + public Builder withMuteUntil(final long val) { muteUntil = val; return this; 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 52ee82e2..7ee80815 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 @@ -29,8 +29,7 @@ public class ServiceConfig { public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) { final var deleteSync = !isPrimaryDevice; - final var versionedExpirationTimer = !isPrimaryDevice; - return new AccountAttributes.Capabilities(true, deleteSync, versionedExpirationTimer); + return new AccountAttributes.Capabilities(true, deleteSync, true); } public static ServiceEnvironmentConfig getServiceEnvironmentConfig( diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java index f9af27f1..56ef89ed 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java @@ -36,8 +36,34 @@ public class ContactHelper { return; } final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); + final var version = contact == null + ? 1 + : contact.messageExpirationTimeVersion() == Integer.MAX_VALUE + ? Integer.MAX_VALUE + : contact.messageExpirationTimeVersion() + 1; account.getContactStore() - .storeContact(recipientId, builder.withMessageExpirationTime(messageExpirationTimer).build()); + .storeContact(recipientId, + builder.withMessageExpirationTime(messageExpirationTimer) + .withMessageExpirationTimeVersion(version) + .build()); + } + + public void setExpirationTimer( + RecipientId recipientId, int messageExpirationTimer, int messageExpirationTimerVersion + ) { + var contact = account.getContactStore().getContact(recipientId); + if (contact != null && ( + contact.messageExpirationTime() == messageExpirationTimer + || contact.messageExpirationTimeVersion() >= messageExpirationTimerVersion + )) { + return; + } + final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); + account.getContactStore() + .storeContact(recipientId, + builder.withMessageExpirationTime(messageExpirationTimer) + .withMessageExpirationTimeVersion(messageExpirationTimerVersion) + .build()); } public void setContactBlocked(RecipientId recipientId, boolean blocked) { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index 5e04f135..294b17f6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -802,7 +802,9 @@ public final class IncomingMessageHandler { } } else if (conversationPartnerAddress != null) { context.getContactHelper() - .setExpirationTimer(conversationPartnerAddress.recipientId(), message.getExpiresInSeconds()); + .setExpirationTimer(conversationPartnerAddress.recipientId(), + message.getExpiresInSeconds(), + message.getExpireTimerVersion()); } } if (!ignoreAttachments) { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java index 84b9f0e6..0622b4a8 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java @@ -86,8 +86,8 @@ public class SendHelper { account.getContactStore().storeContact(recipientId, contact); } - final var expirationTime = contact.messageExpirationTime(); - messageBuilder.withExpiration(expirationTime); + messageBuilder.withExpiration(contact.messageExpirationTime()); + messageBuilder.withExpireTimerVersion(contact.messageExpirationTimeVersion()); if (!contact.isBlocked()) { final var profileKey = account.getProfileKey().serialize(); @@ -187,8 +187,8 @@ public class SendHelper { ) { final var recipientId = account.getSelfRecipientId(); final var contact = account.getContactStore().getContact(recipientId); - final var expirationTime = contact != null ? contact.messageExpirationTime() : 0; - messageBuilder.withExpiration(expirationTime); + messageBuilder.withExpiration(contact != null ? contact.messageExpirationTime() : 0); + messageBuilder.withExpireTimerVersion(contact != null ? contact.messageExpirationTimeVersion() : 1); var message = messageBuilder.build(); return sendSelfMessage(message, editTargetTimestamp); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java index ecaf7605..56665fda 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java @@ -239,7 +239,7 @@ public class SyncHelper { Optional.ofNullable(verifiedMessage), Optional.ofNullable(profileKey), Optional.ofNullable(contact == null ? null : contact.messageExpirationTime()), - Optional.empty(), + Optional.ofNullable(contact == null ? null : contact.messageExpirationTimeVersion()), Optional.empty(), contact != null && contact.isArchived()); } @@ -392,7 +392,18 @@ public class SyncHelper { TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); } if (c.getExpirationTimer().isPresent()) { - builder.withMessageExpirationTime(c.getExpirationTimer().get()); + if (c.getExpirationTimerVersion().isPresent() && ( + contact == null || c.getExpirationTimerVersion().get() > contact.messageExpirationTimeVersion() + )) { + builder.withMessageExpirationTime(c.getExpirationTimer().get()); + builder.withMessageExpirationTimeVersion(c.getExpirationTimerVersion().get()); + } else { + logger.debug( + "[ContactSync] {} was synced with an old expiration timer. Ignoring. Received: {} Current: ${}", + recipientId, + c.getExpirationTimerVersion(), + contact == null ? 1 : contact.messageExpirationTimeVersion()); + } } builder.withIsArchived(c.isArchived()); account.getContactStore().storeContact(recipientId, builder.build()); 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 3d9fad9c..be9e7af1 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 @@ -33,7 +33,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 = 26; + private static final long DATABASE_VERSION = 27; private AccountDatabase(final HikariDataSource dataSource) { super(logger, DATABASE_VERSION, dataSource); @@ -600,6 +600,14 @@ public class AccountDatabase extends Database { """); } } + if (oldVersion < 27) { + logger.debug("Updating database: Create expiration_time_version column"); + try (final var statement = connection.createStatement()) { + statement.executeUpdate(""" + ALTER TABLE recipient ADD expiration_time_version INTEGER DEFAULT 1 NOT NULL; + """); + } + } } private static void createUuidMappingTable( diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index 0ca76e9d..87cf3aa3 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -864,6 +864,7 @@ public class SignalAccount implements Closeable { null, contact.color, contact.messageExpirationTime, + 1, 0, false, contact.blocked, @@ -939,6 +940,7 @@ public class SignalAccount implements Closeable { getContactStore().storeContact(recipientId, Contact.newBuilder(contact) .withMessageExpirationTime(thread.messageExpirationTime) + .withMessageExpirationTimeVersion(1) .build()); } } else { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java index e7aff640..e8d44ab9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java @@ -46,6 +46,7 @@ public class LegacyRecipientStore2 { null, r.contact.color, r.contact.messageExpirationTime, + 1, 0, false, r.contact.blocked, 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 6a672008..54383eb0 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 @@ -79,6 +79,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re color TEXT, expiration_time INTEGER NOT NULL DEFAULT 0, + expiration_time_version INTEGER DEFAULT 1 NOT NULL, mute_until INTEGER NOT NULL DEFAULT 0, blocked INTEGER NOT NULL DEFAULT FALSE, archived INTEGER NOT NULL DEFAULT FALSE, @@ -332,7 +333,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re public List> getContacts() { final var sql = ( """ - SELECT r._id, r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp + SELECT r._id, r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp FROM %s r WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) AND %s AND r.hidden = FALSE """ @@ -356,7 +357,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re SELECT r._id, r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, - r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, + r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing, r.discoverable, r.storage_record @@ -376,7 +377,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re SELECT r._id, r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, - r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, + r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing, r.discoverable, r.storage_record @@ -413,7 +414,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re SELECT r._id, r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, - r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, + r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing, r.discoverable, r.storage_record @@ -817,7 +818,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var sql = ( """ UPDATE %s - SET given_name = ?, family_name = ?, nick_name = ?, expiration_time = ?, mute_until = ?, hide_story = ?, profile_sharing = ?, color = ?, blocked = ?, archived = ?, unregistered_timestamp = ?, nick_name_given_name = ?, nick_name_family_name = ?, note = ? + SET given_name = ?, family_name = ?, nick_name = ?, expiration_time = ?, expiration_time_version = ?, mute_until = ?, hide_story = ?, profile_sharing = ?, color = ?, blocked = ?, archived = ?, unregistered_timestamp = ?, nick_name_given_name = ?, nick_name_family_name = ?, note = ? WHERE _id = ? """ ).formatted(TABLE_RECIPIENT); @@ -826,21 +827,22 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re statement.setString(2, contact == null ? null : contact.familyName()); statement.setString(3, contact == null ? null : contact.nickName()); statement.setInt(4, contact == null ? 0 : contact.messageExpirationTime()); - statement.setLong(5, contact == null ? 0 : contact.muteUntil()); - statement.setBoolean(6, contact != null && contact.hideStory()); - statement.setBoolean(7, contact != null && contact.isProfileSharingEnabled()); - statement.setString(8, contact == null ? null : contact.color()); - statement.setBoolean(9, contact != null && contact.isBlocked()); - statement.setBoolean(10, contact != null && contact.isArchived()); + statement.setInt(5, contact == null ? 0 : Math.max(1, contact.messageExpirationTimeVersion())); + statement.setLong(6, contact == null ? 0 : contact.muteUntil()); + statement.setBoolean(7, contact != null && contact.hideStory()); + statement.setBoolean(8, contact != null && contact.isProfileSharingEnabled()); + statement.setString(9, contact == null ? null : contact.color()); + statement.setBoolean(10, contact != null && contact.isBlocked()); + statement.setBoolean(11, contact != null && contact.isArchived()); if (contact == null || contact.unregisteredTimestamp() == null) { - statement.setNull(11, Types.INTEGER); + statement.setNull(12, Types.INTEGER); } else { - statement.setLong(11, contact.unregisteredTimestamp()); + statement.setLong(12, contact.unregisteredTimestamp()); } - statement.setString(12, contact == null ? null : contact.nickNameGivenName()); - statement.setString(13, contact == null ? null : contact.nickNameFamilyName()); - statement.setString(14, contact == null ? null : contact.note()); - statement.setLong(15, recipientId.id()); + statement.setString(13, contact == null ? null : contact.nickNameGivenName()); + statement.setString(14, contact == null ? null : contact.nickNameFamilyName()); + statement.setString(15, contact == null ? null : contact.note()); + statement.setLong(16, recipientId.id()); statement.executeUpdate(); } if (contact != null && contact.unregisteredTimestamp() != null) { @@ -918,8 +920,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re markDiscoverable(connection, recipientId, false); final var contact = getContact(connection, recipientId); if (recipientAddress.get().address().aci().isEmpty() || ( - contact != null - && contact.unregisteredTimestamp() != null + contact != null && contact.unregisteredTimestamp() != null )) { markUnregisteredAndSplitIfNecessary(connection, recipientId); } @@ -1416,7 +1417,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re private Contact getContact(final Connection connection, final RecipientId recipientId) throws SQLException { final var sql = ( """ - SELECT r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp + SELECT r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp FROM %s r WHERE r._id = ? AND (%s) """ @@ -1514,6 +1515,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re resultSet.getString("note"), resultSet.getString("color"), resultSet.getInt("expiration_time"), + resultSet.getInt("expiration_time_version"), resultSet.getLong("mute_until"), resultSet.getBoolean("hide_story"), resultSet.getBoolean("blocked"), diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index 9ae3001a..bd5bdfb2 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -699,6 +699,7 @@ public class DbusManagerImpl implements Manager { null, null, 0, + 1, 0, false, contactBlocked, -- 2.50.1