From bdede2784c662865fb1809e28c4f181362b7dbd6 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Tue, 13 Jan 2026 06:02:03 +0500 Subject: [PATCH] feat: Add logging for packet details before sending, including attachment count and details --- .../rosetta/messenger/crypto/MessageCrypto.kt | 40 ++++++++++++++++--- .../messenger/ui/chats/ChatDetailScreen.kt | 19 +++++---- .../messenger/ui/chats/ChatViewModel.kt | 7 ++++ 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/crypto/MessageCrypto.kt b/app/src/main/java/com/rosetta/messenger/crypto/MessageCrypto.kt index 0b8b283..22b25b3 100644 --- a/app/src/main/java/com/rosetta/messenger/crypto/MessageCrypto.kt +++ b/app/src/main/java/com/rosetta/messenger/crypto/MessageCrypto.kt @@ -752,7 +752,11 @@ object MessageCrypto { * Использует PBKDF2 + AES-CBC с тем же ключом что и основное сообщение * * В RN: encodeWithPassword(key.toString('utf-8'), JSON.stringify(reply)) - * где key = Buffer.concat([chacha_key, nonce]) - 56 bytes as UTF-8 string + * где key = Buffer.concat([chacha_key, nonce]) - 56 bytes + * + * ВАЖНО: JavaScript Buffer.toString('utf-8') на невалидных UTF-8 байтах + * заменяет их на U+FFFD (replacement character). Это поведение нужно + * воспроизвести в Kotlin для совместимости. * * Формат выхода: "ivBase64:ciphertextBase64" (совместим с desktop) */ @@ -762,9 +766,11 @@ object MessageCrypto { android.util.Log.d("MessageCrypto", " - ReplyJson length: ${replyJson.length}") android.util.Log.d("MessageCrypto", " - PlainKeyAndNonce length: ${plainKeyAndNonce.size}") - // Convert keyAndNonce to UTF-8 string (as password) - same as RN: key.toString('utf-8') - val password = String(plainKeyAndNonce, Charsets.UTF_8) + // Convert keyAndNonce to string - simulate JS Buffer.toString('utf-8') behavior + // which replaces invalid UTF-8 sequences with U+FFFD + val password = bytesToJsUtf8String(plainKeyAndNonce) android.util.Log.d("MessageCrypto", " - Password length: ${password.length}") + android.util.Log.d("MessageCrypto", " - Password char codes: ${password.take(5).map { it.code }}") // Compress with pako (deflate) val deflater = java.util.zip.Deflater() @@ -786,7 +792,7 @@ object MessageCrypto { ) val secretKey = factory.generateSecret(spec) val keyBytes = secretKey.encoded - android.util.Log.d("MessageCrypto", " - PBKDF2 key derived: ${keyBytes.size} bytes") + android.util.Log.d("MessageCrypto", " - PBKDF2 key derived: ${keyBytes.toHex()}") // Generate random IV (16 bytes) val iv = ByteArray(16) @@ -814,6 +820,25 @@ object MessageCrypto { replyJson } } + + /** + * Converts bytes to string mimicking JavaScript's Buffer.toString('utf-8') behavior. + * Invalid UTF-8 sequences are replaced with U+FFFD (replacement character). + */ + private fun bytesToJsUtf8String(bytes: ByteArray): String { + // CharsetDecoder with REPLACE action mimics JS behavior + val decoder = Charsets.UTF_8.newDecoder() + decoder.onMalformedInput(java.nio.charset.CodingErrorAction.REPLACE) + decoder.onUnmappableCharacter(java.nio.charset.CodingErrorAction.REPLACE) + decoder.replaceWith("\uFFFD") + + return try { + decoder.decode(java.nio.ByteBuffer.wrap(bytes)).toString() + } catch (e: Exception) { + // Fallback to standard UTF-8 (shouldn't happen with REPLACE action) + String(bytes, Charsets.UTF_8) + } + } /** * 🔥 Расшифровывает reply blob из attachments (как в React Native) @@ -842,8 +867,10 @@ object MessageCrypto { val ciphertext = Base64.decode(parts[1], Base64.DEFAULT) android.util.Log.d("MessageCrypto", " - IV size: ${iv.size}, Ciphertext size: ${ciphertext.size}") - // Password from keyAndNonce - val password = String(plainKeyAndNonce, Charsets.UTF_8) + // Password from keyAndNonce - use same JS-like UTF-8 conversion + val password = bytesToJsUtf8String(plainKeyAndNonce) + android.util.Log.d("MessageCrypto", " - Password length: ${password.length}") + android.util.Log.d("MessageCrypto", " - Password char codes: ${password.take(5).map { it.code }}") // PBKDF2 key derivation val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") @@ -855,6 +882,7 @@ object MessageCrypto { ) val secretKey = factory.generateSecret(spec) val keyBytes = secretKey.encoded + android.util.Log.d("MessageCrypto", " - PBKDF2 key derived: ${keyBytes.toHex()}") // AES-CBC decryption val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt index 7325e4c..196398e 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt @@ -224,11 +224,18 @@ fun ChatDetailScreen( var showEmojiPicker by remember { mutableStateOf(false) } val emojiPanelHeight = if (imeHeight > 50.dp) imeHeight else 280.dp - // Динамический bottom padding для списка: инпут (~70dp) + клавиатура/эмодзи + // 🔥 Reply/Forward state (нужен для расчёта listBottomPadding) + val replyMessages by viewModel.replyMessages.collectAsState() + val hasReply = replyMessages.isNotEmpty() + + // 🔥 Дополнительная высота для reply панели (~50dp) + val replyPanelHeight = if (hasReply) 50.dp else 0.dp + + // Динамический bottom padding для списка: инпут (~70dp) + reply (~50dp) + клавиатура/эмодзи val listBottomPadding = when { - isKeyboardVisible -> 70.dp + imeHeight - showEmojiPicker -> 70.dp + emojiPanelHeight - else -> 100.dp + isKeyboardVisible -> 70.dp + replyPanelHeight + imeHeight + showEmojiPicker -> 70.dp + replyPanelHeight + emojiPanelHeight + else -> 100.dp + replyPanelHeight } // Telegram-style scroll tracking @@ -289,10 +296,8 @@ fun ChatDetailScreen( val isTyping by viewModel.opponentTyping.collectAsState() val isOnline by viewModel.opponentOnline.collectAsState() - // 🔥 Reply/Forward state - val replyMessages by viewModel.replyMessages.collectAsState() + // 🔥 Reply/Forward state (replyMessages и hasReply определены выше для listBottomPadding) val isForwardMode by viewModel.isForwardMode.collectAsState() - val hasReply = replyMessages.isNotEmpty() // 🔥 Добавляем информацию о датах к сообщениям // В reversed layout (новые внизу) - показываем дату ПОСЛЕ сообщения (визуально сверху) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt index 258588e..88edb2c 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt @@ -801,6 +801,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { attachments = messageAttachments } + // 🔥 Log packet details before sending + ProtocolManager.addLog("📤📤📤 SENDING PACKET 📤📤📤") + ProtocolManager.addLog(" - Attachments count: ${packet.attachments.size}") + packet.attachments.forEach { att -> + ProtocolManager.addLog(" - Attachment: type=${att.type}, id=${att.id}, blob=${att.blob.take(50)}...") + } + // Отправляем пакет ProtocolManager.send(packet)