feat: add finishing of group push for initial group creation and saving group configs to DB. re-implement key signing messages properly for the signing config which doesn't override the config base
This commit is contained in:
parent
2527ac2e21
commit
6067916da9
|
@ -4,6 +4,8 @@ import android.content.Context
|
|||
import androidx.core.content.contentValuesOf
|
||||
import androidx.core.database.getBlobOrNull
|
||||
import androidx.core.database.getLongOrNull
|
||||
import androidx.sqlite.db.transaction
|
||||
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
|
||||
class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(context, helper) {
|
||||
|
@ -20,6 +22,10 @@ class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(co
|
|||
"CREATE TABLE $TABLE_NAME ($VARIANT TEXT NOT NULL, $PUBKEY TEXT NOT NULL, $DATA BLOB, $TIMESTAMP INTEGER NOT NULL DEFAULT 0, PRIMARY KEY($VARIANT, $PUBKEY));"
|
||||
|
||||
private const val VARIANT_AND_PUBKEY_WHERE = "$VARIANT = ? AND $PUBKEY = ?"
|
||||
|
||||
val KEYS_VARIANT = SharedConfigMessage.Kind.ENCRYPTION_KEYS.name
|
||||
val INFO_VARIANT = SharedConfigMessage.Kind.CLOSED_GROUP_INFO.name
|
||||
val MEMBER_VARIANT = SharedConfigMessage.Kind.CLOSED_GROUP_MEMBERS.name
|
||||
}
|
||||
|
||||
fun storeConfig(variant: String, publicKey: String, data: ByteArray, timestamp: Long) {
|
||||
|
@ -33,6 +39,39 @@ class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(co
|
|||
db.insertOrUpdate(TABLE_NAME, contentValues, VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey))
|
||||
}
|
||||
|
||||
fun storeGroupConfigs(publicKey: String, keysConfig: ByteArray, infoConfig: ByteArray, memberConfig: ByteArray, timestamp: Long) {
|
||||
val db = writableDatabase
|
||||
db.transaction {
|
||||
val keyContent = contentValuesOf(
|
||||
VARIANT to KEYS_VARIANT,
|
||||
PUBKEY to publicKey,
|
||||
DATA to keysConfig,
|
||||
TIMESTAMP to timestamp
|
||||
)
|
||||
db.insertOrUpdate(TABLE_NAME, keyContent, VARIANT_AND_PUBKEY_WHERE,
|
||||
arrayOf(KEYS_VARIANT, publicKey)
|
||||
)
|
||||
val infoContent = contentValuesOf(
|
||||
VARIANT to INFO_VARIANT,
|
||||
PUBKEY to publicKey,
|
||||
DATA to infoConfig,
|
||||
TIMESTAMP to timestamp
|
||||
)
|
||||
db.insertOrUpdate(TABLE_NAME, infoContent, VARIANT_AND_PUBKEY_WHERE,
|
||||
arrayOf(INFO_VARIANT, publicKey)
|
||||
)
|
||||
val memberContent = contentValuesOf(
|
||||
VARIANT to MEMBER_VARIANT,
|
||||
PUBKEY to publicKey,
|
||||
DATA to memberConfig,
|
||||
TIMESTAMP to timestamp
|
||||
)
|
||||
db.insertOrUpdate(TABLE_NAME, memberContent, VARIANT_AND_PUBKEY_WHERE,
|
||||
arrayOf(MEMBER_VARIANT, publicKey)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun retrieveConfigAndHashes(variant: String, publicKey: String): ByteArray? {
|
||||
val db = readableDatabase
|
||||
val query = db.query(TABLE_NAME, arrayOf(DATA), VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey),null, null, null)
|
||||
|
|
|
@ -51,7 +51,6 @@ 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
|
||||
|
@ -62,7 +61,9 @@ import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
|||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||
import org.session.libsession.messaging.utilities.UpdateMessageData
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsession.snode.RawResponse
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.snode.SnodeMessage
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
||||
import org.session.libsession.utilities.GroupRecord
|
||||
|
@ -882,16 +883,18 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
|
|||
DatabaseComponent.get(context).groupDatabase().create(groupId, title, members, avatar, relay, admins, formationTimestamp)
|
||||
}
|
||||
|
||||
override suspend fun createNewGroup(groupName: String, groupDescription: String, members: Set<SessionId>): Long? {
|
||||
val userGroups = configFactory.userGroups ?: return null
|
||||
val ourSessionId = getUserPublicKey() ?: return null
|
||||
val userKp = MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return null
|
||||
override fun createNewGroup(groupName: String, groupDescription: String, members: Set<SessionId>): Optional<Boolean> {
|
||||
val userGroups = configFactory.userGroups ?: return Optional.absent()
|
||||
val ourSessionId = getUserPublicKey() ?: return Optional.absent()
|
||||
val userKp = MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return Optional.absent()
|
||||
|
||||
val groupCreationTimestamp = SnodeAPI.nowWithOffset
|
||||
|
||||
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 groupInfo = configFactory.getOrConstructGroupInfoConfig(group.groupSessionId) ?: return Optional.absent()
|
||||
val groupMembers = configFactory.getOrConstructGroupMemberConfig(group.groupSessionId) ?: return Optional.absent()
|
||||
|
||||
with (groupInfo) {
|
||||
setName(groupName)
|
||||
|
@ -910,9 +913,68 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
|
|||
members = groupMembers
|
||||
)
|
||||
|
||||
val newGroupRecipient = group.groupSessionId.hexString()
|
||||
val configTtl = 1 * 24 * 60 * 60 * 1000L // TODO: just testing here, 1 day so we don't fill large space on network
|
||||
// Test the sending
|
||||
val keyPush = groupKeys.pendingPush() ?: return Optional.absent()
|
||||
val keysSnodeMessage = SnodeMessage(
|
||||
newGroupRecipient,
|
||||
Base64.encodeBytes(keyPush),
|
||||
configTtl,
|
||||
groupCreationTimestamp
|
||||
)
|
||||
val keysBatchInfo = SnodeAPI.buildAuthenticatedStoreBatchInfo(
|
||||
GroupKeysConfig.storageNamespace(),
|
||||
keysSnodeMessage,
|
||||
adminKey
|
||||
)
|
||||
|
||||
val (infoPush, infoSeqNo) = groupInfo.push()
|
||||
val infoSnodeMessage = SnodeMessage(
|
||||
newGroupRecipient,
|
||||
Base64.encodeBytes(keyPush),
|
||||
configTtl,
|
||||
groupCreationTimestamp
|
||||
)
|
||||
val infoBatchInfo = SnodeAPI.buildAuthenticatedStoreBatchInfo(
|
||||
groupInfo.configNamespace(),
|
||||
infoSnodeMessage,
|
||||
adminKey
|
||||
)
|
||||
|
||||
val (memberPush, memberSeqNo) = groupMembers.push()
|
||||
val memberSnodeMessage = SnodeMessage(
|
||||
newGroupRecipient,
|
||||
Base64.encodeBytes(memberPush),
|
||||
configTtl,
|
||||
groupCreationTimestamp
|
||||
)
|
||||
val memberBatchInfo = SnodeAPI.buildAuthenticatedStoreBatchInfo(
|
||||
groupMembers.configNamespace(),
|
||||
memberSnodeMessage,
|
||||
adminKey
|
||||
)
|
||||
|
||||
try {
|
||||
MessageSender.sendConfig(Destination.ClosedGroup(group.groupSessionId.hexString()), groupInfo, adminKey)
|
||||
val snode = SnodeAPI.getSingleTargetSnode(newGroupRecipient).get()
|
||||
val response = SnodeAPI.getRawBatchResponse(
|
||||
snode,
|
||||
newGroupRecipient,
|
||||
listOf(keysBatchInfo, infoBatchInfo, memberBatchInfo),
|
||||
true
|
||||
).get()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val responseList = (response["results"] as List<RawResponse>)
|
||||
|
||||
val keyResponse = responseList[0]
|
||||
val infoResponse = responseList[1]
|
||||
val memberResponse = responseList[2]
|
||||
// TODO: check response success
|
||||
configFactory.saveGroupConfigs(groupKeys, groupInfo, groupMembers) // now check poller to be all
|
||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
|
||||
Log.d("Group Config", "Saved group config for $newGroupRecipient")
|
||||
return Optional.of(true)
|
||||
} catch (e: Exception) {
|
||||
Log.e("Group Config", e)
|
||||
Log.e("Group Config", "Deleting group from our group")
|
||||
|
@ -920,7 +982,7 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
|
|||
userGroups.erase(group)
|
||||
}
|
||||
|
||||
return 0
|
||||
return Optional.absent()
|
||||
}
|
||||
|
||||
override fun createInitialConfigGroup(groupPublicKey: String, name: String, members: Map<String, Boolean>, formationTimestamp: Long, encryptionKeyPair: ECKeyPair) {
|
||||
|
|
|
@ -343,4 +343,13 @@ class ConfigFactory(
|
|||
return (changeTimestampMs >= (lastUpdateTimestampMs - ConfigFactory.configChangeBufferPeriod))
|
||||
}
|
||||
|
||||
override fun saveGroupConfigs(
|
||||
groupKeys: GroupKeysConfig,
|
||||
groupInfo: GroupInfoConfig,
|
||||
groupMembers: GroupMembersConfig
|
||||
) {
|
||||
val pubKey = groupInfo.id().hexString()
|
||||
val timestamp = SnodeAPI.nowWithOffset
|
||||
configDatabase.storeGroupConfigs(pubKey, groupKeys.dump(), groupInfo.dump(), groupMembers.dump(), timestamp)
|
||||
}
|
||||
}
|
|
@ -44,6 +44,7 @@ import org.session.libsession.utilities.recipients.Recipient
|
|||
import org.session.libsignal.utilities.SessionId
|
||||
import org.thoughtcrime.securesms.conversation.start.NewConversationDelegate
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.ui.AppTheme
|
||||
import org.thoughtcrime.securesms.ui.EditableAvatar
|
||||
import org.thoughtcrime.securesms.ui.NavigationBar
|
||||
import org.thoughtcrime.securesms.ui.PreviewTheme
|
||||
|
@ -87,20 +88,22 @@ class CreateGroupFragment : Fragment() {
|
|||
fun CreateGroupScreen(viewState: ViewState,
|
||||
createGroupState: CreateGroupState,
|
||||
modifier: Modifier = Modifier) {
|
||||
CreateGroup(
|
||||
viewState,
|
||||
createGroupState,
|
||||
onCreate = { newGroup ->
|
||||
// launch something to create here
|
||||
viewModel.tryCreateGroup(newGroup)
|
||||
},
|
||||
onClose = {
|
||||
delegate.onDialogClosePressed()
|
||||
},
|
||||
onBack = {
|
||||
delegate.onDialogBackPressed()
|
||||
}
|
||||
)
|
||||
AppTheme {
|
||||
CreateGroup(
|
||||
viewState,
|
||||
createGroupState,
|
||||
onCreate = { newGroup ->
|
||||
// launch something to create here
|
||||
viewModel.tryCreateGroup(newGroup)
|
||||
},
|
||||
onClose = {
|
||||
delegate.onDialogClosePressed()
|
||||
},
|
||||
onBack = {
|
||||
delegate.onDialogBackPressed()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class ViewState(
|
||||
|
@ -221,7 +224,8 @@ fun ClosedGroupPreview(
|
|||
) {
|
||||
PreviewTheme(themeResId) {
|
||||
CreateGroup(
|
||||
CreateGroupState("Group Name", "Test Group Description", emptySet()),
|
||||
viewState = CreateGroupFragment.ViewState(false, null, null),
|
||||
createGroupState = CreateGroupState("Group Name", "Test Group Description", emptySet()),
|
||||
onCreate = {},
|
||||
onClose = {},
|
||||
onBack = {},
|
||||
|
|
|
@ -2,6 +2,13 @@
|
|||
#include "group_info.h"
|
||||
#include "group_members.h"
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_network_loki_messenger_libsession_1util_GroupKeysConfig_00024Companion_storageNamespace(JNIEnv* env,
|
||||
jobject thiz) {
|
||||
return (jint)session::config::Namespace::GroupKeys;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_network_loki_messenger_libsession_1util_GroupKeysConfig_00024Companion_newInstance(JNIEnv *env,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "util.h"
|
||||
|
||||
inline session::config::groups::Members* ptrToMembers(JNIEnv* env, jobject obj) {
|
||||
jclass configClass = env->FindClass("network/loki/messenger/libsession_util/GroupMemberConfig");
|
||||
jclass configClass = env->FindClass("network/loki/messenger/libsession_util/GroupMembersConfig");
|
||||
jfieldID pointerField = env->GetFieldID(configClass, "pointer", "J");
|
||||
return (session::config::groups::Members*) env->GetLongField(obj, pointerField);
|
||||
}
|
||||
|
|
|
@ -288,8 +288,10 @@ class GroupKeysConfig(pointer: Long): ConfigBase(pointer), Closeable {
|
|||
info: GroupInfoConfig,
|
||||
members: GroupMembersConfig
|
||||
): GroupKeysConfig
|
||||
external fun storageNamespace(): Int
|
||||
}
|
||||
external fun groupKeys(): Stack<ByteArray>
|
||||
external fun keyDump(): ByteArray
|
||||
external fun loadKey(hash: String,
|
||||
data: ByteArray,
|
||||
msgId: ByteArray,
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.session.libsignal.crypto.ecc.ECKeyPair
|
|||
import org.session.libsignal.messages.SignalServiceAttachmentPointer
|
||||
import org.session.libsignal.messages.SignalServiceGroup
|
||||
import org.session.libsignal.utilities.SessionId
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import network.loki.messenger.libsession_util.util.Contact as LibSessionContact
|
||||
|
||||
interface StorageProtocol {
|
||||
|
@ -155,7 +156,7 @@ interface StorageProtocol {
|
|||
fun setExpirationTimer(address: String, duration: Int)
|
||||
|
||||
// Closed Groups
|
||||
suspend fun createNewGroup(groupName: String, groupDescription: String, members: Set<SessionId>): Long?
|
||||
fun createNewGroup(groupName: String, groupDescription: String, members: Set<SessionId>): Optional<Boolean>
|
||||
fun getMembers(groupPublicKey: String): List<network.loki.messenger.libsession_util.util.GroupMember>
|
||||
|
||||
// Groups
|
||||
|
|
|
@ -69,7 +69,6 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
|
|||
// return a list of batch request objects
|
||||
val snodeMessage = MessageSender.buildWrappedMessageToSnode(destination, message, true)
|
||||
val authenticated = SnodeAPI.buildAuthenticatedStoreBatchInfo(
|
||||
destination.destinationPublicKey(),
|
||||
config.configNamespace(),
|
||||
snodeMessage
|
||||
) ?: return@map null // this entry will be null otherwise
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
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
|
||||
|
@ -73,25 +71,6 @@ 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<String> {
|
||||
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 {
|
||||
|
|
|
@ -19,8 +19,6 @@ 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
|
||||
|
@ -219,7 +217,7 @@ object SnodeAPI {
|
|||
}
|
||||
}
|
||||
|
||||
internal fun getSingleTargetSnode(publicKey: String): Promise<Snode, Exception> {
|
||||
fun getSingleTargetSnode(publicKey: String): Promise<Snode, Exception> {
|
||||
// SecureRandom() should be cryptographically secure
|
||||
return getSwarm(publicKey).map { it.shuffled(SecureRandom()).random() }
|
||||
}
|
||||
|
@ -374,7 +372,7 @@ object SnodeAPI {
|
|||
return invoke(Snode.Method.Retrieve, snode, parameters, publicKey)
|
||||
}
|
||||
|
||||
fun buildAuthenticatedStoreBatchInfo(publicKey: String, namespace: Int, message: SnodeMessage): SnodeBatchRequestInfo? {
|
||||
fun buildAuthenticatedStoreBatchInfo(namespace: Int, message: SnodeMessage, signingKey: ByteArray, ed25519PubKey: String? = null): SnodeBatchRequestInfo {
|
||||
val params = mutableMapOf<String, Any>()
|
||||
// load the message data params into the sub request
|
||||
// currently loads:
|
||||
|
@ -388,13 +386,6 @@ object SnodeAPI {
|
|||
// used for sig generation since it is also the value used in timestamp parameter
|
||||
val messageTimestamp = message.timestamp
|
||||
|
||||
val userEd25519KeyPair = try {
|
||||
MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return null
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
|
||||
val ed25519PublicKey = userEd25519KeyPair.publicKey.asHexString
|
||||
val signature = ByteArray(Sign.BYTES)
|
||||
val verificationData = "store$namespace$messageTimestamp".toByteArray()
|
||||
try {
|
||||
|
@ -402,13 +393,15 @@ object SnodeAPI {
|
|||
signature,
|
||||
verificationData,
|
||||
verificationData.size.toLong(),
|
||||
userEd25519KeyPair.secretKey.asBytes
|
||||
signingKey
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("Loki", "Signing data failed with user secret key", e)
|
||||
}
|
||||
// timestamp already set
|
||||
params["pubkey_ed25519"] = ed25519PublicKey
|
||||
if (ed25519PubKey != null) {
|
||||
params["pubkey_ed25519"] = ed25519PubKey
|
||||
}
|
||||
params["signature"] = Base64.encodeBytes(signature)
|
||||
return SnodeBatchRequestInfo(
|
||||
Snode.Method.SendMessage.rawValue,
|
||||
|
@ -417,6 +410,16 @@ object SnodeAPI {
|
|||
)
|
||||
}
|
||||
|
||||
fun buildAuthenticatedStoreBatchInfo(namespace: Int, message: SnodeMessage): SnodeBatchRequestInfo? {
|
||||
val userEd25519KeyPair = try {
|
||||
MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return null
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
val ed25519PublicKey = userEd25519KeyPair.publicKey.asHexString
|
||||
return buildAuthenticatedStoreBatchInfo(namespace, message, userEd25519KeyPair.secretKey.asBytes, ed25519PublicKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Message hashes can be shared across multiple namespaces (for a single public key destination)
|
||||
* @param publicKey the destination's identity public key to delete from (05...)
|
||||
|
@ -634,11 +637,8 @@ 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)
|
||||
}
|
||||
fun sendAuthenticatedMessage(message: SnodeMessage, signingKey: ByteArray, namespace: Int): RawResponse {
|
||||
val pubKey = message.recipient
|
||||
|
||||
return retryIfNeeded(maxRetryCount) {
|
||||
val timestamp = nowWithOffset
|
||||
|
@ -654,8 +654,7 @@ object SnodeAPI {
|
|||
|
||||
val parameters = mapOf(
|
||||
"pubKey" to pubKey,
|
||||
"data" to Base64.encodeBytes(rawMessage),
|
||||
"ttl" to ttl.toString(),
|
||||
"data" to message.data,
|
||||
"timestamp" to timestamp.toString(),
|
||||
"sig_timestamp" to timestamp.toString(),
|
||||
"signature" to Base64.encodeBytes(verificationData)
|
||||
|
|
|
@ -25,6 +25,11 @@ interface ConfigFactoryProtocol {
|
|||
|
||||
fun conversationInConfig(publicKey: String?, groupPublicKey: String?, openGroupId: String?, visibleOnly: Boolean): Boolean
|
||||
fun canPerformChange(variant: String, publicKey: String, changeTimestampMs: Long): Boolean
|
||||
fun saveGroupConfigs(
|
||||
groupKeys: GroupKeysConfig,
|
||||
groupInfo: GroupInfoConfig,
|
||||
groupMembers: GroupMembersConfig
|
||||
)
|
||||
}
|
||||
|
||||
interface ConfigFactoryUpdateListener {
|
||||
|
|
Loading…
Reference in New Issue