diff --git a/Tests/NIOSSLTests/ClientSNITests.swift b/Tests/NIOSSLTests/ClientSNITests.swift index 2bd7aef8..48cf56d5 100644 --- a/Tests/NIOSSLTests/ClientSNITests.swift +++ b/Tests/NIOSSLTests/ClientSNITests.swift @@ -19,16 +19,6 @@ import NIOTLS import XCTest class ClientSNITests: XCTestCase { - static var cert: NIOSSLCertificate! - static var key: NIOSSLPrivateKey! - - override class func setUp() { - super.setUp() - let (cert, key) = generateSelfSignedCert() - NIOSSLIntegrationTest.cert = cert - NIOSSLIntegrationTest.key = key - } - private func configuredSSLContext() throws -> NIOSSLContext { var config = TLSConfiguration.makeServerConfiguration( certificateChain: [.certificate(NIOSSLIntegrationTest.cert)], @@ -48,15 +38,16 @@ class ClientSNITests: XCTestCase { } let sniPromise: EventLoopPromise = group.next().makePromise() - let sniHandler = ByteToMessageHandler( - SNIHandler { - sniPromise.succeed($0) - return group.next().makeSucceededFuture(()) - } - ) let serverChannel = try serverTLSChannel( context: context, - preHandlers: [sniHandler], + preHandlers: [ + ByteToMessageHandler( + SNIHandler { + sniPromise.succeed($0) + return group.next().makeSucceededFuture(()) + } + ) + ], postHandlers: [], group: group ) diff --git a/Tests/NIOSSLTests/CustomPrivateKeyTests.swift b/Tests/NIOSSLTests/CustomPrivateKeyTests.swift index ee89d0ff..5ab7c922 100644 --- a/Tests/NIOSSLTests/CustomPrivateKeyTests.swift +++ b/Tests/NIOSSLTests/CustomPrivateKeyTests.swift @@ -30,7 +30,9 @@ import CNIOBoringSSL #endif // This is a helper that lets us work with an EVP_PKEY. -private final class CustomPKEY { +// +// This type is thread-safe: it doesn't perform any mutation of the underlying object. +private final class CustomPKEY: @unchecked Sendable { private let ref: OpaquePointer init(from key: NIOSSLPrivateKey) { @@ -164,27 +166,35 @@ private final class CustomKeyImmediateResult: NIOSSLCustomPrivateKey, Hashable { let backing: CustomPKEY let signatureAlgorithms: [SignatureAlgorithm] let expectedChannel: Channel - var signCallCount: Int - var decryptCallCount: Int + let _signCallCount: NIOLockedValueBox + let _decryptCallCount: NIOLockedValueBox + + var signCallCount: Int { + self._signCallCount.withLockedValue { $0 } + } + + var decryptCallCount: Int { + self._decryptCallCount.withLockedValue { $0 } + } fileprivate init(_ backing: CustomPKEY, signatureAlgorithms: [SignatureAlgorithm], expectedChannel: Channel) { self.backing = backing self.signatureAlgorithms = signatureAlgorithms self.expectedChannel = expectedChannel - self.signCallCount = 0 - self.decryptCallCount = 0 + self._signCallCount = .init(0) + self._decryptCallCount = .init(0) } func sign(channel: Channel, algorithm: SignatureAlgorithm, data: ByteBuffer) -> EventLoopFuture { XCTAssertTrue(channel === self.expectedChannel) XCTAssertTrue(self.signatureAlgorithms.contains(algorithm)) - self.signCallCount += 1 + self._signCallCount.withLockedValue { $0 += 1 } return channel.eventLoop.makeSucceededFuture(self.backing.sign(algorithm: algorithm, data: data)) } func decrypt(channel: Channel, data: ByteBuffer) -> EventLoopFuture { XCTAssertTrue(channel === self.expectedChannel) - self.decryptCallCount += 1 + self._decryptCallCount.withLockedValue { $0 += 1 } return channel.eventLoop.makeSucceededFuture(self.backing.decrypt(data: data)) } @@ -202,15 +212,22 @@ private final class CustomKeyDelayedCompletion: NIOSSLCustomPrivateKey, Hashable let backing: CustomPKEY let signatureAlgorithms: [SignatureAlgorithm] let expectedChannel: Channel - var pendingSigningEvents: [EventLoopPromise] - var pendingDecryptionEvents: [EventLoopPromise] + let _pendingSigningEvents: NIOLockedValueBox<[EventLoopPromise]> + let _pendingDecryptionEvents: NIOLockedValueBox<[EventLoopPromise]> + + var pendingSigningEvents: [EventLoopPromise] { + self._pendingSigningEvents.withLockedValue { $0 } + } + var pendingDecryptionEvents: [EventLoopPromise] { + self._pendingDecryptionEvents.withLockedValue { $0 } + } fileprivate init(_ backing: CustomPKEY, signatureAlgorithms: [SignatureAlgorithm], expectedChannel: Channel) { self.backing = backing self.signatureAlgorithms = signatureAlgorithms self.expectedChannel = expectedChannel - self.pendingSigningEvents = [] - self.pendingDecryptionEvents = [] + self._pendingSigningEvents = .init([]) + self._pendingDecryptionEvents = .init([]) } func sign(channel: Channel, algorithm: SignatureAlgorithm, data: ByteBuffer) -> EventLoopFuture { @@ -218,7 +235,7 @@ private final class CustomKeyDelayedCompletion: NIOSSLCustomPrivateKey, Hashable XCTAssertTrue(self.signatureAlgorithms.contains(algorithm)) let promise = channel.eventLoop.makePromise(of: Void.self) - self.pendingSigningEvents.append(promise) + self._pendingSigningEvents.withLockedValue { $0.append(promise) } return promise.futureResult.map { self.backing.sign(algorithm: algorithm, data: data) } @@ -228,7 +245,7 @@ private final class CustomKeyDelayedCompletion: NIOSSLCustomPrivateKey, Hashable XCTAssertTrue(channel === self.expectedChannel) let promise = channel.eventLoop.makePromise(of: Void.self) - self.pendingDecryptionEvents.append(promise) + self._pendingDecryptionEvents.withLockedValue { $0.append(promise) } return promise.futureResult.map { self.backing.decrypt(data: data) } diff --git a/Tests/NIOSSLTests/NIOSSLALPNTest.swift b/Tests/NIOSSLTests/NIOSSLALPNTest.swift index 1cfcab0e..8b42dfe7 100644 --- a/Tests/NIOSSLTests/NIOSSLALPNTest.swift +++ b/Tests/NIOSSLTests/NIOSSLALPNTest.swift @@ -25,16 +25,6 @@ internal import CNIOBoringSSL #endif class NIOSSLALPNTest: XCTestCase { - static var cert: NIOSSLCertificate! - static var key: NIOSSLPrivateKey! - - override class func setUp() { - super.setUp() - let (cert, key) = generateSelfSignedCert() - NIOSSLIntegrationTest.cert = cert - NIOSSLIntegrationTest.key = key - } - private func configuredSSLContextWithAlpnProtocols(protocols: [String]) throws -> NIOSSLContext { var config = TLSConfiguration.makeServerConfiguration( certificateChain: [.certificate(NIOSSLIntegrationTest.cert)], diff --git a/Tests/NIOSSLTests/NIOSSLIntegrationTest.swift b/Tests/NIOSSLTests/NIOSSLIntegrationTest.swift index b2e4a432..2b2899b6 100644 --- a/Tests/NIOSSLTests/NIOSSLIntegrationTest.swift +++ b/Tests/NIOSSLTests/NIOSSLIntegrationTest.swift @@ -74,7 +74,7 @@ internal func interactInMemory( } } -private final class SimpleEchoServer: ChannelInboundHandler { +private final class SimpleEchoServer: ChannelInboundHandler, Sendable { public typealias InboundIn = ByteBuffer public typealias OutboundOut = ByteBuffer @@ -111,7 +111,7 @@ internal final class PromiseOnReadHandler: ChannelInboundHandler { } } -private final class PromiseOnChildChannelInitHandler: ChannelInboundHandler { +private final class PromiseOnChildChannelInitHandler: ChannelInboundHandler, Sendable { typealias InboundIn = ByteBuffer private let promise: EventLoopPromise @@ -126,7 +126,7 @@ private final class PromiseOnChildChannelInitHandler: ChannelInboundHandler { } } -private final class ChannelInactiveHandler: ChannelInboundHandler { +private final class ChannelInactiveHandler: ChannelInboundHandler, Sendable { typealias InboundIn = ByteBuffer private let promise: EventLoopPromise @@ -186,11 +186,6 @@ private final class ShutdownVerificationHandler: ChannelInboundHandler { } } - public func waitForEvent() { - // We always notify it with a success so just force it with ! - try! promise.futureResult.wait() - } - public func channelInactive(context: ChannelHandlerContext) { switch shutdownEvent { case .input: @@ -235,14 +230,17 @@ private final class ReadRecordingHandler: ChannelInboundHandler { } } -private final class WriteCountingHandler: ChannelOutboundHandler { +private final class WriteCountingHandler: ChannelOutboundHandler, Sendable { public typealias OutboundIn = Any public typealias OutboundOut = Any - public var writeCount = 0 + public var writeCount: Int { + self._writeCount.withLockedValue { $0 } + } + private let _writeCount: NIOLockedValueBox = .init(0) public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise?) { - writeCount += 1 + self._writeCount.withLockedValue { $0 += 1 } context.write(data, promise: promise) } } @@ -281,40 +279,43 @@ public final class EventRecorderHandler: ChannelInboundHandler wh } } - public var events: [RecordedEvents] = [] + public var events: [RecordedEvents] { + self._events.withLockedValue { $0 } + } + private let _events: NIOLockedValueBox<[RecordedEvents]> = .init([]) public func channelRegistered(context: ChannelHandlerContext) { - events.append(.Registered) + self._events.withLockedValue { $0.append(.Registered) } context.fireChannelRegistered() } public func channelUnregistered(context: ChannelHandlerContext) { - events.append(.Unregistered) + self._events.withLockedValue { $0.append(.Unregistered) } context.fireChannelUnregistered() } public func channelActive(context: ChannelHandlerContext) { - events.append(.Active) + self._events.withLockedValue { $0.append(.Active) } context.fireChannelActive() } public func channelInactive(context: ChannelHandlerContext) { - events.append(.Inactive) + self._events.withLockedValue { $0.append(.Inactive) } context.fireChannelInactive() } public func channelRead(context: ChannelHandlerContext, data: NIOAny) { - events.append(.Read) + self._events.withLockedValue { $0.append(.Read) } context.fireChannelRead(data) } public func channelReadComplete(context: ChannelHandlerContext) { - events.append(.ReadComplete) + self._events.withLockedValue { $0.append(.ReadComplete) } context.fireChannelReadComplete() } public func channelWritabilityChanged(context: ChannelHandlerContext) { - events.append(.WritabilityChanged) + self._events.withLockedValue { $0.append(.WritabilityChanged) } context.fireChannelWritabilityChanged() } @@ -323,13 +324,18 @@ public final class EventRecorderHandler: ChannelInboundHandler wh context.fireUserInboundEventTriggered(event) return } - events.append(.UserEvent(ourEvent)) + self._events.withLockedValue { $0.append(.UserEvent(ourEvent)) } } } -private class ChannelActiveWaiter: ChannelInboundHandler { +extension EventRecorderHandler: Sendable where UserEventType: Sendable {} + +extension EventRecorderHandler.RecordedEvents: Sendable where UserEventType: Sendable {} + +private final class ChannelActiveWaiter: ChannelInboundHandler, Sendable { public typealias InboundIn = Any - private var activePromise: EventLoopPromise + + private let activePromise: EventLoopPromise public init(promise: EventLoopPromise) { activePromise = promise @@ -368,7 +374,7 @@ private class WriteDelayHandler: ChannelOutboundHandler { internal func serverTLSChannel( context: NIOSSLContext, - handlers: [ChannelHandler], + handlers: @autoclosure @escaping @Sendable () -> [ChannelHandler], group: EventLoopGroup, file: StaticString = #filePath, line: UInt = #line @@ -377,7 +383,7 @@ internal func serverTLSChannel( serverTLSChannel( context: context, preHandlers: [], - postHandlers: handlers, + postHandlers: handlers(), group: group, file: file, line: line @@ -389,10 +395,13 @@ internal func serverTLSChannel( internal func serverTLSChannel( context: NIOSSLContext, - preHandlers: [ChannelHandler], - postHandlers: [ChannelHandler], + preHandlers: @autoclosure @escaping @Sendable () -> [ChannelHandler], + postHandlers: @autoclosure @escaping @Sendable () -> [ChannelHandler], group: EventLoopGroup, - customVerificationCallback: NIOSSLCustomVerificationCallback? = nil, + customVerificationCallback: ( + @Sendable ([NIOSSLCertificate], EventLoopPromise) -> + Void + )? = nil, file: StaticString = #filePath, line: UInt = #line ) throws -> Channel { @@ -400,18 +409,18 @@ internal func serverTLSChannel( ServerBootstrap(group: group) .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) .childChannelInitializer { channel in - let results = preHandlers.map { channel.pipeline.addHandler($0) } - return EventLoopFuture.andAllSucceed(results, on: channel.eventLoop).map { + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.addHandlers(preHandlers()) + + let handler: NIOSSLHandler if let verify = customVerificationCallback { - return NIOSSLServerHandler(context: context, customVerificationCallback: verify) + handler = NIOSSLServerHandler(context: context, customVerificationCallback: verify) } else { - return NIOSSLServerHandler(context: context) + handler = NIOSSLServerHandler(context: context) } - }.flatMap { - channel.pipeline.addHandler($0) - }.flatMap { - let results = postHandlers.map { channel.pipeline.addHandler($0) } - return EventLoopFuture.andAllSucceed(results, on: channel.eventLoop) + try channel.pipeline.syncOperations.addHandler(handler) + + try channel.pipeline.syncOperations.addHandlers(postHandlers()) } }.bind(host: "127.0.0.1", port: 0).wait(), file: file, @@ -425,8 +434,8 @@ typealias SendableAdditionalPeerCertificateVerificationCallback = @Sendable (NIO internal func clientTLSChannel( context: NIOSSLContext, additionalPeerCertificateVerificationCallback: SendableAdditionalPeerCertificateVerificationCallback? = nil, - preHandlers: [ChannelHandler], - postHandlers: [ChannelHandler], + preHandlers: @autoclosure @escaping @Sendable () -> [ChannelHandler], + postHandlers: @autoclosure @escaping @Sendable () -> [ChannelHandler], group: EventLoopGroup, connectingTo: SocketAddress, serverHostname: String? = nil, @@ -452,18 +461,19 @@ internal func clientTLSChannel( } @available(*, deprecated, message: "just for testing the deprecated functionality") -private struct DeprecatedTLSProviderForTests: NIOClientTLSProvider { +private struct DeprecatedTLSProviderForTests: NIOClientTLSProvider, Sendable { public typealias Bootstrap = Bootstrap let context: NIOSSLContext let serverHostname: String? - let verificationCallback: NIOSSLVerificationCallback + let verificationCallback: @Sendable (NIOSSLVerificationResult, NIOSSLCertificate) -> NIOSSLVerificationResult @available(*, deprecated, renamed: "init(context:serverHostname:customVerificationCallback:)") public init( context: NIOSSLContext, serverHostname: String?, - verificationCallback: @escaping NIOSSLVerificationCallback + verificationCallback: @escaping @Sendable (NIOSSLVerificationResult, NIOSSLCertificate) -> + NIOSSLVerificationResult ) { self.context = context self.serverHostname = serverHostname @@ -493,12 +503,12 @@ private struct DeprecatedTLSProviderForTests [ChannelHandler], + postHandlers: @autoclosure @escaping @Sendable () -> [ChannelHandler], group: EventLoopGroup, connectingTo: SocketAddress, serverHostname: String? = nil, - verificationCallback: @escaping NIOSSLVerificationCallback, + verificationCallback: @escaping @Sendable (NIOSSLVerificationResult, NIOSSLCertificate) -> NIOSSLVerificationResult, file: StaticString = #filePath, line: UInt = #line ) throws -> Channel { @@ -518,12 +528,13 @@ internal func clientTLSChannel( internal func clientTLSChannel( context: NIOSSLContext, - preHandlers: [ChannelHandler], - postHandlers: [ChannelHandler], + preHandlers: @autoclosure @escaping @Sendable () -> [ChannelHandler], + postHandlers: @autoclosure @escaping @Sendable () -> [ChannelHandler], group: EventLoopGroup, connectingTo: SocketAddress, serverHostname: String? = nil, - customVerificationCallback: @escaping NIOSSLCustomVerificationCallback, + customVerificationCallback: @escaping @Sendable ([NIOSSLCertificate], EventLoopPromise) -> + Void, file: StaticString = #filePath, line: UInt = #line ) throws -> Channel { @@ -547,8 +558,8 @@ internal func clientTLSChannel( private func _clientTLSChannel( context: NIOSSLContext, - preHandlers: [ChannelHandler], - postHandlers: [ChannelHandler], + preHandlers: @escaping @Sendable () -> [ChannelHandler], + postHandlers: @escaping @Sendable () -> [ChannelHandler], group: EventLoopGroup, connectingTo: SocketAddress, tlsFactory: @escaping () -> TLS, @@ -562,13 +573,16 @@ private func _clientTLSChannel( return try assertNoThrowWithValue( bootstrap .channelInitializer { channel in - channel.pipeline.addHandlers(postHandlers) + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.addHandlers(postHandlers()) + } } .enableTLS() .connect(to: connectingTo) .flatMap { channel in - channel.pipeline.addHandlers(preHandlers, position: .first).map { - channel + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.addHandlers(preHandlers(), position: .first) + return channel } } .wait(), @@ -586,7 +600,7 @@ extension EventLoopFuture { self.whenComplete { result in switch result { case .success(let value): - promise.succeed(value) + promise.assumeIsolated().succeed(value) case .failure(let error): promise.fail(error) } @@ -601,24 +615,26 @@ extension EventLoopFuture { } class NIOSSLIntegrationTest: XCTestCase { - static var cert: NIOSSLCertificate! - static var key: NIOSSLPrivateKey! - static var encryptedKeyPath: String! + private static let certAndKey = generateSelfSignedCert() + static var cert: NIOSSLCertificate { Self.certAndKey.0 } + static var key: NIOSSLPrivateKey { Self.certAndKey.1 } override class func setUp() { super.setUp() guard boringSSLIsInitialized else { fatalError() } - let (cert, key) = generateSelfSignedCert() - NIOSSLIntegrationTest.cert = cert - NIOSSLIntegrationTest.key = key - NIOSSLIntegrationTest.encryptedKeyPath = try! keyInFile( + } + + private static func withEncryptedKeyPath( + _ body: (String) throws -> ReturnType + ) throws -> ReturnType { + let path = try keyInFile( key: NIOSSLIntegrationTest.key, passphrase: "thisisagreatpassword" ) - } - - override class func tearDown() { - _ = unlink(NIOSSLIntegrationTest.encryptedKeyPath) + defer { + unlink(path) + } + return try body(path) } private func configuredSSLContext( @@ -635,27 +651,6 @@ class NIOSSLIntegrationTest: XCTestCase { return try assertNoThrowWithValue(NIOSSLContext(configuration: config), file: file, line: line) } - private func configuredSSLContext( - passphraseCallback: @escaping NIOSSLPassphraseCallback, - file: StaticString = #filePath, - line: UInt = #line - ) throws -> NIOSSLContext - where T.Element == UInt8 { - let privateKey = try NIOSSLPrivateKey(file: NIOSSLIntegrationTest.encryptedKeyPath, format: .pem) { closure in - closure("thisisagreatpassword".utf8) - } - var config = TLSConfiguration.makeServerConfiguration( - certificateChain: [.certificate(NIOSSLIntegrationTest.cert)], - privateKey: .privateKey(privateKey) - ) - config.trustRoots = .certificates([NIOSSLIntegrationTest.cert]) - return try assertNoThrowWithValue( - NIOSSLContext(configuration: config, passphraseCallback: passphraseCallback), - file: file, - line: line - ) - } - private func configuredClientContext( file: StaticString = #filePath, line: UInt = #line @@ -867,20 +862,15 @@ class NIOSSLIntegrationTest: XCTestCase { let completionPromise: EventLoopPromise = group.next().makePromise() - let preHandlers: [ChannelHandler] = [] - let postHandlers = [SimpleEchoServer()] let serverChannel: Channel = try ServerBootstrap(group: group) .childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true) // Important! .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) .childChannelInitializer { channel in - let results = preHandlers.map { channel.pipeline.addHandler($0) } - return EventLoopFuture.andAllSucceed(results, on: channel.eventLoop).map { - NIOSSLServerHandler(context: context) - }.flatMap { - channel.pipeline.addHandler($0) - }.flatMap { - let results = postHandlers.map { channel.pipeline.addHandler($0) } - return EventLoopFuture.andAllSucceed(results, on: channel.eventLoop) + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.addHandlers( + NIOSSLServerHandler(context: context), + SimpleEchoServer() + ) } } .bind(host: "127.0.0.1", port: 0) @@ -923,23 +913,16 @@ class NIOSSLIntegrationTest: XCTestCase { let completionPromise: EventLoopPromise = group.next().makePromise() let childChannelInitPromise: EventLoopPromise = group.next().makePromise() - let preHandlers: [ChannelHandler] = [] - let postHandlers: [ChannelHandler] = [ - PromiseOnChildChannelInitHandler(promise: childChannelInitPromise), - SimpleEchoServer(), - ] let serverChannel: Channel = try ServerBootstrap(group: group) .childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true) // Important! .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) .childChannelInitializer { channel in - let results = preHandlers.map { channel.pipeline.addHandler($0) } - return EventLoopFuture.andAllSucceed(results, on: channel.eventLoop).map { - NIOSSLServerHandler(context: context) - }.flatMap { - channel.pipeline.addHandler($0) - }.flatMap { - let results = postHandlers.map { channel.pipeline.addHandler($0) } - return EventLoopFuture.andAllSucceed(results, on: channel.eventLoop) + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.addHandlers( + NIOSSLServerHandler(context: context), + PromiseOnChildChannelInitHandler(promise: childChannelInitPromise), + SimpleEchoServer() + ) } } .bind(host: "127.0.0.1", port: 0) @@ -949,16 +932,16 @@ class NIOSSLIntegrationTest: XCTestCase { } let clientChannelInactivePromise: EventLoopPromise = group.next().makePromise() - let closeInputVerificationHandler = ShutdownVerificationHandler( - shutdownEvent: .input, - promise: group.next().makePromise() - ) + let shutdownPromise = group.next().makePromise(of: Void.self) let clientChannel = try clientTLSChannel( context: context, preHandlers: [], postHandlers: [ PromiseOnReadHandler(promise: completionPromise), - closeInputVerificationHandler, + ShutdownVerificationHandler( + shutdownEvent: .input, + promise: shutdownPromise + ), ChannelInactiveHandler(promise: clientChannelInactivePromise), ], group: group, @@ -980,7 +963,7 @@ class NIOSSLIntegrationTest: XCTestCase { // Closing the output of the connection on the server should automatically // close the input of the clientChannel. XCTAssertNoThrow(try connectionChildChannel.close(mode: .output).wait()) - closeInputVerificationHandler.waitForEvent() + try shutdownPromise.futureResult.wait() // Closing the output of the client channel (with input closed) should // result in full closure. @@ -998,20 +981,15 @@ class NIOSSLIntegrationTest: XCTestCase { let completionPromise: EventLoopPromise = group.next().makePromise() - let preHandlers: [ChannelHandler] = [] - let postHandlers = [SimpleEchoServer()] let serverChannel: Channel = try ServerBootstrap(group: group) .childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true) // Important! .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) .childChannelInitializer { channel in - let results = preHandlers.map { channel.pipeline.addHandler($0) } - return EventLoopFuture.andAllSucceed(results, on: channel.eventLoop).map { - NIOSSLServerHandler(context: context) - }.flatMap { - channel.pipeline.addHandler($0) - }.flatMap { - let results = postHandlers.map { channel.pipeline.addHandler($0) } - return EventLoopFuture.andAllSucceed(results, on: channel.eventLoop) + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.addHandlers( + NIOSSLServerHandler(context: context), + SimpleEchoServer() + ) } } .bind(host: "127.0.0.1", port: 0) @@ -1053,20 +1031,15 @@ class NIOSSLIntegrationTest: XCTestCase { let completionPromise: EventLoopPromise = group.next().makePromise() - let preHandlers: [ChannelHandler] = [] - let postHandlers = [SimpleEchoServer()] let serverChannel: Channel = try ServerBootstrap(group: group) .childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true) // Important! .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) .childChannelInitializer { channel in - let results = preHandlers.map { channel.pipeline.addHandler($0) } - return EventLoopFuture.andAllSucceed(results, on: channel.eventLoop).map { - NIOSSLServerHandler(context: context) - }.flatMap { - channel.pipeline.addHandler($0) - }.flatMap { - let results = postHandlers.map { channel.pipeline.addHandler($0) } - return EventLoopFuture.andAllSucceed(results, on: channel.eventLoop) + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.addHandlers( + NIOSSLServerHandler(context: context), + SimpleEchoServer() + ) } } .bind(host: "127.0.0.1", port: 0) @@ -1214,7 +1187,7 @@ class NIOSSLIntegrationTest: XCTestCase { originalBuffer.writeString("A") var writeFutures: [EventLoopFuture<()>] = [] for _ in 0..<5 { - writeFutures.append(clientChannel.write(NIOAny(originalBuffer))) + writeFutures.append(clientChannel.write(originalBuffer)) } clientChannel.flush() @@ -1255,25 +1228,28 @@ class NIOSSLIntegrationTest: XCTestCase { // We're going to issue a number of small writes. Each of these should be coalesced together // and all their futures (along with the one for the flush) should fire, in order, with nothing // missed. - var firedFutures: [Int] = [] - var writeFutures: [EventLoopFuture<()>] = [] + let firedFutures: NIOLockedValueBox<[Int]> = .init([]) + let writeFutures: NIOLockedValueBox<[EventLoopFuture<()>]> = .init([]) var originalBuffer = clientChannel.allocator.buffer(capacity: 1) originalBuffer.writeString("A") for index in 0..<5 { let promise: EventLoopPromise = group.next().makePromise() - writeFutures.append(promise.futureResult) + writeFutures.withLockedValue { $0.append(promise.futureResult) } promise.futureResult.map { - XCTAssertEqual(firedFutures.count, index) - firedFutures.append(index) + XCTAssertEqual(firedFutures.withLockedValue { $0.count }, index) + firedFutures.withLockedValue { $0.append(index) } }.whenFailure { error in XCTFail("Write promise failed: \(error)") } - clientChannel.write(NIOAny(originalBuffer), promise: promise) + clientChannel.write(originalBuffer, promise: promise) } clientChannel.flush() - try EventLoopFuture<()>.andAllSucceed(writeFutures, on: clientChannel.eventLoop).map { - XCTAssertEqual(firedFutures, [0, 1, 2, 3, 4]) + try EventLoopFuture<()>.andAllSucceed( + writeFutures.withLockedValue { $0 }, + on: clientChannel.eventLoop + ).map { + XCTAssertEqual(firedFutures.withLockedValue { $0 }, [0, 1, 2, 3, 4]) }.recover { error in XCTFail("Write promised failed: \(error)") }.wait() @@ -1282,7 +1258,9 @@ class NIOSSLIntegrationTest: XCTestCase { func testImmediateCloseSatisfiesPromises() throws { let context = try configuredSSLContext() let channel = EmbeddedChannel() - try channel.pipeline.addHandler(NIOSSLClientHandler(context: context, serverHostname: nil)).wait() + try channel.pipeline.syncOperations.addHandler( + NIOSSLClientHandler(context: context, serverHostname: nil) + ) // Start by initiating the handshake. try channel.connect(to: SocketAddress(unixDomainSocketPath: "/tmp/doesntmatter")).wait() @@ -1314,9 +1292,14 @@ class NIOSSLIntegrationTest: XCTestCase { // Create a client channel without TLS in it, and connect it. let readPromise: EventLoopPromise = group.next().makePromise() - let promiseOnReadHandler = PromiseOnReadHandler(promise: readPromise) let clientChannel = try ClientBootstrap(group: group) - .channelInitializer({ $0.pipeline.addHandler(promiseOnReadHandler) }) + .channelInitializer { channel in + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.addHandler( + PromiseOnReadHandler(promise: readPromise) + ) + } + } .connect(to: serverChannel.localAddress!).wait() defer { XCTAssertNoThrow(try clientChannel.close().wait()) @@ -1330,10 +1313,13 @@ class NIOSSLIntegrationTest: XCTestCase { }.wait() // Now, add the TLS handler to the pipeline. - try clientChannel.pipeline.addHandler( - NIOSSLClientHandler(context: context, serverHostname: nil), - position: .first - ).wait() + try clientChannel.eventLoop.submit { + try clientChannel.pipeline.syncOperations.addHandler( + NIOSSLClientHandler(context: context, serverHostname: nil), + position: .first + ) + }.wait() + var data = clientChannel.allocator.buffer(capacity: 1) data.writeStaticString("x") try clientChannel.writeAndFlush(data).wait() @@ -1592,8 +1578,12 @@ class NIOSSLIntegrationTest: XCTestCase { let context = try configuredSSLContext() - try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait() - try clientChannel.pipeline.addHandler(try NIOSSLClientHandler(context: context, serverHostname: nil)).wait() + try serverChannel.pipeline.syncOperations.addHandler( + NIOSSLServerHandler(context: context) + ) + try clientChannel.pipeline.syncOperations.addHandler( + try NIOSSLClientHandler(context: context, serverHostname: nil) + ) let addr: SocketAddress = try SocketAddress(unixDomainSocketPath: "/tmp/whatever") let connectFuture = clientChannel.connect(to: addr) @@ -1609,7 +1599,7 @@ class NIOSSLIntegrationTest: XCTestCase { // synchronization: all of this should succeed synchronously. If it doesn't, that's // a bug too! let closePromise = serverChannel.close() - closePromise.whenComplete { _ in + closePromise.assumeIsolated().whenComplete { _ in // This looks like unsynchronized access to channelClosed, but it isn't: as we're // using EmbeddedChannel here there is no cross-thread hopping. channelClosed = true @@ -1745,8 +1735,12 @@ class NIOSSLIntegrationTest: XCTestCase { let context = try configuredSSLContext() - try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait() - try clientChannel.pipeline.addHandler(try NIOSSLClientHandler(context: context, serverHostname: nil)).wait() + try serverChannel.pipeline.syncOperations.addHandler( + NIOSSLServerHandler(context: context) + ) + try clientChannel.pipeline.syncOperations.addHandler( + try NIOSSLClientHandler(context: context, serverHostname: nil) + ) let addr = try SocketAddress(unixDomainSocketPath: "/tmp/whatever2") let connectFuture = clientChannel.connect(to: addr) @@ -1762,7 +1756,7 @@ class NIOSSLIntegrationTest: XCTestCase { closePromise.whenComplete { _ in var buffer = serverChannel.allocator.buffer(capacity: 5) buffer.writeStaticString("hello") - serverChannel.pipeline.fireChannelRead(NIOAny(buffer)) + serverChannel.pipeline.fireChannelRead(buffer) serverChannel.pipeline.fireChannelReadComplete() } @@ -1782,12 +1776,20 @@ class NIOSSLIntegrationTest: XCTestCase { let completePromise: EventLoopPromise = serverChannel.eventLoop.makePromise() - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) XCTAssertNoThrow( - try serverChannel.pipeline.addHandler(ReadRecordingHandler(completePromise: completePromise)).wait() + try serverChannel.pipeline.syncOperations.addHandler( + NIOSSLServerHandler(context: context) + ) ) XCTAssertNoThrow( - try clientChannel.pipeline.addHandler(try NIOSSLClientHandler(context: context, serverHostname: nil)).wait() + try serverChannel.pipeline.syncOperations.addHandler( + ReadRecordingHandler(completePromise: completePromise) + ) + ) + XCTAssertNoThrow( + try clientChannel.pipeline.syncOperations.addHandler( + try NIOSSLClientHandler(context: context, serverHostname: nil) + ) ) let addr = try SocketAddress(unixDomainSocketPath: "/tmp/whatever2") @@ -1872,8 +1874,12 @@ class NIOSSLIntegrationTest: XCTestCase { let context = try configuredSSLContext() - try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait() - try clientChannel.pipeline.addHandler(try NIOSSLClientHandler(context: context, serverHostname: nil)).wait() + try serverChannel.pipeline.syncOperations.addHandler( + NIOSSLServerHandler(context: context) + ) + try clientChannel.pipeline.syncOperations.addHandler( + try NIOSSLClientHandler(context: context, serverHostname: nil) + ) let addr = try SocketAddress(unixDomainSocketPath: "/tmp/whatever2") let connectFuture = clientChannel.connect(to: addr) @@ -1885,23 +1891,23 @@ class NIOSSLIntegrationTest: XCTestCase { // writes are succeeded in order. To do that, we want to add a WriteDelayHandler to // prevent the EmbeddedChannel succeeding the early writes. let writeDelayer = WriteDelayHandler() - try clientChannel.pipeline.addHandler(writeDelayer, position: .first).wait() + try clientChannel.pipeline.syncOperations.addHandler(writeDelayer, position: .first) var writeCount = 0 let emptyBuffer = clientChannel.allocator.buffer(capacity: 16) var buffer = clientChannel.allocator.buffer(capacity: 16) buffer.writeStaticString("hello world") - clientChannel.write(buffer).whenComplete { _ in + clientChannel.write(buffer).assumeIsolated().whenComplete { _ in XCTAssertEqual(writeCount, 0) writeCount = 1 } clientChannel.flush() - clientChannel.write(emptyBuffer).whenComplete { _ in + clientChannel.write(emptyBuffer).assumeIsolated().whenComplete { _ in XCTAssertEqual(writeCount, 1) writeCount = 2 } clientChannel.flush() - clientChannel.write(buffer).whenComplete { _ in + clientChannel.write(buffer).assumeIsolated().whenComplete { _ in XCTAssertEqual(writeCount, 2) writeCount = 3 } @@ -1964,12 +1970,20 @@ class NIOSSLIntegrationTest: XCTestCase { let completePromise: EventLoopPromise = serverChannel.eventLoop.makePromise() - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) XCTAssertNoThrow( - try serverChannel.pipeline.addHandler(ReadRecordingHandler(completePromise: completePromise)).wait() + try serverChannel.pipeline.syncOperations.addHandler( + NIOSSLServerHandler(context: context) + ) + ) + XCTAssertNoThrow( + try serverChannel.pipeline.syncOperations.addHandler( + ReadRecordingHandler(completePromise: completePromise) + ) ) XCTAssertNoThrow( - try clientChannel.pipeline.addHandler(try NIOSSLClientHandler(context: context, serverHostname: nil)).wait() + try clientChannel.pipeline.syncOperations.addHandler( + try NIOSSLClientHandler(context: context, serverHostname: nil) + ) ) // Connect @@ -2073,7 +2087,7 @@ class NIOSSLIntegrationTest: XCTestCase { XCTAssertNoThrow(try serverChannel.close().wait()) } - var certificates = [NIOSSLCertificate]() + let certificates = NIOLockedValueBox([NIOSSLCertificate]()) let clientChannel = try clientTLSChannel( context: configuredClientContext(), preHandlers: [], @@ -2082,7 +2096,7 @@ class NIOSSLIntegrationTest: XCTestCase { connectingTo: serverChannel.localAddress!, serverHostname: "localhost", verificationCallback: { verify, certificate in - certificates.append(certificate) + certificates.withLockedValue { $0.append(certificate) } return verify } ) @@ -2097,7 +2111,7 @@ class NIOSSLIntegrationTest: XCTestCase { let newBuffer = try completionPromise.futureResult.wait() XCTAssertEqual(newBuffer, originalBuffer) - XCTAssertEqual(certificates.count, 1) + XCTAssertEqual(certificates.withLockedValue { $0.count }, 1) } func testForcingVerificationFailureNewCallback() throws { @@ -2185,17 +2199,16 @@ class NIOSSLIntegrationTest: XCTestCase { XCTAssertNoThrow(try group.syncShutdownGracefully()) } - var completionPromiseFired: Bool = false - let completionPromiseFiredLock = NIOLock() + let completionPromiseFired = NIOLockedValueBox(false) let completionPromise: EventLoopPromise = group.next().makePromise() completionPromise.futureResult.whenComplete { _ in - completionPromiseFiredLock.withLock { - completionPromiseFired = true + completionPromiseFired.withLockedValue { + $0 = true } } - var handshakeCompletePromise: EventLoopPromise? = nil + let handshakeCompletePromise: NIOLockedValueBox?> = .init(nil) let handshakeFiredPromise: EventLoopPromise = group.next().makePromise() let serverChannel: Channel = try serverTLSChannel( @@ -2204,7 +2217,7 @@ class NIOSSLIntegrationTest: XCTestCase { postHandlers: [PromiseOnReadHandler(promise: completionPromise)], group: group, customVerificationCallback: { innerCertificates, promise in - handshakeCompletePromise = promise + handshakeCompletePromise.withLockedValue { $0 = promise } handshakeFiredPromise.succeed(()) } ) @@ -2231,12 +2244,12 @@ class NIOSSLIntegrationTest: XCTestCase { XCTAssertNoThrow(try handshakeFiredPromise.futureResult.wait()) // We can now check whether the completion promise has fired: it should not have. - completionPromiseFiredLock.withLock { - XCTAssertFalse(completionPromiseFired) + completionPromiseFired.withLockedValue { + XCTAssertFalse($0) } // Ok, allow the handshake to run. - handshakeCompletePromise!.succeed(.certificateVerified) + handshakeCompletePromise.withLockedValue { $0!.succeed(.certificateVerified) } let newBuffer = try completionPromise.futureResult.wait() XCTAssertEqual(newBuffer, originalBuffer) @@ -2250,13 +2263,12 @@ class NIOSSLIntegrationTest: XCTestCase { XCTAssertNoThrow(try group.syncShutdownGracefully()) } - var completionPromiseFired: Bool = false - let completionPromiseFiredLock = NIOLock() + let completionPromiseFired = NIOLockedValueBox(false) let completionPromise: EventLoopPromise = group.next().makePromise() completionPromise.futureResult.whenComplete { _ in - completionPromiseFiredLock.withLock { - completionPromiseFired = true + completionPromiseFired.withLockedValue { + $0 = true } } @@ -2269,7 +2281,7 @@ class NIOSSLIntegrationTest: XCTestCase { XCTAssertNoThrow(try serverChannel.close().wait()) } - var handshakeCompletePromise: EventLoopPromise? = nil + let handshakeCompletePromise: NIOLockedValueBox?> = .init(nil) let handshakeFiredPromise: EventLoopPromise = group.next().makePromise() let clientChannel = try clientTLSChannel( @@ -2280,7 +2292,7 @@ class NIOSSLIntegrationTest: XCTestCase { connectingTo: serverChannel.localAddress!, serverHostname: "localhost", customVerificationCallback: { innerCertificates, promise in - handshakeCompletePromise = promise + handshakeCompletePromise.withLockedValue { $0 = promise } handshakeFiredPromise.succeed(()) } ) @@ -2296,12 +2308,12 @@ class NIOSSLIntegrationTest: XCTestCase { XCTAssertNoThrow(try handshakeFiredPromise.futureResult.wait()) // We can now check whether the completion promise has fired: it should not have. - completionPromiseFiredLock.withLock { - XCTAssertFalse(completionPromiseFired) + completionPromiseFired.withLockedValue { + XCTAssertFalse($0) } // Ok, allow the handshake to run. - handshakeCompletePromise!.succeed(.certificateVerified) + handshakeCompletePromise.withLockedValue { $0!.succeed(.certificateVerified) } let newBuffer = try completionPromise.futureResult.wait() XCTAssertEqual(newBuffer, originalBuffer) @@ -2326,7 +2338,7 @@ class NIOSSLIntegrationTest: XCTestCase { XCTAssertNoThrow(try serverChannel.close().wait()) } - var certificates = [NIOSSLCertificate]() + let certificates = NIOLockedValueBox([NIOSSLCertificate]()) let clientChannel = try clientTLSChannel( context: configuredClientContext(), preHandlers: [], @@ -2335,7 +2347,7 @@ class NIOSSLIntegrationTest: XCTestCase { connectingTo: serverChannel.localAddress!, serverHostname: "localhost", customVerificationCallback: { innerCertificates, promise in - certificates = innerCertificates + certificates.withLockedValue { $0 = innerCertificates } promise.succeed(.certificateVerified) } ) @@ -2350,7 +2362,7 @@ class NIOSSLIntegrationTest: XCTestCase { let newBuffer = try completionPromise.futureResult.wait() XCTAssertEqual(newBuffer, originalBuffer) - XCTAssertEqual(certificates, [NIOSSLIntegrationTest.cert]) + XCTAssertEqual(certificates.withLockedValue { $0 }, [NIOSSLIntegrationTest.cert]) } func testNewCallbackCombinedWithDefaultTrustStore() throws { @@ -2495,9 +2507,15 @@ class NIOSSLIntegrationTest: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) XCTAssertNoThrow( - try clientChannel.pipeline.addHandler(NIOSSLClientHandler(context: context, serverHostname: nil)).wait() + try serverChannel.pipeline.syncOperations.addHandler( + NIOSSLServerHandler(context: context) + ) + ) + XCTAssertNoThrow( + try clientChannel.pipeline.syncOperations.addHandler( + NIOSSLClientHandler(context: context, serverHostname: nil) + ) ) let handshakeHandler = HandshakeCompletedHandler() XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) @@ -2511,16 +2529,16 @@ class NIOSSLIntegrationTest: XCTestCase { XCTAssertTrue(handshakeHandler.handshakeSucceeded) // We're going to close twice: the first one without a promise, the second one with one. - var closed = false + let closed = NIOLockedValueBox(false) clientChannel.close(promise: nil) clientChannel.close().whenComplete { _ in - closed = true + closed.withLockedValue { $0 = true } } - XCTAssertFalse(closed) + XCTAssertFalse(closed.withLockedValue { $0 }) XCTAssertNoThrow(try interactInMemory(clientChannel: clientChannel, serverChannel: serverChannel)) // The closure should have happened. - XCTAssertTrue(closed) + XCTAssertTrue(closed.withLockedValue { $0 }) } func testClosureTimeout() throws { @@ -2535,9 +2553,15 @@ class NIOSSLIntegrationTest: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) XCTAssertNoThrow( - try clientChannel.pipeline.addHandler(NIOSSLClientHandler(context: context, serverHostname: nil)).wait() + try serverChannel.pipeline.syncOperations.addHandler( + NIOSSLServerHandler(context: context) + ) + ) + XCTAssertNoThrow( + try clientChannel.pipeline.syncOperations.addHandler( + NIOSSLClientHandler(context: context, serverHostname: nil) + ) ) let handshakeHandler = HandshakeCompletedHandler() XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) @@ -2550,9 +2574,9 @@ class NIOSSLIntegrationTest: XCTestCase { try connectFuture.wait() XCTAssertTrue(handshakeHandler.handshakeSucceeded) - var closed = false + let closed = NIOLockedValueBox(false) clientChannel.close().whenComplete { _ in - closed = true + closed.withLockedValue { $0 = true } } clientChannel.close().whenFailure { error in @@ -2564,11 +2588,11 @@ class NIOSSLIntegrationTest: XCTestCase { try serverChannel.writeInbound(clientDatum) } - XCTAssertFalse(closed) + XCTAssertFalse(closed.withLockedValue { $0 }) // Let the shutdown timeout. clientChannel.embeddedEventLoop.advanceTime(by: context.configuration.shutdownTimeout) - XCTAssertTrue(closed) + XCTAssertTrue(closed.withLockedValue { $0 }) // Let the server shutdown. try interactInMemory(clientChannel: clientChannel, serverChannel: serverChannel) @@ -2578,7 +2602,7 @@ class NIOSSLIntegrationTest: XCTestCase { let serverChannel = EmbeddedChannel() let clientChannel = EmbeddedChannel() - var clientClosed = false + let clientClosed = NIOLockedValueBox(false) defer { XCTAssertThrowsError(try serverChannel.finish()) @@ -2588,14 +2612,26 @@ class NIOSSLIntegrationTest: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow( + try serverChannel.pipeline.syncOperations.addHandler( + NIOSSLServerHandler(context: context) + ) + ) + XCTAssertNoThrow( + try clientChannel.pipeline.syncOperations.addHandler( + clientHandler + ) + ) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow( + try clientChannel.pipeline.syncOperations.addHandler( + handshakeHandler + ) + ) // Mark the closure of the client. clientChannel.closeFuture.whenComplete { _ in - clientClosed = true + clientClosed.withLockedValue { $0 = true } } // Connect. This should lead to a completed handshake. @@ -2609,7 +2645,7 @@ class NIOSSLIntegrationTest: XCTestCase { // Let's close the client connection. clientChannel.close(promise: nil) (clientChannel.eventLoop as! EmbeddedEventLoop).run() - XCTAssertFalse(clientClosed) + XCTAssertFalse(clientClosed.withLockedValue { $0 }) // Now we're going to simulate the client receiving gibberish data in response, instead // of a CLOSE_NOTIFY. @@ -2627,7 +2663,7 @@ class NIOSSLIntegrationTest: XCTestCase { XCTAssertNotNil(regex.firstMatch(in: errorString, options: [], range: range)) } (clientChannel.eventLoop as! EmbeddedEventLoop).run() - XCTAssertTrue(clientClosed) + XCTAssertTrue(clientClosed.withLockedValue { $0 }) // Clean up by bringing the server up to speed serverChannel.pipeline.fireChannelInactive() @@ -2645,8 +2681,16 @@ class NIOSSLIntegrationTest: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow( + try serverChannel.pipeline.syncOperations.addHandler( + NIOSSLServerHandler(context: context) + ) + ) + XCTAssertNoThrow( + try clientChannel.pipeline.syncOperations.addHandler( + clientHandler + ) + ) let handshakeHandler = HandshakeCompletedHandler() XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) @@ -2659,33 +2703,33 @@ class NIOSSLIntegrationTest: XCTestCase { XCTAssertTrue(handshakeHandler.handshakeSucceeded) // Queue up a write. - var writeCompleted = false + let writeCompleted = NIOLockedValueBox(false) var buffer = clientChannel.allocator.buffer(capacity: 1024) buffer.writeStaticString("Hello, world!") clientChannel.write(buffer).map { XCTFail("Must not succeed") }.whenFailure { error in XCTAssertEqual(error as? ChannelError, .ioOnClosedChannel) - writeCompleted = true + writeCompleted.withLockedValue { $0 = true } } // We haven't spun the event loop, so the handlers are still in the pipeline. Now attempt to close. - var closed = false + let closed = NIOLockedValueBox(false) clientChannel.closeFuture.whenComplete { _ in - closed = true + closed.withLockedValue { $0 = true } } - XCTAssertFalse(writeCompleted) + XCTAssertFalse(writeCompleted.withLockedValue { $0 }) clientChannel.close(promise: nil) (clientChannel.eventLoop as! EmbeddedEventLoop).run() - XCTAssertFalse(writeCompleted) - XCTAssertFalse(closed) + XCTAssertFalse(writeCompleted.withLockedValue { $0 }) + XCTAssertFalse(closed.withLockedValue { $0 }) // Now try to flush the write. This should fail the write early, and take out the connection. clientChannel.flush() (clientChannel.eventLoop as! EmbeddedEventLoop).run() - XCTAssertTrue(writeCompleted) - XCTAssertTrue(closed) + XCTAssertTrue(writeCompleted.withLockedValue { $0 }) + XCTAssertTrue(closed.withLockedValue { $0 }) // Bring the server up to speed. serverChannel.pipeline.fireChannelInactive() @@ -2720,12 +2764,18 @@ class NIOSSLIntegrationTest: XCTestCase { // The client channel is closed in the test. } - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(SecondChannelInactiveSwallower()).wait()) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(FlushOnReadHandler()).wait()) + XCTAssertNoThrow( + try serverChannel.pipeline.syncOperations.addHandlers( + SecondChannelInactiveSwallower(), + NIOSSLServerHandler(context: context), + FlushOnReadHandler() + ) + ) XCTAssertNoThrow( - try clientChannel.pipeline.addHandler(try NIOSSLClientHandler(context: context, serverHostname: nil)).wait() + try clientChannel.pipeline.syncOperations.addHandler( + try NIOSSLClientHandler(context: context, serverHostname: nil) + ) ) // Connect @@ -2780,10 +2830,15 @@ class NIOSSLIntegrationTest: XCTestCase { // Handshake XCTAssertNoThrow( - try clientChannel.pipeline.addHandler(NIOSSLClientHandler(context: clientContext, serverHostname: nil)) - .wait() + try clientChannel.pipeline.syncOperations.addHandler( + NIOSSLClientHandler(context: clientContext, serverHostname: nil) + ) + ) + XCTAssertNoThrow( + try serverChannel.pipeline.syncOperations.addHandler( + NIOSSLServerHandler(context: serverContext) + ) ) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: serverContext)).wait()) let handshakeHandler = HandshakeCompletedHandler() XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) @@ -2853,8 +2908,12 @@ class NIOSSLIntegrationTest: XCTestCase { let context = try configuredSSLContext() - try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait() - try clientChannel.pipeline.addHandler(try NIOSSLClientHandler(context: context, serverHostname: nil)).wait() + try serverChannel.pipeline.syncOperations.addHandler( + NIOSSLServerHandler(context: context) + ) + try clientChannel.pipeline.syncOperations.addHandler( + try NIOSSLClientHandler(context: context, serverHostname: nil) + ) // Do the handshake. let addr: SocketAddress = try SocketAddress(unixDomainSocketPath: "/tmp/whatever") @@ -2868,7 +2927,7 @@ class NIOSSLIntegrationTest: XCTestCase { var buffer = clientChannel.allocator.buffer(capacity: 1024) buffer.writeBytes("Hello, world!".utf8) - clientChannel.write(buffer).whenComplete { _ in + clientChannel.write(buffer).whenComplete { [buffer] _ in clientChannel.writeAndFlush(buffer, promise: nil) } @@ -3069,9 +3128,11 @@ class NIOSSLIntegrationTest: XCTestCase { ) XCTAssertNoThrow(try b2b.connectInMemory()) - var completed = false + let completed = NIOLockedValueBox(false) let promise = b2b.loop.makePromise(of: Void.self) - promise.futureResult.whenComplete { _ in completed = true } + promise.futureResult.whenComplete { _ in + completed.withLockedValue { $0 = true } + } let recordObserver = TLS13RecordObserver() XCTAssertNoThrow( @@ -3090,7 +3151,7 @@ class NIOSSLIntegrationTest: XCTestCase { } let totalReadBytes = reads.reduce(into: 0, { $0 += $1.readableBytes }) XCTAssertEqual(totalReadBytes, targetSize) - XCTAssertTrue(completed) + XCTAssertTrue(completed.withLockedValue { $0 }) XCTAssertEqual(recordObserver.writtenRecords.filter { $0.contentType == .applicationData }.count, 5) b2b.client.close(promise: nil) diff --git a/Tests/NIOSSLTests/SSLCertificateTest.swift b/Tests/NIOSSLTests/SSLCertificateTest.swift index 18d3a1a3..853d49c8 100644 --- a/Tests/NIOSSLTests/SSLCertificateTest.swift +++ b/Tests/NIOSSLTests/SSLCertificateTest.swift @@ -173,30 +173,36 @@ internal func dumpToFile(text: String, fileExtension: String = "") throws -> Str } class SSLCertificateTest: XCTestCase { - static var pemCertFilePath: String! = nil - static var pemCertsFilePath: String! = nil - static var derCertFilePath: String! = nil - static var dynamicallyGeneratedCert: NIOSSLCertificate! = nil + static let dynamicallyGeneratedCert = generateSelfSignedCert().0 - override class func setUp() { - SSLCertificateTest.pemCertFilePath = try! dumpToFile(text: samplePemCert) - SSLCertificateTest.pemCertsFilePath = try! dumpToFile(text: samplePemCerts) - SSLCertificateTest.derCertFilePath = try! dumpToFile(data: sampleDerCert) - - let (cert, _) = generateSelfSignedCert() - SSLCertificateTest.dynamicallyGeneratedCert = cert + private static func withPemCertPath( + _ body: (String) throws -> ReturnType + ) throws -> ReturnType { + let pemCertFilePath = try dumpToFile(text: samplePemCert) + defer { + unlink(pemCertFilePath) + } + return try body(pemCertFilePath) } - override class func tearDown() { - _ = SSLCertificateTest.pemCertFilePath.withCString { - unlink($0) - } - _ = SSLCertificateTest.pemCertsFilePath.withCString { - unlink($0) + private static func withPemCertsPath( + _ body: (String) throws -> ReturnType + ) throws -> ReturnType { + let pemCertsFilePath = try dumpToFile(text: samplePemCerts) + defer { + unlink(pemCertsFilePath) } - _ = SSLCertificateTest.derCertFilePath.withCString { - unlink($0) + return try body(pemCertsFilePath) + } + + private static func withDerCertPath( + _ body: (String) throws -> ReturnType + ) throws -> ReturnType { + let derCertFilePath = try dumpToFile(data: sampleDerCert) + defer { + unlink(derCertFilePath) } + return try body(derCertFilePath) } private func dateFromComponents(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) -> Date { @@ -213,8 +219,12 @@ class SSLCertificateTest: XCTestCase { } func testLoadingPemCertFromFile() throws { - let cert1 = try NIOSSLCertificate(file: SSLCertificateTest.pemCertFilePath, format: .pem) - let cert2 = try NIOSSLCertificate(file: SSLCertificateTest.pemCertFilePath, format: .pem) + let (cert1, cert2) = try Self.withPemCertPath { + ( + try NIOSSLCertificate(file: $0, format: .pem), + try NIOSSLCertificate(file: $0, format: .pem) + ) + } XCTAssertEqual(cert1, cert2) XCTAssertEqual(cert1.hashValue, cert2.hashValue) @@ -223,8 +233,12 @@ class SSLCertificateTest: XCTestCase { } func testLoadingDerCertFromFile() throws { - let cert1 = try NIOSSLCertificate(file: SSLCertificateTest.derCertFilePath, format: .der) - let cert2 = try NIOSSLCertificate(file: SSLCertificateTest.derCertFilePath, format: .der) + let (cert1, cert2) = try Self.withDerCertPath { + ( + try NIOSSLCertificate(file: $0, format: .der), + try NIOSSLCertificate(file: $0, format: .der) + ) + } XCTAssertEqual(cert1, cert2) XCTAssertEqual(cert1.hashValue, cert2.hashValue) @@ -233,8 +247,12 @@ class SSLCertificateTest: XCTestCase { } func testDerAndPemAreIdentical() throws { - let cert1 = try NIOSSLCertificate(file: SSLCertificateTest.pemCertFilePath, format: .pem) - let cert2 = try NIOSSLCertificate(file: SSLCertificateTest.derCertFilePath, format: .der) + let cert1 = try Self.withPemCertPath { + try NIOSSLCertificate(file: $0, format: .pem) + } + let cert2 = try Self.withDerCertPath { + try NIOSSLCertificate(file: $0, format: .der) + } XCTAssertEqual(cert1, cert2) XCTAssertEqual(cert1.hashValue, cert2.hashValue) @@ -266,8 +284,12 @@ class SSLCertificateTest: XCTestCase { } func testLoadingPemCertsFromFile() throws { - let certs1 = try NIOSSLCertificate.fromPEMFile(SSLCertificateTest.pemCertsFilePath) - let certs2 = try NIOSSLCertificate.fromPEMFile(SSLCertificateTest.pemCertsFilePath) + let (certs1, certs2) = try Self.withPemCertsPath { + ( + try NIOSSLCertificate.fromPEMFile($0), + try NIOSSLCertificate.fromPEMFile($0) + ) + } XCTAssertEqual(certs1.count, 2) XCTAssertEqual(certs1, certs2) diff --git a/Tests/NIOSSLTests/SSLContextTests.swift b/Tests/NIOSSLTests/SSLContextTests.swift index c0ce8915..48cd3663 100644 --- a/Tests/NIOSSLTests/SSLContextTests.swift +++ b/Tests/NIOSSLTests/SSLContextTests.swift @@ -35,21 +35,13 @@ final class SSLContextTest: XCTestCase { case contextError } - static var cert1: NIOSSLCertificate! - static var key1: NIOSSLPrivateKey! - - static var cert2: NIOSSLCertificate! - static var key2: NIOSSLPrivateKey! - - override class func setUp() { - super.setUp() - let (cert1, key1) = generateSelfSignedCert() - SSLContextTest.cert1 = cert1 - SSLContextTest.key1 = key1 - let (cert2, key2) = generateSelfSignedCert() - SSLContextTest.cert2 = cert2 - SSLContextTest.key2 = key2 - } + static let _certAndKey1 = generateSelfSignedCert() + static let cert1 = SSLContextTest._certAndKey1.0 + static let key1 = SSLContextTest._certAndKey1.1 + + static let _certAndKey2 = generateSelfSignedCert() + static let cert2 = SSLContextTest._certAndKey2.0 + static let key2 = SSLContextTest._certAndKey2.1 private func configuredClientSSLContext() throws -> NIOSSLContext { var config = TLSConfiguration.makeServerConfiguration( @@ -98,16 +90,17 @@ final class SSLContextTest: XCTestCase { let serverContext = try configuredServerSSLContext(eventLoop: group.next()) let sniPromise: EventLoopPromise = group.next().makePromise() - let sniHandler = ByteToMessageHandler( - SNIHandler { - sniPromise.succeed($0) - return group.next().makeSucceededFuture(()) - } - ) let serverChannel = try serverTLSChannel( context: serverContext, - preHandlers: [sniHandler], + preHandlers: [ + ByteToMessageHandler( + SNIHandler { + sniPromise.succeed($0) + return group.next().makeSucceededFuture(()) + } + ) + ], postHandlers: [], group: group ) @@ -153,18 +146,19 @@ final class SSLContextTest: XCTestCase { let serverContext = try configuredServerSSLContext(eventLoop: group.next(), throwing: expectedError) let sniPromise: EventLoopPromise = group.next().makePromise() - let sniHandler = ByteToMessageHandler( - SNIHandler { - sniPromise.succeed($0) - return group.next().makeSucceededFuture(()) - } - ) let eventHandler = ErrorCatcher() let serverChannel = try serverTLSChannel( context: serverContext, - preHandlers: [sniHandler], + preHandlers: [ + ByteToMessageHandler( + SNIHandler { + sniPromise.succeed($0) + return group.next().makeSucceededFuture(()) + } + ) + ], postHandlers: [eventHandler], group: group ) diff --git a/Tests/NIOSSLTests/SSLPKCS12BundleTest.swift b/Tests/NIOSSLTests/SSLPKCS12BundleTest.swift index 064efc4b..cdd5ae6d 100644 --- a/Tests/NIOSSLTests/SSLPKCS12BundleTest.swift +++ b/Tests/NIOSSLTests/SSLPKCS12BundleTest.swift @@ -408,35 +408,46 @@ var noPassP12: [UInt8] { } class SSLPKCS12BundleTest: XCTestCase { - static var simpleFilePath: String! = nil - static var complexFilePath: String! = nil - static var noPassFilePath: String! = nil - - override class func setUp() { - SSLPKCS12BundleTest.simpleFilePath = try! dumpToFile( + static func withSimpleFilePath(_ body: (String) throws -> Void) throws { + let path = try dumpToFile( data: Data( base64Encoded: base64EncodedSimpleP12, options: .ignoreUnknownCharacters )! ) - SSLPKCS12BundleTest.complexFilePath = try! dumpToFile( + defer { + unlink(path) + } + + return try body(path) + } + + static func withComplexFilePath(_ body: (String) throws -> Void) throws { + let path = try dumpToFile( data: Data( base64Encoded: base64EncodedComplexP12, options: .ignoreUnknownCharacters )! ) - SSLPKCS12BundleTest.noPassFilePath = try! dumpToFile( + defer { + unlink(path) + } + + return try body(path) + } + + static func withNoPasswordFilePath(_ body: (String) throws -> Void) throws { + let path = try dumpToFile( data: Data( base64Encoded: base64EncodedNoPassP12, options: .ignoreUnknownCharacters )! ) - } + defer { + unlink(path) + } - override class func tearDown() { - _ = unlink(SSLPKCS12BundleTest.simpleFilePath) - _ = unlink(SSLPKCS12BundleTest.complexFilePath) - _ = unlink(SSLPKCS12BundleTest.noPassFilePath) + return try body(path) } func testDecodingSimpleP12FromMemory() throws { @@ -471,40 +482,46 @@ class SSLPKCS12BundleTest: XCTestCase { } func testDecodingSimpleP12FromFile() throws { - let p12Bundle = try NIOSSLPKCS12Bundle( - file: SSLPKCS12BundleTest.simpleFilePath, - passphrase: "thisisagreatpassword".utf8 - ) - let expectedKey = try NIOSSLPrivateKey(bytes: .init(samplePemKey.utf8), format: .pem) - let expectedCert = try NIOSSLCertificate(bytes: .init(samplePemCert.utf8), format: .pem) + try Self.withSimpleFilePath { simpleFilePath in + let p12Bundle = try NIOSSLPKCS12Bundle( + file: simpleFilePath, + passphrase: "thisisagreatpassword".utf8 + ) + let expectedKey = try NIOSSLPrivateKey(bytes: .init(samplePemKey.utf8), format: .pem) + let expectedCert = try NIOSSLCertificate(bytes: .init(samplePemCert.utf8), format: .pem) - XCTAssertEqual(p12Bundle.privateKey, expectedKey) - XCTAssertEqual(p12Bundle.certificateChain, [expectedCert]) + XCTAssertEqual(p12Bundle.privateKey, expectedKey) + XCTAssertEqual(p12Bundle.certificateChain, [expectedCert]) + } } func testDecodingComplexP12FromFile() throws { - let p12Bundle = try NIOSSLPKCS12Bundle( - file: SSLPKCS12BundleTest.complexFilePath, - passphrase: "thisisagreatpassword".utf8 - ) - let expectedKey = try NIOSSLPrivateKey(bytes: .init(samplePemKey.utf8), format: .pem) - let expectedCert = try NIOSSLCertificate(bytes: .init(samplePemCert.utf8), format: .pem) - let caOne = try NIOSSLCertificate(bytes: .init(multiSanCert.utf8), format: .pem) - let caTwo = try NIOSSLCertificate(bytes: .init(multiCNCert.utf8), format: .pem) - let caThree = try NIOSSLCertificate(bytes: .init(noCNCert.utf8), format: .pem) - let caFour = try NIOSSLCertificate(bytes: .init(unicodeCNCert.utf8), format: .pem) - - XCTAssertEqual(p12Bundle.privateKey, expectedKey) - XCTAssertEqual(p12Bundle.certificateChain, [expectedCert, caOne, caTwo, caThree, caFour]) + try Self.withComplexFilePath { complexFilePath in + let p12Bundle = try NIOSSLPKCS12Bundle( + file: complexFilePath, + passphrase: "thisisagreatpassword".utf8 + ) + let expectedKey = try NIOSSLPrivateKey(bytes: .init(samplePemKey.utf8), format: .pem) + let expectedCert = try NIOSSLCertificate(bytes: .init(samplePemCert.utf8), format: .pem) + let caOne = try NIOSSLCertificate(bytes: .init(multiSanCert.utf8), format: .pem) + let caTwo = try NIOSSLCertificate(bytes: .init(multiCNCert.utf8), format: .pem) + let caThree = try NIOSSLCertificate(bytes: .init(noCNCert.utf8), format: .pem) + let caFour = try NIOSSLCertificate(bytes: .init(unicodeCNCert.utf8), format: .pem) + + XCTAssertEqual(p12Bundle.privateKey, expectedKey) + XCTAssertEqual(p12Bundle.certificateChain, [expectedCert, caOne, caTwo, caThree, caFour]) + } } func testDecodingSimpleP12FromFileWithoutPassphrase() throws { - let p12Bundle = try NIOSSLPKCS12Bundle(file: SSLPKCS12BundleTest.noPassFilePath) - let expectedKey = try NIOSSLPrivateKey(bytes: .init(samplePemKey.utf8), format: .pem) - let expectedCert = try NIOSSLCertificate(bytes: .init(samplePemCert.utf8), format: .pem) + try Self.withNoPasswordFilePath { noPassFilePath in + let p12Bundle = try NIOSSLPKCS12Bundle(file: noPassFilePath) + let expectedKey = try NIOSSLPrivateKey(bytes: .init(samplePemKey.utf8), format: .pem) + let expectedCert = try NIOSSLCertificate(bytes: .init(samplePemCert.utf8), format: .pem) - XCTAssertEqual(p12Bundle.privateKey, expectedKey) - XCTAssertEqual(p12Bundle.certificateChain, [expectedCert]) + XCTAssertEqual(p12Bundle.privateKey, expectedKey) + XCTAssertEqual(p12Bundle.certificateChain, [expectedCert]) + } } func testDecodingNonExistentPKCS12File() throws { diff --git a/Tests/NIOSSLTests/SSLPrivateKeyTests.swift b/Tests/NIOSSLTests/SSLPrivateKeyTests.swift index 7ef2dfd4..d5da5e78 100644 --- a/Tests/NIOSSLTests/SSLPrivateKeyTests.swift +++ b/Tests/NIOSSLTests/SSLPrivateKeyTests.swift @@ -19,67 +19,86 @@ import XCTest @testable import NIOSSL class SSLPrivateKeyTest: XCTestCase { - static var pemKeyFilePath: String! = nil - static var derKeyFilePath: String! = nil - static var passwordPemKeyFilePath: String! = nil - static var passwordPKCS8PemKeyFilePath: String! = nil - static var dynamicallyGeneratedKey: NIOSSLPrivateKey! = nil - - override class func setUp() { - SSLPrivateKeyTest.pemKeyFilePath = try! dumpToFile( + static let dynamicallyGeneratedKey = generateSelfSignedCert().1 + + static func withPEMKeyFile(_ body: (String) throws -> Void) throws { + let path = try dumpToFile( text: samplePemKey, fileExtension: ".pem" ) - SSLPrivateKeyTest.derKeyFilePath = try! dumpToFile(data: sampleDerKey) - SSLPrivateKeyTest.passwordPemKeyFilePath = try! dumpToFile( + defer { + unlink(path) + } + + return try body(path) + } + + static func withDERKeyFile(_ body: (String) throws -> Void) throws { + let path = try dumpToFile( + data: sampleDerKey + ) + defer { + unlink(path) + } + + return try body(path) + } + + static func withPasswordPEMKeyFile(_ body: (String) throws -> Void) throws { + let path = try dumpToFile( text: samplePemRSAEncryptedKey, fileExtension: ".pem" ) - SSLPrivateKeyTest.passwordPKCS8PemKeyFilePath = try! dumpToFile(text: samplePKCS8PemPrivateKey) + defer { + unlink(path) + } - let (_, key) = generateSelfSignedCert() - SSLPrivateKeyTest.dynamicallyGeneratedKey = key + return try body(path) } - override class func tearDown() { - _ = SSLPrivateKeyTest.pemKeyFilePath.withCString { - unlink($0) - } - _ = SSLPrivateKeyTest.derKeyFilePath.withCString { - unlink($0) - } - _ = SSLPrivateKeyTest.passwordPemKeyFilePath.withCString { - unlink($0) - } - _ = SSLPrivateKeyTest.passwordPKCS8PemKeyFilePath.withCString { - unlink($0) + static func withPasswordPKCS8PEMKeyFile(_ body: (String) throws -> Void) throws { + let path = try dumpToFile( + text: samplePKCS8PemPrivateKey + ) + defer { + unlink(path) } + + return try body(path) } func testLoadingPemKeyFromFile() throws { - let key1 = try NIOSSLPrivateKey(file: SSLPrivateKeyTest.pemKeyFilePath, format: .pem) - let key2 = try NIOSSLPrivateKey(file: SSLPrivateKeyTest.pemKeyFilePath, format: .pem) + try Self.withPEMKeyFile { pemKeyFilePath in + let key1 = try NIOSSLPrivateKey(file: pemKeyFilePath, format: .pem) + let key2 = try NIOSSLPrivateKey(file: pemKeyFilePath, format: .pem) - XCTAssertEqual(key1, key2) - XCTAssertNotEqual(key1, SSLPrivateKeyTest.dynamicallyGeneratedKey) + XCTAssertEqual(key1, key2) + XCTAssertNotEqual(key1, SSLPrivateKeyTest.dynamicallyGeneratedKey) + } } func testLoadingDerKeyFromFile() throws { - let key1 = try NIOSSLPrivateKey(file: SSLPrivateKeyTest.derKeyFilePath, format: .der) - let key2 = try NIOSSLPrivateKey(file: SSLPrivateKeyTest.derKeyFilePath, format: .der) - - XCTAssertEqual(key1, key2) - XCTAssertEqual(key1.hashValue, key2.hashValue) - XCTAssertNotEqual(key1, SSLPrivateKeyTest.dynamicallyGeneratedKey) - XCTAssertNotEqual(key1.hashValue, SSLPrivateKeyTest.dynamicallyGeneratedKey.hashValue) + try Self.withDERKeyFile { derKeyFilePath in + let key1 = try NIOSSLPrivateKey(file: derKeyFilePath, format: .der) + let key2 = try NIOSSLPrivateKey(file: derKeyFilePath, format: .der) + + XCTAssertEqual(key1, key2) + XCTAssertEqual(key1.hashValue, key2.hashValue) + XCTAssertNotEqual(key1, SSLPrivateKeyTest.dynamicallyGeneratedKey) + XCTAssertNotEqual(key1.hashValue, SSLPrivateKeyTest.dynamicallyGeneratedKey.hashValue) + } } func testDerAndPemAreIdentical() throws { - let key1 = try NIOSSLPrivateKey(file: SSLPrivateKeyTest.pemKeyFilePath, format: .pem) - let key2 = try NIOSSLPrivateKey(file: SSLPrivateKeyTest.derKeyFilePath, format: .der) + try Self.withPEMKeyFile { pemKeyFilePath in + try Self.withDERKeyFile { derKeyFilePath in + let key1 = try NIOSSLPrivateKey(file: pemKeyFilePath, format: .pem) + let key2 = try NIOSSLPrivateKey(file: derKeyFilePath, format: .der) - XCTAssertEqual(key1, key2) - XCTAssertEqual(key1.hashValue, key2.hashValue) + XCTAssertEqual(key1, key2) + XCTAssertEqual(key1.hashValue, key2.hashValue) + } + } } func testLoadingPemKeyFromMemory() throws { @@ -195,27 +214,31 @@ class SSLPrivateKeyTest: XCTestCase { } func testLoadingEncryptedRSAKeyFromFile() throws { - let key1 = try NIOSSLPrivateKey(file: SSLPrivateKeyTest.passwordPemKeyFilePath, format: .pem) { closure in - closure("thisisagreatpassword".utf8) - } - let key2 = try NIOSSLPrivateKey(file: SSLPrivateKeyTest.passwordPemKeyFilePath, format: .pem) { closure in - closure("thisisagreatpassword".utf8) - } + try Self.withPasswordPEMKeyFile { passwordPemKeyFilePath in + let key1 = try NIOSSLPrivateKey(file: passwordPemKeyFilePath, format: .pem) { closure in + closure("thisisagreatpassword".utf8) + } + let key2 = try NIOSSLPrivateKey(file: passwordPemKeyFilePath, format: .pem) { closure in + closure("thisisagreatpassword".utf8) + } - XCTAssertEqual(key1, key2) - XCTAssertEqual(key1.hashValue, key2.hashValue) + XCTAssertEqual(key1, key2) + XCTAssertEqual(key1.hashValue, key2.hashValue) + } } func testLoadingEncryptedRSAPKCS8KeyFromFile() throws { - let key1 = try NIOSSLPrivateKey(file: SSLPrivateKeyTest.passwordPKCS8PemKeyFilePath, format: .pem) { closure in - closure("thisisagreatpassword".utf8) - } - let key2 = try NIOSSLPrivateKey(file: SSLPrivateKeyTest.passwordPKCS8PemKeyFilePath, format: .pem) { closure in - closure("thisisagreatpassword".utf8) - } + try Self.withPasswordPKCS8PEMKeyFile { passwordPKCS8PemKeyFilePath in + let key1 = try NIOSSLPrivateKey(file: passwordPKCS8PemKeyFilePath, format: .pem) { closure in + closure("thisisagreatpassword".utf8) + } + let key2 = try NIOSSLPrivateKey(file: passwordPKCS8PemKeyFilePath, format: .pem) { closure in + closure("thisisagreatpassword".utf8) + } - XCTAssertEqual(key1, key2) - XCTAssertEqual(key1.hashValue, key2.hashValue) + XCTAssertEqual(key1, key2) + XCTAssertEqual(key1.hashValue, key2.hashValue) + } } func testWildlyOverlongPassphraseRSAFromMemory() throws { @@ -284,14 +307,16 @@ class SSLPrivateKeyTest: XCTestCase { } @available(*, deprecated, message: "`.file` NIOSSLPrivateKeySource option deprecated") - func testMissingPassword() { - let configuration = TLSConfiguration.makeServerConfiguration( - certificateChain: [], - privateKey: .file(SSLPrivateKeyTest.passwordPemKeyFilePath) - ) - - XCTAssertThrowsError(try NIOSSLContext(configuration: configuration)) { error in - XCTAssertEqual(.failedToLoadPrivateKey, error as? NIOSSLError) + func testMissingPassword() throws { + try Self.withPasswordPEMKeyFile { passwordPemKeyFilePath in + let configuration = TLSConfiguration.makeServerConfiguration( + certificateChain: [], + privateKey: .file(passwordPemKeyFilePath) + ) + + XCTAssertThrowsError(try NIOSSLContext(configuration: configuration)) { error in + XCTAssertEqual(.failedToLoadPrivateKey, error as? NIOSSLError) + } } } diff --git a/Tests/NIOSSLTests/SecurityFrameworkVerificationTests.swift b/Tests/NIOSSLTests/SecurityFrameworkVerificationTests.swift index 617b0b73..9f3239e9 100644 --- a/Tests/NIOSSLTests/SecurityFrameworkVerificationTests.swift +++ b/Tests/NIOSSLTests/SecurityFrameworkVerificationTests.swift @@ -210,13 +210,30 @@ final class SecurityFrameworkVerificationTests: XCTestCase { } } -#if canImport(Darwin) +// This class allows us to work around an awkward bug with our static below. +// We need to mark this type non-Sendable. +#if !canImport(Darwin) +final class SecCertificate { + +} + +@available(*, unavailable) +extension SecCertificate: Sendable {} +#endif + extension SecurityFrameworkVerificationTests { /// If tests fail because of an expired cert, you can regenerate the leaf and intermediate certificates /// by running the following command, and replacing both served certificates as leaf and intermediate, /// in that order: /// `openssl s_client -connect www.apple.com:443 -servername www.apple.com -showcerts` - static let appleComCertChain: [SecCertificate] = { + #if compiler(>=5.10) + nonisolated(unsafe) fileprivate static let appleComCertChain: [SecCertificate] = buildAppleComCertChain() + #else + fileprivate static let appleComCertChain: [SecCertificate] = buildAppleComCertChain() + #endif + + fileprivate static func buildAppleComCertChain() -> [SecCertificate] { + #if canImport(Darwin) // All certs here are PEM format, with the leading/trailing lines stripped. let leaf = """ MIIHezCCBmOgAwIBAgIQdBPCMTNJmIlbB9vLs/QpFTANBgkqhkiG9w0BAQsFADBR @@ -295,6 +312,8 @@ extension SecurityFrameworkVerificationTests { return [leaf, intermediate].map { SecCertificateCreateWithData(nil, Data(base64Encoded: $0, options: .ignoreUnknownCharacters)! as CFData)! } - }() + #else + return [] + #endif + } } -#endif diff --git a/Tests/NIOSSLTests/TLSConfigurationTest.swift b/Tests/NIOSSLTests/TLSConfigurationTest.swift index e389f656..d01e88cf 100644 --- a/Tests/NIOSSLTests/TLSConfigurationTest.swift +++ b/Tests/NIOSSLTests/TLSConfigurationTest.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import NIOConcurrencyHelpers import NIOCore import NIOEmbedded import NIOPosix @@ -32,38 +33,44 @@ internal import CNIOBoringSSL import Dispatch #endif -class ErrorCatcher: ChannelInboundHandler { +final class ErrorCatcher: ChannelInboundHandler, Sendable { public typealias InboundIn = Any - public var errors: [T] + let _errors: NIOLockedValueBox<[T]> + var errors: [T] { + self._errors.withLockedValue { $0 } + } public init() { - errors = [] + self._errors = .init([]) } public func errorCaught(context: ChannelHandlerContext, error: Error) { - errors.append(error as! T) + self._errors.withLockedValue { $0.append(error as! T) } } } -class HandshakeCompletedHandler: ChannelInboundHandler { +final class HandshakeCompletedHandler: ChannelInboundHandler, Sendable { public typealias InboundIn = Any - public var handshakeSucceeded = false + let _handshakeSucceeded = NIOLockedValueBox(false) + var handshakeSucceeded: Bool { + self._handshakeSucceeded.withLockedValue { $0 } + } public func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) { if let event = event as? TLSUserEvent, case .handshakeCompleted = event { - self.handshakeSucceeded = true + self._handshakeSucceeded.withLockedValue { $0 = true } } context.fireUserInboundEventTriggered(event) } } -class WaitForHandshakeHandler: ChannelInboundHandler { +final class WaitForHandshakeHandler: ChannelInboundHandler, Sendable { public typealias InboundIn = Any public var handshakeResult: EventLoopFuture { self.handshakeResultPromise.futureResult } - private var handshakeResultPromise: EventLoopPromise + private let handshakeResultPromise: EventLoopPromise init(handshakeResultPromise: EventLoopPromise) { self.handshakeResultPromise = handshakeResultPromise @@ -85,22 +92,13 @@ class WaitForHandshakeHandler: ChannelInboundHandler { } class TLSConfigurationTest: XCTestCase { - static var cert1: NIOSSLCertificate! - static var key1: NIOSSLPrivateKey! - - static var cert2: NIOSSLCertificate! - static var key2: NIOSSLPrivateKey! + static let _certAndKey1 = generateSelfSignedCert() + static let cert1 = TLSConfigurationTest._certAndKey1.0 + static let key1 = TLSConfigurationTest._certAndKey1.1 - override class func setUp() { - super.setUp() - var (cert, key) = generateSelfSignedCert() - TLSConfigurationTest.cert1 = cert - TLSConfigurationTest.key1 = key - - (cert, key) = generateSelfSignedCert() - TLSConfigurationTest.cert2 = cert - TLSConfigurationTest.key2 = key - } + static let _certAndKey2 = generateSelfSignedCert() + static let cert2 = TLSConfigurationTest._certAndKey2.0 + static let key2 = TLSConfigurationTest._certAndKey2.1 func assertHandshakeError( withClientConfig clientConfig: TLSConfiguration, @@ -260,13 +258,14 @@ class TLSConfigurationTest: XCTestCase { } XCTAssertNoThrow( - try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: serverContext)).wait(), + try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: serverContext)), file: (file), line: line ) XCTAssertNoThrow( - try clientChannel.pipeline.addHandler(NIOSSLClientHandler(context: clientContext, serverHostname: nil)) - .wait(), + try clientChannel.pipeline.syncOperations.addHandler( + NIOSSLClientHandler(context: clientContext, serverHostname: nil) + ), file: (file), line: line ) @@ -688,10 +687,13 @@ class TLSConfigurationTest: XCTestCase { _ = try? clientChannel.finish() } - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: serverContext)).wait()) XCTAssertNoThrow( - try clientChannel.pipeline.addHandler(NIOSSLClientHandler(context: clientContext, serverHostname: nil)) - .wait() + try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: serverContext)) + ) + XCTAssertNoThrow( + try clientChannel.pipeline.syncOperations.addHandler( + NIOSSLClientHandler(context: clientContext, serverHostname: nil) + ) ) let handshakeHandler = HandshakeCompletedHandler() XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) @@ -760,10 +762,13 @@ class TLSConfigurationTest: XCTestCase { _ = try? clientChannel.finish() } - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: serverContext)).wait()) XCTAssertNoThrow( - try clientChannel.pipeline.addHandler(NIOSSLClientHandler(context: clientContext, serverHostname: nil)) - .wait() + try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: serverContext)) + ) + XCTAssertNoThrow( + try clientChannel.pipeline.syncOperations.addHandler( + NIOSSLClientHandler(context: clientContext, serverHostname: nil) + ) ) let handshakeHandler = HandshakeCompletedHandler() XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) @@ -1832,11 +1837,12 @@ class TLSConfigurationTest: XCTestCase { } func testTLSPSKNoServerHint() throws { - let expectation = expectation(description: "pskClientProvider is called") + let pseudoExpectation = ConditionLock(value: false) // This test ensures that different PSKs used on the client and server fail when passed in. let pskClientProvider: NIOPSKClientIdentityProvider = { (context: PSKClientContext) -> PSKClientIdentityResponse in - expectation.fulfill() + pseudoExpectation.lock() + pseudoExpectation.unlock(withValue: true) // Ensure server hint is nil XCTAssertEqual(context.hint, nil) // Evaluate hint and clientIdentity to send back proper PSK. @@ -1869,15 +1875,17 @@ class TLSConfigurationTest: XCTestCase { serverConfig.pskServerProvider = pskServerProvider serverConfig.pskHint = nil try assertHandshakeSucceeded(withClientConfig: clientConfig, andServerConfig: serverConfig) - waitForExpectations(timeout: 1) + XCTAssertTrue(pseudoExpectation.lock(whenValue: true, timeoutSeconds: 1)) + pseudoExpectation.unlock() } func testTLSPSKNoClientHint() throws { - let expectation = expectation(description: "pskClientProvider is called") + let pseudoExpectation = ConditionLock(value: false) // This test ensures that different PSKs used on the client and server fail when passed in. let pskClientProvider: NIOPSKClientIdentityProvider = { (context: PSKClientContext) -> PSKClientIdentityResponse in - expectation.fulfill() + pseudoExpectation.lock() + pseudoExpectation.unlock(withValue: true) // Ensure server hint is nil XCTAssertEqual(context.hint, nil) // Evaluate hint and clientIdentity to send back proper PSK. @@ -1910,7 +1918,8 @@ class TLSConfigurationTest: XCTestCase { serverConfig.pskServerProvider = pskServerProvider serverConfig.pskHint = nil try assertHandshakeSucceeded(withClientConfig: clientConfig, andServerConfig: serverConfig) - waitForExpectations(timeout: 1) + XCTAssertTrue(pseudoExpectation.lock(whenValue: true, timeoutSeconds: 1)) + pseudoExpectation.unlock() } func testClientSideCertSelection() throws { diff --git a/Tests/NIOSSLTests/UnwrappingTests.swift b/Tests/NIOSSLTests/UnwrappingTests.swift index 26d83303..0d77a9a8 100644 --- a/Tests/NIOSSLTests/UnwrappingTests.swift +++ b/Tests/NIOSSLTests/UnwrappingTests.swift @@ -26,10 +26,10 @@ func connectInMemory(client: EmbeddedChannel, server: EmbeddedChannel) throws { XCTAssertNoThrow(try connectFuture.wait()) } -extension ChannelPipeline { +extension ChannelPipeline.SynchronousOperations { func assertContains(handler: ChannelHandler, file: StaticString = #filePath, line: UInt = #line) { do { - _ = try self.context(handler: handler).wait() + _ = try self.context(handler: handler) } catch { XCTFail("Handler \(handler) missing from \(self)", file: (file), line: line) } @@ -37,7 +37,7 @@ extension ChannelPipeline { func assertDoesNotContain(handler: ChannelHandler, file: StaticString = #filePath, line: UInt = #line) { do { - _ = try self.context(handler: handler).wait() + _ = try self.context(handler: handler) XCTFail("Handler \(handler) present in \(self)", file: (file), line: line) } catch { // Expected @@ -46,17 +46,9 @@ extension ChannelPipeline { } final class UnwrappingTests: XCTestCase { - static var cert: NIOSSLCertificate! - static var key: NIOSSLPrivateKey! - static var encryptedKeyPath: String! - - override class func setUp() { - super.setUp() - guard boringSSLIsInitialized else { fatalError() } - let (cert, key) = generateSelfSignedCert() - UnwrappingTests.cert = cert - UnwrappingTests.key = key - } + static let _certAndKey = generateSelfSignedCert() + static let cert = UnwrappingTests._certAndKey.0 + static let key = UnwrappingTests._certAndKey.1 private func configuredSSLContext(file: StaticString = #filePath, line: UInt = #line) throws -> NIOSSLContext { var config = TLSConfiguration.makeServerConfiguration( @@ -84,16 +76,16 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: context))) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Mark the closure of the channels. - clientChannel.closeFuture.whenComplete { _ in + clientChannel.closeFuture.assumeIsolated().whenComplete { _ in clientClosed = true } - serverChannel.closeFuture.whenComplete { _ in + serverChannel.closeFuture.assumeIsolated().whenComplete { _ in serverClosed = true } @@ -104,7 +96,7 @@ final class UnwrappingTests: XCTestCase { // Let's unwrap the client connection. With no additional configuration, this will cause the server // to close. The client will not close because interactInMemory does not propagate closure. let stopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - stopPromise.futureResult.whenComplete { _ in + stopPromise.futureResult.assumeIsolated().whenComplete { _ in unwrapped = true } clientHandler.stopTLS(promise: stopPromise) @@ -115,7 +107,7 @@ final class UnwrappingTests: XCTestCase { XCTAssertNoThrow(try interactInMemory(clientChannel: clientChannel, serverChannel: serverChannel)) - clientChannel.pipeline.assertDoesNotContain(handler: clientHandler) + clientChannel.pipeline.syncOperations.assertDoesNotContain(handler: clientHandler) (serverChannel.eventLoop as! EmbeddedEventLoop).run() (clientChannel.eventLoop as! EmbeddedEventLoop).run() @@ -143,16 +135,16 @@ final class UnwrappingTests: XCTestCase { let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) let serverHandler = try assertNoThrowWithValue(NIOSSLServerHandler(context: context)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(serverHandler).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(serverHandler)) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Mark the closure of the channels. - clientChannel.closeFuture.whenComplete { _ in + clientChannel.closeFuture.assumeIsolated().whenComplete { _ in clientClosed = true } - serverChannel.closeFuture.whenComplete { _ in + serverChannel.closeFuture.assumeIsolated().whenComplete { _ in serverClosed = true } @@ -163,13 +155,13 @@ final class UnwrappingTests: XCTestCase { // Let's unwrap the client connection and the server connection at the same time. This should // not close either channel. let clientStopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - clientStopPromise.futureResult.whenComplete { _ in + clientStopPromise.futureResult.assumeIsolated().whenComplete { _ in clientUnwrapped = true } clientHandler.stopTLS(promise: clientStopPromise) let serverStopPromise: EventLoopPromise = serverChannel.eventLoop.makePromise() - serverStopPromise.futureResult.whenComplete { _ in + serverStopPromise.futureResult.assumeIsolated().whenComplete { _ in serverUnwrapped = true } serverHandler.stopTLS(promise: serverStopPromise) @@ -181,8 +173,8 @@ final class UnwrappingTests: XCTestCase { XCTAssertNoThrow(try interactInMemory(clientChannel: clientChannel, serverChannel: serverChannel)) - clientChannel.pipeline.assertDoesNotContain(handler: clientHandler) - serverChannel.pipeline.assertDoesNotContain(handler: serverHandler) + clientChannel.pipeline.syncOperations.assertDoesNotContain(handler: clientHandler) + serverChannel.pipeline.syncOperations.assertDoesNotContain(handler: serverHandler) (serverChannel.eventLoop as! EmbeddedEventLoop).run() (clientChannel.eventLoop as! EmbeddedEventLoop).run() @@ -209,13 +201,13 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: context))) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Mark the closure of the client. - clientChannel.closeFuture.whenComplete { _ in + clientChannel.closeFuture.assumeIsolated().whenComplete { _ in clientClosed = true } @@ -225,7 +217,7 @@ final class UnwrappingTests: XCTestCase { // Let's unwrap the client connection. let clientStopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - clientStopPromise.futureResult.map { + clientStopPromise.futureResult.assumeIsolated().map { XCTFail("Must not succeed") }.whenFailure { error in XCTAssertEqual(error as? NIOTLSUnwrappingError, NIOTLSUnwrappingError.closeRequestedDuringUnwrap) @@ -234,7 +226,7 @@ final class UnwrappingTests: XCTestCase { clientHandler.stopTLS(promise: clientStopPromise) // Now we're going to close the client. - clientChannel.close().whenComplete { _ in + clientChannel.close().assumeIsolated().whenComplete { _ in XCTAssertFalse(clientClosed) XCTAssertTrue(clientUnwrapped) } @@ -245,7 +237,7 @@ final class UnwrappingTests: XCTestCase { XCTAssertNoThrow( try interactInMemory(clientChannel: clientChannel, serverChannel: serverChannel, runLoops: false) ) - clientChannel.pipeline.assertContains(handler: clientHandler) + clientChannel.pipeline.syncOperations.assertContains(handler: clientHandler) (serverChannel.eventLoop as! EmbeddedEventLoop).run() (clientChannel.eventLoop as! EmbeddedEventLoop).run() @@ -270,13 +262,13 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: context))) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Mark the closure of the client. - clientChannel.closeFuture.whenComplete { _ in + clientChannel.closeFuture.assumeIsolated().whenComplete { _ in clientClosed = true } @@ -286,7 +278,7 @@ final class UnwrappingTests: XCTestCase { // Let's unwrap the client connection. let clientStopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - clientStopPromise.futureResult.map { + clientStopPromise.futureResult.assumeIsolated().map { XCTFail("Must not succeed") }.whenFailure { error in XCTAssertEqual(error as? NIOSSLError, .uncleanShutdown) @@ -299,7 +291,7 @@ final class UnwrappingTests: XCTestCase { // Now we're going to simulate the client receiving a TCP FIN the other way. clientChannel.pipeline.fireChannelInactive() - clientChannel.pipeline.assertContains(handler: clientHandler) + clientChannel.pipeline.syncOperations.assertContains(handler: clientHandler) (clientChannel.eventLoop as! EmbeddedEventLoop).run() XCTAssertTrue(clientUnwrapped) @@ -323,10 +315,10 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: context))) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Connect. This should lead to a completed handshake. XCTAssertNoThrow(try connectInMemory(client: clientChannel, server: serverChannel)) @@ -335,7 +327,7 @@ final class UnwrappingTests: XCTestCase { // Let's unwrap the client connection twice. We'll ignore the first promise. let dummyPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() let stopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - stopPromise.futureResult.whenComplete { _ in + stopPromise.futureResult.assumeIsolated().whenComplete { _ in promiseCalled = true } clientHandler.stopTLS(promise: dummyPromise) @@ -361,10 +353,10 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: context))) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Connect. This should lead to a completed handshake. XCTAssertNoThrow(try connectInMemory(client: clientChannel, server: serverChannel)) @@ -372,7 +364,7 @@ final class UnwrappingTests: XCTestCase { // Let's unwrap the client connection twice. We'll only send a promise the second time. let stopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - stopPromise.futureResult.whenComplete { _ in + stopPromise.futureResult.assumeIsolated().whenComplete { _ in promiseCalled = true } clientHandler.stopTLS(promise: nil) @@ -395,12 +387,12 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let handler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try channel.pipeline.addHandler(handler).wait()) - channel.pipeline.assertContains(handler: handler) + XCTAssertNoThrow(try channel.pipeline.syncOperations.addHandler(handler)) + channel.pipeline.syncOperations.assertContains(handler: handler) // Let's unwrap. This should succeed easily. let stopPromise: EventLoopPromise = channel.eventLoop.makePromise() - stopPromise.futureResult.whenComplete { _ in + stopPromise.futureResult.assumeIsolated().whenComplete { _ in promiseCalled = true } @@ -424,10 +416,10 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: context))) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Connect. This should lead to a completed handshake. XCTAssertNoThrow(try connectInMemory(client: clientChannel, server: serverChannel)) @@ -435,7 +427,7 @@ final class UnwrappingTests: XCTestCase { // Let's unwrap the client connection. let stopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - stopPromise.futureResult.whenComplete { _ in + stopPromise.futureResult.assumeIsolated().whenComplete { _ in promiseCalled = true } clientHandler.stopTLS(promise: stopPromise) @@ -467,10 +459,10 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: context))) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Connect. This should lead to a completed handshake. XCTAssertNoThrow(try connectInMemory(client: clientChannel, server: serverChannel)) @@ -506,13 +498,13 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: context))) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Mark the closure of the client. - clientChannel.closeFuture.whenComplete { _ in + clientChannel.closeFuture.assumeIsolated().whenComplete { _ in clientClosed = true } @@ -522,7 +514,7 @@ final class UnwrappingTests: XCTestCase { // Let's unwrap the client connection. let clientStopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - clientStopPromise.futureResult.map { + clientStopPromise.futureResult.assumeIsolated().map { XCTFail("Must not succeed") }.whenFailure { error in switch error as? NIOSSLError { @@ -558,7 +550,7 @@ final class UnwrappingTests: XCTestCase { // The client should have errored out now. The handler should still be there, as unwrapping // has failed. XCTAssertTrue(clientUnwrapped) - clientChannel.pipeline.assertContains(handler: clientHandler) + clientChannel.pipeline.syncOperations.assertContains(handler: clientHandler) // Clean up by bringing the server up to speed serverChannel.pipeline.fireChannelInactive() @@ -577,10 +569,10 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: context))) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Connect. This should lead to a completed handshake. XCTAssertNoThrow(try connectInMemory(client: clientChannel, server: serverChannel)) @@ -590,7 +582,7 @@ final class UnwrappingTests: XCTestCase { var writeCompleted = false var buffer = clientChannel.allocator.buffer(capacity: 1024) buffer.writeStaticString("Hello, world!") - clientChannel.write(buffer).map { + clientChannel.write(buffer).assumeIsolated().map { XCTFail("Must not succeed") }.whenFailure { error in XCTAssertEqual(error as? NIOTLSUnwrappingError, .unflushedWriteOnUnwrap) @@ -600,7 +592,7 @@ final class UnwrappingTests: XCTestCase { // We haven't spun the event loop, so the handlers are still in the pipeline. Now attempt to unwrap. var unwrapped = false let stopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - stopPromise.futureResult.whenSuccess { + stopPromise.futureResult.assumeIsolated().whenSuccess { XCTAssertTrue(writeCompleted) unwrapped = true } @@ -627,10 +619,10 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: context))) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Connect. This should lead to a completed handshake. XCTAssertNoThrow(try connectInMemory(client: clientChannel, server: serverChannel)) @@ -640,7 +632,7 @@ final class UnwrappingTests: XCTestCase { var writeCompleted = false var buffer = clientChannel.allocator.buffer(capacity: 1024) buffer.writeStaticString("Hello, world!") - clientChannel.write(buffer).map { + clientChannel.write(buffer).assumeIsolated().map { XCTFail("Must not succeed") }.whenFailure { error in XCTAssertEqual(error as? ChannelError, .ioOnClosedChannel) @@ -650,7 +642,7 @@ final class UnwrappingTests: XCTestCase { // We haven't spun the event loop, so the handlers are still in the pipeline. Now attempt to unwrap. var unwrapped = false let stopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - stopPromise.futureResult.whenFailure { error in + stopPromise.futureResult.assumeIsolated().whenFailure { error in switch error as? BoringSSLError { case .some(.sslError): // ok @@ -687,15 +679,17 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: context))) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) let readPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(PromiseOnReadHandler(promise: readPromise)).wait()) + XCTAssertNoThrow( + try clientChannel.pipeline.syncOperations.addHandler(PromiseOnReadHandler(promise: readPromise)) + ) var readCompleted = false - readPromise.futureResult.whenSuccess { buffer in + readPromise.futureResult.assumeIsolated().whenSuccess { buffer in XCTAssertEqual(buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes), "Hello, world!") readCompleted = true } @@ -708,7 +702,7 @@ final class UnwrappingTests: XCTestCase { // to close. The client will not close because interactInMemory does not propagate closure. var unwrapped = false let stopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - stopPromise.futureResult.whenSuccess { + stopPromise.futureResult.assumeIsolated().whenSuccess { unwrapped = true } clientHandler.stopTLS(promise: stopPromise) @@ -752,16 +746,16 @@ final class UnwrappingTests: XCTestCase { let serverHandler = try assertNoThrowWithValue(NIOSSLServerHandler(context: context)) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(serverHandler).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(serverHandler)) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Mark the closure of the channels. - clientChannel.closeFuture.whenComplete { _ in + clientChannel.closeFuture.assumeIsolated().whenComplete { _ in clientClosed = true } - serverChannel.closeFuture.whenComplete { _ in + serverChannel.closeFuture.assumeIsolated().whenComplete { _ in serverClosed = true } @@ -772,7 +766,7 @@ final class UnwrappingTests: XCTestCase { // Let's unwrap the server connection. We are not going to interact in memory, because we want to simulate a // timeout. let stopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - stopPromise.futureResult.whenComplete { result in + stopPromise.futureResult.assumeIsolated().whenComplete { result in unwrapped = true switch result { @@ -794,7 +788,7 @@ final class UnwrappingTests: XCTestCase { XCTAssertFalse(clientClosed) XCTAssertFalse(serverClosed) XCTAssertTrue(unwrapped) - serverChannel.pipeline.assertDoesNotContain(handler: serverHandler) + serverChannel.pipeline.syncOperations.assertDoesNotContain(handler: serverHandler) XCTAssertThrowsError(try serverChannel.throwIfErrorCaught()) { error in XCTAssertTrue(error is NIOSSLCloseTimedOutError, "Unexpected error: \(error)") } @@ -802,7 +796,7 @@ final class UnwrappingTests: XCTestCase { // Now we do the same for the client to get it out of the pipeline too. Naturally, it'll time out. clientHandler.stopTLS(promise: nil) clientChannel.embeddedEventLoop.advanceTime(by: .seconds(5)) - clientChannel.pipeline.assertDoesNotContain(handler: clientHandler) + clientChannel.pipeline.syncOperations.assertDoesNotContain(handler: clientHandler) XCTAssertThrowsError(try clientChannel.throwIfErrorCaught()) { error in XCTAssertTrue(error is NIOSSLCloseTimedOutError, "Unexpected error: \(error)") } @@ -829,16 +823,16 @@ final class UnwrappingTests: XCTestCase { let serverHandler = try assertNoThrowWithValue(NIOSSLServerHandler(context: context)) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(serverHandler).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(serverHandler)) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Mark the closure of the channels. - clientChannel.closeFuture.whenComplete { _ in + clientChannel.closeFuture.assumeIsolated().whenComplete { _ in clientClosed = true } - serverChannel.closeFuture.whenComplete { _ in + serverChannel.closeFuture.assumeIsolated().whenComplete { _ in serverClosed = true } @@ -848,7 +842,7 @@ final class UnwrappingTests: XCTestCase { // Let's unwrap the server connection. let stopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - stopPromise.futureResult.whenSuccess { result in + stopPromise.futureResult.assumeIsolated().whenSuccess { result in unwrapped = true } serverHandler.stopTLS(promise: stopPromise) @@ -865,14 +859,14 @@ final class UnwrappingTests: XCTestCase { XCTAssertFalse(clientClosed) XCTAssertFalse(serverClosed) XCTAssertTrue(unwrapped) - serverChannel.pipeline.assertDoesNotContain(handler: serverHandler) + serverChannel.pipeline.syncOperations.assertDoesNotContain(handler: serverHandler) // Now advance time by 5 seconds and confirm that the server doesn't get closed. serverChannel.embeddedEventLoop.advanceTime(by: .seconds(5)) XCTAssertFalse(clientClosed) XCTAssertFalse(serverClosed) XCTAssertTrue(unwrapped) - serverChannel.pipeline.assertDoesNotContain(handler: serverHandler) + serverChannel.pipeline.syncOperations.assertDoesNotContain(handler: serverHandler) } func testUnwrappingAndClosingShareATimeout() throws { @@ -893,16 +887,16 @@ final class UnwrappingTests: XCTestCase { let serverHandler = try assertNoThrowWithValue(NIOSSLServerHandler(context: context)) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(serverHandler).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(serverHandler)) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(handshakeHandler)) // Mark the closure of the channels. - clientChannel.closeFuture.whenComplete { _ in + clientChannel.closeFuture.assumeIsolated().whenComplete { _ in clientClosed = true } - serverChannel.closeFuture.whenComplete { _ in + serverChannel.closeFuture.assumeIsolated().whenComplete { _ in serverClosed = true } @@ -913,7 +907,7 @@ final class UnwrappingTests: XCTestCase { // Let's unwrap the server connection. We are not going to interact in memory, because we want to simulate a // timeout. let stopPromise: EventLoopPromise = clientChannel.eventLoop.makePromise() - stopPromise.futureResult.whenComplete { result in + stopPromise.futureResult.assumeIsolated().whenComplete { result in unwrapped = true switch result { @@ -934,10 +928,10 @@ final class UnwrappingTests: XCTestCase { XCTAssertFalse(clientClosed) XCTAssertFalse(serverClosed) XCTAssertFalse(unwrapped) - serverChannel.pipeline.assertContains(handler: serverHandler) + serverChannel.pipeline.syncOperations.assertContains(handler: serverHandler) // Now we close. This will report success. - serverChannel.close().whenSuccess { result in + serverChannel.close().assumeIsolated().whenSuccess { result in closed = true } @@ -945,7 +939,7 @@ final class UnwrappingTests: XCTestCase { XCTAssertFalse(serverClosed) XCTAssertFalse(unwrapped) XCTAssertFalse(closed) - serverChannel.pipeline.assertContains(handler: serverHandler) + serverChannel.pipeline.syncOperations.assertContains(handler: serverHandler) // Now we advance two more seconds. This closes the connection. All the promises succeed. serverChannel.embeddedEventLoop.advanceTime(by: .seconds(2)) @@ -953,12 +947,12 @@ final class UnwrappingTests: XCTestCase { XCTAssertTrue(serverClosed) XCTAssertTrue(unwrapped) XCTAssertTrue(closed) - serverChannel.pipeline.assertDoesNotContain(handler: serverHandler) + serverChannel.pipeline.syncOperations.assertDoesNotContain(handler: serverHandler) // Now we do the same for the client to get it out of the pipeline too. Naturally, it'll time out. clientHandler.stopTLS(promise: nil) clientChannel.embeddedEventLoop.advanceTime(by: .seconds(5)) - clientChannel.pipeline.assertDoesNotContain(handler: clientHandler) + clientChannel.pipeline.syncOperations.assertDoesNotContain(handler: clientHandler) XCTAssertThrowsError(try clientChannel.throwIfErrorCaught()) { error in XCTAssertTrue(error is NIOSSLCloseTimedOutError, "Unexpected error: \(error)") } @@ -984,12 +978,12 @@ final class UnwrappingTests: XCTestCase { let context = try assertNoThrowWithValue(configuredSSLContext()) let serverHandler = try assertNoThrowWithValue(NIOSSLServerHandler(context: context)) let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil)) - XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait()) - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait()) + XCTAssertNoThrow(try serverChannel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: context))) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(clientHandler)) let handshakeHandler = HandshakeCompletedHandler() - XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait()) + XCTAssertNoThrow(try clientChannel.pipeline.syncOperations.addHandler(handshakeHandler)) - serverChannel.closeFuture.whenComplete { _ in + serverChannel.closeFuture.assumeIsolated().whenComplete { _ in serverClosed = true } @@ -1023,7 +1017,7 @@ final class UnwrappingTests: XCTestCase { XCTAssertFalse(serverUnwrapped) let serverStopPromise: EventLoopPromise = serverChannel.eventLoop.makePromise() - serverStopPromise.futureResult.whenComplete { _ in + serverStopPromise.futureResult.assumeIsolated().whenComplete { _ in serverUnwrapped = true } serverHandler.stopTLS(promise: serverStopPromise)