session-android/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt

380 lines
20 KiB
Kotlin
Raw Normal View History

2020-12-02 06:39:21 +01:00
package org.session.libsession.messaging.sending_receiving
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.deferred
2021-04-26 03:14:45 +02:00
import org.session.libsession.messaging.MessagingModuleConfiguration
2020-12-02 06:39:21 +01:00
import org.session.libsession.messaging.jobs.JobQueue
2021-03-02 02:24:09 +01:00
import org.session.libsession.messaging.jobs.MessageSendJob
2021-01-20 06:29:52 +01:00
import org.session.libsession.messaging.jobs.NotifyPNServerJob
2020-12-02 06:39:21 +01:00
import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage
import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
import org.session.libsession.messaging.messages.control.UnsendRequest
import org.session.libsession.messaging.messages.visible.LinkPreview
import org.session.libsession.messaging.messages.visible.Profile
import org.session.libsession.messaging.messages.visible.Quote
import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.open_groups.OpenGroupMessageV2
2020-12-02 06:39:21 +01:00
import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.snode.RawResponsePromise
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeMessage
import org.session.libsession.snode.SnodeModule
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.GroupUtil
2021-01-20 06:29:52 +01:00
import org.session.libsession.utilities.SSKEnvironment
2021-05-18 01:50:16 +02:00
import org.session.libsignal.crypto.PushTransportDetails
2021-05-18 01:44:06 +02:00
import org.session.libsignal.protos.SignalServiceProtos
2021-03-02 02:24:09 +01:00
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Namespace
import org.session.libsignal.utilities.defaultRequiresAuth
import org.session.libsignal.utilities.hasNamespaces
import org.session.libsignal.utilities.hexEncodedPublicKey
import java.util.concurrent.atomic.AtomicInteger
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
2021-04-26 03:23:09 +02:00
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote
2020-12-02 06:39:21 +01:00
2020-11-25 02:06:41 +01:00
object MessageSender {
2020-12-02 06:39:21 +01:00
// Error
sealed class Error(val description: String) : Exception(description) {
2020-12-02 06:39:21 +01:00
object InvalidMessage : Error("Invalid message.")
object ProtoConversionFailed : Error("Couldn't convert message to proto.")
object NoUserED25519KeyPair : Error("Couldn't find user ED25519 key pair.")
object SigningFailed : Error("Couldn't sign message.")
object EncryptionFailed : Error("Couldn't encrypt message.")
2020-12-02 06:39:21 +01:00
// Closed groups
object NoThread : Error("Couldn't find a thread associated with the given group public key.")
object NoKeyPair: Error("Couldn't find a private key associated with the given group public key.")
2020-12-02 06:39:21 +01:00
object InvalidClosedGroupUpdate : Error("Invalid group update.")
internal val isRetryable: Boolean = when (this) {
2021-05-13 08:14:54 +02:00
is InvalidMessage, ProtoConversionFailed, InvalidClosedGroupUpdate -> false
2020-12-02 06:39:21 +01:00
else -> true
}
}
// Convenience
fun send(message: Message, destination: Destination): Promise<Unit, Exception> {
2021-05-21 07:02:34 +02:00
if (destination is Destination.OpenGroupV2) {
2020-12-02 06:39:21 +01:00
return sendToOpenGroupDestination(destination, message)
2021-05-21 07:02:34 +02:00
} else {
return sendToSnodeDestination(destination, message)
2020-12-02 06:39:21 +01:00
}
}
// One-on-One Chats & Closed Groups
private fun sendToSnodeDestination(destination: Destination, message: Message, isSyncMessage: Boolean = false): Promise<Unit, Exception> {
2020-12-02 06:39:21 +01:00
val deferred = deferred<Unit, Exception>()
val promise = deferred.promise
2021-04-26 03:14:45 +02:00
val storage = MessagingModuleConfiguration.shared.storage
2020-12-18 06:44:33 +01:00
val userPublicKey = storage.getUserPublicKey()
// Set the timestamp, sender and recipient
2021-05-13 08:14:54 +02:00
if (message.sentTimestamp == null) {
message.sentTimestamp = System.currentTimeMillis() // Visible messages will already have their sent timestamp set
}
message.sender = userPublicKey
2021-03-15 03:14:45 +01:00
val isSelfSend = (message.recipient == userPublicKey)
// Set the failure handler (need it here already for precondition failure handling)
fun handleFailure(error: Exception) {
handleFailedMessageSend(message, error)
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
2021-04-26 02:26:31 +02:00
SnodeModule.shared.broadcaster.broadcast("messageFailed", message.sentTimestamp!!)
2021-03-15 03:14:45 +01:00
}
deferred.reject(error)
}
2020-12-02 06:39:21 +01:00
try {
when (destination) {
is Destination.Contact -> message.recipient = destination.publicKey
is Destination.ClosedGroup -> message.recipient = destination.groupPublicKey
2021-05-21 07:02:34 +02:00
is Destination.OpenGroupV2 -> throw IllegalStateException("Destination should not be an open group.")
2020-12-02 06:39:21 +01:00
}
// Validate the message
if (!message.isValid()) { throw Error.InvalidMessage }
// Stop here if this is a self-send, unless it's:
// • a configuration message
// • a sync message
// • a closed group control message of type `new`
var isNewClosedGroupControlMessage = false
if (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) isNewClosedGroupControlMessage = true
if (isSelfSend && message !is ConfigurationMessage && !isSyncMessage && !isNewClosedGroupControlMessage && message !is UnsendRequest) {
2020-12-18 06:44:33 +01:00
handleSuccessfulMessageSend(message, destination)
deferred.resolve(Unit)
return promise
}
// Attach the user's profile if needed
if (message is VisibleMessage) {
val displayName = storage.getUserDisplayName()!!
val profileKey = storage.getUserProfileKey()
val profilePictureUrl = storage.getUserProfilePictureURL()
if (profileKey != null && profilePictureUrl != null) {
message.profile = Profile(displayName, profileKey, profilePictureUrl)
2020-12-18 06:44:33 +01:00
} else {
message.profile = Profile(displayName)
}
}
2020-12-02 06:39:21 +01:00
// Convert it to protobuf
val proto = message.toProto() ?: throw Error.ProtoConversionFailed
// Serialize the protobuf
val plaintext = PushTransportDetails.getPaddedMessageBody(proto.toByteArray())
2020-12-02 06:39:21 +01:00
// Encrypt the serialized protobuf
val ciphertext: ByteArray
when (destination) {
2021-05-13 08:14:54 +02:00
is Destination.Contact -> ciphertext = MessageEncrypter.encrypt(plaintext, destination.publicKey)
is Destination.ClosedGroup -> {
2021-04-26 03:14:45 +02:00
val encryptionKeyPair = MessagingModuleConfiguration.shared.storage.getLatestClosedGroupEncryptionKeyPair(destination.groupPublicKey)!!
2021-05-13 08:14:54 +02:00
ciphertext = MessageEncrypter.encrypt(plaintext, encryptionKeyPair.hexEncodedPublicKey)
}
2021-05-21 07:02:34 +02:00
is Destination.OpenGroupV2 -> throw IllegalStateException("Destination should not be open group.")
2020-12-02 06:39:21 +01:00
}
// Wrap the result
val kind: SignalServiceProtos.Envelope.Type
val senderPublicKey: String
// TODO: this might change in future for config messages
val forkInfo = SnodeAPI.forkInfo
val namespaces: List<Int> = when {
destination is Destination.ClosedGroup
&& forkInfo.defaultRequiresAuth() -> listOf(Namespace.UNAUTHENTICATED_CLOSED_GROUP)
destination is Destination.ClosedGroup
&& forkInfo.hasNamespaces() -> listOf(Namespace.UNAUTHENTICATED_CLOSED_GROUP, Namespace.DEFAULT)
else -> listOf(Namespace.DEFAULT)
}
2020-12-02 06:39:21 +01:00
when (destination) {
is Destination.Contact -> {
2021-04-26 03:06:00 +02:00
kind = SignalServiceProtos.Envelope.Type.SESSION_MESSAGE
2020-12-02 06:39:21 +01:00
senderPublicKey = ""
}
is Destination.ClosedGroup -> {
2021-04-26 03:06:00 +02:00
kind = SignalServiceProtos.Envelope.Type.CLOSED_GROUP_MESSAGE
2020-12-02 06:39:21 +01:00
senderPublicKey = destination.groupPublicKey
}
2021-05-21 07:02:34 +02:00
is Destination.OpenGroupV2 -> throw IllegalStateException("Destination should not be open group.")
2020-12-02 06:39:21 +01:00
}
val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext)
2021-04-15 02:42:47 +02:00
// Send the result
2020-12-18 06:44:33 +01:00
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
2021-04-26 02:26:31 +02:00
SnodeModule.shared.broadcaster.broadcast("calculatingPoW", message.sentTimestamp!!)
2020-12-18 06:44:33 +01:00
}
2020-12-02 06:39:21 +01:00
val base64EncodedData = Base64.encodeBytes(wrappedMessage)
// Send the result
2021-07-23 06:09:27 +02:00
val timestamp = message.sentTimestamp!! + SnodeAPI.clockOffset
val snodeMessage = SnodeMessage(message.recipient!!, base64EncodedData, message.ttl, timestamp)
2021-03-02 02:24:09 +01:00
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
2021-04-26 02:26:31 +02:00
SnodeModule.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!)
2021-03-02 02:24:09 +01:00
}
namespaces.map { namespace -> SnodeAPI.sendMessage(snodeMessage, requiresAuth = false, namespace = namespace) }.let { promises ->
2020-12-02 06:39:21 +01:00
var isSuccess = false
val promiseCount = promises.size
var errorCount = AtomicInteger(0)
promises.forEach { promise: RawResponsePromise ->
2020-12-02 06:39:21 +01:00
promise.success {
if (isSuccess) { return@success } // Succeed as soon as the first promise succeeds
isSuccess = true
2020-12-18 06:44:33 +01:00
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
2021-04-26 02:26:31 +02:00
SnodeModule.shared.broadcaster.broadcast("messageSent", message.sentTimestamp!!)
2020-12-18 06:44:33 +01:00
}
2021-08-10 08:42:15 +02:00
val hash = it["hash"] as? String
message.serverHash = hash
handleSuccessfulMessageSend(message, destination, isSyncMessage)
val shouldNotify = ((message is VisibleMessage || message is UnsendRequest || message is CallMessage) && !isSyncMessage)
/*
if (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) {
2020-12-18 06:44:33 +01:00
shouldNotify = true
}
*/
2020-12-18 06:44:33 +01:00
if (shouldNotify) {
val notifyPNServerJob = NotifyPNServerJob(snodeMessage)
JobQueue.shared.add(notifyPNServerJob)
}
2021-03-26 05:46:37 +01:00
deferred.resolve(Unit)
2020-12-02 06:39:21 +01:00
}
promise.fail {
errorCount.getAndIncrement()
if (errorCount.get() != promiseCount) { return@fail } // Only error out if all promises failed
2020-12-18 06:44:33 +01:00
handleFailure(it)
2020-12-02 06:39:21 +01:00
}
}
}
} catch (exception: Exception) {
2021-03-15 03:14:45 +01:00
handleFailure(exception)
2020-12-02 06:39:21 +01:00
}
return promise
}
// Open Groups
private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise<Unit, Exception> {
2020-12-02 06:39:21 +01:00
val deferred = deferred<Unit, Exception>()
2021-04-26 03:14:45 +02:00
val storage = MessagingModuleConfiguration.shared.storage
2021-05-13 08:14:54 +02:00
if (message.sentTimestamp == null) {
message.sentTimestamp = System.currentTimeMillis()
}
2020-12-02 06:39:21 +01:00
message.sender = storage.getUserPublicKey()
// Set the failure handler (need it here already for precondition failure handling)
fun handleFailure(error: Exception) {
handleFailedMessageSend(message, error)
deferred.reject(error)
}
2020-12-02 06:39:21 +01:00
try {
when (destination) {
2021-05-13 08:14:54 +02:00
is Destination.Contact, is Destination.ClosedGroup -> throw IllegalStateException("Invalid destination.")
is Destination.OpenGroupV2 -> {
message.recipient = "${destination.server}.${destination.room}"
val server = destination.server
val room = destination.room
// Attach the user's profile if needed
if (message is VisibleMessage) {
val displayName = storage.getUserDisplayName()!!
val profileKey = storage.getUserProfileKey()
val profilePictureUrl = storage.getUserProfilePictureURL()
if (profileKey != null && profilePictureUrl != null) {
message.profile = Profile(displayName, profileKey, profilePictureUrl)
} else {
message.profile = Profile(displayName)
}
}
// Validate the message
if (message !is VisibleMessage || !message.isValid()) {
throw Error.InvalidMessage
}
val proto = message.toProto()!!
2021-05-12 07:47:17 +02:00
val plaintext = PushTransportDetails.getPaddedMessageBody(proto.toByteArray())
val openGroupMessage = OpenGroupMessageV2(
2021-05-13 08:14:54 +02:00
sender = message.sender,
sentTimestamp = message.sentTimestamp!!,
base64EncodedData = Base64.encodeBytes(plaintext),
)
OpenGroupAPIV2.send(openGroupMessage,room,server).success {
message.openGroupServerMessageID = it.serverID
handleSuccessfulMessageSend(message, destination, openGroupSentTimestamp = it.sentTimestamp)
deferred.resolve(Unit)
}.fail {
handleFailure(it)
}
2020-12-02 06:39:21 +01:00
}
}
} catch (exception: Exception) {
handleFailure(exception)
2020-12-02 06:39:21 +01:00
}
return deferred.promise
}
// Result Handling
fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) {
2021-04-26 03:14:45 +02:00
val storage = MessagingModuleConfiguration.shared.storage
2021-03-17 06:22:43 +01:00
val userPublicKey = storage.getUserPublicKey()!!
// Ignore future self-sends
storage.addReceivedMessageTimestamp(message.sentTimestamp!!)
2021-08-16 06:03:06 +02:00
storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender?:userPublicKey)?.let { messageID ->
if (openGroupSentTimestamp != -1L && message is VisibleMessage) {
storage.addReceivedMessageTimestamp(openGroupSentTimestamp)
storage.updateSentTimestamp(messageID, message.isMediaMessage(), openGroupSentTimestamp, message.threadID!!)
message.sentTimestamp = openGroupSentTimestamp
}
// When the sync message is successfully sent, the hash value of this TSOutgoingMessage
// will be replaced by the hash value of the sync message. Since the hash value of the
// real message has no use when we delete a message. It is OK to let it be.
message.serverHash?.let {
storage.setMessageServerHash(messageID, it)
}
// Track the open group server message ID
if (message.openGroupServerMessageID != null && destination is Destination.OpenGroupV2) {
val encoded = GroupUtil.getEncodedOpenGroupID("${destination.server}.${destination.room}".toByteArray())
val threadID = storage.getThreadId(Address.fromSerialized(encoded))
if (threadID != null && threadID >= 0) {
storage.setOpenGroupServerMessageID(messageID, message.openGroupServerMessageID!!, threadID, !(message as VisibleMessage).isMediaMessage())
}
}
// Mark the message as sent
storage.markAsSent(message.sentTimestamp!!, message.sender?:userPublicKey)
storage.markUnidentified(message.sentTimestamp!!, message.sender?:userPublicKey)
// Start the disappearing messages timer if needed
if (message is VisibleMessage && !isSyncMessage) {
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message.sentTimestamp!!, message.sender?:userPublicKey)
}
2021-03-04 07:14:12 +01:00
}
// Sync the message if:
// • it's a visible message
// • the destination was a contact
// • we didn't sync it already
2021-03-02 02:24:09 +01:00
if (destination is Destination.Contact && !isSyncMessage) {
if (message is VisibleMessage) { message.syncTarget = destination.publicKey }
if (message is ExpirationTimerUpdate) { message.syncTarget = destination.publicKey }
sendToSnodeDestination(Destination.Contact(userPublicKey), message, true)
}
2020-12-02 06:39:21 +01:00
}
fun handleFailedMessageSend(message: Message, error: Exception) {
2021-04-26 03:14:45 +02:00
val storage = MessagingModuleConfiguration.shared.storage
2021-03-17 06:22:43 +01:00
val userPublicKey = storage.getUserPublicKey()!!
storage.setErrorMessage(message.sentTimestamp!!, message.sender?:userPublicKey, error)
2021-03-02 02:24:09 +01:00
}
// Convenience
@JvmStatic
fun send(message: VisibleMessage, address: Address, attachments: List<SignalAttachment>, quote: SignalQuote?, linkPreview: SignalLinkPreview?) {
2021-05-13 08:14:54 +02:00
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
val attachmentIDs = messageDataProvider.getAttachmentIDsFor(message.id!!)
message.attachmentIDs.addAll(attachmentIDs)
2021-03-02 02:24:09 +01:00
message.quote = Quote.from(quote)
message.linkPreview = LinkPreview.from(linkPreview)
2021-05-13 08:14:54 +02:00
message.linkPreview?.let { linkPreview ->
if (linkPreview.attachmentID == null) {
messageDataProvider.getLinkPreviewAttachmentIDFor(message.id!!)?.let { attachmentID ->
message.linkPreview!!.attachmentID = attachmentID
message.attachmentIDs.remove(attachmentID)
2021-03-09 00:50:02 +01:00
}
}
}
2021-03-02 02:24:09 +01:00
send(message, address)
}
@JvmStatic
fun send(message: Message, address: Address) {
2021-04-26 03:14:45 +02:00
val threadID = MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(address)
2021-03-02 02:24:09 +01:00
message.threadID = threadID
val destination = Destination.from(address)
val job = MessageSendJob(message, destination)
JobQueue.shared.add(job)
}
fun sendNonDurably(message: VisibleMessage, attachments: List<SignalAttachment>, address: Address): Promise<Unit, Exception> {
2021-04-26 03:14:45 +02:00
val attachmentIDs = MessagingModuleConfiguration.shared.messageDataProvider.getAttachmentIDsFor(message.id!!)
message.attachmentIDs.addAll(attachmentIDs)
2021-03-02 02:24:09 +01:00
return sendNonDurably(message, address)
}
fun sendNonDurably(message: Message, address: Address): Promise<Unit, Exception> {
2021-04-26 03:14:45 +02:00
val threadID = MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(address)
2021-03-02 02:24:09 +01:00
message.threadID = threadID
val destination = Destination.from(address)
return send(message, destination)
2020-12-02 06:39:21 +01:00
}
2021-03-04 04:54:32 +01:00
// Closed groups
fun createClosedGroup(name: String, members: Collection<String>): Promise<String, Exception> {
return create(name, members)
}
fun explicitNameChange(groupPublicKey: String, newName: String) {
return setName(groupPublicKey, newName)
}
fun explicitAddMembers(groupPublicKey: String, membersToAdd: List<String>) {
return addMembers(groupPublicKey, membersToAdd)
}
fun explicitRemoveMembers(groupPublicKey: String, membersToRemove: List<String>) {
return removeMembers(groupPublicKey, membersToRemove)
}
@JvmStatic
2021-03-26 05:46:37 +01:00
fun explicitLeave(groupPublicKey: String, notifyUser: Boolean): Promise<Unit, Exception> {
return leave(groupPublicKey, notifyUser)
2021-03-04 04:54:32 +01:00
}
2020-11-25 02:06:41 +01:00
}