mirror of
				https://github.com/oxen-io/session-android.git
				synced 2025-10-31 07:21:41 +00:00 
			
		
		
		
	feat: incorporate hashes from library, more wrapper for user groups and serialization from c++
This commit is contained in:
		| @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.database | ||||
| import android.content.Context | ||||
| import androidx.core.content.contentValuesOf | ||||
| import androidx.core.database.getBlobOrNull | ||||
| import androidx.core.database.getStringOrNull | ||||
| import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper | ||||
|  | ||||
| class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(context, helper) { | ||||
| @@ -12,35 +11,32 @@ class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(co | ||||
|         private const val VARIANT = "variant" | ||||
|         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" | ||||
|  | ||||
|         const val CREATE_CONFIG_TABLE_COMMAND = | ||||
|             "CREATE TABLE $TABLE_NAME ($VARIANT TEXT NOT NULL, $PUBKEY TEXT NOT NULL, $DATA BLOB, $COMBINED_MESSAGE_HASHES TEXT, PRIMARY KEY($VARIANT, $PUBKEY));" | ||||
|             "CREATE TABLE $TABLE_NAME ($VARIANT TEXT NOT NULL, $PUBKEY TEXT NOT NULL, $DATA BLOB, PRIMARY KEY($VARIANT, $PUBKEY));" | ||||
|  | ||||
|         private const val VARIANT_AND_PUBKEY_WHERE = "$VARIANT = ? AND $PUBKEY = ?" | ||||
|     } | ||||
|  | ||||
|     fun storeConfig(variant: String, publicKey: String, data: ByteArray, hashes: List<String>) { | ||||
|     fun storeConfig(variant: String, publicKey: String, data: ByteArray) { | ||||
|         val db = writableDatabase | ||||
|         val contentValues = contentValuesOf( | ||||
|             VARIANT to variant, | ||||
|             PUBKEY to publicKey, | ||||
|             DATA to data, | ||||
|             COMBINED_MESSAGE_HASHES to hashes.joinToString(",") | ||||
|         ) | ||||
|         db.insertOrUpdate(TABLE_NAME, contentValues, VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey)) | ||||
|     } | ||||
|  | ||||
|     fun retrieveConfigAndHashes(variant: String, publicKey: String): Pair<ByteArray,List<String>>? { | ||||
|     fun retrieveConfigAndHashes(variant: String, publicKey: String): ByteArray? { | ||||
|         val db = readableDatabase | ||||
|         val query = db.query(TABLE_NAME, arrayOf(DATA, COMBINED_MESSAGE_HASHES), VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey),null, null, null) | ||||
|         val query = db.query(TABLE_NAME, arrayOf(DATA), VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey),null, null, null) | ||||
|         return query?.use { cursor -> | ||||
|             if (!cursor.moveToFirst()) return@use null | ||||
|             val bytes = cursor.getBlobOrNull(cursor.getColumnIndex(DATA)) ?: return@use null | ||||
|             val hashes = cursor.getStringOrNull(cursor.getColumnIndex(COMBINED_MESSAGE_HASHES))?.split(",") ?: emptyList() | ||||
|             bytes to hashes | ||||
|             bytes | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -47,10 +47,7 @@ class ConfigFactory(private val context: Context, | ||||
|             Log.d("Loki-DBG", "Getting user configs and hashes") | ||||
|             val userDump = configDatabase.retrieveConfigAndHashes(SharedConfigMessage.Kind.USER_PROFILE.name, publicKey) | ||||
|             _userConfig = if (userDump != null) { | ||||
|                 val (bytes, hashes) = userDump | ||||
|                 userHashes.clear() | ||||
|                 userHashes.addAll(hashes) | ||||
|                 UserProfile.newInstance(secretKey, bytes) | ||||
|                 UserProfile.newInstance(secretKey, userDump) | ||||
|             } else { | ||||
|                 UserProfile.newInstance(secretKey) | ||||
|             } | ||||
| @@ -63,10 +60,7 @@ class ConfigFactory(private val context: Context, | ||||
|             val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null | ||||
|             val contactsDump = configDatabase.retrieveConfigAndHashes(SharedConfigMessage.Kind.CONTACTS.name, publicKey) | ||||
|             _contacts = if (contactsDump != null) { | ||||
|                 val (bytes, hashes) = contactsDump | ||||
|                 contactsHashes.clear() | ||||
|                 contactsHashes.addAll(hashes) | ||||
|                 Contacts.newInstance(secretKey, bytes) | ||||
|                 Contacts.newInstance(secretKey, contactsDump) | ||||
|             } else { | ||||
|                 Contacts.newInstance(secretKey) | ||||
|             } | ||||
| @@ -79,10 +73,7 @@ class ConfigFactory(private val context: Context, | ||||
|             val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null | ||||
|             val convoDump = configDatabase.retrieveConfigAndHashes(SharedConfigMessage.Kind.CONVO_INFO_VOLATILE.name, publicKey) | ||||
|             _convoVolatileConfig = if (convoDump != null) { | ||||
|                 val (bytes, hashes) = convoDump | ||||
|                 convoHashes.clear() | ||||
|                 convoHashes.addAll(hashes) | ||||
|                 ConversationVolatileConfig.newInstance(secretKey, bytes) | ||||
|                 ConversationVolatileConfig.newInstance(secretKey, convoDump) | ||||
|             } else { | ||||
|                 ConversationVolatileConfig.newInstance(secretKey) | ||||
|             } | ||||
| @@ -94,19 +85,19 @@ class ConfigFactory(private val context: Context, | ||||
|     private fun persistUserConfigDump() = synchronized(userLock) { | ||||
|         val dumped = user?.dump() ?: return | ||||
|         val (_, publicKey) = maybeGetUserInfo() ?: return | ||||
|         configDatabase.storeConfig(SharedConfigMessage.Kind.USER_PROFILE.name, publicKey, dumped, userHashes.toList()) | ||||
|         configDatabase.storeConfig(SharedConfigMessage.Kind.USER_PROFILE.name, publicKey, dumped) | ||||
|     } | ||||
|  | ||||
|     private fun persistContactsConfigDump() = synchronized(contactsLock) { | ||||
|         val dumped = contacts?.dump() ?: return | ||||
|         val (_, publicKey) = maybeGetUserInfo() ?: return | ||||
|         configDatabase.storeConfig(SharedConfigMessage.Kind.CONTACTS.name, publicKey, dumped, contactsHashes.toList()) | ||||
|         configDatabase.storeConfig(SharedConfigMessage.Kind.CONTACTS.name, publicKey, dumped) | ||||
|     } | ||||
|  | ||||
|     private fun persistConvoVolatileConfigDump() = synchronized (convoVolatileLock) { | ||||
|         val dumped = convoVolatile?.dump() ?: return | ||||
|         val (_, publicKey) = maybeGetUserInfo() ?: return | ||||
|         configDatabase.storeConfig(SharedConfigMessage.Kind.CONVO_INFO_VOLATILE.name, publicKey, dumped, convoHashes.toList()) | ||||
|         configDatabase.storeConfig(SharedConfigMessage.Kind.CONVO_INFO_VOLATILE.name, publicKey, dumped) | ||||
|     } | ||||
|  | ||||
|     override fun persist(forConfigObject: ConfigBase) { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import android.content.Context | ||||
| import network.loki.messenger.libsession_util.util.UserPic | ||||
| import org.session.libsession.messaging.contacts.Contact | ||||
| import org.session.libsession.utilities.SSKEnvironment | ||||
| import org.session.libsession.utilities.TextSecurePreferences | ||||
| import org.session.libsession.utilities.recipients.Recipient | ||||
| import org.thoughtcrime.securesms.ApplicationContext | ||||
| import org.thoughtcrime.securesms.dependencies.ConfigFactory | ||||
| @@ -84,6 +85,7 @@ class ProfileManager(private val context: Context, private val configFactory: Co | ||||
|  | ||||
|     override fun contactUpdatedInternal(contact: Contact) { | ||||
|         val contactConfig = configFactory.contacts ?: return | ||||
|         if (contact.sessionID == TextSecurePreferences.getLocalNumber(context)) return | ||||
|         contactConfig.upsertContact(contact.sessionID) { | ||||
|             this.name = contact.name.orEmpty() | ||||
|             this.nickname = contact.nickname.orEmpty() | ||||
|   | ||||
 Submodule libsession-util/libsession-util updated: c76d7e06f6...5a7569861d
									
								
							| @@ -120,29 +120,18 @@ Java_network_loki_messenger_libsession_1util_ConfigBase_00024Companion_kindFor(J | ||||
|             return nullptr; | ||||
|     } | ||||
| } | ||||
| extern "C" | ||||
| JNIEXPORT void JNICALL | ||||
| Java_network_loki_messenger_libsession_1util_ConfigBase_removeObsoleteHashes(JNIEnv *env, | ||||
|                                                                              jobject thiz, | ||||
|                                                                              jobjectArray to_remove) { | ||||
|     auto conf = ptrToConfigBase(env, thiz); | ||||
|     size_t number = env->GetArrayLength(to_remove); | ||||
|     for (int i = 0; i < number; i++) { | ||||
|         auto jElement = (jstring) env->GetObjectArrayElement(to_remove, i); | ||||
|         auto element_as_string = env->GetStringUTFChars(jElement, nullptr); | ||||
|         // TODO: uncomment when this is re-implemented | ||||
| //        conf->confirm_removed(element_as_string); | ||||
|         env->ReleaseStringUTFChars(jElement, element_as_string); | ||||
|     } | ||||
| } | ||||
|  | ||||
| extern "C" | ||||
| JNIEXPORT jobject JNICALL | ||||
| Java_network_loki_messenger_libsession_1util_ConfigBase_obsoleteHashes(JNIEnv *env, jobject thiz) { | ||||
| Java_network_loki_messenger_libsession_1util_ConfigBase_currentHashes(JNIEnv *env, jobject thiz) { | ||||
|     auto conf = ptrToConfigBase(env, thiz); | ||||
|     jclass stack = env->FindClass("java/util/Stack"); | ||||
|     jmethodID init = env->GetMethodID(stack, "<init>", "()V"); | ||||
|     jobject our_stack = env->NewObject(stack, init); | ||||
|     jmethodID push = env->GetMethodID(stack, "push", "(Ljava/lang/Object;)Ljava/lang/Object;"); | ||||
|     // TODO: implement obsoleteHashes() | ||||
|     auto vec = conf->current_hashes(); | ||||
|     for (std::string element: vec) { | ||||
|         env->CallObjectMethod(our_stack, push, env->NewStringUTF(element.data())); | ||||
|     } | ||||
|     return our_stack; | ||||
| } | ||||
| @@ -14,7 +14,7 @@ inline session::config::Contacts *ptrToContacts(JNIEnv *env, jobject obj) { | ||||
| inline jobject serialize_contact(JNIEnv *env, session::config::contact_info info) { | ||||
|     jclass contactClass = env->FindClass("network/loki/messenger/libsession_util/util/Contact"); | ||||
|     jmethodID constructor = env->GetMethodID(contactClass, "<init>", | ||||
|                                              "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZLnetwork/loki/messenger/libsession_util/util/UserPic;)V"); | ||||
|                                              "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZLnetwork/loki/messenger/libsession_util/util/UserPic;I)V"); | ||||
|     jstring id = env->NewStringUTF(info.session_id.data()); | ||||
|     jstring name = env->NewStringUTF(info.name.data()); | ||||
|     jstring nickname = env->NewStringUTF(info.nickname.data()); | ||||
| @@ -23,14 +23,14 @@ inline jobject serialize_contact(JNIEnv *env, session::config::contact_info info | ||||
|     approvedMe = info.approved_me; | ||||
|     blocked = info.blocked; | ||||
|     jobject profilePic = util::serialize_user_pic(env, info.profile_picture); | ||||
|     jobject returnObj = env->NewObject(contactClass, constructor, id, name, nickname, approved, approvedMe, blocked, profilePic); | ||||
|     jobject returnObj = env->NewObject(contactClass, constructor, id, name, nickname, approved, approvedMe, blocked, profilePic, info.priority); | ||||
|     return returnObj; | ||||
| } | ||||
|  | ||||
| inline session::config::contact_info* deserialize_contact(JNIEnv *env, jobject info) { | ||||
|     jclass contactClass = env->FindClass("network/loki/messenger/libsession_util/util/Contact"); | ||||
|  | ||||
|     jfieldID getId, getName, getNick, getApproved, getApprovedMe, getBlocked, getUserPic; | ||||
|     jfieldID getId, getName, getNick, getApproved, getApprovedMe, getBlocked, getUserPic, getPriority; | ||||
|     getId = env->GetFieldID(contactClass, "id", "Ljava/lang/String;"); | ||||
|     getName = env->GetFieldID(contactClass, "name", "Ljava/lang/String;"); | ||||
|     getNick = env->GetFieldID(contactClass, "nickname", "Ljava/lang/String;"); | ||||
| @@ -38,11 +38,13 @@ inline session::config::contact_info* deserialize_contact(JNIEnv *env, jobject i | ||||
|     getApprovedMe = env->GetFieldID(contactClass, "approvedMe", "Z"); | ||||
|     getBlocked = env->GetFieldID(contactClass, "blocked", "Z"); | ||||
|     getUserPic = env->GetFieldID(contactClass, "profilePicture", "Lnetwork/loki/messenger/libsession_util/util/UserPic;"); | ||||
|     getPriority = env->GetFieldID(contactClass, "priority", "I"); | ||||
|     jstring name, nickname, session_id; | ||||
|     session_id = static_cast<jstring>(env->GetObjectField(info, getId)); | ||||
|     name = static_cast<jstring>(env->GetObjectField(info, getName)); | ||||
|     nickname = static_cast<jstring>(env->GetObjectField(info, getNick)); | ||||
|     bool approved, approvedMe, blocked; | ||||
|     int priority = env->GetIntField(info, getPriority); | ||||
|     approved = env->GetBooleanField(info, getApproved); | ||||
|     approvedMe = env->GetBooleanField(info, getApprovedMe); | ||||
|     blocked = env->GetBooleanField(info, getBlocked); | ||||
| @@ -86,6 +88,8 @@ inline session::config::contact_info* deserialize_contact(JNIEnv *env, jobject i | ||||
|         env->ReleaseStringUTFChars(nickname, nickname_bytes); | ||||
|     } | ||||
|  | ||||
|     contact_info->priority = priority; | ||||
|  | ||||
|     return contact_info; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -31,8 +31,6 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_00024Companion_new | ||||
|     jmethodID constructor = env->GetMethodID(contactsClass, "<init>", "(J)V"); | ||||
|     jobject newConfig = env->NewObject(contactsClass, constructor, reinterpret_cast<jlong>(user_groups)); | ||||
|  | ||||
|     user_groups->get_or_construct_legacy_group() | ||||
|  | ||||
|     return newConfig; | ||||
| } | ||||
| #pragma clang diagnostic pop | ||||
| @@ -50,7 +48,21 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getCommunityInfo(J | ||||
|                                                                                jobject thiz, | ||||
|                                                                                jstring base_url, | ||||
|                                                                                jstring room) { | ||||
|     auto conf = ptrToUserGroups(env, thiz); | ||||
|     auto base_url_bytes = env->GetStringUTFChars(base_url, nullptr); | ||||
|     auto room_bytes = env->GetStringUTFChars(room, nullptr); | ||||
|  | ||||
|     auto community = conf->get_community(base_url_bytes, room_bytes); | ||||
|  | ||||
|     jobject community_info = nullptr; | ||||
|  | ||||
|     if (community) { | ||||
|         serialize_legacy_group_info() | ||||
|     } | ||||
|  | ||||
|     // TODO: implement getCommunityInfo() | ||||
|     env->ReleaseStringUTFChars(base_url, base_url_bytes); | ||||
|     env->ReleaseStringUTFChars(room, room_bytes); | ||||
| } | ||||
|  | ||||
| extern "C" | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|  | ||||
| #include "jni.h" | ||||
| #include "util.h" | ||||
| #include "conversation.h" | ||||
| #include "session/config/user_groups.hpp" | ||||
|  | ||||
| inline session::config::UserGroups* ptrToUserGroups(JNIEnv *env, jobject obj) { | ||||
| @@ -51,4 +52,11 @@ inline jobject serialize_members(JNIEnv *env, std::map<std::string, bool> member | ||||
|     return nullptr; | ||||
| } | ||||
|  | ||||
| inline jobject serialize_community_info(JNIEnv *env, session::config::community_info info) { | ||||
|     auto priority = info.priority; | ||||
|     auto open_group = session::config::community::parse_full_url(info.full_url()); | ||||
|     auto serialized_community = serialize_open_group(env, (session::config::community) info); | ||||
|     return nullptr; | ||||
| } | ||||
|  | ||||
| #endif //SESSION_ANDROID_USER_GROUPS_H | ||||
|   | ||||
| @@ -34,8 +34,7 @@ sealed class ConfigBase(protected val /* yucky */ pointer: Long) { | ||||
|     external fun encryptionDomain(): String | ||||
|     external fun confirmPushed(seqNo: Long, newHash: String) | ||||
|     external fun merge(toMerge: Array<Pair<String,ByteArray>>): Int | ||||
|     external fun obsoleteHashes(): List<String> | ||||
|     external fun removeObsoleteHashes(toRemove: Array<String>) | ||||
|     external fun currentHashes(): List<String> | ||||
|  | ||||
|     external fun configNamespace(): Int | ||||
|  | ||||
|   | ||||
| @@ -7,5 +7,6 @@ data class Contact( | ||||
|     var approved: Boolean = false, | ||||
|     var approvedMe: Boolean = false, | ||||
|     var blocked: Boolean = false, | ||||
|     var profilePicture: UserPic = UserPic.DEFAULT | ||||
|     var profilePicture: UserPic = UserPic.DEFAULT, | ||||
|     var priority: Int | ||||
| ) | ||||
| @@ -57,6 +57,15 @@ data class ConfigurationSyncJob(val destination: Destination): Job { | ||||
|         // don't run anything if we don't need to push anything | ||||
|         if (configsRequiringPush.isEmpty()) return delegate.handleJobSucceeded(this, dispatcherName) | ||||
|  | ||||
|         // need to get the current hashes before we call `push()` | ||||
|         val toDeleteRequest = configsRequiringPush.map { base -> | ||||
|             // accumulate by adding together | ||||
|             base.currentHashes() | ||||
|         }.reduce(List<String>::plus).let { toDeleteFromAllNamespaces -> | ||||
|             if (toDeleteFromAllNamespaces.isEmpty()) null | ||||
|             else SnodeAPI.buildAuthenticatedDeleteBatchInfo(destination.destinationPublicKey(), toDeleteFromAllNamespaces) | ||||
|         } | ||||
|  | ||||
|         // allow null results here so the list index matches configsRequiringPush | ||||
|         val batchObjects: List<Pair<SharedConfigurationMessage, SnodeAPI.SnodeBatchRequestInfo>?> = configsRequiringPush.map { config -> | ||||
|             val (data, seqNo) = config.push() | ||||
| @@ -72,14 +81,6 @@ data class ConfigurationSyncJob(val destination: Destination): Job { | ||||
|             message to authenticated // to keep track of seqNo for calling confirmPushed later | ||||
|         } | ||||
|  | ||||
|         val toDeleteRequest = configsRequiringPush.map { base -> | ||||
|             base.obsoleteHashes() | ||||
|             // accumulate by adding together | ||||
|         }.reduce(List<String>::plus).let { toDeleteFromAllNamespaces -> | ||||
|             if (toDeleteFromAllNamespaces.isEmpty()) null | ||||
|             else SnodeAPI.buildAuthenticatedDeleteBatchInfo(destination.destinationPublicKey(), toDeleteFromAllNamespaces) | ||||
|         } | ||||
|  | ||||
|         if (batchObjects.any { it == null }) { | ||||
|             // stop running here, something like a signing error occurred | ||||
|             return delegate.handleJobFailedPermanently(this, dispatcherName, NullPointerException("One or more requests had a null batch request info")) | ||||
| @@ -137,8 +138,6 @@ data class ConfigurationSyncJob(val destination: Destination): Job { | ||||
|                 // confirm pushed seqno | ||||
|                 val thisSeqNo = toPushMessage.seqNo | ||||
|                 config.confirmPushed(thisSeqNo, insertHash) | ||||
|                 // wipe any of the existing hashes which we deleted (they may or may not be in this namespace) | ||||
|                 config.removeObsoleteHashes(deletedHashes.toTypedArray()) | ||||
|                 Log.d(TAG, "Successfully removed the deleted hashes from ${config.javaClass.simpleName}") | ||||
|                 // dump and write config after successful | ||||
|                 if (config.needsDump()) { // usually this will be true? | ||||
|   | ||||
| @@ -137,7 +137,7 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti | ||||
|             namespace, | ||||
|             updateLatestHash = false, | ||||
|             updateStoredHashes = false, | ||||
|         ).filter { (_, hash) -> !forConfigObject.obsoleteHashes().contains(hash) } | ||||
|         ).filter { (_, hash) -> !forConfigObject.currentHashes().contains(hash) } | ||||
|  | ||||
|         if (messages.isEmpty()) { | ||||
|             // no new messages to process | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 0x330a
					0x330a