diff --git a/Sources/NIOTransportServices/NIOTSConnectionChannel.swift b/Sources/NIOTransportServices/NIOTSConnectionChannel.swift index e961183..fb06e1a 100644 --- a/Sources/NIOTransportServices/NIOTSConnectionChannel.swift +++ b/Sources/NIOTransportServices/NIOTSConnectionChannel.swift @@ -893,4 +893,61 @@ extension NIOTSConnectionChannel { return SynchronousOptions(channel: self) } } + + +public struct NIOTSConnectionNotInitialized: Error, Hashable { + public init() {} +} + +public struct NIOTSChannelIsNotANIOTSConnectionChannel: Error, Hashable { + public init() {} +} + +@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) +extension NIOTSConnectionChannel { + fileprivate func metadata(definition: NWProtocolDefinition) throws -> NWProtocolMetadata? { + guard let nwConnection = self.nwConnection else { + throw NIOTSConnectionNotInitialized() + } + return nwConnection.metadata(definition: definition) + } +} + +@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) +extension Channel { + /// Retrieves the metadata for a specific protocol from the underlying ``NWConnection`` + /// - Throws: If `self` isn't a `NIOTS` channel with a `NWConnection` this method will throw + /// ``NIOTSChannelIsNotATransportServicesChannel`` or ``NIOTSConnectionNotInitialized``. + public func getMetadata(definition: NWProtocolDefinition) -> EventLoopFuture { + guard let channel = self as? NIOTSConnectionChannel else { + return self.eventLoop.makeFailedFuture(NIOTSChannelIsNotANIOTSConnectionChannel()) + } + if self.eventLoop.inEventLoop { + return self.eventLoop.makeCompletedFuture { + try channel.metadata(definition: definition) + } + } else { + return self.eventLoop.submit { + try channel.metadata(definition: definition) + } + } + } + + /// Retrieves the metadata for a specific protocol from the underlying ``NWConnection`` + /// - Precondition: Must be called on the `EventLoop` the `Channel` is running on. + /// - Throws: If `self` isn't a `NIOTS` channel with a `NWConnection` this method will throw + /// ``NIOTSChannelIsNotATransportServicesChannel`` or ``NIOTSConnectionNotInitialized``. + public func getMetadataSync( + definition: NWProtocolDefinition, + file: StaticString = #fileID, + line: UInt = #line + ) throws -> NWProtocolMetadata? { + self.eventLoop.preconditionInEventLoop(file: file, line: line) + guard let channel = self as? NIOTSConnectionChannel else { + throw NIOTSChannelIsNotANIOTSConnectionChannel() + } + return try channel.metadata(definition: definition) + } +} + #endif diff --git a/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift b/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift index 15b1ba7..f6433d6 100644 --- a/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift +++ b/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift @@ -173,6 +173,7 @@ final class NIOTSBootstrapTests: XCTestCase { XCTFail("can't connect to server1") return } + XCTAssertNotNil(try client1.getMetadata(definition: NWProtocolTCP.definition).wait() as? NWProtocolTCP.Metadata) XCTAssertNoThrow(try client1.writeAndFlush(buffer).wait()) // The TLS connection won't actually succeed but it takes Network.framework a while to tell us, we don't diff --git a/Tests/NIOTransportServicesTests/NIOTSChannelMetadataTests.swift b/Tests/NIOTransportServicesTests/NIOTSChannelMetadataTests.swift new file mode 100644 index 0000000..2e289ae --- /dev/null +++ b/Tests/NIOTransportServicesTests/NIOTSChannelMetadataTests.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2022 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(Network) +import Network +import NIOCore +import XCTest +@testable import NIOTransportServices + +@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) +final class NIOTSChannelMetadataTests: XCTestCase { + func testThrowsIfCalledOnWrongChannel() throws { + let eventLoopGroup = NIOTSEventLoopGroup() + defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } + let listenerBootsrap = NIOTSListenerBootstrap(group: eventLoopGroup) + let listenerChannel = try listenerBootsrap.bind(host: "localhost", port: 0).wait() + defer { XCTAssertNoThrow(try listenerChannel.close().wait()) } + + XCTAssertThrowsError(try listenerChannel.getMetadata(definition: NWProtocolTLS.definition).wait()) { error in + XCTAssertTrue(error is NIOTSChannelIsNotANIOTSConnectionChannel, "unexpected error \(error)") + } + try! listenerChannel.eventLoop.submit { + XCTAssertThrowsError(try listenerChannel.getMetadataSync(definition: NWProtocolTLS.definition)) { error in + XCTAssertTrue(error is NIOTSChannelIsNotANIOTSConnectionChannel, "unexpected error \(error)") + } + }.wait() + + } + func testThowsIfCalledOnANonInitializedChannel() { + let eventLoopGroup = NIOTSEventLoopGroup() + defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } + let channel = NIOTSConnectionChannel(eventLoop: eventLoopGroup.next() as! NIOTSEventLoop, tcpOptions: .init(), tlsOptions: .init()) + XCTAssertThrowsError(try channel.getMetadata(definition: NWProtocolTLS.definition).wait()) { error in + XCTAssertTrue(error is NIOTSConnectionNotInitialized, "unexpected error \(error)") + } + try! channel.eventLoop.submit { + XCTAssertThrowsError(try channel.getMetadataSync(definition: NWProtocolTLS.definition)) { error in + XCTAssertTrue(error is NIOTSConnectionNotInitialized, "unexpected error \(error)") + } + }.wait() + } +} +#endif