This commit is contained in:
callebtc
2025-11-04 11:06:53 +01:00
parent 282ac0fe5a
commit ed0e3e354a
4 changed files with 57 additions and 9 deletions
@@ -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
@@ -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
}
@@ -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
}
@@ -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()