From e419024f6bd3a38e84351422947f2d79b052f3b3 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Wed, 4 Oct 2023 09:59:10 +1100 Subject: [PATCH] feat: add linux native library support for libsession to enable junit testing (might not be the best way) --- .../securesms/database/Storage.kt | 12 ++--- .../securesms/dependencies/ConfigFactory.kt | 15 ++++++ .../securesms/groups/CreateGroupViewModel.kt | 8 +-- .../securesms/groups/ClosedGroupViewTests.kt | 49 ++++++++++++++++++- libsession-util/src/main/cpp/CMakeLists.txt | 22 ++++++--- libsession-util/src/main/cpp/user_groups.cpp | 3 -- libsession-util/src/main/cpp/util.h | 13 ++--- .../utilities/ConfigFactoryProtocol.kt | 5 ++ 8 files changed, 98 insertions(+), 29 deletions(-) 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 978024909..4a0f8ba88 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -21,7 +21,6 @@ import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.avatars.AvatarHelper import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.BlindedIdMapping -import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentUploadJob @@ -920,7 +919,6 @@ open class Storage( val userGroups = configFactory.userGroups ?: return Optional.absent() val convoVolatile = configFactory.convoVolatile ?: return Optional.absent() val ourSessionId = getUserPublicKey() ?: return Optional.absent() - val userKp = MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return Optional.absent() val groupCreationTimestamp = SnodeAPI.nowWithOffset @@ -939,13 +937,11 @@ open class Storage( LibSessionGroupMember(ourSessionId, "admin", admin = true) ) - val groupKeys = GroupKeysConfig.newInstance( - userKp.secretKey.asBytes, - Hex.fromStringCondensed(group.groupSessionId.publicKey), - adminKey, + members.forEach { groupMembers.set(LibSessionGroupMember(it.hexString(), "member", invitePending = true)) } + + val groupKeys = configFactory.constructGroupKeysConfig(group.groupSessionId, info = groupInfo, - members = groupMembers - ) + members = groupMembers) ?: return Optional.absent() 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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt index 3a86e3743..f0316d601 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt @@ -230,6 +230,21 @@ class ConfigFactory( ) } + override fun constructGroupKeysConfig( + groupSessionId: SessionId, + info: GroupInfoConfig, + members: GroupMembersConfig + ): GroupKeysConfig? = getGroupAuthInfo(groupSessionId)?.let { (sk, _) -> + val (userSk, _) = maybeGetUserInfo() ?: return null + GroupKeysConfig.newInstance( + userSk, + Hex.fromStringCondensed(groupSessionId.hexString()), + sk, + info = info, + members = members + ) + } + override fun getUserConfigs(): List = listOfNotNull(user, contacts, convoVolatile, userGroups) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt index c13f4b6a3..096f869ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt @@ -48,18 +48,18 @@ class CreateGroupViewModel @Inject constructor( val members = createGroupState.members // do some validation + // need a name if (name.isEmpty()) { _viewState.postValue( CreateGroupFragment.ViewState(false, R.string.error) ) return null } - - if (members.isEmpty()) { + // TODO: need at least two members + if (members.size <= 1) { _viewState.postValue( - CreateGroupFragment.ViewState(false, R.string.error) + CreateGroupFragment.ViewState(false, R.string.activity_create_closed_group_not_enough_group_members_error) ) - return null } // make a group diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/ClosedGroupViewTests.kt b/app/src/test/java/org/thoughtcrime/securesms/groups/ClosedGroupViewTests.kt index 29ed3e3f6..420f45b6a 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/ClosedGroupViewTests.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/ClosedGroupViewTests.kt @@ -1,21 +1,42 @@ package org.thoughtcrime.securesms.groups +import android.content.Context import androidx.arch.core.executor.testing.InstantTaskExecutorRule import junit.framework.TestCase.assertNotNull import kotlinx.coroutines.ExperimentalCoroutinesApi +import network.loki.messenger.libsession_util.util.Sodium +import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.junit.MockitoJUnitRunner +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.utilities.Hex +import org.session.libsignal.utilities.IdPrefix +import org.session.libsignal.utilities.SessionId import org.thoughtcrime.securesms.MainCoroutineRule +import org.thoughtcrime.securesms.database.ConfigDatabase import org.thoughtcrime.securesms.database.Storage +import org.thoughtcrime.securesms.dependencies.ConfigFactory @OptIn(ExperimentalCoroutinesApi::class) @RunWith(MockitoJUnitRunner::class) class ClosedGroupViewTests { + companion object { + private const val OTHER_ID = "051000000000000000000000000000000000000000000000000000000000000000" + } + + private val seed = + Hex.fromStringCondensed("0123456789abcdef0123456789abcdef00000000000000000000000000000000") + private val keyPair = Sodium.ed25519KeyPair(seed) + private val userSessionId = SessionId(IdPrefix.STANDARD, Sodium.ed25519PkToCurve25519(keyPair.pubKey)) + @ExperimentalCoroutinesApi @get:Rule var mainCoroutineRule = MainCoroutineRule() @@ -24,7 +45,22 @@ class ClosedGroupViewTests { var taskRule = InstantTaskExecutorRule() @Mock lateinit var textSecurePreferences: TextSecurePreferences - @Mock lateinit var storage: Storage + lateinit var storage: Storage + + @Before + fun setup() { + whenever(textSecurePreferences.getLocalNumber()).thenReturn(userSessionId.hexString()) + val context = mock() + val emptyDb = mock { db -> + whenever(db.retrieveConfigAndHashes(any(), any())).thenReturn(byteArrayOf()) + } + val overridenStorage = Storage(mock(), mock(), ConfigFactory(context, emptyDb) { + keyPair.secretKey to userSessionId.hexString() + }, mock()) + storage = spy(overridenStorage) { storage -> + whenever(storage.createNewGroup(any(), any(), any())).thenCallRealMethod() + } + } @Test fun `Should error on empty name`() { @@ -50,6 +86,17 @@ class ClosedGroupViewTests { assertNotNull(viewModel.viewState.value?.error) } + @Test + fun `Should work with valid name and members`() { + val viewModel = createViewModel() + val state = CreateGroupState( + groupName = "group", + groupDescription = "", + members = emptySet() + ) + assertNotNull(viewModel.tryCreateGroup(state)) + } + private fun createViewModel() = CreateGroupViewModel(textSecurePreferences, storage) } \ No newline at end of file diff --git a/libsession-util/src/main/cpp/CMakeLists.txt b/libsession-util/src/main/cpp/CMakeLists.txt index 52e36fbfc..6859825ca 100644 --- a/libsession-util/src/main/cpp/CMakeLists.txt +++ b/libsession-util/src/main/cpp/CMakeLists.txt @@ -42,18 +42,25 @@ add_library( # Sets the name of the library. # Provides a relative path to your source file(s). ${SOURCES}) +if (LINUX) + message("Linux machine detected") + set(JAVA_INCLUDE_PATH "$ENV{JAVA_HOME}/include;$ENV{JAVA_HOME}/include/linux") + find_package(JNI REQUIRED) + include_directories(${JAVA_INCLUDE_PATH}) +endif() + # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. -find_library( # Sets the name of the path variable. - log-lib - - # Specifies the name of the NDK library that - # you want CMake to locate. - log) +#find_library( # Sets the name of the path variable. +# log-lib +# +# # Specifies the name of the NDK library that +# # you want CMake to locate. +# log) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this @@ -66,4 +73,5 @@ target_link_libraries( # Specifies the target library. libsession::crypto # Links the target library to the log library # included in the NDK. - ${log-lib}) + ) + #${log-lib}) diff --git a/libsession-util/src/main/cpp/user_groups.cpp b/libsession-util/src/main/cpp/user_groups.cpp index c0de2746f..254655e2f 100644 --- a/libsession-util/src/main/cpp/user_groups.cpp +++ b/libsession-util/src/main/cpp/user_groups.cpp @@ -203,7 +203,6 @@ inline jobject iterator_as_java_stack(JNIEnv *env, const session::config::UserGr } else if (auto* community = std::get_if(&item)) { serialized = serialize_community_info(env, *community); } else if (auto* closed = std::get_if(&item)) { - LOGD("Item is closed group"); serialized = serialize_closed_group_info(env, *closed); } if (serialized != nullptr) { @@ -219,8 +218,6 @@ JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_UserGroupsConfig_all(JNIEnv *env, jobject thiz) { std::lock_guard lock{util::util_mutex_}; auto conf = ptrToUserGroups(env, thiz); - bool isFin = conf->begin().done(); - LOGD("Group iterator has more: %d", isFin); jobject all_stack = iterator_as_java_stack(env, conf->begin(), conf->end()); return all_stack; } diff --git a/libsession-util/src/main/cpp/util.h b/libsession-util/src/main/cpp/util.h index a87b6c561..e6d16d9bd 100644 --- a/libsession-util/src/main/cpp/util.h +++ b/libsession-util/src/main/cpp/util.h @@ -2,6 +2,7 @@ #define SESSION_ANDROID_UTIL_H #include +#include #include #include #include "session/types.hpp" @@ -11,13 +12,13 @@ #include "session/config/profile_pic.hpp" #include "session/config/user_groups.hpp" #include "session/config/expiring.hpp" -#include +//#include -#define LOG_TAG "libsession-jni" -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) -#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__) -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) -#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +//#define LOG_TAG "libsession-jni" +//#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) +//#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__) +//#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) +//#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) namespace util { extern std::mutex util_mutex_; diff --git a/libsession/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt b/libsession/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt index 7d9c33ff8..cda8e6731 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt @@ -34,6 +34,11 @@ interface ConfigFactoryProtocol { ) fun scheduleUpdate(destination: Destination) + fun constructGroupKeysConfig( + groupSessionId: SessionId, + info: GroupInfoConfig, + members: GroupMembersConfig + ): GroupKeysConfig? } interface ConfigFactoryUpdateListener {