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
This commit is contained in:
jack
2025-07-04 15:35:26 +02:00
parent da44be9449
commit e3cc469bf8
3 changed files with 48 additions and 30 deletions
-2
View File
@@ -26,8 +26,6 @@
<string>bitchat uses Bluetooth to create a secure mesh network for chatting with nearby users.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>bitchat uses Bluetooth to discover and connect with other bitchat users nearby.</string>
<key>NSMicrophoneUsageDescription</key>
<string>bitchat needs access to your microphone to record voice notes.</string>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
+48 -27
View File
@@ -60,6 +60,7 @@ class BluetoothMeshService: NSObject {
private var deliveredMessages: Set<String> = [] // Track delivered message IDs to prevent duplicates
private var cachedMessagesSentToPeer: Set<String> = [] // Track which peers have already received cached messages
private var receivedMessageTimestamps: [String: Date] = [:] // Track timestamps of received messages for debugging
private var recentlySentMessages: Set<String> = [] // 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
-1
View File
@@ -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