Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds content type filter libxmtp update #358

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,20 @@ class MainViewModel : ViewModel() {
val listItems = mutableListOf<MainListItem>()
try {
val conversations = ClientManager.client.conversations.list()
val subscriptions: MutableList<Service.Subscription> = conversations.map {
val subscriptions = conversations.map {
val hmacKeysResult = ClientManager.client.conversations.getHmacKeys()
val hmacKeys = hmacKeysResult.hmacKeysMap
val result = hmacKeys[it.topic]?.valuesList?.map { hmacKey ->
Service.Subscription.HmacKey.newBuilder().also { sub_key ->
sub_key.key = hmacKey.hmacKey
sub_key.thirtyDayPeriodsSinceEpoch = hmacKey.thirtyDayPeriodsSinceEpoch
}.build()
}

Service.Subscription.newBuilder().also { sub ->
sub.addAllHmacKeys(result)
sub.topic = it.topic
sub.isSilent = false
}.build()
}.toMutableList()

Expand Down
2 changes: 1 addition & 1 deletion library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ dependencies {
implementation 'org.web3j:crypto:4.9.4'
implementation "net.java.dev.jna:jna:5.14.0@aar"
api 'com.google.protobuf:protobuf-kotlin-lite:3.22.3'
api 'org.xmtp:proto-kotlin:3.72.3'
api 'org.xmtp:proto-kotlin:3.72.4'

testImplementation 'junit:junit:4.13.2'
testImplementation 'androidx.test:monitor:1.7.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
Expand Down Expand Up @@ -115,7 +116,7 @@ class ConversationsTest {
runBlocking { boClient.conversations.findOrCreateDm(caro.walletAddress) }
val group =
runBlocking { boClient.conversations.newGroup(listOf(caro.walletAddress)) }
assertEquals(runBlocking { boClient.conversations.syncAllConversations() }.toInt(), 2)
assertEquals(runBlocking { boClient.conversations.syncAllConversations() }.toInt(), 3)
assertEquals(
runBlocking { boClient.conversations.syncAllConversations(consentState = ConsentState.ALLOWED) }.toInt(),
2
Expand Down Expand Up @@ -186,4 +187,24 @@ class ConversationsTest {
assertEquals(2, allMessages.size)
job.cancel()
}

@Test
fun testReturnsAllHMACKeys() {
val conversations = mutableListOf<Conversation>()
repeat(5) {
val account = PrivateKeyBuilder()
val client = runBlocking { Client().create(account, fixtures.clientOptions) }
runBlocking {
conversations.add(
alixClient.conversations.newConversation(client.address)
)
}
}
val hmacKeys = alixClient.conversations.getHmacKeys()

val topics = hmacKeys.hmacKeysMap.keys
conversations.forEach { convo ->
assertTrue(topics.contains(convo.topic))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ class HistorySyncTest {

runBlocking {
alixClient2.preferences.syncConsent()
Thread.sleep(2000)
alixClient.conversations.syncAllConversations()
Thread.sleep(2000)
alixClient2.conversations.syncAllConversations()
Expand Down Expand Up @@ -125,8 +124,7 @@ class HistorySyncTest {
runBlocking {
alix2Group.send("A message")
alix2Group.send("A second message")
alixClient3.requestMessageHistorySync()
Thread.sleep(1000)
Thread.sleep(2000)
alixClient.conversations.syncAllConversations()
Thread.sleep(2000)
alixClient2.conversations.syncAllConversations()
Expand Down Expand Up @@ -170,4 +168,45 @@ class HistorySyncTest {
assertEquals(alixGroup.consentState(), ConsentState.DENIED)
job.cancel()
}

@Test
fun testStreamPreferenceUpdates() {
var preferences = 0
val job = CoroutineScope(Dispatchers.IO).launch {
try {
alixClient.preferences.streamPreferenceUpdates()
.collect { entry ->
preferences++
}
} catch (e: Exception) {
}
}

Thread.sleep(2000)

runBlocking {
val alixClient3 = runBlocking {
Client().create(
account = alixWallet,
options = ClientOptions(
ClientOptions.Api(XMTPEnvironment.LOCAL, false),
appContext = fixtures.context,
dbEncryptionKey = fixtures.key,
dbDirectory = File(fixtures.context.filesDir.absolutePath, "xmtp_db3").toPath()
.toString()
)
)
}
alixClient3.conversations.syncAllConversations()
Thread.sleep(2000)
alixClient.conversations.syncAllConversations()
Thread.sleep(2000)
alixClient2.conversations.syncAllConversations()
Thread.sleep(2000)
}

Thread.sleep(2000)
assertEquals(2, preferences)
job.cancel()
}
}
4 changes: 2 additions & 2 deletions library/src/main/java/libxmtp-version.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Version: eccce061
Version: a9111a13
Branch: main
Date: 2024-12-18 17:39:46 +0000
Date: 2024-12-21 00:25:32 +0000
5 changes: 0 additions & 5 deletions library/src/main/java/org/xmtp/android/library/Client.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import org.xmtp.android.library.codecs.TextCodec
import org.xmtp.android.library.libxmtp.Message
import org.xmtp.android.library.messages.rawData
import uniffi.xmtpv3.FfiConversationType
import uniffi.xmtpv3.FfiDeviceSyncKind
import uniffi.xmtpv3.FfiSignatureRequest
import uniffi.xmtpv3.FfiXmtpClient
import uniffi.xmtpv3.XmtpApiClient
Expand Down Expand Up @@ -386,10 +385,6 @@ class Client() {
ffiClient.dbReconnect()
}

suspend fun requestMessageHistorySync() {
ffiClient.sendSyncRequest(FfiDeviceSyncKind.MESSAGES)
}

suspend fun inboxStatesForInboxIds(
refreshFromNetwork: Boolean,
inboxIds: List<String>,
Expand Down
41 changes: 38 additions & 3 deletions library/src/main/java/org/xmtp/android/library/Conversations.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package org.xmtp.android.library

import android.util.Log
import com.google.protobuf.kotlin.toByteString
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
import org.xmtp.android.library.libxmtp.Message
import org.xmtp.android.library.messages.Topic
import org.xmtp.proto.keystore.api.v1.Keystore
import uniffi.xmtpv3.FfiConversation
import uniffi.xmtpv3.FfiConversationCallback
import uniffi.xmtpv3.FfiConversationType
Expand Down Expand Up @@ -131,7 +134,11 @@ data class Conversations(

// Sync all new and existing conversations data from the network
suspend fun syncAllConversations(consentState: ConsentState? = null): UInt {
return ffiConversations.syncAllConversations(consentState?.let { ConsentState.toFfiConsentState(it) })
return ffiConversations.syncAllConversations(
consentState?.let {
ConsentState.toFfiConsentState(it)
}
)
}

suspend fun newConversation(peerAddress: String): Conversation {
Expand Down Expand Up @@ -235,7 +242,8 @@ data class Conversations(
null,
1,
null,
FfiDirection.DESCENDING
FfiDirection.DESCENDING,
null
)
)
.firstOrNull()
Expand Down Expand Up @@ -264,7 +272,15 @@ data class Conversations(
override fun onConversation(conversation: FfiConversation) {
launch(Dispatchers.IO) {
when (conversation.conversationType()) {
FfiConversationType.DM -> trySend(Conversation.Dm(Dm(client, conversation)))
FfiConversationType.DM -> trySend(
Conversation.Dm(
Dm(
client,
conversation
)
)
)

else -> trySend(Conversation.Group(Group(client, conversation)))
}
}
Expand Down Expand Up @@ -305,4 +321,23 @@ data class Conversations(

awaitClose { stream.end() }
}

fun getHmacKeys(): Keystore.GetConversationHmacKeysResponse {
val hmacKeysResponse = Keystore.GetConversationHmacKeysResponse.newBuilder()
val conversations = ffiConversations.getHmacKeys()
conversations.iterator().forEach {
val hmacKeys = Keystore.GetConversationHmacKeysResponse.HmacKeys.newBuilder()
it.value.forEach { key ->
val hmacKeyData = Keystore.GetConversationHmacKeysResponse.HmacKeyData.newBuilder()
hmacKeyData.hmacKey = key.key.toByteString()
hmacKeyData.thirtyDayPeriodsSinceEpoch = key.epoch.toInt()
hmacKeys.addValues(hmacKeyData)
}
hmacKeysResponse.putHmacKeys(
Topic.groupMessage(it.key.toHex()).description,
hmacKeys.build()
)
}
return hmacKeysResponse.build()
}
}
31 changes: 0 additions & 31 deletions library/src/main/java/org/xmtp/android/library/Crypto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import org.xmtp.proto.message.contents.CiphertextOuterClass
import java.security.GeneralSecurityException
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.Mac
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec

Expand Down Expand Up @@ -74,35 +73,5 @@ class Crypto {
null
}
}

fun calculateMac(secret: ByteArray, message: ByteArray): ByteArray {
val sha256HMAC: Mac = Mac.getInstance("HmacSHA256")
val secretKey = SecretKeySpec(secret, "HmacSHA256")
sha256HMAC.init(secretKey)
return sha256HMAC.doFinal(message)
}

fun deriveKey(
secret: ByteArray,
salt: ByteArray,
info: ByteArray,
): ByteArray {
return Hkdf.computeHkdf("HMACSHA256", secret, salt, info, 32)
}

fun verifyHmacSignature(
key: ByteArray,
signature: ByteArray,
message: ByteArray
): Boolean {
return try {
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(key, "HmacSHA256"))
val computedSignature = mac.doFinal(message)
computedSignature.contentEquals(signature)
} catch (e: Exception) {
false
}
}
}
}
5 changes: 4 additions & 1 deletion library/src/main/java/org/xmtp/android/library/Dm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.xmtp.android.library.libxmtp.Message
import org.xmtp.android.library.libxmtp.Message.MessageDeliveryStatus
import org.xmtp.android.library.libxmtp.Message.SortDirection
import org.xmtp.android.library.messages.Topic
import uniffi.xmtpv3.FfiContentType
import uniffi.xmtpv3.FfiConversation
import uniffi.xmtpv3.FfiConversationMetadata
import uniffi.xmtpv3.FfiDeliveryStatus
Expand Down Expand Up @@ -109,6 +110,7 @@ class Dm(val client: Client, private val libXMTPGroup: FfiConversation) {
afterNs: Long? = null,
direction: SortDirection = SortDirection.DESCENDING,
deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.ALL,
contentTypes: List<FfiContentType>? = null
): List<DecodedMessage> {
return libXMTPGroup.findMessages(
opts = FfiListMessagesOptions(
Expand All @@ -124,7 +126,8 @@ class Dm(val client: Client, private val libXMTPGroup: FfiConversation) {
direction = when (direction) {
SortDirection.ASCENDING -> FfiDirection.ASCENDING
else -> FfiDirection.DESCENDING
}
},
contentTypes = contentTypes
)
).mapNotNull {
Message(client, it).decodeOrNull()
Expand Down
5 changes: 4 additions & 1 deletion library/src/main/java/org/xmtp/android/library/Group.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.xmtp.android.library.libxmtp.Message
import org.xmtp.android.library.libxmtp.Message.MessageDeliveryStatus
import org.xmtp.android.library.libxmtp.Message.SortDirection
import org.xmtp.android.library.messages.Topic
import uniffi.xmtpv3.FfiContentType
import uniffi.xmtpv3.FfiConversation
import uniffi.xmtpv3.FfiConversationMetadata
import uniffi.xmtpv3.FfiDeliveryStatus
Expand Down Expand Up @@ -126,6 +127,7 @@ class Group(val client: Client, private val libXMTPGroup: FfiConversation) {
afterNs: Long? = null,
direction: SortDirection = SortDirection.DESCENDING,
deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.ALL,
contentTypes: List<FfiContentType>? = null
): List<DecodedMessage> {
return libXMTPGroup.findMessages(
opts = FfiListMessagesOptions(
Expand All @@ -141,7 +143,8 @@ class Group(val client: Client, private val libXMTPGroup: FfiConversation) {
direction = when (direction) {
SortDirection.ASCENDING -> FfiDirection.ASCENDING
else -> FfiDirection.DESCENDING
}
},
contentTypes = contentTypes
)
).mapNotNull {
Message(client, it).decodeOrNull()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import uniffi.xmtpv3.FfiConsentCallback
import uniffi.xmtpv3.FfiConsentEntityType
import uniffi.xmtpv3.FfiConsentState
import uniffi.xmtpv3.FfiDeviceSyncKind
import uniffi.xmtpv3.FfiPreferenceCallback
import uniffi.xmtpv3.FfiPreferenceUpdate
import uniffi.xmtpv3.FfiSubscribeException
import uniffi.xmtpv3.FfiXmtpClient

Expand Down Expand Up @@ -60,6 +62,10 @@ enum class EntryType {
}
}

enum class PreferenceType {
HMAC_KEYS;
}

data class ConsentRecord(
val value: String,
val entryType: EntryType,
Expand Down Expand Up @@ -100,6 +106,26 @@ data class PrivatePreferences(
ffiClient.sendSyncRequest(FfiDeviceSyncKind.CONSENT)
}

suspend fun streamPreferenceUpdates(): Flow<PreferenceType> = callbackFlow {
val preferenceCallback = object : FfiPreferenceCallback {
override fun onPreferenceUpdate(preference: List<FfiPreferenceUpdate>) {
preference.iterator().forEach {
when (it) {
is FfiPreferenceUpdate.Hmac -> trySend(PreferenceType.HMAC_KEYS)
}
}
}

override fun onError(error: FfiSubscribeException) {
Log.e("XMTP preference update stream", error.message.toString())
}
}

val stream = ffiClient.conversations().streamPreferences(preferenceCallback)

awaitClose { stream.end() }
}

suspend fun streamConsent(): Flow<ConsentRecord> = callbackFlow {
val consentCallback = object : FfiConsentCallback {
override fun onConsentUpdate(consent: List<FfiConsent>) {
Expand Down
Loading
Loading