Logs/robustness: debounce disconnect notifications (1.5s), debounce 'reconnected' logs (2s), add weak-link cooldown after timeouts on very weak RSSI (<= -90)

This commit is contained in:
jack
2025-08-26 13:38:15 +02:00
parent b919b3ff0a
commit 95af23a8f6
3 changed files with 57 additions and 17 deletions
+10 -13
View File
@@ -328,7 +328,6 @@
A2E8C336FA1ADBEC03261DFD /* bitchatShareExtension */, A2E8C336FA1ADBEC03261DFD /* bitchatShareExtension */,
C3D98EB3E1B455E321F519F4 /* bitchatTests */, C3D98EB3E1B455E321F519F4 /* bitchatTests */,
9F37F9F2C353B58AC809E93B /* Products */, 9F37F9F2C353B58AC809E93B /* Products */,
048A4BE52E5CCC5C00162C4A /* Recovered References */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -743,14 +742,13 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
048A4BE72E5CCCC300162C4A /* TransportConfig.swift in Sources */, 048A4BE72E5CCCC300162C4A /* TransportConfig.swift in Sources */,
1234567890ABCDEFFEDCBA13 /* PeerDisplayNameResolver.swift in Sources */, 1234567890ABCDEFFEDCBA13 /* PeerDisplayNameResolver.swift in Sources */,
AA77BB12CC22DD33EE44FF56 /* VerificationService.swift in Sources */, AA77BB12CC22DD33EE44FF56 /* VerificationService.swift in Sources */,
AA77BB15CC22DD33EE44FF59 /* VerificationViews.swift in Sources */, AA77BB15CC22DD33EE44FF59 /* VerificationViews.swift in Sources */,
A1B2C3D54E5F60718293A4B6 /* XChaCha20Poly1305Compat.swift in Sources */, A1B2C3D54E5F60718293A4B6 /* XChaCha20Poly1305Compat.swift in Sources */,
AD11E46940D742AEAF547EB2 /* AppInfoView.swift in Sources */, AD11E46940D742AEAF547EB2 /* AppInfoView.swift in Sources */,
9B51E9B63A3EA59B1A7874BD /* BinaryEncodingUtils.swift in Sources */, 9B51E9B63A3EA59B1A7874BD /* BinaryEncodingUtils.swift in Sources */,
049BD3B42E51F319001A566B /* NostrTransport.swift in Sources */, 049BD3B42E51F319001A566B /* NostrTransport.swift in Sources */,
049BD3B52E51F319001A566B /* MessageRouter.swift in Sources */, 049BD3B52E51F319001A566B /* MessageRouter.swift in Sources */,
4B747085D07A1BCE0F5BA612 /* BinaryProtocol.swift in Sources */, 4B747085D07A1BCE0F5BA612 /* BinaryProtocol.swift in Sources */,
@@ -803,14 +801,13 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
048A4BE82E5CCCC300162C4A /* TransportConfig.swift in Sources */, 048A4BE82E5CCCC300162C4A /* TransportConfig.swift in Sources */,
1234567890ABCDEFFEDCBA14 /* PeerDisplayNameResolver.swift in Sources */, 1234567890ABCDEFFEDCBA14 /* PeerDisplayNameResolver.swift in Sources */,
AA77BB11CC22DD33EE44FF55 /* VerificationService.swift in Sources */, AA77BB11CC22DD33EE44FF55 /* VerificationService.swift in Sources */,
AA77BB14CC22DD33EE44FF58 /* VerificationViews.swift in Sources */, AA77BB14CC22DD33EE44FF58 /* VerificationViews.swift in Sources */,
A1B2C3D44E5F60718293A4B5 /* XChaCha20Poly1305Compat.swift in Sources */, A1B2C3D44E5F60718293A4B5 /* XChaCha20Poly1305Compat.swift in Sources */,
ABAF130D88561F4A646F0430 /* AppInfoView.swift in Sources */, ABAF130D88561F4A646F0430 /* AppInfoView.swift in Sources */,
AFB6AEFCABBE97441CB3102B /* BinaryEncodingUtils.swift in Sources */, AFB6AEFCABBE97441CB3102B /* BinaryEncodingUtils.swift in Sources */,
049BD3B22E51F319001A566B /* NostrTransport.swift in Sources */, 049BD3B22E51F319001A566B /* NostrTransport.swift in Sources */,
049BD3B32E51F319001A566B /* MessageRouter.swift in Sources */, 049BD3B32E51F319001A566B /* MessageRouter.swift in Sources */,
F455F011B3B648ADA233F998 /* BinaryProtocol.swift in Sources */, F455F011B3B648ADA233F998 /* BinaryProtocol.swift in Sources */,
@@ -1007,7 +1004,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.3.2; MARKETING_VERSION = 1.3.3;
PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat.ShareExtension; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat.ShareExtension;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -1038,7 +1035,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.3.2; MARKETING_VERSION = 1.3.3;
PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat;
PRODUCT_NAME = bitchat; PRODUCT_NAME = bitchat;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1093,7 +1090,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.3.2; MARKETING_VERSION = 1.3.3;
PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat;
PRODUCT_NAME = bitchat; PRODUCT_NAME = bitchat;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1125,7 +1122,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.3.1; MARKETING_VERSION = 1.3.3;
PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat;
PRODUCT_NAME = bitchat; PRODUCT_NAME = bitchat;
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
@@ -1214,7 +1211,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.3.1; MARKETING_VERSION = 1.3.3;
PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat;
PRODUCT_NAME = bitchat; PRODUCT_NAME = bitchat;
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
@@ -1307,7 +1304,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.3.2; MARKETING_VERSION = 1.3.3;
PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat.ShareExtension; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat.ShareExtension;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+39 -4
View File
@@ -128,9 +128,13 @@ final class BLEService: NSObject {
// Backpressure-aware write queue per peripheral // Backpressure-aware write queue per peripheral
private var pendingPeripheralWrites: [String: [Data]] = [:] private var pendingPeripheralWrites: [String: [Data]] = [:]
// Debounce duplicate disconnect notifies
private var recentDisconnectNotifies: [String: Date] = [:]
// Store-and-forward for directed messages when we have no links // Store-and-forward for directed messages when we have no links
// Keyed by recipient short peerID -> messageID -> (packet, enqueuedAt) // Keyed by recipient short peerID -> messageID -> (packet, enqueuedAt)
private var pendingDirectedRelays: [String: [String: (packet: BitchatPacket, enqueuedAt: Date)]] = [:] private var pendingDirectedRelays: [String: [String: (packet: BitchatPacket, enqueuedAt: Date)]] = [:]
// Debounce for 'reconnected' logs
private var lastReconnectLogAt: [String: Date] = [:]
// MARK: - Maintenance Timer // MARK: - Maintenance Timer
@@ -1494,12 +1498,19 @@ final class BLEService: NSObject {
) )
} }
// Log connection status only for direct connectivity changes to avoid flapping on relayed announces // Log connection status only for direct connectivity changes; debounce to reduce spam
if isDirectAnnounce || hasPeripheralConnection || hasCentralSubscription { if isDirectAnnounce || hasPeripheralConnection || hasCentralSubscription {
let now = Date()
if existingPeer == nil { if existingPeer == nil {
SecureLogger.log("🆕 New peer: \(announcement.nickname)", category: SecureLogger.session, level: .debug) SecureLogger.log("🆕 New peer: \(announcement.nickname)", category: SecureLogger.session, level: .debug)
} else if wasDisconnected { } else if wasDisconnected {
SecureLogger.log("🔄 Peer \(announcement.nickname) reconnected", category: SecureLogger.session, level: .debug) // Debounce 'reconnected' logs within short window
if let last = lastReconnectLogAt[peerID], now.timeIntervalSince(last) < TransportConfig.bleReconnectLogDebounceSeconds {
// Skip duplicate log
} else {
SecureLogger.log("🔄 Peer \(announcement.nickname) reconnected", category: SecureLogger.session, level: .debug)
lastReconnectLogAt[peerID] = now
}
} else if existingPeer?.nickname != announcement.nickname { } else if existingPeer?.nickname != announcement.nickname {
SecureLogger.log("🔄 Peer \(peerID) changed nickname: \(existingPeer?.nickname ?? "Unknown") -> \(announcement.nickname)", category: SecureLogger.session, level: .debug) SecureLogger.log("🔄 Peer \(peerID) changed nickname: \(existingPeer?.nickname ?? "Unknown") -> \(announcement.nickname)", category: SecureLogger.session, level: .debug)
} }
@@ -1898,6 +1909,18 @@ final class BLEService: NSObject {
} }
} }
// Debounced disconnect notifier to avoid duplicate disconnect callbacks within a short window
private func notifyPeerDisconnectedDebounced(_ peerID: String) {
let now = Date()
let last = recentDisconnectNotifies[peerID]
if last == nil || now.timeIntervalSince(last!) >= TransportConfig.bleDisconnectNotifyDebounceSeconds {
delegate?.didDisconnectFromPeer(peerID)
recentDisconnectNotifies[peerID] = now
} else {
// Suppressed duplicate disconnect notification
}
}
// NEW: Publish peer snapshots to subscribers and notify Transport delegates // NEW: Publish peer snapshots to subscribers and notify Transport delegates
private func publishFullPeerData() { private func publishFullPeerData() {
let transportPeers: [TransportPeerSnapshot] = collectionsQueue.sync { let transportPeers: [TransportPeerSnapshot] = collectionsQueue.sync {
@@ -2422,7 +2445,7 @@ func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeriph
let currentPeerIDs = self.collectionsQueue.sync { Array(self.peers.keys) } let currentPeerIDs = self.collectionsQueue.sync { Array(self.peers.keys) }
if let peerID = peerID { if let peerID = peerID {
self.delegate?.didDisconnectFromPeer(peerID) self.notifyPeerDisconnectedDebounced(peerID)
} }
self.publishFullPeerData() self.publishFullPeerData()
self.delegate?.didUpdatePeerList(currentPeerIDs) self.delegate?.didUpdatePeerList(currentPeerIDs)
@@ -2474,6 +2497,18 @@ extension BLEService {
guard candidate.isConnectable else { return } guard candidate.isConnectable else { return }
let peripheral = candidate.peripheral let peripheral = candidate.peripheral
let peripheralID = peripheral.identifier.uuidString let peripheralID = peripheral.identifier.uuidString
// Weak-link cooldown: if we recently timed out and RSSI is very weak, delay retries
if let lastTO = recentConnectTimeouts[peripheralID] {
let elapsed = Date().timeIntervalSince(lastTO)
if elapsed < TransportConfig.bleWeakLinkCooldownSeconds && candidate.rssi <= TransportConfig.bleWeakLinkRSSICutoff {
// Requeue the candidate and try again later
connectionCandidates.append(candidate)
let remaining = TransportConfig.bleWeakLinkCooldownSeconds - elapsed
let delay = min(max(2.0, remaining), 15.0)
bleQueue.asyncAfter(deadline: .now() + delay) { [weak self] in self?.tryConnectFromQueue() }
return
}
}
if peripherals[peripheralID]?.isConnected == true || peripherals[peripheralID]?.isConnecting == true { if peripherals[peripheralID]?.isConnected == true || peripherals[peripheralID]?.isConnecting == true {
// Already in progress; skip // Already in progress; skip
bleQueue.async { [weak self] in self?.tryConnectFromQueue() } bleQueue.async { [weak self] in self?.tryConnectFromQueue() }
@@ -2811,7 +2846,7 @@ extension BLEService: CBPeripheralManagerDelegate {
// Get current peer list (after removal) // Get current peer list (after removal)
let currentPeerIDs = self.collectionsQueue.sync { Array(self.peers.keys) } let currentPeerIDs = self.collectionsQueue.sync { Array(self.peers.keys) }
self.delegate?.didDisconnectFromPeer(peerID) self.notifyPeerDisconnectedDebounced(peerID)
self.delegate?.didUpdatePeerList(currentPeerIDs) self.delegate?.didUpdatePeerList(currentPeerIDs)
} }
} }
+8
View File
@@ -143,6 +143,14 @@ enum TransportConfig {
// Store-and-forward for directed packets at relays // Store-and-forward for directed packets at relays
static let bleDirectedSpoolWindowSeconds: TimeInterval = 15.0 static let bleDirectedSpoolWindowSeconds: TimeInterval = 15.0
// Log/UI debounce windows
static let bleDisconnectNotifyDebounceSeconds: TimeInterval = 1.5
static let bleReconnectLogDebounceSeconds: TimeInterval = 2.0
// Weak-link cooldown after connection timeouts
static let bleWeakLinkCooldownSeconds: TimeInterval = 30.0
static let bleWeakLinkRSSICutoff: Int = -90
// Content hashing / formatting // Content hashing / formatting
static let contentKeyPrefixLength: Int = 256 static let contentKeyPrefixLength: Int = 256
static let uiLongMessageLengthThreshold: Int = 2000 static let uiLongMessageLengthThreshold: Int = 2000