Changes bitchat protocol (#265)

* create payload

* compiles and can send messages

* identityannouncement

* DMs work, read receipt not sent yet

* works

* delete old code

* simplify

* working

* fragment wip

* compression wip

* use zlib compression

* clean

* nice

* mesh

* remove comments
This commit is contained in:
callebtc
2025-08-18 21:16:27 +02:00
committed by GitHub
parent b86f2cdb11
commit 9795e2ce8a
25 changed files with 1533 additions and 1819 deletions
@@ -119,20 +119,7 @@ class ChannelManager(
// MARK: - Channel Password and Encryption
private fun verifyChannelPassword(channel: String, password: String): Boolean {
val key = deriveChannelKey(password, channel)
// Verify against existing messages if available
val existingMessages = state.getChannelMessagesValue()[channel]?.filter { it.isEncrypted }
if (!existingMessages.isNullOrEmpty()) {
val testMessage = existingMessages.first()
val decryptedContent = decryptChannelMessage(testMessage.encryptedContent ?: byteArrayOf(), channel, key)
if (decryptedContent == null) {
return false
}
}
channelKeys[channel] = key
channelPasswords[channel] = password
// TODO: REMOVE THIS - FOR TESTING ONLY
return true
}
@@ -183,48 +170,8 @@ class ChannelManager(
onEncryptedPayload: (ByteArray) -> Unit,
onFallback: () -> Unit
) {
val key = channelKeys[channel]
if (key == null) {
onFallback()
return
}
coroutineScope.launch {
try {
val contentBytes = content.toByteArray(Charsets.UTF_8)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, key)
val iv = cipher.iv
val encryptedData = cipher.doFinal(contentBytes)
// Combine IV and encrypted data
val combined = ByteArray(iv.size + encryptedData.size)
System.arraycopy(iv, 0, combined, 0, iv.size)
System.arraycopy(encryptedData, 0, combined, iv.size, encryptedData.size)
val encryptedMessage = BitchatMessage(
sender = senderNickname ?: myPeerID,
content = "",
timestamp = Date(),
isRelay = false,
senderPeerID = myPeerID,
mentions = if (mentions.isNotEmpty()) mentions else null,
channel = channel,
encryptedContent = combined,
isEncrypted = true
)
// Send encrypted message via mesh
encryptedMessage.toBinaryPayload()?.let { messageData ->
onEncryptedPayload(messageData)
} ?: onFallback()
} catch (e: Exception) {
// Fallback to unencrypted
onFallback()
}
}
// TODO: REIMPLEMENT REMOVED FOR NOW
return
}
// MARK: - Channel Management
@@ -9,8 +9,7 @@ import androidx.lifecycle.viewModelScope
import com.bitchat.android.mesh.BluetoothMeshDelegate
import com.bitchat.android.mesh.BluetoothMeshService
import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.model.DeliveryAck
import com.bitchat.android.model.ReadReceipt
import com.bitchat.android.protocol.BitchatPacket
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
import java.util.*
@@ -41,8 +40,6 @@ class ChatViewModel(
private val noiseSessionDelegate = object : NoiseSessionDelegate {
override fun hasEstablishedSession(peerID: String): Boolean = meshService.hasEstablishedSession(peerID)
override fun initiateHandshake(peerID: String) = meshService.initiateNoiseHandshake(peerID)
override fun broadcastNoiseIdentityAnnouncement() = meshService.broadcastNoiseIdentityAnnouncement()
override fun sendHandshakeRequest(targetPeerID: String, pendingCount: UByte) = meshService.sendHandshakeRequest(targetPeerID, pendingCount)
override fun getMyPeerID(): String = meshService.myPeerID
}
@@ -384,12 +381,12 @@ class ChatViewModel(
meshDelegateHandler.didReceiveChannelLeave(channel, fromPeer)
}
override fun didReceiveDeliveryAck(ack: DeliveryAck) {
meshDelegateHandler.didReceiveDeliveryAck(ack)
override fun didReceiveDeliveryAck(messageID: String, recipientPeerID: String) {
meshDelegateHandler.didReceiveDeliveryAck(messageID, recipientPeerID)
}
override fun didReceiveReadReceipt(receipt: ReadReceipt) {
meshDelegateHandler.didReceiveReadReceipt(receipt)
override fun didReceiveReadReceipt(messageID: String, recipientPeerID: String) {
meshDelegateHandler.didReceiveReadReceipt(messageID, recipientPeerID)
}
override fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? {
@@ -1,5 +1,6 @@
package com.bitchat.android.ui
import com.bitchat.android.mesh.BluetoothMeshService
import com.bitchat.android.model.BitchatMessage
import java.util.*
@@ -28,7 +29,7 @@ class CommandProcessor(
// MARK: - Command Processing
fun processCommand(command: String, meshService: Any, myPeerID: String, onSendMessage: (String, List<String>, String?) -> Unit): Boolean {
fun processCommand(command: String, meshService: BluetoothMeshService, myPeerID: String, onSendMessage: (String, List<String>, String?) -> Unit): Boolean {
if (!command.startsWith("/")) return false
val parts = command.split(" ")
@@ -77,7 +78,7 @@ class CommandProcessor(
}
}
private fun handleMessageCommand(parts: List<String>, meshService: Any) {
private fun handleMessageCommand(parts: List<String>, meshService: BluetoothMeshService) {
if (parts.size > 1) {
val targetName = parts[1].removePrefix("@")
val peerID = getPeerIDForNickname(targetName, meshService)
@@ -129,7 +130,7 @@ class CommandProcessor(
}
}
private fun handleWhoCommand(meshService: Any) {
private fun handleWhoCommand(meshService: BluetoothMeshService) {
val connectedPeers = state.getConnectedPeersValue()
val peerList = connectedPeers.joinToString(", ") { peerID ->
// Convert peerID to nickname using the mesh service
@@ -214,7 +215,7 @@ class CommandProcessor(
}
}
private fun handleBlockCommand(parts: List<String>, meshService: Any) {
private fun handleBlockCommand(parts: List<String>, meshService: BluetoothMeshService) {
if (parts.size > 1) {
val targetName = parts[1].removePrefix("@")
privateChatManager.blockPeerByNickname(targetName, meshService)
@@ -231,7 +232,7 @@ class CommandProcessor(
}
}
private fun handleUnblockCommand(parts: List<String>, meshService: Any) {
private fun handleUnblockCommand(parts: List<String>, meshService: BluetoothMeshService) {
if (parts.size > 1) {
val targetName = parts[1].removePrefix("@")
privateChatManager.unblockPeerByNickname(targetName, meshService)
@@ -250,7 +251,7 @@ class CommandProcessor(
parts: List<String>,
verb: String,
object_: String,
meshService: Any,
meshService: BluetoothMeshService,
myPeerID: String,
onSendMessage: (String, List<String>, String?) -> Unit
) {
@@ -382,7 +383,7 @@ class CommandProcessor(
// MARK: - Mention Autocomplete
fun updateMentionSuggestions(input: String, meshService: Any) {
fun updateMentionSuggestions(input: String, meshService: BluetoothMeshService) {
// Check if input contains @ and we're at the end of a word or at the end of input
val atIndex = input.lastIndexOf('@')
if (atIndex == -1) {
@@ -401,14 +402,8 @@ class CommandProcessor(
return
}
// Get all connected peer nicknames
val peerNicknames = try {
val method = meshService::class.java.getDeclaredMethod("getPeerNicknames")
val peerNicknamesMap = method.invoke(meshService) as? Map<String, String>
peerNicknamesMap?.values?.toList() ?: emptyList()
} catch (e: Exception) {
emptyList()
}
// Get all connected peer nicknames - now using direct access instead of reflection
val peerNicknames = meshService.getPeerNicknames().values.toList()
// Filter nicknames based on the text after @
val filteredNicknames = peerNicknames.filter { nickname ->
@@ -439,50 +434,21 @@ class CommandProcessor(
return "$textBeforeAt@$nickname "
}
// MARK: - Utility Functions (would access mesh service)
// MARK: - Utility Functions
private fun getPeerIDForNickname(nickname: String, meshService: Any): String? {
return try {
val method = meshService::class.java.getDeclaredMethod("getPeerNicknames")
val peerNicknames = method.invoke(meshService) as? Map<String, String>
peerNicknames?.entries?.find { it.value == nickname }?.key
} catch (e: Exception) {
null
}
private fun getPeerIDForNickname(nickname: String, meshService: BluetoothMeshService): String? {
return meshService.getPeerNicknames().entries.find { it.value == nickname }?.key
}
private fun getPeerNickname(peerID: String, meshService: Any): String {
return try {
val method = meshService::class.java.getDeclaredMethod("getPeerNicknames")
val peerNicknames = method.invoke(meshService) as? Map<String, String>
peerNicknames?.get(peerID) ?: peerID
} catch (e: Exception) {
peerID
}
private fun getPeerNickname(peerID: String, meshService: BluetoothMeshService): String {
return meshService.getPeerNicknames()[peerID] ?: peerID
}
private fun getMyPeerID(meshService: Any): String {
return try {
val field = meshService::class.java.getDeclaredField("myPeerID")
field.isAccessible = true
field.get(meshService) as? String ?: "unknown"
} catch (e: Exception) {
"unknown"
}
private fun getMyPeerID(meshService: BluetoothMeshService): String {
return meshService.myPeerID
}
private fun sendPrivateMessageVia(meshService: Any, content: String, peerID: String, recipientNickname: String, messageId: String) {
try {
val method = meshService::class.java.getDeclaredMethod(
"sendPrivateMessage",
String::class.java,
String::class.java,
String::class.java,
String::class.java
)
method.invoke(meshService, content, peerID, recipientNickname, messageId)
} catch (e: Exception) {
// Handle error
}
private fun sendPrivateMessageVia(meshService: BluetoothMeshService, content: String, peerID: String, recipientNickname: String, messageId: String) {
meshService.sendPrivateMessage(content, peerID, recipientNickname, messageId)
}
}
@@ -2,10 +2,9 @@ package com.bitchat.android.ui
import androidx.lifecycle.LifecycleCoroutineScope
import com.bitchat.android.mesh.BluetoothMeshDelegate
import com.bitchat.android.mesh.BluetoothMeshService
import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.model.DeliveryAck
import com.bitchat.android.model.DeliveryStatus
import com.bitchat.android.model.ReadReceipt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.util.*
@@ -22,7 +21,7 @@ class MeshDelegateHandler(
private val coroutineScope: CoroutineScope,
private val onHapticFeedback: () -> Unit,
private val getMyPeerID: () -> String,
private val getMeshService: () -> Any
private val getMeshService: () -> BluetoothMeshService
) : BluetoothMeshDelegate {
override fun didReceiveMessage(message: BitchatMessage) {
@@ -103,15 +102,15 @@ class MeshDelegateHandler(
}
}
override fun didReceiveDeliveryAck(ack: DeliveryAck) {
override fun didReceiveDeliveryAck(messageID: String, recipientPeerID: String) {
coroutineScope.launch {
messageManager.updateMessageDeliveryStatus(ack.originalMessageID, DeliveryStatus.Delivered(ack.recipientNickname, ack.timestamp))
messageManager.updateMessageDeliveryStatus(messageID, DeliveryStatus.Delivered(recipientPeerID, Date()))
}
}
override fun didReceiveReadReceipt(receipt: ReadReceipt) {
override fun didReceiveReadReceipt(messageID: String, recipientPeerID: String) {
coroutineScope.launch {
messageManager.updateMessageDeliveryStatus(receipt.originalMessageID, DeliveryStatus.Read(receipt.readerNickname, receipt.timestamp))
messageManager.updateMessageDeliveryStatus(messageID, DeliveryStatus.Read(recipientPeerID, Date()))
}
}
@@ -3,6 +3,7 @@ package com.bitchat.android.ui
import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.model.DeliveryStatus
import com.bitchat.android.mesh.PeerFingerprintManager
import com.bitchat.android.mesh.BluetoothMeshService
import java.util.*
import android.util.Log
@@ -13,8 +14,6 @@ import android.util.Log
interface NoiseSessionDelegate {
fun hasEstablishedSession(peerID: String): Boolean
fun initiateHandshake(peerID: String)
fun broadcastNoiseIdentityAnnouncement()
fun sendHandshakeRequest(targetPeerID: String, pendingCount: UByte)
fun getMyPeerID(): String
}
@@ -26,22 +25,22 @@ class PrivateChatManager(
private val state: ChatState,
private val messageManager: MessageManager,
private val dataManager: DataManager,
private val noiseSessionDelegate: NoiseSessionDelegate? = null
private val noiseSessionDelegate: NoiseSessionDelegate
) {
companion object {
private const val TAG = "PrivateChatManager"
}
// Use centralized fingerprint management - NO LOCAL STORAGE
private val fingerprintManager = PeerFingerprintManager.getInstance()
// Track received private messages that need read receipts
private val unreadReceivedMessages = mutableMapOf<String, MutableList<BitchatMessage>>()
// MARK: - Private Chat Lifecycle
fun startPrivateChat(peerID: String, meshService: Any): Boolean {
fun startPrivateChat(peerID: String, meshService: BluetoothMeshService): Boolean {
if (isPeerBlocked(peerID)) {
val peerNickname = getPeerNickname(peerID, meshService)
val systemMessage = BitchatMessage(
@@ -53,28 +52,28 @@ class PrivateChatManager(
messageManager.addMessage(systemMessage)
return false
}
// Establish Noise session if needed before starting the chat
establishNoiseSessionIfNeeded(peerID, meshService)
state.setSelectedPrivateChatPeer(peerID)
// Clear unread
messageManager.clearPrivateUnreadMessages(peerID)
// Initialize chat if needed
messageManager.initializePrivateChat(peerID)
// Send read receipts for all unread messages from this peer
sendReadReceiptsForPeer(peerID, meshService)
return true
}
fun endPrivateChat() {
state.setSelectedPrivateChatPeer(null)
}
fun sendPrivateMessage(
content: String,
peerID: String,
@@ -93,7 +92,7 @@ class PrivateChatManager(
messageManager.addMessage(systemMessage)
return false
}
val message = BitchatMessage(
sender = senderNickname ?: myPeerID,
content = content,
@@ -104,31 +103,31 @@ class PrivateChatManager(
senderPeerID = myPeerID,
deliveryStatus = DeliveryStatus.Sending
)
messageManager.addPrivateMessage(peerID, message)
onSendMessage(content, peerID, recipientNickname ?: "", message.id)
return true
}
// MARK: - Peer Management
fun isPeerBlocked(peerID: String): Boolean {
val fingerprint = fingerprintManager.getFingerprintForPeer(peerID)
return fingerprint != null && dataManager.isUserBlocked(fingerprint)
}
fun toggleFavorite(peerID: String) {
val fingerprint = fingerprintManager.getFingerprintForPeer(peerID) ?: return
Log.d(TAG, "toggleFavorite called for peerID: $peerID, fingerprint: $fingerprint")
val wasFavorite = dataManager.isFavorite(fingerprint)
Log.d(TAG, "Current favorite status: $wasFavorite")
val currentFavorites = state.getFavoritePeersValue()
Log.d(TAG, "Current UI state favorites: $currentFavorites")
if (wasFavorite) {
dataManager.removeFavorite(fingerprint)
Log.d(TAG, "Removed from favorites: $fingerprint")
@@ -136,37 +135,37 @@ class PrivateChatManager(
dataManager.addFavorite(fingerprint)
Log.d(TAG, "Added to favorites: $fingerprint")
}
// Always update state to trigger UI refresh - create new set to ensure change detection
val newFavorites = dataManager.favoritePeers.toSet()
state.setFavoritePeers(newFavorites)
Log.d(TAG, "Force updated favorite peers state. New favorites: $newFavorites")
Log.d(TAG, "All peer fingerprints: ${fingerprintManager.getAllPeerFingerprints()}")
}
fun isFavorite(peerID: String): Boolean {
val fingerprint = fingerprintManager.getFingerprintForPeer(peerID) ?: return false
val isFav = dataManager.isFavorite(fingerprint)
Log.d(TAG, "isFavorite check: peerID=$peerID, fingerprint=$fingerprint, result=$isFav")
return isFav
}
fun getPeerFingerprint(peerID: String): String? {
return fingerprintManager.getFingerprintForPeer(peerID)
}
fun getPeerFingerprints(): Map<String, String> {
return fingerprintManager.getAllPeerFingerprints()
}
// MARK: - Block/Unblock Operations
fun blockPeer(peerID: String, meshService: Any): Boolean {
fun blockPeer(peerID: String, meshService: BluetoothMeshService): Boolean {
val fingerprint = fingerprintManager.getFingerprintForPeer(peerID)
if (fingerprint != null) {
dataManager.addBlockedUser(fingerprint)
val peerNickname = getPeerNickname(peerID, meshService)
val systemMessage = BitchatMessage(
sender = "system",
@@ -175,22 +174,22 @@ class PrivateChatManager(
isRelay = false
)
messageManager.addMessage(systemMessage)
// End private chat if currently in one with this peer
if (state.getSelectedPrivateChatPeerValue() == peerID) {
endPrivateChat()
}
return true
}
return false
}
fun unblockPeer(peerID: String, meshService: Any): Boolean {
fun unblockPeer(peerID: String, meshService: BluetoothMeshService): Boolean {
val fingerprint = fingerprintManager.getFingerprintForPeer(peerID)
if (fingerprint != null && dataManager.isUserBlocked(fingerprint)) {
dataManager.removeBlockedUser(fingerprint)
val peerNickname = getPeerNickname(peerID, meshService)
val systemMessage = BitchatMessage(
sender = "system",
@@ -203,10 +202,10 @@ class PrivateChatManager(
}
return false
}
fun blockPeerByNickname(targetName: String, meshService: Any): Boolean {
fun blockPeerByNickname(targetName: String, meshService: BluetoothMeshService): Boolean {
val peerID = getPeerIDForNickname(targetName, meshService)
if (peerID != null) {
return blockPeer(peerID, meshService)
} else {
@@ -220,10 +219,10 @@ class PrivateChatManager(
return false
}
}
fun unblockPeerByNickname(targetName: String, meshService: Any): Boolean {
fun unblockPeerByNickname(targetName: String, meshService: BluetoothMeshService): Boolean {
val peerID = getPeerIDForNickname(targetName, meshService)
if (peerID != null) {
val fingerprint = fingerprintManager.getFingerprintForPeer(peerID)
if (fingerprint != null && dataManager.isUserBlocked(fingerprint)) {
@@ -249,7 +248,7 @@ class PrivateChatManager(
return false
}
}
fun listBlockedUsers(): String {
val blockedCount = dataManager.blockedUsers.size
return if (blockedCount == 0) {
@@ -258,136 +257,137 @@ class PrivateChatManager(
"blocked users: $blockedCount fingerprints"
}
}
// MARK: - Message Handling
fun handleIncomingPrivateMessage(message: BitchatMessage) {
message.senderPeerID?.let { senderPeerID ->
if (!isPeerBlocked(senderPeerID)) {
// Add to private messages
messageManager.addPrivateMessage(senderPeerID, message)
// Track as unread for read receipt purposes
val unreadList = unreadReceivedMessages.getOrPut(senderPeerID) { mutableListOf() }
unreadList.add(message)
Log.d(TAG, "Added received message ${message.id} from $senderPeerID to unread list (${unreadList.size} unread)")
Log.d(
TAG,
"Added received message ${message.id} from $senderPeerID to unread list (${unreadList.size} unread)"
)
}
}
}
/**
* Send read receipts for all unread messages from a specific peer
* Called when the user focuses on a private chat
*/
fun sendReadReceiptsForPeer(peerID: String, meshService: Any) {
fun sendReadReceiptsForPeer(peerID: String, meshService: BluetoothMeshService) {
val unreadList = unreadReceivedMessages[peerID]
if (unreadList.isNullOrEmpty()) {
Log.d(TAG, "No unread messages to send read receipts for peer $peerID")
return
}
Log.d(TAG, "Sending read receipts for ${unreadList.size} unread messages from $peerID")
// Send read receipt for each unread message
// Send read receipt for each unread message - now using direct method call
unreadList.forEach { message ->
try {
val method = meshService::class.java.getDeclaredMethod("sendReadReceipt", String::class.java, String::class.java, String::class.java)
val myNickname = state.getNicknameValue() ?: "unknown"
method.invoke(meshService, message.id, peerID, myNickname)
meshService.sendReadReceipt(message.id, peerID, myNickname)
Log.d(TAG, "Sent read receipt for message ${message.id} to $peerID")
} catch (e: Exception) {
Log.w(TAG, "Failed to send read receipt for message ${message.id}: ${e.message}")
}
}
// Clear the unread list since we've sent read receipts
unreadReceivedMessages.remove(peerID)
}
fun cleanupDisconnectedPeer(peerID: String) {
// End private chat if peer disconnected
if (state.getSelectedPrivateChatPeerValue() == peerID) {
endPrivateChat()
}
// Clean up unread messages for disconnected peer
unreadReceivedMessages.remove(peerID)
Log.d(TAG, "Cleaned up unread messages for disconnected peer $peerID")
}
// MARK: - Noise Session Management
/**
* Establish Noise session if needed before starting private chat
* Uses same lexicographical logic as MessageHandler.handleNoiseIdentityAnnouncement
*/
private fun establishNoiseSessionIfNeeded(peerID: String, meshService: Any) {
// If we have a clean delegate, use it; otherwise fall back to reflection for backward compatibility
if (noiseSessionDelegate != null) {
if (noiseSessionDelegate.hasEstablishedSession(peerID)) {
Log.d(TAG, "Noise session already established with $peerID")
return
}
Log.d(TAG, "No Noise session with $peerID, determining who should initiate handshake")
val myPeerID = noiseSessionDelegate.getMyPeerID()
// Use lexicographical comparison to decide who initiates (same logic as MessageHandler)
if (myPeerID < peerID) {
// We should initiate the handshake
Log.d(TAG, "Our peer ID lexicographically < target peer ID, initiating Noise handshake with $peerID")
noiseSessionDelegate.initiateHandshake(peerID)
} else {
// They should initiate, we send a Noise identity announcement
Log.d(TAG, "Our peer ID lexicographically >= target peer ID, sending Noise identity announcement to prompt handshake from $peerID")
noiseSessionDelegate.broadcastNoiseIdentityAnnouncement()
// Also send handshake request to this peer
noiseSessionDelegate.sendHandshakeRequest(peerID, 1u) // 1 pending message (the chat we're trying to start)
}
private fun establishNoiseSessionIfNeeded(peerID: String, meshService: BluetoothMeshService) {
if (noiseSessionDelegate.hasEstablishedSession(peerID)) {
Log.d(TAG, "Noise session already established with $peerID")
return
}
Log.d(TAG, "No Noise session with $peerID, determining who should initiate handshake")
val myPeerID = noiseSessionDelegate.getMyPeerID()
// Use lexicographical comparison to decide who initiates (same logic as MessageHandler)
if (myPeerID < peerID) {
// We should initiate the handshake
Log.d(
TAG,
"Our peer ID lexicographically < target peer ID, initiating Noise handshake with $peerID"
)
noiseSessionDelegate.initiateHandshake(peerID)
} else {
// Fallback to reflection-based approach for backward compatibility
establishNoiseSessionIfNeededLegacy(peerID, meshService)
// They should initiate, we send identity announcement through standard announce
Log.d(
TAG,
"Our peer ID lexicographically >= target peer ID, sending identity announcement to prompt handshake from $peerID"
)
meshService.sendAnnouncementToPeer(peerID)
}
}
/**
* Legacy reflection-based implementation for backward compatibility
*/
private fun establishNoiseSessionIfNeededLegacy(peerID: String, meshService: Any) {
try {
// Check if we already have an established Noise session with this peer
val hasSessionMethod = meshService::class.java.getDeclaredMethod("hasEstablishedSession", String::class.java)
val hasSession = hasSessionMethod.invoke(meshService, peerID) as Boolean
if (hasSession) {
Log.d(TAG, "Noise session already established with $peerID")
return
}
Log.d(TAG, "No Noise session with $peerID, determining who should initiate handshake")
// Get our peer ID from mesh service for lexicographical comparison
val myPeerIDField = meshService::class.java.getField("myPeerID")
val myPeerID = myPeerIDField.get(meshService) as String
// Use lexicographical comparison to decide who initiates (same logic as MessageHandler)
if (myPeerID < peerID) {
// We should initiate the handshake
Log.d(TAG, "Our peer ID lexicographically < target peer ID, initiating Noise handshake with $peerID")
initiateHandshakeWithPeer(peerID, meshService)
} else {
// They should initiate, we send a Noise identity announcement
Log.d(TAG, "Our peer ID lexicographically >= target peer ID, sending Noise identity announcement to prompt handshake from $peerID")
sendNoiseIdentityAnnouncement(meshService)
}
} catch (e: Exception) {
Log.e(TAG, "Failed to establish Noise session with $peerID: ${e.message}")
}
}
// /**
// * Legacy reflection-based implementation for backward compatibility
// */
// private fun establishNoiseSessionIfNeededLegacy(peerID: String, meshService: Any) {
// try {
// // Check if we already have an established Noise session with this peer
// val hasSessionMethod = meshService::class.java.getDeclaredMethod("hasEstablishedSession", String::class.java)
// val hasSession = hasSessionMethod.invoke(meshService, peerID) as Boolean
//
// if (hasSession) {
// Log.d(TAG, "Noise session already established with $peerID")
// return
// }
//
// Log.d(TAG, "No Noise session with $peerID, determining who should initiate handshake")
//
// // Get our peer ID from mesh service for lexicographical comparison
// val myPeerIDField = meshService::class.java.getField("myPeerID")
// val myPeerID = myPeerIDField.get(meshService) as String
//
// // Use lexicographical comparison to decide who initiates (same logic as MessageHandler)
// if (myPeerID < peerID) {
// // We should initiate the handshake
// Log.d(TAG, "Our peer ID lexicographically < target peer ID, initiating Noise handshake with $peerID")
// initiateHandshakeWithPeer(peerID, meshService)
// } else {
// // They should initiate, we send a Noise identity announcement
// Log.d(TAG, "Our peer ID lexicographically >= target peer ID, sending Noise identity announcement to prompt handshake from $peerID")
// sendNoiseIdentityAnnouncement(meshService)
// }
//
// } catch (e: Exception) {
// Log.e(TAG, "Failed to establish Noise session with $peerID: ${e.message}")
// }
// }
/**
* Initiate handshake with specific peer using the existing delegate pattern
*/
@@ -398,20 +398,21 @@ class PrivateChatManager(
val messageHandler = meshService::class.java.getDeclaredField("messageHandler")
messageHandler.isAccessible = true
val handler = messageHandler.get(meshService)
val delegate = handler::class.java.getDeclaredField("delegate")
delegate.isAccessible = true
val handlerDelegate = delegate.get(handler)
val method = handlerDelegate::class.java.getMethod("initiateNoiseHandshake", String::class.java)
val method =
handlerDelegate::class.java.getMethod("initiateNoiseHandshake", String::class.java)
method.invoke(handlerDelegate, peerID)
Log.d(TAG, "Successfully initiated Noise handshake with $peerID using delegate pattern")
} catch (e: Exception) {
Log.e(TAG, "Failed to initiate Noise handshake with $peerID: ${e.message}")
}
}
/**
* Send Noise identity announcement to prompt other peer to initiate handshake
* This follows the same pattern as broadcastNoiseIdentityAnnouncement() in BluetoothMeshService
@@ -419,53 +420,40 @@ class PrivateChatManager(
private fun sendNoiseIdentityAnnouncement(meshService: Any) {
try {
// Call broadcastNoiseIdentityAnnouncement which sends a NoiseIdentityAnnouncement
val method = meshService::class.java.getDeclaredMethod("broadcastNoiseIdentityAnnouncement")
val method =
meshService::class.java.getDeclaredMethod("broadcastNoiseIdentityAnnouncement")
method.invoke(meshService)
Log.d(TAG, "Successfully sent Noise identity announcement")
} catch (e: Exception) {
Log.e(TAG, "Failed to send Noise identity announcement: ${e.message}")
}
}
// MARK: - Utility Functions
private fun getPeerIDForNickname(nickname: String, meshService: Any): String? {
// This would need to access the mesh service to get peer nicknames
// For now, we'll assume the mesh service provides a way to get this mapping
return try {
val method = meshService::class.java.getDeclaredMethod("getPeerNicknames")
val peerNicknames = method.invoke(meshService) as? Map<String, String>
peerNicknames?.entries?.find { it.value == nickname }?.key
} catch (e: Exception) {
null
}
private fun getPeerIDForNickname(nickname: String, meshService: BluetoothMeshService): String? {
return meshService.getPeerNicknames().entries.find { it.value == nickname }?.key
}
private fun getPeerNickname(peerID: String, meshService: Any): String {
return try {
val method = meshService::class.java.getDeclaredMethod("getPeerNicknames")
val peerNicknames = method.invoke(meshService) as? Map<String, String>
peerNicknames?.get(peerID) ?: peerID
} catch (e: Exception) {
peerID
}
private fun getPeerNickname(peerID: String, meshService: BluetoothMeshService): String {
return meshService.getPeerNicknames()[peerID] ?: peerID
}
// MARK: - Emergency Clear
fun clearAllPrivateChats() {
state.setSelectedPrivateChatPeer(null)
state.setUnreadPrivateMessages(emptySet())
// Clear unread messages tracking
unreadReceivedMessages.clear()
// Clear fingerprints via centralized manager (only if needed for emergency clear)
// Note: This will be handled by the parent PeerManager.clearAllPeers()
}
// MARK: - Public Getters
fun getAllPeerFingerprints(): Map<String, String> {
return fingerprintManager.getAllPeerFingerprints()
}