feat: improve cpp jni layer and add more test functionality

This commit is contained in:
0x330a 2022-12-02 16:52:57 +11:00
parent f0aea7aaa9
commit 643d139e8a
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C
6 changed files with 196 additions and 76 deletions

View File

@ -40,6 +40,7 @@ android {
dependencies {
testImplementation 'junit:junit:4.13.2'
api(project(":libsignal"))
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -14,6 +14,22 @@ session::config::UserProfile* ptrToProfile(JNIEnv* env, jobject obj) {
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
Java_network_loki_messenger_libsession_1util_UserProfile_00024Companion_newInstance(
JNIEnv* env,
@ -60,28 +76,65 @@ Java_network_loki_messenger_libsession_1util_ConfigBase_dirty(JNIEnv *env, jobje
auto* configBase = ptrToConfigBase(env, thiz);
return configBase->is_dirty();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_needsPush(JNIEnv *env, jobject thiz) {
auto config = ptrToConfigBase(env, thiz);
return config->needs_push();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_needsDump(JNIEnv *env, jobject thiz) {
auto config = ptrToConfigBase(env, thiz);
return config->needs_dump();
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_push(JNIEnv *env, jobject thiz) {
auto config = ptrToConfigBase(env, thiz);
auto pair = config->push();
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;
jclass returnObjectClass = env->FindClass("network/loki/messenger/libsession_util/util/ConfigWithSeqNo");
jmethodID methodId = env->GetMethodID(returnObjectClass, "<init>", "(Ljava/lang/String;J)V");
jobject returnObject = env->NewObject(returnObjectClass, methodId, returnString, seqNo);
jmethodID methodId = env->GetMethodID(returnObjectClass, "<init>", "([BJ)V");
jobject returnObject = env->NewObject(returnObjectClass, methodId, returnByteArray, seqNo);
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);
}

View File

@ -25,4 +25,26 @@ class UserProfile(pointer: Long): ConfigBase(pointer) {
external fun setName(newName: String)
external fun getName(): String?
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
}
}

View File

@ -1,28 +1,22 @@
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
override fun toString(): String = bytes.decodeToString()
data class ConfigWithSeqNo(val config: ByteArray, val seqNo: Long) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as StringWithLen
other as ConfigWithSeqNo
if (!bytes.contentEquals(other.bytes)) return false
if (len != other.len) return false
if (!config.contentEquals(other.config)) return false
if (seqNo != other.seqNo) return false
return true
}
override fun hashCode(): Int {
var result = bytes.contentHashCode()
result = 31 * result + len.hashCode()
var result = config.contentHashCode()
result = 31 * result + seqNo.hashCode()
return result
}
}
data class ConfigWithSeqNo(val config: String, val seqNo: Long)