mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 04:45:20 +00:00
PeerID 19/n: BLEService’s private properties (#770)
This commit is contained in:
@@ -43,7 +43,7 @@ final class BLEService: NSObject {
|
|||||||
var assembler = NotificationStreamAssembler()
|
var assembler = NotificationStreamAssembler()
|
||||||
}
|
}
|
||||||
private var peripherals: [String: PeripheralState] = [:] // UUID -> PeripheralState
|
private var peripherals: [String: PeripheralState] = [:] // UUID -> PeripheralState
|
||||||
private var peerToPeripheralUUID: [String: String] = [:] // PeerID -> Peripheral UUID
|
private var peerToPeripheralUUID: [PeerID: String] = [:] // PeerID -> Peripheral UUID
|
||||||
|
|
||||||
// 2. BLE Centrals (when acting as peripheral)
|
// 2. BLE Centrals (when acting as peripheral)
|
||||||
private var subscribedCentrals: [CBCentral] = []
|
private var subscribedCentrals: [CBCentral] = []
|
||||||
@@ -59,9 +59,9 @@ final class BLEService: NSObject {
|
|||||||
var isVerifiedNickname: Bool
|
var isVerifiedNickname: Bool
|
||||||
var lastSeen: Date
|
var lastSeen: Date
|
||||||
}
|
}
|
||||||
private var peers: [String: PeerInfo] = [:]
|
private var peers: [PeerID: PeerInfo] = [:]
|
||||||
private var currentPeerIDs: [PeerID] {
|
private var currentPeerIDs: [PeerID] {
|
||||||
peers.keys.map { PeerID(str: $0) }
|
Array(peers.keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Efficient Message Deduplication
|
// 4. Efficient Message Deduplication
|
||||||
@@ -108,12 +108,12 @@ final class BLEService: NSObject {
|
|||||||
private let bleQueueKey = DispatchSpecificKey<Void>()
|
private let bleQueueKey = DispatchSpecificKey<Void>()
|
||||||
|
|
||||||
// Queue for messages pending handshake completion
|
// Queue for messages pending handshake completion
|
||||||
private var pendingMessagesAfterHandshake: [String: [(content: String, messageID: String)]] = [:]
|
private var pendingMessagesAfterHandshake: [PeerID: [(content: String, messageID: String)]] = [:]
|
||||||
// Noise typed payloads (ACKs, read receipts, etc.) pending handshake
|
// Noise typed payloads (ACKs, read receipts, etc.) pending handshake
|
||||||
private var pendingNoisePayloadsAfterHandshake: [String: [Data]] = [:]
|
private var pendingNoisePayloadsAfterHandshake: [PeerID: [Data]] = [:]
|
||||||
// Keep a tiny buffer of the last few unique announces we've seen (by sender)
|
// Keep a tiny buffer of the last few unique announces we've seen (by sender)
|
||||||
private var recentAnnounceBySender: [String: BitchatPacket] = [:]
|
private var recentAnnounceBySender: [PeerID: BitchatPacket] = [:]
|
||||||
private var recentAnnounceOrder: [String] = []
|
private var recentAnnounceOrder: [PeerID] = []
|
||||||
private let recentAnnounceBufferCap = 3
|
private let recentAnnounceBufferCap = 3
|
||||||
|
|
||||||
// Queue for notifications that failed due to full queue
|
// Queue for notifications that failed due to full queue
|
||||||
@@ -136,12 +136,12 @@ final class BLEService: NSObject {
|
|||||||
// Backpressure-aware write queue per peripheral
|
// Backpressure-aware write queue per peripheral
|
||||||
private var pendingPeripheralWrites: [String: [Data]] = [:]
|
private var pendingPeripheralWrites: [String: [Data]] = [:]
|
||||||
// Debounce duplicate disconnect notifies
|
// Debounce duplicate disconnect notifies
|
||||||
private var recentDisconnectNotifies: [String: Date] = [:]
|
private var recentDisconnectNotifies: [PeerID: Date] = [:]
|
||||||
// Store-and-forward for directed messages when we have no links
|
// Store-and-forward for directed messages when we have no links
|
||||||
// Keyed by recipient short peerID -> messageID -> (packet, enqueuedAt)
|
// Keyed by recipient short peerID -> messageID -> (packet, enqueuedAt)
|
||||||
private var pendingDirectedRelays: [String: [String: (packet: BitchatPacket, enqueuedAt: Date)]] = [:]
|
private var pendingDirectedRelays: [PeerID: [String: (packet: BitchatPacket, enqueuedAt: Date)]] = [:]
|
||||||
// Debounce for 'reconnected' logs
|
// Debounce for 'reconnected' logs
|
||||||
private var lastReconnectLogAt: [String: Date] = [:]
|
private var lastReconnectLogAt: [PeerID: Date] = [:]
|
||||||
|
|
||||||
// MARK: - Gossip Sync
|
// MARK: - Gossip Sync
|
||||||
private var gossipSyncManager: GossipSyncManager?
|
private var gossipSyncManager: GossipSyncManager?
|
||||||
@@ -495,13 +495,13 @@ final class BLEService: NSObject {
|
|||||||
|
|
||||||
func isPeerConnected(_ peerID: PeerID) -> Bool {
|
func isPeerConnected(_ peerID: PeerID) -> Bool {
|
||||||
// Accept both 16-hex short IDs and 64-hex Noise keys
|
// Accept both 16-hex short IDs and 64-hex Noise keys
|
||||||
let shortID = peerID.toShort().id
|
let shortID = peerID.toShort()
|
||||||
return collectionsQueue.sync { peers[shortID]?.isConnected ?? false }
|
return collectionsQueue.sync { peers[shortID]?.isConnected ?? false }
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPeerReachable(_ peerID: PeerID) -> Bool {
|
func isPeerReachable(_ peerID: PeerID) -> Bool {
|
||||||
// Accept both 16-hex short IDs and 64-hex Noise keys
|
// Accept both 16-hex short IDs and 64-hex Noise keys
|
||||||
let shortID = peerID.toShort().id
|
let shortID = peerID.toShort()
|
||||||
return collectionsQueue.sync {
|
return collectionsQueue.sync {
|
||||||
// Must be mesh-attached: at least one live direct link to the mesh
|
// Must be mesh-attached: at least one live direct link to the mesh
|
||||||
let meshAttached = peers.values.contains { $0.isConnected }
|
let meshAttached = peers.values.contains { $0.isConnected }
|
||||||
@@ -517,7 +517,7 @@ final class BLEService: NSObject {
|
|||||||
|
|
||||||
func peerNickname(peerID: PeerID) -> String? {
|
func peerNickname(peerID: PeerID) -> String? {
|
||||||
collectionsQueue.sync {
|
collectionsQueue.sync {
|
||||||
guard let peer = peers[peerID.id], peer.isConnected else { return nil }
|
guard let peer = peers[peerID], peer.isConnected else { return nil }
|
||||||
return peer.nickname
|
return peer.nickname
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -525,7 +525,7 @@ final class BLEService: NSObject {
|
|||||||
func getPeerNicknames() -> [PeerID: String] {
|
func getPeerNicknames() -> [PeerID: String] {
|
||||||
return collectionsQueue.sync {
|
return collectionsQueue.sync {
|
||||||
let connected = peers.filter { $0.value.isConnected }
|
let connected = peers.filter { $0.value.isConnected }
|
||||||
let tuples = connected.map { (PeerID(str: $0.key), $0.value.nickname, true) }
|
let tuples = connected.map { ($0.key, $0.value.nickname, true) }
|
||||||
return PeerDisplayNameResolver.resolve(tuples, selfNickname: myNickname)
|
return PeerDisplayNameResolver.resolve(tuples, selfNickname: myNickname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -534,7 +534,7 @@ final class BLEService: NSObject {
|
|||||||
|
|
||||||
func getFingerprint(for peerID: PeerID) -> String? {
|
func getFingerprint(for peerID: PeerID) -> String? {
|
||||||
return collectionsQueue.sync {
|
return collectionsQueue.sync {
|
||||||
return peers[peerID.id]?.noisePublicKey?.sha256Fingerprint()
|
return peers[peerID]?.noisePublicKey?.sha256Fingerprint()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,7 +602,7 @@ final class BLEService: NSObject {
|
|||||||
// Queue for after handshake and initiate if needed
|
// Queue for after handshake and initiate if needed
|
||||||
collectionsQueue.async(flags: .barrier) { [weak self] in
|
collectionsQueue.async(flags: .barrier) { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.pendingNoisePayloadsAfterHandshake[peerID.id, default: []].append(payload)
|
self.pendingNoisePayloadsAfterHandshake[peerID, default: []].append(payload)
|
||||||
}
|
}
|
||||||
if !noiseService.hasSession(with: peerID) { initiateNoiseHandshake(with: peerID.id) }
|
if !noiseService.hasSession(with: peerID) { initiateNoiseHandshake(with: peerID.id) }
|
||||||
SecureLogger.debug("🕒 Queued READ receipt for \(peerID) until handshake completes", category: .session)
|
SecureLogger.debug("🕒 Queued READ receipt for \(peerID) until handshake completes", category: .session)
|
||||||
@@ -654,7 +654,7 @@ final class BLEService: NSObject {
|
|||||||
// Queue for after handshake and initiate if needed
|
// Queue for after handshake and initiate if needed
|
||||||
collectionsQueue.async(flags: .barrier) { [weak self] in
|
collectionsQueue.async(flags: .barrier) { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.pendingNoisePayloadsAfterHandshake[peerID.id, default: []].append(payload)
|
self.pendingNoisePayloadsAfterHandshake[peerID, default: []].append(payload)
|
||||||
}
|
}
|
||||||
if !noiseService.hasSession(with: peerID) { initiateNoiseHandshake(with: peerID.id) }
|
if !noiseService.hasSession(with: peerID) { initiateNoiseHandshake(with: peerID.id) }
|
||||||
SecureLogger.debug("🕒 Queued DELIVERED ack for \(peerID) until handshake completes", category: .session)
|
SecureLogger.debug("🕒 Queued DELIVERED ack for \(peerID) until handshake completes", category: .session)
|
||||||
@@ -910,13 +910,13 @@ func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeriph
|
|||||||
|
|
||||||
// Clean up peer mappings
|
// Clean up peer mappings
|
||||||
if let peerID {
|
if let peerID {
|
||||||
peerToPeripheralUUID.removeValue(forKey: peerID.id)
|
peerToPeripheralUUID.removeValue(forKey: peerID)
|
||||||
|
|
||||||
// Do not remove peer; mark as not connected but retain for reachability
|
// Do not remove peer; mark as not connected but retain for reachability
|
||||||
collectionsQueue.sync(flags: .barrier) {
|
collectionsQueue.sync(flags: .barrier) {
|
||||||
if var info = peers[peerID.id] {
|
if var info = peers[peerID] {
|
||||||
info.isConnected = false
|
info.isConnected = false
|
||||||
peers[peerID.id] = info
|
peers[peerID] = info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1036,11 +1036,11 @@ extension BLEService {
|
|||||||
extension BLEService {
|
extension BLEService {
|
||||||
func _test_handlePacket(_ packet: BitchatPacket, fromPeerID: String) {
|
func _test_handlePacket(_ packet: BitchatPacket, fromPeerID: String) {
|
||||||
// Ensure the synthetic peer is known and marked verified for public-message tests
|
// Ensure the synthetic peer is known and marked verified for public-message tests
|
||||||
let normalizedID = packet.senderID.hexEncodedString()
|
let normalizedID = PeerID(hexData: packet.senderID)
|
||||||
collectionsQueue.sync(flags: .barrier) {
|
collectionsQueue.sync(flags: .barrier) {
|
||||||
if peers[normalizedID] == nil {
|
if peers[normalizedID] == nil {
|
||||||
peers[normalizedID] = PeerInfo(
|
peers[normalizedID] = PeerInfo(
|
||||||
peerID: PeerID(str: normalizedID),
|
peerID: normalizedID,
|
||||||
nickname: "TestPeer_\(fromPeerID.prefix(4))",
|
nickname: "TestPeer_\(fromPeerID.prefix(4))",
|
||||||
isConnected: true,
|
isConnected: true,
|
||||||
noisePublicKey: packet.senderID,
|
noisePublicKey: packet.senderID,
|
||||||
@@ -1199,7 +1199,7 @@ extension BLEService: CBPeripheralDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func processNotificationPacket(_ packet: BitchatPacket, from peripheral: CBPeripheral, peripheralUUID: String) {
|
private func processNotificationPacket(_ packet: BitchatPacket, from peripheral: CBPeripheral, peripheralUUID: String) {
|
||||||
let senderID = packet.senderID.hexEncodedString()
|
let senderID = PeerID(hexData: packet.senderID)
|
||||||
|
|
||||||
if packet.type != MessageType.announce.rawValue {
|
if packet.type != MessageType.announce.rawValue {
|
||||||
SecureLogger.debug("📦 Decoded notification packet type: \(packet.type) from sender: \(senderID)", category: .session)
|
SecureLogger.debug("📦 Decoded notification packet type: \(packet.type) from sender: \(senderID)", category: .session)
|
||||||
@@ -1208,7 +1208,7 @@ extension BLEService: CBPeripheralDelegate {
|
|||||||
if packet.type == MessageType.announce.rawValue {
|
if packet.type == MessageType.announce.rawValue {
|
||||||
if packet.ttl == messageTTL {
|
if packet.ttl == messageTTL {
|
||||||
if var state = peripherals[peripheralUUID] {
|
if var state = peripherals[peripheralUUID] {
|
||||||
state.peerID = PeerID(str: senderID)
|
state.peerID = senderID
|
||||||
peripherals[peripheralUUID] = state
|
peripherals[peripheralUUID] = state
|
||||||
}
|
}
|
||||||
peerToPeripheralUUID[senderID] = peripheralUUID
|
peerToPeripheralUUID[senderID] = peripheralUUID
|
||||||
@@ -1218,13 +1218,13 @@ extension BLEService: CBPeripheralDelegate {
|
|||||||
collectionsQueue.async(flags: .barrier) { [weak self] in
|
collectionsQueue.async(flags: .barrier) { [weak self] in
|
||||||
self?.ingressByMessageID[msgID] = (.peripheral(peripheralUUID), Date())
|
self?.ingressByMessageID[msgID] = (.peripheral(peripheralUUID), Date())
|
||||||
}
|
}
|
||||||
handleReceivedPacket(packet, from: senderID)
|
handleReceivedPacket(packet, from: senderID.id)
|
||||||
} else {
|
} else {
|
||||||
let msgID = makeMessageID(for: packet)
|
let msgID = makeMessageID(for: packet)
|
||||||
collectionsQueue.async(flags: .barrier) { [weak self] in
|
collectionsQueue.async(flags: .barrier) { [weak self] in
|
||||||
self?.ingressByMessageID[msgID] = (.peripheral(peripheralUUID), Date())
|
self?.ingressByMessageID[msgID] = (.peripheral(peripheralUUID), Date())
|
||||||
}
|
}
|
||||||
handleReceivedPacket(packet, from: senderID)
|
handleReceivedPacket(packet, from: senderID.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1344,9 +1344,9 @@ extension BLEService: CBPeripheralManagerDelegate {
|
|||||||
if let peerID = centralToPeerID[centralUUID] {
|
if let peerID = centralToPeerID[centralUUID] {
|
||||||
// Mark peer as not connected; retain for reachability
|
// Mark peer as not connected; retain for reachability
|
||||||
collectionsQueue.sync(flags: .barrier) {
|
collectionsQueue.sync(flags: .barrier) {
|
||||||
if var info = peers[peerID.id] {
|
if var info = peers[peerID] {
|
||||||
info.isConnected = false
|
info.isConnected = false
|
||||||
peers[peerID.id] = info
|
peers[peerID] = info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1728,10 +1728,11 @@ extension BLEService {
|
|||||||
// MARK: Private Message Handling
|
// MARK: Private Message Handling
|
||||||
|
|
||||||
private func sendPrivateMessage(_ content: String, to recipientID: String, messageID: String) {
|
private func sendPrivateMessage(_ content: String, to recipientID: String, messageID: String) {
|
||||||
|
let recipientID = PeerID(str: recipientID)
|
||||||
SecureLogger.debug("📨 Sending PM to \(recipientID): \(content.prefix(30))...", category: .session)
|
SecureLogger.debug("📨 Sending PM to \(recipientID): \(content.prefix(30))...", category: .session)
|
||||||
|
|
||||||
// Check if we have an established Noise session
|
// Check if we have an established Noise session
|
||||||
if noiseService.hasEstablishedSession(with: PeerID(str: recipientID)) {
|
if noiseService.hasEstablishedSession(with: recipientID) {
|
||||||
// Encrypt and send
|
// Encrypt and send
|
||||||
do {
|
do {
|
||||||
// Create TLV-encoded private message
|
// Create TLV-encoded private message
|
||||||
@@ -1745,11 +1746,11 @@ extension BLEService {
|
|||||||
var messagePayload = Data([NoisePayloadType.privateMessage.rawValue])
|
var messagePayload = Data([NoisePayloadType.privateMessage.rawValue])
|
||||||
messagePayload.append(tlvData)
|
messagePayload.append(tlvData)
|
||||||
|
|
||||||
let encrypted = try noiseService.encrypt(messagePayload, for: PeerID(str: recipientID))
|
let encrypted = try noiseService.encrypt(messagePayload, for: recipientID)
|
||||||
|
|
||||||
// Convert recipientID to Data (assuming it's a hex string)
|
// Convert recipientID to Data (assuming it's a hex string)
|
||||||
var recipientData = Data()
|
var recipientData = Data()
|
||||||
var tempID = recipientID
|
var tempID = recipientID.id
|
||||||
while tempID.count >= 2 {
|
while tempID.count >= 2 {
|
||||||
let hexByte = String(tempID.prefix(2))
|
let hexByte = String(tempID.prefix(2))
|
||||||
if let byte = UInt8(hexByte, radix: 16) {
|
if let byte = UInt8(hexByte, radix: 16) {
|
||||||
@@ -1800,7 +1801,7 @@ extension BLEService {
|
|||||||
pendingMessagesAfterHandshake[recipientID]?.append((content, messageID))
|
pendingMessagesAfterHandshake[recipientID]?.append((content, messageID))
|
||||||
}
|
}
|
||||||
|
|
||||||
initiateNoiseHandshake(with: recipientID)
|
initiateNoiseHandshake(with: recipientID.id)
|
||||||
|
|
||||||
// Notify delegate that message is pending
|
// Notify delegate that message is pending
|
||||||
notifyUI { [weak self] in
|
notifyUI { [weak self] in
|
||||||
@@ -1840,6 +1841,7 @@ extension BLEService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func sendPendingMessagesAfterHandshake(for peerID: String) {
|
private func sendPendingMessagesAfterHandshake(for peerID: String) {
|
||||||
|
let peerID = PeerID(str: peerID)
|
||||||
// Get and clear pending messages for this peer
|
// Get and clear pending messages for this peer
|
||||||
let pendingMessages = collectionsQueue.sync(flags: .barrier) { () -> [(content: String, messageID: String)]? in
|
let pendingMessages = collectionsQueue.sync(flags: .barrier) { () -> [(content: String, messageID: String)]? in
|
||||||
let messages = pendingMessagesAfterHandshake[peerID]
|
let messages = pendingMessagesAfterHandshake[peerID]
|
||||||
@@ -1864,12 +1866,12 @@ extension BLEService {
|
|||||||
var messagePayload = Data([NoisePayloadType.privateMessage.rawValue])
|
var messagePayload = Data([NoisePayloadType.privateMessage.rawValue])
|
||||||
messagePayload.append(tlvData)
|
messagePayload.append(tlvData)
|
||||||
|
|
||||||
let encrypted = try noiseService.encrypt(messagePayload, for: PeerID(str: peerID))
|
let encrypted = try noiseService.encrypt(messagePayload, for: peerID)
|
||||||
|
|
||||||
let packet = BitchatPacket(
|
let packet = BitchatPacket(
|
||||||
type: MessageType.noiseEncrypted.rawValue,
|
type: MessageType.noiseEncrypted.rawValue,
|
||||||
senderID: myPeerIDData,
|
senderID: myPeerIDData,
|
||||||
recipientID: Data(hexString: peerID),
|
recipientID: Data(hexString: peerID.id),
|
||||||
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
|
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
|
||||||
payload: encrypted,
|
payload: encrypted,
|
||||||
signature: nil,
|
signature: nil,
|
||||||
@@ -1924,7 +1926,7 @@ extension BLEService {
|
|||||||
|
|
||||||
private func sendEncrypted(_ packet: BitchatPacket, data: Data, pad: Bool) {
|
private func sendEncrypted(_ packet: BitchatPacket, data: Data, pad: Bool) {
|
||||||
guard let recipientID = packet.recipientID else { return }
|
guard let recipientID = packet.recipientID else { return }
|
||||||
let recipientPeerID = recipientID.hexEncodedString()
|
let recipientPeerID = PeerID(hexData: recipientID)
|
||||||
var sentEncrypted = false
|
var sentEncrypted = false
|
||||||
|
|
||||||
// Per-link limits for the specific peer
|
// Per-link limits for the specific peer
|
||||||
@@ -1944,13 +1946,13 @@ extension BLEService {
|
|||||||
if let pm = peripheralMaxLen, data.count > pm {
|
if let pm = peripheralMaxLen, data.count > pm {
|
||||||
let overhead = 13 + 8 + 8 + 13
|
let overhead = 13 + 8 + 8 + 13
|
||||||
let chunk = max(64, pm - overhead)
|
let chunk = max(64, pm - overhead)
|
||||||
sendFragmentedPacket(packet, pad: pad, maxChunk: chunk, directedOnlyPeer: recipientPeerID)
|
sendFragmentedPacket(packet, pad: pad, maxChunk: chunk, directedOnlyPeer: recipientPeerID.id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let cm = centralMaxLen, data.count > cm {
|
if let cm = centralMaxLen, data.count > cm {
|
||||||
let overhead = 13 + 8 + 8 + 13
|
let overhead = 13 + 8 + 8 + 13
|
||||||
let chunk = max(64, cm - overhead)
|
let chunk = max(64, cm - overhead)
|
||||||
sendFragmentedPacket(packet, pad: pad, maxChunk: chunk, directedOnlyPeer: recipientPeerID)
|
sendFragmentedPacket(packet, pad: pad, maxChunk: chunk, directedOnlyPeer: recipientPeerID.id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1981,7 +1983,7 @@ extension BLEService {
|
|||||||
|
|
||||||
if !sentEncrypted {
|
if !sentEncrypted {
|
||||||
// Flood as last resort with recipient set; link aware
|
// Flood as last resort with recipient set; link aware
|
||||||
sendOnAllLinks(packet: packet, data: data, pad: pad, directedOnlyPeer: recipientPeerID)
|
sendOnAllLinks(packet: packet, data: data, pad: pad, directedOnlyPeer: recipientPeerID.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2095,6 +2097,7 @@ extension BLEService {
|
|||||||
|
|
||||||
// MARK: Directed store-and-forward
|
// MARK: Directed store-and-forward
|
||||||
private func spoolDirectedPacket(_ packet: BitchatPacket, recipientPeerID: String) {
|
private func spoolDirectedPacket(_ packet: BitchatPacket, recipientPeerID: String) {
|
||||||
|
let recipientPeerID = PeerID(str: recipientPeerID)
|
||||||
let msgID = makeMessageID(for: packet)
|
let msgID = makeMessageID(for: packet)
|
||||||
collectionsQueue.async(flags: .barrier) { [weak self] in
|
collectionsQueue.async(flags: .barrier) { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
@@ -2109,8 +2112,8 @@ extension BLEService {
|
|||||||
|
|
||||||
private func flushDirectedSpool() {
|
private func flushDirectedSpool() {
|
||||||
// Move items out and attempt broadcast; if still no links, they'll be re-spooled
|
// Move items out and attempt broadcast; if still no links, they'll be re-spooled
|
||||||
let toSend: [(String, BitchatPacket)] = collectionsQueue.sync(flags: .barrier) {
|
let toSend: [(PeerID, BitchatPacket)] = collectionsQueue.sync(flags: .barrier) {
|
||||||
var out: [(String, BitchatPacket)] = []
|
var out: [(PeerID, BitchatPacket)] = []
|
||||||
let now = Date()
|
let now = Date()
|
||||||
for (recipient, dict) in pendingDirectedRelays {
|
for (recipient, dict) in pendingDirectedRelays {
|
||||||
for (_, entry) in dict {
|
for (_, entry) in dict {
|
||||||
@@ -2401,6 +2404,7 @@ extension BLEService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func handleAnnounce(_ packet: BitchatPacket, from peerID: String) {
|
private func handleAnnounce(_ packet: BitchatPacket, from peerID: String) {
|
||||||
|
let peerID = PeerID(str: peerID)
|
||||||
guard let announcement = AnnouncementPacket.decode(from: packet.payload) else {
|
guard let announcement = AnnouncementPacket.decode(from: packet.payload) else {
|
||||||
SecureLogger.error("❌ Failed to decode announce packet from \(peerID)", category: .session)
|
SecureLogger.error("❌ Failed to decode announce packet from \(peerID)", category: .session)
|
||||||
return
|
return
|
||||||
@@ -2410,7 +2414,7 @@ extension BLEService {
|
|||||||
// This helps detect relayed or spoofed announces. Only warn in release; assert in debug.
|
// This helps detect relayed or spoofed announces. Only warn in release; assert in debug.
|
||||||
let derivedFromKey = PeerID(publicKey: announcement.noisePublicKey).id
|
let derivedFromKey = PeerID(publicKey: announcement.noisePublicKey).id
|
||||||
if derivedFromKey != peerID {
|
if derivedFromKey != peerID {
|
||||||
SecureLogger.warning("⚠️ Announce sender mismatch: derived \(derivedFromKey.prefix(8))… vs packet \(peerID.prefix(8))…", category: .security)
|
SecureLogger.warning("⚠️ Announce sender mismatch: derived \(derivedFromKey.prefix(8))… vs packet \(peerID.id.prefix(8))…", category: .security)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2427,7 +2431,7 @@ extension BLEService {
|
|||||||
if nowMs >= ageThresholdMs {
|
if nowMs >= ageThresholdMs {
|
||||||
let cutoffMs = nowMs - ageThresholdMs
|
let cutoffMs = nowMs - ageThresholdMs
|
||||||
if packet.timestamp < cutoffMs {
|
if packet.timestamp < cutoffMs {
|
||||||
SecureLogger.debug("⏰ Ignoring stale announce from \(peerID.prefix(8))… (age: \(Double(nowMs - packet.timestamp) / 1000.0)s)", category: .session)
|
SecureLogger.debug("⏰ Ignoring stale announce from \(peerID.id.prefix(8))… (age: \(Double(nowMs - packet.timestamp) / 1000.0)s)", category: .session)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2440,11 +2444,11 @@ extension BLEService {
|
|||||||
if packet.signature != nil {
|
if packet.signature != nil {
|
||||||
verifiedAnnounce = noiseService.verifyPacketSignature(packet, publicKey: announcement.signingPublicKey)
|
verifiedAnnounce = noiseService.verifyPacketSignature(packet, publicKey: announcement.signingPublicKey)
|
||||||
if !verifiedAnnounce {
|
if !verifiedAnnounce {
|
||||||
SecureLogger.warning("⚠️ Signature verification for announce failed \(peerID.prefix(8))", category: .security)
|
SecureLogger.warning("⚠️ Signature verification for announce failed \(peerID.id.prefix(8))", category: .security)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let existingKey = existingPeerForVerify?.noisePublicKey, existingKey != announcement.noisePublicKey {
|
if let existingKey = existingPeerForVerify?.noisePublicKey, existingKey != announcement.noisePublicKey {
|
||||||
SecureLogger.warning("⚠️ Announce key mismatch for \(peerID.prefix(8))… — keeping unverified", category: .security)
|
SecureLogger.warning("⚠️ Announce key mismatch for \(peerID.id.prefix(8))… — keeping unverified", category: .security)
|
||||||
verifiedAnnounce = false
|
verifiedAnnounce = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2459,7 +2463,7 @@ extension BLEService {
|
|||||||
|
|
||||||
// Check if this peer is subscribed to us as a central
|
// Check if this peer is subscribed to us as a central
|
||||||
// Note: We can't identify which specific central is which peer without additional mapping
|
// Note: We can't identify which specific central is which peer without additional mapping
|
||||||
let hasCentralSubscription = centralToPeerID.values.contains(PeerID(str: peerID))
|
let hasCentralSubscription = centralToPeerID.values.contains(peerID)
|
||||||
|
|
||||||
// Direct announces arrive with full TTL (no prior hop)
|
// Direct announces arrive with full TTL (no prior hop)
|
||||||
let isDirectAnnounce = (packet.ttl == messageTTL)
|
let isDirectAnnounce = (packet.ttl == messageTTL)
|
||||||
@@ -2477,7 +2481,7 @@ extension BLEService {
|
|||||||
|
|
||||||
// Require verified announce; ignore otherwise (no backward compatibility)
|
// Require verified announce; ignore otherwise (no backward compatibility)
|
||||||
if !verified {
|
if !verified {
|
||||||
SecureLogger.warning("❌ Ignoring unverified announce from \(peerID.prefix(8))…", category: .security)
|
SecureLogger.warning("❌ Ignoring unverified announce from \(peerID.id.prefix(8))…", category: .security)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2496,7 +2500,7 @@ extension BLEService {
|
|||||||
} else {
|
} else {
|
||||||
// New peer or reconnecting peer
|
// New peer or reconnecting peer
|
||||||
peers[peerID] = PeerInfo(
|
peers[peerID] = PeerInfo(
|
||||||
peerID: PeerID(str: peerID),
|
peerID: peerID,
|
||||||
nickname: announcement.nickname,
|
nickname: announcement.nickname,
|
||||||
isConnected: isDirectAnnounce || hasPeripheralConnection || hasCentralSubscription,
|
isConnected: isDirectAnnounce || hasPeripheralConnection || hasCentralSubscription,
|
||||||
noisePublicKey: announcement.noisePublicKey,
|
noisePublicKey: announcement.noisePublicKey,
|
||||||
@@ -2556,9 +2560,9 @@ extension BLEService {
|
|||||||
|
|
||||||
// Only notify of connection for new or reconnected peers when it is a direct announce
|
// Only notify of connection for new or reconnected peers when it is a direct announce
|
||||||
if (packet.ttl == self.messageTTL) && (isNewPeer || isReconnectedPeer) {
|
if (packet.ttl == self.messageTTL) && (isNewPeer || isReconnectedPeer) {
|
||||||
self.delegate?.didConnectToPeer(PeerID(str: peerID))
|
self.delegate?.didConnectToPeer(peerID)
|
||||||
// Schedule initial unicast sync to this peer
|
// Schedule initial unicast sync to this peer
|
||||||
self.gossipSyncManager?.scheduleInitialSyncToPeer(PeerID(str: peerID), delaySeconds: 1.0)
|
self.gossipSyncManager?.scheduleInitialSyncToPeer(peerID, delaySeconds: 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.requestPeerDataPublish()
|
self.requestPeerDataPublish()
|
||||||
@@ -2635,7 +2639,7 @@ extension BLEService {
|
|||||||
accepted = true
|
accepted = true
|
||||||
senderNickname = myNickname
|
senderNickname = myNickname
|
||||||
}
|
}
|
||||||
else if let info = peers[peerID], info.isVerifiedNickname {
|
else if let info = peers[PeerID(str: peerID)], info.isVerifiedNickname {
|
||||||
// Known verified peer path
|
// Known verified peer path
|
||||||
accepted = true
|
accepted = true
|
||||||
senderNickname = info.nickname
|
senderNickname = info.nickname
|
||||||
@@ -2692,7 +2696,7 @@ extension BLEService {
|
|||||||
}
|
}
|
||||||
// Determine if we have a direct link to the sender
|
// Determine if we have a direct link to the sender
|
||||||
let hasDirectLink: Bool = collectionsQueue.sync {
|
let hasDirectLink: Bool = collectionsQueue.sync {
|
||||||
let perUUID = peerToPeripheralUUID[peerID]
|
let perUUID = peerToPeripheralUUID[PeerID(str: peerID)]
|
||||||
let perConnected = perUUID != nil && peripherals[perUUID!]?.isConnected == true
|
let perConnected = perUUID != nil && peripherals[perUUID!]?.isConnected == true
|
||||||
let hasCentral = centralToPeerID.values.contains(PeerID(str: peerID))
|
let hasCentral = centralToPeerID.values.contains(PeerID(str: peerID))
|
||||||
return perConnected || hasCentral
|
return perConnected || hasCentral
|
||||||
@@ -2810,7 +2814,7 @@ extension BLEService {
|
|||||||
private func handleLeave(_ packet: BitchatPacket, from peerID: String) {
|
private func handleLeave(_ packet: BitchatPacket, from peerID: String) {
|
||||||
_ = collectionsQueue.sync(flags: .barrier) {
|
_ = collectionsQueue.sync(flags: .barrier) {
|
||||||
// Remove the peer when they leave
|
// Remove the peer when they leave
|
||||||
peers.removeValue(forKey: peerID)
|
peers.removeValue(forKey: PeerID(str: peerID))
|
||||||
}
|
}
|
||||||
// Remove any stored announcement for sync purposes
|
// Remove any stored announcement for sync purposes
|
||||||
gossipSyncManager?.removeAnnouncementForPeer(PeerID(str: peerID))
|
gossipSyncManager?.removeAnnouncementForPeer(PeerID(str: peerID))
|
||||||
@@ -2900,6 +2904,7 @@ extension BLEService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func sendPendingNoisePayloadsAfterHandshake(for peerID: String) {
|
private func sendPendingNoisePayloadsAfterHandshake(for peerID: String) {
|
||||||
|
let peerID = PeerID(str: peerID)
|
||||||
let payloads = collectionsQueue.sync(flags: .barrier) { () -> [Data] in
|
let payloads = collectionsQueue.sync(flags: .barrier) { () -> [Data] in
|
||||||
let list = pendingNoisePayloadsAfterHandshake[peerID] ?? []
|
let list = pendingNoisePayloadsAfterHandshake[peerID] ?? []
|
||||||
pendingNoisePayloadsAfterHandshake.removeValue(forKey: peerID)
|
pendingNoisePayloadsAfterHandshake.removeValue(forKey: peerID)
|
||||||
@@ -2909,11 +2914,11 @@ extension BLEService {
|
|||||||
SecureLogger.debug("📤 Sending \(payloads.count) pending noise payloads to \(peerID) after handshake", category: .session)
|
SecureLogger.debug("📤 Sending \(payloads.count) pending noise payloads to \(peerID) after handshake", category: .session)
|
||||||
for payload in payloads {
|
for payload in payloads {
|
||||||
do {
|
do {
|
||||||
let encrypted = try noiseService.encrypt(payload, for: PeerID(str: peerID))
|
let encrypted = try noiseService.encrypt(payload, for: peerID)
|
||||||
let packet = BitchatPacket(
|
let packet = BitchatPacket(
|
||||||
type: MessageType.noiseEncrypted.rawValue,
|
type: MessageType.noiseEncrypted.rawValue,
|
||||||
senderID: myPeerIDData,
|
senderID: myPeerIDData,
|
||||||
recipientID: Data(hexString: peerID),
|
recipientID: Data(hexString: peerID.id),
|
||||||
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
|
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
|
||||||
payload: encrypted,
|
payload: encrypted,
|
||||||
signature: nil,
|
signature: nil,
|
||||||
@@ -2927,6 +2932,7 @@ extension BLEService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updatePeerLastSeen(_ peerID: String) {
|
private func updatePeerLastSeen(_ peerID: String) {
|
||||||
|
let peerID = PeerID(str: peerID)
|
||||||
// Use async to avoid deadlock - we don't need immediate consistency for last seen updates
|
// Use async to avoid deadlock - we don't need immediate consistency for last seen updates
|
||||||
collectionsQueue.async(flags: .barrier) {
|
collectionsQueue.async(flags: .barrier) {
|
||||||
if var peer = self.peers[peerID] {
|
if var peer = self.peers[peerID] {
|
||||||
@@ -2939,9 +2945,10 @@ extension BLEService {
|
|||||||
// Debounced disconnect notifier to avoid duplicate disconnect callbacks within a short window
|
// Debounced disconnect notifier to avoid duplicate disconnect callbacks within a short window
|
||||||
private func notifyPeerDisconnectedDebounced(_ peerID: String) {
|
private func notifyPeerDisconnectedDebounced(_ peerID: String) {
|
||||||
let now = Date()
|
let now = Date()
|
||||||
|
let peerID = PeerID(str: peerID)
|
||||||
let last = recentDisconnectNotifies[peerID]
|
let last = recentDisconnectNotifies[peerID]
|
||||||
if last == nil || now.timeIntervalSince(last!) >= TransportConfig.bleDisconnectNotifyDebounceSeconds {
|
if last == nil || now.timeIntervalSince(last!) >= TransportConfig.bleDisconnectNotifyDebounceSeconds {
|
||||||
delegate?.didDisconnectFromPeer(PeerID(str: peerID))
|
delegate?.didDisconnectFromPeer(peerID)
|
||||||
recentDisconnectNotifies[peerID] = now
|
recentDisconnectNotifies[peerID] = now
|
||||||
} else {
|
} else {
|
||||||
// Suppressed duplicate disconnect notification
|
// Suppressed duplicate disconnect notification
|
||||||
@@ -3056,14 +3063,14 @@ extension BLEService {
|
|||||||
// Check if we still have an active BLE connection to this peer
|
// Check if we still have an active BLE connection to this peer
|
||||||
let hasPeripheralConnection = peerToPeripheralUUID[peerID] != nil &&
|
let hasPeripheralConnection = peerToPeripheralUUID[peerID] != nil &&
|
||||||
peripherals[peerToPeripheralUUID[peerID]!]?.isConnected == true
|
peripherals[peerToPeripheralUUID[peerID]!]?.isConnected == true
|
||||||
let hasCentralConnection = centralToPeerID.values.contains(PeerID(str: peerID))
|
let hasCentralConnection = centralToPeerID.values.contains(peerID)
|
||||||
|
|
||||||
// If direct link is gone, mark as not connected (retain entry for reachability)
|
// If direct link is gone, mark as not connected (retain entry for reachability)
|
||||||
if !hasPeripheralConnection && !hasCentralConnection {
|
if !hasPeripheralConnection && !hasCentralConnection {
|
||||||
var updated = peer
|
var updated = peer
|
||||||
updated.isConnected = false
|
updated.isConnected = false
|
||||||
peers[peerID] = updated
|
peers[peerID] = updated
|
||||||
disconnectedPeers.append(peerID)
|
disconnectedPeers.append(peerID.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Cleanup: remove peers that are not connected and past reachability retention
|
// Cleanup: remove peers that are not connected and past reachability retention
|
||||||
@@ -3071,7 +3078,7 @@ extension BLEService {
|
|||||||
if age > retention {
|
if age > retention {
|
||||||
SecureLogger.debug("🗑️ Removing stale peer after reachability window: \(peerID) (\(peer.nickname))", category: .session)
|
SecureLogger.debug("🗑️ Removing stale peer after reachability window: \(peerID) (\(peer.nickname))", category: .session)
|
||||||
// Also remove any stored announcement from sync candidates
|
// Also remove any stored announcement from sync candidates
|
||||||
gossipSyncManager?.removeAnnouncementForPeer(PeerID(str: peerID))
|
gossipSyncManager?.removeAnnouncementForPeer(peerID)
|
||||||
peers.removeValue(forKey: peerID)
|
peers.removeValue(forKey: peerID)
|
||||||
removedOfflineCount += 1
|
removedOfflineCount += 1
|
||||||
}
|
}
|
||||||
@@ -3137,7 +3144,7 @@ extension BLEService {
|
|||||||
}
|
}
|
||||||
// Clean expired directed spooled items
|
// Clean expired directed spooled items
|
||||||
if !self.pendingDirectedRelays.isEmpty {
|
if !self.pendingDirectedRelays.isEmpty {
|
||||||
var cleaned: [String: [String: (packet: BitchatPacket, enqueuedAt: Date)]] = [:]
|
var cleaned: [PeerID: [String: (packet: BitchatPacket, enqueuedAt: Date)]] = [:]
|
||||||
for (recipient, dict) in self.pendingDirectedRelays {
|
for (recipient, dict) in self.pendingDirectedRelays {
|
||||||
let pruned = dict.filter { now.timeIntervalSince($0.value.enqueuedAt) <= TransportConfig.bleDirectedSpoolWindowSeconds }
|
let pruned = dict.filter { now.timeIntervalSince($0.value.enqueuedAt) <= TransportConfig.bleDirectedSpoolWindowSeconds }
|
||||||
if !pruned.isEmpty { cleaned[recipient] = pruned }
|
if !pruned.isEmpty { cleaned[recipient] = pruned }
|
||||||
|
|||||||
Reference in New Issue
Block a user