/* * Copyright 2019, gRPC Authors All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Logging import NIO import NIOTransportServices /// How a network implementation should be chosen. public struct NetworkPreference: Hashable { private enum Wrapped: Hashable { case best case userDefined(NetworkImplementation) } private var wrapped: Wrapped private init(_ wrapped: Wrapped) { self.wrapped = wrapped } /// Use the best available, that is, Network.framework (and NIOTransportServices) when it is /// available on Darwin platforms (macOS 10.14+, iOS 12.0+, tvOS 12.0+, watchOS 6.0+), and /// falling back to the POSIX network model otherwise. public static let best = NetworkPreference(.best) /// Use the given implementation. Doing so may require additional availability checks depending /// on the implementation. public static func userDefined(_ implementation: NetworkImplementation) -> NetworkPreference { return NetworkPreference(.userDefined(implementation)) } } /// The network implementation to use: POSIX sockets or Network.framework. This also determines /// which variant of NIO to use; NIO or NIOTransportServices, respectively. public struct NetworkImplementation: Hashable { fileprivate enum Wrapped: Hashable { case networkFramework case posix } fileprivate var wrapped: Wrapped private init(_ wrapped: Wrapped) { self.wrapped = wrapped } #if canImport(Network) /// Network.framework (NIOTransportServices). @available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) public static let networkFramework = NetworkImplementation(.networkFramework) #endif /// POSIX (NIO). public static let posix = NetworkImplementation(.posix) } extension NetworkPreference { /// The network implementation, and by extension the NIO variant which will be used. /// /// Network.framework is available on macOS 10.14+, iOS 12.0+, tvOS 12.0+ and watchOS 6.0+. /// /// This isn't directly useful when implementing code which branches on the network preference /// since that code will still need the appropriate availability check: /// /// - `@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)`, or /// - `#available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)`. public var implementation: NetworkImplementation { switch self.wrapped { case .best: #if canImport(Network) if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { return .networkFramework } else { // Older platforms must use the POSIX loop. return .posix } #else return .posix #endif case let .userDefined(implementation): return implementation } } } // MARK: - Generic Bootstraps // TODO: Revisit the handling of NIO/NIOTS once https://github.com/apple/swift-nio/issues/796 // is addressed. /// This protocol is intended as a layer of abstraction over `ClientBootstrap` and /// `NIOTSConnectionBootstrap`. public protocol ClientBootstrapProtocol { func connect(to: SocketAddress) -> EventLoopFuture func connect(host: String, port: Int) -> EventLoopFuture func connect(unixDomainSocketPath: String) -> EventLoopFuture func connectTimeout(_ timeout: TimeAmount) -> Self func channelOption(_ option: T, value: T.Value) -> Self where T: ChannelOption func channelInitializer(_ handler: @escaping (Channel) -> EventLoopFuture) -> Self } extension ClientBootstrap: ClientBootstrapProtocol {} #if canImport(Network) @available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) extension NIOTSConnectionBootstrap: ClientBootstrapProtocol {} #endif /// This protocol is intended as a layer of abstraction over `ServerBootstrap` and /// `NIOTSListenerBootstrap`. public protocol ServerBootstrapProtocol { func bind(to: SocketAddress) -> EventLoopFuture func bind(host: String, port: Int) -> EventLoopFuture func bind(unixDomainSocketPath: String) -> EventLoopFuture func serverChannelInitializer(_ initializer: @escaping (Channel) -> EventLoopFuture) -> Self func serverChannelOption(_ option: T, value: T.Value) -> Self where T: ChannelOption func childChannelInitializer(_ initializer: @escaping (Channel) -> EventLoopFuture) -> Self func childChannelOption(_ option: T, value: T.Value) -> Self where T: ChannelOption } extension ServerBootstrap: ServerBootstrapProtocol {} #if canImport(Network) @available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) extension NIOTSListenerBootstrap: ServerBootstrapProtocol {} #endif // MARK: - Bootstrap / EventLoopGroup helpers public enum PlatformSupport { /// Makes a new event loop group based on the network preference. /// /// If `.best` is chosen and `Network.framework` is available then `NIOTSEventLoopGroup` will /// be returned. A `MultiThreadedEventLoopGroup` will be returned otherwise. /// /// - Parameter loopCount: The number of event loops to create in the event loop group. /// - Parameter networkPreference: Network preference; defaulting to `.best`. public static func makeEventLoopGroup( loopCount: Int, networkPreference: NetworkPreference = .best, logger: Logger = Logger(label: "io.grpc", factory: { _ in SwiftLogNoOpLogHandler() }) ) -> EventLoopGroup { logger.debug("making EventLoopGroup for \(networkPreference) network preference") switch networkPreference.implementation.wrapped { case .networkFramework: #if canImport(Network) guard #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) else { logger.critical("Network.framework can be imported but is not supported on this platform") // This is gated by the availability of `.networkFramework` so should never happen. fatalError(".networkFramework is being used on an unsupported platform") } logger.debug("created NIOTSEventLoopGroup for \(networkPreference) preference") return NIOTSEventLoopGroup(loopCount: loopCount) #else fatalError(".networkFramework is being used on an unsupported platform") #endif case .posix: logger.debug("created MultiThreadedEventLoopGroup for \(networkPreference) preference") return MultiThreadedEventLoopGroup(numberOfThreads: loopCount) } } /// Makes a new client bootstrap using the given `EventLoopGroup`. /// /// If the `EventLoopGroup` is a `NIOTSEventLoopGroup` then the returned bootstrap will be a /// `NIOTSConnectionBootstrap`, otherwise it will be a `ClientBootstrap`. /// /// - Parameter group: The `EventLoopGroup` to use. public static func makeClientBootstrap( group: EventLoopGroup, logger: Logger = Logger(label: "io.grpc", factory: { _ in SwiftLogNoOpLogHandler() }) ) -> ClientBootstrapProtocol { logger.debug("making client bootstrap with event loop group of type \(type(of: group))") #if canImport(Network) if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { if let tsGroup = group as? NIOTSEventLoopGroup { logger .debug( "Network.framework is available and the group is correctly typed, creating a NIOTSConnectionBootstrap" ) return NIOTSConnectionBootstrap(group: tsGroup) } else if let qosEventLoop = group as? QoSEventLoop { logger .debug( "Network.framework is available and the group is correctly typed, creating a NIOTSConnectionBootstrap" ) return NIOTSConnectionBootstrap(group: qosEventLoop) } logger .debug( "Network.framework is available but the group is not typed for NIOTS, falling back to ClientBootstrap" ) } #endif logger.debug("creating a ClientBootstrap") return ClientBootstrap(group: group) } /// Makes a new server bootstrap using the given `EventLoopGroup`. /// /// If the `EventLoopGroup` is a `NIOTSEventLoopGroup` then the returned bootstrap will be a /// `NIOTSListenerBootstrap`, otherwise it will be a `ServerBootstrap`. /// /// - Parameter group: The `EventLoopGroup` to use. public static func makeServerBootstrap( group: EventLoopGroup, logger: Logger = Logger(label: "io.grpc", factory: { _ in SwiftLogNoOpLogHandler() }) ) -> ServerBootstrapProtocol { logger.debug("making server bootstrap with event loop group of type \(type(of: group))") #if canImport(Network) if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { if let tsGroup = group as? NIOTSEventLoopGroup { logger .debug( "Network.framework is available and the group is correctly typed, creating a NIOTSListenerBootstrap" ) return NIOTSListenerBootstrap(group: tsGroup) } else if let qosEventLoop = group as? QoSEventLoop { logger .debug( "Network.framework is available and the group is correctly typed, creating a NIOTSListenerBootstrap" ) return NIOTSListenerBootstrap(group: qosEventLoop) } logger .debug( "Network.framework is available but the group is not typed for NIOTS, falling back to ServerBootstrap" ) } #endif logger.debug("creating a ServerBootstrap") return ServerBootstrap(group: group) } /// Determines whether we may need to work around an issue in Network.framework with zero-length writes. /// /// See https://github.com/apple/swift-nio-transport-services/pull/72 for more. static func requiresZeroLengthWriteWorkaround(group: EventLoopGroup, hasTLS: Bool) -> Bool { #if canImport(Network) if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { if group is NIOTSEventLoopGroup || group is QoSEventLoop { // We need the zero-length write workaround on NIOTS when not using TLS. return !hasTLS } else { return false } } else { return false } #else return false #endif } }