mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 02:25:20 +00:00
Fix peer discovery stability and connection cycling issues
- Added debouncing for peer list updates (500ms) to prevent UI spam - Fixed race conditions in activePeers management by adding proper locking - Prevent duplicate key exchange processing with tracking set - Only notify delegate when peer list actually changes - Fixed thread safety issues in disconnect/unsubscribe handlers - Clear processed key exchanges periodically with bloom filter This resolves the rapid connect/disconnect cycles and excessive peer list updates.
This commit is contained in:
@@ -45,6 +45,7 @@ class BluetoothMeshService: NSObject {
|
|||||||
private let maxTTL: UInt8 = 7 // Maximum hops for long-distance delivery
|
private let maxTTL: UInt8 = 7 // Maximum hops for long-distance delivery
|
||||||
private var announcedToPeers = Set<String>() // Track which peers we've announced to
|
private var announcedToPeers = Set<String>() // Track which peers we've announced to
|
||||||
private var announcedPeers = Set<String>() // Track peers who have already been announced
|
private var announcedPeers = Set<String>() // Track peers who have already been announced
|
||||||
|
private var processedKeyExchanges = Set<String>() // Track processed key exchanges to prevent duplicates
|
||||||
|
|
||||||
// Store-and-forward message cache
|
// Store-and-forward message cache
|
||||||
private struct StoredMessage {
|
private struct StoredMessage {
|
||||||
@@ -72,6 +73,10 @@ class BluetoothMeshService: NSObject {
|
|||||||
private var batteryMonitorTimer: Timer?
|
private var batteryMonitorTimer: Timer?
|
||||||
private var currentBatteryLevel: Float = 1.0 // Default to full battery
|
private var currentBatteryLevel: Float = 1.0 // Default to full battery
|
||||||
|
|
||||||
|
// Peer list update debouncing
|
||||||
|
private var peerListUpdateTimer: Timer?
|
||||||
|
private let peerListUpdateDebounceInterval: TimeInterval = 0.5 // 500ms debounce
|
||||||
|
|
||||||
// Cover traffic for privacy
|
// Cover traffic for privacy
|
||||||
private var coverTrafficTimer: Timer?
|
private var coverTrafficTimer: Timer?
|
||||||
private let coverTrafficPrefix = "☂DUMMY☂" // Prefix to identify dummy messages after decryption
|
private let coverTrafficPrefix = "☂DUMMY☂" // Prefix to identify dummy messages after decryption
|
||||||
@@ -276,6 +281,7 @@ class BluetoothMeshService: NSObject {
|
|||||||
self?.messageQueue.async(flags: .barrier) {
|
self?.messageQueue.async(flags: .barrier) {
|
||||||
self?.messageBloomFilter.reset()
|
self?.messageBloomFilter.reset()
|
||||||
self?.processedMessages.removeAll()
|
self?.processedMessages.removeAll()
|
||||||
|
self?.processedKeyExchanges.removeAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -776,6 +782,24 @@ class BluetoothMeshService: NSObject {
|
|||||||
return Array(validPeers).sorted()
|
return Array(validPeers).sorted()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debounced peer list update notification
|
||||||
|
private func notifyPeerListUpdate() {
|
||||||
|
// Cancel any pending update
|
||||||
|
peerListUpdateTimer?.invalidate()
|
||||||
|
|
||||||
|
// Schedule a new update after debounce interval
|
||||||
|
peerListUpdateTimer = Timer.scheduledTimer(withTimeInterval: peerListUpdateDebounceInterval, repeats: false) { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
let connectedPeerIDs = self.getAllConnectedPeerIDs()
|
||||||
|
print("[DEBUG] Notifying peer list update after debounce: \(connectedPeerIDs.count) peers")
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.delegate?.didUpdatePeerList(connectedPeerIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Store-and-Forward Methods
|
// MARK: - Store-and-Forward Methods
|
||||||
|
|
||||||
private func cacheMessage(_ packet: BitchatPacket, messageID: String) {
|
private func cacheMessage(_ packet: BitchatPacket, messageID: String) {
|
||||||
@@ -1294,6 +1318,18 @@ class BluetoothMeshService: NSObject {
|
|||||||
if let senderID = String(data: packet.senderID.trimmingNullBytes(), encoding: .utf8) {
|
if let senderID = String(data: packet.senderID.trimmingNullBytes(), encoding: .utf8) {
|
||||||
if packet.payload.count > 0 {
|
if packet.payload.count > 0 {
|
||||||
let publicKeyData = packet.payload
|
let publicKeyData = packet.payload
|
||||||
|
|
||||||
|
// Create a unique key for this exchange
|
||||||
|
let exchangeKey = "\(senderID)-\(publicKeyData.hexEncodedString().prefix(16))"
|
||||||
|
|
||||||
|
// Check if we've already processed this key exchange
|
||||||
|
if processedKeyExchanges.contains(exchangeKey) {
|
||||||
|
print("[DEBUG] Ignoring duplicate key exchange from \(senderID)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark this key exchange as processed
|
||||||
|
processedKeyExchanges.insert(exchangeKey)
|
||||||
do {
|
do {
|
||||||
try encryptionService.addPeerPublicKey(senderID, publicKeyData: publicKeyData)
|
try encryptionService.addPeerPublicKey(senderID, publicKeyData: publicKeyData)
|
||||||
// Added public key
|
// Added public key
|
||||||
@@ -1334,12 +1370,18 @@ class BluetoothMeshService: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to active peers immediately on key exchange
|
// Add to active peers with proper locking
|
||||||
activePeers.insert(senderID)
|
activePeersLock.lock()
|
||||||
print("[DEBUG] Added peer \(senderID) to active peers via key exchange")
|
let wasNewPeer = !activePeers.contains(senderID)
|
||||||
let connectedPeerIDs = self.getAllConnectedPeerIDs()
|
if wasNewPeer {
|
||||||
DispatchQueue.main.async {
|
activePeers.insert(senderID)
|
||||||
self.delegate?.didUpdatePeerList(connectedPeerIDs)
|
print("[DEBUG] Added peer \(senderID) to active peers via key exchange")
|
||||||
|
}
|
||||||
|
activePeersLock.unlock()
|
||||||
|
|
||||||
|
// Only notify if this was actually a new peer
|
||||||
|
if wasNewPeer {
|
||||||
|
self.notifyPeerListUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1394,8 +1436,10 @@ class BluetoothMeshService: NSObject {
|
|||||||
announcedPeers.insert(senderID)
|
announcedPeers.insert(senderID)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.delegate?.didConnectToPeer(nickname)
|
self.delegate?.didConnectToPeer(nickname)
|
||||||
self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs())
|
}
|
||||||
|
self.notifyPeerListUpdate()
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
// Check if this is a favorite peer and send notification
|
// Check if this is a favorite peer and send notification
|
||||||
// Note: This might not work immediately if key exchange hasn't happened yet
|
// Note: This might not work immediately if key exchange hasn't happened yet
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
||||||
@@ -1416,9 +1460,7 @@ class BluetoothMeshService: NSObject {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Just update the peer list
|
// Just update the peer list
|
||||||
DispatchQueue.main.async {
|
self.notifyPeerListUpdate()
|
||||||
self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
@@ -1445,15 +1487,18 @@ class BluetoothMeshService: NSObject {
|
|||||||
|
|
||||||
// Peer is leaving
|
// Peer is leaving
|
||||||
|
|
||||||
// Remove from active peers
|
// Remove from active peers with proper locking
|
||||||
|
activePeersLock.lock()
|
||||||
activePeers.remove(senderID)
|
activePeers.remove(senderID)
|
||||||
|
activePeersLock.unlock()
|
||||||
|
|
||||||
announcedPeers.remove(senderID)
|
announcedPeers.remove(senderID)
|
||||||
|
|
||||||
// Show leave message
|
// Show leave message
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.delegate?.didDisconnectFromPeer(nickname)
|
self.delegate?.didDisconnectFromPeer(nickname)
|
||||||
self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs())
|
|
||||||
}
|
}
|
||||||
|
self.notifyPeerListUpdate()
|
||||||
|
|
||||||
// Clean up peer data
|
// Clean up peer data
|
||||||
peerNicknamesLock.lock()
|
peerNicknamesLock.lock()
|
||||||
@@ -1792,8 +1837,11 @@ extension BluetoothMeshService: CBCentralManagerDelegate {
|
|||||||
connectedPeripherals.removeValue(forKey: peerID)
|
connectedPeripherals.removeValue(forKey: peerID)
|
||||||
peripheralCharacteristics.removeValue(forKey: peripheral)
|
peripheralCharacteristics.removeValue(forKey: peripheral)
|
||||||
|
|
||||||
// Remove from active peers
|
// Remove from active peers with proper locking
|
||||||
|
activePeersLock.lock()
|
||||||
activePeers.remove(peerID)
|
activePeers.remove(peerID)
|
||||||
|
activePeersLock.unlock()
|
||||||
|
|
||||||
announcedPeers.remove(peerID)
|
announcedPeers.remove(peerID)
|
||||||
announcedToPeers.remove(peerID)
|
announcedToPeers.remove(peerID)
|
||||||
|
|
||||||
@@ -1809,13 +1857,9 @@ extension BluetoothMeshService: CBCentralManagerDelegate {
|
|||||||
if let nickname = nickname, nickname != peerID {
|
if let nickname = nickname, nickname != peerID {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.delegate?.didDisconnectFromPeer(nickname)
|
self.delegate?.didDisconnectFromPeer(nickname)
|
||||||
self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.notifyPeerListUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep in pool but remove from discovered list
|
// Keep in pool but remove from discovered list
|
||||||
@@ -1967,7 +2011,7 @@ extension BluetoothMeshService: CBPeripheralDelegate {
|
|||||||
// It's a real peer ID, store it
|
// It's a real peer ID, store it
|
||||||
self.peerRSSI[peerID] = RSSI
|
self.peerRSSI[peerID] = RSSI
|
||||||
// Force UI update when we have a real peer ID
|
// Force UI update when we have a real peer ID
|
||||||
self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs())
|
self.notifyPeerListUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2050,9 +2094,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
self.notifyPeerListUpdate()
|
||||||
self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReceivedPacket(packet, from: peerID)
|
handleReceivedPacket(packet, from: peerID)
|
||||||
@@ -2082,9 +2124,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update peer list to show we're connected (even without peer ID yet)
|
// Update peer list to show we're connected (even without peer ID yet)
|
||||||
DispatchQueue.main.async {
|
self.notifyPeerListUpdate()
|
||||||
self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2094,6 +2134,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
// If no more centrals are subscribed, clear all central-connected peers
|
// If no more centrals are subscribed, clear all central-connected peers
|
||||||
if subscribedCentrals.isEmpty {
|
if subscribedCentrals.isEmpty {
|
||||||
// Find and remove peers that were connected as centrals only
|
// Find and remove peers that were connected as centrals only
|
||||||
|
activePeersLock.lock()
|
||||||
let peersToRemove = activePeers.filter { peerID in
|
let peersToRemove = activePeers.filter { peerID in
|
||||||
!connectedPeripherals.keys.contains(peerID)
|
!connectedPeripherals.keys.contains(peerID)
|
||||||
}
|
}
|
||||||
@@ -2111,10 +2152,9 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
activePeersLock.unlock()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
self.notifyPeerListUpdate()
|
||||||
self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure advertising continues for reconnection
|
// Ensure advertising continues for reconnection
|
||||||
|
|||||||
Reference in New Issue
Block a user