Merge branch 'dev' into swap-video-views

This commit is contained in:
Ryan ZHAO
2024-04-08 10:05:28 +10:00
622 changed files with 21580 additions and 22204 deletions

2
libsession-util/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/build
/.cxx/

View File

@@ -0,0 +1,47 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'network.loki.messenger.libsession_util'
compileSdkVersion androidCompileSdkVersion
defaultConfig {
minSdkVersion androidMinimumSdkVersion
targetSdkVersion androidCompileSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
}
}
}
buildTypes {
release {
minifyEnabled false
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.22.1+"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
testImplementation 'junit:junit:4.13.2'
implementation(project(":libsignal"))
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

View File

@@ -0,0 +1,584 @@
package network.loki.messenger.libsession_util
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import network.loki.messenger.libsession_util.util.*
import org.hamcrest.CoreMatchers.not
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.Log
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class InstrumentedTests {
val seed =
Hex.fromStringCondensed("0123456789abcdef0123456789abcdef00000000000000000000000000000000")
private val keyPair: KeyPair
get() {
return Sodium.ed25519KeyPair(seed)
}
@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_test_sodium_kp_ed_curve() {
val kp = keyPair
val curvePkBytes = Sodium.ed25519PkToCurve25519(kp.pubKey)
val edPk = kp.pubKey
val curvePk = curvePkBytes
assertArrayEquals(Hex.fromStringCondensed("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"), edPk)
assertArrayEquals(Hex.fromStringCondensed("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"), curvePk)
assertArrayEquals(kp.secretKey.take(32).toByteArray(), seed)
}
@Test
fun testDirtyEmptyString() {
val contacts = Contacts.newInstance(keyPair.secretKey)
val definitelyRealId = "050000000000000000000000000000000000000000000000000000000000000000"
val contact = contacts.getOrConstruct(definitelyRealId)
contacts.set(contact)
assertTrue(contacts.dirty())
contacts.set(contact.copy(name = "test"))
assertTrue(contacts.dirty())
val push = contacts.push()
contacts.confirmPushed(push.seqNo, "abc123")
contacts.dump()
contacts.set(contact.copy(name = "test2"))
contacts.set(contact.copy(name = "test"))
assertTrue(contacts.dirty())
}
@Test
fun jni_contacts() {
val contacts = Contacts.newInstance(keyPair.secretKey)
val definitelyRealId = "050000000000000000000000000000000000000000000000000000000000000000"
assertNull(contacts.get(definitelyRealId))
// Should be an uninitialized contact apart from ID
val c = contacts.getOrConstruct(definitelyRealId)
assertEquals(definitelyRealId, c.id)
assertTrue(c.name.isEmpty())
assertTrue(c.nickname.isEmpty())
assertFalse(c.approved)
assertFalse(c.approvedMe)
assertFalse(c.blocked)
assertEquals(UserPic.DEFAULT, c.profilePicture)
assertFalse(contacts.needsPush())
assertFalse(contacts.needsDump())
assertEquals(0, contacts.push().seqNo)
c.name = "Joe"
c.nickname = "Joey"
c.approved = true
c.approvedMe = true
contacts.set(c)
val cSaved = contacts.get(definitelyRealId)!!
assertEquals("Joe", cSaved.name)
assertEquals("Joey", cSaved.nickname)
assertTrue(cSaved.approved)
assertTrue(cSaved.approvedMe)
assertFalse(cSaved.blocked)
assertEquals(UserPic.DEFAULT, cSaved.profilePicture)
val push1 = contacts.push()
assertEquals(1, push1.seqNo)
contacts.confirmPushed(push1.seqNo, "fakehash1")
assertFalse(contacts.needsPush())
assertTrue(contacts.needsDump())
val contacts2 = Contacts.newInstance(keyPair.secretKey, contacts.dump())
assertFalse(contacts.needsDump())
assertFalse(contacts2.needsPush())
assertFalse(contacts2.needsDump())
val anotherId = "051111111111111111111111111111111111111111111111111111111111111111"
val c2 = contacts2.getOrConstruct(anotherId)
contacts2.set(c2)
val push2 = contacts2.push()
assertEquals(2, push2.seqNo)
contacts2.confirmPushed(push2.seqNo, "fakehash2")
assertFalse(contacts2.needsPush())
contacts.merge("fakehash2" to push2.config)
assertFalse(contacts.needsPush())
assertEquals(push2.seqNo, contacts.push().seqNo)
val contactList = contacts.all().toList()
assertEquals(definitelyRealId, contactList[0].id)
assertEquals(anotherId, contactList[1].id)
assertEquals("Joey", contactList[0].nickname)
assertEquals("", contactList[1].nickname)
contacts.erase(definitelyRealId)
val thirdId ="052222222222222222222222222222222222222222222222222222222222222222"
val third = Contact(
id = thirdId,
nickname = "Nickname 3",
approved = true,
blocked = true,
profilePicture = UserPic("http://example.com/huge.bmp", "qwertyuio01234567890123456789012".encodeToByteArray()),
expiryMode = ExpiryMode.NONE
)
contacts2.set(third)
assertTrue(contacts.needsPush())
assertTrue(contacts2.needsPush())
val toPush = contacts.push()
val toPush2 = contacts2.push()
assertEquals(toPush.seqNo, toPush2.seqNo)
assertThat(toPush2.config, not(equals(toPush.config)))
contacts.confirmPushed(toPush.seqNo, "fakehash3a")
contacts2.confirmPushed(toPush2.seqNo, "fakehash3b")
contacts.merge("fakehash3b" to toPush2.config)
contacts2.merge("fakehash3a" to toPush.config)
assertTrue(contacts.needsPush())
assertTrue(contacts2.needsPush())
val mergePush = contacts.push()
val mergePush2 = contacts2.push()
assertEquals(mergePush.seqNo, mergePush2.seqNo)
assertArrayEquals(mergePush.config, mergePush2.config)
assertTrue(mergePush.obsoleteHashes.containsAll(listOf("fakehash3b", "fakehash3a")))
assertTrue(mergePush2.obsoleteHashes.containsAll(listOf("fakehash3b", "fakehash3a")))
}
@Test
fun jni_accessible() {
val userProfile = UserProfile.newInstance(keyPair.secretKey)
assertNotNull(userProfile)
userProfile.free()
}
@Test
fun jni_user_profile_c_api() {
val edSk = keyPair.secretKey
val userProfile = UserProfile.newInstance(edSk)
// 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 (_, seqNo) = userProfile.push() // disregarding encrypted
assertEquals("UserProfile", userProfile.encryptionDomain())
assertEquals(0, seqNo)
// This should also be unset:
assertEquals(UserPic.DEFAULT, 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", "secret78901234567890123456789012".encodeToByteArray())
userProfile.setPic(newUserPic)
userProfile.setNtsPriority(9)
// 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("secret78901234567890123456789012", 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 expectedPush1Decrypted = ("d" +
"1:#"+ "i1e" +
"1:&"+ "d"+
"1:+"+ "i9e"+
"1:n"+ "6:Kallie"+
"1:p"+ "34:http://example.org/omg-pic-123.bmp"+
"1:q"+ "32:secret78901234567890123456789012"+
"e"+
"1:<"+ "l"+
"l"+ "i0e"+ "32:").encodeToByteArray() + expHash0 + ("de"+ "e"+
"e"+
"1:="+ "d"+
"1:+" +"0:"+
"1:n" +"0:"+
"1:p" +"0:"+
"1:q" +"0:"+
"e"+
"e").encodeToByteArray()
assertEquals(1, newSeqNo)
// We haven't dumped, so still need to dump:
assertTrue(userProfile.needsDump())
// We did call push but we haven't confirmed it as stored yet, so this will still return true:
assertTrue(userProfile.needsPush())
val dump = userProfile.dump()
// (in a real client we'd now store this to disk)
assertFalse(userProfile.needsDump())
val expectedDump = ("d" +
"1:!"+ "i2e" +
"1:$").encodeToByteArray() + expectedPush1Decrypted.size.toString().encodeToByteArray() +
":".encodeToByteArray() + expectedPush1Decrypted +
"1:(0:1:)le".encodeToByteArray()+
"e".encodeToByteArray()
assertArrayEquals(expectedDump, dump)
userProfile.confirmPushed(newSeqNo, "fakehash1")
val newConf = UserProfile.newInstance(edSk)
val accepted = newConf.merge("fakehash1" to newToPush)
assertEquals(1, accepted)
assertTrue(newConf.needsDump())
assertFalse(newConf.needsPush())
val _ignore = newConf.dump()
assertFalse(newConf.needsDump())
userProfile.setName("Raz")
newConf.setName("Nibbler")
newConf.setPic(UserPic("http://new.example.com/pic", "qwertyuio01234567890123456789012".encodeToByteArray()))
val conf = userProfile.push()
val conf2 = newConf.push()
userProfile.confirmPushed(conf.seqNo, "fakehash2")
newConf.confirmPushed(conf2.seqNo, "fakehash3")
userProfile.dump()
assertFalse(conf.config.contentEquals(conf2.config))
newConf.merge("fakehash2" to conf.config)
userProfile.merge("fakehash3" to conf2.config)
assertTrue(newConf.needsPush())
assertTrue(userProfile.needsPush())
val newSeq1 = userProfile.push()
assertEquals(3, newSeq1.seqNo)
userProfile.confirmPushed(newSeq1.seqNo, "fakehash4")
// assume newConf push gets rejected as it was last to write and clear previous config by hash on oxenss
newConf.merge("fakehash4" to newSeq1.config)
val newSeqMerge = newConf.push()
newConf.confirmPushed(newSeqMerge.seqNo, "fakehash5")
assertEquals("Raz", newConf.getName())
assertEquals(3, newSeqMerge.seqNo)
// userProfile device polls and merges
userProfile.merge("fakehash5" to newSeqMerge.config)
val userConfigMerge = userProfile.push()
assertEquals(3, userConfigMerge.seqNo)
assertEquals("Raz", newConf.getName())
assertEquals("Raz", userProfile.getName())
userProfile.free()
newConf.free()
}
@Test
fun merge_resolves_conflicts() {
val kp = keyPair
val a = UserProfile.newInstance(kp.secretKey)
val b = UserProfile.newInstance(kp.secretKey)
a.setName("A")
val (aPush, aSeq) = a.push()
a.confirmPushed(aSeq, "hashfroma")
b.setName("B")
// polls and sees invalid state, has to merge
b.merge("hashfroma" to aPush)
val (bPush, bSeq) = b.push()
b.confirmPushed(bSeq, "hashfromb")
assertEquals("B", b.getName())
assertEquals(1, aSeq)
assertEquals(2, bSeq)
a.merge("hashfromb" to bPush)
assertEquals(2, a.push().seqNo)
}
@Test
fun jni_setting_getting() {
val userProfile = UserProfile.newInstance(keyPair.secretKey)
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()
}
@Test
fun jni_remove_all_test() {
val convos = ConversationVolatileConfig.newInstance(keyPair.secretKey)
assertEquals(0 /* number removed */, convos.eraseAll { true /* 'erase' every item */ })
val definitelyRealId = "050000000000000000000000000000000000000000000000000000000000000000"
val definitelyRealConvo = Conversation.OneToOne(definitelyRealId, System.currentTimeMillis(), false)
convos.set(definitelyRealConvo)
val anotherDefinitelyReadId = "051111111111111111111111111111111111111111111111111111111111111111"
val anotherDefinitelyRealConvo = Conversation.OneToOne(anotherDefinitelyReadId, System.currentTimeMillis(), false)
convos.set(anotherDefinitelyRealConvo)
assertEquals(2, convos.sizeOneToOnes())
val numErased = convos.eraseAll { convo ->
convo is Conversation.OneToOne && convo.sessionId == definitelyRealId
}
assertEquals(1, numErased)
assertEquals(1, convos.sizeOneToOnes())
}
@Test
fun test_open_group_urls() {
val (base1, room1, pk1) = BaseCommunityInfo.parseFullUrl(
"https://example.com/" +
"someroom?public_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
)!!
val (base2, room2, pk2) = BaseCommunityInfo.parseFullUrl(
"HTTPS://EXAMPLE.COM/" +
"someroom?public_key=0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"
)!!
val (base3, room3, pk3) = BaseCommunityInfo.parseFullUrl(
"HTTPS://EXAMPLE.COM/r/" +
"someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
)!!
val (base4, room4, pk4) = BaseCommunityInfo.parseFullUrl(
"http://example.com/r/" +
"someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
)!!
val (base5, room5, pk5) = BaseCommunityInfo.parseFullUrl(
"HTTPS://EXAMPLE.com:443/r/" +
"someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
)!!
val (base6, room6, pk6) = BaseCommunityInfo.parseFullUrl(
"HTTP://EXAMPLE.com:80/r/" +
"someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
)!!
val (base7, room7, pk7) = BaseCommunityInfo.parseFullUrl(
"http://example.com:80/r/" +
"someroom?public_key=ASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8"
)!!
val (base8, room8, pk8) = BaseCommunityInfo.parseFullUrl(
"http://example.com:80/r/" +
"someroom?public_key=yrtwk3hjixg66yjdeiuauk6p7hy1gtm8tgih55abrpnsxnpm3zzo"
)!!
assertEquals("https://example.com", base1)
assertEquals("http://example.com", base4)
assertEquals(base1, base2)
assertEquals(base1, base3)
assertNotEquals(base1, base4)
assertEquals(base1, base5)
assertEquals(base4, base6)
assertEquals(base4, base7)
assertEquals(base4, base8)
assertEquals("someroom", room1)
assertEquals("someroom", room2)
assertEquals("someroom", room3)
assertEquals("someroom", room4)
assertEquals("someroom", room5)
assertEquals("someroom", room6)
assertEquals("someroom", room7)
assertEquals("someroom", room8)
assertEquals(Hex.toStringCondensed(pk1), "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
assertEquals(Hex.toStringCondensed(pk2), "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
assertEquals(Hex.toStringCondensed(pk3), "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
assertEquals(Hex.toStringCondensed(pk4), "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
assertEquals(Hex.toStringCondensed(pk5), "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
assertEquals(Hex.toStringCondensed(pk6), "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
assertEquals(Hex.toStringCondensed(pk7), "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
assertEquals(Hex.toStringCondensed(pk8), "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
}
@Test
fun test_conversations() {
val convos = ConversationVolatileConfig.newInstance(keyPair.secretKey)
val definitelyRealId = "055000000000000000000000000000000000000000000000000000000000000000"
assertNull(convos.getOneToOne(definitelyRealId))
assertTrue(convos.empty())
assertEquals(0, convos.size())
val c = convos.getOrConstructOneToOne(definitelyRealId)
assertEquals(definitelyRealId, c.sessionId)
assertEquals(0, c.lastRead)
assertFalse(convos.needsPush())
assertFalse(convos.needsDump())
assertEquals(0, convos.push().seqNo)
val nowMs = System.currentTimeMillis()
c.lastRead = nowMs
convos.set(c)
assertNull(convos.getLegacyClosedGroup(definitelyRealId))
assertNotNull(convos.getOneToOne(definitelyRealId))
assertEquals(nowMs, convos.getOneToOne(definitelyRealId)?.lastRead)
assertTrue(convos.needsPush())
assertTrue(convos.needsDump())
val openGroupPubKey = Hex.fromStringCondensed("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
val og = convos.getOrConstructCommunity("http://Example.ORG:5678", "SudokuRoom", openGroupPubKey)
val ogCommunity = og.baseCommunityInfo
assertEquals("http://example.org:5678", ogCommunity.baseUrl) // Note: lower-case
assertEquals("sudokuroom", ogCommunity.room) // Note: lower-case
assertEquals(64, ogCommunity.pubKeyHex.length)
assertEquals("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", ogCommunity.pubKeyHex)
og.unread = true
convos.set(og)
val (_, seqNo) = convos.push()
assertEquals(1, seqNo)
convos.confirmPushed(seqNo, "fakehash1")
assertTrue(convos.needsDump())
assertFalse(convos.needsPush())
val convos2 = ConversationVolatileConfig.newInstance(keyPair.secretKey, convos.dump())
assertFalse(convos.needsPush())
assertFalse(convos.needsDump())
assertEquals(1, convos.push().seqNo)
assertFalse(convos.needsDump())
val x1 = convos2.getOneToOne(definitelyRealId)!!
assertEquals(nowMs, x1.lastRead)
assertEquals(definitelyRealId, x1.sessionId)
assertEquals(false, x1.unread)
val x2 = convos2.getCommunity("http://EXAMPLE.org:5678", "sudokuRoom")!!
val x2Info = x2.baseCommunityInfo
assertEquals("http://example.org:5678", x2Info.baseUrl)
assertEquals("sudokuroom", x2Info.room)
assertEquals(x2Info.pubKeyHex, Hex.toStringCondensed(openGroupPubKey))
assertTrue(x2.unread)
val anotherId = "051111111111111111111111111111111111111111111111111111111111111111"
val c2 = convos.getOrConstructOneToOne(anotherId)
c2.unread = true
convos2.set(c2)
val c3 = convos.getOrConstructLegacyGroup(
"05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
)
c3.lastRead = nowMs - 50
convos2.set(c3)
assertTrue(convos2.needsPush())
val (toPush2, seqNo2) = convos2.push()
assertEquals(2, seqNo2)
convos2.confirmPushed(seqNo2, "fakehash2")
convos.merge("fakehash2" to toPush2)
assertFalse(convos.needsPush())
assertEquals(seqNo2, convos.push().seqNo)
val seen = mutableListOf<String>()
for ((ind, conv) in listOf(convos, convos2).withIndex()) {
Log.e("Test","Testing seen from convo #$ind")
seen.clear()
assertEquals(4, conv.size())
assertEquals(2, conv.sizeOneToOnes())
assertEquals(1, conv.sizeCommunities())
assertEquals(1, conv.sizeLegacyClosedGroups())
assertFalse(conv.empty())
val allConvos = conv.all()
for (convo in allConvos) {
when (convo) {
is Conversation.OneToOne -> seen.add("1-to-1: ${convo.sessionId}")
is Conversation.Community -> seen.add("og: ${convo.baseCommunityInfo.baseUrl}/r/${convo.baseCommunityInfo.room}")
is Conversation.LegacyGroup -> seen.add("cl: ${convo.groupId}")
}
}
assertTrue(seen.contains("1-to-1: 051111111111111111111111111111111111111111111111111111111111111111"))
assertTrue(seen.contains("1-to-1: 055000000000000000000000000000000000000000000000000000000000000000"))
assertTrue(seen.contains("og: http://example.org:5678/r/sudokuroom"))
assertTrue(seen.contains("cl: 05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"))
assertTrue(seen.size == 4) // for some reason iterative checks aren't working in test cases
}
assertFalse(convos.needsPush())
convos.eraseOneToOne("052000000000000000000000000000000000000000000000000000000000000000")
assertFalse(convos.needsPush())
convos.eraseOneToOne("055000000000000000000000000000000000000000000000000000000000000000")
assertTrue(convos.needsPush())
assertEquals(1, convos.allOneToOnes().size)
assertEquals("051111111111111111111111111111111111111111111111111111111111111111",
convos.allOneToOnes().map(Conversation.OneToOne::sessionId).first()
)
assertEquals(1, convos.allCommunities().size)
assertEquals("http://example.org:5678",
convos.allCommunities().map { it.baseCommunityInfo.baseUrl }.first()
)
assertEquals(1, convos.allLegacyClosedGroups().size)
assertEquals("05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
convos.allLegacyClosedGroups().map(Conversation.LegacyGroup::groupId).first()
)
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="network.loki.messenger.libsession_util">
</manifest>

View File

@@ -0,0 +1,67 @@
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.18.1)
# Declares and names the project.
project("session_util")
# Compiles in C++17 mode
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_BUILD_TYPE Release)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
set(STATIC_BUNDLE ON)
add_subdirectory(../../../libsession-util libsession)
set(SOURCES
user_profile.cpp
user_groups.cpp
config_base.cpp
contacts.cpp
conversation.cpp
util.cpp)
add_library( # Sets the name of the library.
session_util
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
${SOURCES})
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
session_util
PUBLIC
libsession::config
libsession::crypto
libsodium::sodium-internal
# Links the target library to the log library
# included in the NDK.
${log-lib})

View File

@@ -0,0 +1,158 @@
#include "config_base.h"
#include "util.h"
extern "C" {
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_dirty(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto* configBase = ptrToConfigBase(env, thiz);
return configBase->is_dirty();
}
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_needsPush(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto config = ptrToConfigBase(env, thiz);
return config->needs_push();
}
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_needsDump(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto config = ptrToConfigBase(env, thiz);
return config->needs_dump();
}
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_push(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto config = ptrToConfigBase(env, thiz);
auto push_tuple = config->push();
auto to_push_str = std::get<1>(push_tuple);
auto to_delete = std::get<2>(push_tuple);
jbyteArray returnByteArray = util::bytes_from_ustring(env, to_push_str);
jlong seqNo = std::get<0>(push_tuple);
jclass returnObjectClass = env->FindClass("network/loki/messenger/libsession_util/util/ConfigPush");
jclass stackClass = env->FindClass("java/util/Stack");
jmethodID methodId = env->GetMethodID(returnObjectClass, "<init>", "([BJLjava/util/List;)V");
jmethodID stack_init = env->GetMethodID(stackClass, "<init>", "()V");
jobject our_stack = env->NewObject(stackClass, stack_init);
jmethodID push_stack = env->GetMethodID(stackClass, "push", "(Ljava/lang/Object;)Ljava/lang/Object;");
for (auto entry : to_delete) {
auto entry_jstring = env->NewStringUTF(entry.data());
env->CallObjectMethod(our_stack, push_stack, entry_jstring);
}
jobject returnObject = env->NewObject(returnObjectClass, methodId, returnByteArray, seqNo, our_stack);
return returnObject;
}
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_free(JNIEnv *env, jobject thiz) {
auto config = ptrToConfigBase(env, thiz);
delete config;
}
JNIEXPORT jbyteArray JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_dump(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto config = ptrToConfigBase(env, thiz);
auto dumped = config->dump();
jbyteArray bytes = util::bytes_from_ustring(env, dumped);
return bytes;
}
JNIEXPORT jstring JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_encryptionDomain(JNIEnv *env,
jobject thiz) {
auto conf = ptrToConfigBase(env, thiz);
return env->NewStringUTF(conf->encryption_domain());
}
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_confirmPushed(JNIEnv *env, jobject thiz,
jlong seq_no,
jstring new_hash_jstring) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToConfigBase(env, thiz);
auto new_hash = env->GetStringUTFChars(new_hash_jstring, nullptr);
conf->confirm_pushed(seq_no, new_hash);
env->ReleaseStringUTFChars(new_hash_jstring, new_hash);
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "bugprone-reserved-identifier"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_merge___3Lkotlin_Pair_2(JNIEnv *env, jobject thiz,
jobjectArray to_merge) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToConfigBase(env, thiz);
size_t number = env->GetArrayLength(to_merge);
std::vector<std::pair<std::string,session::ustring>> configs = {};
for (int i = 0; i < number; i++) {
auto jElement = (jobject) env->GetObjectArrayElement(to_merge, i);
auto pair = extractHashAndData(env, jElement);
configs.push_back(pair);
}
auto returned = conf->merge(configs);
auto string_stack = util::build_string_stack(env, returned);
return string_stack;
}
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConfigBase_merge__Lkotlin_Pair_2(JNIEnv *env, jobject thiz,
jobject to_merge) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToConfigBase(env, thiz);
std::vector<std::pair<std::string, session::ustring>> configs = {extractHashAndData(env, to_merge)};
auto returned = conf->merge(configs);
auto string_stack = util::build_string_stack(env, returned);
return string_stack;
}
#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
Java_network_loki_messenger_libsession_1util_ConfigBase_currentHashes(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToConfigBase(env, thiz);
jclass stack = env->FindClass("java/util/Stack");
jmethodID init = env->GetMethodID(stack, "<init>", "()V");
jobject our_stack = env->NewObject(stack, init);
jmethodID push = env->GetMethodID(stack, "push", "(Ljava/lang/Object;)Ljava/lang/Object;");
auto vec = conf->current_hashes();
for (std::string element: vec) {
env->CallObjectMethod(our_stack, push, env->NewStringUTF(element.data()));
}
return our_stack;
}

View File

@@ -0,0 +1,28 @@
#ifndef SESSION_ANDROID_CONFIG_BASE_H
#define SESSION_ANDROID_CONFIG_BASE_H
#include "session/config/base.hpp"
#include "util.h"
#include <jni.h>
#include <string>
inline session::config::ConfigBase* ptrToConfigBase(JNIEnv *env, jobject obj) {
jclass baseClass = env->FindClass("network/loki/messenger/libsession_util/ConfigBase");
jfieldID pointerField = env->GetFieldID(baseClass, "pointer", "J");
return (session::config::ConfigBase*) env->GetLongField(obj, pointerField);
}
inline std::pair<std::string, session::ustring> extractHashAndData(JNIEnv *env, jobject kotlin_pair) {
jclass pair = env->FindClass("kotlin/Pair");
jfieldID first = env->GetFieldID(pair, "first", "Ljava/lang/Object;");
jfieldID second = env->GetFieldID(pair, "second", "Ljava/lang/Object;");
jstring hash_as_jstring = static_cast<jstring>(env->GetObjectField(kotlin_pair, first));
jbyteArray data_as_jbytes = static_cast<jbyteArray>(env->GetObjectField(kotlin_pair, second));
auto hash_as_string = env->GetStringUTFChars(hash_as_jstring, nullptr);
auto data_as_ustring = util::ustring_from_bytes(env, data_as_jbytes);
auto ret_pair = std::pair<std::string, session::ustring>{hash_as_string, data_as_ustring};
env->ReleaseStringUTFChars(hash_as_jstring, hash_as_string);
return ret_pair;
}
#endif

View File

@@ -0,0 +1,100 @@
#include "contacts.h"
#include "util.h"
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_Contacts_get(JNIEnv *env, jobject thiz,
jstring session_id) {
std::lock_guard lock{util::util_mutex_};
auto contacts = ptrToContacts(env, thiz);
auto session_id_chars = env->GetStringUTFChars(session_id, nullptr);
auto contact = contacts->get(session_id_chars);
env->ReleaseStringUTFChars(session_id, session_id_chars);
if (!contact) return nullptr;
jobject j_contact = serialize_contact(env, contact.value());
return j_contact;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_Contacts_getOrConstruct(JNIEnv *env, jobject thiz,
jstring session_id) {
std::lock_guard lock{util::util_mutex_};
auto contacts = ptrToContacts(env, thiz);
auto session_id_chars = env->GetStringUTFChars(session_id, nullptr);
auto contact = contacts->get_or_construct(session_id_chars);
env->ReleaseStringUTFChars(session_id, session_id_chars);
return serialize_contact(env, contact);
}
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_Contacts_set(JNIEnv *env, jobject thiz,
jobject contact) {
std::lock_guard lock{util::util_mutex_};
auto contacts = ptrToContacts(env, thiz);
auto contact_info = deserialize_contact(env, contact, contacts);
contacts->set(contact_info);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_Contacts_erase(JNIEnv *env, jobject thiz,
jstring session_id) {
std::lock_guard lock{util::util_mutex_};
auto contacts = ptrToContacts(env, thiz);
auto session_id_chars = env->GetStringUTFChars(session_id, nullptr);
bool result = contacts->erase(session_id_chars);
env->ReleaseStringUTFChars(session_id, session_id_chars);
return result;
}
extern "C"
#pragma clang diagnostic push
#pragma ide diagnostic ignored "bugprone-reserved-identifier"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_Contacts_00024Companion_newInstance___3B(JNIEnv *env,
jobject thiz,
jbyteArray ed25519_secret_key) {
std::lock_guard lock{util::util_mutex_};
auto secret_key = util::ustring_from_bytes(env, ed25519_secret_key);
auto* contacts = new session::config::Contacts(secret_key, std::nullopt);
jclass contactsClass = env->FindClass("network/loki/messenger/libsession_util/Contacts");
jmethodID constructor = env->GetMethodID(contactsClass, "<init>", "(J)V");
jobject newConfig = env->NewObject(contactsClass, constructor, reinterpret_cast<jlong>(contacts));
return newConfig;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_Contacts_00024Companion_newInstance___3B_3B(
JNIEnv *env, jobject thiz, jbyteArray ed25519_secret_key, jbyteArray initial_dump) {
std::lock_guard lock{util::util_mutex_};
auto secret_key = util::ustring_from_bytes(env, ed25519_secret_key);
auto initial = util::ustring_from_bytes(env, initial_dump);
auto* contacts = new session::config::Contacts(secret_key, initial);
jclass contactsClass = env->FindClass("network/loki/messenger/libsession_util/Contacts");
jmethodID constructor = env->GetMethodID(contactsClass, "<init>", "(J)V");
jobject newConfig = env->NewObject(contactsClass, constructor, reinterpret_cast<jlong>(contacts));
return newConfig;
}
#pragma clang diagnostic pop
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_Contacts_all(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto contacts = ptrToContacts(env, thiz);
jclass stack = env->FindClass("java/util/Stack");
jmethodID init = env->GetMethodID(stack, "<init>", "()V");
jobject our_stack = env->NewObject(stack, init);
jmethodID push = env->GetMethodID(stack, "push", "(Ljava/lang/Object;)Ljava/lang/Object;");
for (const auto& contact : *contacts) {
auto contact_obj = serialize_contact(env, contact);
env->CallObjectMethod(our_stack, push, contact_obj);
}
return our_stack;
}

View File

@@ -0,0 +1,108 @@
#ifndef SESSION_ANDROID_CONTACTS_H
#define SESSION_ANDROID_CONTACTS_H
#include <jni.h>
#include "session/config/contacts.hpp"
#include "util.h"
inline session::config::Contacts *ptrToContacts(JNIEnv *env, jobject obj) {
jclass contactsClass = env->FindClass("network/loki/messenger/libsession_util/Contacts");
jfieldID pointerField = env->GetFieldID(contactsClass, "pointer", "J");
return (session::config::Contacts *) env->GetLongField(obj, pointerField);
}
inline jobject serialize_contact(JNIEnv *env, session::config::contact_info info) {
jclass contactClass = env->FindClass("network/loki/messenger/libsession_util/util/Contact");
jmethodID constructor = env->GetMethodID(contactClass, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZLnetwork/loki/messenger/libsession_util/util/UserPic;ILnetwork/loki/messenger/libsession_util/util/ExpiryMode;)V");
jstring id = env->NewStringUTF(info.session_id.data());
jstring name = env->NewStringUTF(info.name.data());
jstring nickname = env->NewStringUTF(info.nickname.data());
jboolean approved, approvedMe, blocked;
approved = info.approved;
approvedMe = info.approved_me;
blocked = info.blocked;
auto created = info.created;
jobject profilePic = util::serialize_user_pic(env, info.profile_picture);
jobject returnObj = env->NewObject(contactClass, constructor, id, name, nickname, approved,
approvedMe, blocked, profilePic, info.priority,
util::serialize_expiry(env, info.exp_mode, info.exp_timer));
return returnObj;
}
inline 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;
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", "I");
getExpiry = env->GetFieldID(contactClass, "expiryMode", "Lnetwork/loki/messenger/libsession_util/util/ExpiryMode;");
jstring name, nickname, session_id;
session_id = static_cast<jstring>(env->GetObjectField(info, getId));
name = static_cast<jstring>(env->GetObjectField(info, getName));
nickname = static_cast<jstring>(env->GetObjectField(info, getNick));
bool approved, approvedMe, blocked, hidden;
int priority = env->GetIntField(info, getPriority);
approved = env->GetBooleanField(info, getApproved);
approvedMe = env->GetBooleanField(info, getApprovedMe);
blocked = env->GetBooleanField(info, getBlocked);
jobject user_pic = env->GetObjectField(info, getUserPic);
jobject expiry_mode = env->GetObjectField(info, getExpiry);
auto expiry_pair = util::deserialize_expiry(env, expiry_mode);
std::string url;
session::ustring key;
if (user_pic != nullptr) {
auto deserialized_pic = util::deserialize_user_pic(env, user_pic);
auto url_jstring = deserialized_pic.first;
auto url_bytes = env->GetStringUTFChars(url_jstring, nullptr);
url = std::string(url_bytes);
env->ReleaseStringUTFChars(url_jstring, url_bytes);
key = util::ustring_from_bytes(env, deserialized_pic.second);
}
auto session_id_bytes = env->GetStringUTFChars(session_id, nullptr);
auto name_bytes = name ? env->GetStringUTFChars(name, nullptr) : nullptr;
auto nickname_bytes = nickname ? env->GetStringUTFChars(nickname, nullptr) : nullptr;
auto contact_info = conf->get_or_construct(session_id_bytes);
if (name_bytes) {
contact_info.name = name_bytes;
}
if (nickname_bytes) {
contact_info.nickname = nickname_bytes;
}
contact_info.approved = approved;
contact_info.approved_me = approvedMe;
contact_info.blocked = blocked;
if (!url.empty() && !key.empty()) {
contact_info.profile_picture = session::config::profile_pic(url, key);
} else {
contact_info.profile_picture = session::config::profile_pic();
}
env->ReleaseStringUTFChars(session_id, session_id_bytes);
if (name_bytes) {
env->ReleaseStringUTFChars(name, name_bytes);
}
if (nickname_bytes) {
env->ReleaseStringUTFChars(nickname, nickname_bytes);
}
contact_info.priority = priority;
contact_info.exp_mode = expiry_pair.first;
contact_info.exp_timer = std::chrono::seconds(expiry_pair.second);
return contact_info;
}
#endif //SESSION_ANDROID_CONTACTS_H

View File

@@ -0,0 +1,352 @@
#include <jni.h>
#include "conversation.h"
#pragma clang diagnostic push
extern "C"
#pragma ide diagnostic ignored "bugprone-reserved-identifier"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_00024Companion_newInstance___3B(
JNIEnv *env, jobject thiz, jbyteArray ed25519_secret_key) {
std::lock_guard lock{util::util_mutex_};
auto secret_key = util::ustring_from_bytes(env, ed25519_secret_key);
auto* convo_info_volatile = new session::config::ConvoInfoVolatile(secret_key, std::nullopt);
jclass convoClass = env->FindClass("network/loki/messenger/libsession_util/ConversationVolatileConfig");
jmethodID constructor = env->GetMethodID(convoClass, "<init>", "(J)V");
jobject newConfig = env->NewObject(convoClass, constructor, reinterpret_cast<jlong>(convo_info_volatile));
return newConfig;
}
extern "C"
#pragma ide diagnostic ignored "bugprone-reserved-identifier"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_00024Companion_newInstance___3B_3B(
JNIEnv *env, jobject thiz, jbyteArray ed25519_secret_key, jbyteArray initial_dump) {
std::lock_guard lock{util::util_mutex_};
auto secret_key = util::ustring_from_bytes(env, ed25519_secret_key);
auto initial = util::ustring_from_bytes(env, initial_dump);
auto* convo_info_volatile = new session::config::ConvoInfoVolatile(secret_key, initial);
jclass convoClass = env->FindClass("network/loki/messenger/libsession_util/ConversationVolatileConfig");
jmethodID constructor = env->GetMethodID(convoClass, "<init>", "(J)V");
jobject newConfig = env->NewObject(convoClass, constructor, reinterpret_cast<jlong>(convo_info_volatile));
return newConfig;
}
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_sizeOneToOnes(JNIEnv *env,
jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto conversations = ptrToConvoInfo(env, thiz);
return conversations->size_1to1();
}
#pragma clang diagnostic pop
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_eraseAll(JNIEnv *env,
jobject thiz,
jobject predicate) {
std::lock_guard lock{util::util_mutex_};
auto conversations = ptrToConvoInfo(env, thiz);
jclass predicate_class = env->FindClass("kotlin/jvm/functions/Function1");
jmethodID predicate_call = env->GetMethodID(predicate_class, "invoke", "(Ljava/lang/Object;)Ljava/lang/Object;");
jclass bool_class = env->FindClass("java/lang/Boolean");
jmethodID bool_get = env->GetMethodID(bool_class, "booleanValue", "()Z");
int removed = 0;
auto to_erase = std::vector<session::config::convo::any>();
for (auto it = conversations->begin(); it != conversations->end(); ++it) {
auto result = env->CallObjectMethod(predicate, predicate_call, serialize_any(env, *it));
bool bool_result = env->CallBooleanMethod(result, bool_get);
if (bool_result) {
to_erase.push_back(*it);
}
}
for (auto & entry : to_erase) {
if (conversations->erase(entry)) {
removed++;
}
}
return removed;
}
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_size(JNIEnv *env,
jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto config = ptrToConvoInfo(env, thiz);
return (jint)config->size();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_empty(JNIEnv *env,
jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto config = ptrToConvoInfo(env, thiz);
return config->empty();
}
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_set(JNIEnv *env,
jobject thiz,
jobject to_store) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
jclass one_to_one = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$OneToOne");
jclass open_group = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$Community");
jclass legacy_closed_group = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$LegacyGroup");
jclass to_store_class = env->GetObjectClass(to_store);
if (env->IsSameObject(to_store_class, one_to_one)) {
// store as 1to1
convos->set(deserialize_one_to_one(env, to_store, convos));
} else if (env->IsSameObject(to_store_class,open_group)) {
// store as open_group
convos->set(deserialize_community(env, to_store, convos));
} else if (env->IsSameObject(to_store_class,legacy_closed_group)) {
// store as legacy_closed_group
convos->set(deserialize_legacy_closed_group(env, to_store, convos));
}
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getOneToOne(JNIEnv *env,
jobject thiz,
jstring pub_key_hex) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
auto param = env->GetStringUTFChars(pub_key_hex, nullptr);
auto internal = convos->get_1to1(param);
env->ReleaseStringUTFChars(pub_key_hex, param);
if (internal) {
return serialize_one_to_one(env, *internal);
}
return nullptr;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getOrConstructOneToOne(
JNIEnv *env, jobject thiz, jstring pub_key_hex) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
auto param = env->GetStringUTFChars(pub_key_hex, nullptr);
auto internal = convos->get_or_construct_1to1(param);
env->ReleaseStringUTFChars(pub_key_hex, param);
return serialize_one_to_one(env, internal);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_eraseOneToOne(JNIEnv *env,
jobject thiz,
jstring pub_key_hex) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
auto param = env->GetStringUTFChars(pub_key_hex, nullptr);
auto result = convos->erase_1to1(param);
env->ReleaseStringUTFChars(pub_key_hex, param);
return result;
}
extern "C"
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) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
auto base_url_chars = env->GetStringUTFChars(base_url, nullptr);
auto room_chars = env->GetStringUTFChars(room, nullptr);
auto open = convos->get_community(base_url_chars, room_chars);
if (open) {
auto serialized = serialize_open_group(env, *open);
return serialized;
}
return nullptr;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getOrConstructCommunity__Ljava_lang_String_2Ljava_lang_String_2_3B(
JNIEnv *env, jobject thiz, jstring base_url, jstring room, jbyteArray pub_key) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
auto base_url_chars = env->GetStringUTFChars(base_url, nullptr);
auto room_chars = env->GetStringUTFChars(room, nullptr);
auto pub_key_ustring = util::ustring_from_bytes(env, pub_key);
auto open = convos->get_or_construct_community(base_url_chars, room_chars, pub_key_ustring);
auto serialized = serialize_open_group(env, open);
return serialized;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getOrConstructCommunity__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2(
JNIEnv *env, jobject thiz, jstring base_url, jstring room, jstring pub_key_hex) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
auto base_url_chars = env->GetStringUTFChars(base_url, nullptr);
auto room_chars = env->GetStringUTFChars(room, nullptr);
auto hex_chars = env->GetStringUTFChars(pub_key_hex, nullptr);
auto open = convos->get_or_construct_community(base_url_chars, room_chars, hex_chars);
env->ReleaseStringUTFChars(base_url, base_url_chars);
env->ReleaseStringUTFChars(room, room_chars);
env->ReleaseStringUTFChars(pub_key_hex, hex_chars);
auto serialized = serialize_open_group(env, open);
return serialized;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_eraseCommunity__Lnetwork_loki_messenger_libsession_1util_util_Conversation_Community_2(JNIEnv *env,
jobject thiz,
jobject open_group) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
auto deserialized = deserialize_community(env, open_group, convos);
return convos->erase(deserialized);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_eraseCommunity__Ljava_lang_String_2Ljava_lang_String_2(
JNIEnv *env, jobject thiz, jstring base_url, jstring room) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
auto base_url_chars = env->GetStringUTFChars(base_url, nullptr);
auto room_chars = env->GetStringUTFChars(room, nullptr);
auto result = convos->erase_community(base_url_chars, room_chars);
env->ReleaseStringUTFChars(base_url, base_url_chars);
env->ReleaseStringUTFChars(room, room_chars);
return result;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getLegacyClosedGroup(
JNIEnv *env, jobject thiz, jstring group_id) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
auto id_chars = env->GetStringUTFChars(group_id, nullptr);
auto lgc = convos->get_legacy_group(id_chars);
env->ReleaseStringUTFChars(group_id, id_chars);
if (lgc) {
auto serialized = serialize_legacy_group(env, *lgc);
return serialized;
}
return nullptr;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_getOrConstructLegacyGroup(
JNIEnv *env, jobject thiz, jstring group_id) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
auto id_chars = env->GetStringUTFChars(group_id, nullptr);
auto lgc = convos->get_or_construct_legacy_group(id_chars);
env->ReleaseStringUTFChars(group_id, id_chars);
return serialize_legacy_group(env, lgc);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_eraseLegacyClosedGroup(
JNIEnv *env, jobject thiz, jstring group_id) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
auto id_chars = env->GetStringUTFChars(group_id, nullptr);
auto result = convos->erase_legacy_group(id_chars);
env->ReleaseStringUTFChars(group_id, id_chars);
return result;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_erase(JNIEnv *env,
jobject thiz,
jobject conversation) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
auto deserialized = deserialize_any(env, conversation, convos);
if (!deserialized.has_value()) return false;
return convos->erase(*deserialized);
}
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_sizeCommunities(JNIEnv *env,
jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
return convos->size_communities();
}
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_sizeLegacyClosedGroups(
JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
return convos->size_legacy_groups();
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_all(JNIEnv *env,
jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
jclass stack = env->FindClass("java/util/Stack");
jmethodID init = env->GetMethodID(stack, "<init>", "()V");
jobject our_stack = env->NewObject(stack, init);
jmethodID push = env->GetMethodID(stack, "push", "(Ljava/lang/Object;)Ljava/lang/Object;");
for (const auto& convo : *convos) {
auto contact_obj = serialize_any(env, convo);
env->CallObjectMethod(our_stack, push, contact_obj);
}
return our_stack;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_allOneToOnes(JNIEnv *env,
jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
jclass stack = env->FindClass("java/util/Stack");
jmethodID init = env->GetMethodID(stack, "<init>", "()V");
jobject our_stack = env->NewObject(stack, init);
jmethodID push = env->GetMethodID(stack, "push", "(Ljava/lang/Object;)Ljava/lang/Object;");
for (auto contact = convos->begin_1to1(); contact != convos->end(); ++contact)
env->CallObjectMethod(our_stack, push, serialize_one_to_one(env, *contact));
return our_stack;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_allCommunities(JNIEnv *env,
jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
jclass stack = env->FindClass("java/util/Stack");
jmethodID init = env->GetMethodID(stack, "<init>", "()V");
jobject our_stack = env->NewObject(stack, init);
jmethodID push = env->GetMethodID(stack, "push", "(Ljava/lang/Object;)Ljava/lang/Object;");
for (auto contact = convos->begin_communities(); contact != convos->end(); ++contact)
env->CallObjectMethod(our_stack, push, serialize_open_group(env, *contact));
return our_stack;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_allLegacyClosedGroups(
JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto convos = ptrToConvoInfo(env, thiz);
jclass stack = env->FindClass("java/util/Stack");
jmethodID init = env->GetMethodID(stack, "<init>", "()V");
jobject our_stack = env->NewObject(stack, init);
jmethodID push = env->GetMethodID(stack, "push", "(Ljava/lang/Object;)Ljava/lang/Object;");
for (auto contact = convos->begin_legacy_groups(); contact != convos->end(); ++contact)
env->CallObjectMethod(our_stack, push, serialize_legacy_group(env, *contact));
return our_stack;
}

View File

@@ -0,0 +1,122 @@
#ifndef SESSION_ANDROID_CONVERSATION_H
#define SESSION_ANDROID_CONVERSATION_H
#include <jni.h>
#include "util.h"
#include "session/config/convo_info_volatile.hpp"
inline session::config::ConvoInfoVolatile *ptrToConvoInfo(JNIEnv *env, jobject obj) {
jclass contactsClass = env->FindClass("network/loki/messenger/libsession_util/ConversationVolatileConfig");
jfieldID pointerField = env->GetFieldID(contactsClass, "pointer", "J");
return (session::config::ConvoInfoVolatile *) env->GetLongField(obj, pointerField);
}
inline jobject serialize_one_to_one(JNIEnv *env, session::config::convo::one_to_one one_to_one) {
jclass clazz = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$OneToOne");
jmethodID constructor = env->GetMethodID(clazz, "<init>", "(Ljava/lang/String;JZ)V");
auto session_id = env->NewStringUTF(one_to_one.session_id.data());
auto last_read = one_to_one.last_read;
auto unread = one_to_one.unread;
jobject serialized = env->NewObject(clazz, constructor, session_id, last_read, unread);
return serialized;
}
inline jobject serialize_open_group(JNIEnv *env, session::config::convo::community community) {
jclass clazz = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$Community");
auto base_community = util::serialize_base_community(env, community);
jmethodID constructor = env->GetMethodID(clazz, "<init>",
"(Lnetwork/loki/messenger/libsession_util/util/BaseCommunityInfo;JZ)V");
auto last_read = community.last_read;
auto unread = community.unread;
jobject serialized = env->NewObject(clazz, constructor, base_community, last_read, unread);
return serialized;
}
inline jobject serialize_legacy_group(JNIEnv *env, session::config::convo::legacy_group group) {
jclass clazz = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$LegacyGroup");
jmethodID constructor = env->GetMethodID(clazz, "<init>", "(Ljava/lang/String;JZ)V");
auto group_id = env->NewStringUTF(group.id.data());
auto last_read = group.last_read;
auto unread = group.unread;
jobject serialized = env->NewObject(clazz, constructor, group_id, last_read, unread);
return serialized;
}
inline jobject serialize_any(JNIEnv *env, session::config::convo::any any) {
if (auto* dm = std::get_if<session::config::convo::one_to_one>(&any)) {
return serialize_one_to_one(env, *dm);
} else if (auto* og = std::get_if<session::config::convo::community>(&any)) {
return serialize_open_group(env, *og);
} else if (auto* lgc = std::get_if<session::config::convo::legacy_group>(&any)) {
return serialize_legacy_group(env, *lgc);
}
return nullptr;
}
inline session::config::convo::one_to_one deserialize_one_to_one(JNIEnv *env, jobject info, session::config::ConvoInfoVolatile *conf) {
auto clazz = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$OneToOne");
auto id_getter = env->GetFieldID(clazz, "sessionId", "Ljava/lang/String;");
auto last_read_getter = env->GetFieldID(clazz, "lastRead", "J");
auto unread_getter = env->GetFieldID(clazz, "unread", "Z");
jstring id = static_cast<jstring>(env->GetObjectField(info, id_getter));
auto id_chars = env->GetStringUTFChars(id, nullptr);
std::string id_string = std::string{id_chars};
auto deserialized = conf->get_or_construct_1to1(id_string);
deserialized.last_read = env->GetLongField(info, last_read_getter);
deserialized.unread = env->GetBooleanField(info, unread_getter);
env->ReleaseStringUTFChars(id, id_chars);
return deserialized;
}
inline session::config::convo::community deserialize_community(JNIEnv *env, jobject info, session::config::ConvoInfoVolatile *conf) {
auto clazz = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$Community");
auto base_community_getter = env->GetFieldID(clazz, "baseCommunityInfo", "Lnetwork/loki/messenger/libsession_util/util/BaseCommunityInfo;");
auto last_read_getter = env->GetFieldID(clazz, "lastRead", "J");
auto unread_getter = env->GetFieldID(clazz, "unread", "Z");
auto base_community_info = env->GetObjectField(info, base_community_getter);
auto base_community_deserialized = util::deserialize_base_community(env, base_community_info);
auto deserialized = conf->get_or_construct_community(
base_community_deserialized.base_url(),
base_community_deserialized.room(),
base_community_deserialized.pubkey()
);
deserialized.last_read = env->GetLongField(info, last_read_getter);
deserialized.unread = env->GetBooleanField(info, unread_getter);
return deserialized;
}
inline session::config::convo::legacy_group deserialize_legacy_closed_group(JNIEnv *env, jobject info, session::config::ConvoInfoVolatile *conf) {
auto clazz = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$LegacyGroup");
auto group_id_getter = env->GetFieldID(clazz, "groupId", "Ljava/lang/String;");
auto last_read_getter = env->GetFieldID(clazz, "lastRead", "J");
auto unread_getter = env->GetFieldID(clazz, "unread", "Z");
auto group_id = static_cast<jstring>(env->GetObjectField(info, group_id_getter));
auto group_id_bytes = env->GetStringUTFChars(group_id, nullptr);
auto group_id_string = std::string{group_id_bytes};
auto deserialized = conf->get_or_construct_legacy_group(group_id_string);
deserialized.last_read = env->GetLongField(info, last_read_getter);
deserialized.unread = env->GetBooleanField(info, unread_getter);
env->ReleaseStringUTFChars(group_id, group_id_bytes);
return deserialized;
}
inline std::optional<session::config::convo::any> deserialize_any(JNIEnv *env, jobject convo, session::config::ConvoInfoVolatile *conf) {
auto oto_class = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$OneToOne");
auto og_class = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$Community");
auto lgc_class = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$LegacyGroup");
auto object_class = env->GetObjectClass(convo);
if (env->IsSameObject(object_class, oto_class)) {
return session::config::convo::any{deserialize_one_to_one(env, convo, conf)};
} else if (env->IsSameObject(object_class, og_class)) {
return session::config::convo::any{deserialize_community(env, convo, conf)};
} else if (env->IsSameObject(object_class, lgc_class)) {
return session::config::convo::any{deserialize_legacy_closed_group(env, convo, conf)};
}
return std::nullopt;
}
#endif //SESSION_ANDROID_CONVERSATION_H

View File

@@ -0,0 +1,274 @@
#pragma clang diagnostic push
#pragma ide diagnostic ignored "bugprone-reserved-identifier"
#include "user_groups.h"
#pragma clang diagnostic push
#pragma ide diagnostic ignored "bugprone-reserved-identifier"
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_00024Companion_newInstance___3B(
JNIEnv *env, jobject thiz, jbyteArray ed25519_secret_key) {
std::lock_guard lock{util::util_mutex_};
auto secret_key = util::ustring_from_bytes(env, ed25519_secret_key);
auto* user_groups = new session::config::UserGroups(secret_key, std::nullopt);
jclass contactsClass = env->FindClass("network/loki/messenger/libsession_util/UserGroupsConfig");
jmethodID constructor = env->GetMethodID(contactsClass, "<init>", "(J)V");
jobject newConfig = env->NewObject(contactsClass, constructor, reinterpret_cast<jlong>(user_groups));
return newConfig;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_00024Companion_newInstance___3B_3B(
JNIEnv *env, jobject thiz, jbyteArray ed25519_secret_key, jbyteArray initial_dump) {
std::lock_guard lock{util::util_mutex_};
auto secret_key = util::ustring_from_bytes(env, ed25519_secret_key);
auto initial = util::ustring_from_bytes(env, initial_dump);
auto* user_groups = new session::config::UserGroups(secret_key, initial);
jclass contactsClass = env->FindClass("network/loki/messenger/libsession_util/UserGroupsConfig");
jmethodID constructor = env->GetMethodID(contactsClass, "<init>", "(J)V");
jobject newConfig = env->NewObject(contactsClass, constructor, reinterpret_cast<jlong>(user_groups));
return newConfig;
}
#pragma clang diagnostic pop
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_util_GroupInfo_00024LegacyGroupInfo_00024Companion_NAME_1MAX_1LENGTH(
JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
return session::config::legacy_group_info::NAME_MAX_LENGTH;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getCommunityInfo(JNIEnv *env,
jobject thiz,
jstring base_url,
jstring room) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
auto base_url_bytes = env->GetStringUTFChars(base_url, nullptr);
auto room_bytes = env->GetStringUTFChars(room, nullptr);
auto community = conf->get_community(base_url_bytes, room_bytes);
jobject community_info = nullptr;
if (community) {
community_info = serialize_community_info(env, *community);
}
env->ReleaseStringUTFChars(base_url, base_url_bytes);
env->ReleaseStringUTFChars(room, room_bytes);
return community_info;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getLegacyGroupInfo(JNIEnv *env,
jobject thiz,
jstring session_id) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
auto id_bytes = env->GetStringUTFChars(session_id, nullptr);
auto legacy_group = conf->get_legacy_group(id_bytes);
jobject return_group = nullptr;
if (legacy_group) {
return_group = serialize_legacy_group_info(env, *legacy_group);
}
env->ReleaseStringUTFChars(session_id, id_bytes);
return return_group;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getOrConstructCommunityInfo(
JNIEnv *env, jobject thiz, jstring base_url, jstring room, jstring pub_key_hex) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
auto base_url_bytes = env->GetStringUTFChars(base_url, nullptr);
auto room_bytes = env->GetStringUTFChars(room, nullptr);
auto pub_hex_bytes = env->GetStringUTFChars(pub_key_hex, nullptr);
auto group = conf->get_or_construct_community(base_url_bytes, room_bytes, pub_hex_bytes);
env->ReleaseStringUTFChars(base_url, base_url_bytes);
env->ReleaseStringUTFChars(room, room_bytes);
env->ReleaseStringUTFChars(pub_key_hex, pub_hex_bytes);
return serialize_community_info(env, group);
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getOrConstructLegacyGroupInfo(
JNIEnv *env, jobject thiz, jstring session_id) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
auto id_bytes = env->GetStringUTFChars(session_id, nullptr);
auto group = conf->get_or_construct_legacy_group(id_bytes);
env->ReleaseStringUTFChars(session_id, id_bytes);
return serialize_legacy_group_info(env, group);
}
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_set__Lnetwork_loki_messenger_libsession_1util_util_GroupInfo_2(
JNIEnv *env, jobject thiz, jobject group_info) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
auto community_info = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$CommunityGroupInfo");
auto legacy_info = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$LegacyGroupInfo");
auto object_class = env->GetObjectClass(group_info);
if (env->IsSameObject(community_info, object_class)) {
auto deserialized = deserialize_community_info(env, group_info, conf);
conf->set(deserialized);
} else if (env->IsSameObject(legacy_info, object_class)) {
auto deserialized = deserialize_legacy_group_info(env, group_info, conf);
conf->set(deserialized);
}
}
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_erase__Lnetwork_loki_messenger_libsession_1util_util_GroupInfo_2(
JNIEnv *env, jobject thiz, jobject group_info) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
auto communityInfo = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$CommunityGroupInfo");
auto legacyInfo = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$LegacyGroupInfo");
auto group_object = env->GetObjectClass(group_info);
if (env->IsSameObject(group_object, communityInfo)) {
auto deserialized = deserialize_community_info(env, group_info, conf);
conf->erase(deserialized);
} else if (env->IsSameObject(group_object, legacyInfo)) {
auto deserialized = deserialize_legacy_group_info(env, group_info, conf);
conf->erase(deserialized);
}
}
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_sizeCommunityInfo(JNIEnv *env,
jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
return conf->size_communities();
}
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_sizeLegacyGroupInfo(JNIEnv *env,
jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
return conf->size_legacy_groups();
}
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_size(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToConvoInfo(env, thiz);
return conf->size();
}
inline jobject iterator_as_java_stack(JNIEnv *env, const session::config::UserGroups::iterator& begin, const session::config::UserGroups::iterator& end) {
jclass stack = env->FindClass("java/util/Stack");
jmethodID init = env->GetMethodID(stack, "<init>", "()V");
jobject our_stack = env->NewObject(stack, init);
jmethodID push = env->GetMethodID(stack, "push", "(Ljava/lang/Object;)Ljava/lang/Object;");
for (auto it = begin; it != end;) {
// do something with it
auto item = *it;
jobject serialized = nullptr;
if (auto* lgc = std::get_if<session::config::legacy_group_info>(&item)) {
serialized = serialize_legacy_group_info(env, *lgc);
} else if (auto* community = std::get_if<session::config::community_info>(&item)) {
serialized = serialize_community_info(env, *community);
}
if (serialized != nullptr) {
env->CallObjectMethod(our_stack, push, serialized);
}
it++;
}
return our_stack;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_all(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
jobject all_stack = iterator_as_java_stack(env, conf->begin(), conf->end());
return all_stack;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_allCommunityInfo(JNIEnv *env,
jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
jobject community_stack = iterator_as_java_stack(env, conf->begin_communities(), conf->end());
return community_stack;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_allLegacyGroupInfo(JNIEnv *env,
jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
jobject legacy_stack = iterator_as_java_stack(env, conf->begin_legacy_groups(), conf->end());
return legacy_stack;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_eraseCommunity__Lnetwork_loki_messenger_libsession_1util_util_BaseCommunityInfo_2(JNIEnv *env,
jobject thiz,
jobject base_community_info) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
auto base_community = util::deserialize_base_community(env, base_community_info);
return conf->erase_community(base_community.base_url(),base_community.room());
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_eraseCommunity__Ljava_lang_String_2Ljava_lang_String_2(
JNIEnv *env, jobject thiz, jstring server, jstring room) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
auto server_bytes = env->GetStringUTFChars(server, nullptr);
auto room_bytes = env->GetStringUTFChars(room, nullptr);
auto community = conf->get_community(server_bytes, room_bytes);
bool deleted = false;
if (community) {
deleted = conf->erase(*community);
}
env->ReleaseStringUTFChars(server, server_bytes);
env->ReleaseStringUTFChars(room, room_bytes);
return deleted;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_eraseLegacyGroup(JNIEnv *env,
jobject thiz,
jstring session_id) {
std::lock_guard lock{util::util_mutex_};
auto conf = ptrToUserGroups(env, thiz);
auto session_id_bytes = env->GetStringUTFChars(session_id, nullptr);
bool return_bool = conf->erase_legacy_group(session_id_bytes);
env->ReleaseStringUTFChars(session_id, session_id_bytes);
return return_bool;
}

View File

@@ -0,0 +1,139 @@
#ifndef SESSION_ANDROID_USER_GROUPS_H
#define SESSION_ANDROID_USER_GROUPS_H
#include "jni.h"
#include "util.h"
#include "conversation.h"
#include "session/config/user_groups.hpp"
inline session::config::UserGroups* ptrToUserGroups(JNIEnv *env, jobject obj) {
jclass configClass = env->FindClass("network/loki/messenger/libsession_util/UserGroupsConfig");
jfieldID pointerField = env->GetFieldID(configClass, "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) {
jclass map_class = env->FindClass("java/util/Map");
jclass map_entry_class = env->FindClass("java/util/Map$Entry");
jclass set_class = env->FindClass("java/util/Set");
jclass iterator_class = env->FindClass("java/util/Iterator");
jclass boxed_bool = env->FindClass("java/lang/Boolean");
jmethodID get_entry_set = env->GetMethodID(map_class, "entrySet", "()Ljava/util/Set;");
jmethodID get_at = env->GetMethodID(set_class, "iterator", "()Ljava/util/Iterator;");
jmethodID has_next = env->GetMethodID(iterator_class, "hasNext", "()Z");
jmethodID next = env->GetMethodID(iterator_class, "next", "()Ljava/lang/Object;");
jmethodID get_key = env->GetMethodID(map_entry_class, "getKey", "()Ljava/lang/Object;");
jmethodID get_value = env->GetMethodID(map_entry_class, "getValue", "()Ljava/lang/Object;");
jmethodID get_bool_value = env->GetMethodID(boxed_bool, "booleanValue", "()Z");
jobject entry_set = env->CallObjectMethod(members_map, get_entry_set);
jobject iterator = env->CallObjectMethod(entry_set, get_at);
while (env->CallBooleanMethod(iterator, has_next)) {
jobject entry = env->CallObjectMethod(iterator, next);
jstring key = static_cast<jstring>(env->CallObjectMethod(entry, get_key));
jobject boxed = env->CallObjectMethod(entry, get_value);
bool is_admin = env->CallBooleanMethod(boxed, get_bool_value);
auto member_string = env->GetStringUTFChars(key, nullptr);
to_append_group.insert(member_string, is_admin);
env->ReleaseStringUTFChars(key, member_string);
}
}
inline session::config::legacy_group_info deserialize_legacy_group_info(JNIEnv *env, jobject info, session::config::UserGroups* conf) {
auto clazz = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$LegacyGroupInfo");
auto id_field = env->GetFieldID(clazz, "sessionId", "Ljava/lang/String;");
auto name_field = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
auto members_field = env->GetFieldID(clazz, "members", "Ljava/util/Map;");
auto enc_pub_key_field = env->GetFieldID(clazz, "encPubKey", "[B");
auto enc_sec_key_field = env->GetFieldID(clazz, "encSecKey", "[B");
auto priority_field = env->GetFieldID(clazz, "priority", "I");
auto disappearing_timer_field = env->GetFieldID(clazz, "disappearingTimer", "J");
auto joined_at_field = env->GetFieldID(clazz, "joinedAt", "J");
jstring id = static_cast<jstring>(env->GetObjectField(info, id_field));
jstring name = static_cast<jstring>(env->GetObjectField(info, name_field));
jobject members_map = env->GetObjectField(info, members_field);
jbyteArray enc_pub_key = static_cast<jbyteArray>(env->GetObjectField(info, enc_pub_key_field));
jbyteArray enc_sec_key = static_cast<jbyteArray>(env->GetObjectField(info, enc_sec_key_field));
int priority = env->GetIntField(info, priority_field);
long joined_at = env->GetLongField(info, joined_at_field);
auto id_bytes = env->GetStringUTFChars(id, nullptr);
auto name_bytes = env->GetStringUTFChars(name, nullptr);
auto enc_pub_key_bytes = util::ustring_from_bytes(env, enc_pub_key);
auto enc_sec_key_bytes = util::ustring_from_bytes(env, enc_sec_key);
auto info_deserialized = conf->get_or_construct_legacy_group(id_bytes);
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, info_deserialized);
info_deserialized.name = name_bytes;
info_deserialized.enc_pubkey = enc_pub_key_bytes;
info_deserialized.enc_seckey = enc_sec_key_bytes;
info_deserialized.priority = priority;
info_deserialized.disappearing_timer = std::chrono::seconds(env->GetLongField(info, disappearing_timer_field));
info_deserialized.joined_at = joined_at;
env->ReleaseStringUTFChars(id, id_bytes);
env->ReleaseStringUTFChars(name, name_bytes);
return info_deserialized;
}
inline session::config::community_info deserialize_community_info(JNIEnv *env, jobject info, session::config::UserGroups* conf) {
auto clazz = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$CommunityGroupInfo");
auto base_info = env->GetFieldID(clazz, "community", "Lnetwork/loki/messenger/libsession_util/util/BaseCommunityInfo;");
auto priority = env->GetFieldID(clazz, "priority", "I");
jobject base_community_info = env->GetObjectField(info, base_info);
auto deserialized_base_info = util::deserialize_base_community(env, base_community_info);
int deserialized_priority = env->GetIntField(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<std::string, bool> members_map) {
jclass map_class = env->FindClass("java/util/HashMap");
jclass boxed_bool = env->FindClass("java/lang/Boolean");
jmethodID map_constructor = env->GetMethodID(map_class, "<init>", "()V");
jmethodID insert = env->GetMethodID(map_class, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
jmethodID new_bool = env->GetMethodID(boxed_bool, "<init>", "(Z)V");
jobject new_map = env->NewObject(map_class, map_constructor);
for (auto it = members_map.begin(); it != members_map.end(); it++) {
auto session_id = env->NewStringUTF(it->first.data());
bool is_admin = it->second;
auto jbool = env->NewObject(boxed_bool, new_bool, is_admin);
env->CallObjectMethod(new_map, insert, session_id, jbool);
}
return new_map;
}
inline jobject serialize_legacy_group_info(JNIEnv *env, session::config::legacy_group_info info) {
jstring session_id = env->NewStringUTF(info.session_id.data());
jstring name = env->NewStringUTF(info.name.data());
jobject members = serialize_members(env, info.members());
jbyteArray enc_pubkey = util::bytes_from_ustring(env, info.enc_pubkey);
jbyteArray enc_seckey = util::bytes_from_ustring(env, info.enc_seckey);
int priority = info.priority;
long joined_at = info.joined_at;
jclass legacy_group_class = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$LegacyGroupInfo");
jmethodID constructor = env->GetMethodID(legacy_group_class, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;[B[BIJJ)V");
jobject serialized = env->NewObject(legacy_group_class, constructor, session_id, name, members, enc_pubkey, enc_seckey, priority, (jlong) info.disappearing_timer.count(), joined_at);
return serialized;
}
inline jobject serialize_community_info(JNIEnv *env, session::config::community_info info) {
auto priority = info.priority;
auto serialized_info = util::serialize_base_community(env, info);
auto clazz = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$CommunityGroupInfo");
jmethodID constructor = env->GetMethodID(clazz, "<init>", "(Lnetwork/loki/messenger/libsession_util/util/BaseCommunityInfo;I)V");
jobject serialized = env->NewObject(clazz, constructor, serialized_info, priority);
return serialized;
}
#endif //SESSION_ANDROID_USER_GROUPS_H

View File

@@ -0,0 +1,152 @@
#include "user_profile.h"
#include "util.h"
extern "C" {
#pragma clang diagnostic push
#pragma ide diagnostic ignored "bugprone-reserved-identifier"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_00024Companion_newInstance___3B_3B(
JNIEnv *env, jobject thiz, jbyteArray ed25519_secret_key, jbyteArray initial_dump) {
std::lock_guard lock{util::util_mutex_};
auto secret_key = util::ustring_from_bytes(env, ed25519_secret_key);
auto initial = util::ustring_from_bytes(env, initial_dump);
auto* profile = new session::config::UserProfile(secret_key, std::optional(initial));
jclass userClass = env->FindClass("network/loki/messenger/libsession_util/UserProfile");
jmethodID constructor = env->GetMethodID(userClass, "<init>", "(J)V");
jobject newConfig = env->NewObject(userClass, constructor, reinterpret_cast<jlong>(profile));
return newConfig;
}
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_00024Companion_newInstance___3B(
JNIEnv* env,
jobject,
jbyteArray secretKey) {
std::lock_guard lock{util::util_mutex_};
auto* profile = new session::config::UserProfile(util::ustring_from_bytes(env, secretKey), std::nullopt);
jclass userClass = env->FindClass("network/loki/messenger/libsession_util/UserProfile");
jmethodID constructor = env->GetMethodID(userClass, "<init>", "(J)V");
jobject newConfig = env->NewObject(userClass, constructor, reinterpret_cast<jlong>(profile));
return newConfig;
}
#pragma clang diagnostic pop
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_setName(
JNIEnv* env,
jobject thiz,
jstring newName) {
std::lock_guard lock{util::util_mutex_};
auto profile = ptrToProfile(env, thiz);
auto name_chars = env->GetStringUTFChars(newName, nullptr);
profile->set_name(name_chars);
env->ReleaseStringUTFChars(newName, name_chars);
}
JNIEXPORT jstring JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_getName(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto profile = ptrToProfile(env, thiz);
auto name = profile->get_name();
if (name == std::nullopt) return nullptr;
jstring returnString = env->NewStringUTF(name->data());
return returnString;
}
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_getPic(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto profile = ptrToProfile(env, thiz);
auto pic = profile->get_profile_pic();
jobject returnObject = util::serialize_user_pic(env, pic);
return returnObject;
}
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_setPic(JNIEnv *env, jobject thiz,
jobject user_pic) {
std::lock_guard lock{util::util_mutex_};
auto profile = ptrToProfile(env, thiz);
auto pic = util::deserialize_user_pic(env, user_pic);
auto url = env->GetStringUTFChars(pic.first, nullptr);
auto key = util::ustring_from_bytes(env, pic.second);
profile->set_profile_pic(url, key);
env->ReleaseStringUTFChars(pic.first, url);
}
}
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_setNtsPriority(JNIEnv *env, jobject thiz,
jint priority) {
std::lock_guard lock{util::util_mutex_};
auto profile = ptrToProfile(env, thiz);
profile->set_nts_priority(priority);
}
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_getNtsPriority(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto profile = ptrToProfile(env, thiz);
return profile->get_nts_priority();
}
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_setNtsExpiry(JNIEnv *env, jobject thiz,
jobject expiry_mode) {
std::lock_guard lock{util::util_mutex_};
auto profile = ptrToProfile(env, thiz);
auto expiry = util::deserialize_expiry(env, expiry_mode);
profile->set_nts_expiry(std::chrono::seconds (expiry.second));
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_getNtsExpiry(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
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::after_send, std::chrono::seconds(*nts_expiry));
return expiry;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_getCommunityMessageRequests(
JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto profile = ptrToProfile(env, thiz);
auto blinded_msg_requests = profile->get_blinded_msgreqs();
if (blinded_msg_requests.has_value()) {
return *blinded_msg_requests;
}
return true;
}
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_setCommunityMessageRequests(
JNIEnv *env, jobject thiz, jboolean blocks) {
std::lock_guard lock{util::util_mutex_};
auto profile = ptrToProfile(env, thiz);
profile->set_blinded_msgreqs(std::optional{(bool)blocks});
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_isBlockCommunityMessageRequestsSet(
JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto profile = ptrToProfile(env, thiz);
return profile->get_blinded_msgreqs().has_value();
}

View File

@@ -0,0 +1,14 @@
#ifndef SESSION_ANDROID_USER_PROFILE_H
#define SESSION_ANDROID_USER_PROFILE_H
#include "session/config/user_profile.hpp"
#include <jni.h>
#include <string>
inline session::config::UserProfile* ptrToProfile(JNIEnv* env, jobject obj) {
jclass configClass = env->FindClass("network/loki/messenger/libsession_util/UserProfile");
jfieldID pointerField = env->GetFieldID(configClass, "pointer", "J");
return (session::config::UserProfile*) env->GetLongField(obj, pointerField);
}
#endif

View File

@@ -0,0 +1,178 @@
#include "util.h"
#include <string>
#include <sodium/crypto_sign.h>
namespace util {
std::mutex util_mutex_ = std::mutex();
jbyteArray bytes_from_ustring(JNIEnv* env, session::ustring_view from_str) {
size_t length = from_str.length();
auto jlength = (jsize)length;
jbyteArray new_array = env->NewByteArray(jlength);
env->SetByteArrayRegion(new_array, 0, jlength, (jbyte*)from_str.data());
return new_array;
}
session::ustring ustring_from_bytes(JNIEnv* env, jbyteArray byteArray) {
size_t len = env->GetArrayLength(byteArray);
auto bytes = env->GetByteArrayElements(byteArray, nullptr);
session::ustring st{reinterpret_cast<const unsigned char *>(bytes), len};
env->ReleaseByteArrayElements(byteArray, bytes, 0);
return st;
}
jobject serialize_user_pic(JNIEnv *env, session::config::profile_pic pic) {
jclass returnObjectClass = env->FindClass("network/loki/messenger/libsession_util/util/UserPic");
jmethodID constructor = env->GetMethodID(returnObjectClass, "<init>", "(Ljava/lang/String;[B)V");
jstring url = env->NewStringUTF(pic.url.data());
jbyteArray byteArray = util::bytes_from_ustring(env, pic.key);
return env->NewObject(returnObjectClass, constructor, url, byteArray);
}
std::pair<jstring, jbyteArray> deserialize_user_pic(JNIEnv *env, jobject user_pic) {
jclass userPicClass = env->FindClass("network/loki/messenger/libsession_util/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);
return {pic, key};
}
jobject serialize_base_community(JNIEnv *env, const session::config::community& community) {
jclass base_community_clazz = env->FindClass("network/loki/messenger/libsession_util/util/BaseCommunityInfo");
jmethodID base_community_constructor = env->GetMethodID(base_community_clazz, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
auto base_url = env->NewStringUTF(community.base_url().data());
auto room = env->NewStringUTF(community.room().data());
auto pubkey_jstring = env->NewStringUTF(community.pubkey_hex().data());
jobject ret = env->NewObject(base_community_clazz, base_community_constructor, base_url, room, pubkey_jstring);
return ret;
}
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;");
auto base_url = (jstring)env->GetObjectField(base_community,base_url_field);
auto room = (jstring)env->GetObjectField(base_community, room_field);
auto pub_key_hex = (jstring)env->GetObjectField(base_community, pubkey_hex_field);
auto base_url_chars = env->GetStringUTFChars(base_url, nullptr);
auto room_chars = env->GetStringUTFChars(room, nullptr);
auto pub_key_hex_chars = env->GetStringUTFChars(pub_key_hex, nullptr);
auto community = session::config::community(base_url_chars, room_chars, pub_key_hex_chars);
env->ReleaseStringUTFChars(base_url, base_url_chars);
env->ReleaseStringUTFChars(room, room_chars);
env->ReleaseStringUTFChars(pub_key_hex, pub_key_hex_chars);
return community;
}
jobject serialize_expiry(JNIEnv *env, const session::config::expiration_mode& mode, const std::chrono::seconds& time_seconds) {
jclass none = env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$NONE");
jfieldID none_instance = env->GetStaticFieldID(none, "INSTANCE", "Lnetwork/loki/messenger/libsession_util/util/ExpiryMode$NONE;");
jclass after_send = env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$AfterSend");
jmethodID send_init = env->GetMethodID(after_send, "<init>", "(J)V");
jclass after_read = env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$AfterRead");
jmethodID read_init = env->GetMethodID(after_read, "<init>", "(J)V");
if (mode == session::config::expiration_mode::none) {
return env->GetStaticObjectField(none, none_instance);
} else if (mode == session::config::expiration_mode::after_send) {
return env->NewObject(after_send, send_init, time_seconds.count());
} else if (mode == session::config::expiration_mode::after_read) {
return env->NewObject(after_read, read_init, time_seconds.count());
}
return nullptr;
}
std::pair<session::config::expiration_mode, long> deserialize_expiry(JNIEnv *env, jobject expiry_mode) {
jclass parent = env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode");
jclass after_read = env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$AfterRead");
jclass after_send = env->FindClass("network/loki/messenger/libsession_util/util/ExpiryMode$AfterSend");
jfieldID duration_seconds = env->GetFieldID(parent, "expirySeconds", "J");
jclass object_class = env->GetObjectClass(expiry_mode);
if (env->IsSameObject(object_class, after_read)) {
return std::pair(session::config::expiration_mode::after_read, env->GetLongField(expiry_mode, duration_seconds));
} else if (env->IsSameObject(object_class, after_send)) {
return std::pair(session::config::expiration_mode::after_send, env->GetLongField(expiry_mode, duration_seconds));
}
return std::pair(session::config::expiration_mode::none, 0);
}
jobject build_string_stack(JNIEnv* env, std::vector<std::string> to_add) {
jclass stack_class = env->FindClass("java/util/Stack");
jmethodID constructor = env->GetMethodID(stack_class,"<init>", "()V");
jmethodID add = env->GetMethodID(stack_class, "push", "(Ljava/lang/Object;)Ljava/lang/Object;");
jobject our_stack = env->NewObject(stack_class, constructor);
for (std::basic_string_view<char> string: to_add) {
env->CallObjectMethod(our_stack, add, env->NewStringUTF(string.data()));
}
return our_stack;
}
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_util_Sodium_ed25519KeyPair(JNIEnv *env, jobject thiz, jbyteArray seed) {
std::array<unsigned char, 32> ed_pk; // NOLINT(cppcoreguidelines-pro-type-member-init)
std::array<unsigned char, 64> ed_sk; // NOLINT(cppcoreguidelines-pro-type-member-init)
auto seed_bytes = util::ustring_from_bytes(env, seed);
crypto_sign_ed25519_seed_keypair(ed_pk.data(), ed_sk.data(), seed_bytes.data());
jclass kp_class = env->FindClass("network/loki/messenger/libsession_util/util/KeyPair");
jmethodID kp_constructor = env->GetMethodID(kp_class, "<init>", "([B[B)V");
jbyteArray pk_jarray = util::bytes_from_ustring(env, session::ustring_view {ed_pk.data(), ed_pk.size()});
jbyteArray sk_jarray = util::bytes_from_ustring(env, session::ustring_view {ed_sk.data(), ed_sk.size()});
jobject return_obj = env->NewObject(kp_class, kp_constructor, pk_jarray, sk_jarray);
return return_obj;
}
extern "C"
JNIEXPORT jbyteArray JNICALL
Java_network_loki_messenger_libsession_1util_util_Sodium_ed25519PkToCurve25519(JNIEnv *env,
jobject thiz,
jbyteArray pk) {
auto ed_pk = util::ustring_from_bytes(env, pk);
std::array<unsigned char, 32> curve_pk; // NOLINT(cppcoreguidelines-pro-type-member-init)
int success = crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed_pk.data());
if (success != 0) {
jclass exception = env->FindClass("java/lang/Exception");
env->ThrowNew(exception, "Invalid crypto_sign_ed25519_pk_to_curve25519 operation");
return nullptr;
}
jbyteArray curve_pk_jarray = util::bytes_from_ustring(env, session::ustring_view {curve_pk.data(), curve_pk.size()});
return curve_pk_jarray;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_util_BaseCommunityInfo_00024Companion_parseFullUrl(
JNIEnv *env, jobject thiz, jstring full_url) {
auto bytes = env->GetStringUTFChars(full_url, nullptr);
auto [base, room, pk] = session::config::community::parse_full_url(bytes);
env->ReleaseStringUTFChars(full_url, bytes);
jclass clazz = env->FindClass("kotlin/Triple");
jmethodID constructor = env->GetMethodID(clazz, "<init>", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V");
auto base_j = env->NewStringUTF(base.data());
auto room_j = env->NewStringUTF(room.data());
auto pk_jbytes = util::bytes_from_ustring(env, pk);
jobject triple = env->NewObject(clazz, constructor, base_j, room_j, pk_jbytes);
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 full_url = deserialized.full_url();
return env->NewStringUTF(full_url.data());
}

View File

@@ -0,0 +1,25 @@
#ifndef SESSION_ANDROID_UTIL_H
#define SESSION_ANDROID_UTIL_H
#include <jni.h>
#include <array>
#include <optional>
#include "session/types.hpp"
#include "session/config/profile_pic.hpp"
#include "session/config/user_groups.hpp"
#include "session/config/expiring.hpp"
namespace util {
extern std::mutex util_mutex_;
jbyteArray bytes_from_ustring(JNIEnv* env, session::ustring_view from_str);
session::ustring ustring_from_bytes(JNIEnv* env, jbyteArray byteArray);
jobject serialize_user_pic(JNIEnv *env, session::config::profile_pic pic);
std::pair<jstring, jbyteArray> 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);
std::pair<session::config::expiration_mode, long> deserialize_expiry(JNIEnv *env, jobject expiry_mode);
jobject build_string_stack(JNIEnv* env, std::vector<std::string> to_add);
}
#endif

View File

@@ -0,0 +1,207 @@
package network.loki.messenger.libsession_util
import network.loki.messenger.libsession_util.util.BaseCommunityInfo
import network.loki.messenger.libsession_util.util.ConfigPush
import network.loki.messenger.libsession_util.util.Contact
import network.loki.messenger.libsession_util.util.Conversation
import network.loki.messenger.libsession_util.util.ExpiryMode
import network.loki.messenger.libsession_util.util.GroupInfo
import network.loki.messenger.libsession_util.util.UserPic
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind
import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.Log
import java.util.Stack
sealed class ConfigBase(protected val /* yucky */ pointer: Long) {
companion object {
init {
System.loadLibrary("session_util")
}
external fun kindFor(configNamespace: Int): Class<ConfigBase>
fun ConfigBase.protoKindFor(): Kind = when (this) {
is UserProfile -> Kind.USER_PROFILE
is Contacts -> Kind.CONTACTS
is ConversationVolatileConfig -> Kind.CONVO_INFO_VOLATILE
is UserGroupsConfig -> Kind.GROUPS
}
// TODO: time in future to activate (hardcoded to 1st jan 2024 for testing, change before release)
private const val ACTIVATE_TIME = 1690761600000
fun isNewConfigEnabled(forced: Boolean, currentTime: Long) =
forced || currentTime >= ACTIVATE_TIME
const val PRIORITY_HIDDEN = -1
const val PRIORITY_VISIBLE = 0
const val PRIORITY_PINNED = 1
}
external fun dirty(): Boolean
external fun needsPush(): Boolean
external fun needsDump(): Boolean
external fun push(): ConfigPush
external fun dump(): ByteArray
external fun encryptionDomain(): String
external fun confirmPushed(seqNo: Long, newHash: String)
external fun merge(toMerge: Array<Pair<String,ByteArray>>): Stack<String>
external fun currentHashes(): List<String>
external fun configNamespace(): Int
// Singular merge
external fun merge(toMerge: Pair<String,ByteArray>): Stack<String>
external fun free()
}
class Contacts(pointer: Long) : ConfigBase(pointer) {
companion object {
init {
System.loadLibrary("session_util")
}
external fun newInstance(ed25519SecretKey: ByteArray): Contacts
external fun newInstance(ed25519SecretKey: ByteArray, initialDump: ByteArray): Contacts
}
external fun get(sessionId: String): Contact?
external fun getOrConstruct(sessionId: String): Contact
external fun all(): List<Contact>
external fun set(contact: Contact)
external fun erase(sessionId: String): Boolean
/**
* Similar to [updateIfExists], but will create the underlying contact if it doesn't exist before passing to [updateFunction]
*/
fun upsertContact(sessionId: String, updateFunction: Contact.()->Unit = {}) {
if (sessionId.startsWith(IdPrefix.BLINDED.value)) {
Log.w("Loki", "Trying to create a contact with a blinded ID prefix")
return
} else if (sessionId.startsWith(IdPrefix.UN_BLINDED.value)) {
Log.w("Loki", "Trying to create a contact with an un-blinded ID prefix")
return
} else if (sessionId.startsWith(IdPrefix.BLINDEDV2.value)) {
Log.w("Loki", "Trying to create a contact with a blindedv2 ID prefix")
return
}
val contact = getOrConstruct(sessionId)
updateFunction(contact)
set(contact)
}
/**
* Updates the contact by sessionId with a given [updateFunction], and applies to the underlying config.
* the [updateFunction] doesn't run if there is no contact
*/
fun updateIfExists(sessionId: String, updateFunction: Contact.()->Unit) {
if (sessionId.startsWith(IdPrefix.BLINDED.value)) {
Log.w("Loki", "Trying to create a contact with a blinded ID prefix")
return
} else if (sessionId.startsWith(IdPrefix.UN_BLINDED.value)) {
Log.w("Loki", "Trying to create a contact with an un-blinded ID prefix")
return
} else if (sessionId.startsWith(IdPrefix.BLINDEDV2.value)) {
Log.w("Loki", "Trying to create a contact with a blindedv2 ID prefix")
return
}
val contact = get(sessionId) ?: return
updateFunction(contact)
set(contact)
}
}
class UserProfile(pointer: Long) : ConfigBase(pointer) {
companion object {
init {
System.loadLibrary("session_util")
}
external fun newInstance(ed25519SecretKey: ByteArray): UserProfile
external fun newInstance(ed25519SecretKey: ByteArray, initialDump: ByteArray): UserProfile
}
external fun setName(newName: String)
external fun getName(): String?
external fun getPic(): UserPic
external fun setPic(userPic: UserPic)
external fun setNtsPriority(priority: Int)
external fun getNtsPriority(): Int
external fun setNtsExpiry(expiryMode: ExpiryMode)
external fun getNtsExpiry(): ExpiryMode
external fun getCommunityMessageRequests(): Boolean
external fun setCommunityMessageRequests(blocks: Boolean)
external fun isBlockCommunityMessageRequestsSet(): Boolean
}
class ConversationVolatileConfig(pointer: Long): ConfigBase(pointer) {
companion object {
init {
System.loadLibrary("session_util")
}
external fun newInstance(ed25519SecretKey: ByteArray): ConversationVolatileConfig
external fun newInstance(ed25519SecretKey: ByteArray, initialDump: ByteArray): ConversationVolatileConfig
}
external fun getOneToOne(pubKeyHex: String): Conversation.OneToOne?
external fun getOrConstructOneToOne(pubKeyHex: String): Conversation.OneToOne
external fun eraseOneToOne(pubKeyHex: String): Boolean
external fun getCommunity(baseUrl: String, room: String): Conversation.Community?
external fun getOrConstructCommunity(baseUrl: String, room: String, pubKeyHex: String): Conversation.Community
external fun getOrConstructCommunity(baseUrl: String, room: String, pubKey: ByteArray): Conversation.Community
external fun eraseCommunity(community: Conversation.Community): Boolean
external fun eraseCommunity(baseUrl: String, room: String): Boolean
external fun getLegacyClosedGroup(groupId: String): Conversation.LegacyGroup?
external fun getOrConstructLegacyGroup(groupId: String): Conversation.LegacyGroup
external fun eraseLegacyClosedGroup(groupId: String): Boolean
external fun erase(conversation: Conversation): Boolean
external fun set(toStore: Conversation)
/**
* Erase all conversations that do not satisfy the `predicate`, similar to [MutableList.removeAll]
*/
external fun eraseAll(predicate: (Conversation) -> Boolean): Int
external fun sizeOneToOnes(): Int
external fun sizeCommunities(): Int
external fun sizeLegacyClosedGroups(): Int
external fun size(): Int
external fun empty(): Boolean
external fun allOneToOnes(): List<Conversation.OneToOne>
external fun allCommunities(): List<Conversation.Community>
external fun allLegacyClosedGroups(): List<Conversation.LegacyGroup>
external fun all(): List<Conversation>
}
class UserGroupsConfig(pointer: Long): ConfigBase(pointer) {
companion object {
init {
System.loadLibrary("session_util")
}
external fun newInstance(ed25519SecretKey: ByteArray): UserGroupsConfig
external fun newInstance(ed25519SecretKey: ByteArray, initialDump: ByteArray): UserGroupsConfig
}
external fun getCommunityInfo(baseUrl: String, room: String): GroupInfo.CommunityGroupInfo?
external fun getLegacyGroupInfo(sessionId: String): GroupInfo.LegacyGroupInfo?
external fun getOrConstructCommunityInfo(baseUrl: String, room: String, pubKeyHex: String): GroupInfo.CommunityGroupInfo
external fun getOrConstructLegacyGroupInfo(sessionId: String): GroupInfo.LegacyGroupInfo
external fun set(groupInfo: GroupInfo)
external fun erase(communityInfo: GroupInfo)
external fun eraseCommunity(baseCommunityInfo: BaseCommunityInfo): Boolean
external fun eraseCommunity(server: String, room: String): Boolean
external fun eraseLegacyGroup(sessionId: String): Boolean
external fun sizeCommunityInfo(): Int
external fun sizeLegacyGroupInfo(): Int
external fun size(): Int
external fun all(): List<GroupInfo>
external fun allCommunityInfo(): List<GroupInfo.CommunityGroupInfo>
external fun allLegacyGroupInfo(): List<GroupInfo.LegacyGroupInfo>
}

View File

@@ -0,0 +1,11 @@
package network.loki.messenger.libsession_util.util
data class BaseCommunityInfo(val baseUrl: String, val room: String, val pubKeyHex: String) {
companion object {
init {
System.loadLibrary("session_util")
}
external fun parseFullUrl(fullUrl: String): Triple<String, String, ByteArray>?
}
external fun fullUrl(): String
}

View File

@@ -0,0 +1,13 @@
package network.loki.messenger.libsession_util.util
data class Contact(
val id: String,
var name: String = "",
var nickname: String = "",
var approved: Boolean = false,
var approvedMe: Boolean = false,
var blocked: Boolean = false,
var profilePicture: UserPic = UserPic.DEFAULT,
var priority: Int = 0,
var expiryMode: ExpiryMode,
)

View File

@@ -0,0 +1,25 @@
package network.loki.messenger.libsession_util.util
sealed class Conversation {
abstract var lastRead: Long
abstract var unread: Boolean
data class OneToOne(
val sessionId: String,
override var lastRead: Long,
override var unread: Boolean
): Conversation()
data class Community(
val baseCommunityInfo: BaseCommunityInfo,
override var lastRead: Long,
override var unread: Boolean
) : Conversation()
data class LegacyGroup(
val groupId: String,
override var lastRead: Long,
override var unread: Boolean
): Conversation()
}

View File

@@ -0,0 +1,18 @@
package network.loki.messenger.libsession_util.util
import kotlin.time.Duration.Companion.seconds
sealed class ExpiryMode(val expirySeconds: Long) {
object NONE: ExpiryMode(0)
data class Legacy(private val seconds: Long = 0L): ExpiryMode(seconds)
data class AfterSend(private val seconds: Long = 0L): ExpiryMode(seconds)
data class AfterRead(private val seconds: Long = 0L): ExpiryMode(seconds)
val duration get() = expirySeconds.seconds
val expiryMillis get() = expirySeconds * 1000L
fun coerceSendToRead(coerce: Boolean = true) = if (coerce && this is AfterSend) AfterRead(expirySeconds) else this
}
fun afterSend(seconds: Long) = seconds.takeIf { it > 0 }?.let(ExpiryMode::AfterSend) ?: ExpiryMode.NONE

View File

@@ -0,0 +1,53 @@
package network.loki.messenger.libsession_util.util
sealed class GroupInfo {
data class CommunityGroupInfo(val community: BaseCommunityInfo, val priority: Int) : GroupInfo()
data class LegacyGroupInfo(
val sessionId: String,
val name: String,
val members: Map<String, Boolean>,
val encPubKey: ByteArray,
val encSecKey: ByteArray,
val priority: Int,
val disappearingTimer: Long,
val joinedAt: Long
): GroupInfo() {
companion object {
@Suppress("FunctionName")
external fun NAME_MAX_LENGTH(): Int
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LegacyGroupInfo
if (sessionId != other.sessionId) return false
if (name != other.name) return false
if (members != other.members) return false
if (!encPubKey.contentEquals(other.encPubKey)) return false
if (!encSecKey.contentEquals(other.encSecKey)) return false
if (priority != other.priority) return false
if (disappearingTimer != other.disappearingTimer) return false
if (joinedAt != other.joinedAt) return false
return true
}
override fun hashCode(): Int {
var result = sessionId.hashCode()
result = 31 * result + name.hashCode()
result = 31 * result + members.hashCode()
result = 31 * result + encPubKey.contentHashCode()
result = 31 * result + encSecKey.contentHashCode()
result = 31 * result + priority
result = 31 * result + disappearingTimer.hashCode()
result = 31 * result + joinedAt.hashCode()
return result
}
}
}

View File

@@ -0,0 +1,9 @@
package network.loki.messenger.libsession_util.util
object Sodium {
init {
System.loadLibrary("session_util")
}
external fun ed25519KeyPair(seed: ByteArray): KeyPair
external fun ed25519PkToCurve25519(pk: ByteArray): ByteArray
}

View File

@@ -0,0 +1,67 @@
package network.loki.messenger.libsession_util.util
data class ConfigPush(val config: ByteArray, val seqNo: Long, val obsoleteHashes: List<String>) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ConfigPush
if (!config.contentEquals(other.config)) return false
if (seqNo != other.seqNo) return false
if (obsoleteHashes != other.obsoleteHashes) return false
return true
}
override fun hashCode(): Int {
var result = config.contentHashCode()
result = 31 * result + seqNo.hashCode()
result = 31 * result + obsoleteHashes.hashCode()
return result
}
}
data class UserPic(val url: String, val key: ByteArray) {
companion object {
val DEFAULT = UserPic("", byteArrayOf())
}
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
}
}
data class KeyPair(val pubKey: ByteArray, val secretKey: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as KeyPair
if (!pubKey.contentEquals(other.pubKey)) return false
if (!secretKey.contentEquals(other.secretKey)) return false
return true
}
override fun hashCode(): Int {
var result = pubKey.contentHashCode()
result = 31 * result + secretKey.contentHashCode()
return result
}
}

View File

@@ -0,0 +1,14 @@
package network.loki.messenger.libsession_util
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
}