diff --git a/bitchatTests/BLEServiceCoreTests.swift b/bitchatTests/BLEServiceCoreTests.swift index 927ad178..5989416e 100644 --- a/bitchatTests/BLEServiceCoreTests.swift +++ b/bitchatTests/BLEServiceCoreTests.swift @@ -548,14 +548,25 @@ struct BLEServiceCoreTests { let mallory = NoiseEncryptionService(keychain: MockKeychain()) let alicePeerID = PeerID(publicKey: alice.getStaticPublicKeyData()) - let message1 = try ble._test_noiseInitiateHandshake(with: alicePeerID) + // Establish BLE as responder so the following inbound reconnect is + // not intentionally coalesced by the initiator-completion grace path. + let message1 = try alice.initiateHandshake(with: ble.myPeerID) let message2 = try #require( - try alice.processHandshakeMessage(from: ble.myPeerID, message: message1) + try ble._test_noiseProcessHandshakeMessage( + from: alicePeerID, + message: message1 + ) ) let message3 = try #require( - try ble._test_noiseProcessHandshakeMessage(from: alicePeerID, message: message2) + try alice.processHandshakeMessage( + from: ble.myPeerID, + message: message2 + ) + ) + _ = try ble._test_noiseProcessHandshakeMessage( + from: alicePeerID, + message: message3 ) - _ = try alice.processHandshakeMessage(from: ble.myPeerID, message: message3) await ble._test_drainNoiseMessagePipeline() #expect(ble.canDeliverSecurely(to: alicePeerID)) @@ -577,6 +588,9 @@ struct BLEServiceCoreTests { { outbound.snapshot().contains { $0.type == MessageType.noiseHandshake.rawValue + && PeerID(hexData: $0.senderID) == ble.myPeerID + && $0.payload.count + != NoiseSecurityConstants.xxInitialMessageSize } }, timeout: TestConstants.longTimeout @@ -585,6 +599,9 @@ struct BLEServiceCoreTests { let forgedMessage2 = try #require( outbound.snapshot().first { $0.type == MessageType.noiseHandshake.rawValue + && PeerID(hexData: $0.senderID) == ble.myPeerID + && $0.payload.count + != NoiseSecurityConstants.xxInitialMessageSize }?.payload ) #expect(!ble.canDeliverSecurely(to: alicePeerID)) diff --git a/bitchatTests/Integration/IntegrationTests.swift b/bitchatTests/Integration/IntegrationTests.swift index 11ba1602..e07f0fae 100644 --- a/bitchatTests/Integration/IntegrationTests.swift +++ b/bitchatTests/Integration/IntegrationTests.swift @@ -12,6 +12,7 @@ import Testing @testable import BitFoundation // to avoid unnecessary public's @testable import bitchat +@Suite("Integration Tests", .serialized) struct IntegrationTests { private var helper = TestNetworkHelper() @@ -272,8 +273,18 @@ struct IntegrationTests { // Re-establish Noise handshake explicitly via managers do { let m1 = try helper.noiseManagers["Bob"]!.initiateHandshake(with: helper.nodes["Alice"]!.peerID) - let m2 = try helper.noiseManagers["Alice"]!.handleIncomingHandshake(from: helper.nodes["Bob"]!.peerID, message: m1)! - let m3 = try helper.noiseManagers["Bob"]!.handleIncomingHandshake(from: helper.nodes["Alice"]!.peerID, message: m2)! + let m2 = try #require( + try helper.noiseManagers["Alice"]!.handleIncomingHandshake( + from: helper.nodes["Bob"]!.peerID, + message: m1 + ) + ) + let m3 = try #require( + try helper.noiseManagers["Bob"]!.handleIncomingHandshake( + from: helper.nodes["Alice"]!.peerID, + message: m2 + ) + ) _ = try helper.noiseManagers["Alice"]!.handleIncomingHandshake(from: helper.nodes["Bob"]!.peerID, message: m3) } catch { Issue.record("Failed to re-establish Noise session after restart: \(error)") diff --git a/bitchatTests/Integration/TestNetworkHelper.swift b/bitchatTests/Integration/TestNetworkHelper.swift index d9e0b3a9..0131739c 100644 --- a/bitchatTests/Integration/TestNetworkHelper.swift +++ b/bitchatTests/Integration/TestNetworkHelper.swift @@ -8,6 +8,7 @@ import Foundation import CryptoKit +import Testing @testable import BitFoundation // to avoid unnecessary public's @testable import bitchat @@ -27,9 +28,14 @@ final class TestNetworkHelper { node.mockNickname = name nodes[name] = node - // Create/replace Noise manager for this node + // This synchronous helper directly drives all three XX messages and + // has no transport callback loop for delayed collision recovery. let key = Curve25519.KeyAgreement.PrivateKey() - noiseManagers[name] = NoiseSessionManager(localStaticKey: key, keychain: mockKeychain) + noiseManagers[name] = NoiseSessionManager( + localStaticKey: key, + keychain: mockKeychain, + recentInitiatorCompletionGracePeriod: 0 + ) return node } @@ -108,8 +114,18 @@ final class TestNetworkHelper { let peer2ID = nodes[node2]?.peerID else { return } 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)! + let msg2 = try #require( + try manager2.handleIncomingHandshake( + from: peer1ID, + message: msg1 + ) + ) + let msg3 = try #require( + try manager1.handleIncomingHandshake( + from: peer2ID, + message: msg2 + ) + ) _ = try manager2.handleIncomingHandshake(from: peer1ID, message: msg3) } } diff --git a/bitchatTests/Noise/NoiseProtocolTests.swift b/bitchatTests/Noise/NoiseProtocolTests.swift index f7e8e6be..ac8ef93d 100644 --- a/bitchatTests/Noise/NoiseProtocolTests.swift +++ b/bitchatTests/Noise/NoiseProtocolTests.swift @@ -357,8 +357,18 @@ struct NoiseProtocolTests { @Test func peerRestartDetection() throws { // Establish initial sessions - let aliceManager = NoiseSessionManager(localStaticKey: aliceKey, keychain: mockKeychain) - let bobManager = NoiseSessionManager(localStaticKey: bobKey, keychain: mockKeychain) + // This test explicitly drives the three synchronous XX messages and + // does not exercise the transport's delayed collision recovery. + let aliceManager = NoiseSessionManager( + localStaticKey: aliceKey, + keychain: mockKeychain, + recentInitiatorCompletionGracePeriod: 0 + ) + let bobManager = NoiseSessionManager( + localStaticKey: bobKey, + keychain: mockKeychain, + recentInitiatorCompletionGracePeriod: 0 + ) try establishManagerSessions(aliceManager: aliceManager, bobManager: bobManager) @@ -377,15 +387,24 @@ struct NoiseProtocolTests { let newHandshake1 = try bobManagerRestarted.initiateHandshake(with: bobPeerID) // Alice should accept the new handshake (clearing old session) - let newHandshake2 = try aliceManager.handleIncomingHandshake( - from: alicePeerID, message: newHandshake1) - #expect(newHandshake2 != nil) + let newHandshake2 = try #require( + try aliceManager.handleIncomingHandshake( + from: alicePeerID, + message: newHandshake1 + ) + ) // Complete the new handshake - let newHandshake3 = try bobManagerRestarted.handleIncomingHandshake( - from: bobPeerID, message: newHandshake2!) - #expect(newHandshake3 != nil) - _ = try aliceManager.handleIncomingHandshake(from: alicePeerID, message: newHandshake3!) + let newHandshake3 = try #require( + try bobManagerRestarted.handleIncomingHandshake( + from: bobPeerID, + message: newHandshake2 + ) + ) + _ = try aliceManager.handleIncomingHandshake( + from: alicePeerID, + message: newHandshake3 + ) // Should be able to exchange messages with new sessions let testMessage = Data("After restart".utf8) @@ -543,8 +562,18 @@ struct NoiseProtocolTests { @Test func nonceDesynchronizationCausesRehandshake() throws { // Test that nonce desynchronization leads to proper re-handshake - let aliceManager = NoiseSessionManager(localStaticKey: aliceKey, keychain: mockKeychain) - let bobManager = NoiseSessionManager(localStaticKey: bobKey, keychain: mockKeychain) + // This test explicitly drives the three synchronous XX messages and + // does not exercise the transport's delayed collision recovery. + let aliceManager = NoiseSessionManager( + localStaticKey: aliceKey, + keychain: mockKeychain, + recentInitiatorCompletionGracePeriod: 0 + ) + let bobManager = NoiseSessionManager( + localStaticKey: bobKey, + keychain: mockKeychain, + recentInitiatorCompletionGracePeriod: 0 + ) // Establish sessions try establishManagerSessions(aliceManager: aliceManager, bobManager: bobManager) @@ -572,15 +601,25 @@ struct NoiseProtocolTests { let rehandshake1 = try bobManager.initiateHandshake(with: bobPeerID) // Alice should accept despite having a "valid" (but desynced) session - let rehandshake2 = try aliceManager.handleIncomingHandshake( - from: alicePeerID, message: rehandshake1) - #expect(rehandshake2 != nil, "Alice should accept handshake to fix desync") + let rehandshake2 = try #require( + try aliceManager.handleIncomingHandshake( + from: alicePeerID, + message: rehandshake1 + ), + "Alice should accept handshake to fix desync" + ) // Complete handshake - let rehandshake3 = try bobManager.handleIncomingHandshake( - from: bobPeerID, message: rehandshake2!) - #expect(rehandshake3 != nil) - _ = try aliceManager.handleIncomingHandshake(from: alicePeerID, message: rehandshake3!) + let rehandshake3 = try #require( + try bobManager.handleIncomingHandshake( + from: bobPeerID, + message: rehandshake2 + ) + ) + _ = try aliceManager.handleIncomingHandshake( + from: alicePeerID, + message: rehandshake3 + ) // Verify communication works again let testResynced = Data("Resynced".utf8) diff --git a/bitchatTests/Services/NoiseEncryptionServiceTests.swift b/bitchatTests/Services/NoiseEncryptionServiceTests.swift index 3769fe64..8ef7d71c 100644 --- a/bitchatTests/Services/NoiseEncryptionServiceTests.swift +++ b/bitchatTests/Services/NoiseEncryptionServiceTests.swift @@ -723,7 +723,8 @@ struct NoiseEncryptionServiceTests { message: spoofedMessage1 ) ) - try? await Task.sleep(nanoseconds: 35_000_000) + // Exercise replacement before yielding: the test runner may resume a + // short sleep after the fixed responder deadline under parallel load. _ = try #require( try bob.processHandshakeMessage( from: alicePeerID,