Fix Message#expiryMode de/serialisation

This commit is contained in:
Andrew
2024-01-30 15:53:24 +10:30
parent cb0327ecb2
commit 4c7485f53d
26 changed files with 236 additions and 217 deletions

View File

@@ -6,6 +6,7 @@ import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.GroupUtil
import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
@@ -25,6 +26,8 @@ abstract class Message {
var expiryMode: ExpiryMode = ExpiryMode.NONE
open val coerceDisappearAfterSendToRead = false
open val defaultTtl: Long = 14 * 24 * 60 * 60 * 1000
open val ttl: Long get() = specifiedTtl ?: defaultTtl
open val isSelfSendValid: Boolean = false
@@ -51,26 +54,16 @@ abstract class Message {
abstract fun toProto(): SignalServiceProtos.Content?
fun setGroupContext(dataMessage: SignalServiceProtos.DataMessage.Builder) {
val groupProto = SignalServiceProtos.GroupContext.newBuilder()
val groupID = GroupUtil.doubleEncodeGroupID(recipient!!)
groupProto.id = ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID))
groupProto.type = SignalServiceProtos.GroupContext.Type.DELIVER
dataMessage.group = groupProto.build()
dataMessage.group = SignalServiceProtos.GroupContext.newBuilder().apply {
id = GroupUtil.doubleEncodeGroupID(recipient!!).let(GroupUtil::getDecodedGroupIDAsData).let(ByteString::copyFrom)
type = SignalServiceProtos.GroupContext.Type.DELIVER
}.build()
}
fun SignalServiceProtos.Content.Builder.setExpirationConfigurationIfNeeded(
threadId: Long?,
coerceDisappearAfterSendToRead: Boolean = false
): SignalServiceProtos.Content.Builder {
val config = threadId?.let(MessagingModuleConfiguration.shared.storage::getExpirationConfiguration)
?: run {
expirationTimer = 0
return this
}
expirationTimer = config.expiryMode.expirySeconds.toInt()
lastDisappearingMessageChangeTimestamp = config.updatedTimestampMs
expirationType = when (config.expiryMode) {
is ExpiryMode.AfterSend -> if (coerceDisappearAfterSendToRead) ExpirationType.DELETE_AFTER_READ else ExpirationType.DELETE_AFTER_SEND
fun SignalServiceProtos.Content.Builder.applyExpiryMode(): SignalServiceProtos.Content.Builder {
expirationTimer = expiryMode.expirySeconds.toInt()
expirationType = when (expiryMode) {
is ExpiryMode.AfterSend -> ExpirationType.DELETE_AFTER_SEND
is ExpiryMode.AfterRead -> ExpirationType.DELETE_AFTER_READ
else -> ExpirationType.UNKNOWN
}
@@ -79,13 +72,36 @@ abstract class Message {
}
inline fun <reified M: Message> M.copyExpiration(proto: SignalServiceProtos.Content): M {
val duration: Int = (if (proto.hasExpirationTimer()) proto.expirationTimer else if (proto.hasDataMessage()) proto.dataMessage?.expireTimer else null) ?: return this
expiryMode = when (proto.expirationType.takeIf { duration > 0 }) {
ExpirationType.DELETE_AFTER_SEND -> ExpiryMode.AfterSend(duration.toLong())
ExpirationType.DELETE_AFTER_READ -> ExpiryMode.AfterRead(duration.toLong())
else -> ExpiryMode.NONE
(proto.takeIf { it.hasExpirationTimer() }?.expirationTimer ?: proto.dataMessage?.expireTimer)?.let { duration ->
expiryMode = when (proto.expirationType.takeIf { duration > 0 }) {
ExpirationType.DELETE_AFTER_SEND -> ExpiryMode.AfterSend(duration.toLong())
ExpirationType.DELETE_AFTER_READ -> ExpiryMode.AfterRead(duration.toLong())
else -> ExpiryMode.NONE
}
}
return this
}
fun SignalServiceProtos.Content.expiryMode(): ExpiryMode =
(takeIf { it.hasExpirationTimer() }?.expirationTimer ?: dataMessage?.expireTimer)?.let { duration ->
when (expirationType.takeIf { duration > 0 }) {
ExpirationType.DELETE_AFTER_SEND -> ExpiryMode.AfterSend(duration.toLong())
ExpirationType.DELETE_AFTER_READ -> ExpiryMode.AfterRead(duration.toLong())
else -> ExpiryMode.NONE
}
} ?: ExpiryMode.NONE
/**
* Apply ExpiryMode from the current setting.
*/
inline fun <reified M: Message> M.applyExpiryMode(): M {
val address = Address.fromSerialized(sender ?: return this)
MessagingModuleConfiguration.shared.storage.getThreadId(address)?.let(::applyExpiryMode)
return this
}
inline fun <reified M: Message> M.applyExpiryMode(thread: Long): M {
val storage = MessagingModuleConfiguration.shared.storage
expiryMode = storage.getExpirationConfiguration(thread)?.expiryMode?.coerceSendToRead(coerceDisappearAfterSendToRead) ?: ExpiryMode.NONE
return this
}

View File

@@ -1,5 +1,6 @@
package org.session.libsession.messaging.messages.control
import org.session.libsession.messaging.messages.applyExpiryMode
import org.session.libsession.messaging.messages.copyExpiration
import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.*
@@ -13,6 +14,7 @@ class CallMessage(): ControlMessage() {
var sdpMids: List<String> = listOf()
var callId: UUID? = null
override val coerceDisappearAfterSendToRead = true
override val isSelfSendValid: Boolean get() = type in arrayOf(ANSWER, END_CALL)
override val defaultTtl: Long = 300000L // 5m
@@ -40,21 +42,21 @@ class CallMessage(): ControlMessage() {
listOf(),
listOf(),
callId
)
).applyExpiryMode()
fun preOffer(callId: UUID) = CallMessage(PRE_OFFER,
listOf(),
listOf(),
listOf(),
callId
)
).applyExpiryMode()
fun offer(sdp: String, callId: UUID) = CallMessage(OFFER,
listOf(sdp),
listOf(),
listOf(),
callId
)
).applyExpiryMode()
fun endCall(callId: UUID) = CallMessage(END_CALL, emptyList(), emptyList(), emptyList(), callId)
@@ -83,9 +85,8 @@ class CallMessage(): ControlMessage() {
.addAllSdpMids(sdpMids)
.setUuid(callId!!.toString())
val content = SignalServiceProtos.Content.newBuilder()
content.setExpirationConfigurationIfNeeded(threadID, true)
return content
return SignalServiceProtos.Content.newBuilder()
.applyExpiryMode()
.setCallMessage(callMessage)
.build()
}

View File

@@ -178,7 +178,7 @@ class ClosedGroupControlMessage() : ControlMessage() {
contentProto.dataMessage = dataMessageProto.build()
// Expiration timer
val threadId = groupID?.let { MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(Address.fromSerialized(it)) }
contentProto.setExpirationConfigurationIfNeeded(threadId)
contentProto.applyExpiryMode()
return contentProto.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct closed group control message proto from: $this.")

View File

@@ -20,10 +20,10 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
override val isSelfSendValid: Boolean = true
class ClosedGroup(var publicKey: String, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List<String>, var admins: List<String>, var expirationTimer: Int) {
class ClosedGroup(var publicKey: String, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List<String>, var admins: List<String>) {
val isValid: Boolean get() = members.isNotEmpty() && admins.isNotEmpty()
internal constructor() : this("", "", null, listOf(), listOf(), 0)
internal constructor() : this("", "", null, listOf(), listOf())
override fun toString(): String {
return name
@@ -40,8 +40,7 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray()))
val members = proto.membersList.map { it.toByteArray().toHexString() }
val admins = proto.adminsList.map { it.toByteArray().toHexString() }
val expirationTimer = proto.expirationTimer
return ClosedGroup(publicKey, name, encryptionKeyPair, members, admins, expirationTimer)
return ClosedGroup(publicKey, name, encryptionKeyPair, members, admins)
}
}
@@ -55,7 +54,6 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
result.encryptionKeyPair = encryptionKeyPairAsProto.build()
result.addAllMembers(members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) })
result.addAllAdmins(admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) })
result.expirationTimer = expirationTimer
return result.build()
}
}
@@ -128,15 +126,12 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
if (!group.members.contains(Address.fromSerialized(storage.getUserPublicKey()!!))) continue
val groupPublicKey = GroupUtil.doubleDecodeGroupID(group.encodedId).toHexString()
val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: continue
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(group.encodedId))
val expiryConfig = storage.getExpirationConfiguration(threadID)
val closedGroup = ClosedGroup(
groupPublicKey,
group.title,
encryptionKeyPair,
group.members.map { it.serialize() },
group.admins.map { it.serialize() },
expiryConfig?.expiryMode?.expirySeconds?.toInt() ?: 0
group.admins.map { it.serialize() }
)
closedGroups.add(closedGroup)
}

View File

@@ -7,6 +7,8 @@ import org.session.libsignal.utilities.Log
class DataExtractionNotification() : ControlMessage() {
var kind: Kind? = null
override val coerceDisappearAfterSendToRead = true
sealed class Kind {
class Screenshot() : Kind()
class MediaSaved(val timestamp: Long) : Kind()
@@ -64,10 +66,10 @@ class DataExtractionNotification() : ControlMessage() {
dataExtractionNotification.timestamp = kind.timestamp
}
}
val contentProto = SignalServiceProtos.Content.newBuilder()
contentProto.dataExtractionNotification = dataExtractionNotification.build()
contentProto.setExpirationConfigurationIfNeeded(threadID, true)
return contentProto.build()
return SignalServiceProtos.Content.newBuilder()
.setDataExtractionNotification(dataExtractionNotification.build())
.applyExpiryMode()
.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct data extraction notification proto from: $this")
return null

View File

@@ -1,59 +1,53 @@
package org.session.libsession.messaging.messages.control
import network.loki.messenger.libsession_util.util.ExpiryMode
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.copyExpiration
import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE
import org.session.libsignal.utilities.Log
/** In the case of a sync message, the public key of the person the message was targeted at.
*
* **Note:** `nil` if this isn't a sync message.
*/
data class ExpirationTimerUpdate(var syncTarget: String? = null) : ControlMessage() {
data class ExpirationTimerUpdate(var syncTarget: String? = null, val isGroup: Boolean = false) : ControlMessage() {
override val isSelfSendValid: Boolean = true
companion object {
const val TAG = "ExpirationTimerUpdate"
private val storage = MessagingModuleConfiguration.shared.storage
fun fromProto(proto: SignalServiceProtos.Content): ExpirationTimerUpdate? {
val dataMessageProto = if (proto.hasDataMessage()) proto.dataMessage else return null
val isExpirationTimerUpdate = dataMessageProto.flags.and(
SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE
) != 0
if (!isExpirationTimerUpdate) return null
return ExpirationTimerUpdate(dataMessageProto.syncTarget)
.copyExpiration(proto)
}
fun fromProto(proto: SignalServiceProtos.Content): ExpirationTimerUpdate? =
proto.dataMessage?.takeIf { it.flags and EXPIRATION_TIMER_UPDATE_VALUE != 0 }?.run {
ExpirationTimerUpdate(syncTarget, hasGroup()).copyExpiration(proto)
}
}
override fun toProto(): SignalServiceProtos.Content? {
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder()
dataMessageProto.flags = SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE
dataMessageProto.expireTimer = expiryMode.expirySeconds.toInt()
// Sync target
if (syncTarget != null) {
dataMessageProto.syncTarget = syncTarget
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder().apply {
flags = EXPIRATION_TIMER_UPDATE_VALUE
expireTimer = expiryMode.expirySeconds.toInt()
}
// Sync target
syncTarget?.let { dataMessageProto.syncTarget = it }
// Group context
if (MessagingModuleConfiguration.shared.storage.isClosedGroup(recipient!!)) {
if (storage.isClosedGroup(recipient!!)) {
try {
setGroupContext(dataMessageProto)
} catch(e: Exception) {
Log.w(VisibleMessage.TAG, "Couldn't construct visible message proto from: $this")
Log.w(TAG, "Couldn't construct visible message proto from: $this", e)
return null
}
}
return try {
SignalServiceProtos.Content.newBuilder().apply {
dataMessage = dataMessageProto.build()
setExpirationConfigurationIfNeeded(threadID)
}.build()
SignalServiceProtos.Content.newBuilder()
.setDataMessage(dataMessageProto)
.applyExpiryMode()
.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct expiration timer update proto from: $this")
Log.w(TAG, "Couldn't construct expiration timer update proto from: $this", e)
null
}
}
}
}

View File

@@ -20,7 +20,7 @@ class MessageRequestResponse(val isApproved: Boolean, var profile: Profile? = nu
profile?.profileKey?.let { messageRequestResponseProto.profileKey = ByteString.copyFrom(it) }
return try {
SignalServiceProtos.Content.newBuilder()
.setExpirationConfigurationIfNeeded(threadID)
.applyExpiryMode()
.setMessageRequestResponse(messageRequestResponseProto.build())
.build()
} catch (e: Exception) {

View File

@@ -37,14 +37,15 @@ class ReadReceipt() : ControlMessage() {
Log.w(TAG, "Couldn't construct read receipt proto from: $this")
return null
}
val receiptProto = SignalServiceProtos.ReceiptMessage.newBuilder()
receiptProto.type = SignalServiceProtos.ReceiptMessage.Type.READ
receiptProto.addAllTimestamp(timestamps.asIterable())
val contentProto = SignalServiceProtos.Content.newBuilder()
return try {
contentProto.receiptMessage = receiptProto.build()
contentProto.setExpirationConfigurationIfNeeded(threadID)
contentProto.build()
SignalServiceProtos.Content.newBuilder()
.setReceiptMessage(
SignalServiceProtos.ReceiptMessage.newBuilder()
.setType(SignalServiceProtos.ReceiptMessage.Type.READ)
.addAllTimestamp(timestamps.asIterable()).build()
).applyExpiryMode()
.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct read receipt proto from: $this")
null

View File

@@ -56,14 +56,11 @@ class TypingIndicator() : ControlMessage() {
Log.w(TAG, "Couldn't construct typing indicator proto from: $this")
return null
}
val typingIndicatorProto = SignalServiceProtos.TypingMessage.newBuilder()
typingIndicatorProto.timestamp = timestamp
typingIndicatorProto.action = kind.toProto()
val contentProto = SignalServiceProtos.Content.newBuilder()
return try {
contentProto.typingMessage = typingIndicatorProto.build()
contentProto.setExpirationConfigurationIfNeeded(threadID)
contentProto.build()
SignalServiceProtos.Content.newBuilder()
.setTypingMessage(SignalServiceProtos.TypingMessage.newBuilder().setTimestamp(timestamp).setAction(kind.toProto()).build())
.applyExpiryMode()
.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct typing indicator proto from: $this")
null

View File

@@ -41,14 +41,11 @@ class UnsendRequest(): ControlMessage() {
Log.w(TAG, "Couldn't construct unsend request proto from: $this")
return null
}
val unsendRequestProto = SignalServiceProtos.UnsendRequest.newBuilder()
unsendRequestProto.timestamp = timestamp
unsendRequestProto.author = author
val contentProto = SignalServiceProtos.Content.newBuilder()
return try {
contentProto.unsendRequest = unsendRequestProto.build()
contentProto.setExpirationConfigurationIfNeeded(threadID)
contentProto.build()
SignalServiceProtos.Content.newBuilder()
.setUnsendRequest(SignalServiceProtos.UnsendRequest.newBuilder().setTimestamp(timestamp).setAuthor(author).build())
.applyExpiryMode()
.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct unsend request proto from: $this")
null

View File

@@ -44,52 +44,26 @@ data class VisibleMessage(
companion object {
const val TAG = "VisibleMessage"
fun fromProto(proto: SignalServiceProtos.Content): VisibleMessage? {
val dataMessage = proto.dataMessage ?: return null
val result = VisibleMessage()
if (dataMessage.hasSyncTarget()) { result.syncTarget = dataMessage.syncTarget }
result.text = dataMessage.body
// Attachments are handled in MessageReceiver
val quoteProto = if (dataMessage.hasQuote()) dataMessage.quote else null
if (quoteProto != null) {
val quote = Quote.fromProto(quoteProto)
result.quote = quote
}
val linkPreviewProto = dataMessage.previewList.firstOrNull()
if (linkPreviewProto != null) {
val linkPreview = LinkPreview.fromProto(linkPreviewProto)
result.linkPreview = linkPreview
}
val openGroupInvitationProto = if (dataMessage.hasOpenGroupInvitation()) dataMessage.openGroupInvitation else null
if (openGroupInvitationProto != null) {
val openGroupInvitation = OpenGroupInvitation.fromProto(openGroupInvitationProto)
result.openGroupInvitation = openGroupInvitation
}
// TODO Contact
val profile = Profile.fromProto(dataMessage)
if (profile != null) { result.profile = profile }
val reactionProto = if (dataMessage.hasReaction()) dataMessage.reaction else null
if (reactionProto != null) {
val reaction = Reaction.fromProto(reactionProto)
result.reaction = reaction
}
result.blocksMessageRequests = with (dataMessage) { hasBlocksCommunityMessageRequests() && blocksCommunityMessageRequests }
return result.copyExpiration(proto)
fun fromProto(proto: SignalServiceProtos.Content): VisibleMessage? =
proto.dataMessage?.let { VisibleMessage().apply {
if (it.hasSyncTarget()) syncTarget = it.syncTarget
text = it.body
// Attachments are handled in MessageReceiver
if (it.hasQuote()) quote = Quote.fromProto(it.quote)
linkPreview = it.previewList.firstOrNull()?.let(LinkPreview::fromProto)
if (it.hasOpenGroupInvitation()) openGroupInvitation = it.openGroupInvitation?.let(OpenGroupInvitation::fromProto)
// TODO Contact
profile = Profile.fromProto(it)
if (it.hasReaction()) reaction = it.reaction?.let(Reaction::fromProto)
blocksMessageRequests = it.hasBlocksCommunityMessageRequests() && it.blocksCommunityMessageRequests
}.copyExpiration(proto)
}
}
override fun toProto(): SignalServiceProtos.Content? {
val proto = SignalServiceProtos.Content.newBuilder()
val dataMessage: SignalServiceProtos.DataMessage.Builder
// Profile
val profileProto = profile?.toProto()
dataMessage = if (profileProto != null) {
profileProto.toBuilder()
} else {
SignalServiceProtos.DataMessage.newBuilder()
}
val dataMessage = profile?.toProto()?.toBuilder() ?: SignalServiceProtos.DataMessage.newBuilder()
// Text
if (text != null) { dataMessage.body = text }
// Quote
@@ -124,7 +98,7 @@ data class VisibleMessage(
dataMessage.addAllAttachments(pointers)
// TODO: Contact
// Expiration timer
proto.setExpirationConfigurationIfNeeded(threadID)
proto.applyExpiryMode()
// Group context
val storage = MessagingModuleConfiguration.shared.storage
if (storage.isClosedGroup(recipient!!)) {
@@ -163,4 +137,4 @@ data class VisibleMessage(
fun isMediaMessage(): Boolean {
return attachmentIDs.isNotEmpty() || quote != null || linkPreview != null || reaction != null
}
}
}

View File

@@ -145,9 +145,7 @@ object MessageReceiver {
MessageRequestResponse.fromProto(proto) ?:
CallMessage.fromProto(proto) ?:
SharedConfigurationMessage.fromProto(proto) ?:
VisibleMessage.fromProto(proto) ?: run {
throw Error.UnknownMessage
}
VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage
val isUserBlindedSender = sender == openGroupPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId(IdPrefix.BLINDED, it.publicKey.asBytes).hexString }
val isUserSender = sender == userPublicKey

View File

@@ -9,6 +9,7 @@ import org.session.libsession.messaging.jobs.MessageSendJob
import org.session.libsession.messaging.jobs.NotifyPNServerJob
import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.applyExpiryMode
import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage
import org.session.libsession.messaging.messages.control.ConfigurationMessage
@@ -421,12 +422,7 @@ object MessageSender {
storage.markUnidentified(timestamp, userPublicKey)
// Start the disappearing messages timer if needed
Log.d("MessageSender", "Start the disappearing messages timer if needed message.recipient = ${message.recipient}, userPublicKey = $userPublicKey, isSyncMessage = $isSyncMessage")
message.threadID?.let(storage::getExpirationConfiguration)?.expiryMode?.takeIf { it.expirySeconds > 0 }?.let { mode ->
if (message.recipient == userPublicKey || !isSyncMessage) {
val expireStartedAt = if (mode is ExpiryMode.AfterRead) timestamp + 1 else timestamp
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(timestamp, userPublicKey, expireStartedAt)
}
}
SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(message, startDisappearAfterRead = true)
} ?: run {
storage.updateReactionIfNeeded(message, message.sender?:userPublicKey, openGroupSentTimestamp)
}
@@ -475,6 +471,7 @@ object MessageSender {
@JvmStatic
fun send(message: Message, address: Address) {
message.applyExpiryMode()
val threadID = MessagingModuleConfiguration.shared.storage.getThreadId(address)
message.threadID = threadID
val destination = Destination.from(address)

View File

@@ -124,7 +124,6 @@ private fun MessageReceiver.handleReadReceipt(message: ReadReceipt) {
private fun MessageReceiver.handleCallMessage(message: CallMessage) {
// TODO: refactor this out to persistence, just to help debug the flow and send/receive in synchronous testing
WebRtcUtils.SIGNAL_QUEUE.trySend(message)
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message, coerceToDisappearAfterRead = true)
}
private fun MessageReceiver.handleTypingIndicator(message: TypingIndicator) {
@@ -192,7 +191,6 @@ private fun MessageReceiver.handleDataExtractionNotification(message: DataExtrac
else -> return
}
storage.insertDataExtractionNotificationMessage(senderPublicKey, notification, message.sentTimestamp!!)
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message, coerceToDisappearAfterRead = true)
}
private fun handleConfigurationMessage(message: ConfigurationMessage) {
@@ -221,7 +219,7 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) {
} else {
// only handle new closed group if it's first time sync
handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, closedGroup.publicKey, closedGroup.name,
closedGroup.encryptionKeyPair!!, closedGroup.members, closedGroup.admins, message.sentTimestamp!!, closedGroup.expirationTimer)
closedGroup.encryptionKeyPair!!, closedGroup.members, closedGroup.admins, message.sentTimestamp!!)
}
}
val allV2OpenGroups = storage.getAllOpenGroups().map { it.value.joinURL }
@@ -431,7 +429,7 @@ fun MessageReceiver.handleVisibleMessage(
val isSms = !message.isMediaMessage() && attachments.isEmpty()
storage.setOpenGroupServerMessageID(messageID, it, threadID, isSms)
}
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message)
SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(message)
return messageID
}
return null
@@ -550,10 +548,10 @@ private fun MessageReceiver.handleNewClosedGroup(message: ClosedGroupControlMess
val members = kind.members.map { it.toByteArray().toHexString() }
val admins = kind.admins.map { it.toByteArray().toHexString() }
val expirationTimer = kind.expirationTimer
handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, groupPublicKey, kind.name, kind.encryptionKeyPair!!, members, admins, message.sentTimestamp!!, expirationTimer)
handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, groupPublicKey, kind.name, kind.encryptionKeyPair!!, members, admins, message.sentTimestamp!!)
}
private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPublicKey: String, name: String, encryptionKeyPair: ECKeyPair, members: List<String>, admins: List<String>, formationTimestamp: Long, expireTimer: Int) {
private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPublicKey: String, name: String, encryptionKeyPair: ECKeyPair, members: List<String>, admins: List<String>, formationTimestamp: Long) {
val context = MessagingModuleConfiguration.shared.context
val storage = MessagingModuleConfiguration.shared.storage
val userPublicKey = storage.getUserPublicKey()!!

View File

@@ -3,11 +3,12 @@ package org.session.libsession.utilities
import android.content.Context
import android.util.Log
import network.loki.messenger.libsession_util.util.ExpiryMode
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeAPI.nowWithOffset
import org.session.libsession.utilities.recipients.Recipient
class SSKEnvironment(
@@ -43,18 +44,41 @@ class SSKEnvironment(
interface MessageExpirationManagerProtocol {
fun insertExpirationTimerMessage(message: ExpirationTimerUpdate)
fun startAnyExpiration(timestamp: Long, author: String, expireStartedAt: Long)
fun startAnyExpiration(message: Message, coerceToDisappearAfterRead: Boolean = false) {
Log.d("MessageExpirationManagerProtocol", "startAnyExpiration() called with: message = $message, coerceToDisappearAfterRead = $coerceToDisappearAfterRead")
val timestamp = message.sentTimestamp ?: return
startAnyExpiration(
timestamp = timestamp,
author = message.sender ?: return,
expireStartedAt = if (message.expiryMode is ExpiryMode.AfterRead || coerceToDisappearAfterRead && message.expiryMode.expiryMillis > 0) SnodeAPI.nowWithOffset.coerceAtLeast(timestamp + 1)
else if (message.expiryMode is ExpiryMode.AfterSend) timestamp
else return
fun maybeStartExpiration(message: Message, startDisappearAfterRead: Boolean = false) {
Log.d("MessageExpirationManagerProtocol", "maybeStartExpiration() called with: message = $message, startDisappearAfterRead = $startDisappearAfterRead")
if (message is ExpirationTimerUpdate && message.isGroup) return
maybeStartExpiration(
message.sentTimestamp ?: return,
message.sender ?: return,
message.expiryMode,
startDisappearAfterRead || message.isSenderSelf
)
}
fun startDisappearAfterRead(timestamp: Long, sender: String) {
Log.d("MessageExpirationManagerProtocol", "startDisappearAfterRead() called with: timestamp = $timestamp, sender = $sender")
startAnyExpiration(
timestamp,
sender,
expireStartedAt = nowWithOffset.coerceAtLeast(timestamp + 1)
)
}
fun maybeStartExpiration(timestamp: Long, sender: String, mode: ExpiryMode, startDisappearAfterRead: Boolean = false) {
Log.d("MessageExpirationManagerProtocol", "maybeStartExpiration() called with: timestamp = $timestamp, sender = $sender, mode = $mode, startDisappearAfterRead = $startDisappearAfterRead")
val expireStartedAt = when (mode) {
is ExpiryMode.AfterSend -> timestamp
is ExpiryMode.AfterRead -> if (startDisappearAfterRead) nowWithOffset.coerceAtLeast(timestamp + 1) else return
else -> return
}
startAnyExpiration(timestamp, sender, expireStartedAt)
}
}
companion object {