diff --git a/Sources/GRPCHTTP2Core/Client/HTTP2ClientTransport.swift b/Sources/GRPCHTTP2Core/Client/HTTP2ClientTransport.swift index 03bc39f1c..03ad634e3 100644 --- a/Sources/GRPCHTTP2Core/Client/HTTP2ClientTransport.swift +++ b/Sources/GRPCHTTP2Core/Client/HTTP2ClientTransport.swift @@ -25,7 +25,7 @@ extension HTTP2ClientTransport { } extension HTTP2ClientTransport.Config { - public struct Compression: Sendable { + public struct Compression: Sendable, Hashable { /// The default algorithm used for compressing outbound messages. /// /// This can be overridden on a per-call basis via `CallOptions`. @@ -51,7 +51,7 @@ extension HTTP2ClientTransport.Config { } @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public struct Keepalive: Sendable { + public struct Keepalive: Sendable, Hashable { /// The amount of time to wait after reading data before sending a keepalive ping. /// /// - Note: The transport may choose to increase this value if it is less than 10 seconds. @@ -73,7 +73,7 @@ extension HTTP2ClientTransport.Config { } @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public struct Connection: Sendable { + public struct Connection: Sendable, Hashable { /// The maximum amount of time a connection may be idle before it's closed. /// /// Connections are considered idle when there are no open streams on them. Idle connections @@ -103,7 +103,7 @@ extension HTTP2ClientTransport.Config { } @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public struct Backoff: Sendable { + public struct Backoff: Sendable, Hashable { /// The initial duration to wait before reattempting to establish a connection. public var initial: Duration @@ -135,7 +135,7 @@ extension HTTP2ClientTransport.Config { } } - public struct HTTP2: Sendable { + public struct HTTP2: Sendable, Hashable { /// The max frame size, in bytes. /// /// The actual value used is clamped to `(1 << 14) ... (1 << 24) - 1` (the min and max values diff --git a/Sources/GRPCHTTP2Core/Internal/NIOSocketAddress+GRPCSocketAddress.swift b/Sources/GRPCHTTP2Core/Internal/NIOSocketAddress+GRPCSocketAddress.swift index db7eb537d..e27b07659 100644 --- a/Sources/GRPCHTTP2Core/Internal/NIOSocketAddress+GRPCSocketAddress.swift +++ b/Sources/GRPCHTTP2Core/Internal/NIOSocketAddress+GRPCSocketAddress.swift @@ -14,6 +14,7 @@ * limitations under the License. */ +private import GRPCCore package import NIOCore extension GRPCHTTP2Core.SocketAddress { @@ -38,6 +39,22 @@ extension GRPCHTTP2Core.SocketAddress { } extension NIOCore.SocketAddress { + package init(_ socketAddress: GRPCHTTP2Core.SocketAddress) throws { + if let ipv4 = socketAddress.ipv4 { + self = try Self(ipv4) + } else if let ipv6 = socketAddress.ipv6 { + self = try Self(ipv6) + } else if let unixDomainSocket = socketAddress.unixDomainSocket { + self = try Self(unixDomainSocket) + } else { + throw RPCError( + code: .internalError, + message: + "Unsupported mapping to NIOCore/SocketAddress for GRPCHTTP2Core/SocketAddress: \(socketAddress)." + ) + } + } + package init(_ address: GRPCHTTP2Core.SocketAddress.IPv4) throws { try self.init(ipAddress: address.host, port: address.port) } diff --git a/Sources/GRPCHTTP2Core/Server/HTTP2ServerTransport.swift b/Sources/GRPCHTTP2Core/Server/HTTP2ServerTransport.swift index 07eb6b696..e93b09c26 100644 --- a/Sources/GRPCHTTP2Core/Server/HTTP2ServerTransport.swift +++ b/Sources/GRPCHTTP2Core/Server/HTTP2ServerTransport.swift @@ -26,7 +26,7 @@ extension HTTP2ServerTransport { } extension HTTP2ServerTransport.Config { - public struct Compression: Sendable { + public struct Compression: Sendable, Hashable { /// Compression algorithms enabled for inbound messages. /// /// - Note: `CompressionAlgorithm.none` is always supported, even if it isn't set here. @@ -46,7 +46,7 @@ extension HTTP2ServerTransport.Config { } @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public struct Keepalive: Sendable { + public struct Keepalive: Sendable, Hashable { /// The amount of time to wait after reading data before sending a keepalive ping. public var time: Duration @@ -80,7 +80,7 @@ extension HTTP2ServerTransport.Config { } @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public struct ClientKeepaliveBehavior: Sendable { + public struct ClientKeepaliveBehavior: Sendable, Hashable { /// The minimum allowed interval the client is allowed to send keep-alive pings. /// Pings more frequent than this interval count as 'strikes' and the connection is closed if there are /// too many strikes. @@ -107,7 +107,7 @@ extension HTTP2ServerTransport.Config { } @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public struct Connection: Sendable { + public struct Connection: Sendable, Hashable { /// The maximum amount of time a connection may exist before being gracefully closed. public var maxAge: Duration? @@ -142,7 +142,7 @@ extension HTTP2ServerTransport.Config { } } - public struct HTTP2: Sendable { + public struct HTTP2: Sendable, Hashable { /// The maximum frame size to be used in an HTTP/2 connection. public var maxFrameSize: Int @@ -175,7 +175,7 @@ extension HTTP2ServerTransport.Config { } } - public struct RPC: Sendable { + public struct RPC: Sendable, Hashable { /// The maximum request payload size. public var maxRequestPayloadSize: Int diff --git a/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ClientTransport+Posix.swift b/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ClientTransport+Posix.swift index 57e969e3a..f63adf96a 100644 --- a/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ClientTransport+Posix.swift +++ b/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ClientTransport+Posix.swift @@ -16,11 +16,11 @@ public import GRPCCore public import GRPCHTTP2Core // should be @usableFromInline -public import NIOCore +public import NIOCore // has to be public because of EventLoopGroup param in init public import NIOPosix // has to be public because of default argument value in init #if canImport(NIOSSL) -import NIOSSL +private import NIOSSL #endif @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) @@ -28,7 +28,7 @@ extension HTTP2ClientTransport { /// A ``GRPCCore/ClientTransport`` using HTTP/2 built on top of `NIOPosix`. /// /// This transport builds on top of SwiftNIO's Posix networking layer and is suitable for use - /// on Linux and Darwin based platform (macOS, iOS, etc.) However, it's *strongly* recommended + /// on Linux and Darwin based platforms (macOS, iOS, etc.). However, it's *strongly* recommended /// that if you are targeting Darwin platforms then you should use the `NIOTS` variant of /// the ``GRPCHTTP2Core/HTTP2ClientTransport``. /// @@ -62,7 +62,7 @@ extension HTTP2ClientTransport { public struct Posix: ClientTransport { private let channel: GRPCChannel - /// Creates a new Posix based HTTP/2 client transport. + /// Creates a new NIOPosix-based HTTP/2 client transport. /// /// - Parameters: /// - target: A target to resolve. @@ -91,7 +91,6 @@ extension HTTP2ClientTransport { ) } - // Configure a connector. self.channel = GRPCChannel( resolver: resolver, connector: try Connector(eventLoopGroup: eventLoopGroup, config: config), diff --git a/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ServerTransport+Posix.swift b/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ServerTransport+Posix.swift index 0717f0f35..43da51ae0 100644 --- a/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ServerTransport+Posix.swift +++ b/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ServerTransport+Posix.swift @@ -425,24 +425,6 @@ extension HTTP2ServerTransport.Posix { } } -extension NIOCore.SocketAddress { - fileprivate init(_ socketAddress: GRPCHTTP2Core.SocketAddress) throws { - if let ipv4 = socketAddress.ipv4 { - self = try Self(ipv4) - } else if let ipv6 = socketAddress.ipv6 { - self = try Self(ipv6) - } else if let unixDomainSocket = socketAddress.unixDomainSocket { - self = try Self(unixDomainSocket) - } else { - throw RPCError( - code: .internalError, - message: - "Unsupported mapping to NIOCore/SocketAddress for GRPCHTTP2Core/SocketAddress: \(socketAddress)." - ) - } - } -} - extension ServerBootstrap { @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) fileprivate func bind( diff --git a/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ClientTransport+TransportServices.swift b/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ClientTransport+TransportServices.swift new file mode 100644 index 000000000..3c10615d6 --- /dev/null +++ b/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ClientTransport+TransportServices.swift @@ -0,0 +1,339 @@ +/* + * Copyright 2024, 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. + */ + +#if canImport(Network) +public import GRPCCore +public import GRPCHTTP2Core +public import NIOTransportServices // has to be public because of default argument value in init +public import NIOCore // has to be public because of EventLoopGroup param in init + +private import Network + +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension HTTP2ClientTransport { + /// A ``GRPCCore/ClientTransport`` using HTTP/2 built on top of `NIOTransportServices`. + /// + /// This transport builds on top of SwiftNIO's Transport Services networking layer and is the recommended + /// variant for use on Darwin-based platforms (macOS, iOS, etc.). + /// If you are targeting Linux platforms then you should use the `NIOPosix` variant of + /// the ``GRPCHTTP2Core/HTTP2ClientTransport``. + /// + /// To use this transport you need to provide a 'target' to connect to which will be resolved + /// by an appropriate resolver from the resolver registry. By default the resolver registry can + /// resolve DNS targets, IPv4 and IPv6 targets, and Unix domain socket targets. Virtual Socket + /// targets are not supported with this transport. If you use a custom target you must also provide an + /// appropriately configured registry. + /// + /// You can control various aspects of connection creation, management, security and RPC behavior via + /// the ``Config``. Load balancing policies and other RPC specific behavior can be configured via + /// the ``ServiceConfig`` (if it isn't provided by a resolver). + /// + /// Beyond creating the transport you don't need to interact with it directly, instead, pass it + /// to a `GRPCClient`: + /// + /// ```swift + /// try await withThrowingDiscardingTaskGroup { group in + /// let transport = try HTTP2ClientTransport.TransportServices( + /// target: .ipv4(host: "example.com"), + /// config: .defaults(transportSecurity: .plaintext) + /// ) + /// let client = GRPCClient(transport: transport) + /// group.addTask { + /// try await client.run() + /// } + /// + /// // ... + /// } + /// ``` + public struct TransportServices: ClientTransport { + private let channel: GRPCChannel + + public var retryThrottle: RetryThrottle? { + self.channel.retryThrottle + } + + /// Creates a new NIOTransportServices-based HTTP/2 client transport. + /// + /// - Parameters: + /// - target: A target to resolve. + /// - config: Configuration for the transport. + /// - resolverRegistry: A registry of resolver factories. + /// - serviceConfig: Service config controlling how the transport should establish and + /// load-balance connections. + /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to run connections on. This must + /// be a `MultiThreadedEventLoopGroup` or an `EventLoop` from + /// a `MultiThreadedEventLoopGroup`. + /// - Throws: When no suitable resolver could be found for the `target`. + public init( + target: any ResolvableTarget, + config: Config, + resolverRegistry: NameResolverRegistry = .defaults, + serviceConfig: ServiceConfig = ServiceConfig(), + eventLoopGroup: any EventLoopGroup = .singletonNIOTSEventLoopGroup + ) throws { + guard let resolver = resolverRegistry.makeResolver(for: target) else { + throw RuntimeError( + code: .transportError, + message: """ + No suitable resolvers to resolve '\(target)'. You must make sure that the resolver \ + registry has a suitable name resolver factory registered for the given target. + """ + ) + } + + self.channel = GRPCChannel( + resolver: resolver, + connector: Connector(eventLoopGroup: eventLoopGroup, config: config), + config: GRPCChannel.Config(transportServices: config), + defaultServiceConfig: serviceConfig + ) + } + + public func connect() async throws { + await self.channel.connect() + } + + public func beginGracefulShutdown() { + self.channel.beginGracefulShutdown() + } + + public func withStream( + descriptor: MethodDescriptor, + options: CallOptions, + _ closure: (RPCStream) async throws -> T + ) async throws -> T { + try await self.channel.withStream(descriptor: descriptor, options: options, closure) + } + + public func configuration(forMethod descriptor: MethodDescriptor) -> MethodConfig? { + self.channel.configuration(forMethod: descriptor) + } + } +} + +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension HTTP2ClientTransport.TransportServices { + struct Connector: HTTP2Connector { + private let config: HTTP2ClientTransport.TransportServices.Config + private let eventLoopGroup: any EventLoopGroup + + init( + eventLoopGroup: any EventLoopGroup, + config: HTTP2ClientTransport.TransportServices.Config + ) { + self.eventLoopGroup = eventLoopGroup + self.config = config + } + + func establishConnection( + to address: GRPCHTTP2Core.SocketAddress + ) async throws -> HTTP2Connection { + let bootstrap: NIOTSConnectionBootstrap + let isPlainText: Bool + switch self.config.transportSecurity.wrapped { + case .plaintext: + isPlainText = true + bootstrap = NIOTSConnectionBootstrap(group: self.eventLoopGroup) + + case .tls(let tlsConfig): + isPlainText = false + bootstrap = NIOTSConnectionBootstrap(group: self.eventLoopGroup) + .tlsOptions(try NWProtocolTLS.Options(tlsConfig)) + } + + let (channel, multiplexer) = try await bootstrap.connect(to: address) { channel in + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.configureGRPCClientPipeline( + channel: channel, + config: GRPCChannel.Config(transportServices: self.config) + ) + } + } + + return HTTP2Connection( + channel: channel, + multiplexer: multiplexer, + isPlaintext: isPlainText + ) + } + } +} + +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension HTTP2ClientTransport.TransportServices { + /// Configuration for the `TransportServices` transport. + public struct Config: Sendable { + /// Configuration for HTTP/2 connections. + public var http2: HTTP2ClientTransport.Config.HTTP2 + + /// Configuration for backoff used when establishing a connection. + public var backoff: HTTP2ClientTransport.Config.Backoff + + /// Configuration for connection management. + public var connection: HTTP2ClientTransport.Config.Connection + + /// Compression configuration. + public var compression: HTTP2ClientTransport.Config.Compression + + /// The transport's security. + public var transportSecurity: TransportSecurity + + /// Creates a new connection configuration. + /// + /// - Parameters: + /// - http2: HTTP2 configuration. + /// - backoff: Backoff configuration. + /// - connection: Connection configuration. + /// - compression: Compression configuration. + /// - transportSecurity: The transport's security configuration. + /// + /// - SeeAlso: ``defaults(_:)``. + public init( + http2: HTTP2ClientTransport.Config.HTTP2, + backoff: HTTP2ClientTransport.Config.Backoff, + connection: HTTP2ClientTransport.Config.Connection, + compression: HTTP2ClientTransport.Config.Compression, + transportSecurity: TransportSecurity + ) { + self.http2 = http2 + self.connection = connection + self.backoff = backoff + self.compression = compression + self.transportSecurity = transportSecurity + } + + /// Default values. + /// + /// - Parameters: + /// - transportSecurity: The security settings applied to the transport. + /// - configure: A closure which allows you to modify the defaults before returning them. + public static func defaults( + transportSecurity: TransportSecurity, + configure: (_ config: inout Self) -> Void = { _ in } + ) -> Self { + var config = Self( + http2: .defaults, + backoff: .defaults, + connection: .defaults, + compression: .defaults, + transportSecurity: transportSecurity + ) + configure(&config) + return config + } + } +} + +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension GRPCChannel.Config { + init(transportServices config: HTTP2ClientTransport.TransportServices.Config) { + self.init( + http2: config.http2, + backoff: config.backoff, + connection: config.connection, + compression: config.compression + ) + } +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension NIOTSConnectionBootstrap { + fileprivate func connect( + to address: GRPCHTTP2Core.SocketAddress, + childChannelInitializer: @escaping @Sendable (any Channel) -> EventLoopFuture + ) async throws -> Output { + if address.virtualSocket != nil { + throw RuntimeError( + code: .transportError, + message: """ + Virtual sockets are not supported by 'HTTP2ClientTransport.TransportServices'. \ + Please use the 'HTTP2ClientTransport.Posix' transport. + """ + ) + } else { + return try await self.connect( + to: NIOCore.SocketAddress(address), + channelInitializer: childChannelInitializer + ) + } + } +} + +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension ClientTransport where Self == HTTP2ClientTransport.TransportServices { + /// Create a new `TransportServices` based HTTP/2 client transport. + /// + /// - Parameters: + /// - target: A target to resolve. + /// - config: Configuration for the transport. + /// - resolverRegistry: A registry of resolver factories. + /// - serviceConfig: Service config controlling how the transport should establish and + /// load-balance connections. + /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to run connections on. This must + /// be a `NIOTSEventLoopGroup` or an `EventLoop` from + /// a `NIOTSEventLoopGroup`. + /// - Throws: When no suitable resolver could be found for the `target`. + public static func http2NIOTS( + target: any ResolvableTarget, + config: HTTP2ClientTransport.TransportServices.Config, + resolverRegistry: NameResolverRegistry = .defaults, + serviceConfig: ServiceConfig = ServiceConfig(), + eventLoopGroup: any EventLoopGroup = .singletonNIOTSEventLoopGroup + ) throws -> Self { + try HTTP2ClientTransport.TransportServices( + target: target, + config: config, + resolverRegistry: resolverRegistry, + serviceConfig: serviceConfig, + eventLoopGroup: eventLoopGroup + ) + } +} + +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension NWProtocolTLS.Options { + convenience init(_ tlsConfig: HTTP2ClientTransport.TransportServices.Config.TLS) throws { + self.init() + + guard let sec_identity = sec_identity_create(try tlsConfig.identityProvider()) else { + throw RuntimeError( + code: .transportError, + message: """ + There was an issue creating the SecIdentity required to set up TLS. \ + Please check your TLS configuration. + """ + ) + } + + sec_protocol_options_set_local_identity( + self.securityProtocolOptions, + sec_identity + ) + + sec_protocol_options_set_min_tls_protocol_version( + self.securityProtocolOptions, + .TLSv12 + ) + + for `protocol` in ["grpc-exp", "h2"] { + sec_protocol_options_add_tls_application_protocol( + self.securityProtocolOptions, + `protocol` + ) + } + } +} +#endif diff --git a/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ServerTransport+TransportServices.swift b/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ServerTransport+TransportServices.swift index 7b37bcc4d..12001861d 100644 --- a/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ServerTransport+TransportServices.swift +++ b/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ServerTransport+TransportServices.swift @@ -367,24 +367,6 @@ extension HTTP2ServerTransport.TransportServices { } } -extension NIOCore.SocketAddress { - fileprivate init(_ socketAddress: GRPCHTTP2Core.SocketAddress) throws { - if let ipv4 = socketAddress.ipv4 { - self = try Self(ipv4) - } else if let ipv6 = socketAddress.ipv6 { - self = try Self(ipv6) - } else if let unixDomainSocket = socketAddress.unixDomainSocket { - self = try Self(unixDomainSocket) - } else { - throw RPCError( - code: .internalError, - message: - "Unsupported mapping to NIOCore/SocketAddress for GRPCHTTP2Core/SocketAddress: \(socketAddress)." - ) - } - } -} - @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension NIOTSListenerBootstrap { fileprivate func bind( diff --git a/Sources/GRPCHTTP2TransportNIOTransportServices/TLSConfig.swift b/Sources/GRPCHTTP2TransportNIOTransportServices/TLSConfig.swift index 05840a42d..94bc7dcb2 100644 --- a/Sources/GRPCHTTP2TransportNIOTransportServices/TLSConfig.swift +++ b/Sources/GRPCHTTP2TransportNIOTransportServices/TLSConfig.swift @@ -60,4 +60,35 @@ extension HTTP2ServerTransport.TransportServices.Config { } } } + +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension HTTP2ClientTransport.TransportServices.Config { + /// The security configuration for this connection. + public struct TransportSecurity: Sendable { + package enum Wrapped: Sendable { + case plaintext + case tls(TLS) + } + + package let wrapped: Wrapped + + /// This connection is plaintext: no encryption will take place. + public static let plaintext = Self(wrapped: .plaintext) + + /// This connection will use TLS. + public static func tls(_ tls: TLS) -> Self { + Self(wrapped: .tls(tls)) + } + } + + public struct TLS: Sendable { + /// A provider for the `SecIdentity` to be used when setting up TLS. + public var identityProvider: @Sendable () throws -> SecIdentity + + /// Create a new HTTP2 NIO Transport Services transport TLS config. + public init(identityProvider: @Sendable @escaping () throws -> SecIdentity) { + self.identityProvider = identityProvider + } + } +} #endif diff --git a/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOPosixTests.swift b/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOPosixTests.swift index aa6af81c9..2d94ce482 100644 --- a/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOPosixTests.swift +++ b/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOPosixTests.swift @@ -170,6 +170,28 @@ final class HTTP2TransportNIOPosixTests: XCTestCase { } } + func testServerConfig_Defaults() throws { + let grpcConfig = HTTP2ServerTransport.Posix.Config.defaults( + transportSecurity: .plaintext + ) + + XCTAssertEqual(grpcConfig.compression, HTTP2ServerTransport.Config.Compression.defaults) + XCTAssertEqual(grpcConfig.connection, HTTP2ServerTransport.Config.Connection.defaults) + XCTAssertEqual(grpcConfig.http2, HTTP2ServerTransport.Config.HTTP2.defaults) + XCTAssertEqual(grpcConfig.rpc, HTTP2ServerTransport.Config.RPC.defaults) + } + + func testClientConfig_Defaults() throws { + let grpcConfig = HTTP2ClientTransport.Posix.Config.defaults( + transportSecurity: .plaintext + ) + + XCTAssertEqual(grpcConfig.compression, HTTP2ClientTransport.Config.Compression.defaults) + XCTAssertEqual(grpcConfig.connection, HTTP2ClientTransport.Config.Connection.defaults) + XCTAssertEqual(grpcConfig.http2, HTTP2ClientTransport.Config.HTTP2.defaults) + XCTAssertEqual(grpcConfig.backoff, HTTP2ClientTransport.Config.Backoff.defaults) + } + #if canImport(NIOSSL) static let samplePemCert = """ -----BEGIN CERTIFICATE----- diff --git a/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOTransportServicesTests.swift b/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOTransportServicesTests.swift index 8611bdb16..a74d8374e 100644 --- a/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOTransportServicesTests.swift +++ b/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOTransportServicesTests.swift @@ -199,13 +199,37 @@ final class HTTP2TransportNIOTransportServicesTests: XCTestCase { } } - func testTLSConfig_Defaults() throws { + func testServerConfig_Defaults() throws { let identityProvider = Self.loadIdentity let grpcTLSConfig = HTTP2ServerTransport.TransportServices.Config.TLS.defaults( identityProvider: identityProvider ) + let grpcConfig = HTTP2ServerTransport.TransportServices.Config.defaults( + transportSecurity: .tls(grpcTLSConfig) + ) + + XCTAssertEqual(grpcConfig.compression, HTTP2ServerTransport.Config.Compression.defaults) + XCTAssertEqual(grpcConfig.connection, HTTP2ServerTransport.Config.Connection.defaults) + XCTAssertEqual(grpcConfig.http2, HTTP2ServerTransport.Config.HTTP2.defaults) + XCTAssertEqual(grpcConfig.rpc, HTTP2ServerTransport.Config.RPC.defaults) XCTAssertEqual(try grpcTLSConfig.identityProvider(), try identityProvider()) XCTAssertEqual(grpcTLSConfig.requireALPN, false) } + + func testClientConfig_Defaults() throws { + let identityProvider = Self.loadIdentity + let grpcTLSConfig = HTTP2ClientTransport.TransportServices.Config.TLS( + identityProvider: identityProvider + ) + let grpcConfig = HTTP2ClientTransport.TransportServices.Config.defaults( + transportSecurity: .tls(grpcTLSConfig) + ) + + XCTAssertEqual(grpcConfig.compression, HTTP2ClientTransport.Config.Compression.defaults) + XCTAssertEqual(grpcConfig.connection, HTTP2ClientTransport.Config.Connection.defaults) + XCTAssertEqual(grpcConfig.http2, HTTP2ClientTransport.Config.HTTP2.defaults) + XCTAssertEqual(grpcConfig.backoff, HTTP2ClientTransport.Config.Backoff.defaults) + XCTAssertEqual(try grpcTLSConfig.identityProvider(), try identityProvider()) + } } #endif diff --git a/Tests/GRPCHTTP2TransportTests/HTTP2TransportTests.swift b/Tests/GRPCHTTP2TransportTests/HTTP2TransportTests.swift index 14a9d69c3..108cc709a 100644 --- a/Tests/GRPCHTTP2TransportTests/HTTP2TransportTests.swift +++ b/Tests/GRPCHTTP2TransportTests/HTTP2TransportTests.swift @@ -101,7 +101,7 @@ final class HTTP2TransportTests: XCTestCase { } func forEachClientAndHTTPStatusCodeServer( - _ kind: [Transport.Kind] = [.posix], + _ kind: [Transport.Kind] = [.posix, .niots], _ execute: (ControlClient, Transport.Kind) async throws -> Void ) async throws { for clientKind in kind { @@ -207,7 +207,20 @@ final class HTTP2TransportTests: XCTestCase { ) case .niots: - fatalError("NIOTS isn't supported yet") + #if canImport(Network) + var serviceConfig = ServiceConfig() + serviceConfig.loadBalancingConfig = [.roundRobin] + transport = try HTTP2ClientTransport.TransportServices( + target: target, + config: .defaults(transportSecurity: .plaintext) { + $0.compression.algorithm = compression + $0.compression.enabledAlgorithms = enabledCompression + }, + serviceConfig: serviceConfig + ) + #else + throw XCTSkip("Transport not supported on this platform") + #endif } return GRPCClient(transport: transport) @@ -1421,7 +1434,9 @@ final class HTTP2TransportTests: XCTestCase { extension [HTTP2TransportTests.Transport] { static let supported = [ HTTP2TransportTests.Transport(server: .posix, client: .posix), + HTTP2TransportTests.Transport(server: .niots, client: .niots), HTTP2TransportTests.Transport(server: .niots, client: .posix), + HTTP2TransportTests.Transport(server: .posix, client: .niots), ] }