mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 22:05:21 +00:00
Fix thread safety crashes in BluetoothMeshService
Fixed multiple threading issues that were causing crashes when users sent messages: - Added NSLock to synchronize access to recentlySentMessages Set - Fixed thread-unsafe access to activePeers Set using existing lock - Removed force unwrapping that could cause crashes - Added defensive input validation for message sending methods These fixes address crashes reported in TestFlight where the app would crash when typing and sending messages.
This commit is contained in:
@@ -74,6 +74,7 @@ class BluetoothMeshService: NSObject {
|
||||
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
|
||||
private let recentlySentMessagesLock = NSLock() // Thread safety for recentlySentMessages
|
||||
private var lastMessageFromPeer: [String: Date] = [:] // Track last message time from each peer for connection prioritization
|
||||
|
||||
// Battery and range optimizations
|
||||
@@ -490,6 +491,9 @@ class BluetoothMeshService: NSObject {
|
||||
}
|
||||
|
||||
func sendMessage(_ content: String, mentions: [String] = [], channel: String? = nil, to recipientID: String? = nil) {
|
||||
// Defensive check for empty content
|
||||
guard !content.isEmpty else { return }
|
||||
|
||||
messageQueue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
@@ -532,12 +536,21 @@ class BluetoothMeshService: NSObject {
|
||||
|
||||
// Track this message to prevent duplicate sends
|
||||
let msgID = "\(packet.timestamp)-\(self.myPeerID)-\(packet.payload.prefix(32).hashValue)"
|
||||
if !self.recentlySentMessages.contains(msgID) {
|
||||
|
||||
self.recentlySentMessagesLock.lock()
|
||||
let shouldSend = !self.recentlySentMessages.contains(msgID)
|
||||
if shouldSend {
|
||||
self.recentlySentMessages.insert(msgID)
|
||||
|
||||
}
|
||||
self.recentlySentMessagesLock.unlock()
|
||||
|
||||
if shouldSend {
|
||||
// Clean up old entries after 10 seconds
|
||||
self.messageQueue.asyncAfter(deadline: .now() + 10.0) { [weak self] in
|
||||
self?.recentlySentMessages.remove(msgID)
|
||||
guard let self = self else { return }
|
||||
self.recentlySentMessagesLock.lock()
|
||||
self.recentlySentMessages.remove(msgID)
|
||||
self.recentlySentMessagesLock.unlock()
|
||||
}
|
||||
|
||||
// Add random delay before initial send
|
||||
@@ -559,6 +572,9 @@ class BluetoothMeshService: NSObject {
|
||||
|
||||
|
||||
func sendPrivateMessage(_ content: String, to recipientPeerID: String, recipientNickname: String, messageID: String? = nil) {
|
||||
// Defensive checks
|
||||
guard !content.isEmpty, !recipientPeerID.isEmpty, !recipientNickname.isEmpty else { return }
|
||||
|
||||
messageQueue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
@@ -617,7 +633,11 @@ class BluetoothMeshService: NSObject {
|
||||
|
||||
|
||||
// Check if recipient is offline and cache if they're a favorite
|
||||
if !self.activePeers.contains(recipientPeerID) {
|
||||
self.activePeersLock.lock()
|
||||
let isRecipientOffline = !self.activePeers.contains(recipientPeerID)
|
||||
self.activePeersLock.unlock()
|
||||
|
||||
if isRecipientOffline {
|
||||
if let publicKeyData = self.encryptionService.getPeerIdentityKey(recipientPeerID) {
|
||||
let fingerprint = self.getPublicKeyFingerprint(publicKeyData)
|
||||
if self.delegate?.isFavorite(fingerprint: fingerprint) ?? false {
|
||||
@@ -630,12 +650,21 @@ class BluetoothMeshService: NSObject {
|
||||
|
||||
// Track to prevent duplicate sends
|
||||
let msgID = "\(packet.timestamp)-\(self.myPeerID)-\(packet.payload.prefix(32).hashValue)"
|
||||
if !self.recentlySentMessages.contains(msgID) {
|
||||
|
||||
self.recentlySentMessagesLock.lock()
|
||||
let shouldSend = !self.recentlySentMessages.contains(msgID)
|
||||
if shouldSend {
|
||||
self.recentlySentMessages.insert(msgID)
|
||||
|
||||
}
|
||||
self.recentlySentMessagesLock.unlock()
|
||||
|
||||
if shouldSend {
|
||||
// Clean up after 10 seconds
|
||||
self.messageQueue.asyncAfter(deadline: .now() + 10.0) { [weak self] in
|
||||
self?.recentlySentMessages.remove(msgID)
|
||||
guard let self = self else { return }
|
||||
self.recentlySentMessagesLock.lock()
|
||||
self.recentlySentMessages.remove(msgID)
|
||||
self.recentlySentMessagesLock.unlock()
|
||||
}
|
||||
|
||||
// Message tracking is now done in ChatViewModel to ensure consistent message IDs
|
||||
@@ -798,7 +827,10 @@ class BluetoothMeshService: NSObject {
|
||||
|
||||
do {
|
||||
let sealedBox = try AES.GCM.seal(contentData, using: channelKey)
|
||||
let encryptedData = sealedBox.combined!
|
||||
guard let encryptedData = sealedBox.combined else {
|
||||
// Encryption failed to produce combined data
|
||||
return
|
||||
}
|
||||
|
||||
// Create message with encrypted content
|
||||
let message = BitchatMessage(
|
||||
|
||||
@@ -31,7 +31,10 @@ class MessageRetentionService {
|
||||
|
||||
private init() {
|
||||
// Get documents directory
|
||||
documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
guard let docsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||
fatalError("Unable to access documents directory")
|
||||
}
|
||||
documentsDirectory = docsDir
|
||||
messagesDirectory = documentsDirectory.appendingPathComponent("Messages", isDirectory: true)
|
||||
|
||||
// Create messages directory if it doesn't exist
|
||||
|
||||
Reference in New Issue
Block a user