mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-26 03:25:21 +00:00
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:
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user