diff --git a/bitchat/Protocols/BitchatProtocol.swift b/bitchat/Protocols/BitchatProtocol.swift index 7b21136a..d7585a97 100644 --- a/bitchat/Protocols/BitchatProtocol.swift +++ b/bitchat/Protocols/BitchatProtocol.swift @@ -162,9 +162,9 @@ enum DeliveryStatus: Codable, Equatable, Hashable { protocol BitchatDelegate: AnyObject { func didReceiveMessage(_ message: BitchatMessage) - func didConnectToPeer(_ peerID: String) - func didDisconnectFromPeer(_ peerID: String) - func didUpdatePeerList(_ peers: [String]) + func didConnectToPeer(_ peerID: PeerID) + func didDisconnectFromPeer(_ peerID: PeerID) + func didUpdatePeerList(_ peers: [PeerID]) // Optional method to check if a fingerprint belongs to a favorite peer func isFavorite(fingerprint: String) -> Bool @@ -172,11 +172,11 @@ protocol BitchatDelegate: AnyObject { func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) // Low-level events for better separation of concerns - func didReceiveNoisePayload(from peerID: String, type: NoisePayloadType, payload: Data, timestamp: Date) + func didReceiveNoisePayload(from peerID: PeerID, type: NoisePayloadType, payload: Data, timestamp: Date) // Bluetooth state updates for user notifications func didUpdateBluetoothState(_ state: CBManagerState) - func didReceivePublicMessage(from peerID: String, nickname: String, content: String, timestamp: Date) + func didReceivePublicMessage(from peerID: PeerID, nickname: String, content: String, timestamp: Date) } // Provide default implementation to make it effectively optional @@ -189,11 +189,11 @@ extension BitchatDelegate { // Default empty implementation } - func didReceiveNoisePayload(from peerID: String, type: NoisePayloadType, payload: Data, timestamp: Date) { + func didReceiveNoisePayload(from peerID: PeerID, type: NoisePayloadType, payload: Data, timestamp: Date) { // Default empty implementation } - func didReceivePublicMessage(from peerID: String, nickname: String, content: String, timestamp: Date) { + func didReceivePublicMessage(from peerID: PeerID, nickname: String, content: String, timestamp: Date) { // Default empty implementation } } diff --git a/bitchat/Services/BLEService.swift b/bitchat/Services/BLEService.swift index 29082e0c..0890fe6f 100644 --- a/bitchat/Services/BLEService.swift +++ b/bitchat/Services/BLEService.swift @@ -60,6 +60,9 @@ final class BLEService: NSObject { var lastSeen: Date } private var peers: [String: PeerInfo] = [:] + private var currentPeerIDs: [PeerID] { + peers.keys.map { PeerID(str: $0) } + } // 4. Efficient Message Deduplication private let messageDeduplicator = MessageDeduplicator() @@ -934,7 +937,7 @@ func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeriph guard let self = self else { return } // Get current peer list (after removal) - let currentPeerIDs = self.collectionsQueue.sync { Array(self.peers.keys) } + let currentPeerIDs = self.collectionsQueue.sync { self.currentPeerIDs } if let peerID = peerID { self.notifyPeerDisconnectedDebounced(peerID.id) @@ -1355,7 +1358,7 @@ extension BLEService: CBPeripheralManagerDelegate { guard let self = self else { return } // Get current peer list (after removal) - let currentPeerIDs = self.collectionsQueue.sync { Array(self.peers.keys) } + let currentPeerIDs = self.collectionsQueue.sync { self.currentPeerIDs } self.notifyPeerDisconnectedDebounced(peerID.id) // Publish snapshots so UnifiedPeerService can refresh icons promptly @@ -2549,11 +2552,11 @@ extension BLEService { guard let self = self else { return } // Get current peer list (after addition) - let currentPeerIDs = self.collectionsQueue.sync { Array(self.peers.keys) } + let currentPeerIDs = self.collectionsQueue.sync { self.currentPeerIDs } // Only notify of connection for new or reconnected peers when it is a direct announce if (packet.ttl == self.messageTTL) && (isNewPeer || isReconnectedPeer) { - self.delegate?.didConnectToPeer(peerID) + self.delegate?.didConnectToPeer(PeerID(str: peerID)) // Schedule initial unicast sync to this peer self.gossipSyncManager?.scheduleInitialSyncToPeer(PeerID(str: peerID), delaySeconds: 1.0) } @@ -2700,7 +2703,7 @@ extension BLEService { let ts = Date(timeIntervalSince1970: Double(packet.timestamp) / 1000) notifyUI { [weak self] in - self?.delegate?.didReceivePublicMessage(from: peerID, nickname: senderNickname, content: content, timestamp: ts) + self?.delegate?.didReceivePublicMessage(from: PeerID(str: peerID), nickname: senderNickname, content: content, timestamp: ts) } } @@ -2738,6 +2741,7 @@ extension BLEService { } private func handleNoiseEncrypted(_ packet: BitchatPacket, from peerID: String) { + let peerID = PeerID(str: peerID) SecureLogger.debug("🔐 handleNoiseEncrypted called for packet from \(peerID)") guard let recipientID = packet.recipientID else { @@ -2752,10 +2756,10 @@ extension BLEService { } // Update lastSeen for the peer we received from (important for private messages) - updatePeerLastSeen(peerID) + updatePeerLastSeen(peerID.id) do { - let decrypted = try noiseService.decrypt(packet.payload, from: PeerID(str: peerID)) + let decrypted = try noiseService.decrypt(packet.payload, from: peerID) guard decrypted.count > 0 else { return } // First byte indicates the payload type @@ -2795,8 +2799,8 @@ extension BLEService { // We received an encrypted message before establishing a session with this peer. // Trigger a handshake so future messages can be decrypted. SecureLogger.debug("🔑 Encrypted message from \(peerID) without session; initiating handshake") - if !noiseService.hasSession(with: PeerID(str: peerID)) { - initiateNoiseHandshake(with: peerID) + if !noiseService.hasSession(with: peerID) { + initiateNoiseHandshake(with: peerID.id) } } catch { SecureLogger.error("❌ Failed to decrypt message from \(peerID): \(error)") @@ -2815,9 +2819,9 @@ extension BLEService { guard let self = self else { return } // Get current peer list (after removal) - let currentPeerIDs = self.collectionsQueue.sync { Array(self.peers.keys) } + let currentPeerIDs = self.collectionsQueue.sync { self.currentPeerIDs } - self.delegate?.didDisconnectFromPeer(peerID) + self.delegate?.didDisconnectFromPeer(PeerID(str: peerID)) self.delegate?.didUpdatePeerList(currentPeerIDs) } } @@ -2937,7 +2941,7 @@ extension BLEService { let now = Date() let last = recentDisconnectNotifies[peerID] if last == nil || now.timeIntervalSince(last!) >= TransportConfig.bleDisconnectNotifyDebounceSeconds { - delegate?.didDisconnectFromPeer(peerID) + delegate?.didDisconnectFromPeer(PeerID(str: peerID)) recentDisconnectNotifies[peerID] = now } else { // Suppressed duplicate disconnect notification @@ -3078,13 +3082,13 @@ extension BLEService { // Update UI if there were direct disconnections or offline removals if !disconnectedPeers.isEmpty || removedOfflineCount > 0 { notifyUI { [weak self] in - guard let self = self else { return } + guard let self else { return } // Get current peer list (after removal) - let currentPeerIDs = self.collectionsQueue.sync { Array(self.peers.keys) } + let currentPeerIDs = self.collectionsQueue.sync { self.currentPeerIDs } for peerID in disconnectedPeers { - self.delegate?.didDisconnectFromPeer(peerID) + self.delegate?.didDisconnectFromPeer(PeerID(str: peerID)) } // Publish snapshots so UnifiedPeerService updates connection/reachability icons self.requestPeerDataPublish() diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 3fe260ea..97a2b4f2 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -223,7 +223,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { private let maxMessages = TransportConfig.meshTimelineCap // Maximum messages before oldest are removed @Published var isConnected = false private var hasNotifiedNetworkAvailable = false - private var recentlySeenPeers: Set = [] + private var recentlySeenPeers: Set = [] private var lastNetworkNotificationTime = Date.distantPast private var networkResetTimer: Timer? = nil private let networkResetGraceSeconds: TimeInterval = TransportConfig.networkResetGraceSeconds // avoid refiring on short drops/reconnects @@ -4449,12 +4449,12 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } // Low-level BLE events - func didReceiveNoisePayload(from peerID: String, type: NoisePayloadType, payload: Data, timestamp: Date) { + func didReceiveNoisePayload(from peerID: PeerID, type: NoisePayloadType, payload: Data, timestamp: Date) { Task { @MainActor in switch type { case .privateMessage: guard let pm = PrivateMessagePacket.decode(from: payload) else { return } - let senderName = unifiedPeerService.getPeer(by: peerID)?.nickname ?? "Unknown" + let senderName = unifiedPeerService.getPeer(by: peerID.id)?.nickname ?? "Unknown" let pmMentions = parseMentions(from: pm.content) let msg = BitchatMessage( id: pm.messageID, @@ -4465,27 +4465,27 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { originalSender: nil, isPrivate: true, recipientNickname: nickname, - senderPeerID: PeerID(str: peerID), + senderPeerID: peerID, mentions: pmMentions.isEmpty ? nil : pmMentions ) handlePrivateMessage(msg) // Send delivery ACK back over BLE - meshService.sendDeliveryAck(for: pm.messageID, to: PeerID(str: peerID)) + meshService.sendDeliveryAck(for: pm.messageID, to: peerID) case .delivered: guard let messageID = String(data: payload, encoding: .utf8) else { return } - if let name = unifiedPeerService.getPeer(by: peerID)?.nickname { - if let messages = privateChats[peerID], let idx = messages.firstIndex(where: { $0.id == messageID }) { - privateChats[peerID]?[idx].deliveryStatus = .delivered(to: name, at: Date()) + if let name = unifiedPeerService.getPeer(by: peerID.id)?.nickname { + if let messages = privateChats[peerID.id], let idx = messages.firstIndex(where: { $0.id == messageID }) { + privateChats[peerID.id]?[idx].deliveryStatus = .delivered(to: name, at: Date()) objectWillChange.send() } } case .readReceipt: guard let messageID = String(data: payload, encoding: .utf8) else { return } - if let name = unifiedPeerService.getPeer(by: peerID)?.nickname { - if let messages = privateChats[peerID], let idx = messages.firstIndex(where: { $0.id == messageID }) { - privateChats[peerID]?[idx].deliveryStatus = .read(by: name, at: Date()) + if let name = unifiedPeerService.getPeer(by: peerID.id)?.nickname { + if let messages = privateChats[peerID.id], let idx = messages.firstIndex(where: { $0.id == messageID }) { + privateChats[peerID.id]?[idx].deliveryStatus = .read(by: name, at: Date()) objectWillChange.send() } } @@ -4496,10 +4496,10 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { let myNoiseHex = meshService.getNoiseService().getStaticPublicKeyData().hexEncodedString().lowercased() guard tlv.noiseKeyHex.lowercased() == myNoiseHex else { return } // Deduplicate: ignore if we've already responded to this nonce for this peer - if let last = lastVerifyNonceByPeer[peerID], last == tlv.nonceA { return } - lastVerifyNonceByPeer[peerID] = tlv.nonceA + if let last = lastVerifyNonceByPeer[peerID.id], last == tlv.nonceA { return } + lastVerifyNonceByPeer[peerID.id] = tlv.nonceA // Record inbound challenge time keyed by stable fingerprint if available - if let fp = getFingerprint(for: peerID) { + if let fp = getFingerprint(for: peerID.id) { lastInboundVerifyChallengeAt[fp] = Date() // If we've already verified this fingerprint locally, treat this as mutual and toast immediately (responder side) if verifiedFingerprints.contains(fp) { @@ -4507,7 +4507,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { let last = lastMutualToastAt[fp] ?? .distantPast if now.timeIntervalSince(last) > 60 { // 1-minute throttle lastMutualToastAt[fp] = now - let name = unifiedPeerService.getPeer(by: peerID)?.nickname ?? resolveNickname(for: peerID) + let name = unifiedPeerService.getPeer(by: peerID.id)?.nickname ?? resolveNickname(for: peerID.id) NotificationService.shared.sendLocalNotification( title: "Mutual verification", body: "You and \(name) verified each other", @@ -4516,24 +4516,24 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } } } - meshService.sendVerifyResponse(to: PeerID(str: peerID), noiseKeyHex: tlv.noiseKeyHex, nonceA: tlv.nonceA) + meshService.sendVerifyResponse(to: peerID, noiseKeyHex: tlv.noiseKeyHex, nonceA: tlv.nonceA) // Silent response: no toast needed on responder case .verifyResponse: guard let resp = VerificationService.shared.parseVerifyResponse(payload) else { return } // Check pending for this peer - guard let pending = pendingQRVerifications[peerID] else { return } + guard let pending = pendingQRVerifications[peerID.id] else { return } guard resp.noiseKeyHex.lowercased() == pending.noiseKeyHex.lowercased(), resp.nonceA == pending.nonceA else { return } // Verify signature with expected sign key let ok = VerificationService.shared.verifyResponseSignature(noiseKeyHex: resp.noiseKeyHex, nonceA: resp.nonceA, signature: resp.signature, signerPublicKeyHex: pending.signKeyHex) if ok { - pendingQRVerifications.removeValue(forKey: peerID) - if let fp = getFingerprint(for: peerID) { + pendingQRVerifications.removeValue(forKey: peerID.id) + if let fp = getFingerprint(for: peerID.id) { let short = fp.prefix(8) SecureLogger.info("🔐 Marking verified fingerprint: \(short)", category: .security) identityManager.setVerified(fingerprint: fp, verified: true) identityManager.forceSave() verifiedFingerprints.insert(fp) - let name = unifiedPeerService.getPeer(by: peerID)?.nickname ?? resolveNickname(for: peerID) + let name = unifiedPeerService.getPeer(by: peerID.id)?.nickname ?? resolveNickname(for: peerID.id) NotificationService.shared.sendLocalNotification( title: "Verified", body: "You verified \(name)", @@ -4552,14 +4552,14 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { ) } } - updateEncryptionStatus(for: peerID) + updateEncryptionStatus(for: peerID.id) } } } } } - func didReceivePublicMessage(from peerID: String, nickname: String, content: String, timestamp: Date) { + func didReceivePublicMessage(from peerID: PeerID, nickname: String, content: String, timestamp: Date) { Task { @MainActor in let normalized = content.trimmingCharacters(in: .whitespacesAndNewlines) let publicMentions = parseMentions(from: normalized) @@ -4572,7 +4572,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { originalSender: nil, isPrivate: false, recipientNickname: nil, - senderPeerID: PeerID(str: peerID), + senderPeerID: peerID, mentions: publicMentions.isEmpty ? nil : publicMentions ) handlePublicMessage(msg) @@ -4622,7 +4622,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // MARK: - Peer Connection Events - func didConnectToPeer(_ peerID: String) { + func didConnectToPeer(_ peerID: PeerID) { SecureLogger.debug("🤝 Peer connected: \(peerID)", category: .session) // Handle all main actor work async @@ -4630,7 +4630,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { isConnected = true // Register ephemeral session with identity manager - identityManager.registerEphemeralSession(peerID: PeerID(str: peerID), handshakeState: .none) + identityManager.registerEphemeralSession(peerID: peerID, handshakeState: .none) // Intentionally do not resend favorites on reconnect. // We only send our npub when a favorite is toggled on, or if our npub changes. @@ -4639,36 +4639,34 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { objectWillChange.send() // Cache mapping to full Noise key for session continuity on disconnect - if let peer = unifiedPeerService.getPeer(by: peerID) { + if let peer = unifiedPeerService.getPeer(by: peerID.id) { let noiseKeyHex = peer.noisePublicKey.hexEncodedString() - shortIDToNoiseKey[peerID] = noiseKeyHex + shortIDToNoiseKey[peerID.id] = noiseKeyHex } // Flush any queued messages for this peer via router - messageRouter.flushOutbox(for: PeerID(str: peerID)) + messageRouter.flushOutbox(for: peerID) } - - // } - func didDisconnectFromPeer(_ peerID: String) { + func didDisconnectFromPeer(_ peerID: PeerID) { SecureLogger.debug("👋 Peer disconnected: \(peerID)", category: .session) // Remove ephemeral session from identity manager - identityManager.removeEphemeralSession(peerID: PeerID(str: peerID)) + identityManager.removeEphemeralSession(peerID: peerID) // If the open PM is tied to this short peer ID, switch UI context to the full Noise key (offline favorite) - var derivedStableKeyHex: String? = shortIDToNoiseKey[peerID] + var derivedStableKeyHex: String? = shortIDToNoiseKey[peerID.id] if derivedStableKeyHex == nil, - let key = meshService.getNoiseService().getPeerPublicKeyData(PeerID(str: peerID)) { + let key = meshService.getNoiseService().getPeerPublicKeyData(peerID) { derivedStableKeyHex = key.hexEncodedString() - shortIDToNoiseKey[peerID] = derivedStableKeyHex + shortIDToNoiseKey[peerID.id] = derivedStableKeyHex } if let current = selectedPrivateChatPeer, current == peerID, let stableKeyHex = derivedStableKeyHex { // Migrate messages view context to stable key so header shows favorite + Nostr globe - if let messages = privateChats[peerID] { + if let messages = privateChats[peerID.id] { if privateChats[stableKeyHex] == nil { privateChats[stableKeyHex] = [] } let existing = Set(privateChats[stableKeyHex]!.map { $0.id }) for msg in messages where !existing.contains(msg.id) { @@ -4688,10 +4686,10 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { privateChats[stableKeyHex]?.append(updated) } privateChats[stableKeyHex]?.sort { $0.timestamp < $1.timestamp } - privateChats.removeValue(forKey: peerID) + privateChats.removeValue(forKey: peerID.id) } - if unreadPrivateMessages.contains(peerID) { - unreadPrivateMessages.remove(peerID) + if unreadPrivateMessages.contains(peerID.id) { + unreadPrivateMessages.remove(peerID.id) unreadPrivateMessages.insert(stableKeyHex) } selectedPrivateChatPeer = stableKeyHex @@ -4706,7 +4704,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Clear sent read receipts for this peer since they'll need to be resent after reconnection // Only clear receipts for messages from this specific peer - if let messages = privateChats[peerID] { + if let messages = privateChats[peerID.id] { for message in messages { // Remove read receipts for messages FROM this peer (not TO this peer) if message.senderPeerID == peerID { @@ -4718,7 +4716,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // } - func didUpdatePeerList(_ peers: [String]) { + func didUpdatePeerList(_ peers: [PeerID]) { // UI updates must run on the main thread. // The delegate callback is not guaranteed to be on the main thread. DispatchQueue.main.async { @@ -4736,7 +4734,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { self.networkResetTimer = nil // Count mesh peers that are connected OR recently reachable via mesh relays let meshPeers = peers.filter { peerID in - self.meshService.isPeerConnected(PeerID(str: peerID)) || self.meshService.isPeerReachable(PeerID(str: peerID)) + self.meshService.isPeerConnected(peerID) || self.meshService.isPeerReachable(peerID) } // Rising-edge only: previously zero peers, now > 0 peers @@ -4762,7 +4760,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Register ephemeral sessions for all connected peers for peerID in peers { - self.identityManager.registerEphemeralSession(peerID: PeerID(str: peerID), handshakeState: .none) + self.identityManager.registerEphemeralSession(peerID: peerID, handshakeState: .none) } // Schedule UI refresh to ensure offline favorites are shown diff --git a/bitchatTests/BLEServiceTests.swift b/bitchatTests/BLEServiceTests.swift index c691ca17..9ae29284 100644 --- a/bitchatTests/BLEServiceTests.swift +++ b/bitchatTests/BLEServiceTests.swift @@ -281,12 +281,12 @@ private final class MockBitchatDelegate: BitchatDelegate { messageHandler(message) } - func didConnectToPeer(_ peerID: String) {} - func didDisconnectFromPeer(_ peerID: String) {} - func didUpdatePeerList(_ peers: [String]) {} + func didConnectToPeer(_ peerID: PeerID) {} + func didDisconnectFromPeer(_ peerID: PeerID) {} + func didUpdatePeerList(_ peers: [PeerID]) {} func isFavorite(fingerprint: String) -> Bool { return false } func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {} - func didReceiveNoisePayload(from peerID: String, type: NoisePayloadType, payload: Data, timestamp: Date) {} + func didReceiveNoisePayload(from peerID: PeerID, type: NoisePayloadType, payload: Data, timestamp: Date) {} func didUpdateBluetoothState(_ state: CBManagerState) {} func didReceivePublicMessage(from peerID: String, nickname: String, content: String, timestamp: Date) {} } diff --git a/bitchatTests/EndToEnd/PrivateChatE2ETests.swift b/bitchatTests/EndToEnd/PrivateChatE2ETests.swift index 31b29b29..47d72dd4 100644 --- a/bitchatTests/EndToEnd/PrivateChatE2ETests.swift +++ b/bitchatTests/EndToEnd/PrivateChatE2ETests.swift @@ -48,7 +48,7 @@ struct PrivateChatE2ETests { // Alice sends private message to Bob alice.sendPrivateMessage( TestConstants.testMessage1, - to: PeerID(str: bob.peerID), + to: bob.peerID, recipientNickname: TestConstants.testNickname2 ) @@ -74,7 +74,7 @@ struct PrivateChatE2ETests { // Alice sends private message to Bob alice.sendPrivateMessage( TestConstants.testMessage1, - to: PeerID(str: bob.peerID), + to: bob.peerID, recipientNickname: TestConstants.testNickname2 ) } @@ -99,7 +99,7 @@ struct PrivateChatE2ETests { alice.sendPrivateMessage( TestConstants.testMessage1, - to: PeerID(str: bob.peerID), + to: bob.peerID, recipientNickname: TestConstants.testNickname2 ) } @@ -119,10 +119,10 @@ struct PrivateChatE2ETests { // Establish encrypted session do { - let handshake1 = try aliceManager.initiateHandshake(with: PeerID(str: bob.peerID)) - let handshake2 = try bobManager.handleIncomingHandshake(from: PeerID(str: alice.peerID), message: handshake1)! - let handshake3 = try aliceManager.handleIncomingHandshake(from: PeerID(str: bob.peerID), message: handshake2)! - _ = try bobManager.handleIncomingHandshake(from: PeerID(str: alice.peerID), message: handshake3) + let handshake1 = try aliceManager.initiateHandshake(with: bob.peerID) + let handshake2 = try bobManager.handleIncomingHandshake(from: alice.peerID, message: handshake1)! + let handshake3 = try aliceManager.handleIncomingHandshake(from: bob.peerID, message: handshake2)! + _ = try bobManager.handleIncomingHandshake(from: alice.peerID, message: handshake3) } catch { Issue.record("Failed to establish Noise session: \(error)") } @@ -135,7 +135,7 @@ struct PrivateChatE2ETests { let message = BitchatMessage(packet.payload), message.isPrivate { do { - let encrypted = try aliceManager.encrypt(packet.payload, for: PeerID(str: bob.peerID)) + let encrypted = try aliceManager.encrypt(packet.payload, for: bob.peerID) let encryptedPacket = BitchatPacket( type: 0x02, // Encrypted message type senderID: packet.senderID, @@ -156,7 +156,7 @@ struct PrivateChatE2ETests { // Decrypt incoming encrypted messages if packet.type == 0x02 { do { - let decrypted = try bobManager.decrypt(packet.payload, from: PeerID(str: alice.peerID)) + let decrypted = try bobManager.decrypt(packet.payload, from: alice.peerID) if let message = BitchatMessage(decrypted) { #expect(message.content == TestConstants.testMessage1) #expect(message.isPrivate) @@ -207,7 +207,7 @@ struct PrivateChatE2ETests { // Alice sends private message to Charlie (through Bob) alice.sendPrivateMessage( TestConstants.testMessage1, - to: PeerID(str: charlie.peerID), + to: charlie.peerID, recipientNickname: TestConstants.testNickname3 ) } diff --git a/bitchatTests/EndToEnd/PublicChatE2ETests.swift b/bitchatTests/EndToEnd/PublicChatE2ETests.swift index 728c7ce9..d3af3774 100644 --- a/bitchatTests/EndToEnd/PublicChatE2ETests.swift +++ b/bitchatTests/EndToEnd/PublicChatE2ETests.swift @@ -183,11 +183,11 @@ struct PublicChatE2ETests { let msg = TestHelpers.createTestMessage( content: TestConstants.testMessage1, sender: TestConstants.testNickname1, - senderPeerID: PeerID(str: alice.peerID) + senderPeerID: alice.peerID ) if let payload = msg.toBinaryPayload() { - let pkt = TestHelpers.createTestPacket(senderID: PeerID(str: alice.peerID), payload: payload, ttl: 2) + let pkt = TestHelpers.createTestPacket(senderID: alice.peerID, payload: payload, ttl: 2) bob.simulateIncomingPacket(pkt) } } @@ -407,7 +407,7 @@ struct PublicChatE2ETests { if let relayPayload = relayMessage.toBinaryPayload() { let relayPacket = BitchatPacket( type: packet.type, - senderID: node.peerID.data(using: .utf8)!, + senderID: node.peerID.id.data(using: .utf8)!, recipientID: packet.recipientID, timestamp: packet.timestamp, payload: relayPayload, diff --git a/bitchatTests/Fragmentation/FragmentationTests.swift b/bitchatTests/Fragmentation/FragmentationTests.swift index c2ddfde1..bdfb82fa 100644 --- a/bitchatTests/Fragmentation/FragmentationTests.swift +++ b/bitchatTests/Fragmentation/FragmentationTests.swift @@ -127,19 +127,19 @@ struct FragmentationTests { extension FragmentationTests { private final class CaptureDelegate: BitchatDelegate { - var publicMessages: [(peerID: String, nickname: String, content: String)] = [] + var publicMessages: [(peerID: PeerID, nickname: String, content: String)] = [] func didReceiveMessage(_ message: BitchatMessage) {} - func didConnectToPeer(_ peerID: String) {} - func didDisconnectFromPeer(_ peerID: String) {} - func didUpdatePeerList(_ peers: [String]) {} + func didConnectToPeer(_ peerID: PeerID) {} + func didDisconnectFromPeer(_ peerID: PeerID) {} + func didUpdatePeerList(_ peers: [PeerID]) {} func isFavorite(fingerprint: String) -> Bool { false } func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {} - func didReceiveNoisePayload(from peerID: String, type: NoisePayloadType, payload: Data, timestamp: Date) {} + func didReceiveNoisePayload(from peerID: PeerID, type: NoisePayloadType, payload: Data, timestamp: Date) {} func didUpdateBluetoothState(_ state: CBManagerState) {} - func didReceivePublicMessage(from peerID: String, nickname: String, content: String, timestamp: Date) { + func didReceivePublicMessage(from peerID: PeerID, nickname: String, content: String, timestamp: Date) { publicMessages.append((peerID, nickname, content)) } - func didReceiveRegionalPublicMessage(from peerID: String, nickname: String, content: String, timestamp: Date) {} + func didReceiveRegionalPublicMessage(from peerID: PeerID, nickname: String, content: String, timestamp: Date) {} } // Helper: build a large message packet (unencrypted public message) diff --git a/bitchatTests/Integration/IntegrationTests.swift b/bitchatTests/Integration/IntegrationTests.swift index e0e9d18d..5e0ec7bf 100644 --- a/bitchatTests/Integration/IntegrationTests.swift +++ b/bitchatTests/Integration/IntegrationTests.swift @@ -484,13 +484,10 @@ final class IntegrationTests: XCTestCase { guard let aliceManager = noiseManagers["Alice"], let bobManager = noiseManagers["Bob"], - let aliceStringPeerID = nodes["Alice"]?.peerID, - let bobStringPeerID = nodes["Bob"]?.peerID else { + let alicePeerID = nodes["Alice"]?.peerID, + let bobPeerID = nodes["Bob"]?.peerID else { return XCTFail("Missing managers or peer IDs") } - - let alicePeerID = PeerID(str: aliceStringPeerID) - let bobPeerID = PeerID(str: bobStringPeerID) // Baseline: encrypt from Alice, decrypt at Bob let plaintext1 = Data("hello-secure".utf8) @@ -592,7 +589,7 @@ final class IntegrationTests: XCTestCase { private func createNode(_ name: String, peerID: PeerID) { let node = MockBluetoothMeshService() - node.myPeerID = peerID.id + node.myPeerID = peerID node.mockNickname = name nodes[name] = node @@ -669,9 +666,9 @@ final class IntegrationTests: XCTestCase { let peer1ID = nodes[node1]?.peerID, let peer2ID = nodes[node2]?.peerID else { return } - let msg1 = try manager1.initiateHandshake(with: PeerID(str: peer2ID)) - let msg2 = try manager2.handleIncomingHandshake(from: PeerID(str: peer1ID), message: msg1)! - let msg3 = try manager1.handleIncomingHandshake(from: PeerID(str: peer2ID), message: msg2)! - _ = try manager2.handleIncomingHandshake(from: PeerID(str: peer1ID), message: msg3) + let msg1 = try manager1.initiateHandshake(with: peer2ID) + let msg2 = try manager2.handleIncomingHandshake(from: peer1ID, message: msg1)! + let msg3 = try manager1.handleIncomingHandshake(from: peer2ID, message: msg2)! + _ = try manager2.handleIncomingHandshake(from: peer1ID, message: msg3) } } diff --git a/bitchatTests/Mocks/MockBLEService.swift b/bitchatTests/Mocks/MockBLEService.swift index c84c5d2f..8f40550f 100644 --- a/bitchatTests/Mocks/MockBLEService.swift +++ b/bitchatTests/Mocks/MockBLEService.swift @@ -32,7 +32,7 @@ final class MockBLEService: NSObject { // MARK: - Properties matching BLEService weak var delegate: BitchatDelegate? - var myPeerID: String = "MOCK1234" + var myPeerID: PeerID = "MOCK1234" var myNickname: String = "MockUser" private let mockKeychain = MockKeychain() @@ -40,7 +40,7 @@ final class MockBLEService: NSObject { // Test-specific properties var sentMessages: [(message: BitchatMessage, packet: BitchatPacket)] = [] var sentPackets: [BitchatPacket] = [] - var connectedPeers: Set = [] + var connectedPeers: Set = [] var messageDeliveryHandler: ((BitchatMessage) -> Void)? var packetDeliveryHandler: ((BitchatPacket) -> Void)? @@ -54,7 +54,7 @@ final class MockBLEService: NSObject { return myNickname } - var peerID: String { + var peerID: PeerID { return myPeerID } @@ -72,8 +72,8 @@ final class MockBLEService: NSObject { // MARK: - In-memory test bus (for E2E/Integration) /// Global per-process bus for deterministic routing in tests. - private static var registry: [String: MockBLEService] = [:] - private static var adjacency: [String: Set] = [:] + private static var registry: [PeerID: MockBLEService] = [:] + private static var adjacency: [PeerID: Set] = [:] /// Clears global bus state. Call from test `setUp()`. static func resetTestBus() { @@ -94,7 +94,7 @@ final class MockBLEService: NSObject { } /// Adds an undirected edge between two peerIDs. - private static func connectPeers(_ a: String, _ b: String) { + private static func connectPeers(_ a: PeerID, _ b: PeerID) { var setA = adjacency[a] ?? [] setA.insert(b) adjacency[a] = setA @@ -104,7 +104,7 @@ final class MockBLEService: NSObject { } /// Removes an undirected edge between two peerIDs. - private static func disconnectPeers(_ a: String, _ b: String) { + private static func disconnectPeers(_ a: PeerID, _ b: PeerID) { if var setA = adjacency[a] { setA.remove(b); adjacency[a] = setA } if var setB = adjacency[b] { setB.remove(a); adjacency[b] = setB } } @@ -117,7 +117,7 @@ final class MockBLEService: NSObject { // Mock implementation - do nothing } - func isPeerConnected(_ peerID: String) -> Bool { + func isPeerConnected(_ peerID: PeerID) -> Bool { return connectedPeers.contains(peerID) } @@ -125,15 +125,15 @@ final class MockBLEService: NSObject { "MockPeer_\(peerID)" } - func getPeerNicknames() -> [String: String] { - var nicknames: [String: String] = [:] + func getPeerNicknames() -> [PeerID: String] { + var nicknames: [PeerID: String] = [:] for peer in connectedPeers { nicknames[peer] = "MockPeer_\(peer)" } return nicknames } - func getPeers() -> [String: String] { + func getPeers() -> [PeerID: String] { return getPeerNicknames() } @@ -147,14 +147,14 @@ final class MockBLEService: NSObject { originalSender: nil, isPrivate: recipientID != nil, recipientNickname: nil, - senderPeerID: PeerID(str: myPeerID), + senderPeerID: myPeerID, mentions: mentions.isEmpty ? nil : mentions ) if let payload = message.toBinaryPayload() { let packet = BitchatPacket( type: 0x01, - senderID: myPeerID.data(using: .utf8)!, + senderID: myPeerID.id.data(using: .utf8)!, recipientID: recipientID?.data(using: .utf8), timestamp: UInt64(Date().timeIntervalSince1970 * 1000), payload: payload, @@ -182,7 +182,7 @@ final class MockBLEService: NSObject { } } - func sendPrivateMessage(_ content: String, to recipientPeerID: String, recipientNickname: String, messageID: String) { + func sendPrivateMessage(_ content: String, to recipientPeerID: PeerID, recipientNickname: String, messageID: String) { let message = BitchatMessage( id: messageID, sender: myNickname, @@ -192,15 +192,15 @@ final class MockBLEService: NSObject { originalSender: nil, isPrivate: true, recipientNickname: recipientNickname, - senderPeerID: PeerID(str: myPeerID), + senderPeerID: myPeerID, mentions: nil ) if let payload = message.toBinaryPayload() { let packet = BitchatPacket( type: 0x01, - senderID: myPeerID.data(using: .utf8)!, - recipientID: recipientPeerID.data(using: .utf8)!, + senderID: myPeerID.id.data(using: .utf8)!, + recipientID: recipientPeerID.id.data(using: .utf8)!, timestamp: UInt64(Date().timeIntervalSince1970 * 1000), payload: payload, signature: nil, @@ -277,7 +277,7 @@ final class MockBLEService: NSObject { // MARK: - Test Helper Methods - func simulateConnectedPeer(_ peerID: String) { + func simulateConnectedPeer(_ peerID: PeerID) { registerIfNeeded() MockBLEService.connectPeers(myPeerID, peerID) connectedPeers.insert(peerID) @@ -285,7 +285,7 @@ final class MockBLEService: NSObject { delegate?.didUpdatePeerList(Array(connectedPeers)) } - func simulateDisconnectedPeer(_ peerID: String) { + func simulateDisconnectedPeer(_ peerID: PeerID) { MockBLEService.disconnectPeers(myPeerID, peerID) connectedPeers.remove(peerID) delegate?.didDisconnectFromPeer(peerID) @@ -336,14 +336,14 @@ final class MockBLEService: NSObject { packetDeliveryHandler?(packet) } - func getConnectedPeers() -> [String] { + func getConnectedPeers() -> [PeerID] { return Array(connectedPeers) } // MARK: - Compatibility methods for old tests func sendPrivateMessage(_ content: String, to recipientPeerID: PeerID, recipientNickname: String, messageID: String? = nil) { - sendPrivateMessage(content, to: recipientPeerID.id, recipientNickname: recipientNickname, messageID: messageID ?? UUID().uuidString) + sendPrivateMessage(content, to: recipientPeerID, recipientNickname: recipientNickname, messageID: messageID ?? UUID().uuidString) } } @@ -355,7 +355,7 @@ typealias MockSimplifiedBluetoothService = MockBLEService extension MockBLEService { convenience init(peerID: PeerID, nickname: String) { self.init() - myPeerID = peerID.id + myPeerID = peerID mockNickname = nickname }