mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-24 23:45:19 +00:00
Remove ghost sync (#468)
* delete stale peers and messages from sync manager * ignore old announcenements
This commit is contained in:
@@ -210,6 +210,14 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro
|
|||||||
val peerID = routed.peerID ?: "unknown"
|
val peerID = routed.peerID ?: "unknown"
|
||||||
|
|
||||||
if (peerID == myPeerID) return false
|
if (peerID == myPeerID) return false
|
||||||
|
|
||||||
|
// Ignore stale announcements older than STALE_PEER_TIMEOUT
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val age = now - packet.timestamp.toLong()
|
||||||
|
if (age > com.bitchat.android.util.AppConstants.STALE_PEER_TIMEOUT_MS) {
|
||||||
|
Log.w(TAG, "Ignoring stale ANNOUNCE from ${peerID.take(8)} (age=${age}ms > ${com.bitchat.android.util.AppConstants.STALE_PEER_TIMEOUT_MS}ms)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Try to decode as iOS-compatible IdentityAnnouncement with TLV format
|
// Try to decode as iOS-compatible IdentityAnnouncement with TLV format
|
||||||
val announcement = IdentityAnnouncement.decode(packet.payload)
|
val announcement = IdentityAnnouncement.decode(packet.payload)
|
||||||
|
|||||||
@@ -67,9 +67,11 @@ class PeerManager {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "PeerManager"
|
private const val TAG = "PeerManager"
|
||||||
private const val STALE_PEER_TIMEOUT = 180000L // 3 minutes (same as iOS)
|
|
||||||
private const val CLEANUP_INTERVAL = 60000L // 1 minute
|
private const val CLEANUP_INTERVAL = 60000L // 1 minute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Centralized timeout from AppConstants
|
||||||
|
private val stalePeerTimeoutMs: Long = com.bitchat.android.util.AppConstants.STALE_PEER_TIMEOUT_MS
|
||||||
|
|
||||||
// Peer tracking data - enhanced with verification status
|
// Peer tracking data - enhanced with verification status
|
||||||
private val peers = ConcurrentHashMap<String, PeerInfo>() // peerID -> PeerInfo
|
private val peers = ConcurrentHashMap<String, PeerInfo>() // peerID -> PeerInfo
|
||||||
@@ -299,7 +301,7 @@ class PeerManager {
|
|||||||
fun isPeerActive(peerID: String): Boolean {
|
fun isPeerActive(peerID: String): Boolean {
|
||||||
val info = peers[peerID] ?: return false
|
val info = peers[peerID] ?: return false
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
return (now - info.lastSeen) <= STALE_PEER_TIMEOUT && info.isConnected
|
return (now - info.lastSeen) <= stalePeerTimeoutMs && info.isConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -328,7 +330,7 @@ class PeerManager {
|
|||||||
*/
|
*/
|
||||||
fun getActivePeerIDs(): List<String> {
|
fun getActivePeerIDs(): List<String> {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
return peers.filterValues { (now - it.lastSeen) <= STALE_PEER_TIMEOUT && it.isConnected }
|
return peers.filterValues { (now - it.lastSeen) <= stalePeerTimeoutMs && it.isConnected }
|
||||||
.keys
|
.keys
|
||||||
.toList()
|
.toList()
|
||||||
.sorted()
|
.sorted()
|
||||||
@@ -426,7 +428,7 @@ class PeerManager {
|
|||||||
private fun cleanupStalePeers() {
|
private fun cleanupStalePeers() {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
val peersToRemove = peers.filterValues { (now - it.lastSeen) > STALE_PEER_TIMEOUT }
|
val peersToRemove = peers.filterValues { (now - it.lastSeen) > stalePeerTimeoutMs }
|
||||||
.keys
|
.keys
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ class GossipSyncManager(
|
|||||||
fun gcsTargetFpr(): Double // percent -> 0.0..1.0
|
fun gcsTargetFpr(): Double // percent -> 0.0..1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object { private const val TAG = "GossipSyncManager" }
|
companion object {
|
||||||
|
private const val TAG = "GossipSyncManager"
|
||||||
|
private const val CLEANUP_INTERVAL = 60_000L // 1 minute
|
||||||
|
}
|
||||||
|
|
||||||
var delegate: Delegate? = null
|
var delegate: Delegate? = null
|
||||||
|
|
||||||
@@ -46,6 +49,7 @@ class GossipSyncManager(
|
|||||||
private val latestAnnouncementByPeer = ConcurrentHashMap<String, Pair<String, BitchatPacket>>()
|
private val latestAnnouncementByPeer = ConcurrentHashMap<String, Pair<String, BitchatPacket>>()
|
||||||
|
|
||||||
private var periodicJob: Job? = null
|
private var periodicJob: Job? = null
|
||||||
|
private var cleanupJob: Job? = null
|
||||||
fun start() {
|
fun start() {
|
||||||
periodicJob?.cancel()
|
periodicJob?.cancel()
|
||||||
periodicJob = scope.launch(Dispatchers.IO) {
|
periodicJob = scope.launch(Dispatchers.IO) {
|
||||||
@@ -57,10 +61,23 @@ class GossipSyncManager(
|
|||||||
catch (e: Exception) { Log.e(TAG, "Periodic sync error: ${e.message}") }
|
catch (e: Exception) { Log.e(TAG, "Periodic sync error: ${e.message}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start periodic cleanup of stale announcements and messages
|
||||||
|
cleanupJob?.cancel()
|
||||||
|
cleanupJob = scope.launch(Dispatchers.IO) {
|
||||||
|
while (isActive) {
|
||||||
|
try {
|
||||||
|
delay(CLEANUP_INTERVAL)
|
||||||
|
pruneStaleAnnouncements()
|
||||||
|
} catch (e: CancellationException) { throw e }
|
||||||
|
catch (e: Exception) { Log.e(TAG, "Periodic cleanup error: ${e.message}") }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
periodicJob?.cancel(); periodicJob = null
|
periodicJob?.cancel(); periodicJob = null
|
||||||
|
cleanupJob?.cancel(); cleanupJob = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scheduleInitialSync(delayMs: Long = 5_000L) {
|
fun scheduleInitialSync(delayMs: Long = 5_000L) {
|
||||||
@@ -98,6 +115,13 @@ class GossipSyncManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (isAnnouncement) {
|
} else if (isAnnouncement) {
|
||||||
|
// Ignore stale announcements older than STALE_PEER_TIMEOUT
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val age = now - packet.timestamp.toLong()
|
||||||
|
if (age > com.bitchat.android.util.AppConstants.STALE_PEER_TIMEOUT_MS) {
|
||||||
|
Log.d(TAG, "Ignoring stale ANNOUNCE (age=${age}ms > ${com.bitchat.android.util.AppConstants.STALE_PEER_TIMEOUT_MS}ms)")
|
||||||
|
return
|
||||||
|
}
|
||||||
// senderID is fixed-size 8 bytes; map to hex string for key
|
// senderID is fixed-size 8 bytes; map to hex string for key
|
||||||
val sender = packet.senderID.joinToString("") { b -> "%02x".format(b) }
|
val sender = packet.senderID.joinToString("") { b -> "%02x".format(b) }
|
||||||
latestAnnouncementByPeer[sender] = id to packet
|
latestAnnouncementByPeer[sender] = id to packet
|
||||||
@@ -235,6 +259,42 @@ class GossipSyncManager(
|
|||||||
return RequestSyncPacket(p = params.p, m = mVal, data = params.data).encode()
|
return RequestSyncPacket(p = params.p, m = mVal, data = params.data).encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Periodically remove stale announcements and all their messages
|
||||||
|
private fun pruneStaleAnnouncements() {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val stalePeers = mutableListOf<String>()
|
||||||
|
|
||||||
|
// Identify stale announcements by age
|
||||||
|
for ((peerID, pair) in latestAnnouncementByPeer.entries) {
|
||||||
|
val pkt = pair.second
|
||||||
|
val age = now - pkt.timestamp.toLong()
|
||||||
|
if (age > com.bitchat.android.util.AppConstants.STALE_PEER_TIMEOUT_MS) {
|
||||||
|
stalePeers.add(peerID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stalePeers.isEmpty()) return
|
||||||
|
|
||||||
|
// Remove announcements and their messages
|
||||||
|
var totalPrunedMsgs = 0
|
||||||
|
for (peerID in stalePeers) {
|
||||||
|
// Count messages to be pruned for logging
|
||||||
|
val toRemove = mutableListOf<String>()
|
||||||
|
synchronized(messages) {
|
||||||
|
for ((id, message) in messages) {
|
||||||
|
val sender = message.senderID.joinToString("") { b -> "%02x".format(b) }
|
||||||
|
if (sender == peerID) toRemove.add(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalPrunedMsgs += toRemove.size
|
||||||
|
|
||||||
|
// Reuse existing removal which also clears announcement entry
|
||||||
|
removeAnnouncementForPeer(peerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Pruned ${stalePeers.size} stale announcements and $totalPrunedMsgs messages")
|
||||||
|
}
|
||||||
|
|
||||||
// Explicitly remove stored announcement for a given peer (hex ID)
|
// Explicitly remove stored announcement for a given peer (hex ID)
|
||||||
fun removeAnnouncementForPeer(peerID: String) {
|
fun removeAnnouncementForPeer(peerID: String) {
|
||||||
val key = peerID.lowercase()
|
val key = peerID.lowercase()
|
||||||
@@ -244,16 +304,20 @@ class GossipSyncManager(
|
|||||||
|
|
||||||
// Collect IDs to remove first to avoid modifying collection while iterating
|
// Collect IDs to remove first to avoid modifying collection while iterating
|
||||||
val idsToRemove = mutableListOf<String>()
|
val idsToRemove = mutableListOf<String>()
|
||||||
for ((id, message) in messages) {
|
synchronized(messages) {
|
||||||
val sender = message.senderID.joinToString("") { b -> "%02x".format(b) }
|
for ((id, message) in messages) {
|
||||||
if (sender == key) {
|
val sender = message.senderID.joinToString("") { b -> "%02x".format(b) }
|
||||||
idsToRemove.add(id)
|
if (sender == key) {
|
||||||
|
idsToRemove.add(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now remove the collected IDs
|
// Now remove the collected IDs
|
||||||
for (id in idsToRemove) {
|
synchronized(messages) {
|
||||||
messages.remove(id)
|
for (id in idsToRemove) {
|
||||||
|
messages.remove(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idsToRemove.isNotEmpty()) {
|
if (idsToRemove.isNotEmpty()) {
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.bitchat.android.util
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Centralized application-wide constants.
|
||||||
|
*/
|
||||||
|
object AppConstants {
|
||||||
|
// Peer staleness threshold (ms). Keep consistent across mesh and sync layers.
|
||||||
|
const val STALE_PEER_TIMEOUT_MS: Long = 180_000L // 3 minutes
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user