diff --git a/build.gradle.kts b/build.gradle.kts index b72fb0b..eb3bad5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,4 +2,5 @@ plugins { alias(libs.plugins.android.library) apply false alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlinx.serialization) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 69add3c..5912915 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -agp = "8.13.0" -kotlin = "2.2.20" +agp = "8.13.1" +kotlin = "2.2.21" [libraries] junit = { module = "junit:junit", version = "4.13.2" } @@ -8,8 +8,9 @@ androidx-test-runner = { module = "androidx.test:runner", version = "1.7.0" } androidx-test-rules = { module = "androidx.test:rules", version = "1.7.0" } androidx-test-ext = { module = "androidx.test.ext:junit", version = "1.3.0" } androidx-annotations = { module = "androidx.annotation:annotation", version = "1.9.1" } +kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version = "1.9.0" } [plugins] android-library = { id = "com.android.library", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } - +kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 87a9668..1ebac53 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlinx.serialization) id("maven-publish") } @@ -119,4 +120,5 @@ dependencies { androidTestImplementation(libs.androidx.test.ext) implementation(libs.androidx.annotations) + implementation(libs.kotlinx.serialization.core) } diff --git a/library/src/androidTest/kotlin/network/loki/messenger/libsession_util/protocol/BackendRequestsTest.kt b/library/src/androidTest/kotlin/network/loki/messenger/libsession_util/protocol/BackendRequestsTest.kt new file mode 100644 index 0000000..03233ca --- /dev/null +++ b/library/src/androidTest/kotlin/network/loki/messenger/libsession_util/protocol/BackendRequestsTest.kt @@ -0,0 +1,17 @@ +package network.loki.messenger.libsession_util.protocol + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import network.loki.messenger.libsession_util.pro.BackendRequests +import org.junit.Assert.assertNotNull +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class BackendRequestsTest { + + @Test + fun getProviderMetadataWorks() { + val metadata = BackendRequests.getPaymentProviderMetadata(BackendRequests.PAYMENT_PROVIDER_GOOGLE_PLAY) + assertNotNull(metadata) + } +} \ No newline at end of file diff --git a/library/src/androidTest/kotlin/network/loki/messenger/libsession_util/protocol/SessionProtocolTest.kt b/library/src/androidTest/kotlin/network/loki/messenger/libsession_util/protocol/SessionProtocolTest.kt new file mode 100644 index 0000000..ff6c1c2 --- /dev/null +++ b/library/src/androidTest/kotlin/network/loki/messenger/libsession_util/protocol/SessionProtocolTest.kt @@ -0,0 +1,23 @@ +package network.loki.messenger.libsession_util.protocol + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SessionProtocolTest { + + @Test + fun proFeaturesForMessageWorks() { + val proposedFeatures = ProFeatures.from(listOf(ProFeature.PRO_BADGE)) + val result = SessionProtocol.proFeaturesForMessage( + messageBody = "Hello, Session Pro!", + proposedFeatures + ) + + assertEquals(ProFeaturesForMsg.Status.Success, result.status) + assertEquals(19, result.codepointCount) + assertEquals(proposedFeatures, result.features) + } +} \ No newline at end of file diff --git a/library/src/main/cpp/CMakeLists.txt b/library/src/main/cpp/CMakeLists.txt index f475972..f49bbfa 100644 --- a/library/src/main/cpp/CMakeLists.txt +++ b/library/src/main/cpp/CMakeLists.txt @@ -95,6 +95,8 @@ set(SOURCES attachments.cpp webp_utils.cpp gif_utils.cpp + pro_backend.cpp + pro_proof_util.cpp ${cgif_SOURCE_DIR}/src/cgif.c ${cgif_SOURCE_DIR}/src/cgif_rgb.c @@ -158,4 +160,6 @@ target_link_libraries( # Specifies the target library. webpdemux webpdecoder yuv + nlohmann_json + oxenc ) \ No newline at end of file diff --git a/library/src/main/cpp/attachments.cpp b/library/src/main/cpp/attachments.cpp index 109c256..f54be4d 100644 --- a/library/src/main/cpp/attachments.cpp +++ b/library/src/main/cpp/attachments.cpp @@ -1,6 +1,7 @@ #include #include #include "jni_utils.h" +#include "util.h" using namespace session::attachment; using namespace jni_utils; @@ -49,7 +50,7 @@ Java_network_loki_messenger_libsession_1util_encrypt_Attachments_encryptBytes(JN ); - return util::bytes_from_span(env, std::span(reinterpret_cast(key.data()), key.size())); + return util::bytes_from_span(env, std::span(reinterpret_cast(key.data()), key.size())).release(); }); } diff --git a/library/src/main/cpp/blinded_key.cpp b/library/src/main/cpp/blinded_key.cpp index 28ccd77..ddf015a 100644 --- a/library/src/main/cpp/blinded_key.cpp +++ b/library/src/main/cpp/blinded_key.cpp @@ -17,7 +17,7 @@ Java_network_loki_messenger_libsession_1util_util_BlindKeyAPI_blindVersionKeyPai jbyteArray ed25519_secret_key) { return jni_utils::run_catching_cxx_exception_or_throws(env, [=] { const auto [pk, sk] = session::blind_version_key_pair(util::vector_from_bytes(env, ed25519_secret_key)); - return jni_utils::new_key_pair(env, util::bytes_from_span(env, pk), util::bytes_from_span(env, sk)); + return jni_utils::new_key_pair(env, util::bytes_from_span(env, pk).get(), util::bytes_from_span(env, sk).get()); }); } extern "C" @@ -32,7 +32,7 @@ Java_network_loki_messenger_libsession_1util_util_BlindKeyAPI_blindVersionSign(J session::Platform::android, timestamp ); - return util::bytes_from_vector(env, bytes); + return util::bytes_from_vector(env, bytes).release(); }); } @@ -53,7 +53,7 @@ Java_network_loki_messenger_libsession_1util_util_BlindKeyAPI_blindVersionSignRe jni_utils::JavaStringRef(env, path).view(), body ? std::make_optional(jni_utils::JavaByteArrayRef(env, body).get()) : std::nullopt ); - return util::bytes_from_vector(env, bytes); + return util::bytes_from_vector(env, bytes).release(); }); } @@ -68,7 +68,7 @@ Java_network_loki_messenger_libsession_1util_util_BlindKeyAPI_blind15KeyPair(JNI jni_utils::JavaByteArrayRef(env, ed25519_secret_key).get(), jni_utils::JavaByteArrayRef(env, server_pub_key).get() ); - return jni_utils::new_key_pair(env, util::bytes_from_span(env, pk), util::bytes_from_span(env, sk)); + return jni_utils::new_key_pair(env, util::bytes_from_span(env, pk).get(), util::bytes_from_span(env, sk).get()); }); } @@ -84,7 +84,7 @@ Java_network_loki_messenger_libsession_1util_util_BlindKeyAPI_blind15Sign(JNIEnv jni_utils::JavaStringRef(env, server_pub_key).view(), jni_utils::JavaByteArrayRef(env, message).get() ); - return util::bytes_from_vector(env, data); + return util::bytes_from_vector(env, data).release(); }); } @@ -128,9 +128,9 @@ Java_network_loki_messenger_libsession_1util_util_BlindKeyAPI_blind25Id(JNIEnv * jstring session_id, jstring server_pub_key) { return jni_utils::run_catching_cxx_exception_or_throws(env, [=]() -> jstring { - return util::jstringFromOptional(env, session::blind25_id( + return jni_utils::jstring_from_optional(env, session::blind25_id( jni_utils::JavaStringRef(env, session_id).view(), jni_utils::JavaStringRef(env, server_pub_key).view() - )); + )).release(); }); } \ No newline at end of file diff --git a/library/src/main/cpp/config_base.cpp b/library/src/main/cpp/config_base.cpp index ae23c72..5c07ec4 100644 --- a/library/src/main/cpp/config_base.cpp +++ b/library/src/main/cpp/config_base.cpp @@ -2,6 +2,43 @@ #include "util.h" #include "jni_utils.h" + +std::pair> extractHashAndData(JNIEnv *env, jobject kotlin_pair) { + jni_utils::JavaLocalRef pair(env, env->GetObjectClass(kotlin_pair)); + jfieldID first = env->GetFieldID(pair.get(), "first", "Ljava/lang/Object;"); + jfieldID second = env->GetFieldID(pair.get(), "second", "Ljava/lang/Object;"); + auto hash_as_jstring = jni_utils::JavaLocalRef(env, reinterpret_cast(env->GetObjectField(kotlin_pair, first))); + auto data_as_jbytes = jni_utils::JavaLocalRef(env, reinterpret_cast(env->GetObjectField(kotlin_pair, second))); + + return std::make_pair( + std::string(jni_utils::JavaStringRef(env, hash_as_jstring.get()).view()), + jni_utils::JavaByteArrayRef(env, data_as_jbytes.get()).copy() + ); +} + +struct JavaConfigClassInfo : public jni_utils::JavaClassInfo { + jfieldID pointer_field; + + JavaConfigClassInfo(JNIEnv *env, jobject obj) + :JavaClassInfo(env, obj), + pointer_field(env->GetFieldID(java_class, "pointer", "J")) {} + + static const JavaConfigClassInfo& get(JNIEnv *env, jobject obj) { + static JavaConfigClassInfo class_info(env, obj); + return class_info; + } +}; + +session::config::ConfigBase* ptrToConfigBase(JNIEnv *env, jobject obj) { + return reinterpret_cast( + env->GetLongField(obj, JavaConfigClassInfo::get(env, obj).pointer_field)); +} + +session::config::ConfigSig* ptrToConfigSig(JNIEnv* env, jobject obj) { + return reinterpret_cast( + env->GetLongField(obj, JavaConfigClassInfo::get(env, obj).pointer_field)); +} + extern "C" { JNIEXPORT jboolean JNICALL Java_network_loki_messenger_libsession_1util_ConfigBase_dirty(JNIEnv *env, jobject thiz) { @@ -33,9 +70,14 @@ Java_network_loki_messenger_libsession_1util_ConfigBase_push(JNIEnv *env, jobjec jobject obsoleteHashes = jni_utils::jstring_list_from_collection(env, to_delete); - jclass returnObjectClass = env->FindClass("network/loki/messenger/libsession_util/util/ConfigPush"); - jmethodID methodId = env->GetMethodID(returnObjectClass, "", "(Ljava/util/List;JLjava/util/List;)V"); - return env->NewObject(returnObjectClass, methodId, messages, static_cast(seq_no), obsoleteHashes); + static jni_utils::BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/util/ConfigPush", + "(Ljava/util/List;JLjava/util/List;)V" + ); + + return env->NewObject(class_info.java_class, class_info.constructor, + messages, static_cast(seq_no), obsoleteHashes); }); } @@ -49,8 +91,7 @@ JNIEXPORT jbyteArray JNICALL Java_network_loki_messenger_libsession_1util_ConfigBase_dump(JNIEnv *env, jobject thiz) { auto config = ptrToConfigBase(env, thiz); auto dumped = config->dump(); - jbyteArray bytes = util::bytes_from_vector(env, dumped); - return bytes; + return util::bytes_from_vector(env, dumped).release(); } JNIEXPORT jstring JNICALL @@ -96,34 +137,6 @@ Java_network_loki_messenger_libsession_1util_ConfigBase_merge___3Lkotlin_Pair_2( #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(); -} -extern "C" -JNIEXPORT jclass JNICALL -Java_network_loki_messenger_libsession_1util_ConfigBase_00024Companion_kindFor(JNIEnv *env, - jobject thiz, - jint config_namespace) { - auto user_class = env->FindClass("network/loki/messenger/libsession_util/UserProfile"); - auto contact_class = env->FindClass("network/loki/messenger/libsession_util/Contacts"); - auto convo_volatile_class = env->FindClass("network/loki/messenger/libsession_util/ConversationVolatileConfig"); - auto group_list_class = env->FindClass("network/loki/messenger/libsession_util/UserGroupsConfig"); - switch (config_namespace) { - case (int)session::config::Namespace::UserProfile: - return user_class; - case (int)session::config::Namespace::Contacts: - return contact_class; - case (int)session::config::Namespace::ConvoInfoVolatile: - return convo_volatile_class; - case (int)session::config::Namespace::UserGroups: - return group_list_class; - default: - return nullptr; - } -} extern "C" JNIEXPORT jobject JNICALL diff --git a/library/src/main/cpp/config_base.h b/library/src/main/cpp/config_base.h index 5eebd45..5fec845 100644 --- a/library/src/main/cpp/config_base.h +++ b/library/src/main/cpp/config_base.h @@ -1,36 +1,11 @@ #ifndef SESSION_ANDROID_CONFIG_BASE_H #define SESSION_ANDROID_CONFIG_BASE_H -#include "session/config/base.hpp" -#include "util.h" -#include "jni_utils.h" -#include -#include -#include - -inline session::config::ConfigBase* ptrToConfigBase(JNIEnv *env, jobject obj) { - auto baseClass = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/ConfigBase")); - jfieldID pointerField = env->GetFieldID(baseClass.get(), "pointer", "J"); - return (session::config::ConfigBase*) env->GetLongField(obj, pointerField); -} +#include -inline std::pair> extractHashAndData(JNIEnv *env, jobject kotlin_pair) { - auto pair = jni_utils::JavaLocalRef(env, env->FindClass("kotlin/Pair")); - jfieldID first = env->GetFieldID(pair.get(), "first", "Ljava/lang/Object;"); - jfieldID second = env->GetFieldID(pair.get(), "second", "Ljava/lang/Object;"); - auto hash_as_jstring = jni_utils::JavaLocalRef(env, reinterpret_cast(env->GetObjectField(kotlin_pair, first))); - auto data_as_jbytes = jni_utils::JavaLocalRef(env, reinterpret_cast(env->GetObjectField(kotlin_pair, second))); - - return std::make_pair( - std::string(jni_utils::JavaStringRef(env, hash_as_jstring.get()).view()), - jni_utils::JavaByteArrayRef(env, data_as_jbytes.get()).copy() - ); -} +#include -inline session::config::ConfigSig* ptrToConfigSig(JNIEnv* env, jobject obj) { - auto sigClass = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/ConfigSig")); - jfieldID pointerField = env->GetFieldID(sigClass.get(), "pointer", "J"); - return (session::config::ConfigSig*) env->GetLongField(obj, pointerField); -} +session::config::ConfigBase* ptrToConfigBase(JNIEnv *env, jobject obj); +session::config::ConfigSig* ptrToConfigSig(JNIEnv* env, jobject obj); #endif \ No newline at end of file diff --git a/library/src/main/cpp/contacts.cpp b/library/src/main/cpp/contacts.cpp index 51d504d..b596fc8 100644 --- a/library/src/main/cpp/contacts.cpp +++ b/library/src/main/cpp/contacts.cpp @@ -1,27 +1,115 @@ -#include "contacts.h" +#include +#include + #include "util.h" #include "jni_utils.h" -session::config::Contacts *ptrToContacts(JNIEnv *env, jobject obj) { - auto contactsClass = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/Contacts")); +using namespace jni_utils; + +static session::config::Contacts *ptrToContacts(JNIEnv *env, jobject obj) { + auto contactsClass = JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/Contacts")); jfieldID pointerField = env->GetFieldID(contactsClass.get(), "pointer", "J"); return (session::config::Contacts *) env->GetLongField(obj, pointerField); } +static JavaLocalRef serialize_contact(JNIEnv *env, const session::config::contact_info &info) { + static BasicJavaClassInfo class_info( + env, "network/loki/messenger/libsession_util/util/Contact", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZLnetwork/loki/messenger/libsession_util/util/UserPic;JJJLnetwork/loki/messenger/libsession_util/util/ExpiryMode;J)V"); + + jobject returnObj = env->NewObject(class_info.java_class, + class_info.constructor, + JavaLocalRef(env, env->NewStringUTF(info.session_id.data())).get(), + JavaLocalRef(env, env->NewStringUTF(info.name.data())).get(), + JavaLocalRef(env, env->NewStringUTF(info.nickname.data())).get(), + (jboolean) info.approved, + (jboolean) info.approved_me, + (jboolean) info.blocked, + util::serialize_user_pic(env, info.profile_picture).get(), + (jlong) info.created, + (jlong) (info.profile_updated.time_since_epoch().count()), + (jlong) info.priority, + util::serialize_expiry(env, info.exp_mode, info.exp_timer).get(), + (jlong) info.pro_features); + return {env, returnObj}; +} + +session::config::contact_info deserialize_contact(JNIEnv *env, jobject info, session::config::Contacts *conf) { + struct ClassInfo : JavaClassInfo { + jmethodID get_id; + jmethodID get_name; + jmethodID get_nick; + jmethodID get_approved; + jmethodID get_approved_me; + jmethodID get_blocked; + jmethodID get_user_pic; + jmethodID get_priority; + jmethodID get_expiry; + jmethodID get_profile_updated; + jmethodID get_pro_features; + + ClassInfo(JNIEnv *env, jobject obj): + JavaClassInfo(env, obj), + get_id(env->GetMethodID(java_class, "getId", "()Ljava/lang/String;")), + get_name(env->GetMethodID(java_class, "getName", "()Ljava/lang/String;")), + get_nick(env->GetMethodID(java_class, "getNickname", "()Ljava/lang/String;")), + get_approved(env->GetMethodID(java_class, "getApproved", "()Z")), + get_approved_me(env->GetMethodID(java_class, "getApprovedMe", "()Z")), + get_blocked(env->GetMethodID(java_class, "getBlocked", "()Z")), + get_user_pic(env->GetMethodID(java_class, "getProfilePicture", "()Lnetwork/loki/messenger/libsession_util/util/UserPic;")), + get_priority(env->GetMethodID(java_class, "getPriority", "()J")), + get_expiry(env->GetMethodID(java_class, "getExpiryMode", "()Lnetwork/loki/messenger/libsession_util/util/ExpiryMode;")), + get_profile_updated(env->GetMethodID(java_class, "getProfileUpdatedEpochSeconds", "()J")), + get_pro_features(env->GetMethodID(java_class, "getProFeaturesRaw", "()J")) {} + }; + + static ClassInfo class_info(env, info); + + JavaLocalRef account_id(env, static_cast(env->CallObjectMethod(info, class_info.get_id))); + JavaLocalRef name(env, static_cast(env->CallObjectMethod(info, class_info.get_name))); + JavaLocalRef nickname(env, static_cast(env->CallObjectMethod(info, class_info.get_nick))); + JavaLocalRef user_pic(env, env->CallObjectMethod(info, class_info.get_user_pic)); + JavaLocalRef expiry_mode(env, env->CallObjectMethod(info, class_info.get_expiry)); + + auto expiry_pair = util::deserialize_expiry(env, expiry_mode.get()); + auto profile_updated_seconds = env->CallLongMethod(info, class_info.get_profile_updated); + + auto contact_info = conf->get_or_construct(JavaStringRef(env, account_id.get()).view()); + if (name.get()) { + contact_info.name = JavaStringRef(env, name.get()).view(); + } + if (nickname.get()) { + contact_info.nickname = JavaStringRef(env, nickname.get()).view(); + } + contact_info.approved = env->CallBooleanMethod(info, class_info.get_approved); + contact_info.approved_me = env->CallBooleanMethod(info, class_info.get_approved_me); + contact_info.blocked = env->CallBooleanMethod(info, class_info.get_blocked); + contact_info.profile_updated = std::chrono::sys_seconds{std::chrono::seconds{profile_updated_seconds}}; + if (user_pic.get() != nullptr) { + contact_info.profile_picture = util::deserialize_user_pic(env, user_pic.get()); + } + + contact_info.priority = env->CallLongMethod(info, class_info.get_priority); + contact_info.exp_mode = expiry_pair.first; + contact_info.exp_timer = std::chrono::seconds(expiry_pair.second); + contact_info.pro_features = env->CallLongMethod(info, class_info.get_pro_features); + + return contact_info; +} + extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_Contacts_get(JNIEnv *env, jobject thiz, jstring account_id) { // If an exception is thrown, return nullptr - return jni_utils::run_catching_cxx_exception_or( + return run_catching_cxx_exception_or_throws( + env, [=]() -> jobject { auto contacts = ptrToContacts(env, thiz); - auto contact = contacts->get(jni_utils::JavaStringRef(env, account_id).view()); + auto contact = contacts->get(JavaStringRef(env, account_id).view()); if (!contact) return nullptr; - jobject j_contact = serialize_contact(env, contact.value()); - return j_contact; - }, - [](const char *) -> jobject { return nullptr; } + return serialize_contact(env, contact.value()).release(); + } ); } @@ -29,10 +117,10 @@ extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_Contacts_getOrConstruct(JNIEnv *env, jobject thiz, jstring account_id) { - return jni_utils::run_catching_cxx_exception_or_throws(env, [=] { + return run_catching_cxx_exception_or_throws(env, [=] { auto contacts = ptrToContacts(env, thiz); - auto contact = contacts->get_or_construct(jni_utils::JavaStringRef(env, account_id).view()); - return serialize_contact(env, contact); + auto contact = contacts->get_or_construct(JavaStringRef(env, account_id).view()); + return serialize_contact(env, contact).release(); }); } @@ -40,7 +128,7 @@ extern "C" JNIEXPORT void JNICALL Java_network_loki_messenger_libsession_1util_Contacts_set(JNIEnv *env, jobject thiz, jobject contact) { - jni_utils::run_catching_cxx_exception_or_throws(env, [=] { + run_catching_cxx_exception_or_throws(env, [=] { auto contacts = ptrToContacts(env, thiz); auto contact_info = deserialize_contact(env, contact, contacts); contacts->set(contact_info); @@ -51,9 +139,9 @@ extern "C" JNIEXPORT jboolean JNICALL Java_network_loki_messenger_libsession_1util_Contacts_erase(JNIEnv *env, jobject thiz, jstring account_id) { - return jni_utils::run_catching_cxx_exception_or_throws(env, [=] { + return run_catching_cxx_exception_or_throws(env, [=] { auto contacts = ptrToContacts(env, thiz); - bool result = contacts->erase(jni_utils::JavaStringRef(env, account_id).view()); + bool result = contacts->erase(JavaStringRef(env, account_id).view()); return result; }); } @@ -61,116 +149,77 @@ Java_network_loki_messenger_libsession_1util_Contacts_erase(JNIEnv *env, jobject extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_Contacts_all(JNIEnv *env, jobject thiz) { - return jni_utils::run_catching_cxx_exception_or_throws(env, [=] { - return jni_utils::jlist_from_collection(env, *ptrToContacts(env, thiz), serialize_contact); + return run_catching_cxx_exception_or_throws(env, [=] { + return jlist_from_collection(env, *ptrToContacts(env, thiz), serialize_contact); }); } -jobject serialize_contact(JNIEnv *env, session::config::contact_info info) { - auto contactClass = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/Contact")); - jmethodID constructor = env->GetMethodID(contactClass.get(), "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZLnetwork/loki/messenger/libsession_util/util/UserPic;JJJLnetwork/loki/messenger/libsession_util/util/ExpiryMode;)V"); - jobject returnObj = env->NewObject(contactClass.get(), - constructor, - jni_utils::JavaLocalRef(env, env->NewStringUTF(info.session_id.data())).get(), - jni_utils::JavaLocalRef(env, env->NewStringUTF(info.name.data())).get(), - jni_utils::JavaLocalRef(env, env->NewStringUTF(info.nickname.data())).get(), - (jboolean) info.approved, - (jboolean) info.approved_me, - (jboolean) info.blocked, - jni_utils::JavaLocalRef(env, util::serialize_user_pic(env, info.profile_picture)).get(), - (jlong) info.created, - (jlong) (info.profile_updated.time_since_epoch().count()), - (jlong) info.priority, - util::serialize_expiry(env, info.exp_mode, info.exp_timer)); - return returnObj; -} -session::config::contact_info deserialize_contact(JNIEnv *env, jobject info, session::config::Contacts *conf) { - jclass contactClass = env->FindClass("network/loki/messenger/libsession_util/util/Contact"); - - jfieldID getId, getName, getNick, getApproved, getApprovedMe, getBlocked, getUserPic, getPriority, getExpiry, getHidden, profileUpdatedField; - getId = env->GetFieldID(contactClass, "id", "Ljava/lang/String;"); - getName = env->GetFieldID(contactClass, "name", "Ljava/lang/String;"); - getNick = env->GetFieldID(contactClass, "nickname", "Ljava/lang/String;"); - getApproved = env->GetFieldID(contactClass, "approved", "Z"); - 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", "J"); - getExpiry = env->GetFieldID(contactClass, "expiryMode", "Lnetwork/loki/messenger/libsession_util/util/ExpiryMode;"); - profileUpdatedField = env->GetFieldID(contactClass, "profileUpdatedEpochSeconds", "J"); - - jni_utils::JavaLocalRef account_id(env, static_cast(env->GetObjectField(info, getId))); - jni_utils::JavaLocalRef name(env, static_cast(env->GetObjectField(info, getName))); - jni_utils::JavaLocalRef nickname(env, static_cast(env->GetObjectField(info, getNick))); - jni_utils::JavaLocalRef user_pic(env, env->GetObjectField(info, getUserPic)); - jni_utils::JavaLocalRef expiry_mode(env, env->GetObjectField(info, getExpiry)); - auto expiry_pair = util::deserialize_expiry(env, expiry_mode.get()); - auto profile_updated_seconds = env->GetLongField(info, profileUpdatedField); +JavaLocalRef serialize_blinded_contact(JNIEnv *env, const session::config::blinded_contact_info &info) { + static BasicJavaClassInfo class_info( + env, "network/loki/messenger/libsession_util/util/BlindedContact", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JJLnetwork/loki/messenger/libsession_util/util/UserPic;JJ)V"); - auto contact_info = conf->get_or_construct(jni_utils::JavaStringRef(env, account_id.get()).view()); - if (name.get()) { - contact_info.name = jni_utils::JavaStringRef(env, name.get()).view(); - } - if (nickname.get()) { - contact_info.nickname = jni_utils::JavaStringRef(env, nickname.get()).view(); - } - contact_info.approved = env->GetBooleanField(info, getApproved); - contact_info.approved_me = env->GetBooleanField(info, getApprovedMe); - contact_info.blocked = env->GetBooleanField(info, getBlocked); - contact_info.profile_updated = std::chrono::sys_seconds{std::chrono::seconds{profile_updated_seconds}}; - if (user_pic.get() != nullptr) { - contact_info.profile_picture = util::deserialize_user_pic(env, user_pic.get()); - } - - contact_info.priority = env->GetLongField(info, getPriority); - contact_info.exp_mode = expiry_pair.first; - contact_info.exp_timer = std::chrono::seconds(expiry_pair.second); - - return contact_info; -} - -jobject serialize_blinded_contact(JNIEnv *env, const session::config::blinded_contact_info &info) { - jni_utils::JavaLocalRef clazz(env, env->FindClass("network/loki/messenger/libsession_util/util/BlindedContact")); - auto constructor = env->GetMethodID(clazz.get(), "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JJLnetwork/loki/messenger/libsession_util/util/UserPic;J)V"); - - return env->NewObject( - clazz.get(), - constructor, - jni_utils::JavaLocalRef(env, env->NewStringUTF(info.session_id().c_str())).get(), - jni_utils::JavaLocalRef(env, env->NewStringUTF(info.community_base_url().data())).get(), - jni_utils::JavaLocalRef(env, env->NewStringUTF(info.community_pubkey_hex().data())).get(), - jni_utils::JavaLocalRef(env, env->NewStringUTF(info.name.c_str())).get(), + return {env, env->NewObject( + class_info.java_class, + class_info.constructor, + JavaLocalRef(env, env->NewStringUTF(info.session_id().c_str())).get(), + JavaLocalRef(env, env->NewStringUTF(info.community_base_url().data())).get(), + JavaLocalRef(env, env->NewStringUTF(info.community_pubkey_hex().data())).get(), + JavaLocalRef(env, env->NewStringUTF(info.name.c_str())).get(), (jlong) (info.created.time_since_epoch().count()), (jlong) (info.profile_updated.time_since_epoch().count()), - jni_utils::JavaLocalRef(env, util::serialize_user_pic(env, info.profile_picture)).get(), - (jlong) info.priority - ); + util::serialize_user_pic(env, info.profile_picture).get(), + (jlong) info.priority, + (jlong) info.pro_features + )}; } session::config::blinded_contact_info deserialize_blinded_contact(JNIEnv *env, jobject jInfo) { - jni_utils::JavaLocalRef clazz(env, env->GetObjectClass(jInfo)); - auto idField = env->GetFieldID(clazz.get(), "id", "Ljava/lang/String;"); - auto communityServerField = env->GetFieldID(clazz.get(), "communityServer", "Ljava/lang/String;"); - auto getCommunityServerPubKey = env->GetMethodID(clazz.get(), "getCommunityServerPubKey", "()[B"); - auto nameField = env->GetFieldID(clazz.get(), "name", "Ljava/lang/String;"); - auto createdEpochSecondsField = env->GetFieldID(clazz.get(), "createdEpochSeconds", "J"); - auto profileUpdatedEpochSecondsField = env->GetFieldID(clazz.get(), "profileUpdatedEpochSeconds", "J"); - auto profilePicField = env->GetFieldID(clazz.get(), "profilePic", "Lnetwork/loki/messenger/libsession_util/util/UserPic;"); - auto priorityField = env->GetFieldID(clazz.get(), "priority", "J"); + struct ClassInfo : public JavaClassInfo { + jmethodID id_getter; + jmethodID community_server_getter; + jmethodID community_server_pub_key_getter; + jmethodID name_getter; + jmethodID created_epoch_seconds_getter; + jmethodID profile_updated_epoch_seconds_getter; + jmethodID profile_pic_getter; + jmethodID priority_getter; + jmethodID pro_features_getter; + + ClassInfo(JNIEnv *env, jobject obj) + : JavaClassInfo(env, obj) + , id_getter(env->GetMethodID(java_class, "getId", "()Ljava/lang/String;")) + , community_server_getter(env->GetMethodID(java_class, "getCommunityServer", "()Ljava/lang/String;")) + , community_server_pub_key_getter(env->GetMethodID(java_class, "getCommunityServerPubKey", "()[B")) + , name_getter(env->GetMethodID(java_class, "getName", "()Ljava/lang/String;")) + , created_epoch_seconds_getter(env->GetMethodID(java_class, "getCreatedEpochSeconds", "()J")) + , profile_updated_epoch_seconds_getter(env->GetMethodID(java_class, "getProfileUpdatedEpochSeconds", "()J")) + , profile_pic_getter(env->GetMethodID(java_class, "getProfilePicture", "()Lnetwork/loki/messenger/libsession_util/util/UserPic;")) + , priority_getter(env->GetMethodID(java_class, "getPriority", "()J")) + , pro_features_getter(env->GetMethodID(java_class, "getProFeaturesRaw", "()J")) {} + }; + + static ClassInfo class_info(env, jInfo); + + JavaLocalRef + community_server(env, (jstring) env->CallObjectMethod(jInfo, class_info.community_server_getter)), + id(env, (jstring) env->CallObjectMethod(jInfo, class_info.id_getter)); + + JavaLocalRef community_server_pub_key(env, (jbyteArray) env->CallObjectMethod(jInfo, class_info.community_server_pub_key_getter)); session::config::blinded_contact_info info( - jni_utils::JavaStringRef(env, (jstring) env->GetObjectField(jInfo, communityServerField)).view(), - jni_utils::JavaByteArrayRef(env, (jbyteArray) env->CallObjectMethod(jInfo, getCommunityServerPubKey)).get(), - jni_utils::JavaStringRef(env, (jstring) env->GetObjectField(jInfo, idField)).view() + JavaStringRef(env, community_server.get()).view(), + JavaByteArrayRef(env, community_server_pub_key.get()).get(), + JavaStringRef(env, id.get()).view() ); - info.created = std::chrono::sys_seconds{std::chrono::seconds{env->GetLongField(jInfo, createdEpochSecondsField)}}; - info.profile_picture = util::deserialize_user_pic(env, jni_utils::JavaLocalRef(env, env->GetObjectField(jInfo, profilePicField)).get()); - info.name = jni_utils::JavaStringRef(env, jni_utils::JavaLocalRef(env, (jstring) env->GetObjectField(jInfo, nameField)).get()).view(); - info.profile_updated = std::chrono::sys_seconds{std::chrono::seconds{env->GetLongField(jInfo, profileUpdatedEpochSecondsField)}}; - info.priority = env->GetLongField(jInfo, priorityField); + info.created = std::chrono::sys_seconds{std::chrono::seconds{env->CallLongMethod(jInfo, class_info.created_epoch_seconds_getter)}}; + info.profile_picture = util::deserialize_user_pic(env, JavaLocalRef(env, env->CallObjectMethod(jInfo, class_info.profile_pic_getter)).get()); + info.name = JavaStringRef(env, JavaLocalRef(env, (jstring) env->CallObjectMethod(jInfo, class_info.name_getter)).get()).view(); + info.profile_updated = std::chrono::sys_seconds{std::chrono::seconds{env->CallLongMethod(jInfo, class_info.profile_updated_epoch_seconds_getter)}}; + info.priority = env->CallLongMethod(jInfo, class_info.priority_getter); + info.pro_features = env->CallLongMethod(jInfo, class_info.pro_features_getter); return info; } @@ -183,10 +232,10 @@ Java_network_loki_messenger_libsession_1util_Contacts_getOrConstructBlinded(JNIE jstring community_server_pub_key_hex, jstring blinded_id) { return serialize_blinded_contact(env, ptrToContacts(env, thiz)->get_or_construct_blinded( - jni_utils::JavaStringRef(env, community_server_url).view(), - jni_utils::JavaStringRef(env, community_server_pub_key_hex).view(), - jni_utils::JavaStringRef(env, blinded_id).view() - )); + JavaStringRef(env, community_server_url).view(), + JavaStringRef(env, community_server_pub_key_hex).view(), + JavaStringRef(env, blinded_id).view() + )).release(); } extern "C" @@ -204,15 +253,15 @@ Java_network_loki_messenger_libsession_1util_Contacts_eraseBlinded(JNIEnv *env, jstring community_server_url, jstring blinded_id) { ptrToContacts(env, thiz)->erase_blinded( - jni_utils::JavaStringRef(env, community_server_url).view(), - jni_utils::JavaStringRef(env, blinded_id).view() + JavaStringRef(env, community_server_url).view(), + JavaStringRef(env, blinded_id).view() ); } extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_Contacts_allBlinded(JNIEnv *env, jobject thiz) { - return jni_utils::jlist_from_collection( + return jlist_from_collection( env, ptrToContacts(env, thiz)->blinded(), serialize_blinded_contact @@ -224,10 +273,10 @@ JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_Contacts_getBlinded(JNIEnv *env, jobject thiz, jstring blinded_id) { - auto result = ptrToContacts(env, thiz)->get_blinded(jni_utils::JavaStringRef(env, blinded_id).view()); + auto result = ptrToContacts(env, thiz)->get_blinded(JavaStringRef(env, blinded_id).view()); if (result) { - return serialize_blinded_contact(env, *result); + return serialize_blinded_contact(env, *result).release(); } else { return nullptr; } diff --git a/library/src/main/cpp/contacts.h b/library/src/main/cpp/contacts.h deleted file mode 100644 index 4122a18..0000000 --- a/library/src/main/cpp/contacts.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef SESSION_ANDROID_CONTACTS_H -#define SESSION_ANDROID_CONTACTS_H - -#include -#include "session/config/contacts.hpp" - -session::config::Contacts *ptrToContacts(JNIEnv *env, jobject obj); -jobject serialize_contact(JNIEnv *env, session::config::contact_info info); -session::config::contact_info deserialize_contact(JNIEnv *env, jobject info, session::config::Contacts *conf); - - -#endif //SESSION_ANDROID_CONTACTS_H diff --git a/library/src/main/cpp/conversation.cpp b/library/src/main/cpp/conversation.cpp index b6a1390..729ead5 100644 --- a/library/src/main/cpp/conversation.cpp +++ b/library/src/main/cpp/conversation.cpp @@ -1,50 +1,113 @@ #include -#include "conversation.h" +#include + #include "jni_utils.h" +#include "util.h" +#include "user_groups.h" +#include "config_base.h" + +using namespace jni_utils; + +static auto ptrToConvoInfo(JNIEnv *env, jobject obj) { + return dynamic_cast(ptrToConfigBase(env, obj)); +} + +JavaLocalRef serialize_pro_proof_info(JNIEnv *env, + std::optional> gen_index_hash, + const std::chrono::sys_time & expiry) { + if (!gen_index_hash) { + return {env, nullptr}; + } + + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/util/Conversation$ProProofInfo", + "([BJ)V" + ); + + return {env, env->NewObject( + class_info.java_class, + class_info.constructor, + util::bytes_from_span(env, *gen_index_hash).get(), + static_cast(expiry.time_since_epoch().count()) + )}; +} +JavaLocalRef serialize_one_to_one(JNIEnv *env, const session::config::convo::one_to_one &one_to_one) { + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/util/Conversation$OneToOne", + "(Ljava/lang/String;JZLnetwork/loki/messenger/libsession_util/util/Conversation$ProProofInfo;)V" + ); - jobject serialize_one_to_one(JNIEnv *env, const session::config::convo::one_to_one &one_to_one) { - jni_utils::JavaLocalRef clazz(env, env->FindClass("network/loki/messenger/libsession_util/util/Conversation$OneToOne")); - return env->NewObject(clazz.get(), - env->GetMethodID(clazz.get(), "", "(Ljava/lang/String;JZ)V"), - jni_utils::JavaLocalRef(env, env->NewStringUTF(one_to_one.session_id.data())).get(), + return {env, env->NewObject(class_info.java_class, + class_info.constructor, + JavaLocalRef(env, env->NewStringUTF(one_to_one.session_id.data())).get(), (jlong) one_to_one.last_read, - (jboolean) one_to_one.unread); + (jboolean) one_to_one.unread, + serialize_pro_proof_info(env, one_to_one.pro_gen_index_hash, + one_to_one.pro_expiry_unix_ts).get())}; } session::config::convo::one_to_one deserialize_one_to_one(JNIEnv *env, jobject info) { - jni_utils::JavaLocalRef clazz(env, env->GetObjectClass(info)); + struct ClassInfo : public JavaClassInfo { + jmethodID id_getter; + jmethodID lastRead_getter; + jmethodID unread_getter; + + ClassInfo(JNIEnv *env, jobject obj) + : JavaClassInfo(env, obj), + id_getter(env->GetMethodID(java_class, "getAccountId", "()Ljava/lang/String;")), + lastRead_getter(env->GetMethodID(java_class, "getLastRead", "()J")), + unread_getter(env->GetMethodID(java_class, "getUnread", "()Z")) {} + }; - auto id_getter = env->GetFieldID(clazz.get(), "accountId", "Ljava/lang/String;"); - auto last_read_getter = env->GetFieldID(clazz.get(), "lastRead", "J"); - auto unread_getter = env->GetFieldID(clazz.get(), "unread", "Z"); + static ClassInfo class_info(env, info); session::config::convo::one_to_one r( - jni_utils::JavaStringRef(env, jni_utils::JavaLocalRef(env, static_cast(env->GetObjectField(info, id_getter))).get()).view() + JavaStringRef( + env, + JavaLocalRef(env, (jstring)(env->CallObjectMethod(info, class_info.id_getter))).get() + ).view() ); - r.last_read = env->GetLongField(info, last_read_getter); - r.unread = env->GetBooleanField(info, unread_getter); + r.last_read = env->CallLongMethod(info, class_info.lastRead_getter); + r.unread = env->CallBooleanMethod(info, class_info.unread_getter); return r; } -jobject serialize_community(JNIEnv *env, const session::config::convo::community& community) { - jni_utils::JavaLocalRef clazz(env, env->FindClass("network/loki/messenger/libsession_util/util/Conversation$Community")); - return env->NewObject(clazz.get(), - env->GetMethodID(clazz.get(), "", - "(Lnetwork/loki/messenger/libsession_util/util/BaseCommunityInfo;JZ)V"), - jni_utils::JavaLocalRef(env, util::serialize_base_community(env, community)).get(), +JavaLocalRef serialize_community(JNIEnv *env, const session::config::convo::community& community) { + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/util/Conversation$Community", + "(Lnetwork/loki/messenger/libsession_util/util/BaseCommunityInfo;JZ)V" + ); + + return {env, env->NewObject(class_info.java_class, + class_info.constructor, + serialize_base_community(env, community).get(), (jlong) community.last_read, - (jboolean) community.unread); + (jboolean) community.unread)}; } session::config::convo::community deserialize_community(JNIEnv *env, jobject info) { - jni_utils::JavaLocalRef clazz(env, env->GetObjectClass(info)); - auto base_community_getter = env->GetFieldID(clazz.get(), "baseCommunityInfo", "Lnetwork/loki/messenger/libsession_util/util/BaseCommunityInfo;"); - auto last_read_getter = env->GetFieldID(clazz.get(), "lastRead", "J"); - auto unread_getter = env->GetFieldID(clazz.get(), "unread", "Z"); + struct ClassInfo : public JavaClassInfo { + jmethodID base_community_getter; + jmethodID last_read_getter; + jmethodID unread_getter; + + ClassInfo(JNIEnv *env, jobject obj) + : JavaClassInfo(env, obj), + base_community_getter(env->GetMethodID(java_class, "getBaseCommunityInfo", "()Lnetwork/loki/messenger/libsession_util/util/BaseCommunityInfo;")), + last_read_getter(env->GetMethodID(java_class, "getLastRead", "()J")), + unread_getter(env->GetMethodID(java_class, "getUnread", "()Z")) {} + }; - auto base_community = util::deserialize_base_community(env, jni_utils::JavaLocalRef(env, env->GetObjectField(info, base_community_getter)).get()); + static ClassInfo class_info(env, info); + + auto base_community = deserialize_base_community( + env, + JavaLocalRef(env, env->CallObjectMethod(info, class_info.base_community_getter)).get()); session::config::convo::community community( base_community.base_url(), @@ -52,88 +115,129 @@ session::config::convo::community deserialize_community(JNIEnv *env, jobject inf base_community.pubkey() ); - community.last_read = env->GetLongField(info, last_read_getter); - community.unread = env->GetBooleanField(info, unread_getter); + community.last_read = env->CallLongMethod(info, class_info.last_read_getter); + community.unread = env->CallBooleanMethod(info, class_info.unread_getter); return community; } -jobject serialize_legacy_group(JNIEnv *env, const session::config::convo::legacy_group& group) { - jni_utils::JavaLocalRef clazz(env, env->FindClass("network/loki/messenger/libsession_util/util/Conversation$LegacyGroup")); - return env->NewObject(clazz.get(), - env->GetMethodID(clazz.get(), "","(Ljava/lang/String;JZ)V"), - jni_utils::JavaLocalRef(env, env->NewStringUTF(group.id.data())).get(), +JavaLocalRef serialize_legacy_group(JNIEnv *env, const session::config::convo::legacy_group& group) { + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/util/Conversation$LegacyGroup", + "(Ljava/lang/String;JZ)V" + ); + + return {env, env->NewObject(class_info.java_class, + class_info.constructor, + JavaLocalRef(env, env->NewStringUTF(group.id.data())).get(), (jlong) group.last_read, - (jboolean) group.unread); + (jboolean) group.unread)}; } session::config::convo::legacy_group deserialize_legacy_closed_group(JNIEnv *env, jobject info) { - jni_utils::JavaLocalRef clazz(env, env->GetObjectClass(info)); - auto group_id_getter = env->GetFieldID(clazz.get(), "groupId", "Ljava/lang/String;"); - auto last_read_getter = env->GetFieldID(clazz.get(), "lastRead", "J"); - auto unread_getter = env->GetFieldID(clazz.get(), "unread", "Z"); + struct ClassInfo : public JavaClassInfo{ + jmethodID groupId_getter; + jmethodID lastRead_getter; + jmethodID unread_getter; + + ClassInfo(JNIEnv *env, jobject obj) + : JavaClassInfo(env, obj), + groupId_getter(env->GetMethodID(java_class, "getGroupId", "()Ljava/lang/String;")), + lastRead_getter(env->GetMethodID(java_class, "getLastRead", "()J")), + unread_getter(env->GetMethodID(java_class, "getUnread", "()Z")) {} + }; + + static ClassInfo class_info(env, info); session::config::convo::legacy_group lg( - jni_utils::JavaStringRef(env, jni_utils::JavaLocalRef(env, static_cast(env->GetObjectField(info, group_id_getter))).get()).view() + JavaStringRef(env, JavaLocalRef(env, static_cast(env->CallObjectMethod(info, class_info.groupId_getter))).get()).view() ); - lg.last_read = env->GetLongField(info, last_read_getter); - lg.unread = env->GetBooleanField(info, unread_getter); + lg.last_read = env->CallLongMethod(info, class_info.lastRead_getter); + lg.unread = env->CallBooleanMethod(info, class_info.unread_getter); return lg; } -jobject serialize_closed_group(JNIEnv* env, const session::config::convo::group &group) { - jni_utils::JavaLocalRef clazz(env, env->FindClass("network/loki/messenger/libsession_util/util/Conversation$ClosedGroup")); - return env->NewObject(clazz.get(), - env->GetMethodID(clazz.get(), "", "(Ljava/lang/String;JZ)V"), - jni_utils::JavaLocalRef(env, env->NewStringUTF(group.id.data())).get(), +JavaLocalRef serialize_closed_group(JNIEnv* env, const session::config::convo::group &group) { + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/util/Conversation$ClosedGroup", + "(Ljava/lang/String;JZ)V"); + + return {env, env->NewObject(class_info.java_class, + class_info.constructor, + JavaLocalRef(env, env->NewStringUTF(group.id.data())).get(), (jlong) group.last_read, - (jboolean) group.unread); + (jboolean) group.unread)}; } session::config::convo::group deserialize_closed_group(JNIEnv* env, jobject info) { - jni_utils::JavaLocalRef clazz(env, env->GetObjectClass(info)); - auto id_getter = env->GetFieldID(clazz.get(), "accountId", "Ljava/lang/String;"); - auto last_read_getter = env->GetFieldID(clazz.get(), "lastRead", "J"); - auto unread_getter = env->GetFieldID(clazz.get(), "unread", "Z"); + struct ClassInfo : public JavaClassInfo { + jmethodID id_getter; + jmethodID last_read_getter; + jmethodID unread_getter; + + ClassInfo(JNIEnv *env, jobject obj) + :JavaClassInfo(env, obj), + id_getter(env->GetMethodID(java_class, "getAccountId", "()Ljava/lang/String;")), + last_read_getter(env->GetMethodID(java_class, "getLastRead", "()J")), + unread_getter(env->GetMethodID(java_class, "getUnread", "()Z")) {} + }; + + static ClassInfo class_info(env, info); session::config::convo::group g( - jni_utils::JavaStringRef(env, jni_utils::JavaLocalRef(env, (jstring) env->GetObjectField(info, id_getter)).get()).view()); + JavaStringRef(env, JavaLocalRef(env, (jstring) env->CallObjectMethod(info, class_info.id_getter)).get()).view()); - g.last_read = env->GetLongField(info, last_read_getter); - g.unread = env->GetBooleanField(info, unread_getter); + g.last_read = env->CallLongMethod(info, class_info.last_read_getter); + g.unread = env->CallBooleanMethod(info, class_info.unread_getter); return g; } -jobject serialize_blinded_one_to_one(JNIEnv *env, const session::config::convo::blinded_one_to_one &blinded_one_to_one) { - jni_utils::JavaLocalRef clazz(env, env->FindClass("network/loki/messenger/libsession_util/util/Conversation$BlindedOneToOne")); - return env->NewObject( - clazz.get(), - env->GetMethodID(clazz.get(), "", "(Ljava/lang/String;JZ)V"), - jni_utils::JavaLocalRef(env, env->NewStringUTF(blinded_one_to_one.blinded_session_id.data())).get(), +JavaLocalRef serialize_blinded_one_to_one(JNIEnv *env, const session::config::convo::blinded_one_to_one &blinded_one_to_one) { + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/util/Conversation$BlindedOneToOne", + "(Ljava/lang/String;JZLnetwork/loki/messenger/libsession_util/util/Conversation$ProProofInfo;)V"); + + return {env, env->NewObject( + class_info.java_class, + class_info.constructor, + JavaLocalRef(env, env->NewStringUTF(blinded_one_to_one.blinded_session_id.data())).get(), (jlong) blinded_one_to_one.last_read, - (jboolean) blinded_one_to_one.unread - ); + (jboolean) blinded_one_to_one.unread, + serialize_pro_proof_info(env, blinded_one_to_one.pro_gen_index_hash, blinded_one_to_one.pro_expiry_unix_ts).get() + )}; } session::config::convo::blinded_one_to_one deserialize_blinded_one_to_one(JNIEnv *env, jobject info) { - jni_utils::JavaLocalRef clazz(env, env->GetObjectClass(info)); - auto id_field_id = env->GetFieldID(clazz.get(), "blindedAccountId", "Ljava/lang/String;"); - auto last_read_field_id = env->GetFieldID(clazz.get(), "lastRead", "J"); - auto unread_field_id = env->GetFieldID(clazz.get(), "unread", "Z"); + struct ClassInfo : public JavaClassInfo { + jmethodID id_getter; + jmethodID last_read_getter; + jmethodID unread_getter; + + ClassInfo(JNIEnv *env, jobject obj) + : JavaClassInfo(env, obj), + id_getter(env->GetMethodID(java_class, "getBlindedAccountId", "()Ljava/lang/String;")), + last_read_getter(env->GetMethodID(java_class, "getLastRead", "()J")), + unread_getter(env->GetMethodID(java_class, "getUnread", "()Z")) {} + }; + + static ClassInfo class_info(env, info); session::config::convo::blinded_one_to_one r( - jni_utils::JavaStringRef(env, jni_utils::JavaLocalRef(env, (jstring) env->GetObjectField(info, id_field_id)).get()).view()); + JavaStringRef(env, JavaLocalRef(env, (jstring) env->CallObjectMethod(info, class_info.id_getter)).get()).view()); - r.last_read = env->GetLongField(info, last_read_field_id); - r.unread = env->GetBooleanField(info, unread_field_id); + r.last_read = env->CallLongMethod(info, class_info.last_read_getter); + r.unread = env->CallBooleanMethod(info, class_info.unread_getter); return r; } -jobject serialize_any(JNIEnv *env, session::config::convo::any any) { +JavaLocalRef serialize_any(JNIEnv *env, session::config::convo::any any) { if (auto* dm = std::get_if(&any)) { return serialize_one_to_one(env, *dm); } else if (auto* og = std::get_if(&any)) { @@ -145,7 +249,7 @@ jobject serialize_any(JNIEnv *env, session::config::convo::any any) { } else if (auto *bc = std::get_if(&any)) { return serialize_blinded_one_to_one(env, *bc); } - return nullptr; + return {env, nullptr}; } @@ -164,17 +268,17 @@ Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_eraseAll jobject predicate) { auto conversations = ptrToConvoInfo(env, thiz); - jni_utils::JavaLocalRef predicate_class(env, env->GetObjectClass(predicate)); + JavaLocalRef predicate_class(env, env->GetObjectClass(predicate)); jmethodID predicate_call = env->GetMethodID(predicate_class.get(), "invoke", "(Ljava/lang/Object;)Ljava/lang/Object;"); - jni_utils::JavaLocalRef bool_class(env, env->FindClass("java/lang/Boolean")); + JavaLocalRef bool_class(env, env->FindClass("java/lang/Boolean")); jmethodID bool_get = env->GetMethodID(bool_class.get(), "booleanValue", "()Z"); int removed = 0; auto to_erase = std::vector(); for (auto it = conversations->begin(); it != conversations->end(); ++it) { - jni_utils::JavaLocalRef result(env, env->CallObjectMethod(predicate, predicate_call, jni_utils::JavaLocalRef(env, serialize_any(env, *it)).get())); + JavaLocalRef result(env, env->CallObjectMethod(predicate, predicate_call, serialize_any(env, *it).get())); bool bool_result = env->CallBooleanMethod(result.get(), bool_get); if (bool_result) { to_erase.push_back(*it); @@ -212,9 +316,9 @@ Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getOneTo jobject thiz, jstring pub_key_hex) { auto convos = ptrToConvoInfo(env, thiz); - auto internal = convos->get_1to1(jni_utils::JavaStringRef(env, pub_key_hex).view()); + auto internal = convos->get_1to1(JavaStringRef(env, pub_key_hex).view()); if (internal) { - return serialize_one_to_one(env, *internal); + return serialize_one_to_one(env, *internal).release(); } return nullptr; } @@ -223,7 +327,7 @@ JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getOrConstructOneToOne( JNIEnv *env, jobject thiz, jstring pub_key_hex) { auto convos = ptrToConvoInfo(env, thiz); - return serialize_one_to_one(env, convos->get_or_construct_1to1(jni_utils::JavaStringRef(env, pub_key_hex).view())); + return serialize_one_to_one(env, convos->get_or_construct_1to1(JavaStringRef(env, pub_key_hex).view())).release(); } extern "C" JNIEXPORT jboolean JNICALL @@ -231,7 +335,7 @@ Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_eraseOne jobject thiz, jstring pub_key_hex) { auto convos = ptrToConvoInfo(env, thiz); - return convos->erase_1to1(jni_utils::JavaStringRef(env, pub_key_hex).view()); + return convos->erase_1to1(JavaStringRef(env, pub_key_hex).view()); } extern "C" @@ -239,9 +343,9 @@ JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getCommunity__Ljava_lang_String_2Ljava_lang_String_2( JNIEnv *env, jobject thiz, jstring base_url, jstring room) { auto convos = ptrToConvoInfo(env, thiz); - auto open = convos->get_community(jni_utils::JavaStringRef(env, base_url).view(), jni_utils::JavaStringRef(env, room).view()); + auto open = convos->get_community(JavaStringRef(env, base_url).view(), JavaStringRef(env, room).view()); if (open) { - return serialize_community(env, *open); + return serialize_community(env, *open).release(); } return nullptr; } @@ -251,10 +355,10 @@ Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getOrCon JNIEnv *env, jobject thiz, jstring base_url, jstring room, jbyteArray pub_key) { auto convos = ptrToConvoInfo(env, thiz); auto community = convos->get_or_construct_community( - jni_utils::JavaStringRef(env, base_url).view(), - jni_utils::JavaStringRef(env, room).view(), - jni_utils::JavaByteArrayRef(env, pub_key).get()); - return serialize_community(env, community); + JavaStringRef(env, base_url).view(), + JavaStringRef(env, room).view(), + JavaByteArrayRef(env, pub_key).get()); + return serialize_community(env, community).release(); } extern "C" JNIEXPORT jobject JNICALL @@ -262,10 +366,10 @@ Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getOrCon JNIEnv *env, jobject thiz, jstring base_url, jstring room, jstring pub_key_hex) { auto convos = ptrToConvoInfo(env, thiz); auto community = convos->get_or_construct_community( - jni_utils::JavaStringRef(env, base_url).view(), - jni_utils::JavaStringRef(env, room).view(), - jni_utils::JavaStringRef(env, pub_key_hex).view()); - return serialize_community(env, community); + JavaStringRef(env, base_url).view(), + JavaStringRef(env, room).view(), + JavaStringRef(env, pub_key_hex).view()); + return serialize_community(env, community).release(); } extern "C" JNIEXPORT jboolean JNICALL @@ -282,17 +386,17 @@ Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_eraseCom JNIEnv *env, jobject thiz, jstring base_url, jstring room) { auto convos = ptrToConvoInfo(env, thiz); return convos->erase_community( - jni_utils::JavaStringRef(env, base_url).view(), - jni_utils::JavaStringRef(env, room).view()); + JavaStringRef(env, base_url).view(), + JavaStringRef(env, room).view()); } extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getLegacyClosedGroup( JNIEnv *env, jobject thiz, jstring group_id) { auto convos = ptrToConvoInfo(env, thiz); - auto lgc = convos->get_legacy_group(jni_utils::JavaStringRef(env, group_id).view()); + auto lgc = convos->get_legacy_group(JavaStringRef(env, group_id).view()); if (lgc) { - return serialize_legacy_group(env, *lgc); + return serialize_legacy_group(env, *lgc).release(); } return nullptr; @@ -302,15 +406,15 @@ JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getOrConstructLegacyGroup( JNIEnv *env, jobject thiz, jstring group_id) { auto convos = ptrToConvoInfo(env, thiz); - auto lgc = convos->get_or_construct_legacy_group(jni_utils::JavaStringRef(env, group_id).view()); - return serialize_legacy_group(env, lgc); + auto lgc = convos->get_or_construct_legacy_group(JavaStringRef(env, group_id).view()); + return serialize_legacy_group(env, lgc).release(); } extern "C" JNIEXPORT jboolean JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_eraseLegacyClosedGroup( JNIEnv *env, jobject thiz, jstring group_id) { auto convos = ptrToConvoInfo(env, thiz); - return convos->erase_legacy_group(jni_utils::JavaStringRef(env, group_id).view()); + return convos->erase_legacy_group(JavaStringRef(env, group_id).view()); } extern "C" @@ -342,14 +446,14 @@ Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_all(JNIE jobject thiz) { auto convos = ptrToConvoInfo(env, thiz); - return jni_utils::jlist_from_collection(env, *convos, serialize_any); + return jlist_from_collection(env, *convos, serialize_any); } extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_allOneToOnes(JNIEnv *env, jobject thiz) { auto convos = ptrToConvoInfo(env, thiz); - return jni_utils::jlist_from_iterator(env, convos->begin_1to1(), convos->end(), + return jlist_from_iterator(env, convos->begin_1to1(), convos->end(), serialize_one_to_one); } extern "C" @@ -357,7 +461,7 @@ JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_allCommunities(JNIEnv *env, jobject thiz) { auto convos = ptrToConvoInfo(env, thiz); - return jni_utils::jlist_from_iterator(env, convos->begin_communities(), convos->end(), + return jlist_from_iterator(env, convos->begin_communities(), convos->end(), serialize_community); } extern "C" @@ -365,7 +469,7 @@ JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_allLegacyClosedGroups( JNIEnv *env, jobject thiz) { auto convos = ptrToConvoInfo(env, thiz); - return jni_utils::jlist_from_iterator(env, convos->begin_legacy_groups(), convos->end(), + return jlist_from_iterator(env, convos->begin_legacy_groups(), convos->end(), serialize_legacy_group); } @@ -374,7 +478,7 @@ JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_allClosedGroups(JNIEnv *env, jobject thiz) { auto convos = ptrToConvoInfo(env, thiz); - return jni_utils::jlist_from_iterator(env, convos->begin_groups(), convos->end(), + return jlist_from_iterator(env, convos->begin_groups(), convos->end(), serialize_closed_group); } @@ -384,9 +488,9 @@ Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getClose jobject thiz, jstring session_id) { auto config = ptrToConvoInfo(env, thiz); - auto group = config->get_group(jni_utils::JavaStringRef(env, session_id).view()); + auto group = config->get_group(JavaStringRef(env, session_id).view()); if (group) { - return serialize_closed_group(env, *group); + return serialize_closed_group(env, *group).release(); } return nullptr; } @@ -396,8 +500,8 @@ JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getOrConstructClosedGroup( JNIEnv *env, jobject thiz, jstring session_id) { auto config = ptrToConvoInfo(env, thiz); - auto group = config->get_or_construct_group(jni_utils::JavaStringRef(env, session_id).view()); - return serialize_closed_group(env, group); + auto group = config->get_or_construct_group(JavaStringRef(env, session_id).view()); + return serialize_closed_group(env, group).release(); } extern "C" @@ -405,7 +509,7 @@ JNIEXPORT jboolean JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_eraseClosedGroup( JNIEnv *env, jobject thiz, jstring session_id) { auto config = ptrToConvoInfo(env, thiz); - return config->erase_group(jni_utils::JavaStringRef(env, session_id).view()); + return config->erase_group(JavaStringRef(env, session_id).view()); } extern "C" @@ -447,13 +551,36 @@ extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getOrConstructedBlindedOneToOne( JNIEnv *env, jobject thiz, jstring blinded_id) { - return serialize_blinded_one_to_one(env, ptrToConvoInfo(env, thiz)->get_or_construct_blinded_1to1(jni_utils::JavaStringRef(env, blinded_id).view())); + return serialize_blinded_one_to_one( + env, + ptrToConvoInfo(env, thiz)->get_or_construct_blinded_1to1(JavaStringRef(env, blinded_id).view())).get(); } extern "C" JNIEXPORT jboolean JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_eraseBlindedOneToOne( JNIEnv *env, jobject thiz, jstring blinded_id) { - return ptrToConvoInfo(env, thiz)->erase_blinded_1to1(jni_utils::JavaStringRef(env, blinded_id).view()); + return ptrToConvoInfo(env, thiz)->erase_blinded_1to1(JavaStringRef(env, blinded_id).view()); } +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getBlindedOneToOne( + JNIEnv *env, jobject thiz, jstring pub_key_hex) { + auto blinded = ptrToConvoInfo(env, thiz)->get_blinded_1to1(JavaStringRef(env, pub_key_hex).view()); + if (blinded) { + return serialize_blinded_one_to_one(env, *blinded).release(); + } + return nullptr; +} + +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_allBlindedOneToOnes( + JNIEnv *env, jobject thiz) { + auto convo = ptrToConvoInfo(env, thiz); + return jlist_from_iterator(env, + convo->begin_blinded_1to1(), + convo->end(), + serialize_blinded_one_to_one); +} \ No newline at end of file diff --git a/library/src/main/cpp/conversation.h b/library/src/main/cpp/conversation.h deleted file mode 100644 index 0d784b7..0000000 --- a/library/src/main/cpp/conversation.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef SESSION_ANDROID_CONVERSATION_H -#define SESSION_ANDROID_CONVERSATION_H - -#include -#include -#include "util.h" -#include "session/config/convo_info_volatile.hpp" -#include "jni_utils.h" - -inline session::config::ConvoInfoVolatile *ptrToConvoInfo(JNIEnv *env, jobject obj) { - auto contactsClass = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/ConversationVolatileConfig")); - jfieldID pointerField = env->GetFieldID(contactsClass.get(), "pointer", "J"); - return (session::config::ConvoInfoVolatile *) env->GetLongField(obj, pointerField); -} - - -#endif //SESSION_ANDROID_CONVERSATION_H \ No newline at end of file diff --git a/library/src/main/cpp/curve25519.cpp b/library/src/main/cpp/curve25519.cpp index e4cbe98..837ed77 100644 --- a/library/src/main/cpp/curve25519.cpp +++ b/library/src/main/cpp/curve25519.cpp @@ -1,4 +1,5 @@ #include "jni_utils.h" +#include "util.h" #include @@ -11,7 +12,7 @@ Java_network_loki_messenger_libsession_1util_Curve25519_fromED25519(JNIEnv *env, auto pk = session::curve25519::to_curve25519_pubkey(jni_utils::JavaByteArrayRef(env, ed25519_public_key).get()); auto sk = session::curve25519::to_curve25519_seckey(jni_utils::JavaByteArrayRef(env, ed25519_private_key).get()); - return jni_utils::new_key_pair(env, util::bytes_from_span(env, pk), util::bytes_from_span(env, sk)); + return jni_utils::new_key_pair(env, util::bytes_from_span(env, pk).get(), util::bytes_from_span(env, sk).get()); }); } @@ -22,7 +23,7 @@ Java_network_loki_messenger_libsession_1util_Curve25519_pubKeyFromED25519(JNIEnv jbyteArray ed25519_public_key) { return jni_utils::run_catching_cxx_exception_or_throws(env, [=] { auto pk = session::curve25519::to_curve25519_pubkey(jni_utils::JavaByteArrayRef(env, ed25519_public_key).get()); - return util::bytes_from_span(env, pk); + return util::bytes_from_span(env, pk).release(); }); } @@ -32,7 +33,7 @@ Java_network_loki_messenger_libsession_1util_Curve25519_generateKeyPair(JNIEnv * return jni_utils::run_catching_cxx_exception_or_throws(env, [=] { auto [sk, pk] = session::curve25519::curve25519_key_pair(); return jni_utils::new_key_pair(env, - util::bytes_from_span(env, sk), - util::bytes_from_span(env, pk)); + util::bytes_from_span(env, sk).get(), + util::bytes_from_span(env, pk).get()); }); } \ No newline at end of file diff --git a/library/src/main/cpp/ed25519.cpp b/library/src/main/cpp/ed25519.cpp index cd0d51b..e5bb3b1 100644 --- a/library/src/main/cpp/ed25519.cpp +++ b/library/src/main/cpp/ed25519.cpp @@ -2,6 +2,7 @@ #include "util.h" #include +#include extern "C" JNIEXPORT jbyteArray JNICALL @@ -13,7 +14,7 @@ Java_network_loki_messenger_libsession_1util_ED25519_sign(JNIEnv *env, jobject t jni_utils::JavaByteArrayRef(env, ed25519_private_key).get(), jni_utils::JavaByteArrayRef(env, message).get()); - return util::bytes_from_vector(env, sigs); + return util::bytes_from_vector(env, sigs).release(); }); } @@ -41,6 +42,33 @@ Java_network_loki_messenger_libsession_1util_ED25519_generate(JNIEnv *env, jobje ? session::ed25519::ed25519_key_pair(jni_utils::JavaByteArrayRef(env, seed).get()) : session::ed25519::ed25519_key_pair(); - return jni_utils::new_key_pair(env, util::bytes_from_span(env, pk), util::bytes_from_span(env, sk)); + return jni_utils::new_key_pair(env, util::bytes_from_span(env, pk).get(), util::bytes_from_span(env, sk).get()); + }); +} + +extern "C" +JNIEXPORT jbyteArray JNICALL +Java_network_loki_messenger_libsession_1util_ED25519_generateProMasterKey(JNIEnv *env, jobject thiz, + jbyteArray ed25519_seed) { + return jni_utils::run_catching_cxx_exception_or_throws(env, [=] { + return util::bytes_from_span( + env, + session::ed25519::ed25519_pro_privkey_for_ed25519_seed( + jni_utils::JavaByteArrayRef(env, ed25519_seed).get() + ) + ).release(); + }); +} + +extern "C" +JNIEXPORT jbyteArray JNICALL +Java_network_loki_messenger_libsession_1util_ED25519_positiveEd25519PubKeyFromCurve25519( + JNIEnv *env, jobject thiz, jbyteArray curve25519_pub_key) { + return jni_utils::run_catching_cxx_exception_or_throws(env, [=] { + return util::bytes_from_span( + env, + session::xed25519::pubkey( + jni_utils::JavaByteArrayRef(env, curve25519_pub_key).get()) + ).release(); }); } \ No newline at end of file diff --git a/library/src/main/cpp/encryption.cpp b/library/src/main/cpp/encryption.cpp index 9ce79e0..46c922a 100644 --- a/library/src/main/cpp/encryption.cpp +++ b/library/src/main/cpp/encryption.cpp @@ -3,6 +3,7 @@ #include #include "jni_utils.h" +#include "util.h" #include @@ -29,8 +30,8 @@ Java_network_loki_messenger_libsession_1util_SessionEncrypt_decryptForBlindedRec return jni_utils::new_kotlin_pair( env, - util::jstringFromOptional(env, session_id), - jni_utils::session_bytes_from_range(env, plain_text) + jni_utils::jstring_from_optional(env, session_id).get(), + jni_utils::session_bytes_from_range(env, plain_text).get() ); }); } @@ -49,7 +50,7 @@ Java_network_loki_messenger_libsession_1util_SessionEncrypt_encryptForRecipient( JavaByteArrayRef(env, message).get() ); - return jni_utils::session_bytes_from_range(env, data); + return jni_utils::session_bytes_from_range(env, data).release(); }); } @@ -69,8 +70,8 @@ Java_network_loki_messenger_libsession_1util_SessionEncrypt_decryptIncoming(JNIE return jni_utils::new_kotlin_pair( env, - util::jstringFromOptional(env, session_id), - jni_utils::session_bytes_from_range(env, plain_text) + jni_utils::jstring_from_optional(env, session_id).get(), + jni_utils::session_bytes_from_range(env, plain_text).get() ); }); } @@ -91,7 +92,7 @@ Java_network_loki_messenger_libsession_1util_SessionEncrypt_encryptForBlindedRec JavaByteArrayRef(env, message).get() ); - return jni_utils::session_bytes_from_range(env, data); + return jni_utils::session_bytes_from_range(env, data).release(); }); } @@ -107,7 +108,7 @@ Java_network_loki_messenger_libsession_1util_SessionEncrypt_decryptPushNotificat jni_utils::JavaByteArrayRef(env, secret_key).get() ); - return jni_utils::session_bytes_from_range(env, data); + return jni_utils::session_bytes_from_range(env, data).release(); }); } @@ -125,7 +126,7 @@ Java_network_loki_messenger_libsession_1util_SessionEncrypt_decryptOnsResponse(J nonce ? std::make_optional(jni_utils::JavaByteArrayRef(env, nonce).get()) : std::nullopt ); - return util::jstringFromOptional(env, data); + return jni_utils::jstring_from_optional(env, data).release(); }); } @@ -153,7 +154,7 @@ Java_network_loki_messenger_libsession_1util_SessionEncrypt_calculateECHDAgreeme "secret; is the key valid?"}; } - return util::bytes_from_span(env, shared_secret); + return util::bytes_from_span(env, shared_secret).release(); }); } diff --git a/library/src/main/cpp/group_info.cpp b/library/src/main/cpp/group_info.cpp index bfc13a1..ba66554 100644 --- a/library/src/main/cpp/group_info.cpp +++ b/library/src/main/cpp/group_info.cpp @@ -1,6 +1,14 @@ +#include #include -#include "group_info.h" -#include "session/config/groups/info.hpp" + +#include "util.h" +#include "jni_utils.h" +#include "config_base.h" + +inline auto ptrToInfo(JNIEnv* env, jobject obj) { + return dynamic_cast(ptrToConfigBase(env, obj)); +} + extern "C" JNIEXPORT jlong JNICALL @@ -38,21 +46,21 @@ extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_GroupInfoConfig_getCreated(JNIEnv *env, jobject thiz) { auto group_info = ptrToInfo(env, thiz); - return util::jlongFromOptional(env, group_info->get_created()); + return util::jlongFromOptional(env, group_info->get_created()).release(); } extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_GroupInfoConfig_getDeleteAttachmentsBefore(JNIEnv *env, jobject thiz) { auto group_info = ptrToInfo(env, thiz); - return util::jlongFromOptional(env, group_info->get_delete_attach_before()); + return util::jlongFromOptional(env, group_info->get_delete_attach_before()).release(); } extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_GroupInfoConfig_getDeleteBefore(JNIEnv *env, jobject thiz) { auto group_info = ptrToInfo(env, thiz); - return util::jlongFromOptional(env, group_info->get_delete_before()); + return util::jlongFromOptional(env, group_info->get_delete_before()).release(); } extern "C" @@ -72,7 +80,7 @@ extern "C" JNIEXPORT jstring JNICALL Java_network_loki_messenger_libsession_1util_GroupInfoConfig_getName(JNIEnv *env, jobject thiz) { auto group_info = ptrToInfo(env, thiz); - return util::jstringFromOptional(env, group_info->get_name()); + return jni_utils::jstring_from_optional(env, group_info->get_name()).release(); } extern "C" @@ -80,7 +88,7 @@ JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_GroupInfoConfig_getProfilePic(JNIEnv *env, jobject thiz) { auto group_info = ptrToInfo(env, thiz); - return util::serialize_user_pic(env, group_info->get_profile_pic()); + return util::serialize_user_pic(env, group_info->get_profile_pic()).release(); } extern "C" @@ -155,7 +163,7 @@ extern "C" JNIEXPORT jstring JNICALL Java_network_loki_messenger_libsession_1util_GroupInfoConfig_id(JNIEnv *env, jobject thiz) { auto group_info = ptrToInfo(env, thiz); - return util::jstringFromOptional(env, group_info->id); + return jni_utils::jstring_from_optional(env, group_info->id).release(); } extern "C" @@ -167,8 +175,7 @@ Java_network_loki_messenger_libsession_1util_GroupInfoConfig_getDescription(JNIE if (!description) { return nullptr; } - auto jstring = env->NewStringUTF(description->data()); - return jstring; + return env->NewStringUTF(description->data()); } extern "C" diff --git a/library/src/main/cpp/group_info.h b/library/src/main/cpp/group_info.h deleted file mode 100644 index ab48243..0000000 --- a/library/src/main/cpp/group_info.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef SESSION_ANDROID_GROUP_INFO_H -#define SESSION_ANDROID_GROUP_INFO_H - -#include "util.h" -#include "jni_utils.h" - -inline session::config::groups::Info* ptrToInfo(JNIEnv* env, jobject obj) { - auto configClass = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/GroupInfoConfig")); - jfieldID pointerField = env->GetFieldID(configClass.get(), "pointer", "J"); - return (session::config::groups::Info*) env->GetLongField(obj, pointerField); -} - -#endif //SESSION_ANDROID_GROUP_INFO_H diff --git a/library/src/main/cpp/group_keys.cpp b/library/src/main/cpp/group_keys.cpp index 55f43bc..1c2459d 100644 --- a/library/src/main/cpp/group_keys.cpp +++ b/library/src/main/cpp/group_keys.cpp @@ -1,8 +1,12 @@ -#include "group_keys.h" -#include "group_info.h" -#include "group_members.h" +#include #include "jni_utils.h" +#include "util.h" +#include "config_base.h" + +inline auto ptrToKeys(JNIEnv* env, jobject obj) { + return dynamic_cast(ptrToConfigSig(env, obj)); +} extern "C" JNIEXPORT jint JNICALL @@ -104,8 +108,7 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_pendingKey(JNIEnv * if (!pending) { return nullptr; } - auto pending_bytes = util::bytes_from_span(env, *pending); - return pending_bytes; + return util::bytes_from_span(env, *pending).release(); } extern "C" @@ -117,8 +120,7 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_pendingConfig(JNIEn if (!pending) { return nullptr; } - auto pending_bytes = util::bytes_from_span(env, *pending); - return pending_bytes; + return util::bytes_from_span(env, *pending).release(); } extern "C" @@ -129,8 +131,7 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_rekey(JNIEnv *env, auto info = reinterpret_cast(info_ptr); auto members = reinterpret_cast(members_ptr); auto rekey = keys->rekey(*info, *members); - auto rekey_bytes = util::bytes_from_span(env, rekey); - return rekey_bytes; + return util::bytes_from_span(env, rekey).release(); } extern "C" @@ -138,8 +139,7 @@ JNIEXPORT jbyteArray JNICALL Java_network_loki_messenger_libsession_1util_GroupKeysConfig_dump(JNIEnv *env, jobject thiz) { auto keys = ptrToKeys(env, thiz); auto dump = keys->dump(); - auto byte_array = util::bytes_from_vector(env, dump); - return byte_array; + return util::bytes_from_vector(env, dump).release(); } extern "C" @@ -157,7 +157,7 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_encrypt(JNIEnv *env auto ptr = ptrToKeys(env, thiz); auto plaintext_vector = util::vector_from_bytes(env, plaintext); auto enc = ptr->encrypt_message(plaintext_vector); - return util::bytes_from_vector(env, enc); + return util::bytes_from_vector(env, enc).release(); }); } @@ -172,11 +172,9 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_decrypt(JNIEnv *env auto sender = decrypted.first; auto plaintext = decrypted.second; auto plaintext_bytes = util::bytes_from_vector(env, plaintext); - auto sender_session_id = util::jstringFromOptional(env, sender.data()); - auto pair_class = env->FindClass("kotlin/Pair"); - auto pair_constructor = env->GetMethodID(pair_class, "", "(Ljava/lang/Object;Ljava/lang/Object;)V"); - auto pair_obj = env->NewObject(pair_class, pair_constructor, plaintext_bytes, sender_session_id); - return pair_obj; + auto sender_session_id = jni_utils::jstring_from_optional(env, sender.data()); + + return jni_utils::new_kotlin_pair(env, plaintext_bytes.get(), sender_session_id.get()); }); } @@ -188,10 +186,10 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_keys(JNIEnv *env, j } extern "C" -JNIEXPORT jobject JNICALL +JNIEXPORT jbyteArray JNICALL Java_network_loki_messenger_libsession_1util_GroupKeysConfig_groupEncKey(JNIEnv *env, jobject thiz) { auto ptr = ptrToKeys(env, thiz); - return util::bytes_from_span(env, ptr->group_enc_key()); + return util::bytes_from_span(env, ptr->group_enc_key()).release(); } extern "C" @@ -210,8 +208,7 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_makeSubAccount(JNIE jboolean can_delete) { auto ptr = ptrToKeys(env, thiz); auto new_subaccount_key = ptr->swarm_make_subaccount(jni_utils::JavaStringRef(env, session_id).view(), can_write, can_delete); - auto jbytes = util::bytes_from_vector(env, new_subaccount_key); - return jbytes; + return util::bytes_from_vector(env, new_subaccount_key).release(); } extern "C" @@ -223,8 +220,18 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_getSubAccountToken( jboolean can_delete) { auto ptr = ptrToKeys(env, thiz); auto token = ptr->swarm_subaccount_token(jni_utils::JavaStringRef(env, session_id).view(), can_write, can_delete); - auto jbytes = util::bytes_from_vector(env, token); - return jbytes; + return util::bytes_from_vector(env, token).release(); +} + +static jni_utils::JavaLocalRef deserialize_swarm_auth(JNIEnv *env, session::config::groups::Keys::swarm_auth auth) { + static jni_utils::BasicJavaClassInfo class_info( + env, "network/loki/messenger/libsession_util/GroupKeysConfig$SwarmAuth", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + + auto sub_account = jni_utils::JavaLocalRef(env, env->NewStringUTF(auth.subaccount.data())); + auto sub_account_sig = jni_utils::JavaLocalRef(env, env->NewStringUTF(auth.subaccount_sig.data())); + auto signature = jni_utils::JavaLocalRef(env, env->NewStringUTF(auth.signature.data())); + + return {env, env->NewObject(class_info.java_class, class_info.constructor, sub_account.get(), sub_account_sig.get(), signature.get())}; } extern "C" @@ -237,7 +244,7 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_subAccountSign(JNIE auto message_vector = util::vector_from_bytes(env, message); auto signing_value_vector = util::vector_from_bytes(env, signing_value); auto swarm_auth = ptr->swarm_subaccount_sign(message_vector, signing_value_vector, false); - return util::deserialize_swarm_auth(env, swarm_auth); + return deserialize_swarm_auth(env, swarm_auth).release(); } extern "C" @@ -251,7 +258,7 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_supplementFor(JNIEn user_session_ids.push_back(jni_utils::JavaStringRef(env, jni_utils::JavaLocalRef(env, (jstring)(env->GetObjectArrayElement(j_user_session_ids, i))).get()).copy()); } auto supplement = ptr->key_supplement(user_session_ids); - return util::bytes_from_vector(env, supplement); + return util::bytes_from_vector(env, supplement).release(); } extern "C" JNIEXPORT jint JNICALL diff --git a/library/src/main/cpp/group_keys.h b/library/src/main/cpp/group_keys.h deleted file mode 100644 index 73c41ea..0000000 --- a/library/src/main/cpp/group_keys.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef SESSION_ANDROID_GROUP_KEYS_H -#define SESSION_ANDROID_GROUP_KEYS_H - -#include "util.h" -#include "jni_utils.h" - -inline session::config::groups::Keys* ptrToKeys(JNIEnv* env, jobject obj) { - auto configClass = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/GroupKeysConfig")); - jfieldID pointerField = env->GetFieldID(configClass.get(), "pointer", "J"); - return (session::config::groups::Keys*) env->GetLongField(obj, pointerField); -} - -#endif //SESSION_ANDROID_GROUP_KEYS_H diff --git a/library/src/main/cpp/group_members.cpp b/library/src/main/cpp/group_members.cpp index 8ef9c42..d477725 100644 --- a/library/src/main/cpp/group_members.cpp +++ b/library/src/main/cpp/group_members.cpp @@ -1,6 +1,30 @@ -#include "group_members.h" - #include "jni_utils.h" +#include "util.h" +#include "config_base.h" + +using namespace jni_utils; + +inline session::config::groups::Members* ptrToMembers(JNIEnv* env, jobject obj) { + return dynamic_cast(ptrToConfigBase(env, obj)); +} + +inline session::config::groups::member *ptrToMember(JNIEnv *env, jobject thiz) { + auto ptrField = env->GetFieldID(jni_utils::JavaLocalRef(env, env->GetObjectClass(thiz)).get(), "nativePtr", "J"); + return reinterpret_cast(env->GetLongField(thiz, ptrField)); +} + +static JavaLocalRef serialize_group_member(JNIEnv* env, const session::config::groups::member& member) { + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/util/GroupMember", + "(J)V"); + + return {env, env->NewObject( + class_info.java_class, + class_info.constructor, + reinterpret_cast(new session::config::groups::member(member)) + )}; +} extern "C" JNIEXPORT jlong JNICALL @@ -27,14 +51,14 @@ extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_GroupMembersConfig_all(JNIEnv *env, jobject thiz) { auto config = ptrToMembers(env, thiz); - return jni_utils::jlist_from_collection(env, *config, util::serialize_group_member); + return jlist_from_collection(env, *config, serialize_group_member); } extern "C" JNIEXPORT jboolean JNICALL Java_network_loki_messenger_libsession_1util_GroupMembersConfig_erase(JNIEnv *env, jobject thiz, jstring pub_key_hex) { auto config = ptrToMembers(env, thiz); - auto erased = config->erase(jni_utils::JavaStringRef(env, pub_key_hex).view()); + auto erased = config->erase(JavaStringRef(env, pub_key_hex).view()); return erased; } @@ -42,14 +66,13 @@ extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_GroupMembersConfig_get(JNIEnv *env, jobject thiz, jstring pub_key_hex) { - return jni_utils::run_catching_cxx_exception_or_throws(env, [=]() -> jobject { + return run_catching_cxx_exception_or_throws(env, [=]() -> jobject { auto config = ptrToMembers(env, thiz); - auto member = config->get(jni_utils::JavaStringRef(env, pub_key_hex).view()); + auto member = config->get(JavaStringRef(env, pub_key_hex).view()); if (!member) { return nullptr; } - auto serialized = util::serialize_group_member(env, *member); - return serialized; + return serialize_group_member(env, *member).release(); }); } @@ -59,9 +82,8 @@ Java_network_loki_messenger_libsession_1util_GroupMembersConfig_getOrConstruct(J jobject thiz, jstring pub_key_hex) { auto config = ptrToMembers(env, thiz); - auto member = config->get_or_construct(jni_utils::JavaStringRef(env, pub_key_hex).view()); - auto serialized = util::serialize_group_member(env, member); - return serialized; + auto member = config->get_or_construct(JavaStringRef(env, pub_key_hex).view()); + return serialize_group_member(env, member).release(); } extern "C" @@ -138,14 +160,14 @@ extern "C" JNIEXPORT void JNICALL Java_network_loki_messenger_libsession_1util_util_GroupMember_setName(JNIEnv *env, jobject thiz, jstring name) { - ptrToMember(env, thiz)->set_name(std::string(jni_utils::JavaStringRef(env, name).view())); + ptrToMember(env, thiz)->set_name(std::string(JavaStringRef(env, name).view())); } extern "C" JNIEXPORT jstring JNICALL Java_network_loki_messenger_libsession_1util_util_GroupMember_nameString(JNIEnv *env, jobject thiz) { - return util::jstringFromOptional(env, ptrToMember(env, thiz)->name); + return jni_utils::jstring_from_optional(env, ptrToMember(env, thiz)->name).release(); } extern "C" @@ -165,7 +187,7 @@ extern "C" JNIEXPORT jstring JNICALL Java_network_loki_messenger_libsession_1util_util_GroupMember_accountId(JNIEnv *env, jobject thiz) { - return util::jstringFromOptional(env, ptrToMember(env, thiz)->session_id); + return jni_utils::jstring_from_optional(env, ptrToMember(env, thiz)->session_id).release(); } extern "C" @@ -178,7 +200,7 @@ extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_util_GroupMember_profilePic(JNIEnv *env, jobject thiz) { - return util::serialize_user_pic(env, ptrToMember(env, thiz)->profile_picture); + return util::serialize_user_pic(env, ptrToMember(env, thiz)->profile_picture).release(); } extern "C" @@ -210,5 +232,5 @@ Java_network_loki_messenger_libsession_1util_GroupMembersConfig_setPendingSend(J jobject thiz, jstring pub_key_hex, jboolean pending) { - ptrToMembers(env, thiz)->set_pending_send(jni_utils::JavaStringRef(env, pub_key_hex).copy(), pending); -} \ No newline at end of file + ptrToMembers(env, thiz)->set_pending_send(JavaStringRef(env, pub_key_hex).copy(), pending); +} diff --git a/library/src/main/cpp/group_members.h b/library/src/main/cpp/group_members.h deleted file mode 100644 index e67329c..0000000 --- a/library/src/main/cpp/group_members.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef SESSION_ANDROID_GROUP_MEMBERS_H -#define SESSION_ANDROID_GROUP_MEMBERS_H - -#include "util.h" -#include "jni_utils.h" - -inline session::config::groups::Members* ptrToMembers(JNIEnv* env, jobject obj) { - jfieldID pointerField = env->GetFieldID(jni_utils::JavaLocalRef(env, env->GetObjectClass(obj)).get(), "pointer", "J"); - return (session::config::groups::Members*) env->GetLongField(obj, pointerField); -} - -inline session::config::groups::member *ptrToMember(JNIEnv *env, jobject thiz) { - auto ptrField = env->GetFieldID(jni_utils::JavaLocalRef(env, env->GetObjectClass(thiz)).get(), "nativePtr", "J"); - return reinterpret_cast(env->GetLongField(thiz, ptrField)); -} - - -#endif //SESSION_ANDROID_GROUP_MEMBERS_H diff --git a/library/src/main/cpp/jni_utils.cpp b/library/src/main/cpp/jni_utils.cpp index 75d31cf..3be28c8 100644 --- a/library/src/main/cpp/jni_utils.cpp +++ b/library/src/main/cpp/jni_utils.cpp @@ -2,14 +2,26 @@ namespace jni_utils { jobject new_kotlin_pair(JNIEnv *env, jobject first, jobject second) { - auto pair_class = JavaLocalRef(env, env->FindClass("kotlin/Pair")); - jmethodID constructor = env->GetMethodID(pair_class.get(), "", "(Ljava/lang/Object;Ljava/lang/Object;)V"); - return env->NewObject(pair_class.get(), constructor, first, second); + static BasicJavaClassInfo info(env, "kotlin/Pair", "(Ljava/lang/Object;Ljava/lang/Object;)V"); + return env->NewObject(info.java_class, info.constructor, first, second); } jobject new_key_pair(JNIEnv *env, jbyteArray pubKey, jbyteArray secKey) { - auto kp_class = JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/KeyPair")); - jmethodID kp_constructor = env->GetMethodID(kp_class.get(), "", "([B[B)V"); - return env->NewObject(kp_class.get(), kp_constructor, pubKey, secKey); + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/util/KeyPair", + "([B[B)V" + ); + + return env->NewObject(class_info.java_class, class_info.constructor, pubKey, secKey); + } + + const ArrayListClassInfo & ArrayListClassInfo::get(JNIEnv *env) { + static ArrayListClassInfo instance(env); + return instance; } -} \ No newline at end of file + + ArrayListClassInfo::ArrayListClassInfo(JNIEnv *env) + :BasicJavaClassInfo(env, "java/util/ArrayList", "()V"), + add_method(env->GetMethodID(java_class, "add", "(Ljava/lang/Object;)Z")) {} +} diff --git a/library/src/main/cpp/jni_utils.h b/library/src/main/cpp/jni_utils.h index c74b6b4..2248688 100644 --- a/library/src/main/cpp/jni_utils.h +++ b/library/src/main/cpp/jni_utils.h @@ -6,8 +6,6 @@ #include #include -#include "util.h" - namespace jni_utils { /** * Run a C++ function and catch any exceptions, throwing a Java exception if one is caught, @@ -62,6 +60,10 @@ namespace jni_utils { class JavaLocalRef { JNIEnv *env_; JNIType ref_; + + template + friend class JavaLocalRef; + public: JavaLocalRef(JNIEnv *env, JNIType ref) : env_(env), ref_(ref) {} ~JavaLocalRef() { @@ -69,7 +71,9 @@ namespace jni_utils { env_->DeleteLocalRef(ref_); } } - JavaLocalRef(JavaLocalRef&& other) : env_(other.env_), ref_(other.ref_) { + + template + JavaLocalRef(JavaLocalRef && other) noexcept : env_(other.env_), ref_(other.ref_) { other.ref_ = nullptr; } @@ -85,9 +89,49 @@ namespace jni_utils { inline JNIType get() const { return ref_; } + + // Release the jobject without deleting the local reference + JNIType release() { + auto r = ref_; + ref_ = nullptr; + return r; + } + }; + + struct JavaClassInfo { + jclass const java_class; + + JavaClassInfo(JNIEnv *env, const char *class_name) + :java_class((jclass) env->NewGlobalRef(JavaLocalRef(env, env->FindClass(class_name)).get())) {} + + JavaClassInfo(JNIEnv *env, jobject classInstance) + :java_class((jclass) env->NewGlobalRef(JavaLocalRef(env, env->GetObjectClass(classInstance)).get())) {} + }; + + /** + * Convenient way to store information to create a generic Java object + */ + struct BasicJavaClassInfo : public JavaClassInfo { + jmethodID constructor; + + BasicJavaClassInfo(JNIEnv *env, const char *class_name, const char *constructor_signature) + :JavaClassInfo(env, class_name), + constructor(env->GetMethodID(java_class, "", constructor_signature)) {} }; + /** + * Convenient way to store information to create a Java ArrayList object + */ + struct ArrayListClassInfo: public BasicJavaClassInfo { + jmethodID add_method; + + static const ArrayListClassInfo & get(JNIEnv *env); + + private: + explicit ArrayListClassInfo(JNIEnv *env); + }; + /** * Create a Java List from an iterator. * @@ -102,22 +146,24 @@ namespace jni_utils { */ template jobject jlist_from_iterator(JNIEnv *env, IterBegin begin, IterEnd end, const ConvertItemFunc &func) { - auto list_clazz = JavaLocalRef(env, env->FindClass("java/util/ArrayList")); - jmethodID init = env->GetMethodID(list_clazz.get(), "", "()V"); - jobject our_list = env->NewObject(list_clazz.get(), init); - jmethodID push = env->GetMethodID(list_clazz.get(), "add", "(Ljava/lang/Object;)Z"); + auto info = ArrayListClassInfo::get(env); + + auto our_list = env->NewObject(info.java_class, info.constructor); for (auto iter = begin; iter != end; ++iter) { - std::optional item_java = func(env, *iter); + std::optional> item_java= func(env, *iter); if (item_java.has_value()) { - env->CallBooleanMethod(our_list, push, *item_java); - env->DeleteLocalRef(*item_java); + env->CallBooleanMethod(our_list, info.add_method, item_java->get()); } } return our_list; } + inline JavaLocalRef jstring_from_optional(JNIEnv* env, std::optional optional) { + return {env, optional ? env->NewStringUTF(optional->data()) : nullptr}; + } + /** * Convenience function to create a Java List from a collection, using the lambda to convert * each item. @@ -136,22 +182,22 @@ namespace jni_utils { */ template jobject jstring_list_from_collection(JNIEnv *env, const Collection& obj) { - return jlist_from_collection(env, obj, util::jstringFromOptional); + return jlist_from_collection(env, obj, jstring_from_optional); } /** * Create a Java Bytes class from the collection. The collection must be continous range of byte data */ template - jobject session_bytes_from_range(JNIEnv *env, const Collection &obj) { - auto bytes_clazz = JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/Bytes")); - jmethodID init = env->GetMethodID(bytes_clazz.get(), "", "([B)V"); + JavaLocalRef session_bytes_from_range(JNIEnv *env, const Collection &obj) { + static BasicJavaClassInfo bytes_class( + env, "network/loki/messenger/libsession_util/util/Bytes", "([B)V"); static_assert(sizeof(*obj.data()) == sizeof(jbyte)); auto bytes_array = JavaLocalRef(env, env->NewByteArray(static_cast(obj.size()))); env->SetByteArrayRegion(bytes_array.get(), 0, static_cast(obj.size()), reinterpret_cast(obj.data())); - return env->NewObject(bytes_clazz.get(), init, bytes_array.get()); + return {env, env->NewObject(bytes_class.java_class, bytes_class.constructor, bytes_array.get())}; } /** @@ -194,6 +240,41 @@ namespace jni_utils { std::span get() const { return data; } + + std::span get_raw() const { + return std::span(reinterpret_cast(data.data()), data.size()); + } + }; + + class JavaCharsRef { + JNIEnv *env; + jstring s; + std::span data; + + public: + JavaCharsRef(JNIEnv *env, jstring s) : env(env), s(s) { + const jchar *c_str = env->GetStringChars(s, nullptr); + data = std::span(const_cast(c_str), env->GetStringLength(s)); + } + + JavaCharsRef(const JavaCharsRef &) = delete; + + ~JavaCharsRef() { + env->ReleaseStringChars(s, data.data()); + } + + const jchar* chars() const { + return data.data(); + } + + size_t size() const { + return data.size(); + } + + // Get the data as a span. Only valid during the lifetime of this object. + std::span get() const { + return data; + } }; /** @@ -206,8 +287,13 @@ namespace jni_utils { public: JavaByteArrayRef(JNIEnv *env, jbyteArray byte_array) : env(env), byte_array(byte_array) { - jsize length = env->GetArrayLength(byte_array); - data = std::span(reinterpret_cast(env->GetByteArrayElements(byte_array, nullptr)), length); + if (byte_array) { + jsize length = env->GetArrayLength(byte_array); + data = std::span( + reinterpret_cast(env->GetByteArrayElements(byte_array, + nullptr)), + length); + } } JavaByteArrayRef(const JavaByteArrayRef &) = delete; diff --git a/library/src/main/cpp/pro_backend.cpp b/library/src/main/cpp/pro_backend.cpp new file mode 100644 index 0000000..d40a82f --- /dev/null +++ b/library/src/main/cpp/pro_backend.cpp @@ -0,0 +1,97 @@ +#include +#include "jni_utils.h" +#include "util.h" + +#include + +using namespace jni_utils; + +extern "C" +JNIEXPORT jstring JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_buildAddProPaymentRequestJson( + JNIEnv *env, jobject thiz, jint version, jbyteArray master_private_key, + jbyteArray rotating_private_key, jint payment_provider, jstring payment_id, + jstring order_id) { + return run_catching_cxx_exception_or_throws(env, [=]() { + auto json = session::pro_backend::AddProPaymentRequest::build_to_json( + version, + JavaByteArrayRef(env, master_private_key).get(), + JavaByteArrayRef(env, rotating_private_key).get(), + static_cast(payment_provider), + JavaStringRef(env, payment_id).get_raw(), + JavaStringRef(env, order_id).get_raw()); + + return jni_utils::jstring_from_optional(env, json).release(); + }); +} + +extern "C" +JNIEXPORT jstring JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_buildGenerateProProofRequestJson( + JNIEnv *env, jobject thiz, jint version, jbyteArray master_private_key, + jbyteArray rotating_private_key, jlong now_ms) { + return run_catching_cxx_exception_or_throws(env, [=]() { + auto json = session::pro_backend::GenerateProProofRequest::build_to_json( + version, + JavaByteArrayRef(env, master_private_key).get(), + JavaByteArrayRef(env, rotating_private_key).get(), + std::chrono::sys_time { + std::chrono::milliseconds(now_ms) + } + ); + + return jni_utils::jstring_from_optional(env, json).release(); + }); +} + +extern "C" +JNIEXPORT jstring JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_buildGetProDetailsRequestJson( + JNIEnv *env, jobject thiz, jint version, jbyteArray pro_master_private_key, jlong now_ms, + jint count) { + return run_catching_cxx_exception_or_throws(env, [=]() { + auto json = session::pro_backend::GetProDetailsRequest::build_to_json( + version, + JavaByteArrayRef(env, pro_master_private_key).get(), + std::chrono::sys_time { + std::chrono::milliseconds(now_ms) + }, + static_cast(count) + ); + + return jni_utils::jstring_from_optional(env, json).release(); + }); +} + +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_getPaymentProviderMetadata( + JNIEnv *env, jobject thiz, jint payment_provider) { + return run_catching_cxx_exception_or_throws(env, [=]() -> jobject { + if (payment_provider >= SESSION_PRO_BACKEND_PAYMENT_PROVIDER_COUNT || payment_provider < 0) { + return nullptr; + } + + const auto & metadata = SESSION_PRO_BACKEND_PAYMENT_PROVIDER_METADATA[payment_provider]; + + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/protocol/PaymentProviderMetadata", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + + + return env->NewObject( + class_info.java_class, + class_info.constructor, + jni_utils::jstring_from_optional(env, std::string_view(metadata.device.data, metadata.device.size)).get(), + jni_utils::jstring_from_optional(env, std::string_view(metadata.store.data, metadata.store.size)).get(), + jni_utils::jstring_from_optional(env, std::string_view(metadata.platform.data, metadata.platform.size)).get(), + jni_utils::jstring_from_optional(env, std::string_view(metadata.platform_account.data, metadata.platform_account.size)).get(), + jni_utils::jstring_from_optional(env, std::string_view(metadata.refund_platform_url.data, metadata.refund_platform_url.size)).get(), + jni_utils::jstring_from_optional(env, std::string_view(metadata.refund_support_url.data, metadata.refund_support_url.size)).get(), + jni_utils::jstring_from_optional(env, std::string_view(metadata.refund_status_url.data, metadata.refund_status_url.size)).get(), + jni_utils::jstring_from_optional(env, std::string_view(metadata.update_subscription_url.data, metadata.update_subscription_url.size)).get(), + jni_utils::jstring_from_optional(env, std::string_view(metadata.cancel_subscription_url.data, metadata.cancel_subscription_url.size)).get() + ); + }); +} \ No newline at end of file diff --git a/library/src/main/cpp/pro_proof_util.cpp b/library/src/main/cpp/pro_proof_util.cpp new file mode 100644 index 0000000..48e2fcb --- /dev/null +++ b/library/src/main/cpp/pro_proof_util.cpp @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include + +#include "util.h" +#include "jni_utils.h" +#include "pro_proof_util.h" + +using namespace jni_utils; + +template +static std::array from_hex(std::span input) { + std::array output = {0}; + oxenc::from_hex(input.begin(), input.end(), output.begin()); + return output; +} + +session::ProProof java_to_cpp_proof(JNIEnv *env, jobject proof) { + struct ProProofMethods : public JavaClassInfo { + jmethodID get_version; + jmethodID get_gen_index_hash; + jmethodID get_rotating_pub_key; + jmethodID get_expiry_ms; + jmethodID get_signature; + + ProProofMethods(JNIEnv *env, jobject obj) + : JavaClassInfo(env, obj) + , get_version(env->GetMethodID(java_class, "getVersion", "()I")) + , get_gen_index_hash(env->GetMethodID(java_class, "getGenIndexHashHex", "()Ljava/lang/String;")) + , get_rotating_pub_key(env->GetMethodID(java_class, "getRotatingPubKeyHex", "()Ljava/lang/String;")) + , get_expiry_ms(env->GetMethodID(java_class, "getExpiryMs", "()J")) + , get_signature(env->GetMethodID(java_class, "getSignatureHex", "()Ljava/lang/String;")) {} + }; + + // Cache method IDs + static ProProofMethods methods(env, proof); + + jni_utils::JavaLocalRef gen_index_hash(env, (jstring) env->CallObjectMethod(proof, methods.get_gen_index_hash)); + jni_utils::JavaLocalRef rotating_pub_key(env, (jstring) env->CallObjectMethod(proof, methods.get_rotating_pub_key)); + jni_utils::JavaLocalRef signature(env, (jstring) env->CallObjectMethod(proof, methods.get_signature)); + + return { + .version = static_cast(env->CallIntMethod(proof, methods.get_version)), + .gen_index_hash = from_hex<32>(jni_utils::JavaStringRef(env, gen_index_hash.get()).get()), + .rotating_pubkey = from_hex<32>(jni_utils::JavaStringRef(env, rotating_pub_key.get()).get()), + .expiry_unix_ts = std::chrono::sys_time( + std::chrono::milliseconds(env->CallLongMethod(proof, methods.get_expiry_ms))), + .sig = from_hex<64>(jni_utils::JavaStringRef(env, signature.get()).get()), + }; +} + +JavaLocalRef cpp_to_java_proof(JNIEnv *env, const session::ProProof &proof) { + static BasicJavaClassInfo class_info(env, + "network/loki/messenger/libsession_util/pro/ProProof", + "(I[B[BJ[B)V"); + + return {env, env->NewObject( + class_info.java_class, + class_info.constructor, + static_cast(proof.version), + util::bytes_from_span(env, proof.gen_index_hash).get(), + util::bytes_from_span(env, proof.rotating_pubkey).get(), + static_cast(proof.expiry_unix_ts.time_since_epoch().count()), + util::bytes_from_span(env, proof.sig).get() + )}; +} + +extern "C" +JNIEXPORT jint JNICALL +Java_network_loki_messenger_libsession_1util_pro_ProProof_nativeStatus(JNIEnv *env, jobject thiz, + jlong now_unix_ts, + jbyteArray verify_pub_key, + jbyteArray signed_message_data, + jbyteArray signed_message_signature) { + return run_catching_cxx_exception_or_throws(env, [=]() { + std::optional signed_msg; + JavaByteArrayRef signed_message_data_ref(env, signed_message_data); + JavaByteArrayRef signed_message_signature_ref(env, signed_message_signature); + + if (signed_message_data && signed_message_signature) { + signed_msg.emplace(session::ProSignedMessage { + .sig = signed_message_signature_ref.get(), + .msg = signed_message_data_ref.get(), + }); + } + + return static_cast(java_to_cpp_proof(env, thiz).status( + JavaByteArrayRef(env, verify_pub_key).get(), + std::chrono::sys_time{std::chrono::milliseconds(now_unix_ts)}, + signed_msg + )); + }); +} \ No newline at end of file diff --git a/library/src/main/cpp/pro_proof_util.h b/library/src/main/cpp/pro_proof_util.h new file mode 100644 index 0000000..c91d97b --- /dev/null +++ b/library/src/main/cpp/pro_proof_util.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +#include "jni_utils.h" + +session::ProProof java_to_cpp_proof(JNIEnv *, jobject proof); +jni_utils::JavaLocalRef cpp_to_java_proof(JNIEnv *, const session::ProProof &proof); \ No newline at end of file diff --git a/library/src/main/cpp/protocol.cpp b/library/src/main/cpp/protocol.cpp index 653e54a..dae45d4 100644 --- a/library/src/main/cpp/protocol.cpp +++ b/library/src/main/cpp/protocol.cpp @@ -3,74 +3,52 @@ #include #include "jni_utils.h" +#include "pro_proof_util.h" +#include "util.h" using namespace jni_utils; -static JavaLocalRef serializeProStatus(JNIEnv *env, const std::optional & pro) { - if (!pro.has_value()) { - JavaLocalRef noneClass(env, env->FindClass("network/loki/messenger/libsession_util/protocol/ProStatus$None")); - auto fieldId = env->GetStaticFieldID( - noneClass.get(), - "INSTANCE", "Lnetwork/loki/messenger/libsession_util/protocol/ProStatus$None;"); - return {env, env->GetStaticObjectField(noneClass.get(), fieldId)}; - } - - if (pro->status == session::ProStatus::Valid) { - JavaLocalRef validClass(env, env->FindClass("network/loki/messenger/libsession_util/protocol/ProStatus$Valid")); - auto init = env->GetMethodID(validClass.get(), "", "(JJ)V"); - return {env, env->NewObject(validClass.get(), init, - static_cast(pro->proof.expiry_unix_ts.time_since_epoch().count()), - static_cast(pro->features))}; - } - - JavaLocalRef invalidClass(env, env->FindClass("network/loki/messenger/libsession_util/protocol/ProStatus$Invalid")); - auto fieldId = env->GetStaticFieldID( - invalidClass.get(), - "INSTANCE", "Lnetwork/loki/messenger/libsession_util/protocol/ProStatus$Invalid;"); - return {env, env->GetStaticObjectField(invalidClass.get(), fieldId)}; -} - static JavaLocalRef serializeEnvelop(JNIEnv *env, const session::Envelope &envelope) { - JavaLocalRef envelopClass(env, env->FindClass("network/loki/messenger/libsession_util/protocol/Envelope")); - jmethodID init = env->GetMethodID( - envelopClass.get(), - "", - "(J[BJ[B)V" - ); + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/protocol/Envelope", + "(J[BJ[B)V"); - return {env, env->NewObject(envelopClass.get(), - init, + return {env, env->NewObject(class_info.java_class, + class_info.constructor, static_cast(envelope.timestamp.count()), (envelope.flags & SESSION_PROTOCOL_ENVELOPE_FLAGS_SOURCE) - ? util::bytes_from_span(env, envelope.source) - : nullptr, + ? util::bytes_from_span(env, envelope.source).get() + : nullptr, (envelope.flags & SESSION_PROTOCOL_ENVELOPE_FLAGS_SERVER_TIMESTAMP) - ? static_cast(envelope.server_timestamp) - : 0, - util::bytes_from_span(env, envelope.pro_sig))}; + ? static_cast(envelope.server_timestamp) + : 0, + util::bytes_from_span(env, envelope.pro_sig).get())}; } -static jobject serializeDecodedEnvelope(JNIEnv *env, const session::DecodedEnvelope &envelop) { - JavaLocalRef sender_ed25519(env, util::bytes_from_span(env, envelop.sender_ed25519_pubkey)); - JavaLocalRef sender_x25519(env, util::bytes_from_span(env, envelop.sender_x25519_pubkey)); - JavaLocalRef content(env, util::bytes_from_vector(env, envelop.content_plaintext)); - - JavaLocalRef envelopClass(env, env->FindClass("network/loki/messenger/libsession_util/protocol/DecodedEnvelope")); - jmethodID init = env->GetMethodID( - envelopClass.get(), - "", - "(Lnetwork/loki/messenger/libsession_util/protocol/Envelope;Lnetwork/loki/messenger/libsession_util/protocol/ProStatus;[B[B[BJ)V" +static JavaLocalRef serializeDecodedEnvelope(JNIEnv *env, const session::DecodedEnvelope &envelop) { + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/protocol/DecodedEnvelope", + "(Lnetwork/loki/messenger/libsession_util/protocol/Envelope;ILnetwork/loki/messenger/libsession_util/pro/ProProof;J[B[B[BJ)V" ); - return env->NewObject(envelopClass.get(), init, + JavaLocalRef sender_ed25519 = util::bytes_from_span(env, envelop.sender_ed25519_pubkey); + JavaLocalRef sender_x25519 = util::bytes_from_span(env, envelop.sender_x25519_pubkey); + JavaLocalRef content = util::bytes_from_vector(env, envelop.content_plaintext); + + return {env, env->NewObject(class_info.java_class, class_info.constructor, serializeEnvelop(env, envelop.envelope).get(), - serializeProStatus(env, envelop.pro).get(), + envelop.pro ? static_cast(envelop.pro->status) + : static_cast(-1), + envelop.pro ? cpp_to_java_proof(env, envelop.pro->proof).get() : nullptr, + static_cast(envelop.pro ? envelop.pro->features : 0), content.get(), sender_ed25519.get(), sender_x25519.get(), - static_cast(envelop.envelope.timestamp.count())); + static_cast(envelop.envelope.timestamp.count()))}; } @@ -89,11 +67,12 @@ Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_encodeFor1 session::encode_for_1o1( JavaByteArrayRef(env, plaintext).get(), JavaByteArrayRef(env, my_ed25519_priv_key).get(), - std::chrono::milliseconds { timestamp_ms }, + std::chrono::milliseconds{timestamp_ms}, *java_to_cpp_array<33>(env, recipient_pub_key), - rotating_key ? std::optional(JavaByteArrayRef(env, rotating_key).get()) : std::nullopt + rotating_key ? std::optional(JavaByteArrayRef(env, rotating_key).get()) + : std::nullopt ) - ); + ).release(); }); } @@ -109,12 +88,13 @@ Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_encodeForC session::encode_for_community_inbox( JavaByteArrayRef(env, plaintext).get(), JavaByteArrayRef(env, my_ed25519_priv_key).get(), - std::chrono::milliseconds { timestamp_ms }, + std::chrono::milliseconds{timestamp_ms}, *java_to_cpp_array<33>(env, recipient_pub_key), *java_to_cpp_array<32>(env, community_server_pub_key), - rotating_key ? std::optional(JavaByteArrayRef(env, rotating_key).get()) : std::nullopt + rotating_key ? std::optional(JavaByteArrayRef(env, rotating_key).get()) + : std::nullopt ) - ); + ).release(); }); } @@ -139,12 +119,13 @@ Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_encodeForG session::encode_for_group( JavaByteArrayRef(env, plaintext).get(), JavaByteArrayRef(env, my_ed25519_priv_key).get(), - std::chrono::milliseconds { timestamp_ms }, + std::chrono::milliseconds{timestamp_ms}, *java_to_cpp_array<33>(env, group_ed25519_public_key), group_private_key, - rotating_key ? std::optional(JavaByteArrayRef(env, rotating_key).get()) : std::nullopt + rotating_key ? std::optional(JavaByteArrayRef(env, rotating_key).get()) + : std::nullopt ) - ); + ).release(); }); } @@ -158,22 +139,27 @@ Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_decodeForC auto decoded = session::decode_for_community( payload_ref.get(), - std::chrono::sys_time { std::chrono::milliseconds { now_epoch_ms } }, + std::chrono::sys_time{ + std::chrono::milliseconds{now_epoch_ms}}, *java_to_cpp_array<32>(env, pro_backend_pub_key) ); - JavaLocalRef envelopClass(env, env->FindClass("network/loki/messenger/libsession_util/protocol/DecodedCommunityMessage")); + JavaLocalRef envelopClass(env, env->FindClass( + "network/loki/messenger/libsession_util/protocol/DecodedCommunityMessage")); jmethodID init = env->GetMethodID( envelopClass.get(), "", - "(Lnetwork/loki/messenger/libsession_util/protocol/ProStatus;[B)V" + "(ILnetwork/loki/messenger/libsession_util/pro/ProProof;J[B)V" ); return env->NewObject( envelopClass.get(), init, - serializeProStatus(env, decoded.pro).get(), - util::bytes_from_vector(env, decoded.content_plaintext) + decoded.pro ? static_cast(decoded.pro->status) + : static_cast(-1), + decoded.pro ? cpp_to_java_proof(env, decoded.pro->proof).get() : nullptr, + static_cast(decoded.pro ? decoded.pro->features : 0), + util::bytes_from_vector(env, decoded.content_plaintext).get() ); }); } @@ -190,9 +176,10 @@ Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_encodeForC env, session::encode_for_community( JavaByteArrayRef(env, plaintext).get(), - rotating_key ? std::optional(JavaByteArrayRef(env, rotating_key).get()) : std::nullopt + rotating_key ? std::optional(JavaByteArrayRef(env, rotating_key).get()) + : std::nullopt ) - ); + ).release(); }); } @@ -207,18 +194,19 @@ Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_decodeFor1 return run_catching_cxx_exception_or_throws(env, [=] { JavaByteArrayRef key_ref(env, key); - std::array, 1> keys = { key_ref.get() }; + std::array, 1> keys = {key_ref.get()}; - session::DecodeEnvelopeKey decode_key { - .decrypt_keys = std::span(keys.data(), keys.size()), + session::DecodeEnvelopeKey decode_key{ + .decrypt_keys = std::span(keys.data(), keys.size()), }; return serializeDecodedEnvelope(env, session::decode_envelope( decode_key, JavaByteArrayRef(env, payload).get(), - std::chrono::sys_time { std::chrono::milliseconds { now_epoch_ms } }, + std::chrono::sys_time{ + std::chrono::milliseconds{now_epoch_ms}}, *java_to_cpp_array<32>(env, pro_backend_pub_key) - )); + )).release(); }); } @@ -237,13 +225,14 @@ Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_decodeForG std::vector private_keys_refs; std::vector> private_keys_spans; for (int i = 0, size = env->GetArrayLength(group_ed25519_private_keys); i < size; i++) { - auto bytes = reinterpret_cast(env->GetObjectArrayElement(group_ed25519_private_keys, i)); + auto bytes = reinterpret_cast(env->GetObjectArrayElement( + group_ed25519_private_keys, i)); private_keys_spans.emplace_back(private_keys_refs.emplace_back(env, bytes).get()); } JavaByteArrayRef group_pub_key_ref(env, group_ed25519_public_key); - session::DecodeEnvelopeKey decode_key { + session::DecodeEnvelopeKey decode_key{ .group_ed25519_pubkey = std::make_optional(group_pub_key_ref.get()), .decrypt_keys = std::span(private_keys_spans.data(), private_keys_spans.size()), }; @@ -251,8 +240,39 @@ Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_decodeForG return serializeDecodedEnvelope(env, session::decode_envelope( decode_key, JavaByteArrayRef(env, payload).get(), - std::chrono::sys_time { std::chrono::milliseconds { now_epoch_ms } }, + std::chrono::sys_time{ + std::chrono::milliseconds{now_epoch_ms}}, *java_to_cpp_array<32>(env, pro_backend_pub_key) - )); + )).release(); + }); +} + +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_proFeaturesForMessage( + JNIEnv *env, jobject thiz, jstring message_body, jlong proposed_features) { + return run_catching_cxx_exception_or_throws(env, [=] { + JavaCharsRef message_ref(env, message_body); + + auto features = session::pro_features_for_utf16( + reinterpret_cast(message_ref.chars()), + message_ref.size(), + static_cast(proposed_features) + ); + + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/protocol/ProFeaturesForMsg", + "(ILjava/lang/String;JI)V" + ); + + return env->NewObject( + class_info.java_class, + class_info.constructor, + static_cast(features.status), + features.error.empty() ? nullptr : env->NewStringUTF(std::string(features.error).c_str()), + static_cast(features.features), + static_cast(features.codepoint_count) + ); }); } \ No newline at end of file diff --git a/library/src/main/cpp/user_groups.cpp b/library/src/main/cpp/user_groups.cpp index 2a60c3b..936721b 100644 --- a/library/src/main/cpp/user_groups.cpp +++ b/library/src/main/cpp/user_groups.cpp @@ -1,8 +1,261 @@ #include "user_groups.h" #include "oxenc/hex.h" - +#include "util.h" #include "session/ed25519.hpp" +using namespace jni_utils; + +inline session::config::UserGroups* ptrToUserGroups(JNIEnv *env, jobject obj) { + auto configClass = JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/UserGroupsConfig")); + jfieldID pointerField = env->GetFieldID(configClass.get(), "pointer", "J"); + return (session::config::UserGroups*) env->GetLongField(obj, pointerField); +} + +static void deserialize_members_into(JNIEnv *env, jobject members_map, session::config::legacy_group_info& to_append_group) { + auto map_class = JavaLocalRef(env, env->FindClass("java/util/Map")); + auto map_entry_class = JavaLocalRef(env, env->FindClass("java/util/Map$Entry")); + auto set_class = JavaLocalRef(env, env->FindClass("java/util/Set")); + auto iterator_class = JavaLocalRef(env, env->FindClass("java/util/Iterator")); + auto boxed_bool = JavaLocalRef(env, env->FindClass("java/lang/Boolean")); + + jmethodID get_entry_set = env->GetMethodID(map_class.get(), "entrySet", "()Ljava/util/Set;"); + jmethodID get_at = env->GetMethodID(set_class.get(), "iterator", "()Ljava/util/Iterator;"); + jmethodID has_next = env->GetMethodID(iterator_class.get(), "hasNext", "()Z"); + jmethodID next = env->GetMethodID(iterator_class.get(), "next", "()Ljava/lang/Object;"); + jmethodID get_key = env->GetMethodID(map_entry_class.get(), "getKey", "()Ljava/lang/Object;"); + jmethodID get_value = env->GetMethodID(map_entry_class.get(), "getValue", "()Ljava/lang/Object;"); + jmethodID get_bool_value = env->GetMethodID(boxed_bool.get(), "booleanValue", "()Z"); + + auto entry_set = JavaLocalRef(env, env->CallObjectMethod(members_map, get_entry_set)); + auto iterator = JavaLocalRef(env, env->CallObjectMethod(entry_set.get(), get_at)); + + while (env->CallBooleanMethod(iterator.get(), has_next)) { + JavaLocalRef entry(env, env->CallObjectMethod(iterator.get(), next)); + JavaLocalRef key(env, static_cast(env->CallObjectMethod(entry.get(), get_key))); + JavaLocalRef boxed(env, env->CallObjectMethod(entry.get(), get_value)); + bool is_admin = env->CallBooleanMethod(boxed.get(), get_bool_value); + to_append_group.insert(std::string(JavaStringRef(env, key.get()).view()), is_admin); + } +} + + +static session::config::legacy_group_info deserialize_legacy_group_info(JNIEnv *env, jobject info, session::config::UserGroups* conf) { + auto clazz = JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$LegacyGroupInfo")); + auto id_field = env->GetFieldID(clazz.get(), "accountId", "Ljava/lang/String;"); + auto name_field = env->GetFieldID(clazz.get(), "name", "Ljava/lang/String;"); + auto members_field = env->GetFieldID(clazz.get(), "members", "Ljava/util/Map;"); + auto enc_pub_key_method = env->GetMethodID(clazz.get(), "getEncPubKeyAsByteArray", "()[B"); + auto enc_sec_key_method = env->GetMethodID(clazz.get(), "getEncSecKeyAsByteArray", "()[B"); + auto priority_field = env->GetFieldID(clazz.get(), "priority", "J"); + auto disappearing_timer_field = env->GetFieldID(clazz.get(), "disappearingTimer", "J"); + auto joined_at_field = env->GetFieldID(clazz.get(), "joinedAtSecs", "J"); + auto id = JavaLocalRef(env, static_cast(env->GetObjectField(info, id_field))); + auto name = JavaLocalRef(env, static_cast(env->GetObjectField(info, name_field))); + auto members_map = JavaLocalRef(env, env->GetObjectField(info, members_field)); + auto enc_pub_key = JavaLocalRef(env, static_cast(env->CallObjectMethod(info, enc_pub_key_method))); + auto enc_sec_key = JavaLocalRef(env, static_cast(env->CallObjectMethod(info, enc_sec_key_method))); + int priority = env->GetLongField(info, priority_field); + long joined_at = env->GetLongField(info, joined_at_field); + + auto info_deserialized = conf->get_or_construct_legacy_group(JavaStringRef(env, id.get()).view()); + + auto current_members = info_deserialized.members(); + for (auto member = current_members.begin(); member != current_members.end(); ++member) { + info_deserialized.erase(member->first); + } + deserialize_members_into(env, members_map.get(), info_deserialized); + info_deserialized.name = JavaStringRef(env, name.get()).view(); + info_deserialized.enc_pubkey = JavaByteArrayRef(env, enc_pub_key.get()).copy(); + info_deserialized.enc_seckey = JavaByteArrayRef(env, enc_sec_key.get()).copy(); + info_deserialized.priority = priority; + info_deserialized.disappearing_timer = std::chrono::seconds(env->GetLongField(info, disappearing_timer_field)); + info_deserialized.joined_at = joined_at; + return info_deserialized; +} + +static session::config::community_info deserialize_community_info(JNIEnv *env, jobject info, session::config::UserGroups* conf) { + struct ClassInfo : JavaClassInfo { + jmethodID base_info_getter; + jmethodID priority_getter; + + ClassInfo(JNIEnv *env): JavaClassInfo(env, "network/loki/messenger/libsession_util/util/GroupInfo$CommunityGroupInfo"), + base_info_getter(env->GetMethodID(java_class, "getCommunity", "()Lnetwork/loki/messenger/libsession_util/util/BaseCommunityInfo;")), + priority_getter(env->GetMethodID(java_class, "getPriority", "()J")) {} + }; + + static ClassInfo class_info(env); + + auto base_community_info = JavaLocalRef(env, env->CallObjectMethod(info, class_info.base_info_getter)); + auto deserialized_base_info = deserialize_base_community(env, base_community_info.get()); + int deserialized_priority = env->CallLongMethod(info, class_info.priority_getter); + auto community_info = conf->get_or_construct_community(deserialized_base_info.base_url(), deserialized_base_info.room(), deserialized_base_info.pubkey_hex()); + community_info.priority = deserialized_priority; + return community_info; +} + +static jobject serialize_members(JNIEnv *env, std::map members_map) { + auto map_class = JavaLocalRef(env, env->FindClass("java/util/HashMap")); + auto boxed_bool = JavaLocalRef(env, env->FindClass("java/lang/Boolean")); + jmethodID map_constructor = env->GetMethodID(map_class.get(), "", "()V"); + jmethodID insert = env->GetMethodID(map_class.get(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + jmethodID new_bool = env->GetMethodID(boxed_bool.get(), "", "(Z)V"); + + auto new_map = env->NewObject(map_class.get(), map_constructor); + for (auto it = members_map.begin(); it != members_map.end(); it++) { + auto account_id = JavaLocalRef(env, env->NewStringUTF(it->first.data())); + bool is_admin = it->second; + auto jbool = JavaLocalRef(env, env->NewObject(boxed_bool.get(), new_bool, is_admin)); + env->CallObjectMethod(new_map, insert, account_id.get(), jbool.get()); + } + return new_map; +} + +static JavaLocalRef serialize_legacy_group_info(JNIEnv *env, session::config::legacy_group_info info) { + auto account_id = JavaLocalRef(env, env->NewStringUTF(info.session_id.data())); + auto name = JavaLocalRef(env, env->NewStringUTF(info.name.data())); + auto members = JavaLocalRef(env, serialize_members(env, info.members())); + auto enc_pubkey = session_bytes_from_range(env, info.enc_pubkey); + auto enc_seckey = session_bytes_from_range(env, info.enc_seckey); + long long priority = info.priority; + long long joined_at = info.joined_at; + + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/util/GroupInfo$LegacyGroupInfo", + "(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lnetwork/loki/messenger/libsession_util/util/Bytes;Lnetwork/loki/messenger/libsession_util/util/Bytes;JJJ)V" + ); + + return {env, env->NewObject(class_info.java_class, class_info.constructor, + account_id.get(), name.get(), members.get(), enc_pubkey.get(), + enc_seckey.get(), priority, + (jlong) info.disappearing_timer.count(), joined_at)}; +} + +static JavaLocalRef serialize_closed_group_info(JNIEnv* env, session::config::group_info info) { + auto session_id = jstring_from_optional(env, info.id); + auto admin_bytes = JavaLocalRef(env, info.secretkey.empty() ? nullptr : session_bytes_from_range(env, info.secretkey).release()); + auto auth_bytes = JavaLocalRef(env, info.auth_data.empty() ? nullptr : session_bytes_from_range(env, info.auth_data).release()); + auto name = jstring_from_optional(env, info.name); + + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/util/GroupInfo$ClosedGroupInfo", + "(Ljava/lang/String;Lnetwork/loki/messenger/libsession_util/util/Bytes;Lnetwork/loki/messenger/libsession_util/util/Bytes;JZLjava/lang/String;ZZJ)V" + ); + + return {env, env->NewObject(class_info.java_class, class_info.constructor, + session_id.get(), admin_bytes.get(), auth_bytes.get(), (jlong)info.priority, info.invited, name.get(), + info.kicked(), info.is_destroyed(), info.joined_at)}; +} + +static session::config::group_info deserialize_closed_group_info(JNIEnv* env, jobject info_serialized) { + struct ClassInfo : public JavaClassInfo { + jmethodID id_getter; + jmethodID secret_method; + jmethodID auth_method; + jmethodID name_getter; + jmethodID priority_getter; + jmethodID invited_getter; + jmethodID destroyed_getter; + jmethodID kicked_getter; + jmethodID joined_at_getter; + + ClassInfo(JNIEnv *env, jobject obj) + : JavaClassInfo(env, obj) + , id_getter(env->GetMethodID(java_class, "getGroupAccountId", "()Ljava/lang/String;")) + , secret_method(env->GetMethodID(java_class, "getAdminKeyAsByteArray", "()[B")) + , auth_method(env->GetMethodID(java_class, "getAuthDataAsByteArray", "()[B")) + , name_getter(env->GetMethodID(java_class, "getName", "()Ljava/lang/String;")) + , priority_getter(env->GetMethodID(java_class, "getPriority", "()J")) + , invited_getter(env->GetMethodID(java_class, "getInvited", "()Z")) + , destroyed_getter(env->GetMethodID(java_class, "getDestroyed", "()Z")) + , kicked_getter(env->GetMethodID(java_class, "getKicked", "()Z")) + , joined_at_getter(env->GetMethodID(java_class, "getJoinedAtSecs", "()J")) + {} + }; + + static ClassInfo class_info(env, info_serialized); + + auto id_jobject = JavaLocalRef(env, static_cast(env->CallObjectMethod(info_serialized, class_info.id_getter))); + auto secret_jBytes = JavaLocalRef(env, (jbyteArray)env->CallObjectMethod(info_serialized, class_info.secret_method)); + auto auth_jBytes = JavaLocalRef(env, (jbyteArray)env->CallObjectMethod(info_serialized, class_info.auth_method)); + auto name_jstring = JavaLocalRef(env, (jstring)env->CallObjectMethod(info_serialized, class_info.name_getter)); + + auto secret_bytes = util::vector_from_bytes(env, secret_jBytes.get()); + auto auth_bytes = util::vector_from_bytes(env, auth_jBytes.get()); + + session::config::group_info group_info(JavaStringRef(env, id_jobject.get()).copy()); + group_info.auth_data = auth_bytes; + group_info.secretkey = secret_bytes; + group_info.priority = env->CallLongMethod(info_serialized, class_info.priority_getter); + group_info.invited = env->CallBooleanMethod(info_serialized, class_info.invited_getter); + group_info.name = JavaStringRef(env, name_jstring.get()).view(); + group_info.joined_at = env->CallLongMethod(info_serialized, class_info.joined_at_getter); + + if (env->CallBooleanMethod(info_serialized, class_info.kicked_getter)) { + group_info.mark_kicked(); + } + + if (env->CallBooleanMethod(info_serialized, class_info.destroyed_getter)) { + group_info.mark_destroyed(); + } + + return group_info; +} + +static JavaLocalRef serialize_community_info(JNIEnv *env, const session::config::community_info &info) { + auto priority = (long long)info.priority; + auto serialized_info = serialize_base_community(env, info); + + static BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/util/GroupInfo$CommunityGroupInfo", + "(Lnetwork/loki/messenger/libsession_util/util/BaseCommunityInfo;J)V" + ); + + return {env, env->NewObject(class_info.java_class, class_info.constructor, serialized_info.get(), priority)}; +} + +JavaLocalRef serialize_base_community(JNIEnv *env, const session::config::community& community) { + static BasicJavaClassInfo class_info(env, "network/loki/messenger/libsession_util/util/BaseCommunityInfo", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + + auto base_url = jni_utils::JavaLocalRef(env, env->NewStringUTF(community.base_url().data())); + auto room = jni_utils::JavaLocalRef(env, env->NewStringUTF(community.room().data())); + auto pubkey_jstring = jni_utils::JavaLocalRef(env, env->NewStringUTF(community.pubkey_hex().data())); + return {env, env->NewObject(class_info.java_class, + class_info.constructor, + base_url.get(), room.get(), pubkey_jstring.get())}; +} + +session::config::community deserialize_base_community(JNIEnv *env, jobject base_community) { + struct ClassInfo : public JavaClassInfo { + jmethodID base_url_getter; + jmethodID room_getter; + jmethodID pubkey_getter; + + ClassInfo(JNIEnv *env, jobject obj) + : JavaClassInfo(env, obj) + , base_url_getter(env->GetMethodID(java_class, "getBaseUrl", "()Ljava/lang/String;")) + , room_getter(env->GetMethodID(java_class, "getRoom", "()Ljava/lang/String;")) + , pubkey_getter(env->GetMethodID(java_class, "getPubKeyHex", "()Ljava/lang/String;")) + {} + }; + + static ClassInfo class_info(env, base_community); + + jni_utils::JavaLocalRef base_url(env, (jstring)env->CallObjectMethod(base_community, class_info.base_url_getter)); + jni_utils::JavaLocalRef room(env, (jstring)env->CallObjectMethod(base_community, class_info.room_getter)); + jni_utils::JavaLocalRef pub_key_hex(env, (jstring)env->CallObjectMethod(base_community, class_info.pubkey_getter)); + + return session::config::community( + jni_utils::JavaStringRef(env, base_url.get()).view(), + jni_utils::JavaStringRef(env, room.get()).view(), + jni_utils::JavaStringRef(env, pub_key_hex.get()).view() + ); +} + + extern "C" JNIEXPORT jint JNICALL Java_network_loki_messenger_libsession_1util_util_GroupInfo_00024LegacyGroupInfo_00024Companion_NAME_1MAX_1LENGTH( @@ -18,10 +271,10 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getCommunityInfo(J jstring room) { auto conf = ptrToUserGroups(env, thiz); - auto community = conf->get_community(jni_utils::JavaStringRef(env, base_url).view(), jni_utils::JavaStringRef(env, room).view()); + auto community = conf->get_community(JavaStringRef(env, base_url).view(), JavaStringRef(env, room).view()); if (community) { - return serialize_community_info(env, *community); + return serialize_community_info(env, *community).release(); } return nullptr; @@ -33,10 +286,10 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getLegacyGroupInfo jobject thiz, jstring account_id) { auto conf = ptrToUserGroups(env, thiz); - auto legacy_group = conf->get_legacy_group(jni_utils::JavaStringRef(env, account_id).view()); + auto legacy_group = conf->get_legacy_group(JavaStringRef(env, account_id).view()); jobject return_group = nullptr; if (legacy_group) { - return_group = serialize_legacy_group_info(env, *legacy_group); + return_group = serialize_legacy_group_info(env, *legacy_group).release(); } return return_group; } @@ -48,11 +301,11 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getOrConstructComm auto conf = ptrToUserGroups(env, thiz); auto group = conf->get_or_construct_community( - jni_utils::JavaStringRef(env, base_url).view(), - jni_utils::JavaStringRef(env, room).view(), - jni_utils::JavaStringRef(env, pub_key_hex).view()); + JavaStringRef(env, base_url).view(), + JavaStringRef(env, room).view(), + JavaStringRef(env, pub_key_hex).view()); - return serialize_community_info(env, group); + return serialize_community_info(env, group).release(); } extern "C" @@ -60,8 +313,8 @@ JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getOrConstructLegacyGroupInfo( JNIEnv *env, jobject thiz, jstring account_id) { auto conf = ptrToUserGroups(env, thiz); - auto group = conf->get_or_construct_legacy_group(jni_utils::JavaStringRef(env, account_id).view()); - return serialize_legacy_group_info(env, group); + auto group = conf->get_or_construct_legacy_group(JavaStringRef(env, account_id).view()); + return serialize_legacy_group_info(env, group).release(); } extern "C" @@ -127,13 +380,13 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_sizeLegacyGroupInf extern "C" JNIEXPORT jlong JNICALL Java_network_loki_messenger_libsession_1util_UserGroupsConfig_size(JNIEnv *env, jobject thiz) { - auto conf = ptrToConvoInfo(env, thiz); + auto conf = ptrToUserGroups(env, thiz); return conf->size(); } inline jobject iterator_as_java_list(JNIEnv *env, session::config::UserGroups::iterator begin, session::config::UserGroups::iterator end) { - return jni_utils::jlist_from_iterator(env, begin, end, [](JNIEnv *env, const session::config::UserGroups::value_type &item) { - std::optional serialized = std::nullopt; + return jlist_from_iterator(env, begin, end, [](JNIEnv *env, const session::config::UserGroups::value_type &item) { + std::optional> serialized = std::nullopt; if (auto* lgc = std::get_if(&item)) { serialized = serialize_legacy_group_info(env, *lgc); } else if (auto* community = std::get_if(&item)) { @@ -175,7 +428,7 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_eraseCommunity__Ln jobject thiz, jobject base_community_info) { auto conf = ptrToUserGroups(env, thiz); - auto base_community = util::deserialize_base_community(env, base_community_info); + auto base_community = deserialize_base_community(env, base_community_info); return conf->erase_community(base_community.base_url(),base_community.room()); } @@ -185,8 +438,8 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_eraseCommunity__Lj JNIEnv *env, jobject thiz, jstring server, jstring room) { auto conf = ptrToUserGroups(env, thiz); auto community = conf->get_community( - jni_utils::JavaStringRef(env, server).view(), - jni_utils::JavaStringRef(env, room).view()); + JavaStringRef(env, server).view(), + JavaStringRef(env, room).view()); bool deleted = false; if (community) { deleted = conf->erase(*community); @@ -200,7 +453,7 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_eraseLegacyGroup(J jobject thiz, jstring account_id) { auto conf = ptrToUserGroups(env, thiz); - bool return_bool = conf->erase_legacy_group(jni_utils::JavaStringRef(env, account_id).view()); + bool return_bool = conf->erase_legacy_group(JavaStringRef(env, account_id).view()); return return_bool; } @@ -210,10 +463,10 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getClosedGroup(JNI jobject thiz, jstring session_id) { auto config = ptrToUserGroups(env, thiz); - auto group = config->get_group(jni_utils::JavaStringRef(env, session_id).view()); + auto group = config->get_group(JavaStringRef(env, session_id).view()); if (group) { - return serialize_closed_group_info(env, *group); + return serialize_closed_group_info(env, *group).release(); } return nullptr; } @@ -224,8 +477,8 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getOrConstructClos jobject thiz, jstring session_id) { auto config = ptrToUserGroups(env, thiz); - auto group = config->get_or_construct_group(jni_utils::JavaStringRef(env, session_id).view()); - return serialize_closed_group_info(env, group); + auto group = config->get_or_construct_group(JavaStringRef(env, session_id).view()); + return serialize_closed_group_info(env, group).release(); } extern "C" @@ -243,7 +496,7 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_createGroup(JNIEnv auto config = ptrToUserGroups(env, thiz); auto group = config->create_group(); - return serialize_closed_group_info(env, group); + return serialize_closed_group_info(env, group).release(); } extern "C" @@ -260,7 +513,7 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_eraseClosedGroup(J jobject thiz, jstring session_id) { auto config = ptrToUserGroups(env, thiz); - bool return_value = config->erase_group(jni_utils::JavaStringRef(env, session_id).view()); + bool return_value = config->erase_group(JavaStringRef(env, session_id).view()); return return_value; } @@ -275,7 +528,7 @@ Java_network_loki_messenger_libsession_1util_util_GroupInfo_00024ClosedGroupInfo } auto admin_key = session::ed25519::ed25519_key_pair( - jni_utils::JavaByteArrayRef(env, seed).get()).second; + JavaByteArrayRef(env, seed).get()).second; - return util::bytes_from_span(env, std::span(admin_key.data(), admin_key.size())); + return util::bytes_from_span(env, std::span(admin_key.data(), admin_key.size())).release(); } \ No newline at end of file diff --git a/library/src/main/cpp/user_groups.h b/library/src/main/cpp/user_groups.h index 7efbafa..1a9f76d 100644 --- a/library/src/main/cpp/user_groups.h +++ b/library/src/main/cpp/user_groups.h @@ -2,182 +2,10 @@ #ifndef SESSION_ANDROID_USER_GROUPS_H #define SESSION_ANDROID_USER_GROUPS_H -#include "jni.h" -#include "util.h" #include "jni_utils.h" -#include "conversation.h" -#include "session/config/user_groups.hpp" +#include -inline session::config::UserGroups* ptrToUserGroups(JNIEnv *env, jobject obj) { - auto configClass = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/UserGroupsConfig")); - jfieldID pointerField = env->GetFieldID(configClass.get(), "pointer", "J"); - return (session::config::UserGroups*) env->GetLongField(obj, pointerField); -} - -inline void deserialize_members_into(JNIEnv *env, jobject members_map, session::config::legacy_group_info& to_append_group) { - auto map_class = jni_utils::JavaLocalRef(env, env->FindClass("java/util/Map")); - auto map_entry_class = jni_utils::JavaLocalRef(env, env->FindClass("java/util/Map$Entry")); - auto set_class = jni_utils::JavaLocalRef(env, env->FindClass("java/util/Set")); - auto iterator_class = jni_utils::JavaLocalRef(env, env->FindClass("java/util/Iterator")); - auto boxed_bool = jni_utils::JavaLocalRef(env, env->FindClass("java/lang/Boolean")); - - jmethodID get_entry_set = env->GetMethodID(map_class.get(), "entrySet", "()Ljava/util/Set;"); - jmethodID get_at = env->GetMethodID(set_class.get(), "iterator", "()Ljava/util/Iterator;"); - jmethodID has_next = env->GetMethodID(iterator_class.get(), "hasNext", "()Z"); - jmethodID next = env->GetMethodID(iterator_class.get(), "next", "()Ljava/lang/Object;"); - jmethodID get_key = env->GetMethodID(map_entry_class.get(), "getKey", "()Ljava/lang/Object;"); - jmethodID get_value = env->GetMethodID(map_entry_class.get(), "getValue", "()Ljava/lang/Object;"); - jmethodID get_bool_value = env->GetMethodID(boxed_bool.get(), "booleanValue", "()Z"); - - auto entry_set = jni_utils::JavaLocalRef(env, env->CallObjectMethod(members_map, get_entry_set)); - auto iterator = jni_utils::JavaLocalRef(env, env->CallObjectMethod(entry_set.get(), get_at)); - - while (env->CallBooleanMethod(iterator.get(), has_next)) { - jni_utils::JavaLocalRef entry(env, env->CallObjectMethod(iterator.get(), next)); - jni_utils::JavaLocalRef key(env, static_cast(env->CallObjectMethod(entry.get(), get_key))); - jni_utils::JavaLocalRef boxed(env, env->CallObjectMethod(entry.get(), get_value)); - bool is_admin = env->CallBooleanMethod(boxed.get(), get_bool_value); - to_append_group.insert(std::string(jni_utils::JavaStringRef(env, key.get()).view()), is_admin); - } -} - -inline session::config::legacy_group_info deserialize_legacy_group_info(JNIEnv *env, jobject info, session::config::UserGroups* conf) { - auto clazz = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$LegacyGroupInfo")); - auto id_field = env->GetFieldID(clazz.get(), "accountId", "Ljava/lang/String;"); - auto name_field = env->GetFieldID(clazz.get(), "name", "Ljava/lang/String;"); - auto members_field = env->GetFieldID(clazz.get(), "members", "Ljava/util/Map;"); - auto enc_pub_key_method = env->GetMethodID(clazz.get(), "getEncPubKeyAsByteArray", "()[B"); - auto enc_sec_key_method = env->GetMethodID(clazz.get(), "getEncSecKeyAsByteArray", "()[B"); - auto priority_field = env->GetFieldID(clazz.get(), "priority", "J"); - auto disappearing_timer_field = env->GetFieldID(clazz.get(), "disappearingTimer", "J"); - auto joined_at_field = env->GetFieldID(clazz.get(), "joinedAtSecs", "J"); - auto id = jni_utils::JavaLocalRef(env, static_cast(env->GetObjectField(info, id_field))); - auto name = jni_utils::JavaLocalRef(env, static_cast(env->GetObjectField(info, name_field))); - auto members_map = jni_utils::JavaLocalRef(env, env->GetObjectField(info, members_field)); - auto enc_pub_key = jni_utils::JavaLocalRef(env, static_cast(env->CallObjectMethod(info, enc_pub_key_method))); - auto enc_sec_key = jni_utils::JavaLocalRef(env, static_cast(env->CallObjectMethod(info, enc_sec_key_method))); - int priority = env->GetLongField(info, priority_field); - long joined_at = env->GetLongField(info, joined_at_field); - - auto info_deserialized = conf->get_or_construct_legacy_group(jni_utils::JavaStringRef(env, id.get()).view()); - - auto current_members = info_deserialized.members(); - for (auto member = current_members.begin(); member != current_members.end(); ++member) { - info_deserialized.erase(member->first); - } - deserialize_members_into(env, members_map.get(), info_deserialized); - info_deserialized.name = jni_utils::JavaStringRef(env, name.get()).view(); - info_deserialized.enc_pubkey = jni_utils::JavaByteArrayRef(env, enc_pub_key.get()).copy(); - info_deserialized.enc_seckey = jni_utils::JavaByteArrayRef(env, enc_sec_key.get()).copy(); - info_deserialized.priority = priority; - info_deserialized.disappearing_timer = std::chrono::seconds(env->GetLongField(info, disappearing_timer_field)); - info_deserialized.joined_at = joined_at; - return info_deserialized; -} - -inline session::config::community_info deserialize_community_info(JNIEnv *env, jobject info, session::config::UserGroups* conf) { - auto clazz = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$CommunityGroupInfo")); - auto base_info = env->GetFieldID(clazz.get(), "community", "Lnetwork/loki/messenger/libsession_util/util/BaseCommunityInfo;"); - auto priority = env->GetFieldID(clazz.get(), "priority", "J"); - auto base_community_info = jni_utils::JavaLocalRef(env, env->GetObjectField(info, base_info)); - auto deserialized_base_info = util::deserialize_base_community(env, base_community_info.get()); - int deserialized_priority = env->GetLongField(info, priority); - auto community_info = conf->get_or_construct_community(deserialized_base_info.base_url(), deserialized_base_info.room(), deserialized_base_info.pubkey_hex()); - community_info.priority = deserialized_priority; - return community_info; -} - -inline jobject serialize_members(JNIEnv *env, std::map members_map) { - auto map_class = jni_utils::JavaLocalRef(env, env->FindClass("java/util/HashMap")); - auto boxed_bool = jni_utils::JavaLocalRef(env, env->FindClass("java/lang/Boolean")); - jmethodID map_constructor = env->GetMethodID(map_class.get(), "", "()V"); - jmethodID insert = env->GetMethodID(map_class.get(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); - jmethodID new_bool = env->GetMethodID(boxed_bool.get(), "", "(Z)V"); - - auto new_map = env->NewObject(map_class.get(), map_constructor); - for (auto it = members_map.begin(); it != members_map.end(); it++) { - auto account_id = jni_utils::JavaLocalRef(env, env->NewStringUTF(it->first.data())); - bool is_admin = it->second; - auto jbool = jni_utils::JavaLocalRef(env, env->NewObject(boxed_bool.get(), new_bool, is_admin)); - env->CallObjectMethod(new_map, insert, account_id.get(), jbool.get()); - } - return new_map; -} - -inline jobject serialize_legacy_group_info(JNIEnv *env, session::config::legacy_group_info info) { - auto account_id = jni_utils::JavaLocalRef(env, env->NewStringUTF(info.session_id.data())); - auto name = jni_utils::JavaLocalRef(env, env->NewStringUTF(info.name.data())); - auto members = jni_utils::JavaLocalRef(env, serialize_members(env, info.members())); - auto enc_pubkey = jni_utils::session_bytes_from_range(env, info.enc_pubkey); - auto enc_seckey = jni_utils::session_bytes_from_range(env, info.enc_seckey); - long long priority = info.priority; - long long joined_at = info.joined_at; - - auto legacy_group_class = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$LegacyGroupInfo")); - jmethodID constructor = env->GetMethodID(legacy_group_class.get(), "", "(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lnetwork/loki/messenger/libsession_util/util/Bytes;Lnetwork/loki/messenger/libsession_util/util/Bytes;JJJ)V"); - return env->NewObject(legacy_group_class.get(), constructor, account_id.get(), name.get(), members.get(), enc_pubkey, enc_seckey, priority, (jlong) info.disappearing_timer.count(), joined_at); -} - -inline jobject serialize_closed_group_info(JNIEnv* env, session::config::group_info info) { - auto session_id = util::jstringFromOptional(env, info.id); - auto admin_bytes = jni_utils::JavaLocalRef(env, info.secretkey.empty() ? nullptr : jni_utils::session_bytes_from_range(env, info.secretkey)); - auto auth_bytes = jni_utils::JavaLocalRef(env, info.auth_data.empty() ? nullptr : jni_utils::session_bytes_from_range(env, info.auth_data)); - auto name = jni_utils::JavaLocalRef(env, util::jstringFromOptional(env, info.name)); - - auto group_info_class = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$ClosedGroupInfo")); - jmethodID constructor = env->GetMethodID(group_info_class.get(), "","(Ljava/lang/String;Lnetwork/loki/messenger/libsession_util/util/Bytes;Lnetwork/loki/messenger/libsession_util/util/Bytes;JZLjava/lang/String;ZZJ)V"); - return env->NewObject(group_info_class.get(), constructor, - session_id, admin_bytes.get(), auth_bytes.get(), (jlong)info.priority, info.invited, name.get(), - info.kicked(), info.is_destroyed(), info.joined_at); -} - -inline session::config::group_info deserialize_closed_group_info(JNIEnv* env, jobject info_serialized) { - auto closed_group_class = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$ClosedGroupInfo")); - jfieldID id_field = env->GetFieldID(closed_group_class.get(), "groupAccountId", "Ljava/lang/String;"); - auto secret_method = env->GetMethodID(closed_group_class.get(), "getAdminKeyAsByteArray", "()[B"); - auto auth_method = env->GetMethodID(closed_group_class.get(), "getAuthDataAsByteArray", "()[B"); - jfieldID priority_field = env->GetFieldID(closed_group_class.get(), "priority", "J"); - jfieldID invited_field = env->GetFieldID(closed_group_class.get(), "invited", "Z"); - jfieldID name_field = env->GetFieldID(closed_group_class.get(), "name", "Ljava/lang/String;"); - jfieldID destroy_field = env->GetFieldID(closed_group_class.get(), "destroyed", "Z"); - jfieldID kicked_field = env->GetFieldID(closed_group_class.get(), "kicked", "Z"); - jfieldID joined_at_field = env->GetFieldID(closed_group_class.get(), "joinedAtSecs", "J"); - - - auto id_jobject = jni_utils::JavaLocalRef(env, static_cast(env->GetObjectField(info_serialized, id_field))); - auto secret_jBytes = jni_utils::JavaLocalRef(env, (jbyteArray)env->CallObjectMethod(info_serialized, secret_method)); - auto auth_jBytes = jni_utils::JavaLocalRef(env, (jbyteArray)env->CallObjectMethod(info_serialized, auth_method)); - auto name_jstring = jni_utils::JavaLocalRef(env, (jstring)env->GetObjectField(info_serialized, name_field)); - - auto secret_bytes = util::vector_from_bytes(env, secret_jBytes.get()); - auto auth_bytes = util::vector_from_bytes(env, auth_jBytes.get()); - - session::config::group_info group_info(jni_utils::JavaStringRef(env, id_jobject.get()).copy()); - group_info.auth_data = auth_bytes; - group_info.secretkey = secret_bytes; - group_info.priority = env->GetLongField(info_serialized, priority_field); - group_info.invited = env->GetBooleanField(info_serialized, invited_field); - group_info.name = jni_utils::JavaStringRef(env, name_jstring.get()).view(); - group_info.joined_at = env->GetLongField(info_serialized, joined_at_field); - - if (env->GetBooleanField(info_serialized, kicked_field)) { - group_info.mark_kicked(); - } - - if (env->GetBooleanField(info_serialized, destroy_field)) { - group_info.mark_destroyed(); - } - - return group_info; -} - -inline jobject serialize_community_info(JNIEnv *env, session::config::community_info info) { - auto priority = (long long)info.priority; - auto serialized_info = util::serialize_base_community(env, info); - auto clazz = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$CommunityGroupInfo")); - jmethodID constructor = env->GetMethodID(clazz.get(), "", "(Lnetwork/loki/messenger/libsession_util/util/BaseCommunityInfo;J)V"); - jobject serialized = env->NewObject(clazz.get(), constructor, serialized_info, priority); - return serialized; -} +jni_utils::JavaLocalRef serialize_base_community(JNIEnv *env, const session::config::community& base_community); +session::config::community deserialize_base_community(JNIEnv *env, jobject base_community); #endif //SESSION_ANDROID_USER_GROUPS_H diff --git a/library/src/main/cpp/user_profile.cpp b/library/src/main/cpp/user_profile.cpp index 37ee92a..2e3a5db 100644 --- a/library/src/main/cpp/user_profile.cpp +++ b/library/src/main/cpp/user_profile.cpp @@ -1,10 +1,17 @@ -#include "user_profile.h" +#include + #include "util.h" +#include "pro_proof_util.h" +#include "config_base.h" + +inline auto ptrToProfile(JNIEnv* env, jobject obj) { + return dynamic_cast(ptrToConfigBase(env, obj)); +} extern "C" { JNIEXPORT void JNICALL Java_network_loki_messenger_libsession_1util_UserProfile_setName( - JNIEnv* env, + JNIEnv *env, jobject thiz, jstring newName) { auto profile = ptrToProfile(env, thiz); @@ -16,18 +23,14 @@ Java_network_loki_messenger_libsession_1util_UserProfile_getName(JNIEnv *env, jo auto profile = ptrToProfile(env, thiz); auto name = profile->get_name(); if (name == std::nullopt) return nullptr; - jstring returnString = env->NewStringUTF(name->data()); - return returnString; + return env->NewStringUTF(name->data()); } JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_UserProfile_getPic(JNIEnv *env, jobject thiz) { auto profile = ptrToProfile(env, thiz); auto pic = profile->get_profile_pic(); - - jobject returnObject = util::serialize_user_pic(env, pic); - - return returnObject; + return util::serialize_user_pic(env, pic).release(); } JNIEXPORT void JNICALL @@ -57,7 +60,7 @@ Java_network_loki_messenger_libsession_1util_UserProfile_setNtsExpiry(JNIEnv *en jobject expiry_mode) { auto profile = ptrToProfile(env, thiz); auto expiry = util::deserialize_expiry(env, expiry_mode); - profile->set_nts_expiry(std::chrono::seconds (expiry.second)); + profile->set_nts_expiry(std::chrono::seconds(expiry.second)); } extern "C" @@ -66,11 +69,13 @@ Java_network_loki_messenger_libsession_1util_UserProfile_getNtsExpiry(JNIEnv *en auto profile = ptrToProfile(env, thiz); auto nts_expiry = profile->get_nts_expiry(); if (nts_expiry == std::nullopt) { - auto expiry = util::serialize_expiry(env, session::config::expiration_mode::none, std::chrono::seconds(0)); - return expiry; + auto expiry = util::serialize_expiry(env, session::config::expiration_mode::none, + std::chrono::seconds(0)); + return expiry.release(); } - auto expiry = util::serialize_expiry(env, session::config::expiration_mode::after_send, std::chrono::seconds(*nts_expiry)); - return expiry; + auto expiry = util::serialize_expiry(env, session::config::expiration_mode::after_send, + std::chrono::seconds(*nts_expiry)); + return expiry.release(); } extern "C" @@ -90,7 +95,7 @@ JNIEXPORT void JNICALL Java_network_loki_messenger_libsession_1util_UserProfile_setCommunityMessageRequests( JNIEnv *env, jobject thiz, jboolean blocks) { auto profile = ptrToProfile(env, thiz); - profile->set_blinded_msgreqs(std::optional{(bool)blocks}); + profile->set_blinded_msgreqs(std::optional{(bool) blocks}); } extern "C" @@ -113,4 +118,100 @@ JNIEXPORT void JNICALL Java_network_loki_messenger_libsession_1util_UserProfile_setReuploadedPic(JNIEnv *env, jobject thiz, jobject user_pic) { ptrToProfile(env, thiz)->set_reupload_profile_pic(util::deserialize_user_pic(env, user_pic)); +} + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_UserProfile_removeProConfig(JNIEnv *env, + jobject thiz) { + ptrToProfile(env, thiz)->remove_pro_config(); +} + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_UserProfile_setProConfig(JNIEnv *env, jobject thiz, + jobject proof, + jbyteArray rotating_private_key) { + jni_utils::run_catching_cxx_exception_or_throws(env, [=]() { + + jni_utils::JavaByteArrayRef key_ref(env, rotating_private_key); + auto r = key_ref.get(); + session::cleared_uc64 rotating_privkey; + std::copy(r.begin(), r.end(), rotating_privkey.begin()); + + ptrToProfile(env, thiz)->set_pro_config( + { + .rotating_privkey = rotating_privkey, + .proof = java_to_cpp_proof(env, proof), + } + ); + }); +} + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_UserProfile_setProBadge(JNIEnv *env, jobject thiz, + jboolean pro_badge) { + ptrToProfile(env, thiz)->set_pro_badge(pro_badge); +} + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_UserProfile_setAnimatedAvatar(JNIEnv *env, + jobject thiz, + jboolean enabled) { + ptrToProfile(env, thiz)->set_animated_avatar(enabled); +} + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_UserProfile_setProAccessExpiryMs(JNIEnv *env, + jobject thiz, + jlong epoch_mills) { + ptrToProfile(env, thiz)->set_pro_access_expiry(std::chrono::sys_time{ + std::chrono::milliseconds{epoch_mills} + }); +} + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_UserProfile_removeProAccessExpiry(JNIEnv *env, + jobject thiz) { + ptrToProfile(env, thiz)->set_pro_access_expiry(std::nullopt); +} + +extern "C" +JNIEXPORT jlong JNICALL +Java_network_loki_messenger_libsession_1util_UserProfile_getProFeaturesRaw(JNIEnv *env, + jobject thiz) { + return static_cast(ptrToProfile(env, thiz)->get_pro_features()); +} + +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_UserProfile_getProConfig(JNIEnv *env, jobject thiz) { + auto profile = ptrToProfile(env, thiz)->get_pro_config(); + if (!profile) { + return nullptr; + } + + static jni_utils::BasicJavaClassInfo class_info( + env, + "network/loki/messenger/libsession_util/pro/ProConfig", + "(Lnetwork/loki/messenger/libsession_util/pro/ProProof;[B)V" + ); + + return env->NewObject(class_info.java_class, + class_info.constructor, + cpp_to_java_proof(env, profile->proof).get(), + util::bytes_from_span(env, profile->rotating_privkey).get() + ); +} + +extern "C" +JNIEXPORT jlong JNICALL +Java_network_loki_messenger_libsession_1util_UserProfile_getProAccessExpiryMsOrZero(JNIEnv *env, + jobject thiz) { + auto expiry = ptrToProfile(env, thiz)->get_pro_access_expiry(); + return expiry ? expiry->time_since_epoch().count() : 0; } \ No newline at end of file diff --git a/library/src/main/cpp/user_profile.h b/library/src/main/cpp/user_profile.h deleted file mode 100644 index 2b3f273..0000000 --- a/library/src/main/cpp/user_profile.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef SESSION_ANDROID_USER_PROFILE_H -#define SESSION_ANDROID_USER_PROFILE_H - -#include "session/config/user_profile.hpp" -#include -#include - -#include "jni_utils.h" - -inline session::config::UserProfile* ptrToProfile(JNIEnv* env, jobject obj) { - auto configClass = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/UserProfile")); - jfieldID pointerField = env->GetFieldID(configClass.get(), "pointer", "J"); - return (session::config::UserProfile*) env->GetLongField(obj, pointerField); -} - -#endif \ No newline at end of file diff --git a/library/src/main/cpp/util.cpp b/library/src/main/cpp/util.cpp index 36297cb..f44666e 100644 --- a/library/src/main/cpp/util.cpp +++ b/library/src/main/cpp/util.cpp @@ -5,9 +5,12 @@ #include #include #include "jni_utils.h" +#include "user_groups.h" #include +#include + #define LOG_TAG "libsession_util" #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) @@ -15,14 +18,16 @@ #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +using namespace jni_utils; + namespace util { - jbyteArray bytes_from_vector(JNIEnv* env, const std::vector &from_str) { + JavaLocalRef bytes_from_vector(JNIEnv* env, const std::vector &from_str) { size_t length = from_str.size(); auto jlength = (jsize)length; jbyteArray new_array = env->NewByteArray(jlength); env->SetByteArrayRegion(new_array, 0, jlength, (jbyte*)from_str.data()); - return new_array; + return {env, new_array}; } std::vector vector_from_bytes(JNIEnv* env, jbyteArray byteArray) { @@ -30,85 +35,79 @@ namespace util { return {}; } - return jni_utils::JavaByteArrayRef(env, byteArray).copy(); + return JavaByteArrayRef(env, byteArray).copy(); } - jbyteArray bytes_from_span(JNIEnv* env, std::span from_str) { + JavaLocalRef bytes_from_span(JNIEnv* env, std::span from_str) { size_t length = from_str.size(); auto jlength = (jsize)length; jbyteArray new_array = env->NewByteArray(jlength); env->SetByteArrayRegion(new_array, 0, jlength, (jbyte*)from_str.data()); - return new_array; + return {env, new_array}; } - jobject serialize_user_pic(JNIEnv *env, session::config::profile_pic pic) { - jni_utils::JavaLocalRef returnObjectClass(env, env->FindClass("network/loki/messenger/libsession_util/util/UserPic")); - jmethodID constructor = env->GetMethodID(returnObjectClass.get(), "", "(Ljava/lang/String;Lnetwork/loki/messenger/libsession_util/util/Bytes;)V"); - return env->NewObject(returnObjectClass.get(), constructor, - jni_utils::JavaLocalRef(env, env->NewStringUTF(pic.url.data())).get(), - jni_utils::session_bytes_from_range(env, pic.key) - ); + JavaLocalRef serialize_user_pic(JNIEnv *env, session::config::profile_pic pic) { + static BasicJavaClassInfo class_info( + env, "network/loki/messenger/libsession_util/util/UserPic", + "(Ljava/lang/String;Lnetwork/loki/messenger/libsession_util/util/Bytes;)V" + ); + + return {env, env->NewObject(class_info.java_class, class_info.constructor, + JavaLocalRef(env, env->NewStringUTF(pic.url.data())).get(), + session_bytes_from_range(env, pic.key).get() + )}; } session::config::profile_pic deserialize_user_pic(JNIEnv *env, jobject user_pic) { - jni_utils::JavaLocalRef clazz(env, env->GetObjectClass(user_pic)); - return { - jni_utils::JavaStringRef(env, (jstring) (env->CallObjectMethod(user_pic, env->GetMethodID(clazz.get(), "getUrl", "()Ljava/lang/String;")))).view(), - util::vector_from_bytes(env, static_cast(env->CallObjectMethod(user_pic, env->GetMethodID(clazz.get(), "getKeyAsByteArray", "()[B")))) + struct ClassInfo : public JavaClassInfo { + jmethodID url_getter; + jmethodID key_getter; + + ClassInfo(JNIEnv *env, jobject obj) + : JavaClassInfo(env, obj) + , url_getter(env->GetMethodID(java_class, "getUrl", "()Ljava/lang/String;")) + , key_getter(env->GetMethodID(java_class, "getKeyAsByteArray", "()[B")) + {} }; - } - jobject serialize_base_community(JNIEnv *env, const session::config::community& community) { - auto base_community_clazz = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/BaseCommunityInfo")); - jmethodID base_community_constructor = env->GetMethodID(base_community_clazz.get(), "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); - auto base_url = jni_utils::JavaLocalRef(env, env->NewStringUTF(community.base_url().data())); - auto room = jni_utils::JavaLocalRef(env, env->NewStringUTF(community.room().data())); - auto pubkey_jstring = jni_utils::JavaLocalRef(env, env->NewStringUTF(community.pubkey_hex().data())); - return env->NewObject(base_community_clazz.get(), base_community_constructor, base_url.get(), room.get(), pubkey_jstring.get()); - } + static ClassInfo class_info(env, user_pic); - session::config::community deserialize_base_community(JNIEnv *env, jobject base_community) { - jclass base_community_clazz = env->FindClass("network/loki/messenger/libsession_util/util/BaseCommunityInfo"); - jfieldID base_url_field = env->GetFieldID(base_community_clazz, "baseUrl", "Ljava/lang/String;"); - jfieldID room_field = env->GetFieldID(base_community_clazz, "room", "Ljava/lang/String;"); - jfieldID pubkey_hex_field = env->GetFieldID(base_community_clazz, "pubKeyHex", "Ljava/lang/String;"); - jni_utils::JavaLocalRef base_url(env, (jstring)env->GetObjectField(base_community,base_url_field)); - jni_utils::JavaLocalRef room(env, (jstring)env->GetObjectField(base_community, room_field)); - jni_utils::JavaLocalRef pub_key_hex(env, (jstring)env->GetObjectField(base_community, pubkey_hex_field)); - - return session::config::community( - jni_utils::JavaStringRef(env, base_url.get()).view(), - jni_utils::JavaStringRef(env, room.get()).view(), - jni_utils::JavaStringRef(env, pub_key_hex.get()).view() - ); + return { + JavaStringRef(env, (jstring) (env->CallObjectMethod(user_pic, class_info.url_getter))).view(), + util::vector_from_bytes(env, static_cast(env->CallObjectMethod(user_pic, class_info.key_getter))) + }; } - jobject serialize_expiry(JNIEnv *env, const session::config::expiration_mode& mode, const std::chrono::seconds& time_seconds) { - auto none = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$NONE")); - jfieldID none_instance = env->GetStaticFieldID(none.get(), "INSTANCE", "Lnetwork/loki/messenger/libsession_util/util/ExpiryMode$NONE;"); - auto after_send = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$AfterSend")); - jmethodID send_init = env->GetMethodID(after_send.get(), "", "(J)V"); - auto after_read = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$AfterRead")); - jmethodID read_init = env->GetMethodID(after_read.get(), "", "(J)V"); - + JavaLocalRef serialize_expiry(JNIEnv *env, const session::config::expiration_mode& mode, const std::chrono::seconds& time_seconds) { if (mode == session::config::expiration_mode::none) { - return env->GetStaticObjectField(none.get(), none_instance); + auto none = JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$NONE")); + jfieldID none_instance = env->GetStaticFieldID(none.get(), "INSTANCE", "Lnetwork/loki/messenger/libsession_util/util/ExpiryMode$NONE;"); + + return {env, env->GetStaticObjectField(none.get(), none_instance)}; } else if (mode == session::config::expiration_mode::after_send) { - return env->NewObject(after_send.get(), send_init, time_seconds.count()); + static BasicJavaClassInfo class_info( + env, "network/loki/messenger/libsession_util/util/ExpiryMode$AfterSend", + "(J)V" + ); + return {env, env->NewObject(class_info.java_class, class_info.constructor, time_seconds.count())}; } else if (mode == session::config::expiration_mode::after_read) { - return env->NewObject(after_read.get(), read_init, time_seconds.count()); + static BasicJavaClassInfo class_info( + env, "network/loki/messenger/libsession_util/util/ExpiryMode$AfterRead", + "(J)V" + ); + return {env, env->NewObject(class_info.java_class, class_info.constructor, time_seconds.count())}; } - return nullptr; + return {env, nullptr}; } std::pair deserialize_expiry(JNIEnv *env, jobject expiry_mode) { - auto parent = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode")); - auto after_read = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$AfterRead")); - auto after_send = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$AfterSend")); + auto parent = JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode")); + auto after_read = JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$AfterRead")); + auto after_send = JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$AfterSend")); jfieldID duration_seconds = env->GetFieldID(parent.get(), "expirySeconds", "J"); - auto object_class = jni_utils::JavaLocalRef(env, env->GetObjectClass(expiry_mode)); + auto object_class = JavaLocalRef(env, env->GetObjectClass(expiry_mode)); if (env->IsSameObject(object_class.get(), after_read.get())) { return std::pair(session::config::expiration_mode::after_read, env->GetLongField(expiry_mode, duration_seconds)); @@ -118,39 +117,19 @@ namespace util { return std::pair(session::config::expiration_mode::none, 0); } - jobject serialize_group_member(JNIEnv* env, const session::config::groups::member& member) { - auto group_member_class = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/util/GroupMember")); - jmethodID constructor = env->GetMethodID(group_member_class.get(), "", "(J)V"); - return env->NewObject(group_member_class.get(), - constructor, - reinterpret_cast(new session::config::groups::member(member)) - ); - } - - jobject deserialize_swarm_auth(JNIEnv *env, session::config::groups::Keys::swarm_auth auth) { - auto swarm_auth_class = jni_utils::JavaLocalRef(env, env->FindClass("network/loki/messenger/libsession_util/GroupKeysConfig$SwarmAuth")); - jmethodID constructor = env->GetMethodID(swarm_auth_class.get(), "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); - auto sub_account = jni_utils::JavaLocalRef(env, env->NewStringUTF(auth.subaccount.data())); - auto sub_account_sig = jni_utils::JavaLocalRef(env, env->NewStringUTF(auth.subaccount_sig.data())); - auto signature = jni_utils::JavaLocalRef(env, env->NewStringUTF(auth.signature.data())); - return env->NewObject(swarm_auth_class.get(), constructor, sub_account.get(), sub_account_sig.get(), signature.get()); - } - jobject jlongFromOptional(JNIEnv* env, std::optional optional) { + JavaLocalRef jlongFromOptional(JNIEnv* env, std::optional optional) { if (!optional) { - return nullptr; + return {env, nullptr}; } - auto longClass = jni_utils::JavaLocalRef(env, env->FindClass("java/lang/Long")); - jmethodID constructor = env->GetMethodID(longClass.get(), "", "(J)V"); - return env->NewObject(longClass.get(), constructor, (jlong)*optional); - } - jstring jstringFromOptional(JNIEnv* env, std::optional optional) { - if (!optional) { - return nullptr; - } - return env->NewStringUTF(optional->data()); + static BasicJavaClassInfo class_info( + env, "java/lang/Long", + "(J)V" + ); + + return {env, env->NewObject(class_info.java_class, class_info.constructor, (jlong)*optional)}; } } @@ -169,11 +148,11 @@ Java_network_loki_messenger_libsession_1util_util_MultiEncrypt_encryptForMultipl std::vector> message_vec{}; std::vector> recipient_vec{}; for (int i = 0; i < size; i++) { - jni_utils::JavaLocalRef message_j(env, static_cast(env->GetObjectArrayElement(messages, i))); - jni_utils::JavaLocalRef recipient_j(env, static_cast(env->GetObjectArrayElement(recipients, i))); + JavaLocalRef message_j(env, static_cast(env->GetObjectArrayElement(messages, i))); + JavaLocalRef recipient_j(env, static_cast(env->GetObjectArrayElement(recipients, i))); - message_vec.emplace_back(jni_utils::JavaByteArrayRef(env, message_j.get()).copy()); - recipient_vec.emplace_back(jni_utils::JavaByteArrayRef(env, recipient_j.get()).copy()); + message_vec.emplace_back(JavaByteArrayRef(env, message_j.get()).copy()); + recipient_vec.emplace_back(JavaByteArrayRef(env, recipient_j.get()).copy()); } std::vector> message_sv_vec{}; @@ -189,13 +168,12 @@ Java_network_loki_messenger_libsession_1util_util_MultiEncrypt_encryptForMultipl auto result = session::encrypt_for_multiple_simple( message_sv_vec, recipient_sv_vec, - jni_utils::JavaByteArrayRef(env, ed25519_secret_key).get(), - jni_utils::JavaStringRef(env, domain).view(), + JavaByteArrayRef(env, ed25519_secret_key).get(), + JavaStringRef(env, domain).view(), std::span {random_nonce.data(), 24} ); - auto encoded = util::bytes_from_vector(env, result); - return encoded; + return util::bytes_from_vector(env, result).release(); } extern "C" @@ -207,14 +185,14 @@ Java_network_loki_messenger_libsession_1util_util_MultiEncrypt_decryptForMultipl jbyteArray sender_pub_key, jstring domain) { auto result = session::decrypt_for_multiple_simple( - jni_utils::JavaByteArrayRef(env, encoded).get(), - jni_utils::JavaByteArrayRef(env, secret_key).get(), - jni_utils::JavaByteArrayRef(env, sender_pub_key).get(), - jni_utils::JavaStringRef(env, domain).view() + JavaByteArrayRef(env, encoded).get(), + JavaByteArrayRef(env, secret_key).get(), + JavaByteArrayRef(env, sender_pub_key).get(), + JavaStringRef(env, domain).view() ); if (result) { - return util::bytes_from_vector(env, *result); + return util::bytes_from_vector(env, *result).release(); } else { LOGD("no result from decrypt"); } @@ -225,7 +203,7 @@ extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_util_BaseCommunityInfo_00024Companion_parseFullUrl( JNIEnv *env, jobject thiz, jstring full_url) { - auto [base, room, pk] = session::config::community::parse_full_url(jni_utils::JavaStringRef(env, full_url).view()); + auto [base, room, pk] = session::config::community::parse_full_url(JavaStringRef(env, full_url).view()); jclass clazz = env->FindClass("kotlin/Triple"); jmethodID constructor = env->GetMethodID(clazz, "", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V"); @@ -234,14 +212,14 @@ Java_network_loki_messenger_libsession_1util_util_BaseCommunityInfo_00024Compani auto room_j = env->NewStringUTF(room.data()); auto pk_jbytes = util::bytes_from_vector(env, pk); - jobject triple = env->NewObject(clazz, constructor, base_j, room_j, pk_jbytes); + jobject triple = env->NewObject(clazz, constructor, base_j, room_j, pk_jbytes.get()); return triple; } extern "C" JNIEXPORT jstring JNICALL Java_network_loki_messenger_libsession_1util_util_BaseCommunityInfo_fullUrl(JNIEnv *env, jobject thiz) { - auto deserialized = util::deserialize_base_community(env, thiz); + auto deserialized = deserialize_base_community(env, thiz); auto full_url = deserialized.full_url(); return env->NewStringUTF(full_url.data()); } @@ -323,3 +301,26 @@ Java_network_loki_messenger_libsession_1util_Config_free(JNIEnv *env, jobject th delete config; } } + +extern "C" +JNIEXPORT jint JNICALL +Java_network_loki_messenger_libsession_1util_util_Util_lengthForCodepoints(JNIEnv *env, + jobject thiz, + jstring str, + jint max_codepoints) { + return run_catching_cxx_exception_or_throws(env, [=]() { + JavaCharsRef str_ref(env, str); + return session::utf16_count_truncated_to_codepoints( + {reinterpret_cast(str_ref.chars()), str_ref.size()}, + max_codepoints + ); + }); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_network_loki_messenger_libsession_1util_util_Util_countCodepoints(JNIEnv *env, jobject thiz, + jstring str) { + JavaCharsRef str_ref(env, str); + return session::utf16_count({reinterpret_cast(str_ref.chars()), str_ref.size()}); +} \ No newline at end of file diff --git a/library/src/main/cpp/util.h b/library/src/main/cpp/util.h index 6725ecd..5243906 100644 --- a/library/src/main/cpp/util.h +++ b/library/src/main/cpp/util.h @@ -15,20 +15,19 @@ #include "session/config/user_groups.hpp" #include "session/config/expiring.hpp" +#include "jni_utils.h" + namespace util { - jbyteArray bytes_from_vector(JNIEnv* env, const std::vector &from_str); + jni_utils::JavaLocalRef bytes_from_vector(JNIEnv* env, const std::vector &from_str); std::vector vector_from_bytes(JNIEnv* env, jbyteArray byteArray); - jbyteArray bytes_from_span(JNIEnv* env, std::span from_str); - jobject serialize_user_pic(JNIEnv *env, session::config::profile_pic pic); + jni_utils::JavaLocalRef bytes_from_span(JNIEnv* env, std::span from_str); + jni_utils::JavaLocalRef serialize_user_pic(JNIEnv *env, session::config::profile_pic pic); session::config::profile_pic deserialize_user_pic(JNIEnv *env, jobject user_pic); - jobject serialize_base_community(JNIEnv *env, const session::config::community& base_community); - session::config::community deserialize_base_community(JNIEnv *env, jobject base_community); - jobject serialize_expiry(JNIEnv *env, const session::config::expiration_mode& mode, const std::chrono::seconds& time_seconds); + + jni_utils::JavaLocalRef serialize_expiry(JNIEnv *env, const session::config::expiration_mode& mode, const std::chrono::seconds& time_seconds); std::pair deserialize_expiry(JNIEnv *env, jobject expiry_mode); - jobject serialize_group_member(JNIEnv* env, const session::config::groups::member& member); - jobject jlongFromOptional(JNIEnv* env, std::optional optional); - jstring jstringFromOptional(JNIEnv* env, std::optional optional); - jobject deserialize_swarm_auth(JNIEnv *env, session::config::groups::Keys::swarm_auth auth); + + jni_utils::JavaLocalRef jlongFromOptional(JNIEnv* env, std::optional optional); } #endif \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/Config.kt b/library/src/main/java/network/loki/messenger/libsession_util/Config.kt index 3c8d058..7bcedd2 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/Config.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/Config.kt @@ -1,5 +1,8 @@ package network.loki.messenger.libsession_util +import network.loki.messenger.libsession_util.pro.ProConfig +import network.loki.messenger.libsession_util.pro.ProProof +import network.loki.messenger.libsession_util.protocol.ProFeatures import network.loki.messenger.libsession_util.util.BaseCommunityInfo import network.loki.messenger.libsession_util.util.BlindedContact import network.loki.messenger.libsession_util.util.ConfigPush @@ -9,9 +12,15 @@ import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.GroupMember import network.loki.messenger.libsession_util.util.UserPic -import java.io.Closeable +import java.time.Instant -sealed class Config(initialPointer: Long): Closeable, LibSessionUtilCApi() { +typealias ConversationPriority = Long + +const val PRIORITY_HIDDEN: ConversationPriority = -1L +const val PRIORITY_VISIBLE: ConversationPriority = 0L +const val PRIORITY_PINNED: ConversationPriority = 1L + +sealed class Config(initialPointer: Long): LibSessionUtilCApi() { var pointer = initialPointer private set @@ -23,11 +32,8 @@ sealed class Config(initialPointer: Long): Closeable, LibSessionUtilCApi() { private external fun free() - final override fun close() { - if (pointer != 0L) { - free() - pointer = 0L - } + protected fun finalize() { + free() } } @@ -74,10 +80,14 @@ interface ReadableUserProfile: ReadableConfig { fun getName(): String? fun getPic(): UserPic fun getProfileUpdatedSeconds(): Long - fun getNtsPriority(): Long + fun getNtsPriority(): ConversationPriority fun getNtsExpiry(): ExpiryMode fun getCommunityMessageRequests(): Boolean fun isBlockCommunityMessageRequestsSet(): Boolean + + fun getProFeatures(): ProFeatures + fun getProConfig(): ProConfig? + fun getProAccessExpiryMs(): Long? } interface MutableUserProfile : ReadableUserProfile, MutableConfig { @@ -92,13 +102,23 @@ interface MutableUserProfile : ReadableUserProfile, MutableConfig { * Called when setting a re-uploaded pic */ fun setReuploadedPic(userPic: UserPic) - fun setNtsPriority(priority: Long) + fun setNtsPriority(priority: ConversationPriority) fun setNtsExpiry(expiryMode: ExpiryMode) fun setCommunityMessageRequests(blocks: Boolean) + + fun removeProConfig() + fun setProConfig(proConfig: ProConfig) + fun setProBadge(proBadge: Boolean) + fun setAnimatedAvatar(animatedAvatar: Boolean) + fun setProAccessExpiryMs(epochMills: Long) + fun removeProAccessExpiry() } interface ReadableConversationVolatileConfig: ReadableConfig { fun getOneToOne(pubKeyHex: String): Conversation.OneToOne? + + fun getBlindedOneToOne(pubKeyHex: String): Conversation.BlindedOneToOne? + fun getCommunity(baseUrl: String, room: String): Conversation.Community? fun getLegacyClosedGroup(groupId: String): Conversation.LegacyGroup? fun getClosedGroup(sessionId: String): Conversation.ClosedGroup? @@ -110,6 +130,7 @@ interface ReadableConversationVolatileConfig: ReadableConfig { fun empty(): Boolean fun allOneToOnes(): List + fun allBlindedOneToOnes(): List fun allCommunities(): List fun allLegacyClosedGroups(): List fun allClosedGroups(): List diff --git a/library/src/main/java/network/loki/messenger/libsession_util/ConfigBase.kt b/library/src/main/java/network/loki/messenger/libsession_util/ConfigBase.kt index f0c8bc0..b914598 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/ConfigBase.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/ConfigBase.kt @@ -3,18 +3,6 @@ package network.loki.messenger.libsession_util import network.loki.messenger.libsession_util.util.ConfigPush sealed class ConfigBase(pointer: Long): Config(pointer), MutableConfig { - companion object { - init { - System.loadLibrary("session_util") - } - external fun kindFor(configNamespace: Int): Class - - const val PRIORITY_HIDDEN = -1L - const val PRIORITY_VISIBLE = 0L - const val PRIORITY_PINNED = 1L - - } - external override fun dirty(): Boolean external override fun needsPush(): Boolean external override fun needsDump(): Boolean diff --git a/library/src/main/java/network/loki/messenger/libsession_util/ConversationVolatileConfig.kt b/library/src/main/java/network/loki/messenger/libsession_util/ConversationVolatileConfig.kt index e3e4c5d..60cd109 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/ConversationVolatileConfig.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/ConversationVolatileConfig.kt @@ -39,6 +39,9 @@ class ConversationVolatileConfig private constructor(pointer: Long): ConfigBase( external override fun getOrConstructedBlindedOneToOne(blindedId: String): Conversation.BlindedOneToOne external override fun eraseBlindedOneToOne(blindedId: String): Boolean + external override fun getBlindedOneToOne(pubKeyHex: String): Conversation.BlindedOneToOne? + external override fun allBlindedOneToOnes(): List + override fun set(conv: Conversation) { when (conv) { is Conversation.BlindedOneToOne -> setBlindedOneToOne(conv) diff --git a/library/src/main/java/network/loki/messenger/libsession_util/ED25519.kt b/library/src/main/java/network/loki/messenger/libsession_util/ED25519.kt index 392e9a7..172f392 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/ED25519.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/ED25519.kt @@ -29,4 +29,22 @@ object ED25519 : LibSessionUtilCApi() { ): Boolean external fun generate(seed: ByteArray?): KeyPair + + /** + * Generate the deterministic Master Session Pro key for signing requests to interact with the + * Session Pro features of the protocol. + * + * @param ed25519Seed The seed the user uses to generate their session id + * @return The libsodium-style Master Session Pro Ed25519 secret key, 64 bytes. + */ + external fun generateProMasterKey(ed25519Seed: ByteArray): ByteArray + + private external fun positiveEd25519PubKeyFromCurve25519(curve25519PubKey: ByteArray): ByteArray + + fun ed25519PubKeysFromCurve25519(curve25519PubKey: ByteArray): List { + val positive = positiveEd25519PubKeyFromCurve25519(curve25519PubKey) + val negative = positive.clone() + negative[31] = (negative[31].toInt() xor 0x80).toByte() + return listOf(positive, negative) + } } \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/UserProfile.kt b/library/src/main/java/network/loki/messenger/libsession_util/UserProfile.kt index b1170b3..4c313c6 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/UserProfile.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/UserProfile.kt @@ -1,5 +1,8 @@ package network.loki.messenger.libsession_util +import network.loki.messenger.libsession_util.pro.ProConfig +import network.loki.messenger.libsession_util.pro.ProProof +import network.loki.messenger.libsession_util.protocol.ProFeatures import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.UserPic @@ -27,4 +30,27 @@ class UserProfile private constructor(pointer: Long) : ConfigBase(pointer), Muta external override fun getCommunityMessageRequests(): Boolean external override fun setCommunityMessageRequests(blocks: Boolean) external override fun isBlockCommunityMessageRequestsSet(): Boolean + + external override fun removeProConfig() + + private external fun setProConfig( + proof: ProProof, + rotatingPrivateKey: ByteArray + ) + + override fun setProConfig(proConfig: ProConfig) = setProConfig( + proConfig.proProof, + proConfig.rotatingPrivateKey.data + ) + + external override fun setProBadge(proBadge: Boolean) + external override fun setAnimatedAvatar(animatedAvatar: Boolean) + external override fun setProAccessExpiryMs(epochMills: Long) + external override fun removeProAccessExpiry() + private external fun getProFeaturesRaw(): Long + override fun getProFeatures(): ProFeatures = ProFeatures(getProFeaturesRaw()) + external override fun getProConfig(): ProConfig? + + private external fun getProAccessExpiryMsOrZero(): Long + override fun getProAccessExpiryMs(): Long? = getProAccessExpiryMsOrZero().takeIf { it != 0L } } \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/pro/BackendRequests.kt b/library/src/main/java/network/loki/messenger/libsession_util/pro/BackendRequests.kt new file mode 100644 index 0000000..a76e9d0 --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/pro/BackendRequests.kt @@ -0,0 +1,36 @@ +package network.loki.messenger.libsession_util.pro + +import network.loki.messenger.libsession_util.LibSessionUtilCApi +import network.loki.messenger.libsession_util.protocol.PaymentProviderMetadata + +typealias PaymentProvider = Int + +object BackendRequests : LibSessionUtilCApi() { + const val PAYMENT_PROVIDER_GOOGLE_PLAY: PaymentProvider = 1 + const val PAYMENT_PROVIDER_APP_STORE: PaymentProvider = 2 + + external fun buildAddProPaymentRequestJson( + version: Int, + masterPrivateKey: ByteArray, + rotatingPrivateKey: ByteArray, + paymentProvider: PaymentProvider, + paymentId: String, + orderId: String, + ): String + + external fun buildGenerateProProofRequestJson( + version: Int, + masterPrivateKey: ByteArray, + rotatingPrivateKey: ByteArray, + nowMs: Long, + ): String + + external fun buildGetProDetailsRequestJson( + version: Int, + proMasterPrivateKey: ByteArray, + nowMs: Long, + count: Int, + ): String + + external fun getPaymentProviderMetadata(paymentProvider: PaymentProvider): PaymentProviderMetadata? +} diff --git a/library/src/main/java/network/loki/messenger/libsession_util/pro/ProConfig.kt b/library/src/main/java/network/loki/messenger/libsession_util/pro/ProConfig.kt new file mode 100644 index 0000000..959bd5c --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/pro/ProConfig.kt @@ -0,0 +1,18 @@ +package network.loki.messenger.libsession_util.pro + +import androidx.annotation.Keep +import network.loki.messenger.libsession_util.util.Bytes + +data class ProConfig( + val proProof: ProProof, + val rotatingPrivateKey: Bytes +) { + @Keep + constructor( + proProof: ProProof, + rotatingPrivateKey: ByteArray + ) : this( + proProof, + Bytes(rotatingPrivateKey) + ) +} diff --git a/library/src/main/java/network/loki/messenger/libsession_util/pro/ProProof.kt b/library/src/main/java/network/loki/messenger/libsession_util/pro/ProProof.kt new file mode 100644 index 0000000..e4da110 --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/pro/ProProof.kt @@ -0,0 +1,107 @@ +package network.loki.messenger.libsession_util.pro + +import androidx.annotation.Keep +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import java.time.Instant + +/** + * Represents a proof of Pro. This class is marked as @Serializable to represent the JSON structure + * received from the Pro Backend. + */ +@Serializable +data class ProProof( + val version: Int, + + @SerialName("gen_index_hash") + val genIndexHashHex: String, + + @SerialName("rotating_pkey") + val rotatingPubKeyHex: String, + + @SerialName("expiry_unix_ts_ms") + val expiryMs: Long, + + @SerialName("sig") + val signatureHex: String +) { + @Keep + constructor( + version: Int, + genIndexHash: ByteArray, + rotatingPubKey: ByteArray, + expiryMs: Long, + signature: ByteArray + ): this( + version = version, + genIndexHashHex = genIndexHash.toHexString(), + rotatingPubKeyHex = rotatingPubKey.toHexString(), + expiryMs = expiryMs, + signatureHex = signature.toHexString() + ) + + + init { + check(rotatingPubKeyHex.length == 64) { + "Rotating public key must be 32 bytes" + } + + check(signatureHex.length == 128) { + "Signature must be 64 bytes" + } + } + + enum class Status(internal val nativeValue: Int) { + InvalidProBackendSignature(1), + InvalidUserSignature(2), + Valid(3), + Expired(4), + + ; + companion object { + internal fun fromNativeValue(value: Int): Status { + return entries.first { it.nativeValue == value } + } + + internal fun fromNativeValueOrNull(value: Int): Status? { + return entries.firstOrNull { it.nativeValue == value } + } + } + } + + class ProSignedMessage( + val data: ByteArray, + val signature: ByteArray, + ) + + /** + * Checks the status of the Pro proof. + * + * @param senderED25519PubKey The sender (proof generator)'s ED25519 public key. + * @param signedMessage An optional signed message to verify against the proof. + * @param now The current time to use for expiry checks. Defaults to + */ + fun status( + senderED25519PubKey: ByteArray, + now: Instant, + signedMessage: ProSignedMessage? = null, + ): Status { + val signedMessageData = signedMessage?.data + val signedMessageSignature = signedMessage?.signature + val statusValue = nativeStatus( + nowUnixTs = now.toEpochMilli(), + verifyPubKey = senderED25519PubKey, + signedMessageData = signedMessageData, + signedMessageSignature = signedMessageSignature + ) + + return Status.fromNativeValue(statusValue) + } + + private external fun nativeStatus( + nowUnixTs: Long, + verifyPubKey: ByteArray, + signedMessageData: ByteArray?, + signedMessageSignature: ByteArray? + ): Int +} \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecodedCommunityMessage.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecodedCommunityMessage.kt index 7f8c703..1779c48 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecodedCommunityMessage.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecodedCommunityMessage.kt @@ -1,18 +1,32 @@ package network.loki.messenger.libsession_util.protocol import androidx.annotation.Keep +import network.loki.messenger.libsession_util.pro.ProProof import network.loki.messenger.libsession_util.util.Bytes +import java.util.EnumSet data class DecodedCommunityMessage( - val proStatus: ProStatus, + val proStatus: ProProof.Status?, + val proProof: ProProof?, + val proFeatures: ProFeatures, val contentPlainText: Bytes, ) { @Keep constructor( - proStatus: ProStatus, + status: Int, + proProof: ProProof?, + proFeatures: Long, contentPlainText: ByteArray, ): this( - proStatus = proStatus, + proStatus = ProProof.Status.fromNativeValueOrNull(status), + proProof = proProof, + proFeatures = ProFeatures(proFeatures), contentPlainText = Bytes(contentPlainText), ) + + init { + check(proProof == null || proStatus in EnumSet.of(ProProof.Status.Expired, ProProof.Status.Valid)) { + "proProof must be null unless proStatus is Expired or Valid" + } + } } diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecodedEnvelope.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecodedEnvelope.kt index 35e1f4d..5cce811 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecodedEnvelope.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecodedEnvelope.kt @@ -1,12 +1,16 @@ package network.loki.messenger.libsession_util.protocol import androidx.annotation.Keep +import network.loki.messenger.libsession_util.pro.ProProof import network.loki.messenger.libsession_util.util.Bytes import java.time.Instant +import java.util.EnumSet data class DecodedEnvelope( val envelope: Envelope, - val proStatus: ProStatus, + val proStatus: ProProof.Status?, + val proProof: ProProof?, + val proFeatures: ProFeatures, val contentPlainText: Bytes, val senderEd25519PubKey: Bytes, val senderX25519PubKey: Bytes, @@ -15,17 +19,27 @@ data class DecodedEnvelope( @Keep constructor( envelope: Envelope, - proStatus: ProStatus, + proStatus: Int, + proProof: ProProof?, + proFeatures: Long, contentPlainText: ByteArray, senderEd25519PubKey: ByteArray, senderX25519PubKey: ByteArray, timestampEpochMills: Long ): this( envelope = envelope, - proStatus = proStatus, + proStatus = ProProof.Status.fromNativeValueOrNull(proStatus), + proProof = proProof, + proFeatures = ProFeatures(proFeatures), contentPlainText = Bytes(contentPlainText), senderEd25519PubKey = Bytes(senderEd25519PubKey), senderX25519PubKey = Bytes(senderX25519PubKey), timestamp = Instant.ofEpochMilli(timestampEpochMills) ) + + init { + check(proProof == null || proStatus in EnumSet.of(ProProof.Status.Expired, ProProof.Status.Valid)) { + "proProof must be null unless proStatus is Expired or Valid" + } + } } diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/PaymentProviderMetadata.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/PaymentProviderMetadata.kt new file mode 100644 index 0000000..c7b7149 --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/PaymentProviderMetadata.kt @@ -0,0 +1,15 @@ +package network.loki.messenger.libsession_util.protocol + +import androidx.annotation.Keep + +data class PaymentProviderMetadata @Keep constructor( + val device: String, + val store: String, + val platform: String, + val platformAccount: String, + val refundPlatformUrl: String, + val refundSupportUrl: String, + val refundStatusUrl: String, + val updateSubscriptionUrl: String, + val cancelSubscriptionUrl: String, +) \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProFeatures.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProFeatures.kt index 843be42..32ab7dc 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProFeatures.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProFeatures.kt @@ -1,5 +1,7 @@ package network.loki.messenger.libsession_util.protocol +import kotlinx.serialization.Serializable + enum class ProFeature(internal val bitIndex: Int) { HIGHER_CHARACTER_LIMIT(0), @@ -7,6 +9,26 @@ enum class ProFeature(internal val bitIndex: Int) { ANIMATED_AVATAR(2), } +@Serializable +@JvmInline +value class ProFeatures(val rawValue: Long) { + companion object { + val NONE = ProFeatures(0L) + + fun from(features: Collection): ProFeatures { + return ProFeatures(features.toLong()) + } + } + + fun contains(feature: ProFeature): Boolean { + return (rawValue and (1L shl feature.bitIndex)) != 0L + } + + fun toSet(): Set { + return rawValue.toFeatures() + } +} + internal fun Long.toFeatures(): Set { return buildSet(ProFeature.entries.size) { for (entry in ProFeature.entries) { diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProFeaturesForMsg.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProFeaturesForMsg.kt new file mode 100644 index 0000000..8a6dfce --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProFeaturesForMsg.kt @@ -0,0 +1,37 @@ +package network.loki.messenger.libsession_util.protocol + +import androidx.annotation.Keep + +/** + * Represents the result of trying to augment a message with Pro features. + * + * @param status The status of the augmentation attempt. + * @param error An optional error message if the augmentation failed. + * @param features The Pro features that were successfully applied. + * @param codepointCount The number of codepoints in the message + */ +data class ProFeaturesForMsg( + val status: Status, + val error: String?, + val features: ProFeatures, + val codepointCount: Int, +) { + @Keep + constructor( + statusNativeValue: Int, + error: String?, + featuresNativeValue: Long, + codepointCount: Int, + ) : this( + status = Status.entries.first { it.nativeValue == statusNativeValue }, + error = error, + features = ProFeatures(featuresNativeValue), + codepointCount = codepointCount, + ) + + enum class Status(internal val nativeValue: Int) { + Success(0), + UTFDecodingError(1), + ExceedsCharacterLimit(2), + } +} diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProStatus.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProStatus.kt deleted file mode 100644 index 49b6a94..0000000 --- a/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProStatus.kt +++ /dev/null @@ -1,24 +0,0 @@ -package network.loki.messenger.libsession_util.protocol - -import androidx.annotation.Keep -import java.time.Instant - - -sealed interface ProStatus { - data object None : ProStatus - data object Invalid : ProStatus - - data class Valid( - val expiresAt: Instant, - val proFeatures: Set - ) { - @Keep - constructor( - expiresAtEpochMills: Long, - proFeatures: Long - ): this( - expiresAt = Instant.ofEpochMilli(expiresAtEpochMills), - proFeatures = proFeatures.toFeatures() - ) - } -} \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/SessionProtocol.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/SessionProtocol.kt index 3d18b85..5566420 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/protocol/SessionProtocol.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/SessionProtocol.kt @@ -1,6 +1,7 @@ package network.loki.messenger.libsession_util.protocol import network.loki.messenger.libsession_util.LibSessionUtilCApi +import network.loki.messenger.libsession_util.pro.PaymentProvider object SessionProtocol : LibSessionUtilCApi() { external fun encodeFor1o1( @@ -55,4 +56,17 @@ object SessionProtocol : LibSessionUtilCApi() { groupEd25519PrivateKeys: Array, // all available group private keys proBackendPubKey: ByteArray, // 32 bytes backend key ): DecodedEnvelope + + private external fun proFeaturesForMessage( + messageBody: String, + proposedFeatures: Long + ): ProFeaturesForMsg + + /** + * Determines which Pro features shall be applied to a message based on its content and the proposed features. + */ + fun proFeaturesForMessage( + messageBody: String, + proposedFeatures: ProFeatures + ): ProFeaturesForMsg = proFeaturesForMessage(messageBody, proposedFeatures.rawValue) } \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/util/BlindedContact.kt b/library/src/main/java/network/loki/messenger/libsession_util/util/BlindedContact.kt index d725200..ff9463c 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/util/BlindedContact.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/util/BlindedContact.kt @@ -1,5 +1,9 @@ package network.loki.messenger.libsession_util.util +import androidx.annotation.Keep +import network.loki.messenger.libsession_util.ConversationPriority +import network.loki.messenger.libsession_util.protocol.ProFeatures + data class BlindedContact( val id: String, val communityServer: String, @@ -8,9 +12,13 @@ data class BlindedContact( var createdEpochSeconds: Long, var profileUpdatedEpochSeconds: Long, var profilePic: UserPic, - var priority: Long, + var priority: ConversationPriority, + val proFeatures: ProFeatures, ) { @OptIn(ExperimentalStdlibApi::class) val communityServerPubKey: ByteArray get() = communityServerPubKeyHex.hexToByteArray() + + @get:Keep + private val proFeaturesRaw: Long get() = proFeatures.rawValue } \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/util/ConfigPush.kt b/library/src/main/java/network/loki/messenger/libsession_util/util/ConfigPush.kt new file mode 100644 index 0000000..1da7dff --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/util/ConfigPush.kt @@ -0,0 +1,3 @@ +package network.loki.messenger.libsession_util.util + +data class ConfigPush(val messages: List, val seqNo: Long, val obsoleteHashes: List) \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/util/Contact.kt b/library/src/main/java/network/loki/messenger/libsession_util/util/Contact.kt index 1816c05..79c5602 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/util/Contact.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/util/Contact.kt @@ -1,5 +1,10 @@ package network.loki.messenger.libsession_util.util +import androidx.annotation.Keep +import network.loki.messenger.libsession_util.ConversationPriority +import network.loki.messenger.libsession_util.PRIORITY_VISIBLE +import network.loki.messenger.libsession_util.protocol.ProFeatures + data class Contact( val id: String, var name: String = "", @@ -10,9 +15,14 @@ data class Contact( var profilePicture: UserPic = UserPic.DEFAULT, var createdEpochSeconds: Long = 0, var profileUpdatedEpochSeconds: Long = 0, - var priority: Long = 0, + var priority: ConversationPriority = PRIORITY_VISIBLE, var expiryMode: ExpiryMode = ExpiryMode.NONE, + var proFeatures: ProFeatures = ProFeatures.NONE, ) { + val displayName: String get() = nickname.ifEmpty { name } + + @get:Keep + private val proFeaturesRaw: Long get() = proFeatures.rawValue } \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/util/Conversation.kt b/library/src/main/java/network/loki/messenger/libsession_util/util/Conversation.kt index d7e2ca3..b42e692 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/util/Conversation.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/util/Conversation.kt @@ -1,37 +1,67 @@ package network.loki.messenger.libsession_util.util -sealed class Conversation { +import androidx.annotation.Keep +import java.time.Instant - abstract var lastRead: Long - abstract var unread: Boolean +sealed interface Conversation { + + var lastRead: Long + var unread: Boolean + + /** + * The minimal information about a Pro Proof stored in a Conversation. + * This is not a full ProProof, it contains just enough for the clients to check it it's revoked. + */ + data class ProProofInfo( + val genIndexHash: Bytes, + val expiry: Instant, + ) { + @Keep + constructor( + genIndexHash: ByteArray, + expiryMs: Long, + ) : this( + genIndexHash = Bytes(genIndexHash), + expiry = Instant.ofEpochMilli(expiryMs) + ) + } + + /** + * A Conversation that contains Pro information. + */ + sealed interface WithProProofInfo : Conversation { + var proProofInfo: ProProofInfo? + } data class OneToOne( val accountId: String, override var lastRead: Long, - override var unread: Boolean - ): Conversation() + override var unread: Boolean, + override var proProofInfo: ProProofInfo?, + ): Conversation, WithProProofInfo data class Community( val baseCommunityInfo: BaseCommunityInfo, override var lastRead: Long, override var unread: Boolean - ) : Conversation() + ) : Conversation data class LegacyGroup( val groupId: String, override var lastRead: Long, override var unread: Boolean - ): Conversation() + ): Conversation data class ClosedGroup( val accountId: String, override var lastRead: Long, override var unread: Boolean - ): Conversation() + ): Conversation data class BlindedOneToOne( val blindedAccountId: String, override var lastRead: Long, - override var unread: Boolean - ) : Conversation() + override var unread: Boolean, + override var proProofInfo: ProProofInfo?, + ) : Conversation, WithProProofInfo } \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/util/KeyPair.kt b/library/src/main/java/network/loki/messenger/libsession_util/util/KeyPair.kt new file mode 100644 index 0000000..03945c8 --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/util/KeyPair.kt @@ -0,0 +1,9 @@ +package network.loki.messenger.libsession_util.util + +import androidx.annotation.Keep + +data class KeyPair(val pubKey: Bytes, val secretKey: Bytes) { + @Keep + constructor(pubKey: ByteArray, secretKey: ByteArray) + : this(Bytes(pubKey), Bytes(secretKey)) +} \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/util/Utils.kt b/library/src/main/java/network/loki/messenger/libsession_util/util/UserPic.kt similarity index 59% rename from library/src/main/java/network/loki/messenger/libsession_util/util/Utils.kt rename to library/src/main/java/network/loki/messenger/libsession_util/util/UserPic.kt index a19e747..02f8d20 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/util/Utils.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/util/UserPic.kt @@ -1,7 +1,5 @@ package network.loki.messenger.libsession_util.util -data class ConfigPush(val messages: List, val seqNo: Long, val obsoleteHashes: List) - data class UserPic(val url: String, val key: Bytes) { constructor(url: String, key: ByteArray) :this(url, Bytes(key)) @@ -13,9 +11,4 @@ data class UserPic(val url: String, val key: Bytes) { // Convenience method to get the key as a ByteArray from native side val keyAsByteArray: ByteArray get() = key.data -} - -data class KeyPair(val pubKey: Bytes, val secretKey: Bytes) { - constructor(pubKey: ByteArray, secretKey: ByteArray) - : this(Bytes(pubKey), Bytes(secretKey)) -} +} \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/util/Util.kt b/library/src/main/java/network/loki/messenger/libsession_util/util/Util.kt new file mode 100644 index 0000000..51485de --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/util/Util.kt @@ -0,0 +1,12 @@ +package network.loki.messenger.libsession_util.util + +import network.loki.messenger.libsession_util.LibSessionUtilCApi + +object Util : LibSessionUtilCApi() { + private external fun lengthForCodepoints(str: String, maxCodepoints: Int): Int + + fun truncateCodepoints(str: String, maxCodepoints: Int): String = + str.take(lengthForCodepoints(str, maxCodepoints)) + + external fun countCodepoints(str: String): Int +} \ No newline at end of file diff --git a/libsession-util b/libsession-util index 6134c47..47acb57 160000 --- a/libsession-util +++ b/libsession-util @@ -1 +1 @@ -Subproject commit 6134c4718ae094be53deeb2b4285bed475b20b88 +Subproject commit 47acb57697cd1ec22c3ac4b40252bd2e3fac0a84