From 0acbbdf2e33f5e374ff56da5f73eacb6e78514d1 Mon Sep 17 00:00:00 2001 From: jack Date: Thu, 25 Sep 2025 23:38:28 +0200 Subject: [PATCH] Restore BLE broadcasts when notify buffer is saturated --- bitchat/Services/BLEService.swift | 30 ++++++++++++++++++++------ bitchat/Services/TransportConfig.swift | 4 +++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/bitchat/Services/BLEService.swift b/bitchat/Services/BLEService.swift index a3fce93b..b2be0737 100644 --- a/bitchat/Services/BLEService.swift +++ b/bitchat/Services/BLEService.swift @@ -916,13 +916,7 @@ final class BLEService: NSObject { for central in centrals where mapping[central.identifier.uuidString] == recipientPeerID { let success = peripheralManager?.updateValue(data, for: characteristic, onSubscribedCentrals: [central]) ?? false if success { sentEncrypted = true; break } - collectionsQueue.async(flags: .barrier) { [weak self] in - guard let self = self else { return } - if self.pendingNotifications.count < TransportConfig.blePendingNotificationsCapCount { - self.pendingNotifications.append((data: data, centrals: [central])) - SecureLogger.debug("📋 Queued encrypted packet for retry (notification queue full)", category: .session) - } - } + enqueuePendingNotification(data: data, centrals: [central], context: "encrypted") } } @@ -936,6 +930,28 @@ final class BLEService: NSObject { sendOnAllLinks(packet: packet, data: data, pad: pad, directedOnlyPeer: nil) } + private func enqueuePendingNotification(data: Data, centrals: [CBCentral]?, context: String, attempt: Int = 0) { + collectionsQueue.async(flags: .barrier) { [weak self] in + guard let self = self else { return } + if self.pendingNotifications.count < TransportConfig.blePendingNotificationsCapCount { + self.pendingNotifications.append((data: data, centrals: centrals)) + SecureLogger.debug("📋 Queued \(context) packet for retry (pending=\(self.pendingNotifications.count))", category: .session) + return + } + + if attempt >= TransportConfig.bleNotificationRetryMaxAttempts { + SecureLogger.error("❌ Dropping \(context) packet after exhausting retry window (pending=\(self.pendingNotifications.count))", category: .session) + return + } + + let backoff = TransportConfig.bleNotificationRetryDelayMs * max(1, attempt + 1) + let deadline = DispatchTime.now() + .milliseconds(backoff) + self.messageQueue.asyncAfter(deadline: deadline) { [weak self] in + self?.enqueuePendingNotification(data: data, centrals: centrals, context: context, attempt: attempt + 1) + } + } + } + private func sendOnAllLinks(packet: BitchatPacket, data: Data, pad: Bool, directedOnlyPeer: String?) { // Determine last-hop link for this message to avoid echoing back let messageID = makeMessageID(for: packet) diff --git a/bitchat/Services/TransportConfig.swift b/bitchat/Services/TransportConfig.swift index daf160f6..8b30af92 100644 --- a/bitchat/Services/TransportConfig.swift +++ b/bitchat/Services/TransportConfig.swift @@ -31,7 +31,9 @@ enum TransportConfig { static let bleConnectionCandidatesMax: Int = 100 static let blePendingWriteBufferCapBytes: Int = 1_000_000 static let bleNotificationAssemblerHardCapBytes: Int = 8 * 1024 * 1024 - static let blePendingNotificationsCapCount: Int = 20 + static let blePendingNotificationsCapCount: Int = 128 + static let bleNotificationRetryDelayMs: Int = 25 + static let bleNotificationRetryMaxAttempts: Int = 80 // Nostr static let nostrReadAckInterval: TimeInterval = 0.35 // ~3 per second