diff options
Diffstat (limited to 'src/objective-c')
66 files changed, 6214 insertions, 727 deletions
diff --git a/src/objective-c/!ProtoCompiler-gRPCPlugin.podspec b/src/objective-c/!ProtoCompiler-gRPCPlugin.podspec index 55ca6048bc..659cfebbdc 100644 --- a/src/objective-c/!ProtoCompiler-gRPCPlugin.podspec +++ b/src/objective-c/!ProtoCompiler-gRPCPlugin.podspec @@ -42,7 +42,7 @@ Pod::Spec.new do |s| # exclamation mark ensures that other "regular" pods will be able to find it as it'll be installed # before them. s.name = '!ProtoCompiler-gRPCPlugin' - v = '1.18.0-dev' + v = '1.19.0-dev' s.version = v s.summary = 'The gRPC ProtoC plugin generates Objective-C files from .proto services.' s.description = <<-DESC diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h index 803f19dedf..2ddd53a5c6 100644 --- a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h +++ b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h @@ -19,52 +19,20 @@ #include <AvailabilityMacros.h> -typedef NS_ENUM(NSInteger, GRPCCompressAlgorithm) { - GRPCCompressNone, - GRPCCompressDeflate, - GRPCCompressGzip, -}; - -/** - * Methods to configure GRPC channel options. - */ +// Deprecated interface. Please use GRPCCallOptions instead. @interface GRPCCall (ChannelArg) -/** - * Use the provided @c userAgentPrefix at the beginning of the HTTP User Agent string for all calls - * to the specified @c host. - */ + (void)setUserAgentPrefix:(nonnull NSString *)userAgentPrefix forHost:(nonnull NSString *)host; - -/** The default response size limit is 4MB. Set this to override that default. */ + (void)setResponseSizeLimit:(NSUInteger)limit forHost:(nonnull NSString *)host; - + (void)closeOpenConnections DEPRECATED_MSG_ATTRIBUTE( "The API for this feature is experimental, " "and might be removed or modified at any " "time."); - + (void)setDefaultCompressMethod:(GRPCCompressAlgorithm)algorithm forhost:(nonnull NSString *)host; - -/** Enable keepalive and configure keepalive parameters. A user should call this function once to - * enable keepalive for a particular host. gRPC client sends a ping after every \a interval ms to - * check if the transport is still alive. After waiting for \a timeout ms, if the client does not - * receive the ping ack, it closes the transport; all pending calls to this host will fail with - * error GRPC_STATUS_INTERNAL with error information "keepalive watchdog timeout". */ + (void)setKeepaliveWithInterval:(int)interval timeout:(int)timeout forHost:(nonnull NSString *)host; - -/** Enable/Disable automatic retry of gRPC calls on the channel. If automatic retry is enabled, the - * retry is controlled by server's service config. If automatic retry is disabled, failed calls are - * immediately returned to the application layer. */ + (void)enableRetry:(BOOL)enabled forHost:(nonnull NSString *)host; - -/** Set channel connection timeout and backoff parameters. All parameters are positive integers in - * milliseconds. Set a parameter to 0 to make gRPC use default value for that parameter. - * - * Refer to gRPC's doc at https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md for the - * details of each parameter. */ + (void)setMinConnectTimeout:(unsigned int)timeout initialBackoff:(unsigned int)initialBackoff maxBackoff:(unsigned int)maxBackoff diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m index 0e631fb3ad..ae60d6208e 100644 --- a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m +++ b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m @@ -18,6 +18,7 @@ #import "GRPCCall+ChannelArg.h" +#import "private/GRPCChannelPool.h" #import "private/GRPCHost.h" #import <grpc/impl/codegen/compression_types.h> @@ -31,11 +32,11 @@ + (void)setResponseSizeLimit:(NSUInteger)limit forHost:(nonnull NSString *)host { GRPCHost *hostConfig = [GRPCHost hostWithAddress:host]; - hostConfig.responseSizeLimitOverride = @(limit); + hostConfig.responseSizeLimitOverride = limit; } + (void)closeOpenConnections { - [GRPCHost flushChannelCache]; + [[GRPCChannelPool sharedInstance] disconnectAllChannels]; } + (void)setDefaultCompressMethod:(GRPCCompressAlgorithm)algorithm forhost:(nonnull NSString *)host { diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h b/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h index d7d15c4ee3..7d6f79b765 100644 --- a/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h +++ b/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h @@ -18,20 +18,12 @@ #import "GRPCCall.h" -/** Helpers for setting TLS Trusted Roots, Client Certificates, and Private Key */ +// Deprecated interface. Please use GRPCCallOptions instead. @interface GRPCCall (ChannelCredentials) -/** - * Use the provided @c pemRootCert as the set of trusted root Certificate Authorities for @c host. - */ + (BOOL)setTLSPEMRootCerts:(nullable NSString *)pemRootCert forHost:(nonnull NSString *)host error:(NSError *_Nullable *_Nullable)errorPtr; -/** - * Configures @c host with TLS/SSL Client Credentials and optionally trusted root Certificate - * Authorities. If @c pemRootCerts is nil, the default CA Certificates bundled with gRPC will be - * used. - */ + (BOOL)setTLSPEMRootCerts:(nullable NSString *)pemRootCerts withPrivateKey:(nullable NSString *)pemPrivateKey withCertChain:(nullable NSString *)pemCertChain diff --git a/src/objective-c/GRPCClient/GRPCCall+Cronet.h b/src/objective-c/GRPCClient/GRPCCall+Cronet.h index 2a5f6e9cf0..3059c6f186 100644 --- a/src/objective-c/GRPCClient/GRPCCall+Cronet.h +++ b/src/objective-c/GRPCClient/GRPCCall+Cronet.h @@ -20,22 +20,11 @@ #import "GRPCCall.h" -/** - * Methods for using cronet transport. - */ +// Deprecated interface. Please use GRPCCallOptions instead. @interface GRPCCall (Cronet) -/** - * This method should be called before issuing the first RPC. It should be - * called only once. Create an instance of Cronet engine in your app elsewhere - * and pass the instance pointer in the stream_engine parameter. Once set, - * all subsequent RPCs will use Cronet transport. The method is not thread - * safe. - */ + (void)useCronetWithEngine:(stream_engine*)engine; - + (stream_engine*)cronetEngine; - + (BOOL)isUsingCronet; @end diff --git a/src/objective-c/GRPCClient/GRPCCall+OAuth2.h b/src/objective-c/GRPCClient/GRPCCall+OAuth2.h index adb1042aa0..60cdc50bfd 100644 --- a/src/objective-c/GRPCClient/GRPCCall+OAuth2.h +++ b/src/objective-c/GRPCClient/GRPCCall+OAuth2.h @@ -18,34 +18,13 @@ #import "GRPCCall.h" -/** - * The protocol of an OAuth2 token object from which GRPCCall can acquire a token. - */ -@protocol GRPCAuthorizationProtocol -- (void)getTokenWithHandler:(void (^)(NSString *token))hander; -@end +#import "GRPCCallOptions.h" -/** Helpers for setting and reading headers compatible with OAuth2. */ +// Deprecated interface. Please use GRPCCallOptions instead. @interface GRPCCall (OAuth2) -/** - * Setting this property is equivalent to setting "Bearer <passed token>" as the value of the - * request header with key "authorization" (the authorization header). Setting it to nil removes the - * authorization header from the request. - * The value obtained by getting the property is the OAuth2 bearer token if the authorization header - * of the request has the form "Bearer <token>", or nil otherwise. - */ -@property(atomic, copy) NSString *oauth2AccessToken; - -/** Returns the value (if any) of the "www-authenticate" response header (the challenge header). */ -@property(atomic, readonly) NSString *oauth2ChallengeHeader; - -/** - * The authorization token object to be used when starting the call. If the value is set to nil, no - * oauth authentication will be used. - * - * If tokenProvider exists, it takes precedence over the token set by oauth2AccessToken. - */ +@property(atomic, copy) NSString* oauth2AccessToken; +@property(atomic, copy, readonly) NSString* oauth2ChallengeHeader; @property(atomic, strong) id<GRPCAuthorizationProtocol> tokenProvider; @end diff --git a/src/objective-c/GRPCClient/GRPCCall+Tests.h b/src/objective-c/GRPCClient/GRPCCall+Tests.h index 5d35182ae5..edaa5ed582 100644 --- a/src/objective-c/GRPCClient/GRPCCall+Tests.h +++ b/src/objective-c/GRPCClient/GRPCCall+Tests.h @@ -18,34 +18,13 @@ #import "GRPCCall.h" -/** - * Methods to let tune down the security of gRPC connections for specific hosts. These shouldn't be - * used in releases, but are sometimes needed for testing. - */ +// Deprecated interface. Please use GRPCCallOptions instead. @interface GRPCCall (Tests) -/** - * Establish all SSL connections to the provided host using the passed SSL target name and the root - * certificates found in the file at |certsPath|. - * - * Must be called before any gRPC call to that host is made. It's illegal to pass the same host to - * more than one invocation of the methods of this category. - */ + (void)useTestCertsPath:(NSString *)certsPath testName:(NSString *)testName forHost:(NSString *)host; - -/** - * Establish all connections to the provided host using cleartext instead of SSL. - * - * Must be called before any gRPC call to that host is made. It's illegal to pass the same host to - * more than one invocation of the methods of this category. - */ + (void)useInsecureConnectionsForHost:(NSString *)host; - -/** - * Resets all host configurations to their default values, and flushes all connections from the - * cache. - */ + (void)resetHostSettings; + @end diff --git a/src/objective-c/GRPCClient/GRPCCall+Tests.m b/src/objective-c/GRPCClient/GRPCCall+Tests.m index 0db3ad6b39..ac3b6a658f 100644 --- a/src/objective-c/GRPCClient/GRPCCall+Tests.m +++ b/src/objective-c/GRPCClient/GRPCCall+Tests.m @@ -20,6 +20,8 @@ #import "private/GRPCHost.h" +#import "GRPCCallOptions.h" + @implementation GRPCCall (Tests) + (void)useTestCertsPath:(NSString *)certsPath @@ -42,7 +44,7 @@ + (void)useInsecureConnectionsForHost:(NSString *)host { GRPCHost *hostConfig = [GRPCHost hostWithAddress:host]; - hostConfig.secure = NO; + hostConfig.transportType = GRPCTransportTypeInsecure; } + (void)resetHostSettings { diff --git a/src/objective-c/GRPCClient/GRPCCall.h b/src/objective-c/GRPCClient/GRPCCall.h index ddc6ae054d..6669067fbf 100644 --- a/src/objective-c/GRPCClient/GRPCCall.h +++ b/src/objective-c/GRPCClient/GRPCCall.h @@ -37,6 +37,10 @@ #include <AvailabilityMacros.h> +#include "GRPCCallOptions.h" + +NS_ASSUME_NONNULL_BEGIN + #pragma mark gRPC errors /** Domain of NSError objects produced by gRPC. */ @@ -140,42 +144,148 @@ typedef NS_ENUM(NSUInteger, GRPCErrorCode) { }; /** - * Safety remark of a gRPC method as defined in RFC 2616 Section 9.1 + * Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by + * the server. */ -typedef NS_ENUM(NSUInteger, GRPCCallSafety) { - /** Signal that there is no guarantees on how the call affects the server state. */ - GRPCCallSafetyDefault = 0, - /** Signal that the call is idempotent. gRPC is free to use PUT verb. */ - GRPCCallSafetyIdempotentRequest = 1, - /** Signal that the call is cacheable and will not affect server state. gRPC is free to use GET - verb. */ - GRPCCallSafetyCacheableRequest = 2, -}; +extern NSString *const kGRPCHeadersKey; +extern NSString *const kGRPCTrailersKey; + +/** An object can implement this protocol to receive responses from server from a call. */ +@protocol GRPCResponseHandler<NSObject> + +@required /** - * Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by - * the server. + * All the responses must be issued to a user-provided dispatch queue. This property specifies the + * dispatch queue to be used for issuing the notifications. + */ +@property(atomic, readonly) dispatch_queue_t dispatchQueue; + +@optional + +/** + * Issued when initial metadata is received from the server. + */ +- (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata; + +/** + * Issued when a message is received from the server. The message is the raw data received from the + * server, with decompression and without proto deserialization. */ -extern id const kGRPCHeadersKey; -extern id const kGRPCTrailersKey; +- (void)didReceiveRawMessage:(nullable NSData *)message; + +/** + * Issued when a call finished. If the call finished successfully, \a error is nil and \a + * trainingMetadata consists any trailing metadata received from the server. Otherwise, \a error + * is non-nil and contains the corresponding error information, including gRPC error codes and + * error descriptions. + */ +- (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata + error:(nullable NSError *)error; + +@end + +/** + * Call related parameters. These parameters are automatically specified by Protobuf. If directly + * using the \a GRPCCall2 class, users should specify these parameters manually. + */ +@interface GRPCRequestOptions : NSObject<NSCopying> + +- (instancetype)init NS_UNAVAILABLE; + ++ (instancetype) new NS_UNAVAILABLE; + +/** Initialize with all properties. */ +- (instancetype)initWithHost:(NSString *)host + path:(NSString *)path + safety:(GRPCCallSafety)safety NS_DESIGNATED_INITIALIZER; + +/** The host serving the RPC service. */ +@property(copy, readonly) NSString *host; +/** The path to the RPC call. */ +@property(copy, readonly) NSString *path; +/** + * Specify whether the call is idempotent or cachable. gRPC may select different HTTP verbs for the + * call based on this information. The default verb used by gRPC is POST. + */ +@property(readonly) GRPCCallSafety safety; + +@end #pragma mark GRPCCall -/** Represents a single gRPC remote call. */ -@interface GRPCCall : GRXWriter +/** + * A \a GRPCCall2 object represents an RPC call. + */ +@interface GRPCCall2 : NSObject + +- (instancetype)init NS_UNAVAILABLE; + ++ (instancetype) new NS_UNAVAILABLE; /** - * The authority for the RPC. If nil, the default authority will be used. This property must be nil - * when Cronet transport is enabled. + * Designated initializer for a call. + * \param requestOptions Protobuf generated parameters for the call. + * \param responseHandler The object to which responses should be issued. + * \param callOptions Options for the call. */ -@property(atomic, copy, readwrite) NSString *serverName; +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + responseHandler:(id<GRPCResponseHandler>)responseHandler + callOptions:(nullable GRPCCallOptions *)callOptions + NS_DESIGNATED_INITIALIZER; +/** + * Convenience initializer for a call that uses default call options (see GRPCCallOptions.m for + * the default options). + */ +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + responseHandler:(id<GRPCResponseHandler>)responseHandler; /** - * The timeout for the RPC call in seconds. If set to 0, the call will not timeout. If set to - * positive, the gRPC call returns with status GRPCErrorCodeDeadlineExceeded if it is not completed - * within \a timeout seconds. A negative value is not allowed. + * Starts the call. This function must only be called once for each instance. */ -@property NSTimeInterval timeout; +- (void)start; + +/** + * Cancel the request of this call at best effort. It attempts to notify the server that the RPC + * should be cancelled, and issue didCloseWithTrailingMetadata:error: callback with error code + * CANCELED if no other error code has already been issued. + */ +- (void)cancel; + +/** + * Send a message to the server. Data are sent as raw bytes in gRPC message frames. + */ +- (void)writeData:(NSData *)data; + +/** + * Finish the RPC request and half-close the call. The server may still send messages and/or + * trailers to the client. The method must only be called once and after start is called. + */ +- (void)finish; + +/** + * Get a copy of the original call options. + */ +@property(readonly, copy) GRPCCallOptions *callOptions; + +/** Get a copy of the original request options. */ +@property(readonly, copy) GRPCRequestOptions *requestOptions; + +@end + +NS_ASSUME_NONNULL_END + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnullability-completeness" + +/** + * This interface is deprecated. Please use \a GRPCcall2. + * + * Represents a single gRPC remote call. + */ +@interface GRPCCall : GRXWriter + +- (instancetype)init NS_UNAVAILABLE; /** * The container of the request headers of an RPC conforms to this protocol, which is a subset of @@ -236,7 +346,7 @@ extern id const kGRPCTrailersKey; */ - (instancetype)initWithHost:(NSString *)host path:(NSString *)path - requestsWriter:(GRXWriter *)requestsWriter NS_DESIGNATED_INITIALIZER; + requestsWriter:(GRXWriter *)requestWriter; /** * Finishes the request side of this call, notifies the server that the RPC should be cancelled, and @@ -245,22 +355,13 @@ extern id const kGRPCTrailersKey; - (void)cancel; /** - * Set the call flag for a specific host path. - * - * Host parameter should not contain the scheme (http:// or https://), only the name or IP addr - * and the port number, for example @"localhost:5050". + * The following methods are deprecated. */ + (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path; - -/** - * Set the dispatch queue to be used for callbacks. Current implementation requires \a queue to be a - * serial queue. - * - * This configuration is only effective before the call starts. - */ +@property(atomic, copy, readwrite) NSString *serverName; +@property NSTimeInterval timeout; - (void)setResponseDispatchQueue:(dispatch_queue_t)queue; -// TODO(jcanizales): Let specify a deadline. As a category of GRXWriter? @end #pragma mark Backwards compatibiity @@ -283,3 +384,4 @@ DEPRECATED_MSG_ATTRIBUTE("Use NSDictionary or NSMutableDictionary instead.") @interface NSMutableDictionary (GRPCRequestHeaders)<GRPCRequestHeaders> @end #pragma clang diagnostic pop +#pragma clang diagnostic pop diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m index 084fbdeb49..83c6edc6e3 100644 --- a/src/objective-c/GRPCClient/GRPCCall.m +++ b/src/objective-c/GRPCClient/GRPCCall.m @@ -20,11 +20,16 @@ #import "GRPCCall+OAuth2.h" +#import <RxLibrary/GRXBufferedPipe.h> #import <RxLibrary/GRXConcurrentWriteable.h> #import <RxLibrary/GRXImmediateSingleWriter.h> +#import <RxLibrary/GRXWriter+Immediate.h> #include <grpc/grpc.h> #include <grpc/support/time.h> +#import "GRPCCallOptions.h" +#import "private/GRPCChannelPool.h" +#import "private/GRPCCompletionQueue.h" #import "private/GRPCConnectivityMonitor.h" #import "private/GRPCHost.h" #import "private/GRPCRequestHeaders.h" @@ -52,6 +57,313 @@ const char *kCFStreamVarName = "grpc_cfstream"; @property(atomic, strong) NSDictionary *responseHeaders; @property(atomic, strong) NSDictionary *responseTrailers; @property(atomic) BOOL isWaitingForToken; + +- (instancetype)initWithHost:(NSString *)host + path:(NSString *)path + callSafety:(GRPCCallSafety)safety + requestsWriter:(GRXWriter *)requestsWriter + callOptions:(GRPCCallOptions *)callOptions; + +@end + +@implementation GRPCRequestOptions + +- (instancetype)initWithHost:(NSString *)host path:(NSString *)path safety:(GRPCCallSafety)safety { + NSAssert(host.length != 0 && path.length != 0, @"host and path cannot be empty"); + if (host.length == 0 || path.length == 0) { + return nil; + } + if ((self = [super init])) { + _host = [host copy]; + _path = [path copy]; + _safety = safety; + } + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + GRPCRequestOptions *request = + [[GRPCRequestOptions alloc] initWithHost:_host path:_path safety:_safety]; + + return request; +} + +@end + +@implementation GRPCCall2 { + /** Options for the call. */ + GRPCCallOptions *_callOptions; + /** The handler of responses. */ + id<GRPCResponseHandler> _handler; + + // Thread safety of ivars below are protected by _dispatchQueue. + + /** + * Make use of legacy GRPCCall to make calls. Nullified when call is finished. + */ + GRPCCall *_call; + /** Flags whether initial metadata has been published to response handler. */ + BOOL _initialMetadataPublished; + /** Streaming call writeable to the underlying call. */ + GRXBufferedPipe *_pipe; + /** Serial dispatch queue for tasks inside the call. */ + dispatch_queue_t _dispatchQueue; + /** Flags whether call has started. */ + BOOL _started; + /** Flags whether call has been canceled. */ + BOOL _canceled; + /** Flags whether call has been finished. */ + BOOL _finished; +} + +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + responseHandler:(id<GRPCResponseHandler>)responseHandler + callOptions:(GRPCCallOptions *)callOptions { + NSAssert(requestOptions.host.length != 0 && requestOptions.path.length != 0, + @"Neither host nor path can be nil."); + NSAssert(requestOptions.safety <= GRPCCallSafetyCacheableRequest, @"Invalid call safety value."); + NSAssert(responseHandler != nil, @"Response handler required."); + if (requestOptions.host.length == 0 || requestOptions.path.length == 0) { + return nil; + } + if (requestOptions.safety > GRPCCallSafetyCacheableRequest) { + return nil; + } + if (responseHandler == nil) { + return nil; + } + + if ((self = [super init])) { + _requestOptions = [requestOptions copy]; + if (callOptions == nil) { + _callOptions = [[GRPCCallOptions alloc] init]; + } else { + _callOptions = [callOptions copy]; + } + _handler = responseHandler; + _initialMetadataPublished = NO; + _pipe = [GRXBufferedPipe pipe]; + // Set queue QoS only when iOS version is 8.0 or above and Xcode version is 9.0 or above +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + if (@available(iOS 8.0, macOS 10.10, *)) { + _dispatchQueue = dispatch_queue_create( + NULL, + dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0)); + } else { +#else + { +#endif + _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + } + dispatch_set_target_queue(_dispatchQueue, responseHandler.dispatchQueue); + _started = NO; + _canceled = NO; + _finished = NO; + } + + return self; +} + +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + responseHandler:(id<GRPCResponseHandler>)responseHandler { + return + [self initWithRequestOptions:requestOptions responseHandler:responseHandler callOptions:nil]; +} + +- (void)start { + GRPCCall *copiedCall = nil; + @synchronized(self) { + NSAssert(!_started, @"Call already started."); + NSAssert(!_canceled, @"Call already canceled."); + if (_started) { + return; + } + if (_canceled) { + return; + } + + _started = YES; + if (!_callOptions) { + _callOptions = [[GRPCCallOptions alloc] init]; + } + + _call = [[GRPCCall alloc] initWithHost:_requestOptions.host + path:_requestOptions.path + callSafety:_requestOptions.safety + requestsWriter:_pipe + callOptions:_callOptions]; + if (_callOptions.initialMetadata) { + [_call.requestHeaders addEntriesFromDictionary:_callOptions.initialMetadata]; + } + copiedCall = _call; + } + + void (^valueHandler)(id value) = ^(id value) { + @synchronized(self) { + if (self->_handler) { + if (!self->_initialMetadataPublished) { + self->_initialMetadataPublished = YES; + [self issueInitialMetadata:self->_call.responseHeaders]; + } + if (value) { + [self issueMessage:value]; + } + } + } + }; + void (^completionHandler)(NSError *errorOrNil) = ^(NSError *errorOrNil) { + @synchronized(self) { + if (self->_handler) { + if (!self->_initialMetadataPublished) { + self->_initialMetadataPublished = YES; + [self issueInitialMetadata:self->_call.responseHeaders]; + } + [self issueClosedWithTrailingMetadata:self->_call.responseTrailers error:errorOrNil]; + } + // Clearing _call must happen *after* dispatching close in order to get trailing + // metadata from _call. + if (self->_call) { + // Clean up the request writers. This should have no effect to _call since its + // response writeable is already nullified. + [self->_pipe writesFinishedWithError:nil]; + self->_call = nil; + self->_pipe = nil; + } + } + }; + id<GRXWriteable> responseWriteable = + [[GRXWriteable alloc] initWithValueHandler:valueHandler completionHandler:completionHandler]; + [copiedCall startWithWriteable:responseWriteable]; +} + +- (void)cancel { + GRPCCall *copiedCall = nil; + @synchronized(self) { + if (_canceled) { + return; + } + + _canceled = YES; + + copiedCall = _call; + _call = nil; + _pipe = nil; + + if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) { + dispatch_async(_dispatchQueue, ^{ + // Copy to local so that block is freed after cancellation completes. + id<GRPCResponseHandler> copiedHandler = nil; + @synchronized(self) { + copiedHandler = self->_handler; + self->_handler = nil; + } + + [copiedHandler didCloseWithTrailingMetadata:nil + error:[NSError errorWithDomain:kGRPCErrorDomain + code:GRPCErrorCodeCancelled + userInfo:@{ + NSLocalizedDescriptionKey : + @"Canceled by app" + }]]; + }); + } else { + _handler = nil; + } + } + [copiedCall cancel]; +} + +- (void)writeData:(NSData *)data { + GRXBufferedPipe *copiedPipe = nil; + @synchronized(self) { + NSAssert(!_canceled, @"Call already canceled."); + NSAssert(!_finished, @"Call is half-closed before sending data."); + if (_canceled) { + return; + } + if (_finished) { + return; + } + + if (_pipe) { + copiedPipe = _pipe; + } + } + [copiedPipe writeValue:data]; +} + +- (void)finish { + GRXBufferedPipe *copiedPipe = nil; + @synchronized(self) { + NSAssert(_started, @"Call not started."); + NSAssert(!_canceled, @"Call already canceled."); + NSAssert(!_finished, @"Call already half-closed."); + if (!_started) { + return; + } + if (_canceled) { + return; + } + if (_finished) { + return; + } + + if (_pipe) { + copiedPipe = _pipe; + _pipe = nil; + } + _finished = YES; + } + [copiedPipe writesFinishedWithError:nil]; +} + +- (void)issueInitialMetadata:(NSDictionary *)initialMetadata { + @synchronized(self) { + if (initialMetadata != nil && + [_handler respondsToSelector:@selector(didReceiveInitialMetadata:)]) { + dispatch_async(_dispatchQueue, ^{ + id<GRPCResponseHandler> copiedHandler = nil; + @synchronized(self) { + copiedHandler = self->_handler; + } + [copiedHandler didReceiveInitialMetadata:initialMetadata]; + }); + } + } +} + +- (void)issueMessage:(id)message { + @synchronized(self) { + if (message != nil && [_handler respondsToSelector:@selector(didReceiveRawMessage:)]) { + dispatch_async(_dispatchQueue, ^{ + id<GRPCResponseHandler> copiedHandler = nil; + @synchronized(self) { + copiedHandler = self->_handler; + } + [copiedHandler didReceiveRawMessage:message]; + }); + } + } +} + +- (void)issueClosedWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error { + @synchronized(self) { + if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) { + dispatch_async(_dispatchQueue, ^{ + id<GRPCResponseHandler> copiedHandler = nil; + @synchronized(self) { + copiedHandler = self->_handler; + // Clean up _handler so that no more responses are reported to the handler. + self->_handler = nil; + } + [copiedHandler didCloseWithTrailingMetadata:trailingMetadata error:error]; + }); + } else { + _handler = nil; + } + } +} + @end // The following methods of a C gRPC call object aren't reentrant, and thus @@ -75,6 +387,8 @@ const char *kCFStreamVarName = "grpc_cfstream"; NSString *_host; NSString *_path; + GRPCCallSafety _callSafety; + GRPCCallOptions *_callOptions; GRPCWrappedCall *_wrappedCall; GRPCConnectivityMonitor *_connectivityMonitor; @@ -113,6 +427,9 @@ const char *kCFStreamVarName = "grpc_cfstream"; // Whether the call is finished. If it is, should not call finishWithError again. BOOL _finished; + + // The OAuth2 token fetched from a token provider. + NSString *_fetchedOauth2AccessToken; } @synthesize state = _state; @@ -127,6 +444,9 @@ const char *kCFStreamVarName = "grpc_cfstream"; } + (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path { + if (host.length == 0 || path.length == 0) { + return; + } NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path]; switch (callSafety) { case GRPCCallSafetyDefault: @@ -148,24 +468,42 @@ const char *kCFStreamVarName = "grpc_cfstream"; return [callFlags[hostAndPath] intValue]; } -- (instancetype)init { - return [self initWithHost:nil path:nil requestsWriter:nil]; -} - // Designated initializer - (instancetype)initWithHost:(NSString *)host path:(NSString *)path requestsWriter:(GRXWriter *)requestWriter { + return [self initWithHost:host + path:path + callSafety:GRPCCallSafetyDefault + requestsWriter:requestWriter + callOptions:nil]; +} + +- (instancetype)initWithHost:(NSString *)host + path:(NSString *)path + callSafety:(GRPCCallSafety)safety + requestsWriter:(GRXWriter *)requestWriter + callOptions:(GRPCCallOptions *)callOptions { + // Purposely using pointer rather than length (host.length == 0) for backwards compatibility. + NSAssert(host != nil && path != nil, @"Neither host nor path can be nil."); + NSAssert(safety <= GRPCCallSafetyCacheableRequest, @"Invalid call safety value."); + NSAssert(requestWriter.state == GRXWriterStateNotStarted, + @"The requests writer can't be already started."); if (!host || !path) { - [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."]; + return nil; + } + if (safety > GRPCCallSafetyCacheableRequest) { + return nil; } if (requestWriter.state != GRXWriterStateNotStarted) { - [NSException raise:NSInvalidArgumentException - format:@"The requests writer can't be already started."]; + return nil; } + if ((self = [super init])) { _host = [host copy]; _path = [path copy]; + _callSafety = safety; + _callOptions = [callOptions copy]; // Serial queue to invoke the non-reentrant methods of the grpc_call object. _callQueue = dispatch_queue_create("io.grpc.call", NULL); @@ -209,11 +547,7 @@ const char *kCFStreamVarName = "grpc_cfstream"; [_responseWriteable enqueueSuccessfulCompletion]; } - // Connectivity monitor is not required for CFStream - char *enableCFStream = getenv(kCFStreamVarName); - if (enableCFStream == nil || enableCFStream[0] != '1') { - [GRPCConnectivityMonitor unregisterObserver:self]; - } + [GRPCConnectivityMonitor unregisterObserver:self]; // If the call isn't retained anywhere else, it can be deallocated now. _retainSelf = nil; @@ -221,13 +555,14 @@ const char *kCFStreamVarName = "grpc_cfstream"; - (void)cancelCall { // Can be called from any thread, any number of times. - [_wrappedCall cancel]; + @synchronized(self) { + [_wrappedCall cancel]; + } } - (void)cancel { - if (!self.isWaitingForToken) { + @synchronized(self) { [self cancelCall]; - } else { self.isWaitingForToken = NO; } [self @@ -317,11 +652,37 @@ const char *kCFStreamVarName = "grpc_cfstream"; #pragma mark Send headers -- (void)sendHeaders:(NSDictionary *)headers { +- (void)sendHeaders { + // TODO (mxyan): Remove after deprecated methods are removed + uint32_t callSafetyFlags = 0; + switch (_callSafety) { + case GRPCCallSafetyDefault: + callSafetyFlags = 0; + break; + case GRPCCallSafetyIdempotentRequest: + callSafetyFlags = GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST; + break; + case GRPCCallSafetyCacheableRequest: + callSafetyFlags = GRPC_INITIAL_METADATA_CACHEABLE_REQUEST; + break; + } + + NSMutableDictionary *headers = [_requestHeaders mutableCopy]; + NSString *fetchedOauth2AccessToken; + @synchronized(self) { + fetchedOauth2AccessToken = _fetchedOauth2AccessToken; + } + if (fetchedOauth2AccessToken != nil) { + headers[@"authorization"] = [kBearerPrefix stringByAppendingString:fetchedOauth2AccessToken]; + } else if (_callOptions.oauth2AccessToken != nil) { + headers[@"authorization"] = + [kBearerPrefix stringByAppendingString:_callOptions.oauth2AccessToken]; + } + // TODO(jcanizales): Add error handlers for async failures GRPCOpSendMetadata *op = [[GRPCOpSendMetadata alloc] initWithMetadata:headers - flags:[GRPCCall callFlagsForHost:_host path:_path] + flags:callSafetyFlags handler:nil]; // No clean-up needed after SEND_INITIAL_METADATA if (!_unaryCall) { [_wrappedCall startBatchWithOperations:@[ op ]]; @@ -458,13 +819,27 @@ const char *kCFStreamVarName = "grpc_cfstream"; _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable dispatchQueue:_responseQueue]; - _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host - serverName:_serverName - path:_path - timeout:_timeout]; - NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?"); + GRPCPooledChannel *channel = + [[GRPCChannelPool sharedInstance] channelWithHost:_host callOptions:_callOptions]; + GRPCWrappedCall *wrappedCall = [channel wrappedCallWithPath:_path + completionQueue:[GRPCCompletionQueue completionQueue] + callOptions:_callOptions]; + + if (wrappedCall == nil) { + [self maybeFinishWithError:[NSError errorWithDomain:kGRPCErrorDomain + code:GRPCErrorCodeUnavailable + userInfo:@{ + NSLocalizedDescriptionKey : + @"Failed to create call or channel." + }]]; + return; + } + + @synchronized(self) { + _wrappedCall = wrappedCall; + } - [self sendHeaders:_requestHeaders]; + [self sendHeaders]; [self invokeCall]; // Connectivity monitor is not required for CFStream @@ -486,18 +861,45 @@ const char *kCFStreamVarName = "grpc_cfstream"; // that the life of the instance is determined by this retain cycle. _retainSelf = self; - if (self.tokenProvider != nil) { - self.isWaitingForToken = YES; - __weak typeof(self) weakSelf = self; - [self.tokenProvider getTokenWithHandler:^(NSString *token) { - typeof(self) strongSelf = weakSelf; - if (strongSelf && strongSelf.isWaitingForToken) { - if (token) { - NSString *t = [kBearerPrefix stringByAppendingString:token]; - strongSelf.requestHeaders[kAuthorizationHeader] = t; + if (_callOptions == nil) { + GRPCMutableCallOptions *callOptions = [[GRPCHost callOptionsForHost:_host] mutableCopy]; + if (_serverName.length != 0) { + callOptions.serverAuthority = _serverName; + } + if (_timeout > 0) { + callOptions.timeout = _timeout; + } + uint32_t callFlags = [GRPCCall callFlagsForHost:_host path:_path]; + if (callFlags != 0) { + if (callFlags == GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST) { + _callSafety = GRPCCallSafetyIdempotentRequest; + } else if (callFlags == GRPC_INITIAL_METADATA_CACHEABLE_REQUEST) { + _callSafety = GRPCCallSafetyCacheableRequest; + } + } + + id<GRPCAuthorizationProtocol> tokenProvider = self.tokenProvider; + if (tokenProvider != nil) { + callOptions.authTokenProvider = tokenProvider; + } + _callOptions = callOptions; + } + + NSAssert(_callOptions.authTokenProvider == nil || _callOptions.oauth2AccessToken == nil, + @"authTokenProvider and oauth2AccessToken cannot be set at the same time"); + if (_callOptions.authTokenProvider != nil) { + @synchronized(self) { + self.isWaitingForToken = YES; + } + [_callOptions.authTokenProvider getTokenWithHandler:^(NSString *token) { + @synchronized(self) { + if (self.isWaitingForToken) { + if (token) { + self->_fetchedOauth2AccessToken = [token copy]; + } + [self startCallWithWriteable:writeable]; + self.isWaitingForToken = NO; } - [strongSelf startCallWithWriteable:writeable]; - strongSelf.isWaitingForToken = NO; } }]; } else { diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.h b/src/objective-c/GRPCClient/GRPCCallOptions.h new file mode 100644 index 0000000000..b5bf4c9eb6 --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCallOptions.h @@ -0,0 +1,348 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +/** + * Safety remark of a gRPC method as defined in RFC 2616 Section 9.1 + */ +typedef NS_ENUM(NSUInteger, GRPCCallSafety) { + /** Signal that there is no guarantees on how the call affects the server state. */ + GRPCCallSafetyDefault = 0, + /** Signal that the call is idempotent. gRPC is free to use PUT verb. */ + GRPCCallSafetyIdempotentRequest = 1, + /** + * Signal that the call is cacheable and will not affect server state. gRPC is free to use GET + * verb. + */ + GRPCCallSafetyCacheableRequest = 2, +}; + +// Compression algorithm to be used by a gRPC call +typedef NS_ENUM(NSUInteger, GRPCCompressionAlgorithm) { + GRPCCompressNone = 0, + GRPCCompressDeflate, + GRPCCompressGzip, + GRPCStreamCompressGzip, +}; + +// GRPCCompressAlgorithm is deprecated; use GRPCCompressionAlgorithm +typedef GRPCCompressionAlgorithm GRPCCompressAlgorithm; + +/** The transport to be used by a gRPC call */ +typedef NS_ENUM(NSUInteger, GRPCTransportType) { + GRPCTransportTypeDefault = 0, + /** gRPC internal HTTP/2 stack with BoringSSL */ + GRPCTransportTypeChttp2BoringSSL = 0, + /** Cronet stack */ + GRPCTransportTypeCronet, + /** Insecure channel. FOR TEST ONLY! */ + GRPCTransportTypeInsecure, +}; + +/** + * Implement this protocol to provide a token to gRPC when a call is initiated. + */ +@protocol GRPCAuthorizationProtocol + +/** + * This method is called when gRPC is about to start the call. When OAuth token is acquired, + * \a handler is expected to be called with \a token being the new token to be used for this call. + */ +- (void)getTokenWithHandler:(void (^)(NSString *_Nullable token))handler; + +@end + +@interface GRPCCallOptions : NSObject<NSCopying, NSMutableCopying> + +// Call parameters +/** + * The authority for the RPC. If nil, the default authority will be used. + * + * Note: This property does not have effect on Cronet transport and will be ignored. + * Note: This property cannot be used to validate a self-signed server certificate. It control the + * :authority header field of the call and performs an extra check that server's certificate + * matches the :authority header. + */ +@property(copy, readonly, nullable) NSString *serverAuthority; + +/** + * The timeout for the RPC call in seconds. If set to 0, the call will not timeout. If set to + * positive, the gRPC call returns with status GRPCErrorCodeDeadlineExceeded if it is not completed + * within \a timeout seconds. A negative value is not allowed. + */ +@property(readonly) NSTimeInterval timeout; + +// OAuth2 parameters. Users of gRPC may specify one of the following two parameters. + +/** + * The OAuth2 access token string. The string is prefixed with "Bearer " then used as value of the + * request's "authorization" header field. This parameter should not be used simultaneously with + * \a authTokenProvider. + */ +@property(copy, readonly, nullable) NSString *oauth2AccessToken; + +/** + * The interface to get the OAuth2 access token string. gRPC will attempt to acquire token when + * initiating the call. This parameter should not be used simultaneously with \a oauth2AccessToken. + */ +@property(readonly, nullable) id<GRPCAuthorizationProtocol> authTokenProvider; + +/** + * Initial metadata key-value pairs that should be included in the request. + */ +@property(copy, readonly, nullable) NSDictionary *initialMetadata; + +// Channel parameters; take into account of channel signature. + +/** + * Custom string that is prefixed to a request's user-agent header field before gRPC's internal + * user-agent string. + */ +@property(copy, readonly, nullable) NSString *userAgentPrefix; + +/** + * The size limit for the response received from server. If it is exceeded, an error with status + * code GRPCErrorCodeResourceExhausted is returned. + */ +@property(readonly) NSUInteger responseSizeLimit; + +/** + * The compression algorithm to be used by the gRPC call. For more details refer to + * https://github.com/grpc/grpc/blob/master/doc/compression.md + */ +@property(readonly) GRPCCompressionAlgorithm compressionAlgorithm; + +/** + * Enable/Disable gRPC call's retry feature. The default is enabled. For details of this feature + * refer to + * https://github.com/grpc/proposal/blob/master/A6-client-retries.md + */ +@property(readonly) BOOL retryEnabled; + +// HTTP/2 keep-alive feature. The parameter \a keepaliveInterval specifies the interval between two +// PING frames. The parameter \a keepaliveTimeout specifies the length of the period for which the +// call should wait for PING ACK. If PING ACK is not received after this period, the call fails. +// Negative values are not allowed. +@property(readonly) NSTimeInterval keepaliveInterval; +@property(readonly) NSTimeInterval keepaliveTimeout; + +// Parameters for connection backoff. Negative values are not allowed. +// For details of gRPC's backoff behavior, refer to +// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md +@property(readonly) NSTimeInterval connectMinTimeout; +@property(readonly) NSTimeInterval connectInitialBackoff; +@property(readonly) NSTimeInterval connectMaxBackoff; + +/** + * Specify channel args to be used for this call. For a list of channel args available, see + * grpc/grpc_types.h + */ +@property(copy, readonly, nullable) NSDictionary *additionalChannelArgs; + +// Parameters for SSL authentication. + +/** + * PEM format root certifications that is trusted. If set to nil, gRPC uses a list of default + * root certificates. + */ +@property(copy, readonly, nullable) NSString *PEMRootCertificates; + +/** + * PEM format private key for client authentication, if required by the server. + */ +@property(copy, readonly, nullable) NSString *PEMPrivateKey; + +/** + * PEM format certificate chain for client authentication, if required by the server. + */ +@property(copy, readonly, nullable) NSString *PEMCertificateChain; + +/** + * Select the transport type to be used for this call. + */ +@property(readonly) GRPCTransportType transportType; + +/** + * Override the hostname during the TLS hostname validation process. + */ +@property(copy, readonly, nullable) NSString *hostNameOverride; + +/** + * A string that specify the domain where channel is being cached. Channels with different domains + * will not get cached to the same connection. + */ +@property(copy, readonly, nullable) NSString *channelPoolDomain; + +/** + * Channel id allows control of channel caching within a channelPoolDomain. A call with a unique + * channelID will create a new channel (connection) instead of reusing an existing one. Multiple + * calls in the same channelPoolDomain using identical channelID are allowed to share connection + * if other channel options are also the same. + */ +@property(readonly) NSUInteger channelID; + +/** + * Return if the channel options are equal to another object. + */ +- (BOOL)hasChannelOptionsEqualTo:(GRPCCallOptions *)callOptions; + +/** + * Hash for channel options. + */ +@property(readonly) NSUInteger channelOptionsHash; + +@end + +@interface GRPCMutableCallOptions : GRPCCallOptions<NSCopying, NSMutableCopying> + +// Call parameters +/** + * The authority for the RPC. If nil, the default authority will be used. + * + * Note: This property does not have effect on Cronet transport and will be ignored. + * Note: This property cannot be used to validate a self-signed server certificate. It control the + * :authority header field of the call and performs an extra check that server's certificate + * matches the :authority header. + */ +@property(copy, readwrite, nullable) NSString *serverAuthority; + +/** + * The timeout for the RPC call in seconds. If set to 0, the call will not timeout. If set to + * positive, the gRPC call returns with status GRPCErrorCodeDeadlineExceeded if it is not completed + * within \a timeout seconds. Negative value is invalid; setting the parameter to negative value + * will reset the parameter to 0. + */ +@property(readwrite) NSTimeInterval timeout; + +// OAuth2 parameters. Users of gRPC may specify one of the following two parameters. + +/** + * The OAuth2 access token string. The string is prefixed with "Bearer " then used as value of the + * request's "authorization" header field. This parameter should not be used simultaneously with + * \a authTokenProvider. + */ +@property(copy, readwrite, nullable) NSString *oauth2AccessToken; + +/** + * The interface to get the OAuth2 access token string. gRPC will attempt to acquire token when + * initiating the call. This parameter should not be used simultaneously with \a oauth2AccessToken. + */ +@property(readwrite, nullable) id<GRPCAuthorizationProtocol> authTokenProvider; + +/** + * Initial metadata key-value pairs that should be included in the request. + */ +@property(copy, readwrite, nullable) NSDictionary *initialMetadata; + +// Channel parameters; take into account of channel signature. + +/** + * Custom string that is prefixed to a request's user-agent header field before gRPC's internal + * user-agent string. + */ +@property(copy, readwrite, nullable) NSString *userAgentPrefix; + +/** + * The size limit for the response received from server. If it is exceeded, an error with status + * code GRPCErrorCodeResourceExhausted is returned. + */ +@property(readwrite) NSUInteger responseSizeLimit; + +/** + * The compression algorithm to be used by the gRPC call. For more details refer to + * https://github.com/grpc/grpc/blob/master/doc/compression.md + */ +@property(readwrite) GRPCCompressionAlgorithm compressionAlgorithm; + +/** + * Enable/Disable gRPC call's retry feature. The default is enabled. For details of this feature + * refer to + * https://github.com/grpc/proposal/blob/master/A6-client-retries.md + */ +@property(readwrite) BOOL retryEnabled; + +// HTTP/2 keep-alive feature. The parameter \a keepaliveInterval specifies the interval between two +// PING frames. The parameter \a keepaliveTimeout specifies the length of the period for which the +// call should wait for PING ACK. If PING ACK is not received after this period, the call fails. +// Negative values are invalid; setting these parameters to negative value will reset the +// corresponding parameter to 0. +@property(readwrite) NSTimeInterval keepaliveInterval; +@property(readwrite) NSTimeInterval keepaliveTimeout; + +// Parameters for connection backoff. Negative value is invalid; setting the parameters to negative +// value will reset corresponding parameter to 0. +// For details of gRPC's backoff behavior, refer to +// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md +@property(readwrite) NSTimeInterval connectMinTimeout; +@property(readwrite) NSTimeInterval connectInitialBackoff; +@property(readwrite) NSTimeInterval connectMaxBackoff; + +/** + * Specify channel args to be used for this call. For a list of channel args available, see + * grpc/grpc_types.h + */ +@property(copy, readwrite, nullable) NSDictionary *additionalChannelArgs; + +// Parameters for SSL authentication. + +/** + * PEM format root certifications that is trusted. If set to nil, gRPC uses a list of default + * root certificates. + */ +@property(copy, readwrite, nullable) NSString *PEMRootCertificates; + +/** + * PEM format private key for client authentication, if required by the server. + */ +@property(copy, readwrite, nullable) NSString *PEMPrivateKey; + +/** + * PEM format certificate chain for client authentication, if required by the server. + */ +@property(copy, readwrite, nullable) NSString *PEMCertificateChain; + +/** + * Select the transport type to be used for this call. + */ +@property(readwrite) GRPCTransportType transportType; + +/** + * Override the hostname during the TLS hostname validation process. + */ +@property(copy, readwrite, nullable) NSString *hostNameOverride; + +/** + * A string that specify the domain where channel is being cached. Channels with different domains + * will not get cached to the same channel. For example, a gRPC example app may use the channel pool + * domain 'io.grpc.example' so that its calls do not reuse the channel created by other modules in + * the same process. + */ +@property(copy, readwrite, nullable) NSString *channelPoolDomain; + +/** + * Channel id allows a call to force creating a new channel (connection) rather than using a cached + * channel. Calls using distinct channelID's will not get cached to the same channel. + */ +@property(readwrite) NSUInteger channelID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.m b/src/objective-c/GRPCClient/GRPCCallOptions.m new file mode 100644 index 0000000000..e59a812bd8 --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCallOptions.m @@ -0,0 +1,525 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 "GRPCCallOptions.h" +#import "internal/GRPCCallOptions+Internal.h" + +// The default values for the call options. +static NSString *const kDefaultServerAuthority = nil; +static const NSTimeInterval kDefaultTimeout = 0; +static NSDictionary *const kDefaultInitialMetadata = nil; +static NSString *const kDefaultUserAgentPrefix = nil; +static const NSUInteger kDefaultResponseSizeLimit = 0; +static const GRPCCompressionAlgorithm kDefaultCompressionAlgorithm = GRPCCompressNone; +static const BOOL kDefaultRetryEnabled = YES; +static const NSTimeInterval kDefaultKeepaliveInterval = 0; +static const NSTimeInterval kDefaultKeepaliveTimeout = 0; +static const NSTimeInterval kDefaultConnectMinTimeout = 0; +static const NSTimeInterval kDefaultConnectInitialBackoff = 0; +static const NSTimeInterval kDefaultConnectMaxBackoff = 0; +static NSDictionary *const kDefaultAdditionalChannelArgs = nil; +static NSString *const kDefaultPEMRootCertificates = nil; +static NSString *const kDefaultPEMPrivateKey = nil; +static NSString *const kDefaultPEMCertificateChain = nil; +static NSString *const kDefaultOauth2AccessToken = nil; +static const id<GRPCAuthorizationProtocol> kDefaultAuthTokenProvider = nil; +static const GRPCTransportType kDefaultTransportType = GRPCTransportTypeChttp2BoringSSL; +static NSString *const kDefaultHostNameOverride = nil; +static const id kDefaultLogContext = nil; +static NSString *const kDefaultChannelPoolDomain = nil; +static const NSUInteger kDefaultChannelID = 0; + +// Check if two objects are equal. Returns YES if both are nil; +static BOOL areObjectsEqual(id obj1, id obj2) { + if (obj1 == obj2) { + return YES; + } + if (obj1 == nil || obj2 == nil) { + return NO; + } + return [obj1 isEqual:obj2]; +} + +@implementation GRPCCallOptions { + @protected + NSString *_serverAuthority; + NSTimeInterval _timeout; + NSString *_oauth2AccessToken; + id<GRPCAuthorizationProtocol> _authTokenProvider; + NSDictionary *_initialMetadata; + NSString *_userAgentPrefix; + NSUInteger _responseSizeLimit; + GRPCCompressionAlgorithm _compressionAlgorithm; + BOOL _retryEnabled; + NSTimeInterval _keepaliveInterval; + NSTimeInterval _keepaliveTimeout; + NSTimeInterval _connectMinTimeout; + NSTimeInterval _connectInitialBackoff; + NSTimeInterval _connectMaxBackoff; + NSDictionary *_additionalChannelArgs; + NSString *_PEMRootCertificates; + NSString *_PEMPrivateKey; + NSString *_PEMCertificateChain; + GRPCTransportType _transportType; + NSString *_hostNameOverride; + id<NSObject> _logContext; + NSString *_channelPoolDomain; + NSUInteger _channelID; +} + +@synthesize serverAuthority = _serverAuthority; +@synthesize timeout = _timeout; +@synthesize oauth2AccessToken = _oauth2AccessToken; +@synthesize authTokenProvider = _authTokenProvider; +@synthesize initialMetadata = _initialMetadata; +@synthesize userAgentPrefix = _userAgentPrefix; +@synthesize responseSizeLimit = _responseSizeLimit; +@synthesize compressionAlgorithm = _compressionAlgorithm; +@synthesize retryEnabled = _retryEnabled; +@synthesize keepaliveInterval = _keepaliveInterval; +@synthesize keepaliveTimeout = _keepaliveTimeout; +@synthesize connectMinTimeout = _connectMinTimeout; +@synthesize connectInitialBackoff = _connectInitialBackoff; +@synthesize connectMaxBackoff = _connectMaxBackoff; +@synthesize additionalChannelArgs = _additionalChannelArgs; +@synthesize PEMRootCertificates = _PEMRootCertificates; +@synthesize PEMPrivateKey = _PEMPrivateKey; +@synthesize PEMCertificateChain = _PEMCertificateChain; +@synthesize transportType = _transportType; +@synthesize hostNameOverride = _hostNameOverride; +@synthesize logContext = _logContext; +@synthesize channelPoolDomain = _channelPoolDomain; +@synthesize channelID = _channelID; + +- (instancetype)init { + return [self initWithServerAuthority:kDefaultServerAuthority + timeout:kDefaultTimeout + oauth2AccessToken:kDefaultOauth2AccessToken + authTokenProvider:kDefaultAuthTokenProvider + initialMetadata:kDefaultInitialMetadata + userAgentPrefix:kDefaultUserAgentPrefix + responseSizeLimit:kDefaultResponseSizeLimit + compressionAlgorithm:kDefaultCompressionAlgorithm + retryEnabled:kDefaultRetryEnabled + keepaliveInterval:kDefaultKeepaliveInterval + keepaliveTimeout:kDefaultKeepaliveTimeout + connectMinTimeout:kDefaultConnectMinTimeout + connectInitialBackoff:kDefaultConnectInitialBackoff + connectMaxBackoff:kDefaultConnectMaxBackoff + additionalChannelArgs:kDefaultAdditionalChannelArgs + PEMRootCertificates:kDefaultPEMRootCertificates + PEMPrivateKey:kDefaultPEMPrivateKey + PEMCertificateChain:kDefaultPEMCertificateChain + transportType:kDefaultTransportType + hostNameOverride:kDefaultHostNameOverride + logContext:kDefaultLogContext + channelPoolDomain:kDefaultChannelPoolDomain + channelID:kDefaultChannelID]; +} + +- (instancetype)initWithServerAuthority:(NSString *)serverAuthority + timeout:(NSTimeInterval)timeout + oauth2AccessToken:(NSString *)oauth2AccessToken + authTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider + initialMetadata:(NSDictionary *)initialMetadata + userAgentPrefix:(NSString *)userAgentPrefix + responseSizeLimit:(NSUInteger)responseSizeLimit + compressionAlgorithm:(GRPCCompressionAlgorithm)compressionAlgorithm + retryEnabled:(BOOL)retryEnabled + keepaliveInterval:(NSTimeInterval)keepaliveInterval + keepaliveTimeout:(NSTimeInterval)keepaliveTimeout + connectMinTimeout:(NSTimeInterval)connectMinTimeout + connectInitialBackoff:(NSTimeInterval)connectInitialBackoff + connectMaxBackoff:(NSTimeInterval)connectMaxBackoff + additionalChannelArgs:(NSDictionary *)additionalChannelArgs + PEMRootCertificates:(NSString *)PEMRootCertificates + PEMPrivateKey:(NSString *)PEMPrivateKey + PEMCertificateChain:(NSString *)PEMCertificateChain + transportType:(GRPCTransportType)transportType + hostNameOverride:(NSString *)hostNameOverride + logContext:(id)logContext + channelPoolDomain:(NSString *)channelPoolDomain + channelID:(NSUInteger)channelID { + if ((self = [super init])) { + _serverAuthority = [serverAuthority copy]; + _timeout = timeout < 0 ? 0 : timeout; + _oauth2AccessToken = [oauth2AccessToken copy]; + _authTokenProvider = authTokenProvider; + _initialMetadata = + initialMetadata == nil + ? nil + : [[NSDictionary alloc] initWithDictionary:initialMetadata copyItems:YES]; + _userAgentPrefix = [userAgentPrefix copy]; + _responseSizeLimit = responseSizeLimit; + _compressionAlgorithm = compressionAlgorithm; + _retryEnabled = retryEnabled; + _keepaliveInterval = keepaliveInterval < 0 ? 0 : keepaliveInterval; + _keepaliveTimeout = keepaliveTimeout < 0 ? 0 : keepaliveTimeout; + _connectMinTimeout = connectMinTimeout < 0 ? 0 : connectMinTimeout; + _connectInitialBackoff = connectInitialBackoff < 0 ? 0 : connectInitialBackoff; + _connectMaxBackoff = connectMaxBackoff < 0 ? 0 : connectMaxBackoff; + _additionalChannelArgs = + additionalChannelArgs == nil + ? nil + : [[NSDictionary alloc] initWithDictionary:additionalChannelArgs copyItems:YES]; + _PEMRootCertificates = [PEMRootCertificates copy]; + _PEMPrivateKey = [PEMPrivateKey copy]; + _PEMCertificateChain = [PEMCertificateChain copy]; + _transportType = transportType; + _hostNameOverride = [hostNameOverride copy]; + _logContext = logContext; + _channelPoolDomain = [channelPoolDomain copy]; + _channelID = channelID; + } + return self; +} + +- (nonnull id)copyWithZone:(NSZone *)zone { + GRPCCallOptions *newOptions = + [[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority + timeout:_timeout + oauth2AccessToken:_oauth2AccessToken + authTokenProvider:_authTokenProvider + initialMetadata:_initialMetadata + userAgentPrefix:_userAgentPrefix + responseSizeLimit:_responseSizeLimit + compressionAlgorithm:_compressionAlgorithm + retryEnabled:_retryEnabled + keepaliveInterval:_keepaliveInterval + keepaliveTimeout:_keepaliveTimeout + connectMinTimeout:_connectMinTimeout + connectInitialBackoff:_connectInitialBackoff + connectMaxBackoff:_connectMaxBackoff + additionalChannelArgs:_additionalChannelArgs + PEMRootCertificates:_PEMRootCertificates + PEMPrivateKey:_PEMPrivateKey + PEMCertificateChain:_PEMCertificateChain + transportType:_transportType + hostNameOverride:_hostNameOverride + logContext:_logContext + channelPoolDomain:_channelPoolDomain + channelID:_channelID]; + return newOptions; +} + +- (nonnull id)mutableCopyWithZone:(NSZone *)zone { + GRPCMutableCallOptions *newOptions = [[GRPCMutableCallOptions allocWithZone:zone] + initWithServerAuthority:[_serverAuthority copy] + timeout:_timeout + oauth2AccessToken:[_oauth2AccessToken copy] + authTokenProvider:_authTokenProvider + initialMetadata:[[NSDictionary alloc] initWithDictionary:_initialMetadata + copyItems:YES] + userAgentPrefix:[_userAgentPrefix copy] + responseSizeLimit:_responseSizeLimit + compressionAlgorithm:_compressionAlgorithm + retryEnabled:_retryEnabled + keepaliveInterval:_keepaliveInterval + keepaliveTimeout:_keepaliveTimeout + connectMinTimeout:_connectMinTimeout + connectInitialBackoff:_connectInitialBackoff + connectMaxBackoff:_connectMaxBackoff + additionalChannelArgs:[[NSDictionary alloc] initWithDictionary:_additionalChannelArgs + copyItems:YES] + PEMRootCertificates:[_PEMRootCertificates copy] + PEMPrivateKey:[_PEMPrivateKey copy] + PEMCertificateChain:[_PEMCertificateChain copy] + transportType:_transportType + hostNameOverride:[_hostNameOverride copy] + logContext:_logContext + channelPoolDomain:[_channelPoolDomain copy] + channelID:_channelID]; + return newOptions; +} + +- (BOOL)hasChannelOptionsEqualTo:(GRPCCallOptions *)callOptions { + if (callOptions == nil) return NO; + if (!areObjectsEqual(callOptions.userAgentPrefix, _userAgentPrefix)) return NO; + if (!(callOptions.responseSizeLimit == _responseSizeLimit)) return NO; + if (!(callOptions.compressionAlgorithm == _compressionAlgorithm)) return NO; + if (!(callOptions.retryEnabled == _retryEnabled)) return NO; + if (!(callOptions.keepaliveInterval == _keepaliveInterval)) return NO; + if (!(callOptions.keepaliveTimeout == _keepaliveTimeout)) return NO; + if (!(callOptions.connectMinTimeout == _connectMinTimeout)) return NO; + if (!(callOptions.connectInitialBackoff == _connectInitialBackoff)) return NO; + if (!(callOptions.connectMaxBackoff == _connectMaxBackoff)) return NO; + if (!areObjectsEqual(callOptions.additionalChannelArgs, _additionalChannelArgs)) return NO; + if (!areObjectsEqual(callOptions.PEMRootCertificates, _PEMRootCertificates)) return NO; + if (!areObjectsEqual(callOptions.PEMPrivateKey, _PEMPrivateKey)) return NO; + if (!areObjectsEqual(callOptions.PEMCertificateChain, _PEMCertificateChain)) return NO; + if (!areObjectsEqual(callOptions.hostNameOverride, _hostNameOverride)) return NO; + if (!(callOptions.transportType == _transportType)) return NO; + if (!areObjectsEqual(callOptions.logContext, _logContext)) return NO; + if (!areObjectsEqual(callOptions.channelPoolDomain, _channelPoolDomain)) return NO; + if (!(callOptions.channelID == _channelID)) return NO; + + return YES; +} + +- (NSUInteger)channelOptionsHash { + NSUInteger result = 0; + result ^= _userAgentPrefix.hash; + result ^= _responseSizeLimit; + result ^= _compressionAlgorithm; + result ^= _retryEnabled; + result ^= (unsigned int)(_keepaliveInterval * 1000); + result ^= (unsigned int)(_keepaliveTimeout * 1000); + result ^= (unsigned int)(_connectMinTimeout * 1000); + result ^= (unsigned int)(_connectInitialBackoff * 1000); + result ^= (unsigned int)(_connectMaxBackoff * 1000); + result ^= _additionalChannelArgs.hash; + result ^= _PEMRootCertificates.hash; + result ^= _PEMPrivateKey.hash; + result ^= _PEMCertificateChain.hash; + result ^= _hostNameOverride.hash; + result ^= _transportType; + result ^= _logContext.hash; + result ^= _channelPoolDomain.hash; + result ^= _channelID; + + return result; +} + +@end + +@implementation GRPCMutableCallOptions + +@dynamic serverAuthority; +@dynamic timeout; +@dynamic oauth2AccessToken; +@dynamic authTokenProvider; +@dynamic initialMetadata; +@dynamic userAgentPrefix; +@dynamic responseSizeLimit; +@dynamic compressionAlgorithm; +@dynamic retryEnabled; +@dynamic keepaliveInterval; +@dynamic keepaliveTimeout; +@dynamic connectMinTimeout; +@dynamic connectInitialBackoff; +@dynamic connectMaxBackoff; +@dynamic additionalChannelArgs; +@dynamic PEMRootCertificates; +@dynamic PEMPrivateKey; +@dynamic PEMCertificateChain; +@dynamic transportType; +@dynamic hostNameOverride; +@dynamic logContext; +@dynamic channelPoolDomain; +@dynamic channelID; + +- (instancetype)init { + return [self initWithServerAuthority:kDefaultServerAuthority + timeout:kDefaultTimeout + oauth2AccessToken:kDefaultOauth2AccessToken + authTokenProvider:kDefaultAuthTokenProvider + initialMetadata:kDefaultInitialMetadata + userAgentPrefix:kDefaultUserAgentPrefix + responseSizeLimit:kDefaultResponseSizeLimit + compressionAlgorithm:kDefaultCompressionAlgorithm + retryEnabled:kDefaultRetryEnabled + keepaliveInterval:kDefaultKeepaliveInterval + keepaliveTimeout:kDefaultKeepaliveTimeout + connectMinTimeout:kDefaultConnectMinTimeout + connectInitialBackoff:kDefaultConnectInitialBackoff + connectMaxBackoff:kDefaultConnectMaxBackoff + additionalChannelArgs:kDefaultAdditionalChannelArgs + PEMRootCertificates:kDefaultPEMRootCertificates + PEMPrivateKey:kDefaultPEMPrivateKey + PEMCertificateChain:kDefaultPEMCertificateChain + transportType:kDefaultTransportType + hostNameOverride:kDefaultHostNameOverride + logContext:kDefaultLogContext + channelPoolDomain:kDefaultChannelPoolDomain + channelID:kDefaultChannelID]; +} + +- (nonnull id)copyWithZone:(NSZone *)zone { + GRPCCallOptions *newOptions = + [[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority + timeout:_timeout + oauth2AccessToken:_oauth2AccessToken + authTokenProvider:_authTokenProvider + initialMetadata:_initialMetadata + userAgentPrefix:_userAgentPrefix + responseSizeLimit:_responseSizeLimit + compressionAlgorithm:_compressionAlgorithm + retryEnabled:_retryEnabled + keepaliveInterval:_keepaliveInterval + keepaliveTimeout:_keepaliveTimeout + connectMinTimeout:_connectMinTimeout + connectInitialBackoff:_connectInitialBackoff + connectMaxBackoff:_connectMaxBackoff + additionalChannelArgs:_additionalChannelArgs + PEMRootCertificates:_PEMRootCertificates + PEMPrivateKey:_PEMPrivateKey + PEMCertificateChain:_PEMCertificateChain + transportType:_transportType + hostNameOverride:_hostNameOverride + logContext:_logContext + channelPoolDomain:_channelPoolDomain + channelID:_channelID]; + return newOptions; +} + +- (nonnull id)mutableCopyWithZone:(NSZone *)zone { + GRPCMutableCallOptions *newOptions = [[GRPCMutableCallOptions allocWithZone:zone] + initWithServerAuthority:_serverAuthority + timeout:_timeout + oauth2AccessToken:_oauth2AccessToken + authTokenProvider:_authTokenProvider + initialMetadata:_initialMetadata + userAgentPrefix:_userAgentPrefix + responseSizeLimit:_responseSizeLimit + compressionAlgorithm:_compressionAlgorithm + retryEnabled:_retryEnabled + keepaliveInterval:_keepaliveInterval + keepaliveTimeout:_keepaliveTimeout + connectMinTimeout:_connectMinTimeout + connectInitialBackoff:_connectInitialBackoff + connectMaxBackoff:_connectMaxBackoff + additionalChannelArgs:[_additionalChannelArgs copy] + PEMRootCertificates:_PEMRootCertificates + PEMPrivateKey:_PEMPrivateKey + PEMCertificateChain:_PEMCertificateChain + transportType:_transportType + hostNameOverride:_hostNameOverride + logContext:_logContext + channelPoolDomain:_channelPoolDomain + channelID:_channelID]; + return newOptions; +} + +- (void)setServerAuthority:(NSString *)serverAuthority { + _serverAuthority = [serverAuthority copy]; +} + +- (void)setTimeout:(NSTimeInterval)timeout { + if (timeout < 0) { + _timeout = 0; + } else { + _timeout = timeout; + } +} + +- (void)setOauth2AccessToken:(NSString *)oauth2AccessToken { + _oauth2AccessToken = [oauth2AccessToken copy]; +} + +- (void)setAuthTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider { + _authTokenProvider = authTokenProvider; +} + +- (void)setInitialMetadata:(NSDictionary *)initialMetadata { + _initialMetadata = [[NSDictionary alloc] initWithDictionary:initialMetadata copyItems:YES]; +} + +- (void)setUserAgentPrefix:(NSString *)userAgentPrefix { + _userAgentPrefix = [userAgentPrefix copy]; +} + +- (void)setResponseSizeLimit:(NSUInteger)responseSizeLimit { + _responseSizeLimit = responseSizeLimit; +} + +- (void)setCompressionAlgorithm:(GRPCCompressionAlgorithm)compressionAlgorithm { + _compressionAlgorithm = compressionAlgorithm; +} + +- (void)setRetryEnabled:(BOOL)retryEnabled { + _retryEnabled = retryEnabled; +} + +- (void)setKeepaliveInterval:(NSTimeInterval)keepaliveInterval { + if (keepaliveInterval < 0) { + _keepaliveInterval = 0; + } else { + _keepaliveInterval = keepaliveInterval; + } +} + +- (void)setKeepaliveTimeout:(NSTimeInterval)keepaliveTimeout { + if (keepaliveTimeout < 0) { + _keepaliveTimeout = 0; + } else { + _keepaliveTimeout = keepaliveTimeout; + } +} + +- (void)setConnectMinTimeout:(NSTimeInterval)connectMinTimeout { + if (connectMinTimeout < 0) { + _connectMinTimeout = 0; + } else { + _connectMinTimeout = connectMinTimeout; + } +} + +- (void)setConnectInitialBackoff:(NSTimeInterval)connectInitialBackoff { + if (connectInitialBackoff < 0) { + _connectInitialBackoff = 0; + } else { + _connectInitialBackoff = connectInitialBackoff; + } +} + +- (void)setConnectMaxBackoff:(NSTimeInterval)connectMaxBackoff { + if (connectMaxBackoff < 0) { + _connectMaxBackoff = 0; + } else { + _connectMaxBackoff = connectMaxBackoff; + } +} + +- (void)setAdditionalChannelArgs:(NSDictionary *)additionalChannelArgs { + _additionalChannelArgs = + [[NSDictionary alloc] initWithDictionary:additionalChannelArgs copyItems:YES]; +} + +- (void)setPEMRootCertificates:(NSString *)PEMRootCertificates { + _PEMRootCertificates = [PEMRootCertificates copy]; +} + +- (void)setPEMPrivateKey:(NSString *)PEMPrivateKey { + _PEMPrivateKey = [PEMPrivateKey copy]; +} + +- (void)setPEMCertificateChain:(NSString *)PEMCertificateChain { + _PEMCertificateChain = [PEMCertificateChain copy]; +} + +- (void)setTransportType:(GRPCTransportType)transportType { + _transportType = transportType; +} + +- (void)setHostNameOverride:(NSString *)hostNameOverride { + _hostNameOverride = [hostNameOverride copy]; +} + +- (void)setLogContext:(id)logContext { + _logContext = logContext; +} + +- (void)setChannelPoolDomain:(NSString *)channelPoolDomain { + _channelPoolDomain = [channelPoolDomain copy]; +} + +- (void)setChannelID:(NSUInteger)channelID { + _channelID = channelID; +} + +@end diff --git a/src/objective-c/GRPCClient/internal/GRPCCallOptions+Internal.h b/src/objective-c/GRPCClient/internal/GRPCCallOptions+Internal.h new file mode 100644 index 0000000000..eb691b3acb --- /dev/null +++ b/src/objective-c/GRPCClient/internal/GRPCCallOptions+Internal.h @@ -0,0 +1,39 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 <Foundation/Foundation.h> + +#import "../GRPCCallOptions.h" + +@interface GRPCCallOptions () + +/** + * Parameter used for internal logging. + */ +@property(readonly) id logContext; + +@end + +@interface GRPCMutableCallOptions () + +/** + * Parameter used for internal logging. + */ +@property(readwrite) id logContext; + +@end diff --git a/src/objective-c/GRPCClient/private/ChannelArgsUtil.h b/src/objective-c/GRPCClient/private/ChannelArgsUtil.h new file mode 100644 index 0000000000..f271e846f0 --- /dev/null +++ b/src/objective-c/GRPCClient/private/ChannelArgsUtil.h @@ -0,0 +1,38 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 <Foundation/Foundation.h> + +#include <grpc/impl/codegen/grpc_types.h> + +/** Free resources in the grpc core struct grpc_channel_args */ +void GRPCFreeChannelArgs(grpc_channel_args* channel_args); + +/** + * Allocates a @c grpc_channel_args and populates it with the options specified + * in the + * @c dictionary. Keys must be @c NSString, @c NSNumber, or a pointer. If the + * value responds to + * @c @selector(UTF8String) then it will be mapped to @c GRPC_ARG_STRING. If the + * value responds to + * @c @selector(intValue), it will be mapped to @c GRPC_ARG_INTEGER. Otherwise, + * if the value is not nil, it is mapped as a pointer. The caller of this + * function is responsible for calling + * @c GRPCFreeChannelArgs to free the @c grpc_channel_args struct. + */ +grpc_channel_args* GRPCBuildChannelArgs(NSDictionary* dictionary); diff --git a/src/objective-c/GRPCClient/private/ChannelArgsUtil.m b/src/objective-c/GRPCClient/private/ChannelArgsUtil.m new file mode 100644 index 0000000000..c1c65c3384 --- /dev/null +++ b/src/objective-c/GRPCClient/private/ChannelArgsUtil.m @@ -0,0 +1,94 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 "ChannelArgsUtil.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/string_util.h> + +#include <limits.h> + +static void *copy_pointer_arg(void *p) { + // Add ref count to the object when making copy + id obj = (__bridge id)p; + return (__bridge_retained void *)obj; +} + +static void destroy_pointer_arg(void *p) { + // Decrease ref count to the object when destroying + CFRelease((CFTypeRef)p); +} + +static int cmp_pointer_arg(void *p, void *q) { return p == q; } + +static const grpc_arg_pointer_vtable objc_arg_vtable = {copy_pointer_arg, destroy_pointer_arg, + cmp_pointer_arg}; + +void GRPCFreeChannelArgs(grpc_channel_args *channel_args) { + for (size_t i = 0; i < channel_args->num_args; ++i) { + grpc_arg *arg = &channel_args->args[i]; + gpr_free(arg->key); + if (arg->type == GRPC_ARG_STRING) { + gpr_free(arg->value.string); + } + } + gpr_free(channel_args->args); + gpr_free(channel_args); +} + +grpc_channel_args *GRPCBuildChannelArgs(NSDictionary *dictionary) { + if (dictionary.count == 0) { + return NULL; + } + + NSArray *keys = [dictionary allKeys]; + NSUInteger argCount = [keys count]; + + grpc_channel_args *channelArgs = gpr_malloc(sizeof(grpc_channel_args)); + channelArgs->args = gpr_malloc(argCount * sizeof(grpc_arg)); + + // TODO(kriswuollett) Check that keys adhere to GRPC core library requirements + + NSUInteger j = 0; + for (NSUInteger i = 0; i < argCount; ++i) { + grpc_arg *arg = &channelArgs->args[j]; + arg->key = gpr_strdup([keys[i] UTF8String]); + + id value = dictionary[keys[i]]; + if ([value respondsToSelector:@selector(UTF8String)]) { + arg->type = GRPC_ARG_STRING; + arg->value.string = gpr_strdup([value UTF8String]); + j++; + } else if ([value respondsToSelector:@selector(intValue)]) { + int64_t value64 = [value longLongValue]; + if (value64 <= INT_MAX || value64 >= INT_MIN) { + arg->type = GRPC_ARG_INTEGER; + arg->value.integer = (int)value64; + j++; + } + } else if (value != nil) { + arg->type = GRPC_ARG_POINTER; + arg->value.pointer.p = (__bridge_retained void *)value; + arg->value.pointer.vtable = &objc_arg_vtable; + j++; + } + } + channelArgs->num_args = j; + + return channelArgs; +} diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.h b/src/objective-c/GRPCClient/private/GRPCChannel.h index 6499d4398c..bbada0d8cb 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannel.h +++ b/src/objective-c/GRPCClient/private/GRPCChannel.h @@ -20,49 +20,68 @@ #include <grpc/grpc.h> +@protocol GRPCChannelFactory; + @class GRPCCompletionQueue; +@class GRPCCallOptions; +@class GRPCChannelConfiguration; struct grpc_channel_credentials; +NS_ASSUME_NONNULL_BEGIN + /** - * Each separate instance of this class represents at least one TCP connection to the provided host. + * Signature for the channel. If two channel's signatures are the same and connect to the same + * remote, they share the same underlying \a GRPCChannel object. */ -@interface GRPCChannel : NSObject +@interface GRPCChannelConfiguration : NSObject<NSCopying> -@property(nonatomic, readonly, nonnull) struct grpc_channel *unmanagedChannel; +- (instancetype)init NS_UNAVAILABLE; -- (nullable instancetype)init NS_UNAVAILABLE; ++ (instancetype) new NS_UNAVAILABLE; + +/** The host that this channel is connected to. */ +@property(copy, readonly) NSString *host; /** - * Creates a secure channel to the specified @c host using default credentials and channel - * arguments. If certificates could not be found to create a secure channel, then @c nil is - * returned. + * Options of the corresponding call. Note that only the channel-related options are of interest to + * this class. */ -+ (nullable GRPCChannel *)secureChannelWithHost:(nonnull NSString *)host; +@property(readonly) GRPCCallOptions *callOptions; + +/** Acquire the factory to generate a new channel with current configurations. */ +@property(readonly) id<GRPCChannelFactory> channelFactory; + +/** Acquire the dictionary of channel args with current configurations. */ +@property(copy, readonly) NSDictionary *channelArgs; + +- (nullable instancetype)initWithHost:(NSString *)host + callOptions:(GRPCCallOptions *)callOptions NS_DESIGNATED_INITIALIZER; + +@end /** - * Creates a secure channel to the specified @c host using Cronet as a transport mechanism. + * Each separate instance of this class represents at least one TCP connection to the provided host. */ -#ifdef GRPC_COMPILE_WITH_CRONET -+ (nullable GRPCChannel *)secureCronetChannelWithHost:(nonnull NSString *)host - channelArgs:(nonnull NSDictionary *)channelArgs; -#endif +@interface GRPCChannel : NSObject + +- (nullable instancetype)init NS_UNAVAILABLE; + ++ (nullable instancetype) new NS_UNAVAILABLE; + /** - * Creates a secure channel to the specified @c host using the specified @c credentials and - * @c channelArgs. Only in tests should @c GRPC_SSL_TARGET_NAME_OVERRIDE_ARG channel arg be set. + * Create a channel with remote \a host and signature \a channelConfigurations. */ -+ (nonnull GRPCChannel *)secureChannelWithHost:(nonnull NSString *)host - credentials: - (nonnull struct grpc_channel_credentials *)credentials - channelArgs:(nullable NSDictionary *)channelArgs; +- (nullable instancetype)initWithChannelConfiguration: + (GRPCChannelConfiguration *)channelConfiguration NS_DESIGNATED_INITIALIZER; /** - * Creates an insecure channel to the specified @c host using the specified @c channelArgs. + * Create a grpc core call object (grpc_call) from this channel. If no call is created, NULL is + * returned. */ -+ (nonnull GRPCChannel *)insecureChannelWithHost:(nonnull NSString *)host - channelArgs:(nullable NSDictionary *)channelArgs; +- (nullable grpc_call *)unmanagedCallWithPath:(NSString *)path + completionQueue:(GRPCCompletionQueue *)queue + callOptions:(GRPCCallOptions *)callOptions; -- (nullable grpc_call *)unmanagedCallWithPath:(nonnull NSString *)path - serverName:(nonnull NSString *)serverName - timeout:(NSTimeInterval)timeout - completionQueue:(nonnull GRPCCompletionQueue *)queue; @end + +NS_ASSUME_NONNULL_END diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.m b/src/objective-c/GRPCClient/private/GRPCChannel.m index b1f6ea270e..1a79fb04a0 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannel.m +++ b/src/objective-c/GRPCClient/private/GRPCChannel.m @@ -18,206 +18,243 @@ #import "GRPCChannel.h" -#include <grpc/grpc_security.h> -#ifdef GRPC_COMPILE_WITH_CRONET -#include <grpc/grpc_cronet.h> -#endif -#include <grpc/support/alloc.h> #include <grpc/support/log.h> -#include <grpc/support/string_util.h> -#ifdef GRPC_COMPILE_WITH_CRONET -#import <Cronet/Cronet.h> -#import <GRPCClient/GRPCCall+Cronet.h> -#endif +#import "../internal/GRPCCallOptions+Internal.h" +#import "ChannelArgsUtil.h" +#import "GRPCChannelFactory.h" +#import "GRPCChannelPool.h" #import "GRPCCompletionQueue.h" +#import "GRPCCronetChannelFactory.h" +#import "GRPCInsecureChannelFactory.h" +#import "GRPCSecureChannelFactory.h" +#import "version.h" -static void *copy_pointer_arg(void *p) { - // Add ref count to the object when making copy - id obj = (__bridge id)p; - return (__bridge_retained void *)obj; -} +#import <GRPCClient/GRPCCall+Cronet.h> +#import <GRPCClient/GRPCCallOptions.h> -static void destroy_pointer_arg(void *p) { - // Decrease ref count to the object when destroying - CFRelease((CFTreeRef)p); -} +@implementation GRPCChannelConfiguration -static int cmp_pointer_arg(void *p, void *q) { return p == q; } +- (instancetype)initWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions { + NSAssert(host.length > 0, @"Host must not be empty."); + NSAssert(callOptions != nil, @"callOptions must not be empty."); + if (host.length == 0 || callOptions == nil) { + return nil; + } -static const grpc_arg_pointer_vtable objc_arg_vtable = {copy_pointer_arg, destroy_pointer_arg, - cmp_pointer_arg}; + if ((self = [super init])) { + _host = [host copy]; + _callOptions = [callOptions copy]; + } + return self; +} -static void FreeChannelArgs(grpc_channel_args *channel_args) { - for (size_t i = 0; i < channel_args->num_args; ++i) { - grpc_arg *arg = &channel_args->args[i]; - gpr_free(arg->key); - if (arg->type == GRPC_ARG_STRING) { - gpr_free(arg->value.string); - } +- (id<GRPCChannelFactory>)channelFactory { + GRPCTransportType type = _callOptions.transportType; + switch (type) { + case GRPCTransportTypeChttp2BoringSSL: + // TODO (mxyan): Remove when the API is deprecated +#ifdef GRPC_COMPILE_WITH_CRONET + if (![GRPCCall isUsingCronet]) { +#else + { +#endif + NSError *error; + id<GRPCChannelFactory> factory = [GRPCSecureChannelFactory + factoryWithPEMRootCertificates:_callOptions.PEMRootCertificates + privateKey:_callOptions.PEMPrivateKey + certChain:_callOptions.PEMCertificateChain + error:&error]; + NSAssert(factory != nil, @"Failed to create secure channel factory"); + if (factory == nil) { + NSLog(@"Error creating secure channel factory: %@", error); + } + return factory; + } + // fallthrough + case GRPCTransportTypeCronet: + return [GRPCCronetChannelFactory sharedInstance]; + case GRPCTransportTypeInsecure: + return [GRPCInsecureChannelFactory sharedInstance]; } - gpr_free(channel_args->args); - gpr_free(channel_args); } -/** - * Allocates a @c grpc_channel_args and populates it with the options specified in the - * @c dictionary. Keys must be @c NSString. If the value responds to @c @selector(UTF8String) then - * it will be mapped to @c GRPC_ARG_STRING. If not, it will be mapped to @c GRPC_ARG_INTEGER if the - * value responds to @c @selector(intValue). Otherwise, an exception will be raised. The caller of - * this function is responsible for calling @c freeChannelArgs on a non-NULL returned value. - */ -static grpc_channel_args *BuildChannelArgs(NSDictionary *dictionary) { - if (!dictionary) { - return NULL; - } - - NSArray *keys = [dictionary allKeys]; - NSUInteger argCount = [keys count]; - - grpc_channel_args *channelArgs = gpr_malloc(sizeof(grpc_channel_args)); - channelArgs->num_args = argCount; - channelArgs->args = gpr_malloc(argCount * sizeof(grpc_arg)); - - // TODO(kriswuollett) Check that keys adhere to GRPC core library requirements - - for (NSUInteger i = 0; i < argCount; ++i) { - grpc_arg *arg = &channelArgs->args[i]; - arg->key = gpr_strdup([keys[i] UTF8String]); - - id value = dictionary[keys[i]]; - if ([value respondsToSelector:@selector(UTF8String)]) { - arg->type = GRPC_ARG_STRING; - arg->value.string = gpr_strdup([value UTF8String]); - } else if ([value respondsToSelector:@selector(intValue)]) { - arg->type = GRPC_ARG_INTEGER; - arg->value.integer = [value intValue]; - } else if (value != nil) { - arg->type = GRPC_ARG_POINTER; - arg->value.pointer.p = (__bridge_retained void *)value; - arg->value.pointer.vtable = &objc_arg_vtable; - } else { - [NSException raise:NSInvalidArgumentException - format:@"Invalid value type: %@", [value class]]; - } +- (NSDictionary *)channelArgs { + NSMutableDictionary *args = [NSMutableDictionary new]; + + NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING; + NSString *userAgentPrefix = _callOptions.userAgentPrefix; + if (userAgentPrefix.length != 0) { + args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = + [_callOptions.userAgentPrefix stringByAppendingFormat:@" %@", userAgent]; + } else { + args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent; } - return channelArgs; -} + NSString *hostNameOverride = _callOptions.hostNameOverride; + if (hostNameOverride) { + args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = hostNameOverride; + } -@implementation GRPCChannel { - // Retain arguments to channel_create because they may not be used on the thread that invoked - // the channel_create function. - NSString *_host; - grpc_channel_args *_channelArgs; -} + if (_callOptions.responseSizeLimit) { + args[@GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH] = + [NSNumber numberWithUnsignedInteger:_callOptions.responseSizeLimit]; + } -#ifdef GRPC_COMPILE_WITH_CRONET -- (instancetype)initWithHost:(NSString *)host - cronetEngine:(stream_engine *)cronetEngine - channelArgs:(NSDictionary *)channelArgs { - if (!host) { - [NSException raise:NSInvalidArgumentException format:@"host argument missing"]; + if (_callOptions.compressionAlgorithm != GRPC_COMPRESS_NONE) { + args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] = + [NSNumber numberWithInt:_callOptions.compressionAlgorithm]; } - if (self = [super init]) { - _channelArgs = BuildChannelArgs(channelArgs); - _host = [host copy]; - _unmanagedChannel = - grpc_cronet_secure_channel_create(cronetEngine, _host.UTF8String, _channelArgs, NULL); + if (_callOptions.keepaliveInterval != 0) { + args[@GRPC_ARG_KEEPALIVE_TIME_MS] = + [NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.keepaliveInterval * 1000)]; + args[@GRPC_ARG_KEEPALIVE_TIMEOUT_MS] = + [NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.keepaliveTimeout * 1000)]; } - return self; -} -#endif + if (!_callOptions.retryEnabled) { + args[@GRPC_ARG_ENABLE_RETRIES] = [NSNumber numberWithInt:_callOptions.retryEnabled ? 1 : 0]; + } -- (instancetype)initWithHost:(NSString *)host - secure:(BOOL)secure - credentials:(struct grpc_channel_credentials *)credentials - channelArgs:(NSDictionary *)channelArgs { - if (!host) { - [NSException raise:NSInvalidArgumentException format:@"host argument missing"]; + if (_callOptions.connectMinTimeout > 0) { + args[@GRPC_ARG_MIN_RECONNECT_BACKOFF_MS] = + [NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectMinTimeout * 1000)]; + } + if (_callOptions.connectInitialBackoff > 0) { + args[@GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS] = [NSNumber + numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectInitialBackoff * 1000)]; + } + if (_callOptions.connectMaxBackoff > 0) { + args[@GRPC_ARG_MAX_RECONNECT_BACKOFF_MS] = + [NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectMaxBackoff * 1000)]; } - if (secure && !credentials) { - return nil; + if (_callOptions.logContext != nil) { + args[@GRPC_ARG_MOBILE_LOG_CONTEXT] = _callOptions.logContext; } - if (self = [super init]) { - _channelArgs = BuildChannelArgs(channelArgs); - _host = [host copy]; - if (secure) { - _unmanagedChannel = - grpc_secure_channel_create(credentials, _host.UTF8String, _channelArgs, NULL); - } else { - _unmanagedChannel = grpc_insecure_channel_create(_host.UTF8String, _channelArgs, NULL); - } + if (_callOptions.channelPoolDomain.length != 0) { + args[@GRPC_ARG_CHANNEL_POOL_DOMAIN] = _callOptions.channelPoolDomain; } - return self; + [args addEntriesFromDictionary:_callOptions.additionalChannelArgs]; + + return args; } -- (void)dealloc { - // TODO(jcanizales): Be sure to add a test with a server that closes the connection prematurely, - // as in the past that made this call to crash. - grpc_channel_destroy(_unmanagedChannel); - FreeChannelArgs(_channelArgs); +- (id)copyWithZone:(NSZone *)zone { + GRPCChannelConfiguration *newConfig = + [[GRPCChannelConfiguration alloc] initWithHost:_host callOptions:_callOptions]; + + return newConfig; } -#ifdef GRPC_COMPILE_WITH_CRONET -+ (GRPCChannel *)secureCronetChannelWithHost:(NSString *)host - channelArgs:(NSDictionary *)channelArgs { - stream_engine *engine = [GRPCCall cronetEngine]; - if (!engine) { - [NSException raise:NSInvalidArgumentException format:@"cronet_engine is NULL. Set it first."]; - return nil; +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[GRPCChannelConfiguration class]]) { + return NO; } - return [[GRPCChannel alloc] initWithHost:host cronetEngine:engine channelArgs:channelArgs]; -} -#endif + GRPCChannelConfiguration *obj = (GRPCChannelConfiguration *)object; + if (!(obj.host == _host || (_host != nil && [obj.host isEqualToString:_host]))) return NO; + if (!(obj.callOptions == _callOptions || [obj.callOptions hasChannelOptionsEqualTo:_callOptions])) + return NO; -+ (GRPCChannel *)secureChannelWithHost:(NSString *)host { - return [[GRPCChannel alloc] initWithHost:host secure:YES credentials:NULL channelArgs:NULL]; + return YES; } -+ (GRPCChannel *)secureChannelWithHost:(NSString *)host - credentials:(struct grpc_channel_credentials *)credentials - channelArgs:(NSDictionary *)channelArgs { - return [[GRPCChannel alloc] initWithHost:host - secure:YES - credentials:credentials - channelArgs:channelArgs]; +- (NSUInteger)hash { + NSUInteger result = 31; + result ^= _host.hash; + result ^= _callOptions.channelOptionsHash; + + return result; } -+ (GRPCChannel *)insecureChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)channelArgs { - return [[GRPCChannel alloc] initWithHost:host secure:NO credentials:NULL channelArgs:channelArgs]; +@end + +@implementation GRPCChannel { + GRPCChannelConfiguration *_configuration; + + grpc_channel *_unmanagedChannel; } -- (grpc_call *)unmanagedCallWithPath:(NSString *)path - serverName:(NSString *)serverName - timeout:(NSTimeInterval)timeout - completionQueue:(GRPCCompletionQueue *)queue { - GPR_ASSERT(timeout >= 0); - if (timeout < 0) { - timeout = 0; +- (instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration { + NSAssert(channelConfiguration != nil, @"channelConfiguration must not be empty."); + if (channelConfiguration == nil) { + return nil; } - grpc_slice host_slice = grpc_empty_slice(); - if (serverName) { - host_slice = grpc_slice_from_copied_string(serverName.UTF8String); + + if ((self = [super init])) { + _configuration = [channelConfiguration copy]; + + // Create gRPC core channel object. + NSString *host = channelConfiguration.host; + NSAssert(host.length != 0, @"host cannot be nil"); + NSDictionary *channelArgs; + if (channelConfiguration.callOptions.additionalChannelArgs.count != 0) { + NSMutableDictionary *args = [channelConfiguration.channelArgs mutableCopy]; + [args addEntriesFromDictionary:channelConfiguration.callOptions.additionalChannelArgs]; + channelArgs = args; + } else { + channelArgs = channelConfiguration.channelArgs; + } + id<GRPCChannelFactory> factory = channelConfiguration.channelFactory; + _unmanagedChannel = [factory createChannelWithHost:host channelArgs:channelArgs]; + NSAssert(_unmanagedChannel != NULL, @"Failed to create channel"); + if (_unmanagedChannel == NULL) { + NSLog(@"Unable to create channel."); + return nil; + } } + return self; +} + +- (grpc_call *)unmanagedCallWithPath:(NSString *)path + completionQueue:(GRPCCompletionQueue *)queue + callOptions:(GRPCCallOptions *)callOptions { + NSAssert(path.length > 0, @"path must not be empty."); + NSAssert(queue != nil, @"completionQueue must not be empty."); + NSAssert(callOptions != nil, @"callOptions must not be empty."); + if (path.length == 0) return NULL; + if (queue == nil) return NULL; + if (callOptions == nil) return NULL; + + grpc_call *call = NULL; + // No need to lock here since _unmanagedChannel is only changed in _dealloc + NSAssert(_unmanagedChannel != NULL, @"Channel should have valid unmanaged channel."); + if (_unmanagedChannel == NULL) return NULL; + + NSString *serverAuthority = + callOptions.transportType == GRPCTransportTypeCronet ? nil : callOptions.serverAuthority; + NSTimeInterval timeout = callOptions.timeout; + NSAssert(timeout >= 0, @"Invalid timeout"); + if (timeout < 0) return NULL; + grpc_slice host_slice = serverAuthority + ? grpc_slice_from_copied_string(serverAuthority.UTF8String) + : grpc_empty_slice(); grpc_slice path_slice = grpc_slice_from_copied_string(path.UTF8String); gpr_timespec deadline_ms = timeout == 0 ? gpr_inf_future(GPR_CLOCK_REALTIME) : gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), gpr_time_from_millis((int64_t)(timeout * 1000), GPR_TIMESPAN)); - grpc_call *call = grpc_channel_create_call(_unmanagedChannel, NULL, GRPC_PROPAGATE_DEFAULTS, - queue.unmanagedQueue, path_slice, - serverName ? &host_slice : NULL, deadline_ms, NULL); - if (serverName) { + call = grpc_channel_create_call(_unmanagedChannel, NULL, GRPC_PROPAGATE_DEFAULTS, + queue.unmanagedQueue, path_slice, + serverAuthority ? &host_slice : NULL, deadline_ms, NULL); + if (serverAuthority) { grpc_slice_unref(host_slice); } grpc_slice_unref(path_slice); + NSAssert(call != nil, @"Unable to create call."); + if (call == NULL) { + NSLog(@"Unable to create call."); + } return call; } +- (void)dealloc { + if (_unmanagedChannel) { + grpc_channel_destroy(_unmanagedChannel); + } +} + @end diff --git a/src/objective-c/GRPCClient/private/GRPCChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCChannelFactory.h new file mode 100644 index 0000000000..a934e966e9 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCChannelFactory.h @@ -0,0 +1,34 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 <Foundation/Foundation.h> + +#include <grpc/impl/codegen/grpc_types.h> + +NS_ASSUME_NONNULL_BEGIN + +/** A factory interface which generates new channel. */ +@protocol GRPCChannelFactory + +/** Create a channel with specific channel args to a specific host. */ +- (nullable grpc_channel *)createChannelWithHost:(NSString *)host + channelArgs:(nullable NSDictionary *)args; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/objective-c/GRPCClient/private/GRPCChannelPool+Test.h b/src/objective-c/GRPCClient/private/GRPCChannelPool+Test.h new file mode 100644 index 0000000000..e2c3aee3d9 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCChannelPool+Test.h @@ -0,0 +1,51 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 "GRPCChannelPool.h" + +NS_ASSUME_NONNULL_BEGIN + +/** Test-only interface for \a GRPCPooledChannel. */ +@interface GRPCPooledChannel (Test) + +/** + * Initialize a pooled channel with non-default destroy delay for testing purpose. + */ +- (nullable instancetype)initWithChannelConfiguration: + (GRPCChannelConfiguration *)channelConfiguration + destroyDelay:(NSTimeInterval)destroyDelay; + +/** + * Return the pointer to the raw channel wrapped. + */ +@property(atomic, readonly, nullable) GRPCChannel *wrappedChannel; + +@end + +/** Test-only interface for \a GRPCChannelPool. */ +@interface GRPCChannelPool (Test) + +/** + * Get an instance of pool isolated from the global shared pool with channels' destroy delay being + * \a destroyDelay. + */ +- (nullable instancetype)initTestPool; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/objective-c/GRPCClient/private/GRPCChannelPool.h b/src/objective-c/GRPCClient/private/GRPCChannelPool.h new file mode 100644 index 0000000000..e00ee69e63 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCChannelPool.h @@ -0,0 +1,101 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 <GRPCClient/GRPCCallOptions.h> + +#import "GRPCChannelFactory.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol GRPCChannel; +@class GRPCChannel; +@class GRPCChannelPool; +@class GRPCCompletionQueue; +@class GRPCChannelConfiguration; +@class GRPCWrappedCall; + +/** + * A proxied channel object that can be retained and used to create GRPCWrappedCall object + * regardless of the current connection status. If a connection is not established when a + * GRPCWrappedCall object is requested, it issues a connection/reconnection. This behavior is to + * follow that of gRPC core's channel object. + */ +@interface GRPCPooledChannel : NSObject + +- (nullable instancetype)init NS_UNAVAILABLE; + ++ (nullable instancetype) new NS_UNAVAILABLE; + +/** + * Initialize with an actual channel object \a channel and a reference to the channel pool. + */ +- (nullable instancetype)initWithChannelConfiguration: + (GRPCChannelConfiguration *)channelConfiguration; + +/** + * Create a GRPCWrappedCall object (grpc_call) from this channel. If channel is disconnected, get a + * new channel object from the channel pool. + */ +- (nullable GRPCWrappedCall *)wrappedCallWithPath:(NSString *)path + completionQueue:(GRPCCompletionQueue *)queue + callOptions:(GRPCCallOptions *)callOptions; + +/** + * Notify the pooled channel that a wrapped call object is no longer referenced and will be + * dealloc'ed. + */ +- (void)notifyWrappedCallDealloc:(GRPCWrappedCall *)wrappedCall; + +/** + * Force the channel to disconnect immediately. GRPCWrappedCall objects previously created with + * \a wrappedCallWithPath are failed if not already finished. Subsequent calls to + * unmanagedCallWithPath: will attempt to reconnect to the remote channel. + */ +- (void)disconnect; + +@end + +/** + * Manage the pool of connected channels. When a channel is no longer referenced by any call, + * destroy the channel after a certain period of time elapsed. + */ +@interface GRPCChannelPool : NSObject + +- (nullable instancetype)init NS_UNAVAILABLE; + ++ (nullable instancetype) new NS_UNAVAILABLE; + +/** + * Get the global channel pool. + */ ++ (nullable instancetype)sharedInstance; + +/** + * Return a channel with a particular configuration. The channel may be a cached channel. + */ +- (nullable GRPCPooledChannel *)channelWithHost:(NSString *)host + callOptions:(GRPCCallOptions *)callOptions; + +/** + * Disconnect all channels in this pool. + */ +- (void)disconnectAllChannels; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/objective-c/GRPCClient/private/GRPCChannelPool.m b/src/objective-c/GRPCClient/private/GRPCChannelPool.m new file mode 100644 index 0000000000..a323f0490c --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCChannelPool.m @@ -0,0 +1,276 @@ +/* + * + * Copyright 2015 gRPC authors. + * + * 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 <Foundation/Foundation.h> + +#import "../internal/GRPCCallOptions+Internal.h" +#import "GRPCChannel.h" +#import "GRPCChannelFactory.h" +#import "GRPCChannelPool+Test.h" +#import "GRPCChannelPool.h" +#import "GRPCCompletionQueue.h" +#import "GRPCConnectivityMonitor.h" +#import "GRPCCronetChannelFactory.h" +#import "GRPCInsecureChannelFactory.h" +#import "GRPCSecureChannelFactory.h" +#import "GRPCWrappedCall.h" +#import "version.h" + +#import <GRPCClient/GRPCCall+Cronet.h> +#include <grpc/support/log.h> + +extern const char *kCFStreamVarName; + +static GRPCChannelPool *gChannelPool; +static dispatch_once_t gInitChannelPool; + +/** When all calls of a channel are destroyed, destroy the channel after this much seconds. */ +static const NSTimeInterval kDefaultChannelDestroyDelay = 30; + +@implementation GRPCPooledChannel { + GRPCChannelConfiguration *_channelConfiguration; + NSTimeInterval _destroyDelay; + + NSHashTable<GRPCWrappedCall *> *_wrappedCalls; + GRPCChannel *_wrappedChannel; + NSDate *_lastTimedDestroy; + dispatch_queue_t _timerQueue; +} + +- (instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration { + return [self initWithChannelConfiguration:channelConfiguration + destroyDelay:kDefaultChannelDestroyDelay]; +} + +- (nullable instancetype)initWithChannelConfiguration: + (GRPCChannelConfiguration *)channelConfiguration + destroyDelay:(NSTimeInterval)destroyDelay { + NSAssert(channelConfiguration != nil, @"channelConfiguration cannot be empty."); + if (channelConfiguration == nil) { + return nil; + } + + if ((self = [super init])) { + _channelConfiguration = [channelConfiguration copy]; + _destroyDelay = destroyDelay; + _wrappedCalls = [NSHashTable weakObjectsHashTable]; + _wrappedChannel = nil; + _lastTimedDestroy = nil; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + if (@available(iOS 8.0, macOS 10.10, *)) { + _timerQueue = dispatch_queue_create(NULL, dispatch_queue_attr_make_with_qos_class( + DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0)); + } else { +#else + { +#endif + _timerQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + } + } + + return self; +} + +- (void)dealloc { + // Disconnect GRPCWrappedCall objects created but not yet removed + if (_wrappedCalls.allObjects.count != 0) { + for (GRPCWrappedCall *wrappedCall in _wrappedCalls.allObjects) { + [wrappedCall channelDisconnected]; + }; + } +} + +- (GRPCWrappedCall *)wrappedCallWithPath:(NSString *)path + completionQueue:(GRPCCompletionQueue *)queue + callOptions:(GRPCCallOptions *)callOptions { + NSAssert(path.length > 0, @"path must not be empty."); + NSAssert(queue != nil, @"completionQueue must not be empty."); + NSAssert(callOptions, @"callOptions must not be empty."); + if (path.length == 0 || queue == nil || callOptions == nil) { + return nil; + } + + GRPCWrappedCall *call = nil; + + @synchronized(self) { + if (_wrappedChannel == nil) { + _wrappedChannel = [[GRPCChannel alloc] initWithChannelConfiguration:_channelConfiguration]; + if (_wrappedChannel == nil) { + NSAssert(_wrappedChannel != nil, @"Unable to get a raw channel for proxy."); + return nil; + } + } + _lastTimedDestroy = nil; + + grpc_call *unmanagedCall = + [_wrappedChannel unmanagedCallWithPath:path + completionQueue:[GRPCCompletionQueue completionQueue] + callOptions:callOptions]; + if (unmanagedCall == NULL) { + NSAssert(unmanagedCall != NULL, @"Unable to create grpc_call object"); + return nil; + } + + call = [[GRPCWrappedCall alloc] initWithUnmanagedCall:unmanagedCall pooledChannel:self]; + if (call == nil) { + NSAssert(call != nil, @"Unable to create GRPCWrappedCall object"); + grpc_call_unref(unmanagedCall); + return nil; + } + + [_wrappedCalls addObject:call]; + } + return call; +} + +- (void)notifyWrappedCallDealloc:(GRPCWrappedCall *)wrappedCall { + NSAssert(wrappedCall != nil, @"wrappedCall cannot be empty."); + if (wrappedCall == nil) { + return; + } + @synchronized(self) { + // Detect if all objects weakly referenced in _wrappedCalls are (implicitly) removed. + // _wrappedCalls.count does not work here since the hash table may include deallocated weak + // references. _wrappedCalls.allObjects forces removal of those objects. + if (_wrappedCalls.allObjects.count == 0) { + // No more call has reference to this channel. We may start the timer for destroying the + // channel now. + NSDate *now = [NSDate date]; + NSAssert(now != nil, @"Unable to create NSDate object 'now'."); + _lastTimedDestroy = now; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)_destroyDelay * NSEC_PER_SEC), + _timerQueue, ^{ + @synchronized(self) { + // Check _lastTimedDestroy against now in case more calls are created (and + // maybe destroyed) after this dispatch_async. In that case the current + // dispatch_after block should be discarded; the channel should be + // destroyed in a later dispatch_after block. + if (now != nil && self->_lastTimedDestroy == now) { + self->_wrappedChannel = nil; + self->_lastTimedDestroy = nil; + } + } + }); + } + } +} + +- (void)disconnect { + NSArray<GRPCWrappedCall *> *copiedWrappedCalls = nil; + @synchronized(self) { + if (_wrappedChannel != nil) { + _wrappedChannel = nil; + copiedWrappedCalls = _wrappedCalls.allObjects; + [_wrappedCalls removeAllObjects]; + } + } + for (GRPCWrappedCall *wrappedCall in copiedWrappedCalls) { + [wrappedCall channelDisconnected]; + } +} + +- (GRPCChannel *)wrappedChannel { + GRPCChannel *channel = nil; + @synchronized(self) { + channel = _wrappedChannel; + } + return channel; +} + +@end + +@interface GRPCChannelPool () + +- (instancetype)initPrivate NS_DESIGNATED_INITIALIZER; + +@end + +@implementation GRPCChannelPool { + NSMutableDictionary<GRPCChannelConfiguration *, GRPCPooledChannel *> *_channelPool; +} + ++ (instancetype)sharedInstance { + dispatch_once(&gInitChannelPool, ^{ + gChannelPool = [[GRPCChannelPool alloc] initPrivate]; + NSAssert(gChannelPool != nil, @"Cannot initialize global channel pool."); + }); + return gChannelPool; +} + +- (instancetype)initPrivate { + if ((self = [super init])) { + _channelPool = [NSMutableDictionary dictionary]; + + // Connectivity monitor is not required for CFStream + char *enableCFStream = getenv(kCFStreamVarName); + if (enableCFStream == nil || enableCFStream[0] != '1') { + [GRPCConnectivityMonitor registerObserver:self selector:@selector(connectivityChange:)]; + } + } + return self; +} + +- (void)dealloc { + [GRPCConnectivityMonitor unregisterObserver:self]; +} + +- (GRPCPooledChannel *)channelWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions { + NSAssert(host.length > 0, @"Host must not be empty."); + NSAssert(callOptions != nil, @"callOptions must not be empty."); + if (host.length == 0 || callOptions == nil) { + return nil; + } + + GRPCPooledChannel *pooledChannel = nil; + GRPCChannelConfiguration *configuration = + [[GRPCChannelConfiguration alloc] initWithHost:host callOptions:callOptions]; + @synchronized(self) { + pooledChannel = _channelPool[configuration]; + if (pooledChannel == nil) { + pooledChannel = [[GRPCPooledChannel alloc] initWithChannelConfiguration:configuration]; + _channelPool[configuration] = pooledChannel; + } + } + return pooledChannel; +} + +- (void)disconnectAllChannels { + NSArray<GRPCPooledChannel *> *copiedPooledChannels; + @synchronized(self) { + copiedPooledChannels = _channelPool.allValues; + } + + // Disconnect pooled channels. + for (GRPCPooledChannel *pooledChannel in copiedPooledChannels) { + [pooledChannel disconnect]; + } +} + +- (void)connectivityChange:(NSNotification *)note { + [self disconnectAllChannels]; +} + +@end + +@implementation GRPCChannelPool (Test) + +- (instancetype)initTestPool { + return [self initPrivate]; +} + +@end diff --git a/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m index a36788b35a..bb8618ff33 100644 --- a/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m +++ b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m @@ -76,14 +76,14 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach } } -+ (void)registerObserver:(_Nonnull id)observer selector:(SEL)selector { ++ (void)registerObserver:(id)observer selector:(SEL)selector { [[NSNotificationCenter defaultCenter] addObserver:observer selector:selector name:kGRPCConnectivityNotification object:nil]; } -+ (void)unregisterObserver:(_Nonnull id)observer { ++ (void)unregisterObserver:(id)observer { [[NSNotificationCenter defaultCenter] removeObserver:observer]; } diff --git a/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h new file mode 100644 index 0000000000..738dfdb737 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h @@ -0,0 +1,36 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 "GRPCChannelFactory.h" + +@class GRPCChannel; +typedef struct stream_engine stream_engine; + +NS_ASSUME_NONNULL_BEGIN + +@interface GRPCCronetChannelFactory : NSObject<GRPCChannelFactory> + ++ (nullable instancetype)sharedInstance; + +- (nullable grpc_channel *)createChannelWithHost:(NSString *)host + channelArgs:(nullable NSDictionary *)args; + +- (nullable instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m new file mode 100644 index 0000000000..5bcb021dc4 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m @@ -0,0 +1,79 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 "GRPCCronetChannelFactory.h" + +#import "ChannelArgsUtil.h" +#import "GRPCChannel.h" + +#ifdef GRPC_COMPILE_WITH_CRONET + +#import <Cronet/Cronet.h> +#include <grpc/grpc_cronet.h> + +@implementation GRPCCronetChannelFactory { + stream_engine *_cronetEngine; +} + ++ (instancetype)sharedInstance { + static GRPCCronetChannelFactory *instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc] initWithEngine:[Cronet getGlobalEngine]]; + }); + return instance; +} + +- (instancetype)initWithEngine:(stream_engine *)engine { + NSAssert(engine != NULL, @"Cronet engine cannot be empty."); + if (!engine) { + return nil; + } + if ((self = [super init])) { + _cronetEngine = engine; + } + return self; +} + +- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args { + grpc_channel_args *channelArgs = GRPCBuildChannelArgs(args); + grpc_channel *unmanagedChannel = + grpc_cronet_secure_channel_create(_cronetEngine, host.UTF8String, channelArgs, NULL); + GRPCFreeChannelArgs(channelArgs); + return unmanagedChannel; +} + +@end + +#else + +@implementation GRPCCronetChannelFactory + ++ (instancetype)sharedInstance { + NSAssert(NO, @"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel."); + return nil; +} + +- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args { + NSAssert(NO, @"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel."); + return NULL; +} + +@end + +#endif diff --git a/src/objective-c/GRPCClient/private/GRPCHost.h b/src/objective-c/GRPCClient/private/GRPCHost.h index 291b07df37..ca3c52ea17 100644 --- a/src/objective-c/GRPCClient/private/GRPCHost.h +++ b/src/objective-c/GRPCClient/private/GRPCHost.h @@ -20,6 +20,10 @@ #import <grpc/impl/codegen/compression_types.h> +#import "GRPCChannelFactory.h" + +#import <GRPCClient/GRPCCallOptions.h> + NS_ASSUME_NONNULL_BEGIN @class GRPCCompletionQueue; @@ -28,12 +32,10 @@ struct grpc_channel_credentials; @interface GRPCHost : NSObject -+ (void)flushChannelCache; + (void)resetAllHostSettings; @property(nonatomic, readonly) NSString *address; @property(nonatomic, copy, nullable) NSString *userAgentPrefix; -@property(nonatomic, nullable) struct grpc_channel_credentials *channelCreds; @property(nonatomic) grpc_compression_algorithm compressAlgorithm; @property(nonatomic) int keepaliveInterval; @property(nonatomic) int keepaliveTimeout; @@ -44,14 +46,14 @@ struct grpc_channel_credentials; @property(nonatomic) unsigned int initialConnectBackoff; @property(nonatomic) unsigned int maxConnectBackoff; -/** The following properties should only be modified for testing: */ +@property(nonatomic) id<GRPCChannelFactory> channelFactory; -@property(nonatomic, getter=isSecure) BOOL secure; +/** The following properties should only be modified for testing: */ @property(nonatomic, copy, nullable) NSString *hostNameOverride; /** The default response size limit is 4MB. Set this to override that default. */ -@property(nonatomic, strong, nullable) NSNumber *responseSizeLimitOverride; +@property(nonatomic) NSUInteger responseSizeLimitOverride; - (nullable instancetype)init NS_UNAVAILABLE; /** Host objects initialized with the same address are the same. */ @@ -62,19 +64,10 @@ struct grpc_channel_credentials; withCertChain:(nullable NSString *)pemCertChain error:(NSError **)errorPtr; -/** Create a grpc_call object to the provided path on this host. */ -- (nullable struct grpc_call *)unmanagedCallWithPath:(NSString *)path - serverName:(NSString *)serverName - timeout:(NSTimeInterval)timeout - completionQueue:(GRPCCompletionQueue *)queue; - -// TODO: There's a race when a new RPC is coming through just as an existing one is getting -// notified that there's no connectivity. If connectivity comes back at that moment, the new RPC -// will have its channel destroyed by the other RPC, and will never get notified of a problem, so -// it'll hang (the C layer logs a timeout, with exponential back off). One solution could be to pass -// the GRPCChannel to the GRPCCall, renaming this as "disconnectChannel:channel", which would only -// act on that specific channel. -- (void)disconnect; +@property(atomic) GRPCTransportType transportType; + ++ (GRPCCallOptions *)callOptionsForHost:(NSString *)host; + @end NS_ASSUME_NONNULL_END diff --git a/src/objective-c/GRPCClient/private/GRPCHost.m b/src/objective-c/GRPCClient/private/GRPCHost.m index 85b95dee91..24348c3aed 100644 --- a/src/objective-c/GRPCClient/private/GRPCHost.m +++ b/src/objective-c/GRPCClient/private/GRPCHost.m @@ -18,46 +18,36 @@ #import "GRPCHost.h" +#import <GRPCClient/GRPCCall+Cronet.h> #import <GRPCClient/GRPCCall.h> +#import <GRPCClient/GRPCCallOptions.h> + #include <grpc/grpc.h> #include <grpc/grpc_security.h> -#ifdef GRPC_COMPILE_WITH_CRONET -#import <GRPCClient/GRPCCall+ChannelArg.h> -#import <GRPCClient/GRPCCall+Cronet.h> -#endif -#import "GRPCChannel.h" +#import "../internal/GRPCCallOptions+Internal.h" +#import "GRPCChannelFactory.h" #import "GRPCCompletionQueue.h" #import "GRPCConnectivityMonitor.h" +#import "GRPCCronetChannelFactory.h" +#import "GRPCSecureChannelFactory.h" #import "NSDictionary+GRPC.h" #import "version.h" NS_ASSUME_NONNULL_BEGIN -extern const char *kCFStreamVarName; - -static NSMutableDictionary *kHostCache; +static NSMutableDictionary *gHostCache; @implementation GRPCHost { - // TODO(mlumish): Investigate whether caching channels with strong links is a good idea. - GRPCChannel *_channel; + NSString *_PEMRootCertificates; + NSString *_PEMPrivateKey; + NSString *_PEMCertificateChain; } + (nullable instancetype)hostWithAddress:(NSString *)address { return [[self alloc] initWithAddress:address]; } -- (void)dealloc { - if (_channelCreds != nil) { - grpc_channel_credentials_release(_channelCreds); - } - // Connectivity monitor is not required for CFStream - char *enableCFStream = getenv(kCFStreamVarName); - if (enableCFStream == nil || enableCFStream[0] != '1') { - [GRPCConnectivityMonitor unregisterObserver:self]; - } -} - // Default initializer. - (nullable instancetype)initWithAddress:(NSString *)address { if (!address) { @@ -76,241 +66,89 @@ static NSMutableDictionary *kHostCache; // Look up the GRPCHost in the cache. static dispatch_once_t cacheInitialization; dispatch_once(&cacheInitialization, ^{ - kHostCache = [NSMutableDictionary dictionary]; + gHostCache = [NSMutableDictionary dictionary]; }); - @synchronized(kHostCache) { - GRPCHost *cachedHost = kHostCache[address]; + @synchronized(gHostCache) { + GRPCHost *cachedHost = gHostCache[address]; if (cachedHost) { return cachedHost; } if ((self = [super init])) { - _address = address; - _secure = YES; - kHostCache[address] = self; - _compressAlgorithm = GRPC_COMPRESS_NONE; + _address = [address copy]; _retryEnabled = YES; - } - - // Connectivity monitor is not required for CFStream - char *enableCFStream = getenv(kCFStreamVarName); - if (enableCFStream == nil || enableCFStream[0] != '1') { - [GRPCConnectivityMonitor registerObserver:self selector:@selector(connectivityChange:)]; + gHostCache[address] = self; } } return self; } -+ (void)flushChannelCache { - @synchronized(kHostCache) { - [kHostCache enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, GRPCHost *_Nonnull host, - BOOL *_Nonnull stop) { - [host disconnect]; - }]; - } -} - + (void)resetAllHostSettings { - @synchronized(kHostCache) { - kHostCache = [NSMutableDictionary dictionary]; - } -} - -- (nullable grpc_call *)unmanagedCallWithPath:(NSString *)path - serverName:(NSString *)serverName - timeout:(NSTimeInterval)timeout - completionQueue:(GRPCCompletionQueue *)queue { - // The __block attribute is to allow channel take refcount inside @synchronized block. Without - // this attribute, retain of channel object happens after objc_sync_exit in release builds, which - // may result in channel released before used. See grpc/#15033. - __block GRPCChannel *channel; - // This is racing -[GRPCHost disconnect]. - @synchronized(self) { - if (!_channel) { - _channel = [self newChannel]; - } - channel = _channel; + @synchronized(gHostCache) { + gHostCache = [NSMutableDictionary dictionary]; } - return [channel unmanagedCallWithPath:path - serverName:serverName - timeout:timeout - completionQueue:queue]; -} - -- (NSData *)nullTerminatedDataWithString:(NSString *)string { - // dataUsingEncoding: does not return a null-terminated string. - NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]; - NSMutableData *nullTerminated = [NSMutableData dataWithData:data]; - [nullTerminated appendBytes:"\0" length:1]; - return nullTerminated; } - (BOOL)setTLSPEMRootCerts:(nullable NSString *)pemRootCerts withPrivateKey:(nullable NSString *)pemPrivateKey withCertChain:(nullable NSString *)pemCertChain error:(NSError **)errorPtr { - static NSData *kDefaultRootsASCII; - static NSError *kDefaultRootsError; - static dispatch_once_t loading; - dispatch_once(&loading, ^{ - NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem - // Do not use NSBundle.mainBundle, as it's nil for tests of library projects. - NSBundle *bundle = [NSBundle bundleForClass:self.class]; - NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"]; - NSError *error; - // Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the - // issuer). Load them as UTF8 and produce an ASCII equivalent. - NSString *contentInUTF8 = - [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; - if (contentInUTF8 == nil) { - kDefaultRootsError = error; - return; - } - kDefaultRootsASCII = [self nullTerminatedDataWithString:contentInUTF8]; - }); - - NSData *rootsASCII; - if (pemRootCerts != nil) { - rootsASCII = [self nullTerminatedDataWithString:pemRootCerts]; - } else { - if (kDefaultRootsASCII == nil) { - if (errorPtr) { - *errorPtr = kDefaultRootsError; - } - NSAssert( - kDefaultRootsASCII, - @"Could not read gRPCCertificates.bundle/roots.pem. This file, " - "with the root certificates, is needed to establish secure (TLS) connections. " - "Because the file is distributed with the gRPC library, this error is usually a sign " - "that the library wasn't configured correctly for your project. Error: %@", - kDefaultRootsError); - return NO; - } - rootsASCII = kDefaultRootsASCII; - } - - grpc_channel_credentials *creds; - if (pemPrivateKey == nil && pemCertChain == nil) { - creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL); - } else { - assert(pemPrivateKey != nil && pemCertChain != nil); - grpc_ssl_pem_key_cert_pair key_cert_pair; - NSData *privateKeyASCII = [self nullTerminatedDataWithString:pemPrivateKey]; - NSData *certChainASCII = [self nullTerminatedDataWithString:pemCertChain]; - key_cert_pair.private_key = privateKeyASCII.bytes; - key_cert_pair.cert_chain = certChainASCII.bytes; - creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL, NULL); - } - - @synchronized(self) { - if (_channelCreds != nil) { - grpc_channel_credentials_release(_channelCreds); - } - _channelCreds = creds; - } - + _PEMRootCertificates = [pemRootCerts copy]; + _PEMPrivateKey = [pemPrivateKey copy]; + _PEMCertificateChain = [pemCertChain copy]; return YES; } -- (NSDictionary *)channelArgsUsingCronet:(BOOL)useCronet { - NSMutableDictionary *args = [NSMutableDictionary dictionary]; - - // TODO(jcanizales): Add OS and device information (see - // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents ). - NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING; - if (_userAgentPrefix) { - userAgent = [_userAgentPrefix stringByAppendingFormat:@" %@", userAgent]; - } - args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent; - - if (_secure && _hostNameOverride) { - args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = _hostNameOverride; - } - - if (_responseSizeLimitOverride != nil) { - args[@GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH] = _responseSizeLimitOverride; - } - - if (_compressAlgorithm != GRPC_COMPRESS_NONE) { - args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] = [NSNumber numberWithInt:_compressAlgorithm]; - } - - if (_keepaliveInterval != 0) { - args[@GRPC_ARG_KEEPALIVE_TIME_MS] = [NSNumber numberWithInt:_keepaliveInterval]; - args[@GRPC_ARG_KEEPALIVE_TIMEOUT_MS] = [NSNumber numberWithInt:_keepaliveTimeout]; - } - - id logContext = self.logContext; - if (logContext != nil) { - args[@GRPC_ARG_MOBILE_LOG_CONTEXT] = logContext; - } - - if (useCronet) { - args[@GRPC_ARG_DISABLE_CLIENT_AUTHORITY_FILTER] = [NSNumber numberWithInt:1]; - } - - if (_retryEnabled == NO) { - args[@GRPC_ARG_ENABLE_RETRIES] = [NSNumber numberWithInt:0]; - } - - if (_minConnectTimeout > 0) { - args[@GRPC_ARG_MIN_RECONNECT_BACKOFF_MS] = [NSNumber numberWithInt:_minConnectTimeout]; - } - if (_initialConnectBackoff > 0) { - args[@GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS] = [NSNumber numberWithInt:_initialConnectBackoff]; - } - if (_maxConnectBackoff > 0) { - args[@GRPC_ARG_MAX_RECONNECT_BACKOFF_MS] = [NSNumber numberWithInt:_maxConnectBackoff]; - } - - return args; -} - -- (GRPCChannel *)newChannel { - BOOL useCronet = NO; -#ifdef GRPC_COMPILE_WITH_CRONET - useCronet = [GRPCCall isUsingCronet]; -#endif - NSDictionary *args = [self channelArgsUsingCronet:useCronet]; - if (_secure) { - GRPCChannel *channel; - @synchronized(self) { - if (_channelCreds == nil) { - [self setTLSPEMRootCerts:nil withPrivateKey:nil withCertChain:nil error:nil]; - } +- (GRPCCallOptions *)callOptions { + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.userAgentPrefix = _userAgentPrefix; + options.responseSizeLimit = _responseSizeLimitOverride; + options.compressionAlgorithm = (GRPCCompressionAlgorithm)_compressAlgorithm; + options.retryEnabled = _retryEnabled; + options.keepaliveInterval = (NSTimeInterval)_keepaliveInterval / 1000; + options.keepaliveTimeout = (NSTimeInterval)_keepaliveTimeout / 1000; + options.connectMinTimeout = (NSTimeInterval)_minConnectTimeout / 1000; + options.connectInitialBackoff = (NSTimeInterval)_initialConnectBackoff / 1000; + options.connectMaxBackoff = (NSTimeInterval)_maxConnectBackoff / 1000; + options.PEMRootCertificates = _PEMRootCertificates; + options.PEMPrivateKey = _PEMPrivateKey; + options.PEMCertificateChain = _PEMCertificateChain; + options.hostNameOverride = _hostNameOverride; #ifdef GRPC_COMPILE_WITH_CRONET - if (useCronet) { - channel = [GRPCChannel secureCronetChannelWithHost:_address channelArgs:args]; - } else -#endif - { - channel = - [GRPCChannel secureChannelWithHost:_address credentials:_channelCreds channelArgs:args]; - } + // By old API logic, insecure channel precedes Cronet channel; Cronet channel preceeds default + // channel. + if ([GRPCCall isUsingCronet]) { + if (_transportType == GRPCTransportTypeInsecure) { + options.transportType = GRPCTransportTypeInsecure; + } else { + NSAssert(_transportType == GRPCTransportTypeDefault, @"Invalid transport type"); + options.transportType = GRPCTransportTypeCronet; } - return channel; - } else { - return [GRPCChannel insecureChannelWithHost:_address channelArgs:args]; + } else +#endif + { + options.transportType = _transportType; } -} + options.logContext = _logContext; -- (NSString *)hostName { - // TODO(jcanizales): Default to nil instead of _address when Issue #2635 is clarified. - return _hostNameOverride ?: _address; + return options; } -- (void)disconnect { - // This is racing -[GRPCHost unmanagedCallWithPath:completionQueue:]. - @synchronized(self) { - _channel = nil; ++ (GRPCCallOptions *)callOptionsForHost:(NSString *)host { + // TODO (mxyan): Remove when old API is deprecated + NSURL *hostURL = [NSURL URLWithString:[@"https://" stringByAppendingString:host]]; + if (hostURL.host && hostURL.port == nil) { + host = [hostURL.host stringByAppendingString:@":443"]; } -} -// Flushes the host cache when connectivity status changes or when connection switch between Wifi -// and Cellular data, so that a new call will use a new channel. Otherwise, a new call will still -// use the cached channel which is no longer available and will cause gRPC to hang. -- (void)connectivityChange:(NSNotification *)note { - [self disconnect]; + GRPCCallOptions *callOptions = nil; + @synchronized(gHostCache) { + callOptions = [gHostCache[host] callOptions]; + } + if (callOptions == nil) { + callOptions = [[GRPCCallOptions alloc] init]; + } + return callOptions; } @end diff --git a/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h new file mode 100644 index 0000000000..2d471aebed --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h @@ -0,0 +1,35 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 "GRPCChannelFactory.h" + +@class GRPCChannel; + +NS_ASSUME_NONNULL_BEGIN + +@interface GRPCInsecureChannelFactory : NSObject<GRPCChannelFactory> + ++ (nullable instancetype)sharedInstance; + +- (nullable grpc_channel *)createChannelWithHost:(NSString *)host + channelArgs:(nullable NSDictionary *)args; + +- (nullable instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m new file mode 100644 index 0000000000..8ad1e848f5 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m @@ -0,0 +1,43 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 "GRPCInsecureChannelFactory.h" + +#import "ChannelArgsUtil.h" +#import "GRPCChannel.h" + +@implementation GRPCInsecureChannelFactory + ++ (instancetype)sharedInstance { + static GRPCInsecureChannelFactory *instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc] init]; + }); + return instance; +} + +- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args { + grpc_channel_args *coreChannelArgs = GRPCBuildChannelArgs(args); + grpc_channel *unmanagedChannel = + grpc_insecure_channel_create(host.UTF8String, coreChannelArgs, NULL); + GRPCFreeChannelArgs(coreChannelArgs); + return unmanagedChannel; +} + +@end diff --git a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m b/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m index fa4f022ff0..5f117f0607 100644 --- a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m +++ b/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m @@ -36,7 +36,7 @@ static void CheckIsNonNilASCII(NSString *name, NSString *value) { // Precondition: key isn't nil. static void CheckKeyValuePairIsValid(NSString *key, id value) { if ([key hasSuffix:@"-bin"]) { - if (![value isKindOfClass:NSData.class]) { + if (![value isKindOfClass:[NSData class]]) { [NSException raise:NSInvalidArgumentException format: @"Expected NSData value for header %@ ending in \"-bin\", " @@ -44,7 +44,7 @@ static void CheckKeyValuePairIsValid(NSString *key, id value) { key, value]; } } else { - if (![value isKindOfClass:NSString.class]) { + if (![value isKindOfClass:[NSString class]]) { [NSException raise:NSInvalidArgumentException format: @"Expected NSString value for header %@ not ending in \"-bin\", " diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h new file mode 100644 index 0000000000..588239b706 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h @@ -0,0 +1,38 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 "GRPCChannelFactory.h" + +@class GRPCChannel; + +NS_ASSUME_NONNULL_BEGIN + +@interface GRPCSecureChannelFactory : NSObject<GRPCChannelFactory> + ++ (nullable instancetype)factoryWithPEMRootCertificates:(nullable NSString *)rootCerts + privateKey:(nullable NSString *)privateKey + certChain:(nullable NSString *)certChain + error:(NSError **)errorPtr; + +- (nullable grpc_channel *)createChannelWithHost:(NSString *)host + channelArgs:(nullable NSDictionary *)args; + +- (nullable instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m new file mode 100644 index 0000000000..9699889536 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m @@ -0,0 +1,135 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 "GRPCSecureChannelFactory.h" + +#include <grpc/grpc_security.h> + +#import "ChannelArgsUtil.h" +#import "GRPCChannel.h" + +@implementation GRPCSecureChannelFactory { + grpc_channel_credentials *_channelCreds; +} + ++ (instancetype)factoryWithPEMRootCertificates:(NSString *)rootCerts + privateKey:(NSString *)privateKey + certChain:(NSString *)certChain + error:(NSError **)errorPtr { + return [[self alloc] initWithPEMRootCerts:rootCerts + privateKey:privateKey + certChain:certChain + error:errorPtr]; +} + +- (NSData *)nullTerminatedDataWithString:(NSString *)string { + // dataUsingEncoding: does not return a null-terminated string. + NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]; + if (data == nil) { + return nil; + } + NSMutableData *nullTerminated = [NSMutableData dataWithData:data]; + [nullTerminated appendBytes:"\0" length:1]; + return nullTerminated; +} + +- (instancetype)initWithPEMRootCerts:(NSString *)rootCerts + privateKey:(NSString *)privateKey + certChain:(NSString *)certChain + error:(NSError **)errorPtr { + static NSData *defaultRootsASCII; + static NSError *defaultRootsError; + static dispatch_once_t loading; + dispatch_once(&loading, ^{ + NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem + // Do not use NSBundle.mainBundle, as it's nil for tests of library projects. + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"]; + NSError *error; + // Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the + // issuer). Load them as UTF8 and produce an ASCII equivalent. + NSString *contentInUTF8 = + [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; + if (contentInUTF8 == nil) { + defaultRootsError = error; + return; + } + defaultRootsASCII = [self nullTerminatedDataWithString:contentInUTF8]; + }); + + NSData *rootsASCII; + if (rootCerts != nil) { + rootsASCII = [self nullTerminatedDataWithString:rootCerts]; + } else { + if (defaultRootsASCII == nil) { + if (errorPtr) { + *errorPtr = defaultRootsError; + } + NSAssert( + defaultRootsASCII, NSObjectNotAvailableException, + @"Could not read gRPCCertificates.bundle/roots.pem. This file, " + "with the root certificates, is needed to establish secure (TLS) connections. " + "Because the file is distributed with the gRPC library, this error is usually a sign " + "that the library wasn't configured correctly for your project. Error: %@", + defaultRootsError); + return nil; + } + rootsASCII = defaultRootsASCII; + } + + grpc_channel_credentials *creds = NULL; + if (privateKey.length == 0 && certChain.length == 0) { + creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL); + } else { + grpc_ssl_pem_key_cert_pair key_cert_pair; + NSData *privateKeyASCII = [self nullTerminatedDataWithString:privateKey]; + NSData *certChainASCII = [self nullTerminatedDataWithString:certChain]; + key_cert_pair.private_key = privateKeyASCII.bytes; + key_cert_pair.cert_chain = certChainASCII.bytes; + if (key_cert_pair.private_key == NULL || key_cert_pair.cert_chain == NULL) { + creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL); + } else { + creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL, NULL); + } + } + + if ((self = [super init])) { + _channelCreds = creds; + } + return self; +} + +- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args { + NSAssert(host.length != 0, @"host cannot be empty"); + if (host.length == 0) { + return NULL; + } + grpc_channel_args *coreChannelArgs = GRPCBuildChannelArgs(args); + grpc_channel *unmanagedChannel = + grpc_secure_channel_create(_channelCreds, host.UTF8String, coreChannelArgs, NULL); + GRPCFreeChannelArgs(coreChannelArgs); + return unmanagedChannel; +} + +- (void)dealloc { + if (_channelCreds != NULL) { + grpc_channel_credentials_release(_channelCreds); + } +} + +@end diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h index f711850c2f..92bd1be257 100644 --- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h @@ -71,12 +71,16 @@ #pragma mark GRPCWrappedCall +@class GRPCPooledChannel; + @interface GRPCWrappedCall : NSObject -- (instancetype)initWithHost:(NSString *)host - serverName:(NSString *)serverName - path:(NSString *)path - timeout:(NSTimeInterval)timeout NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; + ++ (instancetype) new NS_UNAVAILABLE; + +- (instancetype)initWithUnmanagedCall:(grpc_call *)unmanagedCall + pooledChannel:(GRPCPooledChannel *)pooledChannel NS_DESIGNATED_INITIALIZER; - (void)startBatchWithOperations:(NSArray *)ops errorHandler:(void (^)(void))errorHandler; @@ -84,4 +88,6 @@ - (void)cancel; +- (void)channelDisconnected; + @end diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m index 7781d27ca4..7d7e77f6ba 100644 --- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m @@ -23,6 +23,8 @@ #include <grpc/grpc.h> #include <grpc/support/alloc.h> +#import "GRPCChannel.h" +#import "GRPCChannelPool.h" #import "GRPCCompletionQueue.h" #import "GRPCHost.h" #import "NSData+GRPC.h" @@ -234,35 +236,22 @@ #pragma mark GRPCWrappedCall @implementation GRPCWrappedCall { - GRPCCompletionQueue *_queue; + // pooledChannel holds weak reference to this object so this is ok + GRPCPooledChannel *_pooledChannel; grpc_call *_call; } -- (instancetype)init { - return [self initWithHost:nil serverName:nil path:nil timeout:0]; -} - -- (instancetype)initWithHost:(NSString *)host - serverName:(NSString *)serverName - path:(NSString *)path - timeout:(NSTimeInterval)timeout { - if (!path || !host) { - [NSException raise:NSInvalidArgumentException format:@"path and host cannot be nil."]; +- (instancetype)initWithUnmanagedCall:(grpc_call *)unmanagedCall + pooledChannel:(GRPCPooledChannel *)pooledChannel { + NSAssert(unmanagedCall != NULL, @"unmanagedCall cannot be empty."); + NSAssert(pooledChannel != nil, @"pooledChannel cannot be empty."); + if (unmanagedCall == NULL || pooledChannel == nil) { + return nil; } - if (self = [super init]) { - // Each completion queue consumes one thread. There's a trade to be made between creating and - // consuming too many threads and having contention of multiple calls in a single completion - // queue. Currently we use a singleton queue. - _queue = [GRPCCompletionQueue completionQueue]; - - _call = [[GRPCHost hostWithAddress:host] unmanagedCallWithPath:path - serverName:serverName - timeout:timeout - completionQueue:_queue]; - if (_call == NULL) { - return nil; - } + if ((self = [super init])) { + _call = unmanagedCall; + _pooledChannel = pooledChannel; } return self; } @@ -278,41 +267,70 @@ [GRPCOpBatchLog addOpBatchToLog:operations]; #endif - size_t nops = operations.count; - grpc_op *ops_array = gpr_malloc(nops * sizeof(grpc_op)); - size_t i = 0; - for (GRPCOperation *operation in operations) { - ops_array[i++] = operation.op; - } - grpc_call_error error = - grpc_call_start_batch(_call, ops_array, nops, (__bridge_retained void *)(^(bool success) { - if (!success) { - if (errorHandler) { - errorHandler(); - } else { - return; - } - } - for (GRPCOperation *operation in operations) { - [operation finish]; - } - }), - NULL); - gpr_free(ops_array); - - if (error != GRPC_CALL_OK) { - [NSException - raise:NSInternalInconsistencyException - format:@"A precondition for calling grpc_call_start_batch wasn't met. Error %i", error]; + @synchronized(self) { + if (_call != NULL) { + size_t nops = operations.count; + grpc_op *ops_array = gpr_malloc(nops * sizeof(grpc_op)); + size_t i = 0; + for (GRPCOperation *operation in operations) { + ops_array[i++] = operation.op; + } + grpc_call_error error = + grpc_call_start_batch(_call, ops_array, nops, (__bridge_retained void *)(^(bool success) { + if (!success) { + if (errorHandler) { + errorHandler(); + } else { + return; + } + } + for (GRPCOperation *operation in operations) { + [operation finish]; + } + }), + NULL); + gpr_free(ops_array); + + NSAssert(error == GRPC_CALL_OK, @"Error starting a batch of operations: %i", error); + // To avoid compiler complaint when NSAssert is disabled. + if (error != GRPC_CALL_OK) { + return; + } + } } } - (void)cancel { - grpc_call_cancel(_call, NULL); + @synchronized(self) { + if (_call != NULL) { + grpc_call_cancel(_call, NULL); + } + } +} + +- (void)channelDisconnected { + @synchronized(self) { + if (_call != NULL) { + // Unreference the call will lead to its cancellation in the core. Note that since + // this function is only called with a network state change, any existing GRPCCall object will + // also receive the same notification and cancel themselves with GRPCErrorCodeUnavailable, so + // the user gets GRPCErrorCodeUnavailable in this case. + grpc_call_unref(_call); + _call = NULL; + } + } } - (void)dealloc { - grpc_call_unref(_call); + @synchronized(self) { + if (_call != NULL) { + grpc_call_unref(_call); + _call = NULL; + } + } + // Explicitly converting weak reference _pooledChannel to strong. + __strong GRPCPooledChannel *channel = _pooledChannel; + [channel notifyWrappedCallDealloc:self]; } @end diff --git a/src/objective-c/GRPCClient/private/version.h b/src/objective-c/GRPCClient/private/version.h index 0be0e3c9a0..5e089fde31 100644 --- a/src/objective-c/GRPCClient/private/version.h +++ b/src/objective-c/GRPCClient/private/version.h @@ -22,4 +22,4 @@ // instead. This file can be regenerated from the template by running // `tools/buildgen/generate_projects.sh`. -#define GRPC_OBJC_VERSION_STRING @"1.18.0-dev" +#define GRPC_OBJC_VERSION_STRING @"1.19.0-dev" diff --git a/src/objective-c/ProtoRPC/ProtoRPC.h b/src/objective-c/ProtoRPC/ProtoRPC.h index 45d3526092..8ce3421cc1 100644 --- a/src/objective-c/ProtoRPC/ProtoRPC.h +++ b/src/objective-c/ProtoRPC/ProtoRPC.h @@ -21,6 +21,122 @@ #import "ProtoMethod.h" +NS_ASSUME_NONNULL_BEGIN + +@class GPBMessage; + +/** An object can implement this protocol to receive responses from server from a call. */ +@protocol GRPCProtoResponseHandler<NSObject> + +@required + +/** + * All the responses must be issued to a user-provided dispatch queue. This property specifies the + * dispatch queue to be used for issuing the notifications. + */ +@property(atomic, readonly) dispatch_queue_t dispatchQueue; + +@optional + +/** + * Issued when initial metadata is received from the server. + */ +- (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata; + +/** + * Issued when a message is received from the server. The message is the deserialized proto object. + */ +- (void)didReceiveProtoMessage:(nullable GPBMessage *)message; + +/** + * Issued when a call finished. If the call finished successfully, \a error is nil and \a + * trainingMetadata consists any trailing metadata received from the server. Otherwise, \a error + * is non-nil and contains the corresponding error information, including gRPC error codes and + * error descriptions. + */ +- (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata + error:(nullable NSError *)error; + +@end + +/** A unary-request RPC call with Protobuf. */ +@interface GRPCUnaryProtoCall : NSObject + +- (instancetype)init NS_UNAVAILABLE; + ++ (instancetype) new NS_UNAVAILABLE; + +/** + * Users should not use this initializer directly. Call objects will be created, initialized, and + * returned to users by methods of the generated service. + */ +- (nullable instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + message:(GPBMessage *)message + responseHandler:(id<GRPCProtoResponseHandler>)handler + callOptions:(nullable GRPCCallOptions *)callOptions + responseClass:(Class)responseClass NS_DESIGNATED_INITIALIZER; + +/** + * Start the call. This function must only be called once for each instance. + */ +- (void)start; + +/** + * Cancel the request of this call at best effort. It attempts to notify the server that the RPC + * should be cancelled, and issue didCloseWithTrailingMetadata:error: callback with error code + * CANCELED if no other error code has already been issued. + */ +- (void)cancel; + +@end + +/** A client-streaming RPC call with Protobuf. */ +@interface GRPCStreamingProtoCall : NSObject + +- (instancetype)init NS_UNAVAILABLE; + ++ (instancetype) new NS_UNAVAILABLE; + +/** + * Users should not use this initializer directly. Call objects will be created, initialized, and + * returned to users by methods of the generated service. + */ +- (nullable instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + responseHandler:(id<GRPCProtoResponseHandler>)handler + callOptions:(nullable GRPCCallOptions *)callOptions + responseClass:(Class)responseClass NS_DESIGNATED_INITIALIZER; + +/** + * Start the call. This function must only be called once for each instance. + */ +- (void)start; + +/** + * Cancel the request of this call at best effort. It attempts to notify the server that the RPC + * should be cancelled, and issue didCloseWithTrailingMetadata:error: callback with error code + * CANCELED if no other error code has already been issued. + */ +- (void)cancel; + +/** + * Send a message to the server. The message should be a Protobuf message which will be serialized + * internally. + */ +- (void)writeMessage:(GPBMessage *)message; + +/** + * Finish the RPC request and half-close the call. The server may still send messages and/or + * trailers to the client. + */ +- (void)finish; + +@end + +NS_ASSUME_NONNULL_END + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnullability-completeness" + __attribute__((deprecated("Please use GRPCProtoCall."))) @interface ProtoRPC : GRPCCall @@ -47,3 +163,5 @@ __attribute__((deprecated("Please use GRPCProtoCall."))) @interface ProtoRPC #pragma clang diagnostic pop @end + +#pragma clang diagnostic pop diff --git a/src/objective-c/ProtoRPC/ProtoRPC.m b/src/objective-c/ProtoRPC/ProtoRPC.m index 5dca971b08..0ab96a5ba2 100644 --- a/src/objective-c/ProtoRPC/ProtoRPC.m +++ b/src/objective-c/ProtoRPC/ProtoRPC.m @@ -23,9 +23,13 @@ #else #import <GPBProtocolBuffers.h> #endif +#import <GRPCClient/GRPCCall.h> #import <RxLibrary/GRXWriteable.h> #import <RxLibrary/GRXWriter+Transformations.h> +/** + * Generate an NSError object that represents a failure in parsing a proto class. + */ static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsingError) { NSDictionary *info = @{ NSLocalizedDescriptionKey : @"Unable to parse response from the server", @@ -41,6 +45,227 @@ static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsing return [NSError errorWithDomain:@"io.grpc" code:13 userInfo:info]; } +@implementation GRPCUnaryProtoCall { + GRPCStreamingProtoCall *_call; + GPBMessage *_message; +} + +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + message:(GPBMessage *)message + responseHandler:(id<GRPCProtoResponseHandler>)handler + callOptions:(GRPCCallOptions *)callOptions + responseClass:(Class)responseClass { + NSAssert(message != nil, @"message cannot be empty."); + NSAssert(responseClass != nil, @"responseClass cannot be empty."); + if (message == nil || responseClass == nil) { + return nil; + } + if ((self = [super init])) { + _call = [[GRPCStreamingProtoCall alloc] initWithRequestOptions:requestOptions + responseHandler:handler + callOptions:callOptions + responseClass:responseClass]; + _message = [message copy]; + } + return self; +} + +- (void)start { + [_call start]; + [_call writeMessage:_message]; + [_call finish]; +} + +- (void)cancel { + [_call cancel]; +} + +@end + +@interface GRPCStreamingProtoCall ()<GRPCResponseHandler> + +@end + +@implementation GRPCStreamingProtoCall { + GRPCRequestOptions *_requestOptions; + id<GRPCProtoResponseHandler> _handler; + GRPCCallOptions *_callOptions; + Class _responseClass; + + GRPCCall2 *_call; + dispatch_queue_t _dispatchQueue; +} + +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + responseHandler:(id<GRPCProtoResponseHandler>)handler + callOptions:(GRPCCallOptions *)callOptions + responseClass:(Class)responseClass { + NSAssert(requestOptions.host.length != 0 && requestOptions.path.length != 0 && + requestOptions.safety <= GRPCCallSafetyCacheableRequest, + @"Invalid callOptions."); + NSAssert(handler != nil, @"handler cannot be empty."); + if (requestOptions.host.length == 0 || requestOptions.path.length == 0 || + requestOptions.safety > GRPCCallSafetyCacheableRequest) { + return nil; + } + if (handler == nil) { + return nil; + } + + if ((self = [super init])) { + _requestOptions = [requestOptions copy]; + _handler = handler; + _callOptions = [callOptions copy]; + _responseClass = responseClass; + + // Set queue QoS only when iOS version is 8.0 or above and Xcode version is 9.0 or above +#if __IPHONE_OS_VERSION_MAX_ALLOWED < 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED < 101300 + if (@available(iOS 8.0, macOS 10.10, *)) { + _dispatchQueue = dispatch_queue_create( + NULL, + dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0)); + } else { +#else + { +#endif + _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + } + dispatch_set_target_queue(_dispatchQueue, handler.dispatchQueue); + + _call = [[GRPCCall2 alloc] initWithRequestOptions:_requestOptions + responseHandler:self + callOptions:_callOptions]; + } + return self; +} + +- (void)start { + GRPCCall2 *copiedCall; + @synchronized(self) { + copiedCall = _call; + } + [copiedCall start]; +} + +- (void)cancel { + GRPCCall2 *copiedCall; + @synchronized(self) { + copiedCall = _call; + _call = nil; + if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) { + dispatch_async(_dispatchQueue, ^{ + id<GRPCProtoResponseHandler> copiedHandler = nil; + @synchronized(self) { + copiedHandler = self->_handler; + self->_handler = nil; + } + [copiedHandler didCloseWithTrailingMetadata:nil + error:[NSError errorWithDomain:kGRPCErrorDomain + code:GRPCErrorCodeCancelled + userInfo:@{ + NSLocalizedDescriptionKey : + @"Canceled by app" + }]]; + }); + } else { + _handler = nil; + } + } + [copiedCall cancel]; +} + +- (void)writeMessage:(GPBMessage *)message { + NSAssert([message isKindOfClass:[GPBMessage class]], @"Parameter message must be a GPBMessage"); + if (![message isKindOfClass:[GPBMessage class]]) { + NSLog(@"Failed to send a message that is non-proto."); + return; + } + + GRPCCall2 *copiedCall; + @synchronized(self) { + copiedCall = _call; + } + [copiedCall writeData:[message data]]; +} + +- (void)finish { + GRPCCall2 *copiedCall; + @synchronized(self) { + copiedCall = _call; + _call = nil; + } + [copiedCall finish]; +} + +- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata { + @synchronized(self) { + if (initialMetadata != nil && + [_handler respondsToSelector:@selector(didReceiveInitialMetadata:)]) { + dispatch_async(_dispatchQueue, ^{ + id<GRPCProtoResponseHandler> copiedHandler = nil; + @synchronized(self) { + copiedHandler = self->_handler; + } + [copiedHandler didReceiveInitialMetadata:initialMetadata]; + }); + } + } +} + +- (void)didReceiveRawMessage:(NSData *)message { + if (message == nil) return; + + NSError *error = nil; + GPBMessage *parsed = [_responseClass parseFromData:message error:&error]; + @synchronized(self) { + if (parsed && [_handler respondsToSelector:@selector(didReceiveProtoMessage:)]) { + dispatch_async(_dispatchQueue, ^{ + id<GRPCProtoResponseHandler> copiedHandler = nil; + @synchronized(self) { + copiedHandler = self->_handler; + } + [copiedHandler didReceiveProtoMessage:parsed]; + }); + } else if (!parsed && + [_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) { + dispatch_async(_dispatchQueue, ^{ + id<GRPCProtoResponseHandler> copiedHandler = nil; + @synchronized(self) { + copiedHandler = self->_handler; + self->_handler = nil; + } + [copiedHandler + didCloseWithTrailingMetadata:nil + error:ErrorForBadProto(message, self->_responseClass, error)]; + }); + [_call cancel]; + _call = nil; + } + } +} + +- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error { + @synchronized(self) { + if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) { + dispatch_async(_dispatchQueue, ^{ + id<GRPCProtoResponseHandler> copiedHandler = nil; + @synchronized(self) { + copiedHandler = self->_handler; + self->_handler = nil; + } + [copiedHandler didCloseWithTrailingMetadata:trailingMetadata error:error]; + }); + } + _call = nil; + } +} + +- (dispatch_queue_t)dispatchQueue { + return _dispatchQueue; +} + +@end + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" @implementation ProtoRPC { @@ -72,7 +297,7 @@ static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsing } // A writer that serializes the proto messages to send. GRXWriter *bytesWriter = [requestsWriter map:^id(GPBMessage *proto) { - if (![proto isKindOfClass:GPBMessage.class]) { + if (![proto isKindOfClass:[GPBMessage class]]) { [NSException raise:NSInvalidArgumentException format:@"Request must be a proto message: %@", proto]; } diff --git a/src/objective-c/ProtoRPC/ProtoService.h b/src/objective-c/ProtoRPC/ProtoService.h index 29c4e9be36..900ec8d0e1 100644 --- a/src/objective-c/ProtoRPC/ProtoService.h +++ b/src/objective-c/ProtoRPC/ProtoService.h @@ -21,18 +21,47 @@ @class GRPCProtoCall; @protocol GRXWriteable; @class GRXWriter; +@class GRPCCallOptions; +@class GRPCProtoCall; +@class GRPCUnaryProtoCall; +@class GRPCStreamingProtoCall; +@protocol GRPCProtoResponseHandler; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnullability-completeness" __attribute__((deprecated("Please use GRPCProtoService."))) @interface ProtoService - : NSObject - - (instancetype)initWithHost : (NSString *)host packageName - : (NSString *)packageName serviceName : (NSString *)serviceName NS_DESIGNATED_INITIALIZER; + : NSObject + + - + (nullable instancetype)initWithHost : (nonnull NSString *)host packageName + : (nonnull NSString *)packageName serviceName : (nonnull NSString *)serviceName callOptions + : (nullable GRPCCallOptions *)callOptions NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithHost:(NSString *)host + packageName:(NSString *)packageName + serviceName:(NSString *)serviceName; - (GRPCProtoCall *)RPCToMethod:(NSString *)method requestsWriter:(GRXWriter *)requestsWriter responseClass:(Class)responseClass responsesWriteable:(id<GRXWriteable>)responsesWriteable; + +- (nullable GRPCUnaryProtoCall *)RPCToMethod:(nonnull NSString *)method + message:(nonnull id)message + responseHandler:(nonnull id<GRPCProtoResponseHandler>)handler + callOptions:(nullable GRPCCallOptions *)callOptions + responseClass:(nonnull Class)responseClass; + +- (nullable GRPCStreamingProtoCall *)RPCToMethod:(nonnull NSString *)method + responseHandler:(nonnull id<GRPCProtoResponseHandler>)handler + callOptions:(nullable GRPCCallOptions *)callOptions + responseClass:(nonnull Class)responseClass; + @end +#pragma clang diagnostic pop + /** * This subclass is empty now. Eventually we'll remove ProtoService class * to avoid potential naming conflict diff --git a/src/objective-c/ProtoRPC/ProtoService.m b/src/objective-c/ProtoRPC/ProtoService.m index 3e9cc5c0b2..80a1f2f226 100644 --- a/src/objective-c/ProtoRPC/ProtoService.m +++ b/src/objective-c/ProtoRPC/ProtoService.m @@ -18,6 +18,7 @@ #import "ProtoService.h" +#import <GRPCClient/GRPCCall.h> #import <RxLibrary/GRXWriteable.h> #import <RxLibrary/GRXWriter.h> @@ -31,6 +32,7 @@ NSString *_host; NSString *_packageName; NSString *_serviceName; + GRPCCallOptions *_callOptions; } - (instancetype)init { @@ -40,19 +42,41 @@ // Designated initializer - (instancetype)initWithHost:(NSString *)host packageName:(NSString *)packageName - serviceName:(NSString *)serviceName { - if (!host || !serviceName) { - [NSException raise:NSInvalidArgumentException - format:@"Neither host nor serviceName can be nil."]; + serviceName:(NSString *)serviceName + callOptions:(GRPCCallOptions *)callOptions { + NSAssert(host.length != 0 && packageName.length != 0 && serviceName.length != 0, + @"Invalid parameter."); + if (host.length == 0 || packageName.length == 0 || serviceName.length == 0) { + return nil; } if ((self = [super init])) { _host = [host copy]; _packageName = [packageName copy]; _serviceName = [serviceName copy]; + _callOptions = [callOptions copy]; } return self; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-designated-initializers" +// Do not call designated initializer here due to nullability incompatibility. This method is from +// old API and does not assert on nullability of the parameters. + +- (instancetype)initWithHost:(NSString *)host + packageName:(NSString *)packageName + serviceName:(NSString *)serviceName { + if ((self = [super init])) { + _host = [host copy]; + _packageName = [packageName copy]; + _serviceName = [serviceName copy]; + _callOptions = nil; + } + return self; +} + +#pragma clang diagnostic pop + - (GRPCProtoCall *)RPCToMethod:(NSString *)method requestsWriter:(GRXWriter *)requestsWriter responseClass:(Class)responseClass @@ -65,6 +89,41 @@ responseClass:responseClass responsesWriteable:responsesWriteable]; } + +- (GRPCUnaryProtoCall *)RPCToMethod:(NSString *)method + message:(id)message + responseHandler:(id<GRPCProtoResponseHandler>)handler + callOptions:(GRPCCallOptions *)callOptions + responseClass:(Class)responseClass { + GRPCProtoMethod *methodName = + [[GRPCProtoMethod alloc] initWithPackage:_packageName service:_serviceName method:method]; + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:_host + path:methodName.HTTPPath + safety:GRPCCallSafetyDefault]; + return [[GRPCUnaryProtoCall alloc] initWithRequestOptions:requestOptions + message:message + responseHandler:handler + callOptions:callOptions ?: _callOptions + responseClass:responseClass]; +} + +- (GRPCStreamingProtoCall *)RPCToMethod:(NSString *)method + responseHandler:(id<GRPCProtoResponseHandler>)handler + callOptions:(GRPCCallOptions *)callOptions + responseClass:(Class)responseClass { + GRPCProtoMethod *methodName = + [[GRPCProtoMethod alloc] initWithPackage:_packageName service:_serviceName method:method]; + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:_host + path:methodName.HTTPPath + safety:GRPCCallSafetyDefault]; + return [[GRPCStreamingProtoCall alloc] initWithRequestOptions:requestOptions + responseHandler:handler + callOptions:callOptions ?: _callOptions + responseClass:responseClass]; +} + @end @implementation GRPCProtoService diff --git a/src/objective-c/examples/SwiftSample/ViewController.swift b/src/objective-c/examples/SwiftSample/ViewController.swift index 0ba6e21f02..4ed10266a4 100644 --- a/src/objective-c/examples/SwiftSample/ViewController.swift +++ b/src/objective-c/examples/SwiftSample/ViewController.swift @@ -54,8 +54,8 @@ class ViewController: UIViewController { } else { NSLog("2. Finished with error: \(error!)") } - NSLog("2. Response headers: \(RPC.responseHeaders)") - NSLog("2. Response trailers: \(RPC.responseTrailers)") + NSLog("2. Response headers: \(String(describing: RPC.responseHeaders))") + NSLog("2. Response trailers: \(String(describing: RPC.responseTrailers))") } // TODO(jcanizales): Revert to using subscript syntax once XCode 8 is released. @@ -68,7 +68,7 @@ class ViewController: UIViewController { let method = GRPCProtoMethod(package: "grpc.testing", service: "TestService", method: "UnaryCall")! - let requestsWriter = GRXWriter(value: request.data()) + let requestsWriter = GRXWriter(value: request.data())! let call = GRPCCall(host: RemoteHost, path: method.httpPath, requestsWriter: requestsWriter)! @@ -80,8 +80,8 @@ class ViewController: UIViewController { } else { NSLog("3. Finished with error: \(error!)") } - NSLog("3. Response headers: \(call.responseHeaders)") - NSLog("3. Response trailers: \(call.responseTrailers)") + NSLog("3. Response headers: \(String(describing: call.responseHeaders))") + NSLog("3. Response trailers: \(String(describing: call.responseTrailers))") }) } } diff --git a/src/objective-c/tests/APIv2Tests/APIv2Tests.m b/src/objective-c/tests/APIv2Tests/APIv2Tests.m new file mode 100644 index 0000000000..ca7bf47283 --- /dev/null +++ b/src/objective-c/tests/APIv2Tests/APIv2Tests.m @@ -0,0 +1,478 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 <GRPCClient/GRPCCall.h> +#import <ProtoRPC/ProtoMethod.h> +#import <RemoteTest/Messages.pbobjc.h> +#import <XCTest/XCTest.h> + +#include <grpc/grpc.h> + +#import "../version.h" + +// The server address is derived from preprocessor macro, which is +// in turn derived from environment variable of the same name. +#define NSStringize_helper(x) #x +#define NSStringize(x) @NSStringize_helper(x) +static NSString *const kHostAddress = NSStringize(HOST_PORT_LOCAL); +static NSString *const kRemoteSSLHost = NSStringize(HOST_PORT_REMOTE); + +// Package and service name of test server +static NSString *const kPackage = @"grpc.testing"; +static NSString *const kService = @"TestService"; + +static GRPCProtoMethod *kInexistentMethod; +static GRPCProtoMethod *kEmptyCallMethod; +static GRPCProtoMethod *kUnaryCallMethod; +static GRPCProtoMethod *kFullDuplexCallMethod; + +static const int kSimpleDataLength = 100; + +static const NSTimeInterval kTestTimeout = 16; + +// Reveal the _class ivar for testing access +@interface GRPCCall2 () { + @public + GRPCCall *_call; +} + +@end + +// Convenience class to use blocks as callbacks +@interface ClientTestsBlockCallbacks : NSObject<GRPCResponseHandler> + +- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback + messageCallback:(void (^)(id))messageCallback + closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback; + +@end + +@implementation ClientTestsBlockCallbacks { + void (^_initialMetadataCallback)(NSDictionary *); + void (^_messageCallback)(id); + void (^_closeCallback)(NSDictionary *, NSError *); + dispatch_queue_t _dispatchQueue; +} + +- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback + messageCallback:(void (^)(id))messageCallback + closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback { + if ((self = [super init])) { + _initialMetadataCallback = initialMetadataCallback; + _messageCallback = messageCallback; + _closeCallback = closeCallback; + _dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL); + } + return self; +} + +- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata { + if (self->_initialMetadataCallback) { + self->_initialMetadataCallback(initialMetadata); + } +} + +- (void)didReceiveRawMessage:(GPBMessage *)message { + if (self->_messageCallback) { + self->_messageCallback(message); + } +} + +- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error { + if (self->_closeCallback) { + self->_closeCallback(trailingMetadata, error); + } +} + +- (dispatch_queue_t)dispatchQueue { + return _dispatchQueue; +} + +@end + +@interface CallAPIv2Tests : XCTestCase<GRPCAuthorizationProtocol> + +@end + +@implementation CallAPIv2Tests + +- (void)setUp { + // This method isn't implemented by the remote server. + kInexistentMethod = + [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"Inexistent"]; + kEmptyCallMethod = + [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"EmptyCall"]; + kUnaryCallMethod = + [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"UnaryCall"]; + kFullDuplexCallMethod = + [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"FullDuplexCall"]; +} + +- (void)testMetadata { + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."]; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + request.fillUsername = YES; + request.fillOauthScope = YES; + + GRPCRequestOptions *callRequest = + [[GRPCRequestOptions alloc] initWithHost:(NSString *)kRemoteSSLHost + path:kUnaryCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + __block NSDictionary *init_md; + __block NSDictionary *trailing_md; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.oauth2AccessToken = @"bogusToken"; + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:callRequest + responseHandler:[[ClientTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { + init_md = initialMetadata; + } + messageCallback:^(id message) { + XCTFail(@"Received unexpected response."); + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + trailing_md = trailingMetadata; + if (error) { + XCTAssertEqual(error.code, 16, + @"Finished with unexpected error: %@", error); + XCTAssertEqualObjects(init_md, + error.userInfo[kGRPCHeadersKey]); + XCTAssertEqualObjects(trailing_md, + error.userInfo[kGRPCTrailersKey]); + NSString *challengeHeader = init_md[@"www-authenticate"]; + XCTAssertGreaterThan(challengeHeader.length, 0, + @"No challenge in response headers %@", + init_md); + [expectation fulfill]; + } + }] + callOptions:options]; + + [call start]; + [call writeData:[request data]]; + [call finish]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; +} + +- (void)testUserAgentPrefix { + __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; + __weak XCTestExpectation *recvInitialMd = + [self expectationWithDescription:@"Did not receive initial md."]; + + GRPCRequestOptions *request = [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kEmptyCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + NSDictionary *headers = + [NSDictionary dictionaryWithObjectsAndKeys:@"", @"x-grpc-test-echo-useragent", nil]; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = GRPCTransportTypeInsecure; + options.userAgentPrefix = @"Foo"; + options.initialMetadata = headers; + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:request + responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:^( + NSDictionary *initialMetadata) { + NSString *userAgent = initialMetadata[@"x-grpc-test-echo-useragent"]; + // Test the regex is correct + NSString *expectedUserAgent = @"Foo grpc-objc/"; + expectedUserAgent = + [expectedUserAgent stringByAppendingString:GRPC_OBJC_VERSION_STRING]; + expectedUserAgent = [expectedUserAgent stringByAppendingString:@" grpc-c/"]; + expectedUserAgent = + [expectedUserAgent stringByAppendingString:GRPC_C_VERSION_STRING]; + expectedUserAgent = [expectedUserAgent stringByAppendingString:@" (ios; chttp2; "]; + expectedUserAgent = [expectedUserAgent + stringByAppendingString:[NSString stringWithUTF8String:grpc_g_stands_for()]]; + expectedUserAgent = [expectedUserAgent stringByAppendingString:@")"]; + XCTAssertEqualObjects(userAgent, expectedUserAgent); + + NSError *error = nil; + // Change in format of user-agent field in a direction that does not match + // the regex will likely cause problem for certain gRPC users. For details, + // refer to internal doc https://goo.gl/c2diBc + NSRegularExpression *regex = [NSRegularExpression + regularExpressionWithPattern: + @" grpc-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)?/[^ ,]+( \\([^)]*\\))?" + options:0 + error:&error]; + + NSString *customUserAgent = + [regex stringByReplacingMatchesInString:userAgent + options:0 + range:NSMakeRange(0, [userAgent length]) + withTemplate:@""]; + XCTAssertEqualObjects(customUserAgent, @"Foo"); + [recvInitialMd fulfill]; + } + messageCallback:^(id message) { + XCTAssertNotNil(message); + XCTAssertEqual([message length], 0, + @"Non-empty response received: %@", message); + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + if (error) { + XCTFail(@"Finished with unexpected error: %@", error); + } else { + [completion fulfill]; + } + }] + callOptions:options]; + [call writeData:[NSData data]]; + [call start]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; +} + +- (void)getTokenWithHandler:(void (^)(NSString *token))handler { + dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + dispatch_sync(queue, ^{ + handler(@"test-access-token"); + }); +} + +- (void)testOAuthToken { + __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; + + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kEmptyCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = GRPCTransportTypeInsecure; + options.authTokenProvider = self; + __block GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + responseHandler:[[ClientTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, + NSError *error) { + [completion fulfill]; + }] + callOptions:options]; + [call writeData:[NSData data]]; + [call start]; + [call finish]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; +} + +- (void)testResponseSizeLimitExceeded { + __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; + + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kUnaryCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.responseSizeLimit = kSimpleDataLength; + options.transportType = GRPCTransportTypeInsecure; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + request.payload.body = [NSMutableData dataWithLength:options.responseSizeLimit]; + request.responseSize = (int32_t)(options.responseSizeLimit * 2); + + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + responseHandler:[[ClientTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, + NSError *error) { + XCTAssertNotNil(error, + @"Expecting non-nil error"); + XCTAssertEqual(error.code, + GRPCErrorCodeResourceExhausted); + [completion fulfill]; + }] + callOptions:options]; + [call writeData:[request data]]; + [call start]; + [call finish]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; +} + +- (void)testIdempotentProtoRPC { + __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."]; + __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + request.responseSize = kSimpleDataLength; + request.fillUsername = YES; + request.fillOauthScope = YES; + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kUnaryCallMethod.HTTPPath + safety:GRPCCallSafetyIdempotentRequest]; + + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = GRPCTransportTypeInsecure; + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + messageCallback:^(id message) { + NSData *data = (NSData *)message; + XCTAssertNotNil(data, @"nil value received as response."); + XCTAssertGreaterThan(data.length, 0, + @"Empty response received."); + RMTSimpleResponse *responseProto = + [RMTSimpleResponse parseFromData:data error:NULL]; + // We expect empty strings, not nil: + XCTAssertNotNil(responseProto.username, + @"Response's username is nil."); + XCTAssertNotNil(responseProto.oauthScope, + @"Response's OAuth scope is nil."); + [response fulfill]; + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNil(error, @"Finished with unexpected error: %@", + error); + [completion fulfill]; + }] + callOptions:options]; + + [call start]; + [call writeData:[request data]]; + [call finish]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; +} + +- (void)testTimeout { + __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; + + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.timeout = 0.001; + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kFullDuplexCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + responseHandler: + [[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + messageCallback:^(NSData *data) { + XCTFail(@"Failure: response received; Expect: no response received."); + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNotNil(error, + @"Failure: no error received; Expect: receive " + @"deadline exceeded."); + XCTAssertEqual(error.code, GRPCErrorCodeDeadlineExceeded); + [completion fulfill]; + }] + callOptions:options]; + + [call start]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; +} + +- (void)testTimeoutBackoffWithTimeout:(double)timeout Backoff:(double)backoff { + const double maxConnectTime = timeout > backoff ? timeout : backoff; + const double kMargin = 0.1; + + __weak XCTestExpectation *completion = [self expectationWithDescription:@"Timeout in a second."]; + NSString *const kDummyAddress = [NSString stringWithFormat:@"127.0.0.1:10000"]; + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kDummyAddress + path:@"/dummy/path" + safety:GRPCCallSafetyDefault]; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.connectMinTimeout = timeout; + options.connectInitialBackoff = backoff; + options.connectMaxBackoff = 0; + + NSDate *startTime = [NSDate date]; + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + messageCallback:^(NSData *data) { + XCTFail(@"Received message. Should not reach here."); + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNotNil(error, + @"Finished with no error; expecting error"); + XCTAssertLessThan( + [[NSDate date] timeIntervalSinceDate:startTime], + maxConnectTime + kMargin); + [completion fulfill]; + }] + callOptions:options]; + + [call start]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; +} + +- (void)testTimeoutBackoff1 { + [self testTimeoutBackoffWithTimeout:0.7 Backoff:0.4]; +} + +- (void)testTimeoutBackoff2 { + [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.8]; +} + +- (void)testCompression { + __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + request.expectCompressed = [RMTBoolValue message]; + request.expectCompressed.value = YES; + request.responseCompressed = [RMTBoolValue message]; + request.expectCompressed.value = YES; + request.responseSize = kSimpleDataLength; + request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength]; + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kUnaryCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = GRPCTransportTypeInsecure; + options.compressionAlgorithm = GRPCCompressGzip; + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + messageCallback:^(NSData *data) { + NSError *error; + RMTSimpleResponse *response = + [RMTSimpleResponse parseFromData:data error:&error]; + XCTAssertNil(error, @"Error when parsing response: %@", error); + XCTAssertEqual(response.payload.body.length, kSimpleDataLength); + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNil(error, @"Received failure: %@", error); + [completion fulfill]; + }] + + callOptions:options]; + + [call start]; + [call writeData:[request data]]; + [call finish]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; +} + +@end diff --git a/src/objective-c/tests/APIv2Tests/Info.plist b/src/objective-c/tests/APIv2Tests/Info.plist new file mode 100644 index 0000000000..6c40a6cd0c --- /dev/null +++ b/src/objective-c/tests/APIv2Tests/Info.plist @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>$(DEVELOPMENT_LANGUAGE)</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/src/objective-c/tests/ChannelTests/ChannelPoolTest.m b/src/objective-c/tests/ChannelTests/ChannelPoolTest.m new file mode 100644 index 0000000000..eab8c5193f --- /dev/null +++ b/src/objective-c/tests/ChannelTests/ChannelPoolTest.m @@ -0,0 +1,63 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 <XCTest/XCTest.h> + +#import "../../GRPCClient/private/GRPCChannel.h" +#import "../../GRPCClient/private/GRPCChannelPool+Test.h" +#import "../../GRPCClient/private/GRPCCompletionQueue.h" + +#define TEST_TIMEOUT 32 + +static NSString *kDummyHost = @"dummy.host"; +static NSString *kDummyHost2 = @"dummy.host.2"; +static NSString *kDummyPath = @"/dummy/path"; + +@interface ChannelPoolTest : XCTestCase + +@end + +@implementation ChannelPoolTest + ++ (void)setUp { + grpc_init(); +} + +- (void)testCreateAndCacheChannel { + GRPCChannelPool *pool = [[GRPCChannelPool alloc] initTestPool]; + GRPCCallOptions *options1 = [[GRPCCallOptions alloc] init]; + GRPCCallOptions *options2 = [options1 copy]; + GRPCMutableCallOptions *options3 = [options1 mutableCopy]; + options3.transportType = GRPCTransportTypeInsecure; + + GRPCPooledChannel *channel1 = [pool channelWithHost:kDummyHost callOptions:options1]; + GRPCPooledChannel *channel2 = [pool channelWithHost:kDummyHost callOptions:options2]; + GRPCPooledChannel *channel3 = [pool channelWithHost:kDummyHost callOptions:options3]; + GRPCPooledChannel *channel4 = [pool channelWithHost:kDummyHost2 callOptions:options1]; + + XCTAssertNotNil(channel1); + XCTAssertNotNil(channel2); + XCTAssertNotNil(channel3); + XCTAssertNotNil(channel4); + XCTAssertEqual(channel1, channel2); + XCTAssertNotEqual(channel1, channel3); + XCTAssertNotEqual(channel1, channel4); + XCTAssertNotEqual(channel3, channel4); +} + +@end diff --git a/src/objective-c/tests/ChannelTests/ChannelTests.m b/src/objective-c/tests/ChannelTests/ChannelTests.m new file mode 100644 index 0000000000..df78e8b116 --- /dev/null +++ b/src/objective-c/tests/ChannelTests/ChannelTests.m @@ -0,0 +1,112 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 <XCTest/XCTest.h> + +#import "../../GRPCClient/GRPCCallOptions.h" +#import "../../GRPCClient/private/GRPCChannel.h" +#import "../../GRPCClient/private/GRPCChannelPool+Test.h" +#import "../../GRPCClient/private/GRPCChannelPool.h" +#import "../../GRPCClient/private/GRPCCompletionQueue.h" +#import "../../GRPCClient/private/GRPCWrappedCall.h" + +static NSString *kDummyHost = @"dummy.host"; +static NSString *kDummyPath = @"/dummy/path"; + +@interface ChannelTests : XCTestCase + +@end + +@implementation ChannelTests + ++ (void)setUp { + grpc_init(); +} + +- (void)testPooledChannelCreatingChannel { + GRPCCallOptions *options = [[GRPCCallOptions alloc] init]; + GRPCChannelConfiguration *config = + [[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options]; + GRPCPooledChannel *channel = [[GRPCPooledChannel alloc] initWithChannelConfiguration:config]; + GRPCCompletionQueue *cq = [GRPCCompletionQueue completionQueue]; + GRPCWrappedCall *wrappedCall = + [channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options]; + XCTAssertNotNil(channel.wrappedChannel); + (void)wrappedCall; +} + +- (void)testTimedDestroyChannel { + const NSTimeInterval kDestroyDelay = 1.0; + GRPCCallOptions *options = [[GRPCCallOptions alloc] init]; + GRPCChannelConfiguration *config = + [[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options]; + GRPCPooledChannel *channel = + [[GRPCPooledChannel alloc] initWithChannelConfiguration:config destroyDelay:kDestroyDelay]; + GRPCCompletionQueue *cq = [GRPCCompletionQueue completionQueue]; + GRPCWrappedCall *wrappedCall; + GRPCChannel *wrappedChannel; + @autoreleasepool { + wrappedCall = [channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options]; + XCTAssertNotNil(channel.wrappedChannel); + + // Unref and ref channel immediately; expect using the same raw channel. + wrappedChannel = channel.wrappedChannel; + + wrappedCall = nil; + wrappedCall = [channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options]; + XCTAssertEqual(channel.wrappedChannel, wrappedChannel); + + // Unref and ref channel after destroy delay; expect a new raw channel. + wrappedCall = nil; + } + sleep(kDestroyDelay + 1); + XCTAssertNil(channel.wrappedChannel); + wrappedCall = [channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options]; + XCTAssertNotEqual(channel.wrappedChannel, wrappedChannel); +} + +- (void)testDisconnect { + const NSTimeInterval kDestroyDelay = 1.0; + GRPCCallOptions *options = [[GRPCCallOptions alloc] init]; + GRPCChannelConfiguration *config = + [[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options]; + GRPCPooledChannel *channel = + [[GRPCPooledChannel alloc] initWithChannelConfiguration:config destroyDelay:kDestroyDelay]; + GRPCCompletionQueue *cq = [GRPCCompletionQueue completionQueue]; + GRPCWrappedCall *wrappedCall = + [channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options]; + XCTAssertNotNil(channel.wrappedChannel); + + // Disconnect; expect wrapped channel to be dropped + [channel disconnect]; + XCTAssertNil(channel.wrappedChannel); + + // Create a new call and unref the old call; confirm that destroy of the old call does not make + // the channel disconnect, even after the destroy delay. + GRPCWrappedCall *wrappedCall2 = + [channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options]; + XCTAssertNotNil(channel.wrappedChannel); + GRPCChannel *wrappedChannel = channel.wrappedChannel; + wrappedCall = nil; + sleep(kDestroyDelay + 1); + XCTAssertNotNil(channel.wrappedChannel); + XCTAssertEqual(wrappedChannel, channel.wrappedChannel); + (void)wrappedCall2; +} + +@end diff --git a/src/objective-c/tests/ChannelTests/Info.plist b/src/objective-c/tests/ChannelTests/Info.plist new file mode 100644 index 0000000000..6c40a6cd0c --- /dev/null +++ b/src/objective-c/tests/ChannelTests/Info.plist @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>$(DEVELOPMENT_LANGUAGE)</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm b/src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm index 80fa0f4785..fe85e915d4 100644 --- a/src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm +++ b/src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm @@ -81,13 +81,7 @@ static void cronet_init_client_secure_fullstack(grpc_end2end_test_fixture *f, grpc_channel_args *client_args, stream_engine *cronetEngine) { fullstack_secure_fixture_data *ffd = (fullstack_secure_fixture_data *)f->fixture_data; - grpc_arg arg; - arg.key = const_cast<char *>(GRPC_ARG_DISABLE_CLIENT_AUTHORITY_FILTER); - arg.type = GRPC_ARG_INTEGER; - arg.value.integer = 1; - client_args = grpc_channel_args_copy_and_add(client_args, &arg, 1); f->client = grpc_cronet_secure_channel_create(cronetEngine, ffd->localaddr, client_args, NULL); - grpc_channel_args_destroy(client_args); GPR_ASSERT(f->client != NULL); } diff --git a/src/objective-c/tests/CronetUnitTests/CronetUnitTests.m b/src/objective-c/tests/CronetUnitTests/CronetUnitTests.m index 84893b92c1..d732bc6ba9 100644 --- a/src/objective-c/tests/CronetUnitTests/CronetUnitTests.m +++ b/src/objective-c/tests/CronetUnitTests/CronetUnitTests.m @@ -124,14 +124,6 @@ unsigned int parse_h2_length(const char *field) { ((unsigned int)(unsigned char)(field[2])); } -grpc_channel_args *add_disable_client_authority_filter_args(grpc_channel_args *args) { - grpc_arg arg; - arg.key = const_cast<char *>(GRPC_ARG_DISABLE_CLIENT_AUTHORITY_FILTER); - arg.type = GRPC_ARG_INTEGER; - arg.value.integer = 1; - return grpc_channel_args_copy_and_add(args, &arg, 1); -} - - (void)testInternalError { grpc_call *c; grpc_slice request_payload_slice = grpc_slice_from_copied_string("hello world"); @@ -151,9 +143,7 @@ grpc_channel_args *add_disable_client_authority_filter_args(grpc_channel_args *a gpr_join_host_port(&addr, "127.0.0.1", port); grpc_completion_queue *cq = grpc_completion_queue_create_for_next(NULL); stream_engine *cronetEngine = [Cronet getGlobalEngine]; - grpc_channel_args *client_args = add_disable_client_authority_filter_args(NULL); - grpc_channel *client = grpc_cronet_secure_channel_create(cronetEngine, addr, client_args, NULL); - grpc_channel_args_destroy(client_args); + grpc_channel *client = grpc_cronet_secure_channel_create(cronetEngine, addr, NULL, NULL); cq_verifier *cqv = cq_verifier_create(cq); grpc_op ops[6]; @@ -265,7 +255,6 @@ grpc_channel_args *add_disable_client_authority_filter_args(grpc_channel_args *a arg.type = GRPC_ARG_INTEGER; arg.value.integer = useCoalescing ? 1 : 0; grpc_channel_args *args = grpc_channel_args_copy_and_add(NULL, &arg, 1); - args = add_disable_client_authority_filter_args(args); grpc_call *c; grpc_slice request_payload_slice = grpc_slice_from_copied_string("hello world"); diff --git a/src/objective-c/tests/GRPCClientTests.m b/src/objective-c/tests/GRPCClientTests.m index a0de8ba899..3ef046835e 100644 --- a/src/objective-c/tests/GRPCClientTests.m +++ b/src/objective-c/tests/GRPCClientTests.m @@ -362,9 +362,10 @@ static GRPCProtoMethod *kFullDuplexCallMethod; // TODO(makarandd): Move to a different file that contains only unit tests - (void)testExceptions { + GRXWriter *writer = [GRXWriter writerWithValue:[NSData data]]; // Try to set parameters to nil for GRPCCall. This should cause an exception @try { - (void)[[GRPCCall alloc] initWithHost:nil path:nil requestsWriter:nil]; + (void)[[GRPCCall alloc] initWithHost:nil path:nil requestsWriter:writer]; XCTFail(@"Did not receive an exception when parameters are nil"); } @catch (NSException *theException) { NSLog(@"Received exception as expected: %@", theException.name); @@ -554,13 +555,14 @@ static GRPCProtoMethod *kFullDuplexCallMethod; __weak XCTestExpectation *completion = [self expectationWithDescription:@"Timeout in a second."]; NSString *const kDummyAddress = [NSString stringWithFormat:@"8.8.8.8:1"]; - GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress - path:@"" - requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; + [GRPCCall useInsecureConnectionsForHost:kDummyAddress]; [GRPCCall setMinConnectTimeout:timeout * 1000 initialBackoff:backoff * 1000 maxBackoff:0 forHost:kDummyAddress]; + GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress + path:@"/dummyPath" + requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; NSDate *startTime = [NSDate date]; id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(id value) { XCTAssert(NO, @"Received message. Should not reach here"); @@ -583,11 +585,11 @@ static GRPCProtoMethod *kFullDuplexCallMethod; // The numbers of the following three tests are selected to be smaller than the default values of // initial backoff (1s) and min_connect_timeout (20s), so that if they fail we know the default // values fail to be overridden by the channel args. -- (void)testTimeoutBackoff2 { +- (void)testTimeoutBackoff1 { [self testTimeoutBackoffWithTimeout:0.7 Backoff:0.3]; } -- (void)testTimeoutBackoff3 { +- (void)testTimeoutBackoff2 { [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.7]; } diff --git a/src/objective-c/tests/InteropTests.h b/src/objective-c/tests/InteropTests.h index 039d92a8d3..038f24b62e 100644 --- a/src/objective-c/tests/InteropTests.h +++ b/src/objective-c/tests/InteropTests.h @@ -18,6 +18,8 @@ #import <XCTest/XCTest.h> +#import <GRPCClient/GRPCCallOptions.h> + /** * Implements tests as described here: * https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md @@ -38,4 +40,23 @@ * remote servers enconde responses with different overhead (?), so this is defined per-subclass. */ - (int32_t)encodingOverhead; + +/** + * The type of transport to be used. The base implementation returns default. Subclasses should + * override to appropriate settings. + */ ++ (GRPCTransportType)transportType; + +/** + * The root certificates to be used. The base implementation returns nil. Subclasses should override + * to appropriate settings. + */ ++ (NSString *)PEMRootCertificates; + +/** + * The root certificates to be used. The base implementation returns nil. Subclasses should override + * to appropriate settings. + */ ++ (NSString *)hostNameOverride; + @end diff --git a/src/objective-c/tests/InteropTests.m b/src/objective-c/tests/InteropTests.m index 9d79606881..717dfd81f7 100644 --- a/src/objective-c/tests/InteropTests.m +++ b/src/objective-c/tests/InteropTests.m @@ -74,6 +74,58 @@ BOOL isRemoteInteropTest(NSString *host) { return [host isEqualToString:@"grpc-test.sandbox.googleapis.com"]; } +// Convenience class to use blocks as callbacks +@interface InteropTestsBlockCallbacks : NSObject<GRPCProtoResponseHandler> + +- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback + messageCallback:(void (^)(id))messageCallback + closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback; + +@end + +@implementation InteropTestsBlockCallbacks { + void (^_initialMetadataCallback)(NSDictionary *); + void (^_messageCallback)(id); + void (^_closeCallback)(NSDictionary *, NSError *); + dispatch_queue_t _dispatchQueue; +} + +- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback + messageCallback:(void (^)(id))messageCallback + closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback { + if ((self = [super init])) { + _initialMetadataCallback = initialMetadataCallback; + _messageCallback = messageCallback; + _closeCallback = closeCallback; + _dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL); + } + return self; +} + +- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata { + if (_initialMetadataCallback) { + _initialMetadataCallback(initialMetadata); + } +} + +- (void)didReceiveProtoMessage:(GPBMessage *)message { + if (_messageCallback) { + _messageCallback(message); + } +} + +- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error { + if (_closeCallback) { + _closeCallback(trailingMetadata, error); + } +} + +- (dispatch_queue_t)dispatchQueue { + return _dispatchQueue; +} + +@end + #pragma mark Tests @implementation InteropTests { @@ -91,6 +143,18 @@ BOOL isRemoteInteropTest(NSString *host) { return 0; } ++ (GRPCTransportType)transportType { + return GRPCTransportTypeChttp2BoringSSL; +} + ++ (NSString *)PEMRootCertificates { + return nil; +} + ++ (NSString *)hostNameOverride { + return nil; +} + + (void)setUp { NSLog(@"InteropTest Started, class: %@", [[self class] description]); #ifdef GRPC_COMPILE_WITH_CRONET @@ -109,11 +173,11 @@ BOOL isRemoteInteropTest(NSString *host) { [GRPCCall resetHostSettings]; - _service = self.class.host ? [RMTTestService serviceWithHost:self.class.host] : nil; + _service = [[self class] host] ? [RMTTestService serviceWithHost:[[self class] host]] : nil; } - (void)testEmptyUnaryRPC { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"EmptyUnary"]; GPBEmpty *request = [GPBEmpty message]; @@ -131,8 +195,40 @@ BOOL isRemoteInteropTest(NSString *host) { [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testEmptyUnaryRPCWithV2API { + XCTAssertNotNil([[self class] host]); + __weak XCTestExpectation *expectReceive = + [self expectationWithDescription:@"EmptyUnaryWithV2API received message"]; + __weak XCTestExpectation *expectComplete = + [self expectationWithDescription:@"EmptyUnaryWithV2API completed"]; + + GPBEmpty *request = [GPBEmpty message]; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = [[self class] transportType]; + options.PEMRootCertificates = [[self class] PEMRootCertificates]; + options.hostNameOverride = [[self class] hostNameOverride]; + + GRPCUnaryProtoCall *call = [_service + emptyCallWithMessage:request + responseHandler:[[InteropTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + messageCallback:^(id message) { + if (message) { + id expectedResponse = [GPBEmpty message]; + XCTAssertEqualObjects(message, expectedResponse); + [expectReceive fulfill]; + } + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNil(error, @"Unexpected error: %@", error); + [expectComplete fulfill]; + }] + callOptions:options]; + [call start]; + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + - (void)testLargeUnaryRPC { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"]; RMTSimpleRequest *request = [RMTSimpleRequest message]; @@ -155,8 +251,50 @@ BOOL isRemoteInteropTest(NSString *host) { [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testLargeUnaryRPCWithV2API { + XCTAssertNotNil([[self class] host]); + __weak XCTestExpectation *expectReceive = + [self expectationWithDescription:@"LargeUnaryWithV2API received message"]; + __weak XCTestExpectation *expectComplete = + [self expectationWithDescription:@"LargeUnaryWithV2API received complete"]; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + request.responseType = RMTPayloadType_Compressable; + request.responseSize = 314159; + request.payload.body = [NSMutableData dataWithLength:271828]; + + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = [[self class] transportType]; + options.PEMRootCertificates = [[self class] PEMRootCertificates]; + options.hostNameOverride = [[self class] hostNameOverride]; + + GRPCUnaryProtoCall *call = [_service + unaryCallWithMessage:request + responseHandler:[[InteropTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + messageCallback:^(id message) { + XCTAssertNotNil(message); + if (message) { + RMTSimpleResponse *expectedResponse = + [RMTSimpleResponse message]; + expectedResponse.payload.type = RMTPayloadType_Compressable; + expectedResponse.payload.body = + [NSMutableData dataWithLength:314159]; + XCTAssertEqualObjects(message, expectedResponse); + + [expectReceive fulfill]; + } + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNil(error, @"Unexpected error: %@", error); + [expectComplete fulfill]; + }] + callOptions:options]; + [call start]; + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + - (void)testPacketCoalescing { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"]; RMTSimpleRequest *request = [RMTSimpleRequest message]; @@ -195,7 +333,7 @@ BOOL isRemoteInteropTest(NSString *host) { } - (void)test4MBResponsesAreAccepted { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"MaxResponseSize"]; RMTSimpleRequest *request = [RMTSimpleRequest message]; @@ -213,7 +351,7 @@ BOOL isRemoteInteropTest(NSString *host) { } - (void)testResponsesOverMaxSizeFailWithActionableMessage { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"ResponseOverMaxSize"]; RMTSimpleRequest *request = [RMTSimpleRequest message]; @@ -239,7 +377,7 @@ BOOL isRemoteInteropTest(NSString *host) { } - (void)testResponsesOver4MBAreAcceptedIfOptedIn { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"HigherResponseSizeLimit"]; @@ -247,7 +385,7 @@ BOOL isRemoteInteropTest(NSString *host) { const size_t kPayloadSize = 5 * 1024 * 1024; // 5MB request.responseSize = kPayloadSize; - [GRPCCall setResponseSizeLimit:6 * 1024 * 1024 forHost:self.class.host]; + [GRPCCall setResponseSizeLimit:6 * 1024 * 1024 forHost:[[self class] host]]; [_service unaryCallWithRequest:request handler:^(RMTSimpleResponse *response, NSError *error) { @@ -260,7 +398,7 @@ BOOL isRemoteInteropTest(NSString *host) { } - (void)testClientStreamingRPC { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"ClientStreaming"]; RMTStreamingInputCallRequest *request1 = [RMTStreamingInputCallRequest message]; @@ -295,7 +433,7 @@ BOOL isRemoteInteropTest(NSString *host) { } - (void)testServerStreamingRPC { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"ServerStreaming"]; NSArray *expectedSizes = @[ @31415, @9, @2653, @58979 ]; @@ -334,7 +472,7 @@ BOOL isRemoteInteropTest(NSString *host) { } - (void)testPingPongRPC { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"PingPong"]; NSArray *requests = @[ @27182, @8, @1828, @45904 ]; @@ -380,8 +518,60 @@ BOOL isRemoteInteropTest(NSString *host) { [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testPingPongRPCWithV2API { + XCTAssertNotNil([[self class] host]); + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"PingPongWithV2API"]; + + NSArray *requests = @[ @27182, @8, @1828, @45904 ]; + NSArray *responses = @[ @31415, @9, @2653, @58979 ]; + + __block int index = 0; + + id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index] + requestedResponseSize:responses[index]]; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = [[self class] transportType]; + options.PEMRootCertificates = [[self class] PEMRootCertificates]; + options.hostNameOverride = [[self class] hostNameOverride]; + + __block GRPCStreamingProtoCall *call = [_service + fullDuplexCallWithResponseHandler:[[InteropTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:^(id message) { + XCTAssertLessThan(index, 4, + @"More than 4 responses received."); + id expected = [RMTStreamingOutputCallResponse + messageWithPayloadSize:responses[index]]; + XCTAssertEqualObjects(message, expected); + index += 1; + if (index < 4) { + id request = [RMTStreamingOutputCallRequest + messageWithPayloadSize:requests[index] + requestedResponseSize:responses[index]]; + [call writeMessage:request]; + } else { + [call finish]; + } + } + closeCallback:^(NSDictionary *trailingMetadata, + NSError *error) { + XCTAssertNil(error, + @"Finished with unexpected error: %@", + error); + XCTAssertEqual(index, 4, + @"Received %i responses instead of 4.", + index); + [expectation fulfill]; + }] + callOptions:options]; + [call start]; + [call writeMessage:request]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + - (void)testEmptyStreamRPC { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"EmptyStream"]; [_service fullDuplexCallWithRequestsWriter:[GRXWriter emptyWriter] eventHandler:^(BOOL done, RMTStreamingOutputCallResponse *response, @@ -394,7 +584,7 @@ BOOL isRemoteInteropTest(NSString *host) { } - (void)testCancelAfterBeginRPC { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"CancelAfterBegin"]; // A buffered pipe to which we never write any value acts as a writer that just hangs. @@ -418,8 +608,32 @@ BOOL isRemoteInteropTest(NSString *host) { [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testCancelAfterBeginRPCWithV2API { + XCTAssertNotNil([[self class] host]); + __weak XCTestExpectation *expectation = + [self expectationWithDescription:@"CancelAfterBeginWithV2API"]; + + // A buffered pipe to which we never write any value acts as a writer that just hangs. + __block GRPCStreamingProtoCall *call = [_service + streamingInputCallWithResponseHandler:[[InteropTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:^(id message) { + XCTFail(@"Not expected to receive message"); + } + closeCallback:^(NSDictionary *trailingMetadata, + NSError *error) { + XCTAssertEqual(error.code, GRPC_STATUS_CANCELLED); + [expectation fulfill]; + }] + callOptions:nil]; + [call start]; + [call cancel]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + - (void)testCancelAfterFirstResponseRPC { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"CancelAfterFirstResponse"]; @@ -454,8 +668,76 @@ BOOL isRemoteInteropTest(NSString *host) { [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testCancelAfterFirstResponseRPCWithV2API { + XCTAssertNotNil([[self class] host]); + __weak XCTestExpectation *completionExpectation = + [self expectationWithDescription:@"Call completed."]; + __weak XCTestExpectation *responseExpectation = + [self expectationWithDescription:@"Received response."]; + + __block BOOL receivedResponse = NO; + + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = self.class.transportType; + options.PEMRootCertificates = self.class.PEMRootCertificates; + options.hostNameOverride = [[self class] hostNameOverride]; + + id request = + [RMTStreamingOutputCallRequest messageWithPayloadSize:@21782 requestedResponseSize:@31415]; + + __block GRPCStreamingProtoCall *call = [_service + fullDuplexCallWithResponseHandler:[[InteropTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:^(id message) { + XCTAssertFalse(receivedResponse); + receivedResponse = YES; + [call cancel]; + [responseExpectation fulfill]; + } + closeCallback:^(NSDictionary *trailingMetadata, + NSError *error) { + XCTAssertEqual(error.code, GRPC_STATUS_CANCELLED); + [completionExpectation fulfill]; + }] + callOptions:options]; + [call start]; + [call writeMessage:request]; + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testCancelAfterFirstRequestWithV2API { + XCTAssertNotNil([[self class] host]); + __weak XCTestExpectation *completionExpectation = + [self expectationWithDescription:@"Call completed."]; + + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = self.class.transportType; + options.PEMRootCertificates = self.class.PEMRootCertificates; + options.hostNameOverride = [[self class] hostNameOverride]; + + id request = + [RMTStreamingOutputCallRequest messageWithPayloadSize:@21782 requestedResponseSize:@31415]; + + __block GRPCStreamingProtoCall *call = [_service + fullDuplexCallWithResponseHandler:[[InteropTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:^(id message) { + XCTFail(@"Received unexpected response."); + } + closeCallback:^(NSDictionary *trailingMetadata, + NSError *error) { + XCTAssertEqual(error.code, GRPC_STATUS_CANCELLED); + [completionExpectation fulfill]; + }] + callOptions:options]; + [call start]; + [call writeMessage:request]; + [call cancel]; + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + - (void)testRPCAfterClosingOpenConnections { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC after closing connection"]; @@ -487,10 +769,10 @@ BOOL isRemoteInteropTest(NSString *host) { - (void)testCompressedUnaryRPC { // This test needs to be disabled for remote test because interop server grpc-test // does not support compression. - if (isRemoteInteropTest(self.class.host)) { + if (isRemoteInteropTest([[self class] host])) { return; } - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"]; RMTSimpleRequest *request = [RMTSimpleRequest message]; @@ -498,7 +780,7 @@ BOOL isRemoteInteropTest(NSString *host) { request.responseSize = 314159; request.payload.body = [NSMutableData dataWithLength:271828]; request.expectCompressed.value = YES; - [GRPCCall setDefaultCompressMethod:GRPCCompressGzip forhost:self.class.host]; + [GRPCCall setDefaultCompressMethod:GRPCCompressGzip forhost:[[self class] host]]; [_service unaryCallWithRequest:request handler:^(RMTSimpleResponse *response, NSError *error) { @@ -517,10 +799,10 @@ BOOL isRemoteInteropTest(NSString *host) { #ifndef GRPC_COMPILE_WITH_CRONET - (void)testKeepalive { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Keepalive"]; - [GRPCCall setKeepaliveWithInterval:1500 timeout:0 forHost:self.class.host]; + [GRPCCall setKeepaliveWithInterval:1500 timeout:0 forHost:[[self class] host]]; NSArray *requests = @[ @27182, @8 ]; NSArray *responses = @[ @31415, @9 ]; diff --git a/src/objective-c/tests/InteropTestsCallOptions/Info.plist b/src/objective-c/tests/InteropTestsCallOptions/Info.plist new file mode 100644 index 0000000000..6c40a6cd0c --- /dev/null +++ b/src/objective-c/tests/InteropTestsCallOptions/Info.plist @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>$(DEVELOPMENT_LANGUAGE)</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/src/objective-c/tests/InteropTestsCallOptions/InteropTestsCallOptions.m b/src/objective-c/tests/InteropTestsCallOptions/InteropTestsCallOptions.m new file mode 100644 index 0000000000..db51cb1cfb --- /dev/null +++ b/src/objective-c/tests/InteropTestsCallOptions/InteropTestsCallOptions.m @@ -0,0 +1,116 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 <XCTest/XCTest.h> + +#import <RemoteTest/Messages.pbobjc.h> +#import <RemoteTest/Test.pbobjc.h> +#import <RemoteTest/Test.pbrpc.h> +#import <RxLibrary/GRXBufferedPipe.h> +#import <RxLibrary/GRXWriter+Immediate.h> +#import <grpc/grpc.h> + +#define NSStringize_helper(x) #x +#define NSStringize(x) @NSStringize_helper(x) +static NSString *kRemoteHost = NSStringize(HOST_PORT_REMOTE); +const int32_t kRemoteInteropServerOverhead = 12; + +static const NSTimeInterval TEST_TIMEOUT = 16000; + +@interface InteropTestsCallOptions : XCTestCase + +@end + +@implementation InteropTestsCallOptions { + RMTTestService *_service; +} + +- (void)setUp { + self.continueAfterFailure = NO; + _service = [RMTTestService serviceWithHost:kRemoteHost]; + _service.options = [[GRPCCallOptions alloc] init]; +} + +- (void)test4MBResponsesAreAccepted { + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"MaxResponseSize"]; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + const int32_t kPayloadSize = + 4 * 1024 * 1024 - kRemoteInteropServerOverhead; // 4MB - encoding overhead + request.responseSize = kPayloadSize; + + [_service unaryCallWithRequest:request + handler:^(RMTSimpleResponse *response, NSError *error) { + XCTAssertNil(error, @"Finished with unexpected error: %@", error); + XCTAssertEqual(response.payload.body.length, kPayloadSize); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testResponsesOverMaxSizeFailWithActionableMessage { + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"ResponseOverMaxSize"]; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + const int32_t kPayloadSize = + 4 * 1024 * 1024 - kRemoteInteropServerOverhead + 1; // 1B over max size + request.responseSize = kPayloadSize; + + [_service unaryCallWithRequest:request + handler:^(RMTSimpleResponse *response, NSError *error) { + XCTAssertEqualObjects( + error.localizedDescription, + @"Received message larger than max (4194305 vs. 4194304)"); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testResponsesOver4MBAreAcceptedIfOptedIn { + __weak XCTestExpectation *expectation = + [self expectationWithDescription:@"HigherResponseSizeLimit"]; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + const size_t kPayloadSize = 5 * 1024 * 1024; // 5MB + request.responseSize = kPayloadSize; + + GRPCProtoCall *rpc = [_service + RPCToUnaryCallWithRequest:request + handler:^(RMTSimpleResponse *response, NSError *error) { + XCTAssertNil(error, @"Finished with unexpected error: %@", error); + XCTAssertEqual(response.payload.body.length, kPayloadSize); + [expectation fulfill]; + }]; + GRPCCallOptions *options = rpc.options; + options.responseSizeLimit = 6 * 1024 * 1024; + + [rpc start]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end diff --git a/src/objective-c/tests/InteropTestsLocalCleartext.m b/src/objective-c/tests/InteropTestsLocalCleartext.m index d49e875bd0..a9c6918333 100644 --- a/src/objective-c/tests/InteropTestsLocalCleartext.m +++ b/src/objective-c/tests/InteropTestsLocalCleartext.m @@ -41,6 +41,14 @@ static int32_t kLocalInteropServerOverhead = 10; return kLocalCleartextHost; } ++ (NSString *)PEMRootCertificates { + return nil; +} + ++ (NSString *)hostNameOverride { + return nil; +} + - (int32_t)encodingOverhead { return kLocalInteropServerOverhead; // bytes } @@ -52,4 +60,8 @@ static int32_t kLocalInteropServerOverhead = 10; [GRPCCall useInsecureConnectionsForHost:kLocalCleartextHost]; } ++ (GRPCTransportType)transportType { + return GRPCTransportTypeInsecure; +} + @end diff --git a/src/objective-c/tests/InteropTestsLocalSSL.m b/src/objective-c/tests/InteropTestsLocalSSL.m index a8c4dc7dfd..e8222f602f 100644 --- a/src/objective-c/tests/InteropTestsLocalSSL.m +++ b/src/objective-c/tests/InteropTestsLocalSSL.m @@ -40,15 +40,31 @@ static int32_t kLocalInteropServerOverhead = 10; return kLocalSSLHost; } ++ (NSString *)PEMRootCertificates { + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSString *certsPath = + [bundle pathForResource:@"TestCertificates.bundle/test-certificates" ofType:@"pem"]; + NSError *error; + return [NSString stringWithContentsOfFile:certsPath encoding:NSUTF8StringEncoding error:&error]; +} + ++ (NSString *)hostNameOverride { + return @"foo.test.google.fr"; +} + - (int32_t)encodingOverhead { return kLocalInteropServerOverhead; // bytes } ++ (GRPCTransportType)transportType { + return GRPCTransportTypeChttp2BoringSSL; +} + - (void)setUp { [super setUp]; // Register test server certificates and name. - NSBundle *bundle = [NSBundle bundleForClass:self.class]; + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; NSString *certsPath = [bundle pathForResource:@"TestCertificates.bundle/test-certificates" ofType:@"pem"]; [GRPCCall useTestCertsPath:certsPath testName:@"foo.test.google.fr" forHost:kLocalSSLHost]; diff --git a/src/objective-c/tests/InteropTestsMultipleChannels/Info.plist b/src/objective-c/tests/InteropTestsMultipleChannels/Info.plist new file mode 100644 index 0000000000..6c40a6cd0c --- /dev/null +++ b/src/objective-c/tests/InteropTestsMultipleChannels/Info.plist @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>$(DEVELOPMENT_LANGUAGE)</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/src/objective-c/tests/InteropTestsMultipleChannels/InteropTestsMultipleChannels.m b/src/objective-c/tests/InteropTestsMultipleChannels/InteropTestsMultipleChannels.m new file mode 100644 index 0000000000..b0d4e4883a --- /dev/null +++ b/src/objective-c/tests/InteropTestsMultipleChannels/InteropTestsMultipleChannels.m @@ -0,0 +1,259 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 <XCTest/XCTest.h> + +#import <Cronet/Cronet.h> +#import <RemoteTest/Messages.pbobjc.h> +#import <RemoteTest/Test.pbobjc.h> +#import <RemoteTest/Test.pbrpc.h> +#import <RxLibrary/GRXBufferedPipe.h> + +#define NSStringize_helper(x) #x +#define NSStringize(x) @NSStringize_helper(x) +static NSString *const kRemoteSSLHost = NSStringize(HOST_PORT_REMOTE); +static NSString *const kLocalSSLHost = NSStringize(HOST_PORT_LOCALSSL); +static NSString *const kLocalCleartextHost = NSStringize(HOST_PORT_LOCAL); + +static const NSTimeInterval TEST_TIMEOUT = 8000; + +@interface RMTStreamingOutputCallRequest (Constructors) ++ (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize + requestedResponseSize:(NSNumber *)responseSize; +@end + +@implementation RMTStreamingOutputCallRequest (Constructors) ++ (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize + requestedResponseSize:(NSNumber *)responseSize { + RMTStreamingOutputCallRequest *request = [self message]; + RMTResponseParameters *parameters = [RMTResponseParameters message]; + parameters.size = responseSize.intValue; + [request.responseParametersArray addObject:parameters]; + request.payload.body = [NSMutableData dataWithLength:payloadSize.unsignedIntegerValue]; + return request; +} +@end + +@interface RMTStreamingOutputCallResponse (Constructors) ++ (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize; +@end + +@implementation RMTStreamingOutputCallResponse (Constructors) ++ (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize { + RMTStreamingOutputCallResponse *response = [self message]; + response.payload.type = RMTPayloadType_Compressable; + response.payload.body = [NSMutableData dataWithLength:payloadSize.unsignedIntegerValue]; + return response; +} +@end + +@interface InteropTestsMultipleChannels : XCTestCase + +@end + +dispatch_once_t initCronet; + +@implementation InteropTestsMultipleChannels { + RMTTestService *_remoteService; + RMTTestService *_remoteCronetService; + RMTTestService *_localCleartextService; + RMTTestService *_localSSLService; +} + +- (void)setUp { + [super setUp]; + + self.continueAfterFailure = NO; + + // Default stack with remote host + _remoteService = [RMTTestService serviceWithHost:kRemoteSSLHost]; + + // Cronet stack with remote host + _remoteCronetService = [RMTTestService serviceWithHost:kRemoteSSLHost]; + + dispatch_once(&initCronet, ^{ + [Cronet setHttp2Enabled:YES]; + [Cronet start]; + }); + + GRPCCallOptions *options = [[GRPCCallOptions alloc] init]; + options.transportType = GRPCTransportTypeCronet; + options.cronetEngine = [Cronet getGlobalEngine]; + _remoteCronetService.options = options; + + // Local stack with no SSL + _localCleartextService = [RMTTestService serviceWithHost:kLocalCleartextHost]; + options = [[GRPCCallOptions alloc] init]; + options.transportType = GRPCTransportTypeInsecure; + _localCleartextService.options = options; + + // Local stack with SSL + _localSSLService = [RMTTestService serviceWithHost:kLocalSSLHost]; + + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSString *certsPath = + [bundle pathForResource:@"TestCertificates.bundle/test-certificates" ofType:@"pem"]; + NSError *error = nil; + NSString *certs = + [NSString stringWithContentsOfFile:certsPath encoding:NSUTF8StringEncoding error:&error]; + XCTAssertNil(error); + + options = [[GRPCCallOptions alloc] init]; + options.transportType = GRPCTransportTypeChttp2BoringSSL; + options.PEMRootCertificates = certs; + options.hostNameOverride = @"foo.test.google.fr"; + _localSSLService.options = options; +} + +- (void)testEmptyUnaryRPC { + __weak XCTestExpectation *expectRemote = [self expectationWithDescription:@"Remote RPC finish"]; + __weak XCTestExpectation *expectCronetRemote = + [self expectationWithDescription:@"Remote RPC finish"]; + __weak XCTestExpectation *expectCleartext = + [self expectationWithDescription:@"Remote RPC finish"]; + __weak XCTestExpectation *expectSSL = [self expectationWithDescription:@"Remote RPC finish"]; + + GPBEmpty *request = [GPBEmpty message]; + + void (^handler)(GPBEmpty *response, NSError *error) = ^(GPBEmpty *response, NSError *error) { + XCTAssertNil(error, @"Finished with unexpected error: %@", error); + + id expectedResponse = [GPBEmpty message]; + XCTAssertEqualObjects(response, expectedResponse); + }; + + [_remoteService emptyCallWithRequest:request + handler:^(GPBEmpty *response, NSError *error) { + handler(response, error); + [expectRemote fulfill]; + }]; + [_remoteCronetService emptyCallWithRequest:request + handler:^(GPBEmpty *response, NSError *error) { + handler(response, error); + [expectCronetRemote fulfill]; + }]; + [_localCleartextService emptyCallWithRequest:request + handler:^(GPBEmpty *response, NSError *error) { + handler(response, error); + [expectCleartext fulfill]; + }]; + [_localSSLService emptyCallWithRequest:request + handler:^(GPBEmpty *response, NSError *error) { + handler(response, error); + [expectSSL fulfill]; + }]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testFullDuplexRPC { + __weak XCTestExpectation *expectRemote = [self expectationWithDescription:@"Remote RPC finish"]; + __weak XCTestExpectation *expectCronetRemote = + [self expectationWithDescription:@"Remote RPC finish"]; + __weak XCTestExpectation *expectCleartext = + [self expectationWithDescription:@"Remote RPC finish"]; + __weak XCTestExpectation *expectSSL = [self expectationWithDescription:@"Remote RPC finish"]; + + NSArray *requestSizes = @[ @100, @101, @102, @103 ]; + NSArray *responseSizes = @[ @104, @105, @106, @107 ]; + XCTAssertEqual([requestSizes count], [responseSizes count]); + NSUInteger kRounds = [requestSizes count]; + + NSMutableArray *requests = [NSMutableArray arrayWithCapacity:kRounds]; + NSMutableArray *responses = [NSMutableArray arrayWithCapacity:kRounds]; + for (int i = 0; i < kRounds; i++) { + requests[i] = [RMTStreamingOutputCallRequest messageWithPayloadSize:requestSizes[i] + requestedResponseSize:responseSizes[i]]; + responses[i] = [RMTStreamingOutputCallResponse messageWithPayloadSize:responseSizes[i]]; + } + + __block NSMutableArray *steps = [NSMutableArray arrayWithCapacity:4]; + __block NSMutableArray *requestsBuffers = [NSMutableArray arrayWithCapacity:4]; + for (int i = 0; i < 4; i++) { + steps[i] = [NSNumber numberWithUnsignedInteger:0]; + requestsBuffers[i] = [[GRXBufferedPipe alloc] init]; + [requestsBuffers[i] writeValue:requests[0]]; + } + + BOOL (^handler)(int, BOOL, RMTStreamingOutputCallResponse *, NSError *) = + ^(int index, BOOL done, RMTStreamingOutputCallResponse *response, NSError *error) { + XCTAssertNil(error, @"Finished with unexpected error: %@", error); + XCTAssertTrue(done || response, @"Event handler called without an event."); + if (response) { + NSUInteger step = [steps[index] unsignedIntegerValue]; + XCTAssertLessThan(step, kRounds, @"More than %lu responses received.", + (unsigned long)kRounds); + XCTAssertEqualObjects(response, responses[step]); + step++; + steps[index] = [NSNumber numberWithUnsignedInteger:step]; + GRXBufferedPipe *pipe = requestsBuffers[index]; + if (step < kRounds) { + [pipe writeValue:requests[step]]; + } else { + [pipe writesFinishedWithError:nil]; + } + } + if (done) { + NSUInteger step = [steps[index] unsignedIntegerValue]; + XCTAssertEqual(step, kRounds, @"Received %lu responses instead of %lu.", step, kRounds); + return YES; + } + return NO; + }; + + [_remoteService + fullDuplexCallWithRequestsWriter:requestsBuffers[0] + eventHandler:^(BOOL done, + RMTStreamingOutputCallResponse *_Nullable response, + NSError *_Nullable error) { + if (handler(0, done, response, error)) { + [expectRemote fulfill]; + } + }]; + [_remoteCronetService + fullDuplexCallWithRequestsWriter:requestsBuffers[1] + eventHandler:^(BOOL done, + RMTStreamingOutputCallResponse *_Nullable response, + NSError *_Nullable error) { + if (handler(1, done, response, error)) { + [expectCronetRemote fulfill]; + } + }]; + [_localCleartextService + fullDuplexCallWithRequestsWriter:requestsBuffers[2] + eventHandler:^(BOOL done, + RMTStreamingOutputCallResponse *_Nullable response, + NSError *_Nullable error) { + if (handler(2, done, response, error)) { + [expectCleartext fulfill]; + } + }]; + [_localSSLService + fullDuplexCallWithRequestsWriter:requestsBuffers[3] + eventHandler:^(BOOL done, + RMTStreamingOutputCallResponse *_Nullable response, + NSError *_Nullable error) { + if (handler(3, done, response, error)) { + [expectSSL fulfill]; + } + }]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +@end diff --git a/src/objective-c/tests/InteropTestsRemote.m b/src/objective-c/tests/InteropTestsRemote.m index e5738aac73..c1cd9b81ef 100644 --- a/src/objective-c/tests/InteropTestsRemote.m +++ b/src/objective-c/tests/InteropTestsRemote.m @@ -41,8 +41,26 @@ static int32_t kRemoteInteropServerOverhead = 12; return kRemoteSSLHost; } ++ (NSString *)PEMRootCertificates { + return nil; +} + ++ (NSString *)hostNameOverride { + return nil; +} + - (int32_t)encodingOverhead { return kRemoteInteropServerOverhead; // bytes } +#ifdef GRPC_COMPILE_WITH_CRONET ++ (GRPCTransportType)transportType { + return GRPCTransportTypeCronet; +} +#else ++ (GRPCTransportType)transportType { + return GRPCTransportTypeChttp2BoringSSL; +} +#endif + @end diff --git a/src/objective-c/tests/Podfile b/src/objective-c/tests/Podfile index 5d2f1340da..8e5f588906 100644 --- a/src/objective-c/tests/Podfile +++ b/src/objective-c/tests/Podfile @@ -14,7 +14,10 @@ GRPC_LOCAL_SRC = '../../..' InteropTestsLocalSSL InteropTestsLocalCleartext InteropTestsRemoteWithCronet + InteropTestsMultipleChannels + InteropTestsCallOptions UnitTests + APIv2Tests ).each do |target_name| target target_name do pod 'Protobuf', :path => "#{GRPC_LOCAL_SRC}/third_party/protobuf", :inhibit_warnings => true @@ -30,7 +33,7 @@ GRPC_LOCAL_SRC = '../../..' pod 'gRPC-ProtoRPC', :path => GRPC_LOCAL_SRC, :inhibit_warnings => true pod 'RemoteTest', :path => "RemoteTestClient", :inhibit_warnings => true - if target_name == 'InteropTestsRemoteWithCronet' + if target_name == 'InteropTestsRemoteWithCronet' or target_name == 'InteropTestsMultipleChannels' pod 'gRPC-Core/Cronet-Implementation', :path => GRPC_LOCAL_SRC pod 'CronetFramework', :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c" end @@ -72,6 +75,12 @@ end end end +target 'ChannelTests' do + pod 'gRPC', :path => GRPC_LOCAL_SRC + pod 'gRPC-Core', :path => GRPC_LOCAL_SRC + pod 'BoringSSL-GRPC', :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c", :inhibit_warnings => true +end + # gRPC-Core.podspec needs to be modified to be successfully used for local development. A Podfile's # pre_install hook lets us do that. The block passed to it runs after the podspecs are downloaded # and before they are installed in the user project. @@ -139,5 +148,16 @@ post_install do |installer| end end end + + # Enable NSAssert on gRPC + if target.name == 'gRPC' || target.name.start_with?('gRPC.') || + target.name == 'ProtoRPC' || target.name.start_with?('ProtoRPC.') || + target.name == 'RxLibrary' || target.name.start_with?('RxLibrary.') + target.build_configurations.each do |config| + if config.name != 'Release' + config.build_settings['ENABLE_NS_ASSERTIONS'] = 'YES' + end + end + end end end diff --git a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj index f0d8123263..b6762cc600 100644 --- a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj +++ b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj @@ -11,14 +11,24 @@ 09B76D9585ACE7403804D9DC /* libPods-InteropTestsRemoteWithCronet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E9444C764F0FFF64A7EB58E /* libPods-InteropTestsRemoteWithCronet.a */; }; 0F9232F984C08643FD40C34F /* libPods-InteropTestsRemote.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DBE059B4AC7A51919467EEC0 /* libPods-InteropTestsRemote.a */; }; 16A9E77B6E336B3C0B9BA6E0 /* libPods-InteropTestsLocalSSL.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DBEDE45BDA60DF1E1C8950C0 /* libPods-InteropTestsLocalSSL.a */; }; + 1A0FB7F8C95A2F82538BC950 /* libPods-ChannelTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 48F1841C9A920626995DC28C /* libPods-ChannelTests.a */; }; 20DFDF829DD993A4A00D5662 /* libPods-RxLibraryUnitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A58BE6DF1C62D1739EBB2C78 /* libPods-RxLibraryUnitTests.a */; }; 333E8FC01C8285B7C547D799 /* libPods-InteropTestsLocalCleartext.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD346DB2C23F676C4842F3FF /* libPods-InteropTestsLocalCleartext.a */; }; 5E0282E9215AA697007AC99D /* UnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E0282E8215AA697007AC99D /* UnitTests.m */; }; 5E0282EB215AA697007AC99D /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; }; + 5E3B95A521CAC6C500C0A151 /* APIv2Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E3B95A421CAC6C500C0A151 /* APIv2Tests.m */; }; + 5E7D71AD210954A8001EA6BA /* TestCertificates.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */; }; + 5E7D71B5210B9EC9001EA6BA /* InteropTestsCallOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E7D71B4210B9EC9001EA6BA /* InteropTestsCallOptions.m */; }; + 5E7D71B7210B9EC9001EA6BA /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; }; 5E8A5DA71D3840B4000F8BC4 /* CoreCronetEnd2EndTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5E8A5DA61D3840B4000F8BC4 /* CoreCronetEnd2EndTests.mm */; }; 5E8A5DA91D3840B4000F8BC4 /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; }; 5EAD6D271E27047400002378 /* CronetUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EAD6D261E27047400002378 /* CronetUnitTests.m */; }; 5EAD6D291E27047400002378 /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; }; + 5EB2A2E72107DED300EB4B69 /* ChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EB2A2E62107DED300EB4B69 /* ChannelTests.m */; }; + 5EB2A2E92107DED300EB4B69 /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; }; + 5EB2A2F82109284500EB4B69 /* InteropTestsMultipleChannels.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EB2A2F72109284500EB4B69 /* InteropTestsMultipleChannels.m */; }; + 5EB2A2FA2109284500EB4B69 /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; }; + 5EB5C3AA21656CEA00ADC300 /* ChannelPoolTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EB5C3A921656CEA00ADC300 /* ChannelPoolTest.m */; }; 5EC5E42B2081782C000EF4AD /* InteropTestsRemote.m in Sources */ = {isa = PBXBuildFile; fileRef = 6379CC4F1BE16703001BC0A1 /* InteropTestsRemote.m */; }; 5EC5E42C20817832000EF4AD /* InteropTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */; }; 5EC5E43B208185A7000EF4AD /* InteropTestsLocalCleartext.m in Sources */ = {isa = PBXBuildFile; fileRef = 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */; }; @@ -53,7 +63,10 @@ 63DC84501BE153AA000708E8 /* GRPCClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6312AE4D1B1BF49B00341DEE /* GRPCClientTests.m */; }; 63E240CE1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */; }; 63E240D01B6C63DC005F3B0E /* TestCertificates.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */; }; + 6C1A3F81CCF7C998B4813EFD /* libPods-InteropTestsCallOptions.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AF3FC2CFFE7B0961823BC740 /* libPods-InteropTestsCallOptions.a */; }; + 886717A79EFF774F356798E6 /* libPods-InteropTestsMultipleChannels.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 355D0E30AD224763BC9519F4 /* libPods-InteropTestsMultipleChannels.a */; }; 91D4B3C85B6D8562F409CB48 /* libPods-InteropTestsLocalSSLCFStream.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F3AB031E0E26AC8EF30A2A2A /* libPods-InteropTestsLocalSSLCFStream.a */; }; + 98478C9F42329DF769A45B6C /* libPods-APIv2Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B6AD69CACF67505B0F028E92 /* libPods-APIv2Tests.a */; }; BC111C80CBF7068B62869352 /* libPods-InteropTestsRemoteCFStream.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F44AC3F44E3491A8C0D890FE /* libPods-InteropTestsRemoteCFStream.a */; }; C3D6F4270A2FFF634D8849ED /* libPods-InteropTestsLocalCleartextCFStream.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BDA4BA011779D5D25B5618C /* libPods-InteropTestsLocalCleartextCFStream.a */; }; CCF5C0719EF608276AE16374 /* libPods-UnitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 22A3EBB488699C8CEA19707B /* libPods-UnitTests.a */; }; @@ -68,6 +81,13 @@ remoteGlobalIDString = 635697C61B14FC11007A7283; remoteInfo = Tests; }; + 5E7D71B8210B9EC9001EA6BA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 635697BF1B14FC11007A7283 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 635697C61B14FC11007A7283; + remoteInfo = Tests; + }; 5E8A5DAA1D3840B4000F8BC4 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 635697BF1B14FC11007A7283 /* Project object */; @@ -82,6 +102,20 @@ remoteGlobalIDString = 635697C61B14FC11007A7283; remoteInfo = Tests; }; + 5EB2A2EA2107DED300EB4B69 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 635697BF1B14FC11007A7283 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 635697C61B14FC11007A7283; + remoteInfo = Tests; + }; + 5EB2A2FB2109284500EB4B69 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 635697BF1B14FC11007A7283 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 635697C61B14FC11007A7283; + remoteInfo = Tests; + }; 5EE84BF71D4717E40050C6CC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 635697BF1B14FC11007A7283 /* Project object */; @@ -144,24 +178,33 @@ 0A4F89D9C90E9C30990218F0 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; }; 0BDA4BA011779D5D25B5618C /* libPods-InteropTestsLocalCleartextCFStream.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsLocalCleartextCFStream.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 0D2284C3DF7E57F0ED504E39 /* Pods-CoreCronetEnd2EndTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CoreCronetEnd2EndTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CoreCronetEnd2EndTests/Pods-CoreCronetEnd2EndTests.debug.xcconfig"; sourceTree = "<group>"; }; + 1286B30AD74CB64CD91FB17D /* Pods-APIv2Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-APIv2Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-APIv2Tests/Pods-APIv2Tests.debug.xcconfig"; sourceTree = "<group>"; }; + 1295CCBD1082B4A7CFCED95F /* Pods-InteropTestsMultipleChannels.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsMultipleChannels.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsMultipleChannels/Pods-InteropTestsMultipleChannels.cronet.xcconfig"; sourceTree = "<group>"; }; + 12B238CD1702393C2BA5DE80 /* Pods-APIv2Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-APIv2Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-APIv2Tests/Pods-APIv2Tests.release.xcconfig"; sourceTree = "<group>"; }; 14B09A58FEE53A7A6B838920 /* Pods-InteropTestsLocalSSL.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSL.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL.cronet.xcconfig"; sourceTree = "<group>"; }; 1588C85DEAF7FC0ACDEA4C02 /* Pods-InteropTestsLocalCleartext.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartext.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext.test.xcconfig"; sourceTree = "<group>"; }; 17F60BF2871F6AF85FB3FA12 /* Pods-InteropTestsRemoteWithCronet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteWithCronet.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet.debug.xcconfig"; sourceTree = "<group>"; }; 20DFF2F3C97EF098FE5A3171 /* libPods-Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 22A3EBB488699C8CEA19707B /* libPods-UnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-UnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 2650FEF00956E7924772F9D9 /* Pods-InteropTestsMultipleChannels.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsMultipleChannels.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsMultipleChannels/Pods-InteropTestsMultipleChannels.release.xcconfig"; sourceTree = "<group>"; }; 2B89F3037963E6EDDD48D8C3 /* Pods-InteropTestsRemoteWithCronet.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteWithCronet.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet.test.xcconfig"; sourceTree = "<group>"; }; 303F4A17EB1650FC44603D17 /* Pods-InteropTestsRemoteCFStream.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteCFStream.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteCFStream/Pods-InteropTestsRemoteCFStream.release.xcconfig"; sourceTree = "<group>"; }; 32748C4078AEB05F8F954361 /* Pods-InteropTestsRemoteCFStream.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteCFStream.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteCFStream/Pods-InteropTestsRemoteCFStream.debug.xcconfig"; sourceTree = "<group>"; }; + 355D0E30AD224763BC9519F4 /* libPods-InteropTestsMultipleChannels.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsMultipleChannels.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 386712AEACF7C2190C4B8B3F /* Pods-CronetUnitTests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CronetUnitTests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-CronetUnitTests/Pods-CronetUnitTests.cronet.xcconfig"; sourceTree = "<group>"; }; + 3A98DF08852F60AF1D96481D /* Pods-InteropTestsCallOptions.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsCallOptions.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsCallOptions/Pods-InteropTestsCallOptions.debug.xcconfig"; sourceTree = "<group>"; }; 3B0861FC805389C52DB260D4 /* Pods-RxLibraryUnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxLibraryUnitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxLibraryUnitTests/Pods-RxLibraryUnitTests.release.xcconfig"; sourceTree = "<group>"; }; + 3CADF86203B9D03EA96C359D /* Pods-InteropTestsMultipleChannels.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsMultipleChannels.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsMultipleChannels/Pods-InteropTestsMultipleChannels.debug.xcconfig"; sourceTree = "<group>"; }; 3EB55EF291706E3DDE23C3B7 /* Pods-InteropTestsLocalSSLCFStream.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSLCFStream.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSLCFStream/Pods-InteropTestsLocalSSLCFStream.debug.xcconfig"; sourceTree = "<group>"; }; 3F27B2E744482771EB93C394 /* Pods-InteropTestsRemoteWithCronet.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteWithCronet.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet.cronet.xcconfig"; sourceTree = "<group>"; }; 41AA59529240A6BBBD3DB904 /* Pods-InteropTestsLocalSSLCFStream.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSLCFStream.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSLCFStream/Pods-InteropTestsLocalSSLCFStream.test.xcconfig"; sourceTree = "<group>"; }; + 48F1841C9A920626995DC28C /* libPods-ChannelTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ChannelTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 4A1A42B2E941CCD453489E5B /* Pods-InteropTestsRemoteCFStream.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteCFStream.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteCFStream/Pods-InteropTestsRemoteCFStream.cronet.xcconfig"; sourceTree = "<group>"; }; 4AD97096D13D7416DC91A72A /* Pods-CoreCronetEnd2EndTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CoreCronetEnd2EndTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-CoreCronetEnd2EndTests/Pods-CoreCronetEnd2EndTests.release.xcconfig"; sourceTree = "<group>"; }; 4ADEA1C8BBE10D90940AC68E /* Pods-InteropTestsRemote.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemote.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemote/Pods-InteropTestsRemote.cronet.xcconfig"; sourceTree = "<group>"; }; 51A275E86C141416ED63FF76 /* Pods-InteropTestsLocalCleartext.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartext.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext.release.xcconfig"; sourceTree = "<group>"; }; + 51F2A64B7AADBA1B225B132E /* Pods-APIv2Tests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-APIv2Tests.test.xcconfig"; path = "Pods/Target Support Files/Pods-APIv2Tests/Pods-APIv2Tests.test.xcconfig"; sourceTree = "<group>"; }; 553BBBED24E4162D1F769D65 /* Pods-InteropTestsLocalSSL.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSL.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL.debug.xcconfig"; sourceTree = "<group>"; }; 55B630C1FF8C36D1EFC4E0A4 /* Pods-InteropTestsLocalSSLCFStream.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSLCFStream.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSLCFStream/Pods-InteropTestsLocalSSLCFStream.cronet.xcconfig"; sourceTree = "<group>"; }; 573450F334B331D0BED8B961 /* Pods-CoreCronetEnd2EndTests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CoreCronetEnd2EndTests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-CoreCronetEnd2EndTests/Pods-CoreCronetEnd2EndTests.cronet.xcconfig"; sourceTree = "<group>"; }; @@ -169,6 +212,12 @@ 5E0282E6215AA697007AC99D /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5E0282E8215AA697007AC99D /* UnitTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UnitTests.m; sourceTree = "<group>"; }; 5E0282EA215AA697007AC99D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + 5E3B95A221CAC6C500C0A151 /* APIv2Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = APIv2Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 5E3B95A421CAC6C500C0A151 /* APIv2Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = APIv2Tests.m; sourceTree = "<group>"; }; + 5E3B95A621CAC6C500C0A151 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + 5E7D71B2210B9EC8001EA6BA /* InteropTestsCallOptions.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InteropTestsCallOptions.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 5E7D71B4210B9EC9001EA6BA /* InteropTestsCallOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InteropTestsCallOptions.m; sourceTree = "<group>"; }; + 5E7D71B6210B9EC9001EA6BA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 5E8A5DA41D3840B4000F8BC4 /* CoreCronetEnd2EndTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreCronetEnd2EndTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5E8A5DA61D3840B4000F8BC4 /* CoreCronetEnd2EndTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CoreCronetEnd2EndTests.mm; sourceTree = "<group>"; }; 5EA908CF4CDA4CE218352A06 /* Pods-InteropTestsLocalSSLCFStream.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSLCFStream.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSLCFStream/Pods-InteropTestsLocalSSLCFStream.release.xcconfig"; sourceTree = "<group>"; }; @@ -176,6 +225,13 @@ 5EAD6D261E27047400002378 /* CronetUnitTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CronetUnitTests.m; sourceTree = "<group>"; }; 5EAD6D281E27047400002378 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 5EAFE8271F8EFB87007F2189 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = "<group>"; }; + 5EB2A2E42107DED300EB4B69 /* ChannelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChannelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 5EB2A2E62107DED300EB4B69 /* ChannelTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ChannelTests.m; sourceTree = "<group>"; }; + 5EB2A2E82107DED300EB4B69 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + 5EB2A2F52109284500EB4B69 /* InteropTestsMultipleChannels.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InteropTestsMultipleChannels.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 5EB2A2F72109284500EB4B69 /* InteropTestsMultipleChannels.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InteropTestsMultipleChannels.m; sourceTree = "<group>"; }; + 5EB2A2F92109284500EB4B69 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + 5EB5C3A921656CEA00ADC300 /* ChannelPoolTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ChannelPoolTest.m; sourceTree = "<group>"; }; 5EC5E421208177CC000EF4AD /* InteropTestsRemoteCFStream.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InteropTestsRemoteCFStream.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5EC5E4312081856B000EF4AD /* InteropTestsLocalCleartextCFStream.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InteropTestsLocalCleartextCFStream.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5EC5E442208185CE000EF4AD /* InteropTestsLocalSSLCFStream.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InteropTestsLocalSSLCFStream.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -200,25 +256,34 @@ 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = TestCertificates.bundle; sourceTree = "<group>"; }; 64F68A9A6A63CC930DD30A6E /* Pods-CronetUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CronetUnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CronetUnitTests/Pods-CronetUnitTests.debug.xcconfig"; sourceTree = "<group>"; }; 6793C9D019CB268C5BB491A2 /* Pods-CoreCronetEnd2EndTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CoreCronetEnd2EndTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-CoreCronetEnd2EndTests/Pods-CoreCronetEnd2EndTests.test.xcconfig"; sourceTree = "<group>"; }; + 73D2DF07027835BA0FB0B1C0 /* Pods-InteropTestsCallOptions.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsCallOptions.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsCallOptions/Pods-InteropTestsCallOptions.cronet.xcconfig"; sourceTree = "<group>"; }; 781089FAE980F51F88A3BE0B /* Pods-RxLibraryUnitTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxLibraryUnitTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-RxLibraryUnitTests/Pods-RxLibraryUnitTests.test.xcconfig"; sourceTree = "<group>"; }; 79C68EFFCB5533475D810B79 /* Pods-RxLibraryUnitTests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxLibraryUnitTests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-RxLibraryUnitTests/Pods-RxLibraryUnitTests.cronet.xcconfig"; sourceTree = "<group>"; }; 7A2E97E3F469CC2A758D77DE /* Pods-InteropTestsLocalSSL.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSL.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL.release.xcconfig"; sourceTree = "<group>"; }; 7BA53C6D224288D5870FE6F3 /* Pods-InteropTestsLocalCleartextCFStream.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartextCFStream.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartextCFStream/Pods-InteropTestsLocalCleartextCFStream.release.xcconfig"; sourceTree = "<group>"; }; 8B498B05C6DA0818B2FA91D4 /* Pods-InteropTestsLocalCleartextCFStream.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartextCFStream.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartextCFStream/Pods-InteropTestsLocalCleartextCFStream.cronet.xcconfig"; sourceTree = "<group>"; }; + 8C233E85C3EB45B3CAE52EDF /* Pods-APIv2Tests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-APIv2Tests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-APIv2Tests/Pods-APIv2Tests.cronet.xcconfig"; sourceTree = "<group>"; }; + 90E63AD3C4A1E3E6BC745096 /* Pods-ChannelTests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChannelTests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-ChannelTests/Pods-ChannelTests.cronet.xcconfig"; sourceTree = "<group>"; }; 943138072A9605B5B8DC1FC0 /* Pods-InteropTestsLocalCleartextCFStream.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartextCFStream.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartextCFStream/Pods-InteropTestsLocalCleartextCFStream.debug.xcconfig"; sourceTree = "<group>"; }; 94D7A5FAA13480E9A5166D7A /* Pods-UnitTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.test.xcconfig"; sourceTree = "<group>"; }; 9E9444C764F0FFF64A7EB58E /* libPods-InteropTestsRemoteWithCronet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsRemoteWithCronet.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A25967A0D40ED14B3287AD81 /* Pods-InteropTestsCallOptions.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsCallOptions.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsCallOptions/Pods-InteropTestsCallOptions.test.xcconfig"; sourceTree = "<group>"; }; A2DCF2570BE515B62CB924CA /* Pods-UnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.debug.xcconfig"; sourceTree = "<group>"; }; A58BE6DF1C62D1739EBB2C78 /* libPods-RxLibraryUnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RxLibraryUnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A6F832FCEFA6F6881E620F12 /* Pods-InteropTestsRemote.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemote.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemote/Pods-InteropTestsRemote.test.xcconfig"; sourceTree = "<group>"; }; AA7CB64B4DD9915AE7C03163 /* Pods-InteropTestsLocalCleartext.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartext.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext.cronet.xcconfig"; sourceTree = "<group>"; }; AC414EF7A6BF76ED02B6E480 /* Pods-InteropTestsRemoteWithCronet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteWithCronet.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet.release.xcconfig"; sourceTree = "<group>"; }; + AF3FC2CFFE7B0961823BC740 /* libPods-InteropTestsCallOptions.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsCallOptions.a"; sourceTree = BUILT_PRODUCTS_DIR; }; B226619DC4E709E0FFFF94B8 /* Pods-CronetUnitTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CronetUnitTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-CronetUnitTests/Pods-CronetUnitTests.test.xcconfig"; sourceTree = "<group>"; }; + B6AD69CACF67505B0F028E92 /* libPods-APIv2Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-APIv2Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; B94C27C06733CF98CE1B2757 /* Pods-AllTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AllTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AllTests/Pods-AllTests.debug.xcconfig"; sourceTree = "<group>"; }; + BED74BC8ABF9917C66175879 /* Pods-ChannelTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChannelTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-ChannelTests/Pods-ChannelTests.test.xcconfig"; sourceTree = "<group>"; }; C17F57E5BCB989AB1C2F1F25 /* Pods-InteropTestsRemoteCFStream.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteCFStream.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteCFStream/Pods-InteropTestsRemoteCFStream.test.xcconfig"; sourceTree = "<group>"; }; C6134277D2EB8B380862A03F /* libPods-CronetUnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CronetUnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + C9172F9020E8C97A470D7250 /* Pods-InteropTestsCallOptions.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsCallOptions.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsCallOptions/Pods-InteropTestsCallOptions.release.xcconfig"; sourceTree = "<group>"; }; CAE086D5B470DA367D415AB0 /* libPods-AllTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AllTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; D13BEC8181B8E678A1B52F54 /* Pods-InteropTestsLocalSSL.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSL.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL.test.xcconfig"; sourceTree = "<group>"; }; + D52B92A7108602F170DA8091 /* Pods-ChannelTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChannelTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ChannelTests/Pods-ChannelTests.release.xcconfig"; sourceTree = "<group>"; }; DB1F4391AF69D20D38D74B67 /* Pods-AllTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AllTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-AllTests/Pods-AllTests.test.xcconfig"; sourceTree = "<group>"; }; DBE059B4AC7A51919467EEC0 /* libPods-InteropTestsRemote.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsRemote.a"; sourceTree = BUILT_PRODUCTS_DIR; }; DBEDE45BDA60DF1E1C8950C0 /* libPods-InteropTestsLocalSSL.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsLocalSSL.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -228,9 +293,11 @@ E4275A759BDBDF143B9B438F /* Pods-InteropTestsRemote.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemote.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemote/Pods-InteropTestsRemote.release.xcconfig"; sourceTree = "<group>"; }; E4FD4606D4AB8D5A314D72F0 /* Pods-InteropTestsLocalCleartextCFStream.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartextCFStream.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartextCFStream/Pods-InteropTestsLocalCleartextCFStream.test.xcconfig"; sourceTree = "<group>"; }; E7E4D3FD76E3B745D992AF5F /* Pods-AllTests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AllTests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-AllTests/Pods-AllTests.cronet.xcconfig"; sourceTree = "<group>"; }; + EA8B122ACDE73E3AAA0E4A19 /* Pods-InteropTestsMultipleChannels.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsMultipleChannels.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsMultipleChannels/Pods-InteropTestsMultipleChannels.test.xcconfig"; sourceTree = "<group>"; }; EBFFEC04B514CB0D4922DC40 /* Pods-UnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.release.xcconfig"; sourceTree = "<group>"; }; F3AB031E0E26AC8EF30A2A2A /* libPods-InteropTestsLocalSSLCFStream.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsLocalSSLCFStream.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F44AC3F44E3491A8C0D890FE /* libPods-InteropTestsRemoteCFStream.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsRemoteCFStream.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + F9E48EF5ACB1F38825171C5F /* Pods-ChannelTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChannelTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ChannelTests/Pods-ChannelTests.debug.xcconfig"; sourceTree = "<group>"; }; FBD98AC417B9882D32B19F28 /* libPods-CoreCronetEnd2EndTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CoreCronetEnd2EndTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; FD346DB2C23F676C4842F3FF /* libPods-InteropTestsLocalCleartext.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsLocalCleartext.a"; sourceTree = BUILT_PRODUCTS_DIR; }; FF7B5489BCFE40111D768DD0 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; }; @@ -246,6 +313,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5E3B959F21CAC6C500C0A151 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 98478C9F42329DF769A45B6C /* libPods-APIv2Tests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5E7D71AF210B9EC8001EA6BA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E7D71B7210B9EC9001EA6BA /* libTests.a in Frameworks */, + 6C1A3F81CCF7C998B4813EFD /* libPods-InteropTestsCallOptions.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5E8A5DA11D3840B4000F8BC4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -264,6 +348,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5EB2A2E12107DED300EB4B69 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5EB2A2E92107DED300EB4B69 /* libTests.a in Frameworks */, + 1A0FB7F8C95A2F82538BC950 /* libPods-ChannelTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5EB2A2F22109284500EB4B69 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5EB2A2FA2109284500EB4B69 /* libTests.a in Frameworks */, + 886717A79EFF774F356798E6 /* libPods-InteropTestsMultipleChannels.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5EC5E41E208177CC000EF4AD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -368,7 +470,11 @@ F44AC3F44E3491A8C0D890FE /* libPods-InteropTestsRemoteCFStream.a */, 0BDA4BA011779D5D25B5618C /* libPods-InteropTestsLocalCleartextCFStream.a */, F3AB031E0E26AC8EF30A2A2A /* libPods-InteropTestsLocalSSLCFStream.a */, + 48F1841C9A920626995DC28C /* libPods-ChannelTests.a */, + 355D0E30AD224763BC9519F4 /* libPods-InteropTestsMultipleChannels.a */, + AF3FC2CFFE7B0961823BC740 /* libPods-InteropTestsCallOptions.a */, 22A3EBB488699C8CEA19707B /* libPods-UnitTests.a */, + B6AD69CACF67505B0F028E92 /* libPods-APIv2Tests.a */, ); name = Frameworks; sourceTree = "<group>"; @@ -422,10 +528,26 @@ 41AA59529240A6BBBD3DB904 /* Pods-InteropTestsLocalSSLCFStream.test.xcconfig */, 55B630C1FF8C36D1EFC4E0A4 /* Pods-InteropTestsLocalSSLCFStream.cronet.xcconfig */, 5EA908CF4CDA4CE218352A06 /* Pods-InteropTestsLocalSSLCFStream.release.xcconfig */, + F9E48EF5ACB1F38825171C5F /* Pods-ChannelTests.debug.xcconfig */, + BED74BC8ABF9917C66175879 /* Pods-ChannelTests.test.xcconfig */, + 90E63AD3C4A1E3E6BC745096 /* Pods-ChannelTests.cronet.xcconfig */, + D52B92A7108602F170DA8091 /* Pods-ChannelTests.release.xcconfig */, + 3CADF86203B9D03EA96C359D /* Pods-InteropTestsMultipleChannels.debug.xcconfig */, + EA8B122ACDE73E3AAA0E4A19 /* Pods-InteropTestsMultipleChannels.test.xcconfig */, + 1295CCBD1082B4A7CFCED95F /* Pods-InteropTestsMultipleChannels.cronet.xcconfig */, + 2650FEF00956E7924772F9D9 /* Pods-InteropTestsMultipleChannels.release.xcconfig */, + 3A98DF08852F60AF1D96481D /* Pods-InteropTestsCallOptions.debug.xcconfig */, + A25967A0D40ED14B3287AD81 /* Pods-InteropTestsCallOptions.test.xcconfig */, + 73D2DF07027835BA0FB0B1C0 /* Pods-InteropTestsCallOptions.cronet.xcconfig */, + C9172F9020E8C97A470D7250 /* Pods-InteropTestsCallOptions.release.xcconfig */, A2DCF2570BE515B62CB924CA /* Pods-UnitTests.debug.xcconfig */, 94D7A5FAA13480E9A5166D7A /* Pods-UnitTests.test.xcconfig */, E1E7660656D902104F728892 /* Pods-UnitTests.cronet.xcconfig */, EBFFEC04B514CB0D4922DC40 /* Pods-UnitTests.release.xcconfig */, + 1286B30AD74CB64CD91FB17D /* Pods-APIv2Tests.debug.xcconfig */, + 51F2A64B7AADBA1B225B132E /* Pods-APIv2Tests.test.xcconfig */, + 8C233E85C3EB45B3CAE52EDF /* Pods-APIv2Tests.cronet.xcconfig */, + 12B238CD1702393C2BA5DE80 /* Pods-APIv2Tests.release.xcconfig */, ); name = Pods; sourceTree = "<group>"; @@ -439,6 +561,24 @@ path = UnitTests; sourceTree = "<group>"; }; + 5E3B95A321CAC6C500C0A151 /* APIv2Tests */ = { + isa = PBXGroup; + children = ( + 5E3B95A421CAC6C500C0A151 /* APIv2Tests.m */, + 5E3B95A621CAC6C500C0A151 /* Info.plist */, + ); + path = APIv2Tests; + sourceTree = "<group>"; + }; + 5E7D71B3210B9EC9001EA6BA /* InteropTestsCallOptions */ = { + isa = PBXGroup; + children = ( + 5E7D71B4210B9EC9001EA6BA /* InteropTestsCallOptions.m */, + 5E7D71B6210B9EC9001EA6BA /* Info.plist */, + ); + path = InteropTestsCallOptions; + sourceTree = "<group>"; + }; 5E8A5DA51D3840B4000F8BC4 /* CoreCronetEnd2EndTests */ = { isa = PBXGroup; children = ( @@ -456,6 +596,25 @@ path = CronetUnitTests; sourceTree = "<group>"; }; + 5EB2A2E52107DED300EB4B69 /* ChannelTests */ = { + isa = PBXGroup; + children = ( + 5EB5C3A921656CEA00ADC300 /* ChannelPoolTest.m */, + 5EB2A2E62107DED300EB4B69 /* ChannelTests.m */, + 5EB2A2E82107DED300EB4B69 /* Info.plist */, + ); + path = ChannelTests; + sourceTree = "<group>"; + }; + 5EB2A2F62109284500EB4B69 /* InteropTestsMultipleChannels */ = { + isa = PBXGroup; + children = ( + 5EB2A2F72109284500EB4B69 /* InteropTestsMultipleChannels.m */, + 5EB2A2F92109284500EB4B69 /* Info.plist */, + ); + path = InteropTestsMultipleChannels; + sourceTree = "<group>"; + }; 5EE84BF21D4717E40050C6CC /* InteropTestsRemoteWithCronet */ = { isa = PBXGroup; children = ( @@ -473,7 +632,11 @@ 5E8A5DA51D3840B4000F8BC4 /* CoreCronetEnd2EndTests */, 5EE84BF21D4717E40050C6CC /* InteropTestsRemoteWithCronet */, 5EAD6D251E27047400002378 /* CronetUnitTests */, + 5EB2A2E52107DED300EB4B69 /* ChannelTests */, + 5EB2A2F62109284500EB4B69 /* InteropTestsMultipleChannels */, + 5E7D71B3210B9EC9001EA6BA /* InteropTestsCallOptions */, 5E0282E7215AA697007AC99D /* UnitTests */, + 5E3B95A321CAC6C500C0A151 /* APIv2Tests */, 635697C81B14FC11007A7283 /* Products */, 51E4650F34F854F41FF053B3 /* Pods */, 136D535E19727099B941D7B1 /* Frameworks */, @@ -495,7 +658,11 @@ 5EC5E421208177CC000EF4AD /* InteropTestsRemoteCFStream.xctest */, 5EC5E4312081856B000EF4AD /* InteropTestsLocalCleartextCFStream.xctest */, 5EC5E442208185CE000EF4AD /* InteropTestsLocalSSLCFStream.xctest */, + 5EB2A2E42107DED300EB4B69 /* ChannelTests.xctest */, + 5EB2A2F52109284500EB4B69 /* InteropTestsMultipleChannels.xctest */, + 5E7D71B2210B9EC8001EA6BA /* InteropTestsCallOptions.xctest */, 5E0282E6215AA697007AC99D /* UnitTests.xctest */, + 5E3B95A221CAC6C500C0A151 /* APIv2Tests.xctest */, ); name = Products; sourceTree = "<group>"; @@ -548,6 +715,45 @@ productReference = 5E0282E6215AA697007AC99D /* UnitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 5E3B95A121CAC6C500C0A151 /* APIv2Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5E3B95A721CAC6C500C0A151 /* Build configuration list for PBXNativeTarget "APIv2Tests" */; + buildPhases = ( + EDDD3FA856BCA3443ED36D1E /* [CP] Check Pods Manifest.lock */, + 5E3B959E21CAC6C500C0A151 /* Sources */, + 5E3B959F21CAC6C500C0A151 /* Frameworks */, + 5E3B95A021CAC6C500C0A151 /* Resources */, + C17B826BBD02FDD4A5F355AF /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = APIv2Tests; + productName = APIv2Tests; + productReference = 5E3B95A221CAC6C500C0A151 /* APIv2Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 5E7D71B1210B9EC8001EA6BA /* InteropTestsCallOptions */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5E7D71BA210B9EC9001EA6BA /* Build configuration list for PBXNativeTarget "InteropTestsCallOptions" */; + buildPhases = ( + 2865C6386D677998F861E183 /* [CP] Check Pods Manifest.lock */, + 5E7D71AE210B9EC8001EA6BA /* Sources */, + 5E7D71AF210B9EC8001EA6BA /* Frameworks */, + 5E7D71B0210B9EC8001EA6BA /* Resources */, + 6BA6D00B1816306453BF82D4 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5E7D71B9210B9EC9001EA6BA /* PBXTargetDependency */, + ); + name = InteropTestsCallOptions; + productName = InteropTestsCallOptions; + productReference = 5E7D71B2210B9EC8001EA6BA /* InteropTestsCallOptions.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 5E8A5DA31D3840B4000F8BC4 /* CoreCronetEnd2EndTests */ = { isa = PBXNativeTarget; buildConfigurationList = 5E8A5DAE1D3840B4000F8BC4 /* Build configuration list for PBXNativeTarget "CoreCronetEnd2EndTests" */; @@ -588,6 +794,47 @@ productReference = 5EAD6D241E27047400002378 /* CronetUnitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 5EB2A2E32107DED300EB4B69 /* ChannelTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5EB2A2F02107DED300EB4B69 /* Build configuration list for PBXNativeTarget "ChannelTests" */; + buildPhases = ( + 021B3B1F545989843EBC9A4B /* [CP] Check Pods Manifest.lock */, + 5EB2A2E02107DED300EB4B69 /* Sources */, + 5EB2A2E12107DED300EB4B69 /* Frameworks */, + 5EB2A2E22107DED300EB4B69 /* Resources */, + 1EAF0CBDBFAB18D4DA64535D /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5EB2A2EB2107DED300EB4B69 /* PBXTargetDependency */, + ); + name = ChannelTests; + productName = ChannelTests; + productReference = 5EB2A2E42107DED300EB4B69 /* ChannelTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 5EB2A2F42109284500EB4B69 /* InteropTestsMultipleChannels */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5EB2A3012109284500EB4B69 /* Build configuration list for PBXNativeTarget "InteropTestsMultipleChannels" */; + buildPhases = ( + 8C1ED025E07C4D457C355336 /* [CP] Check Pods Manifest.lock */, + 5EB2A2F12109284500EB4B69 /* Sources */, + 5EB2A2F22109284500EB4B69 /* Frameworks */, + 5EB2A2F32109284500EB4B69 /* Resources */, + 8D685CB612835DB3F7A6F14A /* [CP] Embed Pods Frameworks */, + 0617B5294978A95BEBBFF733 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5EB2A2FC2109284500EB4B69 /* PBXTargetDependency */, + ); + name = InteropTestsMultipleChannels; + productName = InteropTestsMultipleChannels; + productReference = 5EB2A2F52109284500EB4B69 /* InteropTestsMultipleChannels.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 5EC5E420208177CC000EF4AD /* InteropTestsRemoteCFStream */ = { isa = PBXNativeTarget; buildConfigurationList = 5EC5E42A208177CD000EF4AD /* Build configuration list for PBXNativeTarget "InteropTestsRemoteCFStream" */; @@ -796,12 +1043,28 @@ CreatedOnToolsVersion = 9.2; ProvisioningStyle = Automatic; }; + 5E3B95A121CAC6C500C0A151 = { + CreatedOnToolsVersion = 10.0; + ProvisioningStyle = Automatic; + }; + 5E7D71B1210B9EC8001EA6BA = { + CreatedOnToolsVersion = 9.3; + ProvisioningStyle = Automatic; + }; 5E8A5DA31D3840B4000F8BC4 = { CreatedOnToolsVersion = 7.3.1; }; 5EAD6D231E27047400002378 = { CreatedOnToolsVersion = 7.3.1; }; + 5EB2A2E32107DED300EB4B69 = { + CreatedOnToolsVersion = 9.3; + ProvisioningStyle = Automatic; + }; + 5EB2A2F42109284500EB4B69 = { + CreatedOnToolsVersion = 9.3; + ProvisioningStyle = Automatic; + }; 5EC5E420208177CC000EF4AD = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Automatic; @@ -861,7 +1124,11 @@ 5EC5E420208177CC000EF4AD /* InteropTestsRemoteCFStream */, 5EC5E4302081856B000EF4AD /* InteropTestsLocalCleartextCFStream */, 5EC5E441208185CE000EF4AD /* InteropTestsLocalSSLCFStream */, + 5EB2A2E32107DED300EB4B69 /* ChannelTests */, + 5EB2A2F42109284500EB4B69 /* InteropTestsMultipleChannels */, + 5E7D71B1210B9EC8001EA6BA /* InteropTestsCallOptions */, 5E0282E5215AA697007AC99D /* UnitTests */, + 5E3B95A121CAC6C500C0A151 /* APIv2Tests */, ); }; /* End PBXProject section */ @@ -874,6 +1141,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5E3B95A021CAC6C500C0A151 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5E7D71B0210B9EC8001EA6BA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5E8A5DA21D3840B4000F8BC4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -888,6 +1169,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5EB2A2E22107DED300EB4B69 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5EB2A2F32109284500EB4B69 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E7D71AD210954A8001EA6BA /* TestCertificates.bundle in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5EC5E41F208177CC000EF4AD /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -957,6 +1253,60 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 021B3B1F545989843EBC9A4B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ChannelTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 0617B5294978A95BEBBFF733 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsMultipleChannels/Pods-InteropTestsMultipleChannels-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsMultipleChannels/Pods-InteropTestsMultipleChannels-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 1EAF0CBDBFAB18D4DA64535D /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-ChannelTests/Pods-ChannelTests-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ChannelTests/Pods-ChannelTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 252A376345E38FD452A89C3D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -975,6 +1325,24 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 2865C6386D677998F861E183 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-InteropTestsCallOptions-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 31F8D1C407195CBF0C02929B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1083,6 +1451,24 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL-resources.sh\"\n"; showEnvVarsInLog = 0; }; + 6BA6D00B1816306453BF82D4 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsCallOptions/Pods-InteropTestsCallOptions-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsCallOptions/Pods-InteropTestsCallOptions-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 7418AC7B3844B29E48D24FC7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1137,6 +1523,42 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext-resources.sh\"\n"; showEnvVarsInLog = 0; }; + 8C1ED025E07C4D457C355336 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-InteropTestsMultipleChannels-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8D685CB612835DB3F7A6F14A /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsMultipleChannels/Pods-InteropTestsMultipleChannels-frameworks.sh", + "${PODS_ROOT}/CronetFramework/Cronet.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Cronet.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsMultipleChannels/Pods-InteropTestsMultipleChannels-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 914ADDD7106BA9BB8A7E569F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1281,6 +1703,28 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + C17B826BBD02FDD4A5F355AF /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-APIv2Tests/Pods-APIv2Tests-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + ); + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-APIv2Tests/Pods-APIv2Tests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; C2E09DC4BD239F71160F0CC1 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1353,6 +1797,28 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-CoreCronetEnd2EndTests/Pods-CoreCronetEnd2EndTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + EDDD3FA856BCA3443ED36D1E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-APIv2Tests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; F07941C0BAF6A7C67AA60C48 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1418,6 +1884,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5E3B959E21CAC6C500C0A151 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E3B95A521CAC6C500C0A151 /* APIv2Tests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5E7D71AE210B9EC8001EA6BA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E7D71B5210B9EC9001EA6BA /* InteropTestsCallOptions.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5E8A5DA01D3840B4000F8BC4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1434,6 +1916,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5EB2A2E02107DED300EB4B69 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5EB2A2E72107DED300EB4B69 /* ChannelTests.m in Sources */, + 5EB5C3AA21656CEA00ADC300 /* ChannelPoolTest.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5EB2A2F12109284500EB4B69 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5EB2A2F82109284500EB4B69 /* InteropTestsMultipleChannels.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5EC5E41D208177CC000EF4AD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1536,6 +2035,11 @@ target = 635697C61B14FC11007A7283 /* Tests */; targetProxy = 5E0282EC215AA697007AC99D /* PBXContainerItemProxy */; }; + 5E7D71B9210B9EC9001EA6BA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 635697C61B14FC11007A7283 /* Tests */; + targetProxy = 5E7D71B8210B9EC9001EA6BA /* PBXContainerItemProxy */; + }; 5E8A5DAB1D3840B4000F8BC4 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 635697C61B14FC11007A7283 /* Tests */; @@ -1546,6 +2050,16 @@ target = 635697C61B14FC11007A7283 /* Tests */; targetProxy = 5EAD6D2A1E27047400002378 /* PBXContainerItemProxy */; }; + 5EB2A2EB2107DED300EB4B69 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 635697C61B14FC11007A7283 /* Tests */; + targetProxy = 5EB2A2EA2107DED300EB4B69 /* PBXContainerItemProxy */; + }; + 5EB2A2FC2109284500EB4B69 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 635697C61B14FC11007A7283 /* Tests */; + targetProxy = 5EB2A2FB2109284500EB4B69 /* PBXContainerItemProxy */; + }; 5EE84BF81D4717E40050C6CC /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 635697C61B14FC11007A7283 /* Tests */; @@ -1909,6 +2423,271 @@ }; name = Test; }; + 5E3B95A821CAC6C500C0A151 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1286B30AD74CB64CD91FB17D /* Pods-APIv2Tests.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = APIv2Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.APIv2Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 5E3B95A921CAC6C500C0A151 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 51F2A64B7AADBA1B225B132E /* Pods-APIv2Tests.test.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = APIv2Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.APIv2Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Test; + }; + 5E3B95AA21CAC6C500C0A151 /* Cronet */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8C233E85C3EB45B3CAE52EDF /* Pods-APIv2Tests.cronet.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = APIv2Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.APIv2Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Cronet; + }; + 5E3B95AB21CAC6C500C0A151 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 12B238CD1702393C2BA5DE80 /* Pods-APIv2Tests.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = APIv2Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.APIv2Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 5E7D71BB210B9EC9001EA6BA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3A98DF08852F60AF1D96481D /* Pods-InteropTestsCallOptions.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = InteropTestsCallOptions/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsCallOptions; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 5E7D71BC210B9EC9001EA6BA /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A25967A0D40ED14B3287AD81 /* Pods-InteropTestsCallOptions.test.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = InteropTestsCallOptions/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsCallOptions; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Test; + }; + 5E7D71BD210B9EC9001EA6BA /* Cronet */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 73D2DF07027835BA0FB0B1C0 /* Pods-InteropTestsCallOptions.cronet.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = InteropTestsCallOptions/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsCallOptions; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Cronet; + }; + 5E7D71BE210B9EC9001EA6BA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C9172F9020E8C97A470D7250 /* Pods-InteropTestsCallOptions.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = InteropTestsCallOptions/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsCallOptions; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 5E8A5DAC1D3840B4000F8BC4 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 0D2284C3DF7E57F0ED504E39 /* Pods-CoreCronetEnd2EndTests.debug.xcconfig */; @@ -2011,6 +2790,266 @@ }; name = Release; }; + 5EB2A2EC2107DED300EB4B69 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F9E48EF5ACB1F38825171C5F /* Pods-ChannelTests.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = ChannelTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.ChannelTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 5EB2A2ED2107DED300EB4B69 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BED74BC8ABF9917C66175879 /* Pods-ChannelTests.test.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = ChannelTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.ChannelTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Test; + }; + 5EB2A2EE2107DED300EB4B69 /* Cronet */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 90E63AD3C4A1E3E6BC745096 /* Pods-ChannelTests.cronet.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = ChannelTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.ChannelTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Cronet; + }; + 5EB2A2EF2107DED300EB4B69 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D52B92A7108602F170DA8091 /* Pods-ChannelTests.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = ChannelTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.ChannelTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 5EB2A2FD2109284500EB4B69 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3CADF86203B9D03EA96C359D /* Pods-InteropTestsMultipleChannels.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = InteropTestsMultipleChannels/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsMultipleChannels; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 5EB2A2FE2109284500EB4B69 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EA8B122ACDE73E3AAA0E4A19 /* Pods-InteropTestsMultipleChannels.test.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = InteropTestsMultipleChannels/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsMultipleChannels; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Test; + }; + 5EB2A2FF2109284500EB4B69 /* Cronet */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1295CCBD1082B4A7CFCED95F /* Pods-InteropTestsMultipleChannels.cronet.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = InteropTestsMultipleChannels/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsMultipleChannels; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Cronet; + }; + 5EB2A3002109284500EB4B69 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2650FEF00956E7924772F9D9 /* Pods-InteropTestsMultipleChannels.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = InteropTestsMultipleChannels/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsMultipleChannels; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 5EC3C7A01D4FC18C000330E2 /* Cronet */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2039,6 +3078,8 @@ "DEBUG=1", "$(inherited)", "HOST_PORT_REMOTE=$(HOST_PORT_REMOTE)", + "HOST_PORT_LOCALSSL=$(HOST_PORT_LOCALSSL)", + "HOST_PORT_LOCAL=$(HOST_PORT_LOCAL)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -2859,6 +3900,28 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 5E3B95A721CAC6C500C0A151 /* Build configuration list for PBXNativeTarget "APIv2Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5E3B95A821CAC6C500C0A151 /* Debug */, + 5E3B95A921CAC6C500C0A151 /* Test */, + 5E3B95AA21CAC6C500C0A151 /* Cronet */, + 5E3B95AB21CAC6C500C0A151 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5E7D71BA210B9EC9001EA6BA /* Build configuration list for PBXNativeTarget "InteropTestsCallOptions" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5E7D71BB210B9EC9001EA6BA /* Debug */, + 5E7D71BC210B9EC9001EA6BA /* Test */, + 5E7D71BD210B9EC9001EA6BA /* Cronet */, + 5E7D71BE210B9EC9001EA6BA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 5E8A5DAE1D3840B4000F8BC4 /* Build configuration list for PBXNativeTarget "CoreCronetEnd2EndTests" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2881,6 +3944,28 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 5EB2A2F02107DED300EB4B69 /* Build configuration list for PBXNativeTarget "ChannelTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5EB2A2EC2107DED300EB4B69 /* Debug */, + 5EB2A2ED2107DED300EB4B69 /* Test */, + 5EB2A2EE2107DED300EB4B69 /* Cronet */, + 5EB2A2EF2107DED300EB4B69 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5EB2A3012109284500EB4B69 /* Build configuration list for PBXNativeTarget "InteropTestsMultipleChannels" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5EB2A2FD2109284500EB4B69 /* Debug */, + 5EB2A2FE2109284500EB4B69 /* Test */, + 5EB2A2FF2109284500EB4B69 /* Cronet */, + 5EB2A3002109284500EB4B69 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 5EC5E42A208177CD000EF4AD /* Build configuration list for PBXNativeTarget "InteropTestsRemoteCFStream" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/APIv2Tests.xcscheme b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/APIv2Tests.xcscheme new file mode 100644 index 0000000000..e0d1d1fdca --- /dev/null +++ b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/APIv2Tests.xcscheme @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1000" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "NO" + buildForArchiving = "NO" + buildForAnalyzing = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "5E3B95A121CAC6C500C0A151" + BuildableName = "APIv2Tests.xctest" + BlueprintName = "APIv2Tests" + ReferencedContainer = "container:Tests.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Test" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "5E3B95A121CAC6C500C0A151" + BuildableName = "APIv2Tests.xctest" + BlueprintName = "APIv2Tests" + ReferencedContainer = "container:Tests.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + <AdditionalOptions> + </AdditionalOptions> + </TestAction> + <LaunchAction + buildConfiguration = "Test" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "5E3B95A121CAC6C500C0A151" + BuildableName = "APIv2Tests.xctest" + BlueprintName = "APIv2Tests" + ReferencedContainer = "container:Tests.xcodeproj"> + </BuildableReference> + </MacroExpansion> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "5E3B95A121CAC6C500C0A151" + BuildableName = "APIv2Tests.xctest" + BlueprintName = "APIv2Tests" + ReferencedContainer = "container:Tests.xcodeproj"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/ChannelTests.xcscheme b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/ChannelTests.xcscheme new file mode 100644 index 0000000000..acae965bed --- /dev/null +++ b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/ChannelTests.xcscheme @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0930" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "NO" + buildForArchiving = "NO" + buildForAnalyzing = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "5EB2A2E32107DED300EB4B69" + BuildableName = "ChannelTests.xctest" + BlueprintName = "ChannelTests" + ReferencedContainer = "container:Tests.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "5EB2A2E32107DED300EB4B69" + BuildableName = "ChannelTests.xctest" + BlueprintName = "ChannelTests" + ReferencedContainer = "container:Tests.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + <AdditionalOptions> + </AdditionalOptions> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "5EB2A2E32107DED300EB4B69" + BuildableName = "ChannelTests.xctest" + BlueprintName = "ChannelTests" + ReferencedContainer = "container:Tests.xcodeproj"> + </BuildableReference> + </MacroExpansion> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "5EB2A2E32107DED300EB4B69" + BuildableName = "ChannelTests.xctest" + BlueprintName = "ChannelTests" + ReferencedContainer = "container:Tests.xcodeproj"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsCallOptions.xcscheme b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsCallOptions.xcscheme new file mode 100644 index 0000000000..dd83282190 --- /dev/null +++ b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsCallOptions.xcscheme @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0930" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + </BuildAction> + <TestAction + buildConfiguration = "Test" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "5E7D71B1210B9EC8001EA6BA" + BuildableName = "InteropTestsCallOptions.xctest" + BlueprintName = "InteropTestsCallOptions" + ReferencedContainer = "container:Tests.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + <AdditionalOptions> + </AdditionalOptions> + </TestAction> + <LaunchAction + buildConfiguration = "Test" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsLocalCleartext.xcscheme b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsLocalCleartext.xcscheme index 510115fc75..11b41c9214 100644 --- a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsLocalCleartext.xcscheme +++ b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsLocalCleartext.xcscheme @@ -26,7 +26,6 @@ buildConfiguration = "Test" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" shouldUseLaunchSchemeArgsEnv = "YES"> <Testables> <TestableReference @@ -40,12 +39,6 @@ </BuildableReference> <SkippedTests> <Test - Identifier = "GRPCClientTests/testConnectionToRemoteServer"> - </Test> - <Test - Identifier = "GRPCClientTests/testMetadata"> - </Test> - <Test Identifier = "InteropTests"> </Test> </SkippedTests> @@ -58,7 +51,6 @@ buildConfiguration = "Test" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsMultipleChannels.xcscheme b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsMultipleChannels.xcscheme new file mode 100644 index 0000000000..1b4c1f5e51 --- /dev/null +++ b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsMultipleChannels.xcscheme @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0930" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + </BuildAction> + <TestAction + buildConfiguration = "Cronet" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "5EB2A2F42109284500EB4B69" + BuildableName = "InteropTestsMultipleChannels.xctest" + BlueprintName = "InteropTestsMultipleChannels" + ReferencedContainer = "container:Tests.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + <AdditionalOptions> + </AdditionalOptions> + </TestAction> + <LaunchAction + buildConfiguration = "Cronet" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsRemote.xcscheme b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsRemote.xcscheme index 48837e57f9..412bf6a014 100644 --- a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsRemote.xcscheme +++ b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsRemote.xcscheme @@ -26,7 +26,6 @@ buildConfiguration = "Test" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" shouldUseLaunchSchemeArgsEnv = "YES"> <Testables> <TestableReference @@ -61,7 +60,6 @@ buildConfiguration = "Test" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/src/objective-c/tests/run_tests.sh b/src/objective-c/tests/run_tests.sh index f37c6cf9f6..f6fea96920 100755 --- a/src/objective-c/tests/run_tests.sh +++ b/src/objective-c/tests/run_tests.sh @@ -173,4 +173,26 @@ xcodebuild \ | egrep -v '^$' \ | egrep -v "(GPBDictionary|GPBArray)" - +echo "TIME: $(date)" +xcodebuild \ + -workspace Tests.xcworkspace \ + -scheme ChannelTests \ + -destination name="iPhone 8" \ + test \ + | egrep -v "$XCODEBUILD_FILTER" \ + | egrep -v '^$' \ + | egrep -v "(GPBDictionary|GPBArray)" - + +echo "TIME: $(date)" +xcodebuild \ + -workspace Tests.xcworkspace \ + -scheme APIv2Tests \ + -destination name="iPhone 8" \ + HOST_PORT_LOCAL=localhost:5050 \ + HOST_PORT_REMOTE=grpc-test.sandbox.googleapis.com \ + test \ + | egrep -v "$XCODEBUILD_FILTER" \ + | egrep -v '^$' \ + | egrep -v "(GPBDictionary|GPBArray)" - + exit 0 diff --git a/src/objective-c/tests/version.h b/src/objective-c/tests/version.h index f2fd692070..54f95ad16a 100644 --- a/src/objective-c/tests/version.h +++ b/src/objective-c/tests/version.h @@ -22,5 +22,5 @@ // instead. This file can be regenerated from the template by running // `tools/buildgen/generate_projects.sh`. -#define GRPC_OBJC_VERSION_STRING @"1.18.0-dev" +#define GRPC_OBJC_VERSION_STRING @"1.19.0-dev" #define GRPC_C_VERSION_STRING @"7.0.0-dev" |