mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-25 11:05:25 +00:00
feat: improve cpp jni layer and add more test functionality
This commit is contained in:
parent
f0aea7aaa9
commit
643d139e8a
@ -40,6 +40,7 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
api(project(":libsignal"))
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
|
||||||
}
|
}
|
@ -1,60 +0,0 @@
|
|||||||
package network.loki.messenger.libsession_util
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import org.junit.Assert.*
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
fun useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
assertEquals("network.loki.messenger.libsession_util.test", appContext.packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun jni_accessible() {
|
|
||||||
val userProfile = UserProfile.newInstance()
|
|
||||||
assertNotNull(userProfile)
|
|
||||||
userProfile.free()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun jni_user_profile_c_api() {
|
|
||||||
val userProfile = UserProfile.newInstance()
|
|
||||||
|
|
||||||
assertFalse(userProfile.needsPush())
|
|
||||||
assertFalse(userProfile.needsDump())
|
|
||||||
|
|
||||||
val name = userProfile.getName()
|
|
||||||
assertNull(name)
|
|
||||||
|
|
||||||
val (toPush, seqNo) = userProfile.push()
|
|
||||||
assertEquals("d1:#i0e1:&de1:<le1:=dee", toPush)
|
|
||||||
assertEquals(0, seqNo)
|
|
||||||
|
|
||||||
userProfile.free()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun jni_setting_getting() {
|
|
||||||
val userProfile = UserProfile.newInstance()
|
|
||||||
val newName = "test"
|
|
||||||
println("Name being set via JNI call: $newName")
|
|
||||||
userProfile.setName(newName)
|
|
||||||
val nameFromNative = userProfile.getName()
|
|
||||||
assertEquals(newName, nameFromNative)
|
|
||||||
println("Name received by JNI call: $nameFromNative")
|
|
||||||
assertTrue(userProfile.dirty())
|
|
||||||
userProfile.free()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,110 @@
|
|||||||
|
package network.loki.messenger.libsession_util
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.session.libsignal.utilities.Hex
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class InstrumentedTests {
|
||||||
|
@Test
|
||||||
|
fun useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
assertEquals("network.loki.messenger.libsession_util.test", appContext.packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun jni_accessible() {
|
||||||
|
val userProfile = UserProfile.newInstance()
|
||||||
|
assertNotNull(userProfile)
|
||||||
|
userProfile.free()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun jni_user_profile_c_api() {
|
||||||
|
Log.d("JNITEST","Creating profile")
|
||||||
|
val userProfile = UserProfile.newInstance()
|
||||||
|
|
||||||
|
// these should be false as empty config
|
||||||
|
assertFalse(userProfile.needsPush())
|
||||||
|
assertFalse(userProfile.needsDump())
|
||||||
|
|
||||||
|
// Since it's empty there shouldn't be a name
|
||||||
|
assertNull(userProfile.getName())
|
||||||
|
|
||||||
|
// Don't need to push yet so this is just for testing
|
||||||
|
val (toPush, seqNo) = userProfile.push()
|
||||||
|
assertEquals("d1:#i0e1:&de1:<le1:=dee", toPush.decodeToString())
|
||||||
|
assertEquals(0, seqNo)
|
||||||
|
|
||||||
|
// This should also be unset:
|
||||||
|
assertNull(userProfile.getPic())
|
||||||
|
|
||||||
|
// Now let's go set a profile name and picture:
|
||||||
|
// not sending keylen like c api so cutting off the NOTSECRET in key for testing purposes
|
||||||
|
userProfile.setName("Kallie")
|
||||||
|
val newUserPic = UserPic("http://example.org/omg-pic-123.bmp", "secret".encodeToByteArray())
|
||||||
|
Log.d("JNITEST","setting pic")
|
||||||
|
userProfile.setPic(newUserPic)
|
||||||
|
Log.d("JNITEST","set pic")
|
||||||
|
|
||||||
|
// Retrieve them just to make sure they set properly:
|
||||||
|
assertEquals("Kallie", userProfile.getName())
|
||||||
|
val pic = userProfile.getPic()
|
||||||
|
assertEquals("http://example.org/omg-pic-123.bmp", pic?.url)
|
||||||
|
assertEquals("secret", pic?.key?.decodeToString())
|
||||||
|
|
||||||
|
// Since we've made changes, we should need to push new config to the swarm, *and* should need
|
||||||
|
// to dump the updated state:
|
||||||
|
assertTrue(userProfile.needsPush())
|
||||||
|
assertTrue(userProfile.needsDump())
|
||||||
|
val (newToPush, newSeqNo) = userProfile.push()
|
||||||
|
|
||||||
|
val expHash0 = Hex.fromStringCondensed("ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965")
|
||||||
|
val expectedPush1 = ("d" +
|
||||||
|
"1:#" + "i1e"+
|
||||||
|
"1:&" + "d"+
|
||||||
|
"1:n" + "6:Kallie"+
|
||||||
|
"1:p" + "34:http://example.org/omg-pic-123.bmp"+
|
||||||
|
"1:q" + "6:secret"+
|
||||||
|
"e"+
|
||||||
|
"1:<" + "l"+
|
||||||
|
"l" + "i0e" + "32:").encodeToByteArray() + expHash0 + ("de" + "e" +
|
||||||
|
"e" +
|
||||||
|
"1:=" + "d" +
|
||||||
|
"1:n" + "0:" +
|
||||||
|
"1:p" + "0:" +
|
||||||
|
"1:q" + "0:" +
|
||||||
|
"e" +
|
||||||
|
"e").encodeToByteArray()
|
||||||
|
|
||||||
|
assertEquals(1, newSeqNo)
|
||||||
|
assertArrayEquals(expectedPush1, newToPush)
|
||||||
|
|
||||||
|
|
||||||
|
userProfile.free()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun jni_setting_getting() {
|
||||||
|
val userProfile = UserProfile.newInstance()
|
||||||
|
val newName = "test"
|
||||||
|
println("Name being set via JNI call: $newName")
|
||||||
|
userProfile.setName(newName)
|
||||||
|
val nameFromNative = userProfile.getName()
|
||||||
|
assertEquals(newName, nameFromNative)
|
||||||
|
println("Name received by JNI call: $nameFromNative")
|
||||||
|
assertTrue(userProfile.dirty())
|
||||||
|
userProfile.free()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -14,6 +14,22 @@ session::config::UserProfile* ptrToProfile(JNIEnv* env, jobject obj) {
|
|||||||
return (session::config::UserProfile*) env->GetLongField(obj, pointerField);
|
return (session::config::UserProfile*) env->GetLongField(obj, pointerField);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jbyteArray bytes_from_string(JNIEnv* env, std::string_view from_str) {
|
||||||
|
size_t length = from_str.length();
|
||||||
|
jsize jlength = (jsize)length;
|
||||||
|
jbyteArray new_array = env->NewByteArray(jlength);
|
||||||
|
env->SetByteArrayRegion(new_array, 0, jlength, reinterpret_cast<const jbyte *>(from_str.data()));
|
||||||
|
return new_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string string_from_bytes(JNIEnv* env, jbyteArray byteArray) {
|
||||||
|
size_t len = env->GetArrayLength(byteArray);
|
||||||
|
jbyte* bytes = env->GetByteArrayElements(byteArray, nullptr);
|
||||||
|
std::string newSt((char*)bytes, len);
|
||||||
|
env->ReleaseByteArrayElements(byteArray, bytes, 0);
|
||||||
|
return newSt;
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jobject JNICALL
|
extern "C" JNIEXPORT jobject JNICALL
|
||||||
Java_network_loki_messenger_libsession_1util_UserProfile_00024Companion_newInstance(
|
Java_network_loki_messenger_libsession_1util_UserProfile_00024Companion_newInstance(
|
||||||
JNIEnv* env,
|
JNIEnv* env,
|
||||||
@ -60,28 +76,65 @@ Java_network_loki_messenger_libsession_1util_ConfigBase_dirty(JNIEnv *env, jobje
|
|||||||
auto* configBase = ptrToConfigBase(env, thiz);
|
auto* configBase = ptrToConfigBase(env, thiz);
|
||||||
return configBase->is_dirty();
|
return configBase->is_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_network_loki_messenger_libsession_1util_ConfigBase_needsPush(JNIEnv *env, jobject thiz) {
|
Java_network_loki_messenger_libsession_1util_ConfigBase_needsPush(JNIEnv *env, jobject thiz) {
|
||||||
auto config = ptrToConfigBase(env, thiz);
|
auto config = ptrToConfigBase(env, thiz);
|
||||||
return config->needs_push();
|
return config->needs_push();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_network_loki_messenger_libsession_1util_ConfigBase_needsDump(JNIEnv *env, jobject thiz) {
|
Java_network_loki_messenger_libsession_1util_ConfigBase_needsDump(JNIEnv *env, jobject thiz) {
|
||||||
auto config = ptrToConfigBase(env, thiz);
|
auto config = ptrToConfigBase(env, thiz);
|
||||||
return config->needs_dump();
|
return config->needs_dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jobject JNICALL
|
JNIEXPORT jobject JNICALL
|
||||||
Java_network_loki_messenger_libsession_1util_ConfigBase_push(JNIEnv *env, jobject thiz) {
|
Java_network_loki_messenger_libsession_1util_ConfigBase_push(JNIEnv *env, jobject thiz) {
|
||||||
auto config = ptrToConfigBase(env, thiz);
|
auto config = ptrToConfigBase(env, thiz);
|
||||||
auto pair = config->push();
|
auto pair = config->push();
|
||||||
std::string to_push_str = pair.first;
|
std::string to_push_str = pair.first;
|
||||||
jstring returnString = env->NewStringUTF(to_push_str.c_str());
|
jbyteArray returnByteArray = bytes_from_string(env, to_push_str);
|
||||||
jlong seqNo = pair.second;
|
jlong seqNo = pair.second;
|
||||||
jclass returnObjectClass = env->FindClass("network/loki/messenger/libsession_util/util/ConfigWithSeqNo");
|
jclass returnObjectClass = env->FindClass("network/loki/messenger/libsession_util/util/ConfigWithSeqNo");
|
||||||
jmethodID methodId = env->GetMethodID(returnObjectClass, "<init>", "(Ljava/lang/String;J)V");
|
jmethodID methodId = env->GetMethodID(returnObjectClass, "<init>", "([BJ)V");
|
||||||
jobject returnObject = env->NewObject(returnObjectClass, methodId, returnString, seqNo);
|
jobject returnObject = env->NewObject(returnObjectClass, methodId, returnByteArray, seqNo);
|
||||||
return returnObject;
|
return returnObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jobject JNICALL
|
||||||
|
Java_network_loki_messenger_libsession_1util_UserProfile_getPic(JNIEnv *env, jobject thiz) {
|
||||||
|
auto profile = ptrToProfile(env, thiz);
|
||||||
|
auto pair = profile->get_profile_pic();
|
||||||
|
// return nullptr if either parameter is null as per profile class
|
||||||
|
if (pair.first == nullptr || pair.second == nullptr) return nullptr;
|
||||||
|
const std::string* pic_url = pair.first;
|
||||||
|
const std::string* pic_key = pair.second;
|
||||||
|
jclass returnObjectClass = env->FindClass("network/loki/messenger/libsession_util/UserPic");
|
||||||
|
jmethodID constructor = env->GetMethodID(returnObjectClass, "<init>", "(Ljava/lang/String;[B)V");
|
||||||
|
jstring url = env->NewStringUTF(pic_url->c_str());
|
||||||
|
jbyteArray byteArray = bytes_from_string(env, *pic_key);
|
||||||
|
jobject returnObject = env->NewObject(returnObjectClass, constructor, url, byteArray);
|
||||||
|
return returnObject;
|
||||||
|
}
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_network_loki_messenger_libsession_1util_UserProfile_setPic(JNIEnv *env, jobject thiz,
|
||||||
|
jobject user_pic) {
|
||||||
|
auto profile = ptrToProfile(env, thiz);
|
||||||
|
jclass userPicClass = env->FindClass("network/loki/messenger/libsession_util/UserPic");
|
||||||
|
jfieldID picField = env->GetFieldID(userPicClass, "url", "Ljava/lang/String;");
|
||||||
|
jfieldID keyField = env->GetFieldID(userPicClass, "key", "[B");
|
||||||
|
auto pic = (jstring)env->GetObjectField(user_pic, picField);
|
||||||
|
auto key = (jbyteArray)env->GetObjectField(user_pic, keyField);
|
||||||
|
|
||||||
|
const char* pic_chars = env->GetStringUTFChars(pic, nullptr);
|
||||||
|
const std::string key_str = string_from_bytes(env, key);
|
||||||
|
auto* pic_string = new std::string(pic_chars);
|
||||||
|
|
||||||
|
profile->set_profile_pic(*pic_string, key_str);
|
||||||
|
}
|
@ -25,4 +25,26 @@ class UserProfile(pointer: Long): ConfigBase(pointer) {
|
|||||||
external fun setName(newName: String)
|
external fun setName(newName: String)
|
||||||
external fun getName(): String?
|
external fun getName(): String?
|
||||||
external fun free()
|
external fun free()
|
||||||
|
external fun getPic(): UserPic?
|
||||||
|
external fun setPic(userPic: UserPic)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class UserPic(val url: String, val key: ByteArray) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as UserPic
|
||||||
|
|
||||||
|
if (url != other.url) return false
|
||||||
|
if (!key.contentEquals(other.key)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = url.hashCode()
|
||||||
|
result = 31 * result + key.contentHashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,28 +1,22 @@
|
|||||||
package network.loki.messenger.libsession_util.util
|
package network.loki.messenger.libsession_util.util
|
||||||
|
|
||||||
data class StringWithLen(private val bytes: ByteArray, private val len: Long) { // We might not need this class, could be helpful though
|
data class ConfigWithSeqNo(val config: ByteArray, val seqNo: Long) {
|
||||||
|
|
||||||
override fun toString(): String = bytes.decodeToString()
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
other as StringWithLen
|
other as ConfigWithSeqNo
|
||||||
|
|
||||||
if (!bytes.contentEquals(other.bytes)) return false
|
if (!config.contentEquals(other.config)) return false
|
||||||
if (len != other.len) return false
|
if (seqNo != other.seqNo) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = bytes.contentHashCode()
|
var result = config.contentHashCode()
|
||||||
result = 31 * result + len.hashCode()
|
result = 31 * result + seqNo.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data class ConfigWithSeqNo(val config: String, val seqNo: Long)
|
|
Loading…
Reference in New Issue
Block a user