diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index b9c680ff9c..7d38f542e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -51,6 +51,7 @@ import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.GroupMember import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroupApi +import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage @@ -881,24 +882,17 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co DatabaseComponent.get(context).groupDatabase().create(groupId, title, members, avatar, relay, admins, formationTimestamp) } - override fun createNewGroup(groupName: String, groupDescription: String, members: Set): Long? { + override suspend fun createNewGroup(groupName: String, groupDescription: String, members: Set): Long? { val userGroups = configFactory.userGroups ?: return null val ourSessionId = getUserPublicKey() ?: return null val userKp = MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return null val group = userGroups.createGroup() + val adminKey = group.adminKey userGroups.set(group) val groupInfo = configFactory.getOrConstructGroupInfoConfig(group.groupSessionId) ?: return null val groupMembers = configFactory.getOrConstructGroupMemberConfig(group.groupSessionId) ?: return null - val groupKeys = GroupKeysConfig.newInstance( - userKp.secretKey.asBytes, - Hex.fromStringCondensed(group.groupSessionId.publicKey), - group.adminKey, - info = groupInfo, - members = groupMembers - ) - with (groupInfo) { setName(groupName) setDescription(groupDescription) @@ -908,10 +902,25 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co LibSessionGroupMember(ourSessionId, "admin", admin = true) ) - // Test the sending - val userGroupsUpdate = + val groupKeys = GroupKeysConfig.newInstance( + userKp.secretKey.asBytes, + Hex.fromStringCondensed(group.groupSessionId.publicKey), + group.adminKey, + info = groupInfo, + members = groupMembers + ) - TODO() + // Test the sending + try { + MessageSender.sendConfig(Destination.ClosedGroup(group.groupSessionId.hexString()), groupInfo, adminKey) + } catch (e: Exception) { + Log.e("Group Config", e) + Log.e("Group Config", "Deleting group from our group") + // delete the group from user groups + userGroups.erase(group) + } + + return 0 } override fun createInitialConfigGroup(groupPublicKey: String, name: String, members: Map, formationTimestamp: Long, encryptionKeyPair: ECKeyPair) { diff --git a/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt b/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt index 1eb00e7853..b1aed05b81 100644 --- a/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt +++ b/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt @@ -203,7 +203,7 @@ class UserGroupsConfig(pointer: Long): ConfigBase(pointer) { external fun getOrConstructLegacyGroupInfo(sessionId: String): GroupInfo.LegacyGroupInfo external fun getOrConstructClosedGroup(sessionId: String): GroupInfo.ClosedGroupInfo external fun set(groupInfo: GroupInfo) - external fun erase(communityInfo: GroupInfo) + external fun erase(groupInfo: GroupInfo) external fun eraseCommunity(baseCommunityInfo: BaseCommunityInfo): Boolean external fun eraseCommunity(server: String, room: String): Boolean external fun eraseLegacyGroup(sessionId: String): Boolean diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index 77c88c3e70..5eabe48ef1 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -155,7 +155,7 @@ interface StorageProtocol { fun setExpirationTimer(address: String, duration: Int) // Closed Groups - fun createNewGroup(groupName: String, groupDescription: String, members: Set): Long? + suspend fun createNewGroup(groupName: String, groupDescription: String, members: Set): Long? fun getMembers(groupPublicKey: String): List // Groups diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 098abbda0e..52787e7abb 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -1,5 +1,7 @@ package org.session.libsession.messaging.sending_receiving +import androidx.annotation.WorkerThread +import network.loki.messenger.libsession_util.ConfigBase import nl.komponents.kovenant.Promise import nl.komponents.kovenant.deferred import org.session.libsession.messaging.MessagingModuleConfiguration @@ -49,6 +51,7 @@ object MessageSender { object NoUserED25519KeyPair : Error("Couldn't find user ED25519 key pair.") object SigningFailed : Error("Couldn't sign message.") object EncryptionFailed : Error("Couldn't encrypt message.") + data class InvalidDestination(val destination: Destination): Error("Can't send this way to $destination") // Closed groups object NoThread : Error("Couldn't find a thread associated with the given group public key.") @@ -70,6 +73,25 @@ object MessageSender { } } + // New closed groups and configs not requiring additional overhead (already handled by libsession) + @WorkerThread + fun sendConfig(destination: Destination, config: ConfigBase, signingKey: ByteArray): Result { + if (destination !is Destination.ClosedGroup) return Result.failure(Error.InvalidDestination(destination)) + + val (bytes, _) = config.push() + + val testTtl = 30 * 24 * 60 * 60 * 1000L // 30 days + + // handle this error thrown case + val response = SnodeAPI.sendMessage(destination, bytes, testTtl, signingKey, config.configNamespace()) + + Log.d("Send Config", "Response is good") + + val hash = response["hash"] as? String ?: return Result.failure(Error("No returned hash of string type")) + + return Result.success(hash) + } + // One-on-One Chats & Closed Groups @Throws(Exception::class) fun buildWrappedMessageToSnode(destination: Destination, message: Message, isSyncMessage: Boolean): SnodeMessage { @@ -213,11 +235,6 @@ object MessageSender { else -> false } - /* - if (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) { - shouldNotify = true - } - */ if (shouldNotify) { val notifyPNServerJob = NotifyPNServerJob(snodeMessage) JobQueue.shared.add(notifyPNServerJob) diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index b1a274773c..87828e6820 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -3,6 +3,7 @@ package org.session.libsession.snode import android.os.Build +import androidx.annotation.WorkerThread import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.SodiumAndroid import com.goterl.lazysodium.exceptions.SodiumException @@ -18,6 +19,8 @@ import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.task import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.messaging.messages.Destination +import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsignal.crypto.getRandomElement import org.session.libsignal.database.LokiAPIDatabaseProtocol @@ -630,6 +633,40 @@ object SnodeAPI { } } + @WorkerThread + fun sendMessage(destination: Destination, rawMessage: ByteArray, ttl: Long, signingKey: ByteArray, namespace: Int): RawResponse { + val pubKey = when (destination) { + is Destination.ClosedGroup -> destination.publicKey + else -> throw MessageSender.Error.InvalidDestination(destination) + } + + return retryIfNeeded(maxRetryCount) { + val timestamp = nowWithOffset + + val signature = ByteArray(Sign.BYTES) + // assume namespace here is non-zero, as zero namespace doesn't require auth + val verificationData = "store$namespace$timestamp".toByteArray() + try { + sodium.cryptoSignDetached(signature, verificationData, verificationData.size.toLong(), signingKey) + } catch (exception: Exception) { + return@retryIfNeeded Promise.ofFail(Error.SigningFailed) + } + + val parameters = mapOf( + "pubKey" to pubKey, + "data" to Base64.encodeBytes(rawMessage), + "ttl" to ttl.toString(), + "timestamp" to timestamp.toString(), + "sig_timestamp" to timestamp.toString(), + "signature" to Base64.encodeBytes(verificationData) + ) + + getSingleTargetSnode(pubKey).bind { targetSnode -> + invoke(Snode.Method.SendMessage, targetSnode, parameters, pubKey) + } + }.get() + } + fun sendMessage(message: SnodeMessage, requiresAuth: Boolean = false, namespace: Int = 0): RawResponsePromise { val destination = message.recipient return retryIfNeeded(maxRetryCount) {