mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-28 20:45:17 +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 {
|
||||
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'
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
Loading…
Reference in New Issue
Block a user