feat: Add logging for packet details before sending, including attachment count and details

This commit is contained in:
k1ngsterr1
2026-01-13 06:02:03 +05:00
parent c52b6c1799
commit bdede2784c
3 changed files with 53 additions and 13 deletions

View File

@@ -752,7 +752,11 @@ object MessageCrypto {
* Использует PBKDF2 + AES-CBC с тем же ключом что и основное сообщение * Использует PBKDF2 + AES-CBC с тем же ключом что и основное сообщение
* *
* В RN: encodeWithPassword(key.toString('utf-8'), JSON.stringify(reply)) * В 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) * Формат выхода: "ivBase64:ciphertextBase64" (совместим с desktop)
*/ */
@@ -762,9 +766,11 @@ object MessageCrypto {
android.util.Log.d("MessageCrypto", " - ReplyJson length: ${replyJson.length}") android.util.Log.d("MessageCrypto", " - ReplyJson length: ${replyJson.length}")
android.util.Log.d("MessageCrypto", " - PlainKeyAndNonce length: ${plainKeyAndNonce.size}") android.util.Log.d("MessageCrypto", " - PlainKeyAndNonce length: ${plainKeyAndNonce.size}")
// Convert keyAndNonce to UTF-8 string (as password) - same as RN: key.toString('utf-8') // Convert keyAndNonce to string - simulate JS Buffer.toString('utf-8') behavior
val password = String(plainKeyAndNonce, Charsets.UTF_8) // 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 length: ${password.length}")
android.util.Log.d("MessageCrypto", " - Password char codes: ${password.take(5).map { it.code }}")
// Compress with pako (deflate) // Compress with pako (deflate)
val deflater = java.util.zip.Deflater() val deflater = java.util.zip.Deflater()
@@ -786,7 +792,7 @@ object MessageCrypto {
) )
val secretKey = factory.generateSecret(spec) val secretKey = factory.generateSecret(spec)
val keyBytes = secretKey.encoded 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) // Generate random IV (16 bytes)
val iv = ByteArray(16) val iv = ByteArray(16)
@@ -815,6 +821,25 @@ object MessageCrypto {
} }
} }
/**
* 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) * 🔥 Расшифровывает reply blob из attachments (как в React Native)
* Формат входа: "ivBase64:ciphertextBase64" * Формат входа: "ivBase64:ciphertextBase64"
@@ -842,8 +867,10 @@ object MessageCrypto {
val ciphertext = Base64.decode(parts[1], Base64.DEFAULT) val ciphertext = Base64.decode(parts[1], Base64.DEFAULT)
android.util.Log.d("MessageCrypto", " - IV size: ${iv.size}, Ciphertext size: ${ciphertext.size}") android.util.Log.d("MessageCrypto", " - IV size: ${iv.size}, Ciphertext size: ${ciphertext.size}")
// Password from keyAndNonce // Password from keyAndNonce - use same JS-like UTF-8 conversion
val password = String(plainKeyAndNonce, Charsets.UTF_8) 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 // PBKDF2 key derivation
val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
@@ -855,6 +882,7 @@ object MessageCrypto {
) )
val secretKey = factory.generateSecret(spec) val secretKey = factory.generateSecret(spec)
val keyBytes = secretKey.encoded val keyBytes = secretKey.encoded
android.util.Log.d("MessageCrypto", " - PBKDF2 key derived: ${keyBytes.toHex()}")
// AES-CBC decryption // AES-CBC decryption
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")

View File

@@ -224,11 +224,18 @@ fun ChatDetailScreen(
var showEmojiPicker by remember { mutableStateOf(false) } var showEmojiPicker by remember { mutableStateOf(false) }
val emojiPanelHeight = if (imeHeight > 50.dp) imeHeight else 280.dp 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 { val listBottomPadding = when {
isKeyboardVisible -> 70.dp + imeHeight isKeyboardVisible -> 70.dp + replyPanelHeight + imeHeight
showEmojiPicker -> 70.dp + emojiPanelHeight showEmojiPicker -> 70.dp + replyPanelHeight + emojiPanelHeight
else -> 100.dp else -> 100.dp + replyPanelHeight
} }
// Telegram-style scroll tracking // Telegram-style scroll tracking
@@ -289,10 +296,8 @@ fun ChatDetailScreen(
val isTyping by viewModel.opponentTyping.collectAsState() val isTyping by viewModel.opponentTyping.collectAsState()
val isOnline by viewModel.opponentOnline.collectAsState() val isOnline by viewModel.opponentOnline.collectAsState()
// 🔥 Reply/Forward state // 🔥 Reply/Forward state (replyMessages и hasReply определены выше для listBottomPadding)
val replyMessages by viewModel.replyMessages.collectAsState()
val isForwardMode by viewModel.isForwardMode.collectAsState() val isForwardMode by viewModel.isForwardMode.collectAsState()
val hasReply = replyMessages.isNotEmpty()
// 🔥 Добавляем информацию о датах к сообщениям // 🔥 Добавляем информацию о датах к сообщениям
// В reversed layout (новые внизу) - показываем дату ПОСЛЕ сообщения (визуально сверху) // В reversed layout (новые внизу) - показываем дату ПОСЛЕ сообщения (визуально сверху)

View File

@@ -801,6 +801,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
attachments = messageAttachments 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) ProtocolManager.send(packet)