From e3cc469bf8aacf4ec0ef80f36b0efb57a0098fa8 Mon Sep 17 00:00:00 2001 From: jack Date: Fri, 4 Jul 2025 15:35:26 +0200 Subject: [PATCH] Remove voice memo references and improve duplicate message prevention - Remove NSMicrophoneUsageDescription from Info.plist and project.yml - Add millisecond timestamps to prevent same-second message collisions - Include payload hash in message ID for absolute uniqueness - Add recentlySentMessages tracking to prevent any duplicate sends - Reduce retries from 2 to 1 for broadcast messages - Clean up sent message tracking after 10 seconds --- bitchat/Info.plist | 2 - bitchat/Services/BluetoothMeshService.swift | 75 +++++++++++++-------- project.yml | 1 - 3 files changed, 48 insertions(+), 30 deletions(-) diff --git a/bitchat/Info.plist b/bitchat/Info.plist index 08d8f420..4943de82 100644 --- a/bitchat/Info.plist +++ b/bitchat/Info.plist @@ -26,8 +26,6 @@ bitchat uses Bluetooth to create a secure mesh network for chatting with nearby users. NSBluetoothPeripheralUsageDescription bitchat uses Bluetooth to discover and connect with other bitchat users nearby. - NSMicrophoneUsageDescription - bitchat needs access to your microphone to record voice notes. UIBackgroundModes bluetooth-central diff --git a/bitchat/Services/BluetoothMeshService.swift b/bitchat/Services/BluetoothMeshService.swift index afbb15f2..6a9cc449 100644 --- a/bitchat/Services/BluetoothMeshService.swift +++ b/bitchat/Services/BluetoothMeshService.swift @@ -60,6 +60,7 @@ class BluetoothMeshService: NSObject { private var deliveredMessages: Set = [] // Track delivered message IDs to prevent duplicates private var cachedMessagesSentToPeer: Set = [] // Track which peers have already received cached messages private var receivedMessageTimestamps: [String: Date] = [:] // Track timestamps of received messages for debugging + private var recentlySentMessages: Set = [] // Short-term cache to prevent any duplicate sends // Battery and range optimizations private var scanDutyCycleTimer: Timer? @@ -516,26 +517,34 @@ class BluetoothMeshService: NSObject { type: MessageType.message.rawValue, senderID: Data(self.myPeerID.utf8), recipientID: SpecialRecipients.broadcast, // Special broadcast ID - timestamp: UInt64(Date().timeIntervalSince1970), + timestamp: UInt64(Date().timeIntervalSince1970 * 1000), // milliseconds payload: messageData, signature: signature, ttl: self.adaptiveTTL ) - // Add random delay before initial send - let initialDelay = self.randomDelay() - DispatchQueue.main.asyncAfter(deadline: .now() + initialDelay) { [weak self] in - self?.broadcastPacket(packet) - // Sending message - } - - // Retry with randomized delays for reliability - let baseDelays = [0.2, 0.5] - for baseDelay in baseDelays { - let jitteredDelay = baseDelay + self.randomDelay() - DispatchQueue.main.asyncAfter(deadline: .now() + jitteredDelay) { [weak self] in + // Track this message to prevent duplicate sends + let msgID = "\(packet.timestamp)-\(self.myPeerID)-\(packet.payload.prefix(32).hashValue)" + if !self.recentlySentMessages.contains(msgID) { + self.recentlySentMessages.insert(msgID) + + // Clean up old entries after 10 seconds + DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) { [weak self] in + self?.recentlySentMessages.remove(msgID) + } + + // Add random delay before initial send + let initialDelay = self.randomDelay() + DispatchQueue.main.asyncAfter(deadline: .now() + initialDelay) { [weak self] in self?.broadcastPacket(packet) - // Re-sending message with jitter + // Sending message + } + + // Single retry for reliability + let retryDelay = 0.3 + self.randomDelay() + DispatchQueue.main.asyncAfter(deadline: .now() + retryDelay) { [weak self] in + self?.broadcastPacket(packet) + // Re-sending message } } } @@ -593,7 +602,7 @@ class BluetoothMeshService: NSObject { type: MessageType.message.rawValue, senderID: Data(self.myPeerID.utf8), recipientID: Data(recipientPeerID.utf8), - timestamp: UInt64(Date().timeIntervalSince1970), + timestamp: UInt64(Date().timeIntervalSince1970 * 1000), // milliseconds payload: encryptedPayload, signature: signature, ttl: self.adaptiveTTL @@ -614,14 +623,25 @@ class BluetoothMeshService: NSObject { } } - // Add random delay for timing obfuscation - let delay = self.randomDelay() - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in - self?.broadcastPacket(packet) - // Private message sent with timing delay + // Track to prevent duplicate sends + let msgID = "\(packet.timestamp)-\(self.myPeerID)-\(packet.payload.prefix(32).hashValue)" + if !self.recentlySentMessages.contains(msgID) { + self.recentlySentMessages.insert(msgID) + + // Clean up after 10 seconds + DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) { [weak self] in + self?.recentlySentMessages.remove(msgID) + } + + // Add random delay for timing obfuscation + let delay = self.randomDelay() + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in + self?.broadcastPacket(packet) + // Private message sent with timing delay + } + + // Don't call didReceiveMessage here - let the view model handle it directly } - - // Don't call didReceiveMessage here - let the view model handle it directly } } } @@ -801,7 +821,7 @@ class BluetoothMeshService: NSObject { // Create stored message with original packet timestamp preserved let storedMessage = StoredMessage( packet: packet, - timestamp: Date(timeIntervalSince1970: TimeInterval(packet.timestamp)), + timestamp: Date(timeIntervalSince1970: TimeInterval(packet.timestamp) / 1000.0), // convert from milliseconds messageID: messageID, isForFavorite: isForFavorite ) @@ -1042,10 +1062,10 @@ class BluetoothMeshService: NSObject { } // Replay attack protection: Check timestamp is within reasonable window (5 minutes) - let currentTime = UInt64(Date().timeIntervalSince1970) + let currentTime = UInt64(Date().timeIntervalSince1970 * 1000) // milliseconds let timeDiff = abs(Int64(currentTime) - Int64(packet.timestamp)) - if timeDiff > 300 { // 5 minutes - print("[SECURITY] Dropping packet with timestamp too far from current time: \(timeDiff) seconds") + if timeDiff > 300000 { // 5 minutes in milliseconds + print("[SECURITY] Dropping packet with timestamp too far from current time: \(timeDiff/1000) seconds") return } @@ -1057,7 +1077,8 @@ class BluetoothMeshService: NSObject { // Include both type and payload hash for fragments to ensure uniqueness messageID = "\(packet.timestamp)-\(String(data: packet.senderID.trimmingNullBytes(), encoding: .utf8) ?? "")-\(packet.type)-\(packet.payload.hashValue)" } else { - messageID = "\(packet.timestamp)-\(String(data: packet.senderID.trimmingNullBytes(), encoding: .utf8) ?? "")" + // Include payload hash for absolute uniqueness (handles same-second messages) + messageID = "\(packet.timestamp)-\(String(data: packet.senderID.trimmingNullBytes(), encoding: .utf8) ?? "")-\(packet.payload.prefix(64).hashValue)" } // Use bloom filter for efficient duplicate detection diff --git a/project.yml b/project.yml index 9fcf827f..ed54e21f 100644 --- a/project.yml +++ b/project.yml @@ -25,7 +25,6 @@ targets: LSMinimumSystemVersion: $(MACOSX_DEPLOYMENT_TARGET) NSBluetoothAlwaysUsageDescription: bitchat uses Bluetooth to create a secure mesh network for chatting with nearby users. NSBluetoothPeripheralUsageDescription: bitchat uses Bluetooth to discover and connect with other bitchat users nearby. - NSMicrophoneUsageDescription: bitchat needs access to your microphone to record voice notes. UIBackgroundModes: - bluetooth-central - bluetooth-peripheral