commit 6f545905a19cd86ec4309442c503cebde51eee3e
parent ab846b11ebbfef8e8315964ab4a188fefea2b6dd
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Wed, 18 Oct 2023 01:21:16 +0200

pref: database and e2ee
- fix ff preview for e2e messages

Diffstat:
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ModContext.kt | 2++
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt | 3+--
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/database/DatabaseAccess.kt | 34+++++++++++++++++++++++++++++-----
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/EndToEndEncryption.kt | 54+++++++++++++++++++++++++++++++-----------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/FriendFeedMessagePreview.kt | 29++++++++++++++++++++++++-----
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/messaging/CoreMessagingBridge.kt | 4++--
6 files changed, 89 insertions(+), 37 deletions(-)

diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ModContext.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ModContext.kt @@ -27,6 +27,7 @@ import me.rhunk.snapenhance.core.features.Feature import me.rhunk.snapenhance.core.logger.CoreLogger import me.rhunk.snapenhance.core.manager.impl.ActionManager import me.rhunk.snapenhance.core.manager.impl.FeatureManager +import me.rhunk.snapenhance.core.messaging.CoreMessagingBridge import me.rhunk.snapenhance.core.messaging.MessageSender import me.rhunk.snapenhance.core.scripting.CoreScriptRuntime import me.rhunk.snapenhance.core.util.media.HttpServer @@ -61,6 +62,7 @@ class ModContext { val eventDispatcher = EventDispatcher(this) val native = NativeLib() val scriptRuntime by lazy { CoreScriptRuntime(androidContext, log) } + val messagingBridge = CoreMessagingBridge(this) val isDeveloper by lazy { config.scripting.developerMode.get() } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt @@ -19,7 +19,6 @@ import me.rhunk.snapenhance.core.bridge.loadFromBridge import me.rhunk.snapenhance.core.data.SnapClassCache import me.rhunk.snapenhance.core.event.events.impl.SnapWidgetBroadcastReceiveEvent import me.rhunk.snapenhance.core.event.events.impl.UnaryCallEvent -import me.rhunk.snapenhance.core.messaging.CoreMessagingBridge import me.rhunk.snapenhance.core.util.LSPatchUpdater import me.rhunk.snapenhance.core.util.hook.HookStage import me.rhunk.snapenhance.core.util.hook.hook @@ -117,7 +116,7 @@ class SnapEnhance { logCritical(null, throwable) } } - bridgeClient.registerMessagingBridge(CoreMessagingBridge(this)) + bridgeClient.registerMessagingBridge(messagingBridge) reloadConfig() actionManager.init() diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/database/DatabaseAccess.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/database/DatabaseAccess.kt @@ -26,6 +26,10 @@ class DatabaseAccess(private val context: ModContext) : Manager { context.androidContext.getDatabasePath("main.db") } + private val dmOtherParticipantCache by lazy { + getAllDMOtherParticipants().toMutableMap() + } + private fun openMain(): SQLiteDatabase { return SQLiteDatabase.openDatabase( mainDatabase.absolutePath, @@ -95,7 +99,7 @@ class DatabaseAccess(private val context: ModContext) : Manager { val myUserId by lazy { safeDatabaseOperation(openArroyo()) { arroyoDatabase: SQLiteDatabase -> arroyoDatabase.rawQuery(buildString { - append("SELECT * FROM required_values WHERE key = 'USERID'") + append("SELECT value FROM required_values WHERE key = 'USERID'") }, null).use { query -> if (!query.moveToFirst()) { return@safeDatabaseOperation null @@ -163,7 +167,7 @@ class DatabaseAccess(private val context: ModContext) : Manager { fun getConversationType(conversationId: String): Int? { return safeDatabaseOperation(openArroyo()) { database -> database.rawQuery( - "SELECT * FROM user_conversation WHERE client_conversation_id = ?", + "SELECT conversation_type FROM user_conversation WHERE client_conversation_id = ?", arrayOf(conversationId) ).use { query -> if (!query.moveToFirst()) { @@ -186,10 +190,30 @@ class DatabaseAccess(private val context: ModContext) : Manager { } } + private fun getAllDMOtherParticipants(): Map<String, String?> { + return safeDatabaseOperation(openArroyo()) { arroyoDatabase: SQLiteDatabase -> + arroyoDatabase.rawQuery( + "SELECT client_conversation_id, user_id FROM user_conversation WHERE conversation_type = 0", + null + ).use { query -> + val participants = mutableMapOf<String, String>() + if (!query.moveToFirst()) { + return@safeDatabaseOperation null + } + do { + participants[query.getString(query.getColumnIndex("client_conversation_id"))] = + query.getString(query.getColumnIndex("user_id")) + } while (query.moveToNext()) + participants + } + } ?: emptyMap() + } + fun getDMOtherParticipant(conversationId: String): String? { + if (dmOtherParticipantCache.containsKey(conversationId)) return dmOtherParticipantCache[conversationId] return safeDatabaseOperation(openArroyo()) { cursor -> cursor.rawQuery( - "SELECT * FROM user_conversation WHERE client_conversation_id = ? AND conversation_type = 0", + "SELECT user_id FROM user_conversation WHERE client_conversation_id = ? AND conversation_type = 0", arrayOf(conversationId) ).use { query -> val participants = mutableListOf<String>() @@ -201,7 +225,7 @@ class DatabaseAccess(private val context: ModContext) : Manager { } while (query.moveToNext()) participants.firstOrNull { it != myUserId } } - } + }.also { dmOtherParticipantCache[conversationId] = it } } @@ -214,7 +238,7 @@ class DatabaseAccess(private val context: ModContext) : Manager { fun getConversationParticipants(conversationId: String): List<String>? { return safeDatabaseOperation(openArroyo()) { arroyoDatabase: SQLiteDatabase -> arroyoDatabase.rawQuery( - "SELECT * FROM user_conversation WHERE client_conversation_id = ?", + "SELECT user_id FROM user_conversation WHERE client_conversation_id = ?", arrayOf(conversationId) ).use { if (!it.moveToFirst()) { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/EndToEndEncryption.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/EndToEndEncryption.kt @@ -12,6 +12,7 @@ import android.view.ViewGroup.MarginLayoutParams import android.widget.Button import android.widget.TextView import me.rhunk.snapenhance.common.data.ContentType +import me.rhunk.snapenhance.common.data.MessageState import me.rhunk.snapenhance.common.data.MessagingRuleType import me.rhunk.snapenhance.common.data.RuleState import me.rhunk.snapenhance.common.util.protobuf.ProtoEditor @@ -268,25 +269,26 @@ class EndToEndEncryption : MessagingRuleFeature( }.digest() } - private fun messageHook(conversationId: String, messageId: Long, senderId: String, messageContent: MessageContent) { - if (messageContent.contentType != ContentType.STATUS && decryptedMessageCache.containsKey(messageId)) { - val (contentType, buffer) = decryptedMessageCache[messageId]!! - messageContent.contentType = contentType - messageContent.content = buffer - return + fun tryDecryptMessage(senderId: String, clientMessageId: Long, conversationId: String, contentType: ContentType, messageBuffer: ByteArray): Pair<ContentType, ByteArray> { + if (contentType != ContentType.STATUS && decryptedMessageCache.containsKey(clientMessageId)) { + return decryptedMessageCache[clientMessageId]!! } - val reader = ProtoReader(messageContent.content) - messageContent.contentType = fixContentType(messageContent.contentType!!, reader) + val reader = ProtoReader(messageBuffer) + var outputBuffer = messageBuffer + var outputContentType = fixContentType(contentType, reader) ?: contentType + val conversationParticipants by lazy { + getE2EParticipants(conversationId) + } fun setMessageContent(buffer: ByteArray) { - messageContent.content = buffer - messageContent.contentType = fixContentType(messageContent.contentType, ProtoReader(buffer)) - decryptedMessageCache[messageId] = messageContent.contentType!! to buffer + outputBuffer = buffer + outputContentType = fixContentType(outputContentType, ProtoReader(buffer)) ?: outputContentType + decryptedMessageCache[clientMessageId] = outputContentType to buffer } fun replaceMessageText(text: String) { - messageContent.content = ProtoWriter().apply { + outputBuffer = ProtoWriter().apply { from(2) { addString(1, text) } @@ -297,9 +299,6 @@ class EndToEndEncryption : MessagingRuleFeature( reader.followPath(2, 1) { val messageTypeId = getVarInt(1)?.toInt() ?: return@followPath val isMe = context.database.myUserId == senderId - val conversationParticipants by lazy { - getE2EParticipants(conversationId) - } if (messageTypeId == ENCRYPTED_MESSAGE_ID) { runCatching { @@ -314,7 +313,7 @@ class EndToEndEncryption : MessagingRuleFeature( setMessageContent( e2eeInterface.decryptMessage(participantId, ciphertext, iv) ) - encryptedMessages.add(messageId) + encryptedMessages.add(clientMessageId) return@eachBuffer } @@ -323,14 +322,14 @@ class EndToEndEncryption : MessagingRuleFeature( setMessageContent( e2eeInterface.decryptMessage(senderId, ciphertext, iv) ) - encryptedMessages.add(messageId) + encryptedMessages.add(clientMessageId) } }.onFailure { - context.log.error("Failed to decrypt message id: $messageId", it) - messageContent.contentType = ContentType.CHAT - messageContent.content = ProtoWriter().apply { + context.log.error("Failed to decrypt message id: $clientMessageId", it) + outputContentType = ContentType.CHAT + outputBuffer = ProtoWriter().apply { from(2) { - addString(1, "Failed to decrypt message, id=$messageId. Check logcat for more details.") + addString(1, "Failed to decrypt message, id=$clientMessageId. Check logcat for more details.") } }.toByteArray() } @@ -354,15 +353,23 @@ class EndToEndEncryption : MessagingRuleFeature( when (messageTypeId) { REQUEST_PK_MESSAGE_ID -> { - pkRequests[messageId] = payload + pkRequests[clientMessageId] = payload replaceMessageText("You just received a public key request. Click below to accept it.") } RESPONSE_SK_MESSAGE_ID -> { - secretResponses[messageId] = payload + secretResponses[clientMessageId] = payload replaceMessageText("Your friend just accepted your public key. Click below to accept the secret.") } } } + + return outputContentType to outputBuffer + } + + private fun messageHook(conversationId: String, messageId: Long, senderId: String, messageContent: MessageContent) { + val (contentType, buffer) = tryDecryptMessage(senderId, messageId, conversationId, messageContent.contentType ?: ContentType.CHAT, messageContent.content) + messageContent.contentType = contentType + messageContent.content = buffer } override fun asyncInit() { @@ -474,6 +481,7 @@ class EndToEndEncryption : MessagingRuleFeature( context.event.subscribe(BuildMessageEvent::class, priority = 0) { event -> val message = event.message + if (message.messageState != MessageState.COMMITTED) return@subscribe val conversationId = message.messageDescriptor.conversationId.toString() messageHook( conversationId = conversationId, diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/FriendFeedMessagePreview.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/FriendFeedMessagePreview.kt @@ -15,8 +15,10 @@ import me.rhunk.snapenhance.common.util.protobuf.ProtoReader import me.rhunk.snapenhance.core.event.events.impl.BindViewEvent import me.rhunk.snapenhance.core.features.Feature import me.rhunk.snapenhance.core.features.FeatureLoadParams +import me.rhunk.snapenhance.core.features.impl.experiments.EndToEndEncryption import me.rhunk.snapenhance.core.ui.addForegroundDrawable import me.rhunk.snapenhance.core.ui.removeForegroundDrawable +import me.rhunk.snapenhance.core.util.EvictingMap import kotlin.math.absoluteValue @SuppressLint("DiscouragedApi") @@ -27,12 +29,17 @@ class FriendFeedMessagePreview : Feature("FriendFeedMessagePreview", loadParams ).getColor(0, 0) } + private val friendNameCache = EvictingMap<String, String>(100) + private fun getDimens(name: String) = context.resources.getDimensionPixelSize(context.resources.getIdentifier(name, "dimen", Constants.SNAPCHAT_PACKAGE_NAME)) override fun onActivityCreate() { val setting = context.config.userInterface.friendFeedMessagePreview if (setting.globalState != true) return + val hasE2EE = context.config.experimental.e2eEncryption.globalState == true + val endToEndEncryption by lazy { context.feature(EndToEndEncryption::class) } + val ffItemId = context.resources.getIdentifier("ff_item", "id", Constants.SNAPCHAT_PACKAGE_NAME) val secondaryTextSize = getDimens("ff_feed_cell_secondary_text_size").toFloat() @@ -58,17 +65,29 @@ class FriendFeedMessagePreview : Feature("FriendFeedMessagePreview", loadParams frameLayout.removeForegroundDrawable("ffItem") val stringMessages = context.database.getMessagesFromConversationId(conversationId, setting.amount.get().absoluteValue)?.mapNotNull { message -> - val messageContainer = message.messageContent - ?.let { ProtoReader(it) } - ?.followPath(4, 4) + val messageContainer = + message.messageContent + ?.let { ProtoReader(it) } + ?.followPath(4, 4)?.let { messageReader -> + takeIf { hasE2EE }?.let takeIf@{ + endToEndEncryption.tryDecryptMessage( + senderId = message.senderId ?: return@takeIf null, + clientMessageId = message.clientMessageId.toLong(), + conversationId = message.clientConversationId ?: return@takeIf null, + contentType = ContentType.fromId(message.contentType), + messageBuffer = messageReader.getBuffer() + ).second + }?.let { ProtoReader(it) } ?: messageReader + } ?: return@mapNotNull null val messageString = messageContainer.getString(2, 1) ?: ContentType.fromMessageContainer(messageContainer)?.name ?: return@mapNotNull null - val friendName = context.database.getFriendInfo(message.senderId ?: return@mapNotNull null)?.let { it.displayName?: it.mutableUsername } ?: "Unknown" - + val friendName = friendNameCache.getOrPut(message.senderId ?: return@mapNotNull null) { + context.database.getFriendInfo(message.senderId ?: return@mapNotNull null)?.let { it.displayName?: it.mutableUsername } ?: "Unknown" + } "$friendName: $messageString" }?.reversed() ?: return@friendFeedItem diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/CoreMessagingBridge.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/CoreMessagingBridge.kt @@ -36,7 +36,7 @@ class CoreMessagingBridge( val callback = CallbackBuilder( context.mappings.getMappedClass("callbacks", "FetchMessageCallback") ).override("onFetchMessageComplete") { param -> - val message = me.rhunk.snapenhance.core.wrapper.impl.Message(param.arg(1)).toBridge() + val message = me.rhunk.snapenhance.core.wrapper.impl.Message(param.arg(0)).toBridge() continuation.resumeWith(Result.success(message)) } .override("onServerRequest", shouldUnhook = false) {} @@ -47,7 +47,7 @@ class CoreMessagingBridge( context.classCache.conversationManager.methods.first { it.name == "fetchMessage" }.invoke( conversationManager, SnapUUID.fromString(conversationId).instanceNonNull(), - clientMessageId, + clientMessageId.toLong(), callback ) }