mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-28 20:45:17 +00:00
refactor: config database changes, new protos, adding in support for config base namespace queries
This commit is contained in:
parent
00fe77af8a
commit
8bea5e73e6
@ -8,34 +8,39 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
|||||||
class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(context, helper) {
|
class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(context, helper) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val KEY = "key"
|
private const val VARIANT = "variant"
|
||||||
private const val VALUE = "value"
|
private const val PUBKEY = "publicKey"
|
||||||
|
private const val DATA = "data"
|
||||||
|
private const val COMBINED_MESSAGE_HASHES = "combined_message_hashes"
|
||||||
|
|
||||||
private const val TABLE_NAME = "configs_table"
|
private const val TABLE_NAME = "configs_table"
|
||||||
|
|
||||||
const val CREATE_CONFIG_TABLE_COMMAND = "CREATE TABLE $TABLE_NAME ($KEY TEXT NOT NULL, $VALUE BLOB NOT NULL, PRIMARY KEY($KEY));"
|
const val CREATE_CONFIG_TABLE_COMMAND =
|
||||||
private const val KEY_WHERE = "$KEY = ?"
|
"CREATE TABLE $TABLE_NAME ($VARIANT TEXT NOT NULL, $PUBKEY TEXT NOT NULL, $DATA BLOB, $COMBINED_MESSAGE_HASHES TEXT, PRIMARY KEY($VARIANT, $PUBKEY));"
|
||||||
|
private const val VARIANT_WHERE = "$VARIANT = ?"
|
||||||
|
private const val VARIANT_AND_PUBKEY_WHERE = "$VARIANT = ? AND $PUBKEY = ?"
|
||||||
|
|
||||||
const val USER_KEY = "user"
|
const val USER_KEY = "user"
|
||||||
const val CONTACTS_KEY = "contacts"
|
const val CONTACTS_KEY = "contacts"
|
||||||
// conversations use publicKey / URL
|
// conversations use publicKey / URL
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeConfig(key: String, data: ByteArray) {
|
fun storeConfig(variant: String, publicKey: String, data: ByteArray) {
|
||||||
val db = writableDatabase
|
val db = writableDatabase
|
||||||
val contentValues = contentValuesOf(
|
val contentValues = contentValuesOf(
|
||||||
KEY to key,
|
VARIANT to variant,
|
||||||
VALUE to data
|
PUBKEY to publicKey,
|
||||||
|
DATA to data
|
||||||
)
|
)
|
||||||
db.insertOrUpdate(TABLE_NAME, contentValues, KEY_WHERE, arrayOf(key))
|
db.insertOrUpdate(TABLE_NAME, contentValues, VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun retrieveConfig(key: String): ByteArray? {
|
fun retrieveConfig(variant: String, publicKey: String): ByteArray? {
|
||||||
val db = readableDatabase
|
val db = readableDatabase
|
||||||
val query = db.query(TABLE_NAME, arrayOf(VALUE), KEY_WHERE, arrayOf(key),null, null, null)
|
val query = db.query(TABLE_NAME, arrayOf(DATA), VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey),null, null, null)
|
||||||
val bytes = query?.use { cursor ->
|
val bytes = query?.use { cursor ->
|
||||||
if (!cursor.moveToFirst()) return null
|
if (!cursor.moveToFirst()) return null
|
||||||
cursor.getBlobOrNull(cursor.getColumnIndex(VALUE))
|
cursor.getBlobOrNull(cursor.getColumnIndex(DATA))
|
||||||
}
|
}
|
||||||
return bytes
|
return bytes
|
||||||
}
|
}
|
||||||
|
@ -2,57 +2,54 @@ package org.thoughtcrime.securesms.util
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import org.session.libsession.messaging.messages.Destination
|
|
||||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
|
||||||
import org.session.libsession.utilities.Address
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
|
||||||
|
|
||||||
object ConfigurationMessageUtilities {
|
object ConfigurationMessageUtilities {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun syncConfigurationIfNeeded(context: Context) {
|
fun syncConfigurationIfNeeded(context: Context) {
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
|
return
|
||||||
val lastSyncTime = TextSecurePreferences.getLastConfigurationSyncTime(context)
|
// val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
|
||||||
val now = System.currentTimeMillis()
|
// val lastSyncTime = TextSecurePreferences.getLastConfigurationSyncTime(context)
|
||||||
if (now - lastSyncTime < 7 * 24 * 60 * 60 * 1000) return
|
// val now = System.currentTimeMillis()
|
||||||
val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
|
// if (now - lastSyncTime < 7 * 24 * 60 * 60 * 1000) return
|
||||||
!recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
|
// val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
|
||||||
}.map { recipient ->
|
// !recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
|
||||||
ConfigurationMessage.Contact(
|
// }.map { recipient ->
|
||||||
publicKey = recipient.address.serialize(),
|
// ConfigurationMessage.Contact(
|
||||||
name = recipient.name!!,
|
// publicKey = recipient.address.serialize(),
|
||||||
profilePicture = recipient.profileAvatar,
|
// name = recipient.name!!,
|
||||||
profileKey = recipient.profileKey,
|
// profilePicture = recipient.profileAvatar,
|
||||||
isApproved = recipient.isApproved,
|
// profileKey = recipient.profileKey,
|
||||||
isBlocked = recipient.isBlocked,
|
// isApproved = recipient.isApproved,
|
||||||
didApproveMe = recipient.hasApprovedMe()
|
// isBlocked = recipient.isBlocked,
|
||||||
)
|
// didApproveMe = recipient.hasApprovedMe()
|
||||||
}
|
// )
|
||||||
val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return
|
// }
|
||||||
MessageSender.send(configurationMessage, Address.fromSerialized(userPublicKey))
|
// val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return
|
||||||
TextSecurePreferences.setLastConfigurationSyncTime(context, now)
|
// MessageSender.send(configurationMessage, Address.fromSerialized(userPublicKey))
|
||||||
|
// TextSecurePreferences.setLastConfigurationSyncTime(context, now)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forceSyncConfigurationNowIfNeeded(context: Context): Promise<Unit, Exception> {
|
fun forceSyncConfigurationNowIfNeeded(context: Context): Promise<Unit, Exception> {
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofSuccess(Unit)
|
return Promise.ofSuccess(Unit)
|
||||||
val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
|
// val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofSuccess(Unit)
|
||||||
!recipient.isGroupRecipient && !recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
|
// val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
|
||||||
}.map { recipient ->
|
// !recipient.isGroupRecipient && !recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
|
||||||
ConfigurationMessage.Contact(
|
// }.map { recipient ->
|
||||||
publicKey = recipient.address.serialize(),
|
// ConfigurationMessage.Contact(
|
||||||
name = recipient.name!!,
|
// publicKey = recipient.address.serialize(),
|
||||||
profilePicture = recipient.profileAvatar,
|
// name = recipient.name!!,
|
||||||
profileKey = recipient.profileKey,
|
// profilePicture = recipient.profileAvatar,
|
||||||
isApproved = recipient.isApproved,
|
// profileKey = recipient.profileKey,
|
||||||
isBlocked = recipient.isBlocked,
|
// isApproved = recipient.isApproved,
|
||||||
didApproveMe = recipient.hasApprovedMe()
|
// isBlocked = recipient.isBlocked,
|
||||||
)
|
// didApproveMe = recipient.hasApprovedMe()
|
||||||
}
|
// )
|
||||||
val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return Promise.ofSuccess(Unit)
|
// }
|
||||||
val promise = MessageSender.send(configurationMessage, Destination.from(Address.fromSerialized(userPublicKey)))
|
// val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return Promise.ofSuccess(Unit)
|
||||||
TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis())
|
// val promise = MessageSender.send(configurationMessage, Destination.from(Address.fromSerialized(userPublicKey)))
|
||||||
return promise
|
// TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis())
|
||||||
|
// return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -86,3 +86,9 @@ Java_network_loki_messenger_libsession_1util_ConfigBase_merge___3B(JNIEnv *env,
|
|||||||
}
|
}
|
||||||
#pragma clang diagnostic pop
|
#pragma clang diagnostic pop
|
||||||
}
|
}
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jint JNICALL
|
||||||
|
Java_network_loki_messenger_libsession_1util_ConfigBase_configNamespace(JNIEnv *env, jobject thiz) {
|
||||||
|
auto conf = ptrToConfigBase(env, thiz);
|
||||||
|
return (std::int16_t) conf->storage_namespace();
|
||||||
|
}
|
@ -21,6 +21,8 @@ sealed class ConfigBase(protected val /* yucky */ pointer: Long) {
|
|||||||
external fun confirmPushed(seqNo: Long)
|
external fun confirmPushed(seqNo: Long)
|
||||||
external fun merge(toMerge: Array<ByteArray>): Int
|
external fun merge(toMerge: Array<ByteArray>): Int
|
||||||
|
|
||||||
|
external fun configNamespace(): Int
|
||||||
|
|
||||||
// Singular merge
|
// Singular merge
|
||||||
external fun merge(toMerge: ByteArray): Int
|
external fun merge(toMerge: ByteArray): Int
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package org.session.libsession.messaging.sending_receiving.pollers
|
package org.session.libsession.messaging.sending_receiving.pollers
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import nl.komponents.kovenant.Deferred
|
import nl.komponents.kovenant.Deferred
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.deferred
|
import nl.komponents.kovenant.deferred
|
||||||
@ -100,10 +104,24 @@ class Poller(private val configFactory: ConfigFactoryProtocol) {
|
|||||||
|
|
||||||
private fun poll(snode: Snode, deferred: Deferred<Unit, Exception>): Promise<Unit, Exception> {
|
private fun poll(snode: Snode, deferred: Deferred<Unit, Exception>): Promise<Unit, Exception> {
|
||||||
if (!hasStarted) { return Promise.ofFail(PromiseCanceledException()) }
|
if (!hasStarted) { return Promise.ofFail(PromiseCanceledException()) }
|
||||||
return SnodeAPI.getRawMessages(snode, userPublicKey).bind { rawResponse ->
|
return task {
|
||||||
|
runBlocking(Dispatchers.IO) {
|
||||||
|
// get user profile namespace
|
||||||
|
val deferredUserConfig = configFactory.userConfig?.let { currentUserConfig ->
|
||||||
|
async {
|
||||||
|
try {
|
||||||
|
val currentNetworkConfig = SnodeAPI.getRawMessages(snode, userPublicKey, namespace = currentUserConfig.configNamespace()).get()
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("Poller", "Error getting current user config from network", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: CompletableDeferred(null)
|
||||||
|
|
||||||
|
SnodeAPI.getRawMessages(snode, userPublicKey).bind { rawResponse ->
|
||||||
isCaughtUp = true
|
isCaughtUp = true
|
||||||
if (deferred.promise.isDone()) {
|
if (deferred.promise.isDone()) {
|
||||||
task { Unit } // The long polling connection has been canceled; don't recurse
|
return@bind Promise.ofSuccess(Unit)
|
||||||
} else {
|
} else {
|
||||||
val messages = SnodeAPI.parseRawMessagesResponse(rawResponse, snode, userPublicKey)
|
val messages = SnodeAPI.parseRawMessagesResponse(rawResponse, snode, userPublicKey)
|
||||||
val parameters = messages.map { (envelope, serverHash) ->
|
val parameters = messages.map { (envelope, serverHash) ->
|
||||||
@ -117,6 +135,9 @@ class Poller(private val configFactory: ConfigFactoryProtocol) {
|
|||||||
poll(snode, deferred)
|
poll(snode, deferred)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
@ -310,14 +310,15 @@ object SnodeAPI {
|
|||||||
fun getRawMessages(snode: Snode, publicKey: String, requiresAuth: Boolean = true, namespace: Int = 0): RawResponsePromise {
|
fun getRawMessages(snode: Snode, publicKey: String, requiresAuth: Boolean = true, namespace: Int = 0): RawResponsePromise {
|
||||||
// Get last message hash
|
// Get last message hash
|
||||||
val lastHashValue = database.getLastMessageHashValue(snode, publicKey, namespace) ?: ""
|
val lastHashValue = database.getLastMessageHashValue(snode, publicKey, namespace) ?: ""
|
||||||
val parameters = mutableMapOf<String,Any>(
|
val parameters = mutableMapOf<String, Any>(
|
||||||
"pubKey" to publicKey,
|
"pubKey" to publicKey,
|
||||||
"last_hash" to lastHashValue,
|
"last_hash" to lastHashValue,
|
||||||
)
|
)
|
||||||
// Construct signature
|
// Construct signature
|
||||||
if (requiresAuth) {
|
if (requiresAuth) {
|
||||||
val userED25519KeyPair = try {
|
val userED25519KeyPair = try {
|
||||||
MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return Promise.ofFail(Error.NoKeyPair)
|
MessagingModuleConfiguration.shared.getUserED25519KeyPair()
|
||||||
|
?: return Promise.ofFail(Error.NoKeyPair)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Loki", "Error getting KeyPair", e)
|
Log.e("Loki", "Error getting KeyPair", e)
|
||||||
return Promise.ofFail(Error.NoKeyPair)
|
return Promise.ofFail(Error.NoKeyPair)
|
||||||
@ -329,7 +330,12 @@ object SnodeAPI {
|
|||||||
if (namespace != 0) "retrieve$namespace$timestamp".toByteArray()
|
if (namespace != 0) "retrieve$namespace$timestamp".toByteArray()
|
||||||
else "retrieve$timestamp".toByteArray()
|
else "retrieve$timestamp".toByteArray()
|
||||||
try {
|
try {
|
||||||
sodium.cryptoSignDetached(signature, verificationData, verificationData.size.toLong(), userED25519KeyPair.secretKey.asBytes)
|
sodium.cryptoSignDetached(
|
||||||
|
signature,
|
||||||
|
verificationData,
|
||||||
|
verificationData.size.toLong(),
|
||||||
|
userED25519KeyPair.secretKey.asBytes
|
||||||
|
)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
return Promise.ofFail(Error.SigningFailed)
|
return Promise.ofFail(Error.SigningFailed)
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ message Content {
|
|||||||
optional DataExtractionNotification dataExtractionNotification = 8;
|
optional DataExtractionNotification dataExtractionNotification = 8;
|
||||||
optional UnsendRequest unsendRequest = 9;
|
optional UnsendRequest unsendRequest = 9;
|
||||||
optional MessageRequestResponse messageRequestResponse = 10;
|
optional MessageRequestResponse messageRequestResponse = 10;
|
||||||
|
optional SharedConfigMessage sharedConfigMessage = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message KeyPair {
|
message KeyPair {
|
||||||
@ -236,6 +237,25 @@ message MessageRequestResponse {
|
|||||||
required bool isApproved = 1; // Whether the request was approved
|
required bool isApproved = 1; // Whether the request was approved
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SharedConfigMessage {
|
||||||
|
enum Kind {
|
||||||
|
USER_PROFILE = 1;
|
||||||
|
CONTACTS = 2;
|
||||||
|
CONVERSATION_INFO = 3;
|
||||||
|
LEGACY_CLOSED_GROUPS = 4;
|
||||||
|
CLOSED_GROUP_INFO = 5;
|
||||||
|
CLOSED_GROUP_MEMBERS = 6;
|
||||||
|
ENCRYPTION_KEYS = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @required
|
||||||
|
required Kind kind = 1;
|
||||||
|
// @required
|
||||||
|
required int64 seqno = 2;
|
||||||
|
// @required
|
||||||
|
required bytes data = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message ReceiptMessage {
|
message ReceiptMessage {
|
||||||
|
|
||||||
enum Type {
|
enum Type {
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user