Fix macOS crash and Bluetooth errors

- Fix NSInvalidArgumentException crash by adding thread-safe access to peerNicknames
- Fix 'can only accept commands while connected' by checking peripheral state
- Add safety checks for characteristic write properties
- Clean up disconnected peripherals from tracking
- Prevent type confusion in getAllConnectedPeerIDs

These fixes address the crash when accessing peerNicknames dictionary
which could be corrupted by concurrent access from multiple threads.
This commit is contained in:
jack
2025-07-04 13:49:14 +02:00
parent e6fcae6275
commit a59c3f7ec6
+32 -8
View File
@@ -30,7 +30,15 @@ class BluetoothMeshService: NSObject {
private var peripheralCharacteristics: [CBPeripheral: CBCharacteristic] = [:]
private var characteristic: CBMutableCharacteristic!
private var subscribedCentrals: [CBCentral] = []
private var peerNicknames: [String: String] = [:]
private var _peerNicknames: [String: String] = [:]
private var peerNicknames: [String: String] {
get {
messageQueue.sync { _peerNicknames }
}
set {
messageQueue.async(flags: .barrier) { self._peerNicknames = newValue }
}
}
private var activePeers: Set<String> = [] // Track all active peers
private var peerRSSI: [String: NSNumber] = [:] // Track RSSI values for peers
private var peripheralRSSI: [String: NSNumber] = [:] // Track RSSI by peripheral ID during discovery
@@ -717,11 +725,17 @@ class BluetoothMeshService: NSObject {
guard !peerID.isEmpty,
peerID != "unknown",
peerID != myPeerID,
peerID.count <= 8, // Filter out temp IDs
peerNicknames[peerID] != nil else { // Only include peers who have announced
peerID.count <= 8 else { // Filter out temp IDs
return nil
}
return peerID
// Safely check if peer has a nickname (thread-safe)
let hasNickname = self._peerNicknames[peerID] != nil
if hasNickname {
return peerID
}
return nil
})
// Active peers: \(announcedPeers.count)
return Array(announcedPeers).sorted()
@@ -848,7 +862,8 @@ class BluetoothMeshService: NSObject {
ttl: storedMessage.packet.ttl
)
if let data = updatedPacket.toBinaryData() {
if let data = updatedPacket.toBinaryData(),
characteristic.properties.contains(.writeWithoutResponse) {
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
// Sent cached message
}
@@ -887,10 +902,19 @@ class BluetoothMeshService: NSObject {
// Use withoutResponse for faster transmission when possible
// Only use withResponse for critical messages or when MTU negotiation needed
let writeType: CBCharacteristicWriteType = data.count > 512 ? .withResponse : .withoutResponse
peripheral.writeValue(data, for: characteristic, type: writeType)
sentToPeripherals += 1
// Additional safety check for characteristic properties
if characteristic.properties.contains(.write) ||
characteristic.properties.contains(.writeWithoutResponse) {
peripheral.writeValue(data, for: characteristic, type: writeType)
sentToPeripherals += 1
}
} else {
// Peripheral not connected
// Peripheral not connected - remove from our tracking
if let peerID = connectedPeripherals.first(where: { $0.value == peripheral })?.key {
connectedPeripherals.removeValue(forKey: peerID)
peripheralCharacteristics.removeValue(forKey: peripheral)
}
}
} else {
// No characteristic for peripheral