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

@ -14,6 +14,7 @@ import org.session.libsession.utilities.ExpirationUtil
import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerProtocol import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerProtocol
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.getExpirationTypeDisplayValue import org.session.libsession.utilities.getExpirationTypeDisplayValue
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.showSessionDialog
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
@ -25,11 +26,11 @@ class DisappearingMessages @Inject constructor(
private val textSecurePreferences: TextSecurePreferences, private val textSecurePreferences: TextSecurePreferences,
private val messageExpirationManager: MessageExpirationManagerProtocol, private val messageExpirationManager: MessageExpirationManagerProtocol,
) { ) {
fun set(threadId: Long, address: Address, mode: ExpiryMode) { fun set(threadId: Long, address: Address, mode: ExpiryMode, isGroup: Boolean) {
val expiryChangeTimestampMs = SnodeAPI.nowWithOffset val expiryChangeTimestampMs = SnodeAPI.nowWithOffset
MessagingModuleConfiguration.shared.storage.setExpirationConfiguration(ExpirationConfiguration(threadId, mode, expiryChangeTimestampMs)) MessagingModuleConfiguration.shared.storage.setExpirationConfiguration(ExpirationConfiguration(threadId, mode, expiryChangeTimestampMs))
val message = ExpirationTimerUpdate().apply { val message = ExpirationTimerUpdate(isGroup = isGroup).apply {
expiryMode = mode expiryMode = mode
sender = textSecurePreferences.getLocalNumber() sender = textSecurePreferences.getLocalNumber()
isSenderSelf = true isSenderSelf = true
@ -62,7 +63,7 @@ class DisappearingMessages @Inject constructor(
text = if (message.expiresIn == 0L) R.string.dialog_disappearing_messages_follow_setting_confirm else R.string.dialog_disappearing_messages_follow_setting_set, text = if (message.expiresIn == 0L) R.string.dialog_disappearing_messages_follow_setting_confirm else R.string.dialog_disappearing_messages_follow_setting_set,
contentDescription = if (message.expiresIn == 0L) R.string.AccessibilityId_confirm else R.string.AccessibilityId_set_button contentDescription = if (message.expiresIn == 0L) R.string.AccessibilityId_confirm else R.string.AccessibilityId_set_button
) { ) {
set(message.threadId, message.recipient.address, message.expiryMode) set(message.threadId, message.recipient.address, message.expiryMode, message.recipient.isClosedGroupRecipient)
} }
cancelButton() cancelButton()
} }

View File

@ -87,7 +87,7 @@ class DisappearingMessagesViewModel(
return@launch return@launch
} }
disappearingMessages.set(threadId, address, mode) disappearingMessages.set(threadId, address, mode, state.isGroup)
_event.send(Event.SUCCESS) _event.send(Event.SUCCESS)
} }

View File

@ -72,6 +72,7 @@ import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.mentions.Mention import org.session.libsession.messaging.mentions.Mention
import org.session.libsession.messaging.mentions.MentionsManager import org.session.libsession.messaging.mentions.MentionsManager
import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.messages.ExpirationConfiguration
import org.session.libsession.messaging.messages.applyExpiryMode
import org.session.libsession.messaging.messages.control.DataExtractionNotification import org.session.libsession.messaging.messages.control.DataExtractionNotification
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
@ -1574,7 +1575,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
return null return null
} }
// Create the message // Create the message
val message = VisibleMessage() val message = VisibleMessage().applyExpiryMode(viewModel.threadId)
message.sentTimestamp = sentTimestamp message.sentTimestamp = sentTimestamp
message.text = text message.text = text
val expiresInMillis = viewModel.expirationConfiguration?.expiryMode?.expiryMillis ?: 0 val expiresInMillis = viewModel.expirationConfiguration?.expiryMode?.expiryMillis ?: 0
@ -1609,7 +1610,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val sentTimestamp = SnodeAPI.nowWithOffset val sentTimestamp = SnodeAPI.nowWithOffset
processMessageRequestApproval() processMessageRequestApproval()
// Create the message // Create the message
val message = VisibleMessage() val message = VisibleMessage().applyExpiryMode()
message.sentTimestamp = sentTimestamp message.sentTimestamp = sentTimestamp
message.text = body message.text = body
val quote = quotedMessage?.let { val quote = quotedMessage?.let {

View File

@ -30,6 +30,10 @@ class ExpirationTimerView @JvmOverloads constructor(
R.drawable.timer60 R.drawable.timer60
) )
fun setTimerIcon() {
setExpirationTime(0L, 0L)
}
fun setExpirationTime(startedAt: Long, expiresIn: Long) { fun setExpirationTime(startedAt: Long, expiresIn: Long) {
if (expiresIn == 0L) { if (expiresIn == 0L) {
setImageResource(R.drawable.timer55) setImageResource(R.drawable.timer55)

View File

@ -53,12 +53,19 @@ class ControlMessageView : LinearLayout {
Log.d(TAG, "bind() called, messageBody = $messageBody") Log.d(TAG, "bind() called, messageBody = $messageBody")
expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn) val threadRecipient = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(message.threadId)
if (threadRecipient?.isClosedGroupRecipient == true) {
expirationTimerView.setTimerIcon()
} else {
expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn)
}
followSetting.isVisible = ExpirationConfiguration.isNewConfigEnabled followSetting.isVisible = ExpirationConfiguration.isNewConfigEnabled
&& !message.isOutgoing && !message.isOutgoing
&& message.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE) && message.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE)
&& DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(message.threadId)?.isGroupRecipient != true && threadRecipient?.isGroupRecipient != true
followSetting.setOnClickListener { disappearingMessages.showFollowSettingDialog(context, message) } followSetting.setOnClickListener { disappearingMessages.showFollowSettingDialog(context, message) }
} }

View File

@ -4,11 +4,11 @@ import org.thoughtcrime.securesms.conversation.disappearingmessages.ExpiryType
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId
data class MarkedMessageInfo(val syncMessageId: SyncMessageId, val expirationInfo: ExpirationInfo) { data class MarkedMessageInfo(val syncMessageId: SyncMessageId, val expirationInfo: ExpirationInfo) {
fun guessExpiryType(): ExpiryType = expirationInfo.run { val expiryType get() = when {
when { syncMessageId.timetamp == expirationInfo.expireStarted -> ExpiryType.AFTER_SEND
syncMessageId.timetamp == expireStarted -> ExpiryType.AFTER_SEND expirationInfo.expiresIn > 0 -> ExpiryType.AFTER_READ
expiresIn > 0 -> ExpiryType.AFTER_READ else -> ExpiryType.NONE
else -> ExpiryType.NONE
}
} }
val expiryMode get() = expiryType.mode(expirationInfo.expiresIn)
} }

View File

@ -382,9 +382,6 @@ open class Storage(
DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(id, message.isMediaMessage(), serverHash) DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(id, message.isMediaMessage(), serverHash)
} }
} }
if (expiryMode is ExpiryMode.AfterSend) {
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message.sentTimestamp!!, message.sender!!, expireStartedAt)
}
return messageID return messageID
} }
@ -972,26 +969,24 @@ open class Storage(
val recipient = Recipient.from(context, fromSerialized(groupID), false) val recipient = Recipient.from(context, fromSerialized(groupID), false)
val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient)
val expirationConfig = getExpirationConfiguration(threadId) val expirationConfig = getExpirationConfiguration(threadId)
val expiryMode = expirationConfig?.expiryMode val expiryMode = expirationConfig?.expiryMode ?: ExpiryMode.NONE
val expiresInMillis = expiryMode?.expiryMillis ?: 0 val expiresInMillis = expiryMode.expiryMillis
val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0
val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), expiresInMillis, expireStartedAt, true, false) val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), expiresInMillis, expireStartedAt, true, false)
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON()
val infoMessage = IncomingGroupMessage(m, groupID, updateData, true) val infoMessage = IncomingGroupMessage(m, groupID, updateData, true)
val smsDB = DatabaseComponent.get(context).smsDatabase() val smsDB = DatabaseComponent.get(context).smsDatabase()
smsDB.insertMessageInbox(infoMessage, true) smsDB.insertMessageInbox(infoMessage, true)
if (expiryMode is ExpiryMode.AfterSend) { SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(sentTimestamp, senderPublicKey, expiryMode)
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(sentTimestamp, senderPublicKey, expireStartedAt)
}
} }
override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long) { override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long) {
val userPublicKey = getUserPublicKey() val userPublicKey = getUserPublicKey()!!
val recipient = Recipient.from(context, fromSerialized(groupID), false) val recipient = Recipient.from(context, fromSerialized(groupID), false)
val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient)
val expirationConfig = getExpirationConfiguration(threadId) val expirationConfig = getExpirationConfiguration(threadId)
val expiryMode = expirationConfig?.expiryMode val expiryMode = expirationConfig?.expiryMode ?: ExpiryMode.NONE
val expiresInMillis = expiryMode?.expiryMillis ?: 0 val expiresInMillis = expiryMode.expiryMillis
val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: "" val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: ""
val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, expiresInMillis, expireStartedAt, true, null, listOf(), listOf()) val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, expiresInMillis, expireStartedAt, true, null, listOf(), listOf())
@ -1000,9 +995,7 @@ open class Storage(
if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) return if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) return
val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null, runThreadUpdate = true) val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null, runThreadUpdate = true)
mmsDB.markAsSent(infoMessageID, true) mmsDB.markAsSent(infoMessageID, true)
if (expiryMode is ExpiryMode.AfterSend) { SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(sentTimestamp, userPublicKey, expiryMode)
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(sentTimestamp, userPublicKey!!, expireStartedAt)
}
} }
override fun isClosedGroup(publicKey: String): Boolean { override fun isClosedGroup(publicKey: String): Boolean {
@ -1403,8 +1396,8 @@ open class Storage(
if (recipient.isBlocked) return if (recipient.isBlocked) return
val threadId = getThreadId(recipient) ?: return val threadId = getThreadId(recipient) ?: return
val expirationConfig = getExpirationConfiguration(threadId) val expirationConfig = getExpirationConfiguration(threadId)
val expiryMode = expirationConfig?.expiryMode val expiryMode = expirationConfig?.expiryMode ?: ExpiryMode.NONE
val expiresInMillis = expiryMode?.expiryMillis ?: 0 val expiresInMillis = expiryMode.expiryMillis
val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0
val mediaMessage = IncomingMediaMessage( val mediaMessage = IncomingMediaMessage(
address, address,
@ -1426,9 +1419,8 @@ open class Storage(
) )
database.insertSecureDecryptedMessageInbox(mediaMessage, threadId, runThreadUpdate = true) database.insertSecureDecryptedMessageInbox(mediaMessage, threadId, runThreadUpdate = true)
if (expiryMode is ExpiryMode.AfterSend) {
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(sentTimestamp, senderPublicKey, expireStartedAt) SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(sentTimestamp, senderPublicKey, expiryMode)
}
} }
override fun insertMessageRequestResponse(response: MessageRequestResponse) { override fun insertMessageRequestResponse(response: MessageRequestResponse) {
@ -1557,14 +1549,12 @@ open class Storage(
val recipient = Recipient.from(context, address, false) val recipient = Recipient.from(context, address, false)
val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient)
val expirationConfig = getExpirationConfiguration(threadId) val expirationConfig = getExpirationConfiguration(threadId)
val expiryMode = expirationConfig?.expiryMode val expiryMode = expirationConfig?.expiryMode?.coerceSendToRead() ?: ExpiryMode.NONE
val expiresInMillis = expiryMode?.expiryMillis ?: 0 val expiresInMillis = expiryMode.expiryMillis
val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0
val callMessage = IncomingTextMessage.fromCallInfo(callMessageType, address, Optional.absent(), sentTimestamp, expiresInMillis, expireStartedAt) val callMessage = IncomingTextMessage.fromCallInfo(callMessageType, address, Optional.absent(), sentTimestamp, expiresInMillis, expireStartedAt)
database.insertCallMessage(callMessage) database.insertCallMessage(callMessage)
if (expiryMode is ExpiryMode.AfterSend) { SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(sentTimestamp, senderPublicKey, expiryMode)
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(sentTimestamp, senderPublicKey, expireStartedAt)
}
} }
override fun conversationHasOutgoing(userPublicKey: String): Boolean { override fun conversationHasOutgoing(userPublicKey: String): Boolean {

View File

@ -11,6 +11,7 @@ import org.session.libsession.messaging.messages.control.ReadReceipt
import org.session.libsession.messaging.sending_receiving.MessageSender.send import org.session.libsession.messaging.sending_receiving.MessageSender.send
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeAPI.nowWithOffset import org.session.libsession.snode.SnodeAPI.nowWithOffset
import org.session.libsession.utilities.SSKEnvironment
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled
import org.session.libsession.utilities.associateByNotNull import org.session.libsession.utilities.associateByNotNull
@ -62,6 +63,18 @@ class MarkReadReceiver : BroadcastReceiver() {
sendReadReceipts(context, markedReadMessages) sendReadReceipts(context, markedReadMessages)
markedReadMessages
.filter { it.expiryType == ExpiryType.AFTER_READ }
.forEach { info ->
DatabaseComponent.get(context).mmsSmsDatabase().getMessageForTimestamp(info.syncMessageId.timetamp)
?.takeUnless { it.isExpirationTimerUpdate && it.recipient.isClosedGroupRecipient }
?.run {
SSKEnvironment.shared.messageExpirationManager.startDisappearAfterRead(
info.syncMessageId.timetamp,
info.syncMessageId.address.serialize()
)
}
}
markedReadMessages.forEach { scheduleDeletion(context, it.expirationInfo) } markedReadMessages.forEach { scheduleDeletion(context, it.expirationInfo) }
hashToDisappearAfterReadMessage(context, markedReadMessages)?.let { hashToDisappearAfterReadMessage(context, markedReadMessages)?.let {
@ -77,7 +90,7 @@ class MarkReadReceiver : BroadcastReceiver() {
val loki = DatabaseComponent.get(context).lokiMessageDatabase() val loki = DatabaseComponent.get(context).lokiMessageDatabase()
return markedReadMessages return markedReadMessages
.filter { it.guessExpiryType() == ExpiryType.AFTER_READ } .filter { it.expiryType == ExpiryType.AFTER_READ }
.associateByNotNull { it.expirationInfo.run { loki.getMessageServerHash(id, isMms) } } .associateByNotNull { it.expirationInfo.run { loki.getMessageServerHash(id, isMms) } }
.takeIf { it.isNotEmpty() } .takeIf { it.isNotEmpty() }
} }

View File

@ -49,6 +49,9 @@ class ExpiringMessageManager(context: Context) : MessageExpirationManagerProtoco
fun scheduleDeletion(id: Long, mms: Boolean, startedAtTimestamp: Long, expiresInMillis: Long) { fun scheduleDeletion(id: Long, mms: Boolean, startedAtTimestamp: Long, expiresInMillis: Long) {
Log.d(TAG, "scheduleDeletion() called with: id = $id, mms = $mms, startedAtTimestamp = $startedAtTimestamp, expiresInMillis = $expiresInMillis") Log.d(TAG, "scheduleDeletion() called with: id = $id, mms = $mms, startedAtTimestamp = $startedAtTimestamp, expiresInMillis = $expiresInMillis")
if (startedAtTimestamp <= 0) return
val expiresAtMillis = startedAtTimestamp + expiresInMillis val expiresAtMillis = startedAtTimestamp + expiresInMillis
synchronized(expiringMessageReferences) { synchronized(expiringMessageReferences) {
expiringMessageReferences += ExpiringMessageReference(id, mms, expiresAtMillis) expiringMessageReferences += ExpiringMessageReference(id, mms, expiresAtMillis)
@ -164,16 +167,16 @@ class ExpiringMessageManager(context: Context) : MessageExpirationManagerProtoco
insertIncomingExpirationTimerMessage(message, expireStartedAt) insertIncomingExpirationTimerMessage(message, expireStartedAt)
} }
startAnyExpiration(message) maybeStartExpiration(message)
} }
override fun startAnyExpiration(timestamp: Long, author: String, expireStartedAt: Long) { override fun startAnyExpiration(timestamp: Long, author: String, expireStartedAt: Long) {
Log.d(TAG, "startAnyExpiration() called with: timestamp = $timestamp, author = $author, expireStartedAt = $expireStartedAt") Log.d(TAG, "startAnyExpiration() called with: timestamp = $timestamp, author = $author, expireStartedAt = $expireStartedAt")
val messageRecord = mmsSmsDatabase.getMessageFor(timestamp, author) ?: throw Exception("no message record!!!")
Log.d(TAG, "startAnyExpiration() $messageRecord") mmsSmsDatabase.getMessageFor(timestamp, author)?.run {
val mms = messageRecord.isMms() getDatabase(isMms()).markExpireStarted(getId(), expireStartedAt)
getDatabase(mms).markExpireStarted(messageRecord.getId(), expireStartedAt) scheduleDeletion(getId(), isMms(), expireStartedAt, expiresIn)
scheduleDeletion(messageRecord.getId(), mms, expireStartedAt, messageRecord.expiresIn) } ?: Log.e(TAG, "no message record!!!")
} }
private inner class LoadTask : Runnable { private inner class LoadTask : Runnable {

View File

@ -16,6 +16,7 @@ import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.bind
import org.session.libsession.database.StorageProtocol import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.calls.CallMessageType
import org.session.libsession.messaging.messages.applyExpiryMode
import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
@ -57,8 +58,12 @@ import java.util.ArrayDeque
import java.util.UUID import java.util.UUID
import org.thoughtcrime.securesms.webrtc.data.State as CallState import org.thoughtcrime.securesms.webrtc.data.State as CallState
class CallManager(context: Context, audioManager: AudioManagerCompat, private val storage: StorageProtocol): PeerConnection.Observer, class CallManager(
SignalAudioManager.EventListener, CameraEventListener, DataChannel.Observer { private val context: Context,
audioManager: AudioManagerCompat,
private val storage: StorageProtocol
): PeerConnection.Observer,
SignalAudioManager.EventListener, CameraEventListener, DataChannel.Observer {
sealed class StateEvent { sealed class StateEvent {
data class AudioEnabled(val isEnabled: Boolean): StateEvent() data class AudioEnabled(val isEnabled: Boolean): StateEvent()
@ -293,17 +298,16 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
while (pendingOutgoingIceUpdates.isNotEmpty()) { while (pendingOutgoingIceUpdates.isNotEmpty()) {
currentPendings.add(pendingOutgoingIceUpdates.pop()) currentPendings.add(pendingOutgoingIceUpdates.pop())
} }
val sdps = currentPendings.map { it.sdp }
val sdpMLineIndexes = currentPendings.map { it.sdpMLineIndex }
val sdpMids = currentPendings.map { it.sdpMid }
MessageSender.sendNonDurably(CallMessage( CallMessage(
ICE_CANDIDATES, ICE_CANDIDATES,
sdps = sdps, sdps = currentPendings.map(IceCandidate::sdp),
sdpMLineIndexes = sdpMLineIndexes, sdpMLineIndexes = currentPendings.map(IceCandidate::sdpMLineIndex),
sdpMids = sdpMids, sdpMids = currentPendings.map(IceCandidate::sdpMid),
currentCallId currentCallId
), currentRecipient.address, isSyncMessage = currentRecipient.isLocalNumber) )
.applyExpiryMode()
.also { MessageSender.sendNonDurably(it, currentRecipient.address, isSyncMessage = currentRecipient.isLocalNumber) }
} }
} }
} }

View File

@ -4,11 +4,13 @@ import kotlin.time.Duration.Companion.seconds
sealed class ExpiryMode(val expirySeconds: Long) { sealed class ExpiryMode(val expirySeconds: Long) {
object NONE: ExpiryMode(0) object NONE: ExpiryMode(0)
data class Legacy(private val seconds: Long): ExpiryMode(seconds) // after read data class Legacy(private val seconds: Long): ExpiryMode(seconds)
data class AfterSend(private val seconds: Long): ExpiryMode(seconds) data class AfterSend(private val seconds: Long): ExpiryMode(seconds)
data class AfterRead(private val seconds: Long): ExpiryMode(seconds) data class AfterRead(private val seconds: Long): ExpiryMode(seconds)
val duration get() = expirySeconds.seconds val duration get() = expirySeconds.seconds
val expiryMillis get() = expirySeconds * 1000L val expiryMillis get() = expirySeconds * 1000L
fun coerceSendToRead(coerce: Boolean = true) = if (coerce && this is AfterSend) AfterRead(expirySeconds) else this
} }

View File

@ -6,6 +6,7 @@ import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
@ -25,6 +26,8 @@ abstract class Message {
var expiryMode: ExpiryMode = ExpiryMode.NONE var expiryMode: ExpiryMode = ExpiryMode.NONE
open val coerceDisappearAfterSendToRead = false
open val defaultTtl: Long = 14 * 24 * 60 * 60 * 1000 open val defaultTtl: Long = 14 * 24 * 60 * 60 * 1000
open val ttl: Long get() = specifiedTtl ?: defaultTtl open val ttl: Long get() = specifiedTtl ?: defaultTtl
open val isSelfSendValid: Boolean = false open val isSelfSendValid: Boolean = false
@ -51,26 +54,16 @@ abstract class Message {
abstract fun toProto(): SignalServiceProtos.Content? abstract fun toProto(): SignalServiceProtos.Content?
fun setGroupContext(dataMessage: SignalServiceProtos.DataMessage.Builder) { fun setGroupContext(dataMessage: SignalServiceProtos.DataMessage.Builder) {
val groupProto = SignalServiceProtos.GroupContext.newBuilder() dataMessage.group = SignalServiceProtos.GroupContext.newBuilder().apply {
val groupID = GroupUtil.doubleEncodeGroupID(recipient!!) id = GroupUtil.doubleEncodeGroupID(recipient!!).let(GroupUtil::getDecodedGroupIDAsData).let(ByteString::copyFrom)
groupProto.id = ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID)) type = SignalServiceProtos.GroupContext.Type.DELIVER
groupProto.type = SignalServiceProtos.GroupContext.Type.DELIVER }.build()
dataMessage.group = groupProto.build()
} }
fun SignalServiceProtos.Content.Builder.setExpirationConfigurationIfNeeded( fun SignalServiceProtos.Content.Builder.applyExpiryMode(): SignalServiceProtos.Content.Builder {
threadId: Long?, expirationTimer = expiryMode.expirySeconds.toInt()
coerceDisappearAfterSendToRead: Boolean = false expirationType = when (expiryMode) {
): SignalServiceProtos.Content.Builder { is ExpiryMode.AfterSend -> ExpirationType.DELETE_AFTER_SEND
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
is ExpiryMode.AfterRead -> ExpirationType.DELETE_AFTER_READ is ExpiryMode.AfterRead -> ExpirationType.DELETE_AFTER_READ
else -> ExpirationType.UNKNOWN else -> ExpirationType.UNKNOWN
} }
@ -79,13 +72,36 @@ abstract class Message {
} }
inline fun <reified M: Message> M.copyExpiration(proto: SignalServiceProtos.Content): M { 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 (proto.takeIf { it.hasExpirationTimer() }?.expirationTimer ?: proto.dataMessage?.expireTimer)?.let { duration ->
expiryMode = when (proto.expirationType.takeIf { duration > 0 }) {
expiryMode = when (proto.expirationType.takeIf { duration > 0 }) { ExpirationType.DELETE_AFTER_SEND -> ExpiryMode.AfterSend(duration.toLong())
ExpirationType.DELETE_AFTER_SEND -> ExpiryMode.AfterSend(duration.toLong()) ExpirationType.DELETE_AFTER_READ -> ExpiryMode.AfterRead(duration.toLong())
ExpirationType.DELETE_AFTER_READ -> ExpiryMode.AfterRead(duration.toLong()) else -> ExpiryMode.NONE
else -> ExpiryMode.NONE }
} }
return this 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 package org.session.libsession.messaging.messages.control
import org.session.libsession.messaging.messages.applyExpiryMode
import org.session.libsession.messaging.messages.copyExpiration import org.session.libsession.messaging.messages.copyExpiration
import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.* import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.*
@ -13,6 +14,7 @@ class CallMessage(): ControlMessage() {
var sdpMids: List<String> = listOf() var sdpMids: List<String> = listOf()
var callId: UUID? = null var callId: UUID? = null
override val coerceDisappearAfterSendToRead = true
override val isSelfSendValid: Boolean get() = type in arrayOf(ANSWER, END_CALL) override val isSelfSendValid: Boolean get() = type in arrayOf(ANSWER, END_CALL)
override val defaultTtl: Long = 300000L // 5m override val defaultTtl: Long = 300000L // 5m
@ -40,21 +42,21 @@ class CallMessage(): ControlMessage() {
listOf(), listOf(),
listOf(), listOf(),
callId callId
) ).applyExpiryMode()
fun preOffer(callId: UUID) = CallMessage(PRE_OFFER, fun preOffer(callId: UUID) = CallMessage(PRE_OFFER,
listOf(), listOf(),
listOf(), listOf(),
listOf(), listOf(),
callId callId
) ).applyExpiryMode()
fun offer(sdp: String, callId: UUID) = CallMessage(OFFER, fun offer(sdp: String, callId: UUID) = CallMessage(OFFER,
listOf(sdp), listOf(sdp),
listOf(), listOf(),
listOf(), listOf(),
callId callId
) ).applyExpiryMode()
fun endCall(callId: UUID) = CallMessage(END_CALL, emptyList(), emptyList(), emptyList(), callId) fun endCall(callId: UUID) = CallMessage(END_CALL, emptyList(), emptyList(), emptyList(), callId)
@ -83,9 +85,8 @@ class CallMessage(): ControlMessage() {
.addAllSdpMids(sdpMids) .addAllSdpMids(sdpMids)
.setUuid(callId!!.toString()) .setUuid(callId!!.toString())
val content = SignalServiceProtos.Content.newBuilder() return SignalServiceProtos.Content.newBuilder()
content.setExpirationConfigurationIfNeeded(threadID, true) .applyExpiryMode()
return content
.setCallMessage(callMessage) .setCallMessage(callMessage)
.build() .build()
} }

View File

@ -178,7 +178,7 @@ class ClosedGroupControlMessage() : ControlMessage() {
contentProto.dataMessage = dataMessageProto.build() contentProto.dataMessage = dataMessageProto.build()
// Expiration timer // Expiration timer
val threadId = groupID?.let { MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(Address.fromSerialized(it)) } val threadId = groupID?.let { MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(Address.fromSerialized(it)) }
contentProto.setExpirationConfigurationIfNeeded(threadId) contentProto.applyExpiryMode()
return contentProto.build() return contentProto.build()
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Couldn't construct closed group control message proto from: $this.") 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 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() 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 { override fun toString(): String {
return name return name
@ -40,8 +40,7 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray())) DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray()))
val members = proto.membersList.map { it.toByteArray().toHexString() } val members = proto.membersList.map { it.toByteArray().toHexString() }
val admins = proto.adminsList.map { it.toByteArray().toHexString() } val admins = proto.adminsList.map { it.toByteArray().toHexString() }
val expirationTimer = proto.expirationTimer return ClosedGroup(publicKey, name, encryptionKeyPair, members, admins)
return ClosedGroup(publicKey, name, encryptionKeyPair, members, admins, expirationTimer)
} }
} }
@ -55,7 +54,6 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
result.encryptionKeyPair = encryptionKeyPairAsProto.build() result.encryptionKeyPair = encryptionKeyPairAsProto.build()
result.addAllMembers(members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) result.addAllMembers(members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) })
result.addAllAdmins(admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) result.addAllAdmins(admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) })
result.expirationTimer = expirationTimer
return result.build() return result.build()
} }
} }
@ -128,15 +126,12 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
if (!group.members.contains(Address.fromSerialized(storage.getUserPublicKey()!!))) continue if (!group.members.contains(Address.fromSerialized(storage.getUserPublicKey()!!))) continue
val groupPublicKey = GroupUtil.doubleDecodeGroupID(group.encodedId).toHexString() val groupPublicKey = GroupUtil.doubleDecodeGroupID(group.encodedId).toHexString()
val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: continue val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: continue
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(group.encodedId))
val expiryConfig = storage.getExpirationConfiguration(threadID)
val closedGroup = ClosedGroup( val closedGroup = ClosedGroup(
groupPublicKey, groupPublicKey,
group.title, group.title,
encryptionKeyPair, encryptionKeyPair,
group.members.map { it.serialize() }, group.members.map { it.serialize() },
group.admins.map { it.serialize() }, group.admins.map { it.serialize() }
expiryConfig?.expiryMode?.expirySeconds?.toInt() ?: 0
) )
closedGroups.add(closedGroup) closedGroups.add(closedGroup)
} }

View File

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

View File

@ -1,58 +1,52 @@
package org.session.libsession.messaging.messages.control 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.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.copyExpiration import org.session.libsession.messaging.messages.copyExpiration
import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
/** In the case of a sync message, the public key of the person the message was targeted at. /** 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. * **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 override val isSelfSendValid: Boolean = true
companion object { companion object {
const val TAG = "ExpirationTimerUpdate" const val TAG = "ExpirationTimerUpdate"
private val storage = MessagingModuleConfiguration.shared.storage
fun fromProto(proto: SignalServiceProtos.Content): ExpirationTimerUpdate? { fun fromProto(proto: SignalServiceProtos.Content): ExpirationTimerUpdate? =
val dataMessageProto = if (proto.hasDataMessage()) proto.dataMessage else return null proto.dataMessage?.takeIf { it.flags and EXPIRATION_TIMER_UPDATE_VALUE != 0 }?.run {
val isExpirationTimerUpdate = dataMessageProto.flags.and( ExpirationTimerUpdate(syncTarget, hasGroup()).copyExpiration(proto)
SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE }
) != 0
if (!isExpirationTimerUpdate) return null
return ExpirationTimerUpdate(dataMessageProto.syncTarget)
.copyExpiration(proto)
}
} }
override fun toProto(): SignalServiceProtos.Content? { override fun toProto(): SignalServiceProtos.Content? {
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder() val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder().apply {
dataMessageProto.flags = SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE flags = EXPIRATION_TIMER_UPDATE_VALUE
dataMessageProto.expireTimer = expiryMode.expirySeconds.toInt() expireTimer = expiryMode.expirySeconds.toInt()
// Sync target
if (syncTarget != null) {
dataMessageProto.syncTarget = syncTarget
} }
// Sync target
syncTarget?.let { dataMessageProto.syncTarget = it }
// Group context // Group context
if (MessagingModuleConfiguration.shared.storage.isClosedGroup(recipient!!)) { if (storage.isClosedGroup(recipient!!)) {
try { try {
setGroupContext(dataMessageProto) setGroupContext(dataMessageProto)
} catch(e: Exception) { } 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 null
} }
} }
return try { return try {
SignalServiceProtos.Content.newBuilder().apply { SignalServiceProtos.Content.newBuilder()
dataMessage = dataMessageProto.build() .setDataMessage(dataMessageProto)
setExpirationConfigurationIfNeeded(threadID) .applyExpiryMode()
}.build() .build()
} catch (e: Exception) { } 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 null
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -145,9 +145,7 @@ object MessageReceiver {
MessageRequestResponse.fromProto(proto) ?: MessageRequestResponse.fromProto(proto) ?:
CallMessage.fromProto(proto) ?: CallMessage.fromProto(proto) ?:
SharedConfigurationMessage.fromProto(proto) ?: SharedConfigurationMessage.fromProto(proto) ?:
VisibleMessage.fromProto(proto) ?: run { VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage
throw Error.UnknownMessage
}
val isUserBlindedSender = sender == openGroupPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId(IdPrefix.BLINDED, it.publicKey.asBytes).hexString } val isUserBlindedSender = sender == openGroupPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId(IdPrefix.BLINDED, it.publicKey.asBytes).hexString }
val isUserSender = sender == userPublicKey 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.jobs.NotifyPNServerJob
import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.Message 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.CallMessage
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage
import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage
@ -421,12 +422,7 @@ object MessageSender {
storage.markUnidentified(timestamp, userPublicKey) storage.markUnidentified(timestamp, userPublicKey)
// Start the disappearing messages timer if needed // 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") 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 -> SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(message, startDisappearAfterRead = true)
if (message.recipient == userPublicKey || !isSyncMessage) {
val expireStartedAt = if (mode is ExpiryMode.AfterRead) timestamp + 1 else timestamp
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(timestamp, userPublicKey, expireStartedAt)
}
}
} ?: run { } ?: run {
storage.updateReactionIfNeeded(message, message.sender?:userPublicKey, openGroupSentTimestamp) storage.updateReactionIfNeeded(message, message.sender?:userPublicKey, openGroupSentTimestamp)
} }
@ -475,6 +471,7 @@ object MessageSender {
@JvmStatic @JvmStatic
fun send(message: Message, address: Address) { fun send(message: Message, address: Address) {
message.applyExpiryMode()
val threadID = MessagingModuleConfiguration.shared.storage.getThreadId(address) val threadID = MessagingModuleConfiguration.shared.storage.getThreadId(address)
message.threadID = threadID message.threadID = threadID
val destination = Destination.from(address) val destination = Destination.from(address)

View File

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

View File

@ -3,11 +3,12 @@ package org.session.libsession.utilities
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import network.loki.messenger.libsession_util.util.ExpiryMode 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.contacts.Contact
import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier 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 import org.session.libsession.utilities.recipients.Recipient
class SSKEnvironment( class SSKEnvironment(
@ -43,18 +44,41 @@ class SSKEnvironment(
interface MessageExpirationManagerProtocol { interface MessageExpirationManagerProtocol {
fun insertExpirationTimerMessage(message: ExpirationTimerUpdate) fun insertExpirationTimerMessage(message: ExpirationTimerUpdate)
fun startAnyExpiration(timestamp: Long, author: String, expireStartedAt: Long) 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 fun maybeStartExpiration(message: Message, startDisappearAfterRead: Boolean = false) {
startAnyExpiration( Log.d("MessageExpirationManagerProtocol", "maybeStartExpiration() called with: message = $message, startDisappearAfterRead = $startDisappearAfterRead")
timestamp = timestamp,
author = message.sender ?: return, if (message is ExpirationTimerUpdate && message.isGroup) 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 maybeStartExpiration(
else return 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 { companion object {