From ed0e3e354a897648eddcb44688d27d4228a2a717 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:06:53 +0100 Subject: [PATCH] wip --- .../android/mesh/BluetoothMeshService.kt | 13 ++++++++-- .../bitchat/android/mesh/MessageHandler.kt | 24 +++++++++++++++---- .../bitchat/android/mesh/SecurityManager.kt | 15 +++++++++--- .../bitchat/android/sync/GossipSyncManager.kt | 14 +++++++++++ 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt index dd12138d..bddb9cee 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -160,8 +160,8 @@ class BluetoothMeshService(private val context: Context) { } } - // SecurityManager delegate for key exchange notifications - securityManager.delegate = object : SecurityManagerDelegate { + // SecurityManager delegate for key exchange notifications + gossip lookup + securityManager.delegate = object : SecurityManagerDelegate, SecurityManagerGossipLookup { override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray) { // Send announcement and cached messages after key exchange serviceScope.launch { @@ -193,6 +193,11 @@ class BluetoothMeshService(private val context: Context) { override fun getPeerInfo(peerID: String): PeerInfo? { return peerManager.getPeerInfo(peerID) } + + // SecurityManagerGossipLookup + override fun isInGossipSync(packet: BitchatPacket): Boolean { + return try { gossipSyncManager.hasSeenPacket(packet) } catch (_: Exception) { false } + } } // StoreForwardManager delegates @@ -366,6 +371,10 @@ class BluetoothMeshService(private val context: Context) { override fun onReadReceiptReceived(messageID: String, peerID: String) { delegate?.didReceiveReadReceipt(messageID, peerID) } + + override fun isInGossipSync(packet: BitchatPacket): Boolean { + return try { gossipSyncManager.hasSeenPacket(packet) } catch (_: Exception) { false } + } } // PacketProcessor delegates diff --git a/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt b/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt index d016dd37..ea2a92e4 100644 --- a/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt +++ b/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt @@ -41,8 +41,12 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro Log.d(TAG, "Processing Noise encrypted message from $peerID (${packet.payload.size} bytes)") - // Skip our own messages - if (peerID == myPeerID) return + // Skip our own messages, except when coming from sync (TTL=SYNC_TTL_HOPS) and not tracked in gossip + if (peerID == myPeerID) { + val isSyncTtl = packet.ttl == com.bitchat.android.util.AppConstants.SYNC_TTL_HOPS + val seenInGossip = try { delegate?.isInGossipSync(packet) } catch (_: Exception) { null } + if (!(isSyncTtl && (seenInGossip == false))) return + } // Check if this message is for us val recipientID = packet.recipientID?.toHexString() @@ -209,7 +213,12 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro val packet = routed.packet val peerID = routed.peerID ?: "unknown" - if (peerID == myPeerID) return false + // Skip our own announce, except when coming from sync (TTL=SYNC_TTL_HOPS) and not tracked in gossip + if (peerID == myPeerID) { + val isSyncTtl = packet.ttl == com.bitchat.android.util.AppConstants.SYNC_TTL_HOPS + val seenInGossip = try { delegate?.isInGossipSync(packet) } catch (_: Exception) { null } + if (!(isSyncTtl && (seenInGossip == false))) return false + } // Ignore stale announcements older than STALE_PEER_TIMEOUT val now = System.currentTimeMillis() @@ -342,7 +351,11 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro suspend fun handleMessage(routed: RoutedPacket) { val packet = routed.packet val peerID = routed.peerID ?: "unknown" - if (peerID == myPeerID) return + if (peerID == myPeerID) { + val isSyncTtl = packet.ttl == com.bitchat.android.util.AppConstants.SYNC_TTL_HOPS + val seenInGossip = try { delegate?.isInGossipSync(packet) } catch (_: Exception) { null } + if (!(isSyncTtl && (seenInGossip == false))) return + } val senderNickname = delegate?.getPeerNickname(peerID) if (senderNickname != null) { Log.d(TAG, "Received message from $senderNickname") @@ -611,4 +624,7 @@ interface MessageHandlerDelegate { fun onChannelLeave(channel: String, fromPeer: String) fun onDeliveryAckReceived(messageID: String, peerID: String) fun onReadReceiptReceived(messageID: String, peerID: String) + + // Gossip sync visibility + fun isInGossipSync(packet: BitchatPacket): Boolean } diff --git a/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt b/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt index bce69c26..f6626206 100644 --- a/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt @@ -44,10 +44,14 @@ class SecurityManager(private val encryptionService: EncryptionService, private * Validate packet security (timestamp, replay attacks, duplicates, signatures) */ fun validatePacket(packet: BitchatPacket, peerID: String): Boolean { - // Skip validation for our own packets + // Skip our own packets, except allow sync copies of our own packets if (peerID == myPeerID) { - Log.d(TAG, "Skipping validation for our own packet") - return false + val isSyncTtl = packet.ttl == com.bitchat.android.util.AppConstants.SYNC_TTL_HOPS + val seenInGossip = try { delegate?.let { (it as? SecurityManagerGossipLookup)?.isInGossipSync(packet) } } catch (_: Exception) { null } + if (!(isSyncTtl && (seenInGossip == false))) { + Log.d(TAG, "Skipping validation for our own packet (not sync-eligible)") + return false + } } // Replay attack protection (same 5-minute window as iOS) @@ -384,3 +388,8 @@ interface SecurityManagerDelegate { fun sendHandshakeResponse(peerID: String, response: ByteArray) fun getPeerInfo(peerID: String): PeerInfo? // NEW: For signature verification } + +// Optional extension for gossip sync visibility (implemented by host service) +interface SecurityManagerGossipLookup { + fun isInGossipSync(packet: BitchatPacket): Boolean +} diff --git a/app/src/main/java/com/bitchat/android/sync/GossipSyncManager.kt b/app/src/main/java/com/bitchat/android/sync/GossipSyncManager.kt index 6e29aa79..713a693e 100644 --- a/app/src/main/java/com/bitchat/android/sync/GossipSyncManager.kt +++ b/app/src/main/java/com/bitchat/android/sync/GossipSyncManager.kt @@ -133,6 +133,20 @@ class GossipSyncManager( } } + // Check if we've already tracked this packet in our gossip sync state + fun hasSeenPacket(packet: BitchatPacket): Boolean { + return try { + val idBytes = PacketIdUtil.computeIdBytes(packet) + val id = idBytes.joinToString("") { b -> "%02x".format(b) } + // Check broadcast messages cache + synchronized(messages) { + if (messages.containsKey(id)) return true + } + // Check announcements cache (latest by peer keeps id hex) + latestAnnouncementByPeer.values.any { pair -> pair.first.equals(id, ignoreCase = true) } + } catch (_: Exception) { false } + } + private fun sendRequestSync() { val payload = buildGcsPayload()