feat: syncing / joining / leaving working on open group v2

This commit is contained in:
jubb 2021-04-23 17:49:24 +10:00
parent a4d79ea2d3
commit 6272856ef9
13 changed files with 180 additions and 74 deletions

View File

@ -353,6 +353,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
val openGroupV2 = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID)
//TODO Move open group related logic to OpenGroupUtilities / PublicChatManager / GroupManager //TODO Move open group related logic to OpenGroupUtilities / PublicChatManager / GroupManager
if (publicChat != null) { if (publicChat != null) {
val apiDB = DatabaseFactory.getLokiAPIDatabase(context) val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
@ -364,6 +365,13 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
ApplicationContext.getInstance(context).publicChatManager ApplicationContext.getInstance(context).publicChatManager
.removeChat(publicChat.server, publicChat.channel) .removeChat(publicChat.server, publicChat.channel)
} else if (openGroupV2 != null) {
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
apiDB.removeLastMessageServerID(openGroupV2.room, openGroupV2.server)
apiDB.removeLastDeletionServerID(openGroupV2.room, openGroupV2.server)
ApplicationContext.getInstance(context).publicChatManager
.removeChat(openGroupV2.server, openGroupV2.room)
} else { } else {
threadDB.deleteConversation(threadID) threadDB.deleteConversation(threadID)
} }

View File

@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.loki.activities
import android.animation.Animator import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -23,9 +25,15 @@ import kotlinx.coroutines.withContext
import network.loki.messenger.R import network.loki.messenger.R
import okhttp3.HttpUrl import okhttp3.HttpUrl
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.DefaultGroup import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.DefaultGroup
import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.DistributionTypes
import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.GroupUtil
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.ConversationActivity
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
@ -86,14 +94,27 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
try { try {
if (isV2OpenGroup) { val (threadID, groupID) = if (isV2OpenGroup) {
val server = HttpUrl.Builder().scheme(httpUrl.scheme()).host(httpUrl.host()).build() val server = HttpUrl.Builder().scheme(httpUrl.scheme()).host(httpUrl.host()).build()
OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, server.toString().removeSuffix("/"), room!!, publicKey!!) val group = OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, server.toString().removeSuffix("/"), room!!, publicKey!!)
val threadID = GroupManager.getOpenGroupThreadID(group.id, this@JoinPublicChatActivity)
val groupID = GroupUtil.getEncodedOpenGroupID(group.id.toByteArray())
threadID to groupID
} else { } else {
val channel: Long = 1 val channel: Long = 1
OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, url, channel) val group = OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, url, channel)
val threadID = GroupManager.getOpenGroupThreadID(group.id, this@JoinPublicChatActivity)
val groupID = GroupUtil.getEncodedOpenGroupID(group.id.toByteArray())
threadID to groupID
} }
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@JoinPublicChatActivity) MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@JoinPublicChatActivity)
withContext(Dispatchers.Main) {
// go to the new conversation and finish this one
openConversationActivity(this@JoinPublicChatActivity, threadID, Recipient.from(this@JoinPublicChatActivity, Address.fromSerialized(groupID), false))
finish()
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e("JoinPublicChatActivity", "Fialed to join open group.", e) Log.e("JoinPublicChatActivity", "Fialed to join open group.", e)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -102,10 +123,19 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
} }
return@launch return@launch
} }
withContext(Dispatchers.Main) { finish() }
} }
} }
// endregion // endregion
// region Convenience
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
val intent = Intent(context, ConversationActivity::class.java)
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId)
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address)
context.startActivity(intent)
}
// endregion
} }
// region Adapter // region Adapter
@ -188,11 +218,13 @@ class EnterChatURLFragment : Fragment() {
} }
} }
// region Convenience
private fun joinPublicChatIfPossible() { private fun joinPublicChatIfPossible() {
val inputMethodManager = requireContext().getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager val inputMethodManager = requireContext().getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(chatURLEditText.windowToken, 0) inputMethodManager.hideSoftInputFromWindow(chatURLEditText.windowToken, 0)
val chatURL = chatURLEditText.text.trim().toString().toLowerCase() val chatURL = chatURLEditText.text.trim().toString().toLowerCase()
(requireActivity() as JoinPublicChatActivity).joinPublicChatIfPossible(chatURL) (requireActivity() as JoinPublicChatActivity).joinPublicChatIfPossible(chatURL)
} }
// endregion
} }
// endregion // endregion

View File

@ -107,8 +107,8 @@ class PublicChatManager(private val context: Context) {
} }
@WorkerThread @WorkerThread
fun addChat(server: String, room: String, info: OpenGroupAPIV2.Info): OpenGroupV2 { fun addChat(server: String, room: String, info: OpenGroupAPIV2.Info, publicKey: String): OpenGroupV2 {
val chat = OpenGroupV2(server, room, info.id, info.name) val chat = OpenGroupV2(server, room, info.name, publicKey)
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context) var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
var profilePicture: Bitmap? = null var profilePicture: Bitmap? = null
if (threadID < 0) { if (threadID < 0) {
@ -136,6 +136,16 @@ class PublicChatManager(private val context: Context) {
Util.runOnMain { startPollersIfNeeded() } Util.runOnMain { startPollersIfNeeded() }
} }
fun removeChat(server: String, room: String) {
val threadDB = DatabaseFactory.getThreadDatabase(context)
val groupId = "$server.$room"
val threadId = GroupManager.getOpenGroupThreadID(groupId, context)
val groupAddress = threadDB.getRecipientForThreadId(threadId)!!.address.serialize()
GroupManager.deleteGroup(groupAddress, context)
Util.runOnMain { startPollersIfNeeded() }
}
private fun refreshChatsAndPollers() { private fun refreshChatsAndPollers() {
val storage = MessagingConfiguration.shared.storage val storage = MessagingConfiguration.shared.storage
val chatsInDB = storage.getAllOpenGroups() val chatsInDB = storage.getAllOpenGroups()

View File

@ -34,7 +34,7 @@ object OpenGroupUtilities {
val groupInfo = OpenGroupAPIV2.getInfo(room,server).get() val groupInfo = OpenGroupAPIV2.getInfo(room,server).get()
val application = ApplicationContext.getInstance(context) val application = ApplicationContext.getInstance(context)
val group = application.publicChatManager.addChat(server, room, groupInfo) val group = application.publicChatManager.addChat(server, room, groupInfo, publicKey)
val storage = MessagingConfiguration.shared.storage val storage = MessagingConfiguration.shared.storage
storage.removeLastDeletionServerId(room, server) storage.removeLastDeletionServerId(room, server)

View File

@ -6,6 +6,7 @@ import com.esotericsoftware.kryo.io.Output
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.fileserver.FileServerAPI import org.session.libsession.messaging.fileserver.FileServerAPI
import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.service.api.crypto.AttachmentCipherOutputStream import org.session.libsignal.service.api.crypto.AttachmentCipherOutputStream
@ -52,10 +53,15 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
var shouldEncrypt = true var shouldEncrypt = true
val usePadding = false val usePadding = false
val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID) val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID)
val openGroupV2 = MessagingConfiguration.shared.storage.getV2OpenGroup(threadID)
openGroup?.let { openGroup?.let {
server = it.server server = it.server
shouldEncrypt = false shouldEncrypt = false
} }
openGroupV2?.let {
server = it.server
shouldEncrypt = false
}
val attachmentKey = Util.getSecretBytes(64) val attachmentKey = Util.getSecretBytes(64)
val paddedLength = if (usePadding) PaddingInputStream.getPaddedSize(attachment.length) else attachment.length val paddedLength = if (usePadding) PaddingInputStream.getPaddedSize(attachment.length) else attachment.length
@ -65,7 +71,11 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
val outputStreamFactory = if (shouldEncrypt) AttachmentCipherOutputStreamFactory(attachmentKey) else PlaintextOutputStreamFactory() val outputStreamFactory = if (shouldEncrypt) AttachmentCipherOutputStreamFactory(attachmentKey) else PlaintextOutputStreamFactory()
val attachmentData = PushAttachmentData(attachment.contentType, dataStream, ciphertextLength, outputStreamFactory, attachment.listener) val attachmentData = PushAttachmentData(attachment.contentType, dataStream, ciphertextLength, outputStreamFactory, attachment.listener)
val uploadResult = FileServerAPI.shared.uploadAttachment(server, attachmentData) val uploadResult = if (openGroupV2 == null) FileServerAPI.shared.uploadAttachment(server, attachmentData) else {
val dataBytes = attachmentData.data.readBytes()
val result = OpenGroupAPIV2.upload(dataBytes, openGroupV2.room, openGroupV2.server).get()
DotNetAPI.UploadResult(result, "${openGroupV2.server}/files/$result", byteArrayOf())
}
handleSuccess(attachment, attachmentKey, uploadResult) handleSuccess(attachment, attachmentKey, uploadResult)
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {

View File

@ -5,6 +5,9 @@ import org.session.libsession.messaging.threads.Address
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
typealias OpenGroupModel = org.session.libsession.messaging.opengroups.OpenGroup
typealias OpenGroupV2Model = org.session.libsession.messaging.opengroups.OpenGroupV2
sealed class Destination { sealed class Destination {
class Contact(var publicKey: String) : Destination() { class Contact(var publicKey: String) : Destination() {
@ -16,6 +19,9 @@ sealed class Destination {
class OpenGroup(var channel: Long, var server: String) : Destination() { class OpenGroup(var channel: Long, var server: String) : Destination() {
internal constructor(): this(0, "") internal constructor(): this(0, "")
} }
class OpenGroupV2(var room: String, var server: String): Destination() {
internal constructor(): this("", "")
}
companion object { companion object {
fun from(address: Address): Destination { fun from(address: Address): Destination {
@ -29,9 +35,13 @@ sealed class Destination {
ClosedGroup(groupPublicKey) ClosedGroup(groupPublicKey)
} }
address.isOpenGroup -> { address.isOpenGroup -> {
val threadID = MessagingConfiguration.shared.storage.getThreadID(address.contactIdentifier())!! val storage = MessagingConfiguration.shared.storage
val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID)!! val threadID = storage.getThreadID(address.contactIdentifier())!!
OpenGroup(openGroup.channel, openGroup.server) when (val openGroup = storage.getOpenGroup(threadID) ?: storage.getV2OpenGroup(threadID)) {
is OpenGroupModel -> OpenGroup(openGroup.channel, openGroup.server)
is OpenGroupV2Model -> OpenGroupV2(openGroup.room, openGroup.server)
else -> throw Exception("Invalid OpenGroup $openGroup")
}
} }
else -> { else -> {
throw Exception("TODO: Handle legacy closed groups.") throw Exception("TODO: Handle legacy closed groups.")

View File

@ -21,6 +21,7 @@ import org.session.libsignal.service.loki.utilities.DownloadUtilities
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.session.libsignal.utilities.Base64.* import org.session.libsignal.utilities.Base64.*
import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.createContext import org.session.libsignal.utilities.createContext
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
@ -58,6 +59,11 @@ object OpenGroupAPIV2 {
val imageID: String? val imageID: String?
) )
data class CompactPollResult(val messages: List<OpenGroupMessageV2>,
val deletions: List<Long>,
val moderators: List<String>
)
data class Request( data class Request(
val verb: HTTP.Verb, val verb: HTTP.Verb,
val room: String?, val room: String?,
@ -224,10 +230,10 @@ object OpenGroupAPIV2 {
fun send(message: OpenGroupMessageV2, room: String, server: String): Promise<OpenGroupMessageV2, Exception> { fun send(message: OpenGroupMessageV2, room: String, server: String): Promise<OpenGroupMessageV2, Exception> {
val signedMessage = message.sign() ?: return Promise.ofFail(Error.SIGNING_FAILED) val signedMessage = message.sign() ?: return Promise.ofFail(Error.SIGNING_FAILED)
val json = signedMessage.toJSON() val jsonMessage = signedMessage.toJSON()
val request = Request(verb = POST, room = room, server = server, endpoint = "messages", parameters = json) val request = Request(verb = POST, room = room, server = server, endpoint = "messages", parameters = jsonMessage)
return send(request).map(sharedContext) { return send(request).map(sharedContext) { json ->
@Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map<String, String> @Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map<String, Any>
?: throw Error.PARSING_FAILED ?: throw Error.PARSING_FAILED
OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.PARSING_FAILED OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.PARSING_FAILED
} }
@ -249,21 +255,25 @@ object OpenGroupAPIV2 {
var currentMax = lastMessageServerId var currentMax = lastMessageServerId
val messages = rawMessages.mapNotNull { json -> val messages = rawMessages.mapNotNull { json ->
val message = OpenGroupMessageV2.fromJSON(json) ?: return@mapNotNull null try {
if (message.serverID == null || message.sender.isNullOrEmpty()) return@mapNotNull null val message = OpenGroupMessageV2.fromJSON(json) ?: return@mapNotNull null
val sender = message.sender if (message.serverID == null || message.sender.isNullOrEmpty()) return@mapNotNull null
val data = decode(message.base64EncodedData) val sender = message.sender
val signature = decode(message.base64EncodedSignature) val data = decode(message.base64EncodedData)
val publicKey = sender.removing05PrefixIfNeeded().encodeToByteArray() val signature = decode(message.base64EncodedSignature)
val isValid = curve.verifySignature(publicKey, data, signature) val publicKey = Hex.fromStringCondensed(sender.removing05PrefixIfNeeded())
if (!isValid) { val isValid = curve.verifySignature(publicKey, data, signature)
Log.d("Loki", "Ignoring message with invalid signature") if (!isValid) {
return@mapNotNull null Log.d("Loki", "Ignoring message with invalid signature")
return@mapNotNull null
}
if (message.serverID > lastMessageServerId) {
currentMax = message.serverID
}
message
} catch (e: Exception) {
null
} }
if (message.serverID > lastMessageServerId) {
currentMax = message.serverID
}
message
} }
storage.setLastMessageServerId(room, server, currentMax) storage.setLastMessageServerId(room, server, currentMax)
messages messages
@ -332,6 +342,10 @@ object OpenGroupAPIV2 {
// endregion // endregion
// region General // region General
// fun getCompactPoll(): Promise<CompactPollResult, Exception> {
// val request = Request()
// }
fun getDefaultRoomsIfNeeded(): Promise<List<DefaultGroup>, Exception> { fun getDefaultRoomsIfNeeded(): Promise<List<DefaultGroup>, Exception> {
val storage = MessagingConfiguration.shared.storage val storage = MessagingConfiguration.shared.storage
storage.setOpenGroupPublicKey(DEFAULT_SERVER, DEFAULT_SERVER_PUBLIC_KEY) storage.setOpenGroupPublicKey(DEFAULT_SERVER, DEFAULT_SERVER_PUBLIC_KEY)

View File

@ -8,14 +8,14 @@ import org.session.libsignal.utilities.logging.Log
import org.whispersystems.curve25519.Curve25519 import org.whispersystems.curve25519.Curve25519
data class OpenGroupMessageV2( data class OpenGroupMessageV2(
val serverID: Long?, val serverID: Long? = null,
val sender: String?, val sender: String?,
val sentTimestamp: Long, val sentTimestamp: Long,
// The serialized protobuf in base64 encoding // The serialized protobuf in base64 encoding
val base64EncodedData: String, val base64EncodedData: String,
// When sending a message, the sender signs the serialized protobuf with their private key so that // When sending a message, the sender signs the serialized protobuf with their private key so that
// a receiving user can verify that the message wasn't tampered with. // a receiving user can verify that the message wasn't tampered with.
val base64EncodedSignature: String? val base64EncodedSignature: String? = null
) { ) {
companion object { companion object {
@ -24,10 +24,10 @@ data class OpenGroupMessageV2(
fun fromJSON(json: Map<String, Any>): OpenGroupMessageV2? { fun fromJSON(json: Map<String, Any>): OpenGroupMessageV2? {
val base64EncodedData = json["data"] as? String ?: return null val base64EncodedData = json["data"] as? String ?: return null
val sentTimestamp = json["timestamp"] as? Long ?: return null val sentTimestamp = json["timestamp"] as? Long ?: return null
val serverID = json["server_id"] as? Long val serverID = json["server_id"] as? Int
val sender = json["public_key"] as? String val sender = json["public_key"] as? String
val base64EncodedSignature = json["signature"] as? String val base64EncodedSignature = json["signature"] as? String
return OpenGroupMessageV2(serverID = serverID, return OpenGroupMessageV2(serverID = serverID?.toLong(),
sender = sender, sender = sender,
sentTimestamp = sentTimestamp, sentTimestamp = sentTimestamp,
base64EncodedData = base64EncodedData, base64EncodedData = base64EncodedData,
@ -44,7 +44,7 @@ data class OpenGroupMessageV2(
if (sender != publicKey) return null // only sign our own messages? if (sender != publicKey) return null // only sign our own messages?
val signature = try { val signature = try {
curve.calculateSignature(privateKey, Base64.decode(base64EncodedData)) curve.calculateSignature(privateKey, decode(base64EncodedData))
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Loki", "Couldn't sign OpenGroupV2Message", e) Log.e("Loki", "Couldn't sign OpenGroupV2Message", e)
return null return null

View File

@ -13,7 +13,9 @@ import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
import org.session.libsession.messaging.messages.visible.* import org.session.libsession.messaging.messages.visible.*
import org.session.libsession.messaging.opengroups.OpenGroupAPI import org.session.libsession.messaging.opengroups.OpenGroupAPI
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2
import org.session.libsession.messaging.opengroups.OpenGroupMessage import org.session.libsession.messaging.opengroups.OpenGroupMessage
import org.session.libsession.messaging.opengroups.OpenGroupMessageV2
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.snode.RawResponsePromise import org.session.libsession.snode.RawResponsePromise
@ -63,7 +65,7 @@ object MessageSender {
// Convenience // Convenience
fun send(message: Message, destination: Destination): Promise<Unit, Exception> { fun send(message: Message, destination: Destination): Promise<Unit, Exception> {
if (destination is Destination.OpenGroup) { if (destination is Destination.OpenGroup || destination is Destination.OpenGroupV2) {
return sendToOpenGroupDestination(destination, message) return sendToOpenGroupDestination(destination, message)
} }
return sendToSnodeDestination(destination, message) return sendToSnodeDestination(destination, message)
@ -91,7 +93,8 @@ object MessageSender {
when (destination) { when (destination) {
is Destination.Contact -> message.recipient = destination.publicKey is Destination.Contact -> message.recipient = destination.publicKey
is Destination.ClosedGroup -> message.recipient = destination.groupPublicKey is Destination.ClosedGroup -> message.recipient = destination.groupPublicKey
is Destination.OpenGroup -> throw Error.PreconditionFailure("Destination should not be open groups!") is Destination.OpenGroup,
is Destination.OpenGroupV2 -> throw Error.PreconditionFailure("Destination should not be open groups!")
} }
// Validate the message // Validate the message
if (!message.isValid()) { throw Error.InvalidMessage } if (!message.isValid()) { throw Error.InvalidMessage }
@ -129,7 +132,8 @@ object MessageSender {
val encryptionKeyPair = MessagingConfiguration.shared.storage.getLatestClosedGroupEncryptionKeyPair(destination.groupPublicKey)!! val encryptionKeyPair = MessagingConfiguration.shared.storage.getLatestClosedGroupEncryptionKeyPair(destination.groupPublicKey)!!
ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, encryptionKeyPair.hexEncodedPublicKey) ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, encryptionKeyPair.hexEncodedPublicKey)
} }
is Destination.OpenGroup -> throw Error.PreconditionFailure("Destination should not be open groups!") is Destination.OpenGroup,
is Destination.OpenGroupV2 -> throw Error.PreconditionFailure("Destination should not be open groups!")
} }
// Wrap the result // Wrap the result
val kind: SignalServiceProtos.Envelope.Type val kind: SignalServiceProtos.Envelope.Type
@ -143,7 +147,8 @@ object MessageSender {
kind = SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT kind = SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT
senderPublicKey = destination.groupPublicKey senderPublicKey = destination.groupPublicKey
} }
is Destination.OpenGroup -> throw Error.PreconditionFailure("Destination should not be open groups!") is Destination.OpenGroup,
is Destination.OpenGroupV2 -> throw Error.PreconditionFailure("Destination should not be open groups!")
} }
val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext) val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext)
// Calculate proof of work // Calculate proof of work
@ -207,32 +212,59 @@ object MessageSender {
deferred.reject(error) deferred.reject(error)
} }
try { try {
val server: String
val channel: Long
when (destination) { when (destination) {
is Destination.Contact -> throw Error.PreconditionFailure("Destination should not be contacts!") is Destination.Contact -> throw Error.PreconditionFailure("Destination should not be contacts!")
is Destination.ClosedGroup -> throw Error.PreconditionFailure("Destination should not be closed groups!") is Destination.ClosedGroup -> throw Error.PreconditionFailure("Destination should not be closed groups!")
is Destination.OpenGroup -> { is Destination.OpenGroup -> {
message.recipient = "${destination.server}.${destination.channel}" message.recipient = "${destination.server}.${destination.channel}"
server = destination.server val server = destination.server
channel = destination.channel val channel = destination.channel
// Validate the message
if (message !is VisibleMessage || !message.isValid()) {
throw Error.InvalidMessage
}
// Convert the message to an open group message
val openGroupMessage = OpenGroupMessage.from(message, server) ?: run {
throw Error.InvalidMessage
}
// Send the result
OpenGroupAPI.sendMessage(openGroupMessage, channel, server).success {
message.openGroupServerMessageID = it.serverID
handleSuccessfulMessageSend(message, destination)
deferred.resolve(Unit)
}.fail {
handleFailure(it)
}
}
is Destination.OpenGroupV2 -> {
message.recipient = "${destination.server}.${destination.room}"
val server = destination.server
val room = destination.room
// Validate the message
if (message !is VisibleMessage || !message.isValid()) {
throw Error.InvalidMessage
}
val proto = message.toProto()!!
val openGroupMessage = OpenGroupMessageV2(
sender = message.sender,
sentTimestamp = message.sentTimestamp!!,
base64EncodedData = Base64.encodeBytes(PushTransportDetails.getPaddedMessageBody(proto.dataMessage!!.toByteArray())),
)
OpenGroupAPIV2.send(openGroupMessage,room,server).success {
message.openGroupServerMessageID = it.serverID
handleSuccessfulMessageSend(message, destination)
deferred.resolve(Unit)
}.fail {
handleFailure(it)
}
} }
}
// Validate the message
if (message !is VisibleMessage || !message.isValid()) {
throw Error.InvalidMessage
}
// Convert the message to an open group message
val openGroupMessage = OpenGroupMessage.from(message, server) ?: kotlin.run {
throw Error.InvalidMessage
}
// Send the result
OpenGroupAPI.sendMessage(openGroupMessage, channel, server).success {
message.openGroupServerMessageID = it.serverID
handleSuccessfulMessageSend(message, destination)
deferred.resolve(Unit)
}.fail {
handleFailure(it)
} }
} catch (exception: Exception) { } catch (exception: Exception) {
handleFailure(exception) handleFailure(exception)

View File

@ -71,9 +71,6 @@ class ClosedGroupPoller {
// ignore inactive group's messages // ignore inactive group's messages
return@successBackground return@successBackground
} }
if (messages.isNotEmpty()) {
Log.d("Loki", "Received ${messages.count()} new message(s) in closed group with public key: $publicKey.")
}
messages.forEach { message -> messages.forEach { message ->
val rawMessageAsJSON = message as? Map<*, *> val rawMessageAsJSON = message as? Map<*, *>
val base64EncodedData = rawMessageAsJSON?.get("data") as? String val base64EncodedData = rawMessageAsJSON?.get("data") as? String

View File

@ -72,7 +72,6 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ
// Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below // Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below
OpenGroupAPI.getMessages(openGroup.channel, openGroup.server).successBackground { messages -> OpenGroupAPI.getMessages(openGroup.channel, openGroup.server).successBackground { messages ->
// Process messages in the background // Process messages in the background
Log.d("Loki", "received ${messages.size} messages")
messages.forEach { message -> messages.forEach { message ->
try { try {
val senderPublicKey = message.senderPublicKey val senderPublicKey = message.senderPublicKey

View File

@ -62,7 +62,6 @@ class OpenGroupV2Poller(private val openGroup: OpenGroupV2, private val executor
// Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below // Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below
OpenGroupAPIV2.getMessages(openGroup.room, openGroup.server).successBackground { messages -> OpenGroupAPIV2.getMessages(openGroup.room, openGroup.server).successBackground { messages ->
// Process messages in the background // Process messages in the background
Log.d("Loki", "received ${messages.size} messages")
messages.forEach { message -> messages.forEach { message ->
try { try {
val senderPublicKey = message.sender!! val senderPublicKey = message.sender!!

View File

@ -5,14 +5,10 @@ import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.functional.map
import nl.komponents.kovenant.then import nl.komponents.kovenant.then
import okhttp3.* import okhttp3.*
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.fileserver.FileServerAPI
import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.messaging.fileserver.FileServerAPI
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.DiffieHellman
import org.session.libsignal.service.api.crypto.ProfileCipherOutputStream import org.session.libsignal.service.api.crypto.ProfileCipherOutputStream
import org.session.libsignal.service.api.messages.SignalServiceAttachment import org.session.libsignal.service.api.messages.SignalServiceAttachment
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException
@ -22,13 +18,12 @@ import org.session.libsignal.service.internal.push.ProfileAvatarData
import org.session.libsignal.service.internal.push.PushAttachmentData import org.session.libsignal.service.internal.push.PushAttachmentData
import org.session.libsignal.service.internal.push.http.DigestingRequestBody import org.session.libsignal.service.internal.push.http.DigestingRequestBody
import org.session.libsignal.service.internal.push.http.ProfileCipherOutputStreamFactory import org.session.libsignal.service.internal.push.http.ProfileCipherOutputStreamFactory
import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.service.loki.api.utilities.HTTP import org.session.libsignal.service.loki.api.utilities.HTTP
import org.session.libsignal.service.loki.utilities.* import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.retryIfNeeded
import org.session.libsignal.utilities.* import org.session.libsignal.utilities.*
import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.logging.Log
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.OutputStream import java.io.OutputStream
@ -270,13 +265,13 @@ open class DotNetAPI {
return upload(server, request) { json -> // Retrying is handled by AttachmentUploadJob return upload(server, request) { json -> // Retrying is handled by AttachmentUploadJob
val data = json["data"] as? Map<*, *> val data = json["data"] as? Map<*, *>
if (data == null) { if (data == null) {
Log.d("Loki", "Couldn't parse attachment from: $json.") Log.e("Loki", "Couldn't parse attachment from: $json.")
throw Error.ParsingFailed throw Error.ParsingFailed
} }
val id = data["id"] as? Long ?: (data["id"] as? Int)?.toLong() ?: (data["id"] as? String)?.toLong() val id = data["id"] as? Long ?: (data["id"] as? Int)?.toLong() ?: (data["id"] as? String)?.toLong()
val url = data["url"] as? String val url = data["url"] as? String
if (id == null || url == null || url.isEmpty()) { if (id == null || url == null || url.isEmpty()) {
Log.d("Loki", "Couldn't parse upload from: $json.") Log.e("Loki", "Couldn't parse upload from: $json.")
throw Error.ParsingFailed throw Error.ParsingFailed
} }
UploadResult(id, url, file.transmittedDigest) UploadResult(id, url, file.transmittedDigest)