Add support for versioned expiration timers.

Co-authored-by: Greyson Parrelli <greyson@signal.org>
This commit is contained in:
Cody Henthorne
2024-08-27 07:41:35 -04:00
committed by Nicholas Tinsley
parent 4152294b57
commit 1f196f74ff
43 changed files with 392 additions and 139 deletions

View File

@@ -716,7 +716,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
}
if (groupState?.disappearingMessagesTimer != null) {
recipients.setExpireMessages(groupRecipientId, groupState.disappearingMessagesTimer!!.duration)
recipients.setExpireMessagesForGroup(groupRecipientId, groupState.disappearingMessagesTimer!!.duration)
}
if (groupId.isMms || Recipient.resolved(groupRecipientId).isProfileSharing) {
@@ -843,7 +843,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
}
if (decryptedGroup.disappearingMessagesTimer != null) {
recipients.setExpireMessages(groupRecipientId, decryptedGroup.disappearingMessagesTimer!!.duration)
recipients.setExpireMessagesForGroup(groupRecipientId, decryptedGroup.disappearingMessagesTimer!!.duration)
}
if (groupId.isMms || Recipient.resolved(groupRecipientId).isProfileSharing) {

View File

@@ -169,6 +169,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
const val SMS_SUBSCRIPTION_ID = "subscription_id"
const val EXPIRES_IN = "expires_in"
const val EXPIRE_STARTED = "expire_started"
const val EXPIRE_TIMER_VERSION = "expire_timer_version"
const val NOTIFIED = "notified"
const val NOTIFIED_TIMESTAMP = "notified_timestamp"
const val UNIDENTIFIED = "unidentified"
@@ -264,7 +265,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
$LATEST_REVISION_ID INTEGER DEFAULT NULL REFERENCES $TABLE_NAME ($ID) ON DELETE CASCADE,
$ORIGINAL_MESSAGE_ID INTEGER DEFAULT NULL REFERENCES $TABLE_NAME ($ID) ON DELETE CASCADE,
$REVISION_NUMBER INTEGER DEFAULT 0,
$MESSAGE_EXTRAS BLOB DEFAULT NULL
$MESSAGE_EXTRAS BLOB DEFAULT NULL,
$EXPIRE_TIMER_VERSION INTEGER DEFAULT 1 NOT NULL
)
"""
@@ -321,6 +323,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
SMS_SUBSCRIPTION_ID,
EXPIRES_IN,
EXPIRE_STARTED,
EXPIRE_TIMER_VERSION,
NOTIFIED,
QUOTE_ID,
QUOTE_AUTHOR,
@@ -2404,6 +2407,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val timestamp = cursor.requireLong(DATE_SENT)
val subscriptionId = cursor.requireInt(SMS_SUBSCRIPTION_ID)
val expiresIn = cursor.requireLong(EXPIRES_IN)
val expireTimerVersion = cursor.requireInt(EXPIRE_TIMER_VERSION)
val viewOnce = cursor.requireLong(VIEW_ONCE) == 1L
val threadId = cursor.requireLong(THREAD_ID)
val threadRecipient = Recipient.resolved(threads.getRecipientIdForThreadId(threadId)!!)
@@ -2480,7 +2484,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
OutgoingMessage.expirationUpdateMessage(
threadRecipient = threadRecipient,
sentTimeMillis = timestamp,
expiresIn = expiresIn
expiresIn = expiresIn,
expireTimerVersion = expireTimerVersion
)
} else if (MessageTypes.isPaymentsNotification(outboxType)) {
OutgoingMessage.paymentNotificationMessage(
@@ -2539,6 +2544,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
attachments = attachments,
timestamp = timestamp,
expiresIn = expiresIn,
expireTimerVersion = expireTimerVersion,
viewOnce = viewOnce,
distributionType = distributionType,
storyType = storyType,
@@ -2971,6 +2977,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
contentValues.put(DATE_RECEIVED, editedMessage?.dateReceived ?: System.currentTimeMillis())
contentValues.put(SMS_SUBSCRIPTION_ID, message.subscriptionId)
contentValues.put(EXPIRES_IN, editedMessage?.expiresIn ?: message.expiresIn)
contentValues.put(EXPIRE_TIMER_VERSION, editedMessage?.expireTimerVersion ?: message.expireTimerVersion)
contentValues.put(VIEW_ONCE, message.isViewOnce)
contentValues.put(FROM_RECIPIENT_ID, Recipient.self().id.serialize())
contentValues.put(FROM_DEVICE_ID, SignalStore.account.deviceId)
@@ -5214,6 +5221,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val subscriptionId = cursor.requireInt(SMS_SUBSCRIPTION_ID)
val expiresIn = cursor.requireLong(EXPIRES_IN)
val expireStarted = cursor.requireLong(EXPIRE_STARTED)
val expireTimerVersion = cursor.requireInt(EXPIRE_TIMER_VERSION)
val unidentified = cursor.requireBoolean(UNIDENTIFIED)
val isViewOnce = cursor.requireBoolean(VIEW_ONCE)
val remoteDelete = cursor.requireBoolean(REMOTE_DELETED)
@@ -5296,6 +5304,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
subscriptionId,
expiresIn,
expireStarted,
expireTimerVersion,
isViewOnce,
hasReadReceipt,
quote,

View File

@@ -25,6 +25,7 @@ import org.signal.core.util.orNull
import org.signal.core.util.readToList
import org.signal.core.util.readToSet
import org.signal.core.util.readToSingleBoolean
import org.signal.core.util.readToSingleInt
import org.signal.core.util.readToSingleLong
import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireBlob
@@ -166,6 +167,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
const val CALL_VIBRATE = "call_vibrate"
const val MUTE_UNTIL = "mute_until"
const val MESSAGE_EXPIRATION_TIME = "message_expiration_time"
const val MESSAGE_EXPIRATION_TIME_VERSION = "message_expiration_time_version"
const val SEALED_SENDER_MODE = "sealed_sender_mode"
const val STORAGE_SERVICE_ID = "storage_service_id"
const val STORAGE_SERVICE_PROTO = "storage_service_proto"
@@ -263,7 +265,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
$NICKNAME_GIVEN_NAME TEXT DEFAULT NULL,
$NICKNAME_FAMILY_NAME TEXT DEFAULT NULL,
$NICKNAME_JOINED_NAME TEXT DEFAULT NULL,
$NOTE TEXT DEFAULT NULL
$NOTE TEXT DEFAULT NULL,
$MESSAGE_EXPIRATION_TIME_VERSION INTEGER DEFAULT 1 NOT NULL
)
"""
@@ -307,6 +310,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
CALL_VIBRATE,
MUTE_UNTIL,
MESSAGE_EXPIRATION_TIME,
MESSAGE_EXPIRATION_TIME_VERSION,
SEALED_SENDER_MODE,
STORAGE_SERVICE_ID,
MENTION_SETTING,
@@ -410,6 +414,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
fun maskCapabilitiesToLong(capabilities: SignalServiceProfile.Capabilities): Long {
var value: Long = 0
value = Bitmask.update(value, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isDeleteSync).serialize().toLong())
value = Bitmask.update(value, Capabilities.VERSIONED_EXPIRATION_TIMER, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isVersionedExpirationTimer).serialize().toLong())
return value
}
}
@@ -1473,7 +1478,27 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
StorageSyncHelper.scheduleSyncForDataChange()
}
fun setExpireMessages(id: RecipientId, expiration: Int) {
fun setExpireMessagesAndIncrementVersion(id: RecipientId, expiration: Int): Int {
val version = writableDatabase.rawQuery(
"""
UPDATE $TABLE_NAME
SET $MESSAGE_EXPIRATION_TIME = $expiration,
$MESSAGE_EXPIRATION_TIME_VERSION = MIN($MESSAGE_EXPIRATION_TIME_VERSION + 1, ${Int.MAX_VALUE})
WHERE $ID = ${id.serialize()}
RETURNING $MESSAGE_EXPIRATION_TIME_VERSION
""",
null
).readToSingleInt(defaultValue = 1)
AppDependencies.databaseObserver.notifyRecipientChanged(id)
return version
}
/**
* Sets the expiration timer without incrementing the version. Will eventually be removed once everyone has the ability to understand expireTimerVersions.
*/
fun setExpireMessagesWithoutIncrementingVersion(id: RecipientId, expiration: Int) {
val values = ContentValues(1).apply {
put(MESSAGE_EXPIRATION_TIME, expiration)
}
@@ -1482,6 +1507,23 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
}
}
/**
* Groups do not have an expireTimerVersion, and therefore we do not need to provide them.
*/
fun setExpireMessagesForGroup(id: RecipientId, expiration: Int) {
setExpireMessages(id, expiration, 1)
}
fun setExpireMessages(id: RecipientId, expiration: Int, expirationVersion: Int) {
val values = contentValuesOf(
MESSAGE_EXPIRATION_TIME to expiration,
MESSAGE_EXPIRATION_TIME_VERSION to expirationVersion
)
if (update(id, values)) {
AppDependencies.databaseObserver.notifyRecipientChanged(id)
}
}
fun setSealedSenderAccessMode(id: RecipientId, sealedSenderAccessMode: SealedSenderAccessMode) {
val values = ContentValues(1).apply {
put(SEALED_SENDER_MODE, sealedSenderAccessMode.mode)
@@ -3988,6 +4030,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
AVATAR_COLOR to primaryRecord.avatarColor.serialize(),
CUSTOM_CHAT_COLORS_ID to Optional.ofNullable(primaryRecord.chatColors).or(Optional.ofNullable(secondaryRecord.chatColors)).map { colors: ChatColors? -> colors!!.id.longValue }.orElse(null),
MESSAGE_EXPIRATION_TIME to if (primaryRecord.expireMessages > 0) primaryRecord.expireMessages else secondaryRecord.expireMessages,
MESSAGE_EXPIRATION_TIME_VERSION to max(primaryRecord.expireTimerVersion, secondaryRecord.expireTimerVersion),
REGISTERED to RegisteredState.REGISTERED.id,
SYSTEM_GIVEN_NAME to secondaryRecord.systemProfileName.givenName,
SYSTEM_FAMILY_NAME to secondaryRecord.systemProfileName.familyName,
@@ -4591,6 +4634,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
// const val PNP = 7
// const val PAYMENT_ACTIVATION = 8
const val DELETE_SYNC = 9
const val VERSIONED_EXPIRATION_TIMER = 10
// IMPORTANT: We cannot sore more than 32 capabilities in the bitmask.
}

View File

@@ -135,6 +135,7 @@ object RecipientTableCursorUtil {
messageRingtone = Util.uri(cursor.requireString(RecipientTable.MESSAGE_RINGTONE)),
callRingtone = Util.uri(cursor.requireString(RecipientTable.CALL_RINGTONE)),
expireMessages = cursor.requireInt(RecipientTable.MESSAGE_EXPIRATION_TIME),
expireTimerVersion = cursor.requireInt(RecipientTable.MESSAGE_EXPIRATION_TIME_VERSION),
registered = RegisteredState.fromId(cursor.requireInt(RecipientTable.REGISTERED)),
profileKey = profileKey,
expiringProfileKeyCredential = expiringProfileKeyCredential,
@@ -175,7 +176,8 @@ object RecipientTableCursorUtil {
val capabilities = cursor.requireLong(RecipientTable.CAPABILITIES)
return RecipientRecord.Capabilities(
rawBits = capabilities,
deleteSync = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH).toInt())
deleteSync = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH).toInt()),
versionedExpirationTimer = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.VERSIONED_EXPIRATION_TIMER, Capabilities.BIT_LENGTH).toInt())
)
}

View File

@@ -98,6 +98,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V237_ResetGroupForc
import org.thoughtcrime.securesms.database.helpers.migration.V238_AddGroupSendEndorsementsColumns
import org.thoughtcrime.securesms.database.helpers.migration.V239_MessageFullTextSearchEmojiSupport
import org.thoughtcrime.securesms.database.helpers.migration.V240_MessageFullTextSearchSecureDelete
import org.thoughtcrime.securesms.database.helpers.migration.V241_ExpireTimerVersion
/**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@@ -198,10 +199,11 @@ object SignalDatabaseMigrations {
237 to V237_ResetGroupForceUpdateTimestamps,
238 to V238_AddGroupSendEndorsementsColumns,
239 to V239_MessageFullTextSearchEmojiSupport,
240 to V240_MessageFullTextSearchSecureDelete
240 to V240_MessageFullTextSearchSecureDelete,
241 to V241_ExpireTimerVersion
)
const val DATABASE_VERSION = 240
const val DATABASE_VERSION = 241
@JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import net.zetetic.database.sqlcipher.SQLiteDatabase
/**
* Migration for new field tracking expiration timer version.
*/
object V241_ExpireTimerVersion : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("ALTER TABLE message ADD COLUMN expire_timer_version INTEGER DEFAULT 1 NOT NULL;")
db.execSQL("ALTER TABLE recipient ADD COLUMN message_expiration_time_version INTEGER DEFAULT 1 NOT NULL;")
db.execSQL(
"""
UPDATE recipient
SET message_expiration_time_version = 2
WHERE message_expiration_time > 0
"""
)
}
}

View File

@@ -48,6 +48,7 @@ public class InMemoryMessageRecord extends MessageRecord {
-1,
0,
System.currentTimeMillis(),
1,
false,
false,
Collections.emptyList(),

View File

@@ -99,6 +99,7 @@ public abstract class MessageRecord extends DisplayRecord {
private final int subscriptionId;
private final long expiresIn;
private final long expireStarted;
private final int expireTimerVersion;
private final boolean unidentified;
private final List<ReactionRecord> reactions;
private final long serverTimestamp;
@@ -119,6 +120,7 @@ public abstract class MessageRecord extends DisplayRecord {
int subscriptionId,
long expiresIn,
long expireStarted,
int expireTimerVersion,
boolean hasReadReceipt,
boolean unidentified,
@NonNull List<ReactionRecord> reactions,
@@ -140,6 +142,7 @@ public abstract class MessageRecord extends DisplayRecord {
this.subscriptionId = subscriptionId;
this.expiresIn = expiresIn;
this.expireStarted = expireStarted;
this.expireTimerVersion = expireTimerVersion;
this.unidentified = unidentified;
this.reactions = reactions;
this.serverTimestamp = dateServer;
@@ -754,6 +757,10 @@ public abstract class MessageRecord extends DisplayRecord {
return expireStarted;
}
public int getExpireTimerVersion() {
return expireTimerVersion;
}
public boolean isUnidentified() {
return unidentified;
}

View File

@@ -94,6 +94,7 @@ public class MmsMessageRecord extends MessageRecord {
int subscriptionId,
long expiresIn,
long expireStarted,
int expireTimerVersion,
boolean viewOnce,
boolean hasReadReceipt,
@Nullable Quote quote,
@@ -121,7 +122,7 @@ public class MmsMessageRecord extends MessageRecord {
{
super(id, body, fromRecipient, fromDeviceId, toRecipient,
dateSent, dateReceived, dateServer, threadId, Status.STATUS_NONE, hasDeliveryReceipt,
mailbox, mismatches, failures, subscriptionId, expiresIn, expireStarted, hasReadReceipt,
mailbox, mismatches, failures, subscriptionId, expiresIn, expireStarted, expireTimerVersion, hasReadReceipt,
unidentified, reactions, remoteDelete, notifiedTimestamp, viewed, receiptTimestamp, originalMessageId, revisionNumber, messageExtras);
this.slideDeck = slideDeck;
@@ -323,7 +324,7 @@ public class MmsMessageRecord extends MessageRecord {
public @NonNull MmsMessageRecord withReactions(@NonNull List<ReactionRecord> reactions) {
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
@@ -331,7 +332,7 @@ public class MmsMessageRecord extends MessageRecord {
public @NonNull MmsMessageRecord withoutQuote() {
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
hasReadReceipt(), null, getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
@@ -353,7 +354,7 @@ public class MmsMessageRecord extends MessageRecord {
SlideDeck slideDeck = MessageTable.MmsReader.buildSlideDeck(slideAttachments);
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), slideDeck,
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
hasReadReceipt(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
@@ -361,7 +362,7 @@ public class MmsMessageRecord extends MessageRecord {
public @NonNull MmsMessageRecord withPayment(@NonNull Payment payment) {
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
@@ -370,7 +371,7 @@ public class MmsMessageRecord extends MessageRecord {
public @NonNull MmsMessageRecord withCall(@Nullable CallTable.Call call) {
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call, getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());

View File

@@ -43,6 +43,7 @@ data class RecipientRecord(
val messageRingtone: Uri?,
val callRingtone: Uri?,
val expireMessages: Int,
val expireTimerVersion: Int,
val registered: RegisteredState,
val profileKey: ByteArray?,
val expiringProfileKeyCredential: ExpiringProfileKeyCredential?,
@@ -119,13 +120,15 @@ data class RecipientRecord(
data class Capabilities(
val rawBits: Long,
val deleteSync: Recipient.Capability
val deleteSync: Recipient.Capability,
val versionedExpirationTimer: Recipient.Capability
) {
companion object {
@JvmField
val UNKNOWN = Capabilities(
0,
Recipient.Capability.UNKNOWN
rawBits = 0,
deleteSync = Recipient.Capability.UNKNOWN,
versionedExpirationTimer = Recipient.Capability.UNKNOWN
)
}
}