Implement message expiration timer version

Fixes #1605
This commit is contained in:
AsamK 2024-10-26 13:08:21 +02:00
parent 5171107a29
commit 5a4f4ba6db
11 changed files with 91 additions and 31 deletions

View file

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

View file

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

View file

@ -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) {

View file

@ -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) {

View file

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

View file

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

View file

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

View file

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

View file

@ -46,6 +46,7 @@ public class LegacyRecipientStore2 {
null,
r.contact.color,
r.contact.messageExpirationTime,
1,
0,
false,
r.contact.blocked,

View file

@ -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<Pair<RecipientId, Contact>> 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"),