diff options
64 files changed, 5419 insertions, 692 deletions
diff --git a/gRPC.podspec b/gRPC.podspec index 5e513cb127..47a130d12d 100644 --- a/gRPC.podspec +++ b/gRPC.podspec @@ -58,7 +58,7 @@ Pod::Spec.new do |s| ss.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}" ss.exclude_files = "#{src_dir}/GRPCCall+GID.{h,m}" - ss.private_header_files = "#{src_dir}/private/*.h" + ss.private_header_files = "#{src_dir}/private/*.h", "#{src_dir}/internal/GRPCCallOptions+Internal.h" ss.dependency 'gRPC-Core', version end diff --git a/include/grpc/impl/codegen/grpc_types.h b/include/grpc/impl/codegen/grpc_types.h index 17a43fab0f..402e490e34 100644 --- a/include/grpc/impl/codegen/grpc_types.h +++ b/include/grpc/impl/codegen/grpc_types.h @@ -350,6 +350,10 @@ typedef struct { /** If set, inhibits health checking (which may be enabled via the * service config.) */ #define GRPC_ARG_INHIBIT_HEALTH_CHECKING "grpc.inhibit_health_checking" +/** gRPC Objective-C channel pooling domain string. */ +#define GRPC_ARG_CHANNEL_POOL_DOMAIN "grpc.channel_pooling_domain" +/** gRPC Objective-C channel pooling id. */ +#define GRPC_ARG_CHANNEL_ID "grpc.channel_id" /** \} */ /** Result of a grpc call. If the caller satisfies the prerequisites of a diff --git a/src/compiler/objective_c_generator.cc b/src/compiler/objective_c_generator.cc index 39f68cb956..0a6b64f595 100644 --- a/src/compiler/objective_c_generator.cc +++ b/src/compiler/objective_c_generator.cc @@ -113,6 +113,29 @@ void PrintAdvancedSignature(Printer* printer, const MethodDescriptor* method, PrintMethodSignature(printer, method, vars); } +void PrintV2Signature(Printer* printer, const MethodDescriptor* method, + map< ::grpc::string, ::grpc::string> vars) { + if (method->client_streaming()) { + vars["return_type"] = "GRPCStreamingProtoCall *"; + } else { + vars["return_type"] = "GRPCUnaryProtoCall *"; + } + vars["method_name"] = + grpc_generator::LowercaseFirstLetter(vars["method_name"]); + + PrintAllComments(method, printer); + + printer->Print(vars, "- ($return_type$)$method_name$With"); + if (method->client_streaming()) { + printer->Print("ResponseHandler:(id<GRPCProtoResponseHandler>)handler"); + } else { + printer->Print(vars, + "Message:($request_class$ *)message " + "responseHandler:(id<GRPCProtoResponseHandler>)handler"); + } + printer->Print(" callOptions:(GRPCCallOptions *_Nullable)callOptions"); +} + inline map< ::grpc::string, ::grpc::string> GetMethodVars( const MethodDescriptor* method) { map< ::grpc::string, ::grpc::string> res; @@ -135,6 +158,16 @@ void PrintMethodDeclarations(Printer* printer, const MethodDescriptor* method) { printer->Print(";\n\n\n"); } +void PrintV2MethodDeclarations(Printer* printer, + const MethodDescriptor* method) { + map< ::grpc::string, ::grpc::string> vars = GetMethodVars(method); + + PrintProtoRpcDeclarationAsPragma(printer, method, vars); + + PrintV2Signature(printer, method, vars); + printer->Print(";\n\n"); +} + void PrintSimpleImplementation(Printer* printer, const MethodDescriptor* method, map< ::grpc::string, ::grpc::string> vars) { printer->Print("{\n"); @@ -177,6 +210,25 @@ void PrintAdvancedImplementation(Printer* printer, printer->Print("}\n"); } +void PrintV2Implementation(Printer* printer, const MethodDescriptor* method, + map< ::grpc::string, ::grpc::string> vars) { + printer->Print(" {\n"); + if (method->client_streaming()) { + printer->Print(vars, " return [self RPCToMethod:@\"$method_name$\"\n"); + printer->Print(" responseHandler:handler\n"); + printer->Print(" callOptions:callOptions\n"); + printer->Print( + vars, " responseClass:[$response_class$ class]];\n}\n\n"); + } else { + printer->Print(vars, " return [self RPCToMethod:@\"$method_name$\"\n"); + printer->Print(" message:message\n"); + printer->Print(" responseHandler:handler\n"); + printer->Print(" callOptions:callOptions\n"); + printer->Print( + vars, " responseClass:[$response_class$ class]];\n}\n\n"); + } +} + void PrintMethodImplementations(Printer* printer, const MethodDescriptor* method) { map< ::grpc::string, ::grpc::string> vars = GetMethodVars(method); @@ -184,12 +236,16 @@ void PrintMethodImplementations(Printer* printer, PrintProtoRpcDeclarationAsPragma(printer, method, vars); // TODO(jcanizales): Print documentation from the method. + printer->Print("// Deprecated methods.\n"); PrintSimpleSignature(printer, method, vars); PrintSimpleImplementation(printer, method, vars); printer->Print("// Returns a not-yet-started RPC object.\n"); PrintAdvancedSignature(printer, method, vars); PrintAdvancedImplementation(printer, method, vars); + + PrintV2Signature(printer, method, vars); + PrintV2Implementation(printer, method, vars); } } // namespace @@ -231,6 +287,25 @@ void PrintMethodImplementations(Printer* printer, return output; } +::grpc::string GetV2Protocol(const ServiceDescriptor* service) { + ::grpc::string output; + + // Scope the output stream so it closes and finalizes output to the string. + grpc::protobuf::io::StringOutputStream output_stream(&output); + Printer printer(&output_stream, '$'); + + map< ::grpc::string, ::grpc::string> vars = { + {"service_class", ServiceClassName(service) + "2"}}; + + printer.Print(vars, "@protocol $service_class$ <NSObject>\n\n"); + for (int i = 0; i < service->method_count(); i++) { + PrintV2MethodDeclarations(&printer, service->method(i)); + } + printer.Print("@end\n\n"); + + return output; +} + ::grpc::string GetInterface(const ServiceDescriptor* service) { ::grpc::string output; @@ -248,10 +323,16 @@ void PrintMethodImplementations(Printer* printer, " */\n"); printer.Print(vars, "@interface $service_class$ :" - " GRPCProtoService<$service_class$>\n"); + " GRPCProtoService<$service_class$, $service_class$2>\n"); printer.Print( - "- (instancetype)initWithHost:(NSString *)host" + "- (instancetype)initWithHost:(NSString *)host " + "callOptions:(GRPCCallOptions " + "*_Nullable)callOptions" " NS_DESIGNATED_INITIALIZER;\n"); + printer.Print("- (instancetype)initWithHost:(NSString *)host;\n"); + printer.Print( + "+ (instancetype)serviceWithHost:(NSString *)host " + "callOptions:(GRPCCallOptions *_Nullable)callOptions;\n"); printer.Print("+ (instancetype)serviceWithHost:(NSString *)host;\n"); printer.Print("@end\n"); @@ -273,11 +354,19 @@ void PrintMethodImplementations(Printer* printer, printer.Print(vars, "@implementation $service_class$\n\n" "// Designated initializer\n" - "- (instancetype)initWithHost:(NSString *)host {\n" + "- (instancetype)initWithHost:(NSString *)host " + "callOptions:(GRPCCallOptions *_Nullable)callOptions{\n" " self = [super initWithHost:host\n" " packageName:@\"$package$\"\n" - " serviceName:@\"$service_name$\"];\n" + " serviceName:@\"$service_name$\"\n" + " callOptions:callOptions];\n" " return self;\n" + "}\n\n" + "- (instancetype)initWithHost:(NSString *)host {\n" + " return [self initWithHost:host\n" + " packageName:@\"$package$\"\n" + " serviceName:@\"$service_name$\"\n" + " callOptions:nil];\n" "}\n\n"); printer.Print( @@ -292,7 +381,11 @@ void PrintMethodImplementations(Printer* printer, printer.Print( "#pragma mark - Class Methods\n\n" "+ (instancetype)serviceWithHost:(NSString *)host {\n" - " return [[self alloc] initWithHost:host];\n" + " return [self serviceWithHost:host callOptions:nil];\n" + "}\n\n" + "+ (instancetype)serviceWithHost:(NSString *)host " + "callOptions:(GRPCCallOptions *_Nullable)callOptions {\n" + " return [[self alloc] initWithHost:host callOptions:callOptions];\n" "}\n\n"); printer.Print("#pragma mark - Method Implementations\n\n"); diff --git a/src/compiler/objective_c_generator.h b/src/compiler/objective_c_generator.h index eb1c7ff005..c171e5bf77 100644 --- a/src/compiler/objective_c_generator.h +++ b/src/compiler/objective_c_generator.h @@ -32,9 +32,14 @@ using ::grpc::string; string GetAllMessageClasses(const FileDescriptor* file); // Returns the content to be included defining the @protocol segment at the -// insertion point of the generated implementation file. +// insertion point of the generated implementation file. This interface is +// legacy and for backwards compatibility. string GetProtocol(const ServiceDescriptor* service); +// Returns the content to be included defining the @protocol segment at the +// insertion point of the generated implementation file. +string GetV2Protocol(const ServiceDescriptor* service); + // Returns the content to be included defining the @interface segment at the // insertion point of the generated implementation file. string GetInterface(const ServiceDescriptor* service); diff --git a/src/compiler/objective_c_plugin.cc b/src/compiler/objective_c_plugin.cc index f0fe3688cc..87977095d0 100644 --- a/src/compiler/objective_c_plugin.cc +++ b/src/compiler/objective_c_plugin.cc @@ -93,7 +93,13 @@ class ObjectiveCGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { SystemImport("RxLibrary/GRXWriteable.h") + SystemImport("RxLibrary/GRXWriter.h"); - ::grpc::string forward_declarations = "@class GRPCProtoCall;\n\n"; + ::grpc::string forward_declarations = + "@class GRPCProtoCall;\n" + "@class GRPCUnaryProtoCall;\n" + "@class GRPCStreamingProtoCall;\n" + "@class GRPCCallOptions;\n" + "@protocol GRPCProtoResponseHandler;\n" + "\n"; ::grpc::string class_declarations = grpc_objective_c_generator::GetAllMessageClasses(file); @@ -103,6 +109,12 @@ class ObjectiveCGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { class_imports += ImportProtoHeaders(file->dependency(i), " "); } + ::grpc::string ng_protocols; + for (int i = 0; i < file->service_count(); i++) { + const grpc::protobuf::ServiceDescriptor* service = file->service(i); + ng_protocols += grpc_objective_c_generator::GetV2Protocol(service); + } + ::grpc::string protocols; for (int i = 0; i < file->service_count(); i++) { const grpc::protobuf::ServiceDescriptor* service = file->service(i); @@ -120,9 +132,10 @@ class ObjectiveCGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { PreprocIfNot(kProtocolOnly, system_imports) + "\n" + class_declarations + "\n" + PreprocIfNot(kForwardDeclare, class_imports) + "\n" + - forward_declarations + "\n" + kNonNullBegin + "\n" + protocols + - "\n" + PreprocIfNot(kProtocolOnly, interfaces) + "\n" + - kNonNullEnd + "\n"); + forward_declarations + "\n" + kNonNullBegin + "\n" + + ng_protocols + protocols + "\n" + + PreprocIfNot(kProtocolOnly, interfaces) + "\n" + kNonNullEnd + + "\n"); } { diff --git a/src/core/ext/transport/cronet/client/secure/cronet_channel_create.cc b/src/core/ext/transport/cronet/client/secure/cronet_channel_create.cc index 40a30e4a31..dffb61b082 100644 --- a/src/core/ext/transport/cronet/client/secure/cronet_channel_create.cc +++ b/src/core/ext/transport/cronet/client/secure/cronet_channel_create.cc @@ -46,9 +46,19 @@ GRPCAPI grpc_channel* grpc_cronet_secure_channel_create( "grpc_create_cronet_transport: stream_engine = %p, target=%s", engine, target); + // Disable client authority filter when using Cronet + grpc_arg arg; + arg.key = const_cast<char*>(GRPC_ARG_DISABLE_CLIENT_AUTHORITY_FILTER); + arg.type = GRPC_ARG_INTEGER; + arg.value.integer = 1; + grpc_channel_args* new_args = grpc_channel_args_copy_and_add(args, &arg, 1); + grpc_transport* ct = - grpc_create_cronet_transport(engine, target, args, reserved); + grpc_create_cronet_transport(engine, target, new_args, reserved); grpc_core::ExecCtx exec_ctx; - return grpc_channel_create(target, args, GRPC_CLIENT_DIRECT_CHANNEL, ct); + grpc_channel* channel = + grpc_channel_create(target, new_args, GRPC_CLIENT_DIRECT_CHANNEL, ct); + grpc_channel_args_destroy(new_args); + return channel; } 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..d01d0c0d4f 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/GRPCChannel.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]; + [GRPCChannel closeOpenConnections]; } + (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..3054bfc5a7 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, 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 e0ef8b1391..a1f139fc18 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,146 @@ 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> + +@optional /** - * Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by - * the server. + * Issued when initial metadata is received from the server. The task must be scheduled onto the + * dispatch queue in property \a dispatchQueue. + */ +- (void)receivedInitialMetadata:(NSDictionary *_Nullable)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. The task must be scheduled onto the + * dispatch queue in property \a dispatchQueue. + */ +- (void)receivedRawMessage:(NSData *_Nullable)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. The task must be scheduled onto the dispatch queue in property + * \a dispatchQueue. + */ +- (void)closedWithTrailingMetadata:(NSDictionary *_Nullable)trailingMetadata + error:(NSError *_Nullable)error; + +@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. */ -extern id const kGRPCHeadersKey; -extern id const kGRPCTrailersKey; +@property(atomic, readonly) dispatch_queue_t dispatchQueue; + +@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. + */ +@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:(GRPCCallOptions *_Nullable)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 should only be called once; additional calls will be discarded. */ -@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 closedWithTrailingMetadata: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. + */ +- (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 + +/** + * This interface is deprecated. Please use \a GRPCcall2. + * + * Represents a single gRPC remote call. + */ +@interface GRPCCall : GRXWriter /** * The container of the request headers of an RPC conforms to this protocol, which is a subset of @@ -201,7 +309,7 @@ extern id const kGRPCTrailersKey; * * The property is initialized to an empty NSMutableDictionary. */ -@property(atomic, readonly) NSMutableDictionary *requestHeaders; +@property(null_unspecified, atomic, readonly) NSMutableDictionary *requestHeaders; /** * This dictionary is populated with the HTTP headers received from the server. This happens before @@ -212,7 +320,7 @@ extern id const kGRPCTrailersKey; * The value of this property is nil until all response headers are received, and will change before * any of -writeValue: or -writesFinishedWithError: are sent to the writeable. */ -@property(atomic, readonly) NSDictionary *responseHeaders; +@property(null_unspecified, atomic, readonly) NSDictionary *responseHeaders; /** * Same as responseHeaders, but populated with the HTTP trailers received from the server before the @@ -221,7 +329,7 @@ extern id const kGRPCTrailersKey; * The value of this property is nil until all response trailers are received, and will change * before -writesFinishedWithError: is sent to the writeable. */ -@property(atomic, readonly) NSDictionary *responseTrailers; +@property(null_unspecified, atomic, readonly) NSDictionary *responseTrailers; /** * The request writer has to write NSData objects into the provided Writeable. The server will @@ -234,9 +342,9 @@ extern id const kGRPCTrailersKey; * host parameter should not contain the scheme (http:// or https://), only the name or IP addr * and the port number, for example @"localhost:5050". */ -- (instancetype)initWithHost:(NSString *)host - path:(NSString *)path - requestsWriter:(GRXWriter *)requestsWriter NS_DESIGNATED_INITIALIZER; +- (instancetype _Null_unspecified)initWithHost:(NSString *_Null_unspecified)host + path:(NSString *_Null_unspecified)path + requestsWriter:(GRXWriter *_Null_unspecified)requestWriter; /** * Finishes the request side of this call, notifies the server that the RPC should be cancelled, and @@ -245,21 +353,15 @@ 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. - * - * This configuration is only effective before the call starts. - */ -- (void)setResponseDispatchQueue:(dispatch_queue_t)queue; ++ (void)setCallSafety:(GRPCCallSafety)callSafety + host:(NSString *_Null_unspecified)host + path:(NSString *_Null_unspecified)path; +@property(null_unspecified, atomic, copy, readwrite) NSString *serverName; +@property NSTimeInterval timeout; +- (void)setResponseDispatchQueue:(dispatch_queue_t _Null_unspecified)queue; -// TODO(jcanizales): Let specify a deadline. As a category of GRXWriter? @end #pragma mark Backwards compatibiity @@ -269,11 +371,11 @@ DEPRECATED_MSG_ATTRIBUTE("Use NSDictionary or NSMutableDictionary instead.") @protocol GRPCRequestHeaders<NSObject> @property(nonatomic, readonly) NSUInteger count; -- (id)objectForKeyedSubscript:(id)key; -- (void)setObject:(id)obj forKeyedSubscript:(id)key; +- (id _Null_unspecified)objectForKeyedSubscript:(id _Null_unspecified)key; +- (void)setObject:(id _Null_unspecified)obj forKeyedSubscript:(id _Null_unspecified)key; - (void)removeAllObjects; -- (void)removeObjectForKey:(id)key; +- (void)removeObjectForKey:(id _Null_unspecified)key; @end #pragma clang diagnostic push diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m index 084fbdeb49..85a2837336 100644 --- a/src/objective-c/GRPCClient/GRPCCall.m +++ b/src/objective-c/GRPCClient/GRPCCall.m @@ -20,11 +20,14 @@ #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/GRPCConnectivityMonitor.h" #import "private/GRPCHost.h" #import "private/GRPCRequestHeaders.h" @@ -52,6 +55,221 @@ 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 { + 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 _dispatcheQueue. + + /** + * 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; +} + +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + responseHandler:(id<GRPCResponseHandler>)responseHandler + callOptions:(GRPCCallOptions *_Nullable)callOptions { + if (requestOptions.host.length == 0 || requestOptions.path.length == 0) { + [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."]; + } + if (requestOptions.safety > GRPCCallSafetyCacheableRequest) { + [NSException raise:NSInvalidArgumentException format:@"Invalid call safety value."]; + } + if (responseHandler == nil) { + [NSException raise:NSInvalidArgumentException format:@"Response handler required."]; + } + + if ((self = [super init])) { + _requestOptions = [requestOptions copy]; + _callOptions = [callOptions copy]; + _handler = responseHandler; + _initialMetadataPublished = NO; + _pipe = [GRXBufferedPipe pipe]; + if (@available(iOS 8.0, *)) { + _dispatchQueue = dispatch_queue_create( + NULL, + dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1)); + } else { + // Fallback on earlier versions + _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + } + dispatch_set_target_queue(responseHandler.dispatchQueue, _dispatchQueue); + _started = NO; + } + + return self; +} + +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + responseHandler:(id<GRPCResponseHandler>)responseHandler { + return + [self initWithRequestOptions:requestOptions responseHandler:responseHandler callOptions:nil]; +} + +- (void)start { + dispatch_async(_dispatchQueue, ^{ + if (self->_started) { + return; + } + self->_started = YES; + if (!self->_callOptions) { + self->_callOptions = [[GRPCCallOptions alloc] init]; + } + + self->_call = [[GRPCCall alloc] initWithHost:self->_requestOptions.host + path:self->_requestOptions.path + callSafety:self->_requestOptions.safety + requestsWriter:self->_pipe + callOptions:self->_callOptions]; + if (self->_callOptions.initialMetadata) { + [self->_call.requestHeaders addEntriesFromDictionary:self->_callOptions.initialMetadata]; + } + id<GRXWriteable> responseWriteable = [[GRXWriteable alloc] initWithValueHandler:^(id value) { + dispatch_async(self->_dispatchQueue, ^{ + if (self->_handler) { + NSDictionary *headers = nil; + if (!self->_initialMetadataPublished) { + headers = self->_call.responseHeaders; + self->_initialMetadataPublished = YES; + } + if (headers) { + [self issueInitialMetadata:headers]; + } + if (value) { + [self issueMessage:value]; + } + } + }); + } + completionHandler:^(NSError *errorOrNil) { + dispatch_async(self->_dispatchQueue, ^{ + if (self->_handler) { + NSDictionary *headers = nil; + if (!self->_initialMetadataPublished) { + headers = self->_call.responseHeaders; + self->_initialMetadataPublished = YES; + } + if (headers) { + [self issueInitialMetadata:headers]; + } + [self issueClosedWithTrailingMetadata:self->_call.responseTrailers error:errorOrNil]; + + // Clean up _handler so that no more responses are reported to the handler. + self->_handler = nil; + + if (self->_call) { + [self->_pipe writesFinishedWithError:nil]; + self->_call = nil; + self->_pipe = nil; + } + } + }); + }]; + [self->_call startWithWriteable:responseWriteable]; + }); +} + +- (void)cancel { + dispatch_async(_dispatchQueue, ^{ + if (self->_call) { + [self->_call cancel]; + self->_call = nil; + self->_pipe = nil; + } + if (self->_handler) { + id<GRPCResponseHandler> handler = self->_handler; + dispatch_async(handler.dispatchQueue, ^{ + if ([handler respondsToSelector:@selector(closedWithTrailingMetadata:error:)]) { + [handler closedWithTrailingMetadata:nil + error:[NSError errorWithDomain:kGRPCErrorDomain + code:GRPCErrorCodeCancelled + userInfo:@{ + NSLocalizedDescriptionKey : + @"Canceled by app" + }]]; + } + }); + + // Clean up _handler so that no more responses are reported to the handler. + self->_handler = nil; + } + }); +} + +- (void)writeData:(NSData *)data { + dispatch_async(_dispatchQueue, ^{ + [self->_pipe writeValue:data]; + }); +} + +- (void)finish { + dispatch_async(_dispatchQueue, ^{ + if (self->_call) { + [self->_pipe writesFinishedWithError:nil]; + } + self->_pipe = nil; + }); +} + +- (void)issueInitialMetadata:(NSDictionary *)initialMetadata { + if (initialMetadata != nil && [_handler respondsToSelector:@selector(receivedInitialMetadata:)]) { + [_handler receivedInitialMetadata:initialMetadata]; + } +} + +- (void)issueMessage:(id)message { + if (message != nil && [_handler respondsToSelector:@selector(receivedRawMessage:)]) { + [_handler receivedRawMessage:message]; + } +} + +- (void)issueClosedWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error { + NSDictionary *trailers = _call.responseTrailers; + if ([_handler respondsToSelector:@selector(closedWithTrailingMetadata:error:)]) { + [_handler closedWithTrailingMetadata:trailers error:error]; + } +} + @end // The following methods of a C gRPC call object aren't reentrant, and thus @@ -75,6 +293,8 @@ const char *kCFStreamVarName = "grpc_cfstream"; NSString *_host; NSString *_path; + GRPCCallSafety _callSafety; + GRPCCallOptions *_callOptions; GRPCWrappedCall *_wrappedCall; GRPCConnectivityMonitor *_connectivityMonitor; @@ -113,6 +333,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; @@ -156,8 +379,24 @@ const char *kCFStreamVarName = "grpc_cfstream"; - (instancetype)initWithHost:(NSString *)host path:(NSString *)path requestsWriter:(GRXWriter *)requestWriter { - if (!host || !path) { - [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."]; + 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 { + if (host.length == 0 || path.length == 0) { + [NSException raise:NSInvalidArgumentException + format:@"Neither host nor path can be nil or empty."]; + } + if (safety > GRPCCallSafetyCacheableRequest) { + [NSException raise:NSInvalidArgumentException format:@"Invalid call safety value."]; } if (requestWriter.state != GRXWriterStateNotStarted) { [NSException raise:NSInvalidArgumentException @@ -166,6 +405,8 @@ const char *kCFStreamVarName = "grpc_cfstream"; 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 +450,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; @@ -225,10 +462,12 @@ const char *kCFStreamVarName = "grpc_cfstream"; } - (void)cancel { - if (!self.isWaitingForToken) { - [self cancelCall]; - } else { - self.isWaitingForToken = NO; + @synchronized(self) { + if (!self.isWaitingForToken) { + [self cancelCall]; + } else { + self.isWaitingForToken = NO; + } } [self maybeFinishWithError:[NSError @@ -317,11 +556,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; + __block 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 +723,18 @@ 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?"); + _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host path:_path callOptions:_callOptions]; + if (_wrappedCall == nil) { + [self maybeFinishWithError:[NSError errorWithDomain:kGRPCErrorDomain + code:GRPCErrorCodeUnavailable + userInfo:@{ + NSLocalizedDescriptionKey : + @"Failed to create call or channel." + }]]; + return; + } - [self sendHeaders:_requestHeaders]; + [self sendHeaders]; [self invokeCall]; // Connectivity monitor is not required for CFStream @@ -486,18 +756,47 @@ 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; + if (_callOptions == nil) { + GRPCMutableCallOptions *callOptions; + + 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; + } [self.tokenProvider getTokenWithHandler:^(NSString *token) { - typeof(self) strongSelf = weakSelf; - if (strongSelf && strongSelf.isWaitingForToken) { - if (token) { - NSString *t = [kBearerPrefix stringByAppendingString:token]; - strongSelf.requestHeaders[kAuthorizationHeader] = t; + @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..9683bd3c63 --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCallOptions.h @@ -0,0 +1,343 @@ +/* + * + * 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> + +/** + * 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(NSInteger, 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(NSInteger, 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 *token))hander; +@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) 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) 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) id<GRPCAuthorizationProtocol> authTokenProvider; + +/** + * Initial metadata key-value pairs that should be included in the request. + */ +@property(copy, readonly) 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) 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) 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) NSString *PEMRootCertificates; + +/** + * PEM format private key for client authentication, if required by the server. + */ +@property(copy, readonly) NSString *PEMPrivateKey; + +/** + * PEM format certificate chain for client authentication, if required by the server. + */ +@property(copy, readonly) NSString *PEMCertChain; + +/** + * 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) 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) 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) 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) 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) id<GRPCAuthorizationProtocol> authTokenProvider; + +/** + * Initial metadata key-value pairs that should be included in the request. + */ +@property(copy, readwrite) 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) 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) 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) NSString *PEMRootCertificates; + +/** + * PEM format private key for client authentication, if required by the server. + */ +@property(copy, readwrite) NSString *PEMPrivateKey; + +/** + * PEM format certificate chain for client authentication, if required by the server. + */ +@property(copy, readwrite) NSString *PEMCertChain; + +/** + * 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) 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) 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 diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.m b/src/objective-c/GRPCClient/GRPCCallOptions.m new file mode 100644 index 0000000000..0977a4ccdb --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCallOptions.m @@ -0,0 +1,521 @@ +/* + * + * 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 kDefaultPEMCertChain = 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; + +@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 *_PEMCertChain; + GRPCTransportType _transportType; + NSString *_hostNameOverride; + id _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 PEMCertChain = _PEMCertChain; +@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 + PEMCertChain:kDefaultPEMCertChain + 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 + PEMCertChain:(NSString *)PEMCertChain + transportType:(GRPCTransportType)transportType + hostNameOverride:(NSString *)hostNameOverride + logContext:(id)logContext + channelPoolDomain:(NSString *)channelPoolDomain + channelID:(NSUInteger)channelID { + if ((self = [super init])) { + _serverAuthority = [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]; + _PEMCertChain = [PEMCertChain 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 + PEMCertChain:_PEMCertChain + 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 + PEMCertChain:_PEMCertChain + transportType:_transportType + hostNameOverride:_hostNameOverride + logContext:_logContext + channelPoolDomain:_channelPoolDomain + channelID:_channelID]; + return newOptions; +} + +- (BOOL)hasChannelOptionsEqualTo:(GRPCCallOptions *)callOptions { + if (!(callOptions.userAgentPrefix == _userAgentPrefix || + [callOptions.userAgentPrefix isEqualToString:_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 (!(callOptions.additionalChannelArgs == _additionalChannelArgs || + [callOptions.additionalChannelArgs isEqualToDictionary:_additionalChannelArgs])) + return NO; + if (!(callOptions.PEMRootCertificates == _PEMRootCertificates || + [callOptions.PEMRootCertificates isEqualToString:_PEMRootCertificates])) + return NO; + if (!(callOptions.PEMPrivateKey == _PEMPrivateKey || + [callOptions.PEMPrivateKey isEqualToString:_PEMPrivateKey])) + return NO; + if (!(callOptions.PEMCertChain == _PEMCertChain || + [callOptions.PEMCertChain isEqualToString:_PEMCertChain])) + return NO; + if (!(callOptions.hostNameOverride == _hostNameOverride || + [callOptions.hostNameOverride isEqualToString:_hostNameOverride])) + return NO; + if (!(callOptions.transportType == _transportType)) return NO; + if (!(callOptions.logContext == _logContext || [callOptions.logContext isEqual:_logContext])) + return NO; + if (!(callOptions.channelPoolDomain == _channelPoolDomain || + [callOptions.channelPoolDomain isEqualToString:_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 ^= _PEMCertChain.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 PEMCertChain; +@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 + PEMCertChain:kDefaultPEMCertChain + 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 copy] + PEMRootCertificates:_PEMRootCertificates + PEMPrivateKey:_PEMPrivateKey + PEMCertChain:_PEMCertChain + 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 + PEMCertChain:_PEMCertChain + 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)setPEMCertChain:(NSString *)PEMCertChain { + _PEMCertChain = [PEMCertChain 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..b8342a79e7 --- /dev/null +++ b/src/objective-c/GRPCClient/private/ChannelArgsUtil.m @@ -0,0 +1,95 @@ +/* + * + * 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((CFTreeRef)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) { + 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)]) { + if ([value compare:[NSNumber numberWithInteger:INT_MAX]] == NSOrderedDescending || + [value compare:[NSNumber numberWithInteger:INT_MIN]] == NSOrderedAscending) { + [NSException raise:NSInvalidArgumentException + format:@"Out of range for a value-typed channel argument: %@", value]; + } + 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 channel argument type: %@", [value class]]; + } + } + + return channelArgs; +} diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.h b/src/objective-c/GRPCClient/private/GRPCChannel.h index 6499d4398c..e1bf8fb1af 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannel.h +++ b/src/objective-c/GRPCClient/private/GRPCChannel.h @@ -21,6 +21,8 @@ #include <grpc/grpc.h> @class GRPCCompletionQueue; +@class GRPCCallOptions; +@class GRPCChannelConfiguration; struct grpc_channel_credentials; /** @@ -28,41 +30,45 @@ struct grpc_channel_credentials; */ @interface GRPCChannel : NSObject -@property(nonatomic, readonly, nonnull) struct grpc_channel *unmanagedChannel; - - (nullable instancetype)init NS_UNAVAILABLE; ++ (nullable instancetype) new NS_UNAVAILABLE; + /** - * 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. + * Returns a channel connecting to \a host with options as \a callOptions. The channel may be new + * or a cached channel that is already connected. */ -+ (nullable GRPCChannel *)secureChannelWithHost:(nonnull NSString *)host; ++ (nullable instancetype)channelWithHost:(nonnull NSString *)host + callOptions:(nullable GRPCCallOptions *)callOptions; /** - * Creates a secure channel to the specified @c host using Cronet as a transport mechanism. + * Create a channel object with the signature \a config. */ -#ifdef GRPC_COMPILE_WITH_CRONET -+ (nullable GRPCChannel *)secureCronetChannelWithHost:(nonnull NSString *)host - channelArgs:(nonnull NSDictionary *)channelArgs; -#endif ++ (nullable instancetype)createChannelWithConfiguration:(nonnull GRPCChannelConfiguration *)config; + /** - * 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. + * Get a grpc core call object from this channel. */ -+ (nonnull GRPCChannel *)secureChannelWithHost:(nonnull NSString *)host - credentials: - (nonnull struct grpc_channel_credentials *)credentials - channelArgs:(nullable NSDictionary *)channelArgs; +- (nullable grpc_call *)unmanagedCallWithPath:(nonnull NSString *)path + completionQueue:(nonnull GRPCCompletionQueue *)queue + callOptions:(nonnull GRPCCallOptions *)callOptions; /** - * Creates an insecure channel to the specified @c host using the specified @c channelArgs. + * Increase the refcount of the channel. If the channel was timed to be destroyed, cancel the timer. */ -+ (nonnull GRPCChannel *)insecureChannelWithHost:(nonnull NSString *)host - channelArgs:(nullable NSDictionary *)channelArgs; +- (void)ref; -- (nullable grpc_call *)unmanagedCallWithPath:(nonnull NSString *)path - serverName:(nonnull NSString *)serverName - timeout:(NSTimeInterval)timeout - completionQueue:(nonnull GRPCCompletionQueue *)queue; +/** + * Decrease the refcount of the channel. If the refcount of the channel decrease to 0, the channel + * is destroyed after 30 seconds. + */ +- (void)unref; + +/** + * Force the channel to be disconnected and destroyed immediately. + */ +- (void)disconnect; + +// TODO (mxyan): deprecate with GRPCCall:closeOpenConnections ++ (void)closeOpenConnections; @end diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.m b/src/objective-c/GRPCClient/private/GRPCChannel.m index b1f6ea270e..777cbab809 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannel.m +++ b/src/objective-c/GRPCClient/private/GRPCChannel.m @@ -18,206 +18,280 @@ #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 "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); -} +/** When all calls of a channel are destroyed, destroy the channel after this much seconds. */ +NSTimeInterval kChannelDestroyDelay = 30; -static int cmp_pointer_arg(void *p, void *q) { return p == q; } +/** Global instance of channel pool. */ +static GRPCChannelPool *gChannelPool; -static const grpc_arg_pointer_vtable objc_arg_vtable = {copy_pointer_arg, destroy_pointer_arg, - cmp_pointer_arg}; +/** + * Time the channel destroy when the channel's calls are unreffed. If there's new call, reset the + * timer. + */ +@interface GRPCChannelRef : NSObject -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); - } - } - gpr_free(channel_args->args); - gpr_free(channel_args); +- (instancetype)initWithDestroyDelay:(NSTimeInterval)destroyDelay + destroyChannelCallback:(void (^)())destroyChannelCallback; + +/** Add call ref count to the channel and maybe reset the timer. */ +- (void)refChannel; + +/** Reduce call ref count to the channel and maybe set the timer. */ +- (void)unrefChannel; + +/** Disconnect the channel immediately. */ +- (void)disconnect; + +@end + +@implementation GRPCChannelRef { + NSTimeInterval _destroyDelay; + void (^_destroyChannelCallback)(); + + NSUInteger _refCount; + BOOL _disconnected; + dispatch_queue_t _dispatchQueue; + dispatch_queue_t _timerQueue; + NSDate *_lastDispatch; } -/** - * 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; - } +- (instancetype)initWithDestroyDelay:(NSTimeInterval)destroyDelay + destroyChannelCallback:(void (^)())destroyChannelCallback { + if ((self = [super init])) { + _destroyDelay = destroyDelay; + _destroyChannelCallback = destroyChannelCallback; - 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; + _refCount = 1; + _disconnected = NO; + if (@available(iOS 8.0, *)) { + _dispatchQueue = dispatch_queue_create( + NULL, + dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1)); + _timerQueue = + dispatch_queue_create(NULL, dispatch_queue_attr_make_with_qos_class( + DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_DEFAULT, -1)); } else { - [NSException raise:NSInvalidArgumentException - format:@"Invalid value type: %@", [value class]]; + _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + _timerQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT); } + _lastDispatch = nil; } + return self; +} + +- (void)refChannel { + dispatch_async(_dispatchQueue, ^{ + if (!self->_disconnected) { + self->_refCount++; + self->_lastDispatch = nil; + } + }); +} + +- (void)unrefChannel { + dispatch_async(_dispatchQueue, ^{ + if (!self->_disconnected) { + self->_refCount--; + if (self->_refCount == 0) { + self->_lastDispatch = [NSDate date]; + dispatch_time_t delay = + dispatch_time(DISPATCH_TIME_NOW, (int64_t)kChannelDestroyDelay * 1e9); + dispatch_after(delay, self->_timerQueue, ^{ + [self timerFire]; + }); + } + } + }); +} + +- (void)disconnect { + dispatch_async(_dispatchQueue, ^{ + if (!self->_disconnected) { + self->_lastDispatch = nil; + self->_disconnected = YES; + // Break retain loop + self->_destroyChannelCallback = nil; + } + }); +} - return channelArgs; +- (void)timerFire { + dispatch_async(_dispatchQueue, ^{ + if (self->_disconnected || self->_lastDispatch == nil || + -[self->_lastDispatch timeIntervalSinceNow] < -kChannelDestroyDelay) { + return; + } + self->_lastDispatch = nil; + self->_disconnected = YES; + self->_destroyChannelCallback(); + // Break retain loop + self->_destroyChannelCallback = nil; + }); } +@end + @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; -} - -#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"]; - } + GRPCChannelConfiguration *_configuration; + grpc_channel *_unmanagedChannel; + GRPCChannelRef *_channelRef; + dispatch_queue_t _dispatchQueue; +} - if (self = [super init]) { - _channelArgs = BuildChannelArgs(channelArgs); - _host = [host copy]; - _unmanagedChannel = - grpc_cronet_secure_channel_create(cronetEngine, _host.UTF8String, _channelArgs, NULL); - } +- (grpc_call *)unmanagedCallWithPath:(NSString *)path + completionQueue:(GRPCCompletionQueue *)queue + callOptions:(GRPCCallOptions *)callOptions { + NSAssert(path.length, @"path must not be empty."); + NSAssert(queue, @"completionQueue must not be empty."); + NSAssert(callOptions, @"callOptions must not be empty."); + __block grpc_call *call = nil; + dispatch_sync(_dispatchQueue, ^{ + if (self->_unmanagedChannel) { + NSString *serverAuthority = + callOptions.transportType == GRPCTransportTypeCronet ? nil : callOptions.serverAuthority; + NSTimeInterval timeout = callOptions.timeout; + NSAssert(timeout >= 0, @"Invalid timeout"); + grpc_slice host_slice = grpc_empty_slice(); + if (serverAuthority) { + host_slice = grpc_slice_from_copied_string(serverAuthority.UTF8String); + } + 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)); + call = grpc_channel_create_call(self->_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); + } + }); + return call; +} - return self; +- (void)ref { + dispatch_async(_dispatchQueue, ^{ + if (self->_unmanagedChannel) { + [self->_channelRef refChannel]; + } + }); } -#endif -- (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"]; - } +- (void)unref { + dispatch_async(_dispatchQueue, ^{ + if (self->_unmanagedChannel) { + [self->_channelRef unrefChannel]; + } + }); +} + +- (void)disconnect { + dispatch_async(_dispatchQueue, ^{ + if (self->_unmanagedChannel) { + grpc_channel_destroy(self->_unmanagedChannel); + self->_unmanagedChannel = nil; + [self->_channelRef disconnect]; + } + }); +} + +- (void)destroyChannel { + dispatch_async(_dispatchQueue, ^{ + if (self->_unmanagedChannel) { + grpc_channel_destroy(self->_unmanagedChannel); + self->_unmanagedChannel = nil; + [gChannelPool removeChannel:self]; + } + }); +} - if (secure && !credentials) { +- (nullable instancetype)initWithUnmanagedChannel:(grpc_channel *_Nullable)unmanagedChannel + configuration:(GRPCChannelConfiguration *)configuration { + NSAssert(configuration, @"Configuration must not be empty."); + if (!unmanagedChannel) { return nil; } - - if (self = [super init]) { - _channelArgs = BuildChannelArgs(channelArgs); - _host = [host copy]; - if (secure) { - _unmanagedChannel = - grpc_secure_channel_create(credentials, _host.UTF8String, _channelArgs, NULL); + if ((self = [super init])) { + _unmanagedChannel = unmanagedChannel; + _configuration = [configuration copy]; + _channelRef = [[GRPCChannelRef alloc] initWithDestroyDelay:kChannelDestroyDelay + destroyChannelCallback:^{ + [self destroyChannel]; + }]; + if (@available(iOS 8.0, *)) { + _dispatchQueue = dispatch_queue_create( + NULL, + dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1)); } else { - _unmanagedChannel = grpc_insecure_channel_create(_host.UTF8String, _channelArgs, NULL); + _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); } } - return self; } - (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); -} - -#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; + if (_unmanagedChannel) { + grpc_channel_destroy(_unmanagedChannel); } - return [[GRPCChannel alloc] initWithHost:host cronetEngine:engine channelArgs:channelArgs]; } -#endif -+ (GRPCChannel *)secureChannelWithHost:(NSString *)host { - return [[GRPCChannel alloc] initWithHost:host secure:YES credentials:NULL channelArgs:NULL]; -} ++ (nullable instancetype)createChannelWithConfiguration:(GRPCChannelConfiguration *)config { + NSString *host = config.host; + if (host.length == 0) { + return nil; + } -+ (GRPCChannel *)secureChannelWithHost:(NSString *)host - credentials:(struct grpc_channel_credentials *)credentials - channelArgs:(NSDictionary *)channelArgs { - return [[GRPCChannel alloc] initWithHost:host - secure:YES - credentials:credentials - channelArgs:channelArgs]; + NSDictionary *channelArgs; + if (config.callOptions.additionalChannelArgs.count != 0) { + NSMutableDictionary *args = [config.channelArgs mutableCopy]; + [args addEntriesFromDictionary:config.callOptions.additionalChannelArgs]; + channelArgs = args; + } else { + channelArgs = config.channelArgs; + } + id<GRPCChannelFactory> factory = config.channelFactory; + grpc_channel *unmanaged_channel = [factory createChannelWithHost:host channelArgs:channelArgs]; + return [[GRPCChannel alloc] initWithUnmanagedChannel:unmanaged_channel configuration:config]; } -+ (GRPCChannel *)insecureChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)channelArgs { - return [[GRPCChannel alloc] initWithHost:host secure:NO credentials:NULL channelArgs:channelArgs]; -} ++ (nullable instancetype)channelWithHost:(NSString *)host + callOptions:(GRPCCallOptions *)callOptions { + static dispatch_once_t initChannelPool; + dispatch_once(&initChannelPool, ^{ + gChannelPool = [[GRPCChannelPool alloc] init]; + }); -- (grpc_call *)unmanagedCallWithPath:(NSString *)path - serverName:(NSString *)serverName - timeout:(NSTimeInterval)timeout - completionQueue:(GRPCCompletionQueue *)queue { - GPR_ASSERT(timeout >= 0); - if (timeout < 0) { - timeout = 0; - } - grpc_slice host_slice = grpc_empty_slice(); - if (serverName) { - host_slice = grpc_slice_from_copied_string(serverName.UTF8String); + NSURL *hostURL = [NSURL URLWithString:[@"https://" stringByAppendingString:host]]; + if (hostURL.host && !hostURL.port) { + host = [hostURL.host stringByAppendingString:@":443"]; } - 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) { - grpc_slice_unref(host_slice); - } - grpc_slice_unref(path_slice); - return call; + + GRPCChannelConfiguration *channelConfig = + [[GRPCChannelConfiguration alloc] initWithHost:host callOptions:callOptions]; + + return [gChannelPool channelWithConfiguration:channelConfig]; +} + ++ (void)closeOpenConnections { + [gChannelPool removeAndCloseAllChannels]; } @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..3a3500fc95 --- /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. */ +- (grpc_channel *_Nullable)createChannelWithHost:(NSString *)host + channelArgs:(NSDictionary *_Nullable)args; + +@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..2244361df2 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCChannelPool.h @@ -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. + * + */ + +/** + * Signature for the channel. If two channel's signatures are the same, they share the same + * underlying \a GRPCChannel object. + */ + +#import <GRPCClient/GRPCCallOptions.h> + +#import "GRPCChannelFactory.h" + +NS_ASSUME_NONNULL_BEGIN + +@class GRPCChannel; + +/** Caching signature of a channel. */ +@interface GRPCChannelConfiguration : NSObject<NSCopying> + +/** The host that this channel is connected to. */ +@property(copy, readonly) NSString *host; + +/** + * Options of the corresponding call. Note that only the channel-related options are of interest to + * this class. + */ +@property(strong, 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; + +@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 + +- (instancetype)init; + +/** + * Return a channel with a particular configuration. If the channel does not exist, execute \a + * createChannel then add it in the pool. If the channel exists, increase its reference count. + */ +- (GRPCChannel *)channelWithConfiguration:(GRPCChannelConfiguration *)configuration; + +/** Remove a channel from the pool. */ +- (void)removeChannel:(GRPCChannel *)channel; + +/** Clear all channels in the pool. */ +- (void)removeAllChannels; + +/** Clear all channels in the pool and destroy the channels. */ +- (void)removeAndCloseAllChannels; + +@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..56f76450b2 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCChannelPool.m @@ -0,0 +1,241 @@ +/* + * + * 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.h" +#import "GRPCConnectivityMonitor.h" +#import "GRPCCronetChannelFactory.h" +#import "GRPCInsecureChannelFactory.h" +#import "GRPCSecureChannelFactory.h" +#import "version.h" + +#import <GRPCClient/GRPCCall+Cronet.h> +#include <grpc/support/log.h> + +extern const char *kCFStreamVarName; + +@implementation GRPCChannelConfiguration + +- (nullable instancetype)initWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions { + NSAssert(host.length, @"Host must not be empty."); + NSAssert(callOptions, @"callOptions must not be empty."); + if ((self = [super init])) { + _host = [host copy]; + _callOptions = [callOptions copy]; + } + return self; +} + +- (id<GRPCChannelFactory>)channelFactory { + NSError *error; + id<GRPCChannelFactory> factory; + GRPCTransportType type = _callOptions.transportType; + switch (type) { + case GRPCTransportTypeChttp2BoringSSL: + // TODO (mxyan): Remove when the API is deprecated +#ifdef GRPC_COMPILE_WITH_CRONET + if (![GRPCCall isUsingCronet]) { +#endif + factory = [GRPCSecureChannelFactory + factoryWithPEMRootCertificates:_callOptions.PEMRootCertificates + privateKey:_callOptions.PEMPrivateKey + certChain:_callOptions.PEMCertChain + error:&error]; + if (factory == nil) { + NSLog(@"Error creating secure channel factory: %@", error); + } + return factory; +#ifdef GRPC_COMPILE_WITH_CRONET + } +#endif + // fallthrough + case GRPCTransportTypeCronet: + return [GRPCCronetChannelFactory sharedInstance]; + case GRPCTransportTypeInsecure: + return [GRPCInsecureChannelFactory sharedInstance]; + } +} + +- (NSDictionary *)channelArgs { + NSMutableDictionary *args = [NSMutableDictionary new]; + + NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING; + NSString *userAgentPrefix = _callOptions.userAgentPrefix; + if (userAgentPrefix) { + args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = + [_callOptions.userAgentPrefix stringByAppendingFormat:@" %@", userAgent]; + } else { + args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent; + } + + NSString *hostNameOverride = _callOptions.hostNameOverride; + if (hostNameOverride) { + args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = hostNameOverride; + } + + if (_callOptions.responseSizeLimit) { + args[@GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH] = + [NSNumber numberWithUnsignedInteger:_callOptions.responseSizeLimit]; + } + + if (_callOptions.compressionAlgorithm != GRPC_COMPRESS_NONE) { + args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] = + [NSNumber numberWithInt:_callOptions.compressionAlgorithm]; + } + + 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)]; + } + + if (_callOptions.retryEnabled == NO) { + args[@GRPC_ARG_ENABLE_RETRIES] = [NSNumber numberWithInt:_callOptions.retryEnabled]; + } + + 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 (_callOptions.logContext != nil) { + args[@GRPC_ARG_MOBILE_LOG_CONTEXT] = _callOptions.logContext; + } + + if (_callOptions.channelPoolDomain.length != 0) { + args[@GRPC_ARG_CHANNEL_POOL_DOMAIN] = _callOptions.channelPoolDomain; + } + + [args addEntriesFromDictionary:_callOptions.additionalChannelArgs]; + + return args; +} + +- (nonnull id)copyWithZone:(nullable NSZone *)zone { + GRPCChannelConfiguration *newConfig = + [[GRPCChannelConfiguration alloc] initWithHost:_host callOptions:_callOptions]; + + return newConfig; +} + +- (BOOL)isEqual:(id)object { + NSAssert([object isKindOfClass:[GRPCChannelConfiguration class]], @"Illegal :isEqual"); + GRPCChannelConfiguration *obj = (GRPCChannelConfiguration *)object; + if (!(obj.host == _host || [obj.host isEqualToString:_host])) return NO; + if (!(obj.callOptions == _callOptions || [obj.callOptions hasChannelOptionsEqualTo:_callOptions])) + return NO; + + return YES; +} + +- (NSUInteger)hash { + NSUInteger result = 0; + result ^= _host.hash; + result ^= _callOptions.channelOptionsHash; + + return result; +} + +@end + +#pragma mark GRPCChannelPool + +@implementation GRPCChannelPool { + NSMutableDictionary<GRPCChannelConfiguration *, GRPCChannel *> *_channelPool; +} + +- (instancetype)init { + 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]; +} + +- (GRPCChannel *)channelWithConfiguration:(GRPCChannelConfiguration *)configuration { + __block GRPCChannel *channel; + @synchronized(self) { + if ([_channelPool objectForKey:configuration]) { + channel = _channelPool[configuration]; + [channel ref]; + } else { + channel = [GRPCChannel createChannelWithConfiguration:configuration]; + if (channel != nil) { + _channelPool[configuration] = channel; + } + } + } + return channel; +} + +- (void)removeChannel:(GRPCChannel *)channel { + @synchronized(self) { + [_channelPool + enumerateKeysAndObjectsUsingBlock:^(GRPCChannelConfiguration *_Nonnull key, + GRPCChannel *_Nonnull obj, BOOL *_Nonnull stop) { + if (obj == channel) { + [self->_channelPool removeObjectForKey:key]; + } + }]; + } +} + +- (void)removeAllChannels { + @synchronized(self) { + _channelPool = [NSMutableDictionary dictionary]; + } +} + +- (void)removeAndCloseAllChannels { + @synchronized(self) { + [_channelPool + enumerateKeysAndObjectsUsingBlock:^(GRPCChannelConfiguration *_Nonnull key, + GRPCChannel *_Nonnull obj, BOOL *_Nonnull stop) { + [obj disconnect]; + }]; + _channelPool = [NSMutableDictionary dictionary]; + } +} + +- (void)connectivityChange:(NSNotification *)note { + [self removeAndCloseAllChannels]; +} + +@end diff --git a/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h new file mode 100644 index 0000000000..5e35576ea1 --- /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> + ++ (instancetype _Nullable)sharedInstance; + +- (grpc_channel *_Nullable)createChannelWithHost:(NSString *)host + channelArgs:(NSDictionary *_Nullable)args; + +- (instancetype _Nullable)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..781881d211 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m @@ -0,0 +1,91 @@ +/* + * + * 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> + +NS_ASSUME_NONNULL_BEGIN + +@implementation GRPCCronetChannelFactory { + stream_engine *_cronetEngine; +} + ++ (instancetype _Nullable)sharedInstance { + static GRPCCronetChannelFactory *instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc] initWithEngine:[Cronet getGlobalEngine]]; + }); + return instance; +} + +- (instancetype _Nullable)initWithEngine:(stream_engine *)engine { + if (!engine) { + [NSException raise:NSInvalidArgumentException format:@"Cronet engine is NULL. Set it first."]; + return nil; + } + if ((self = [super init])) { + _cronetEngine = engine; + } + return self; +} + +- (grpc_channel *_Nullable)createChannelWithHost:(NSString *)host + channelArgs:(NSDictionary *_Nullable)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 + +NS_ASSUME_NONNULL_END + +#else + +NS_ASSUME_NONNULL_BEGIN + +@implementation GRPCCronetChannelFactory + ++ (instancetype _Nullable)sharedInstance { + [NSException raise:NSInvalidArgumentException + format:@"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel."]; + return nil; +} + +- (grpc_channel *_Nullable)createChannelWithHost:(NSString *)host + channelArgs:(NSDictionary *_Nullable)args { + [NSException raise:NSInvalidArgumentException + format:@"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel."]; + return NULL; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/src/objective-c/GRPCClient/private/GRPCHost.h b/src/objective-c/GRPCClient/private/GRPCHost.h index 291b07df37..f1d5719642 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, readwrite) 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..ab5b69cc4e 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 *_pemCertChain; } + (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,91 @@ 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; _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]; + @synchronized(gHostCache) { + gHostCache = [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; - } - 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]; + _pemCertChain = [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.PEMCertChain = _pemCertChain; + 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]; + __block GRPCCallOptions *callOptions = nil; + @synchronized(gHostCache) { + if ([gHostCache objectForKey:host]) { + 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..98a985fe84 --- /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> + ++ (instancetype _Nullable)sharedInstance; + +- (grpc_channel *_Nullable)createChannelWithHost:(NSString *)host + channelArgs:(NSDictionary *_Nullable)args; + +- (instancetype _Nullable)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..9969887712 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m @@ -0,0 +1,48 @@ +/* + * + * 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" + +NS_ASSUME_NONNULL_BEGIN + +@implementation GRPCInsecureChannelFactory + ++ (instancetype _Nullable)sharedInstance { + static GRPCInsecureChannelFactory *instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc] init]; + }); + return instance; +} + +- (grpc_channel *_Nullable)createChannelWithHost:(NSString *)host + channelArgs:(NSDictionary *_Nullable)args { + grpc_channel_args *coreChannelArgs = GRPCBuildChannelArgs([args copy]); + grpc_channel *unmanagedChannel = + grpc_insecure_channel_create(host.UTF8String, coreChannelArgs, NULL); + GRPCFreeChannelArgs(coreChannelArgs); + return unmanagedChannel; +} + +@end + +NS_ASSUME_NONNULL_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..02e7052996 --- /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> + ++ (instancetype _Nullable)factoryWithPEMRootCertificates:(NSString *_Nullable)rootCerts + privateKey:(NSString *_Nullable)privateKey + certChain:(NSString *_Nullable)certChain + error:(NSError **)errorPtr; + +- (grpc_channel *_Nullable)createChannelWithHost:(NSString *)host + channelArgs:(NSDictionary *_Nullable)args; + +- (instancetype _Nullable)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..1f6458c524 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m @@ -0,0 +1,136 @@ +/* + * + * 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" + +NS_ASSUME_NONNULL_BEGIN + +@implementation GRPCSecureChannelFactory { + grpc_channel_credentials *_channelCreds; +} + ++ (instancetype _Nullable)factoryWithPEMRootCertificates:(NSString *_Nullable)rootCerts + privateKey:(NSString *_Nullable)privateKey + certChain:(NSString *_Nullable)certChain + error:(NSError **)errorPtr { + return [[self alloc] initWithPEMRootCerts:rootCerts + privateKey:privateKey + certChain:certChain + error:errorPtr]; +} + +- (NSData *_Nullable)nullTerminatedDataWithString:(NSString *_Nullable)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 _Nullable)initWithPEMRootCerts:(NSString *_Nullable)rootCerts + privateKey:(NSString *_Nullable)privateKey + certChain:(NSString *_Nullable)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, + @"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 *_Nullable)createChannelWithHost:(NSString *)host + channelArgs:(NSDictionary *_Nullable)args { + grpc_channel_args *coreChannelArgs = GRPCBuildChannelArgs([args copy]); + 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 + +NS_ASSUME_NONNULL_END diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h index f711850c2f..19aa5367c7 100644 --- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h @@ -74,9 +74,8 @@ @interface GRPCWrappedCall : NSObject - (instancetype)initWithHost:(NSString *)host - serverName:(NSString *)serverName path:(NSString *)path - timeout:(NSTimeInterval)timeout NS_DESIGNATED_INITIALIZER; + callOptions:(GRPCCallOptions *)callOptions NS_DESIGNATED_INITIALIZER; - (void)startBatchWithOperations:(NSArray *)ops errorHandler:(void (^)(void))errorHandler; diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m index 7781d27ca4..100d4cf291 100644 --- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m @@ -23,6 +23,7 @@ #include <grpc/grpc.h> #include <grpc/support/alloc.h> +#import "GRPCChannel.h" #import "GRPCCompletionQueue.h" #import "GRPCHost.h" #import "NSData+GRPC.h" @@ -235,31 +236,28 @@ @implementation GRPCWrappedCall { GRPCCompletionQueue *_queue; + GRPCChannel *_channel; grpc_call *_call; } - (instancetype)init { - return [self initWithHost:nil serverName:nil path:nil timeout:0]; + return [self initWithHost:nil path:nil callOptions:[[GRPCCallOptions alloc] init]]; } - (instancetype)initWithHost:(NSString *)host - serverName:(NSString *)serverName path:(NSString *)path - timeout:(NSTimeInterval)timeout { - if (!path || !host) { + callOptions:(GRPCCallOptions *)callOptions { + if (host.length == 0 || path.length == 0) { [NSException raise:NSInvalidArgumentException format:@"path and host cannot be nil."]; } - if (self = [super init]) { + 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]; + _channel = [GRPCChannel channelWithHost:host callOptions:callOptions]; + _call = [_channel unmanagedCallWithPath:path completionQueue:_queue callOptions:callOptions]; if (_call == NULL) { return nil; } @@ -312,7 +310,11 @@ } - (void)dealloc { - grpc_call_unref(_call); + if (_call) { + grpc_call_unref(_call); + } + [_channel unref]; + _channel = nil; } @end diff --git a/src/objective-c/ProtoRPC/ProtoRPC.h b/src/objective-c/ProtoRPC/ProtoRPC.h index 45d3526092..635ba0c90e 100644 --- a/src/objective-c/ProtoRPC/ProtoRPC.h +++ b/src/objective-c/ProtoRPC/ProtoRPC.h @@ -21,6 +21,111 @@ #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> + +@optional + +/** + * Issued when initial metadata is received from the server. The task must be scheduled onto the + * dispatch queue in property \a dispatchQueue. */ +- (void)receivedInitialMetadata:(NSDictionary *_Nullable)initialMetadata; + +/** + * Issued when a message is received from the server. The message is the deserialized proto object. + * The task must be scheduled onto the dispatch queue in property \a dispatchQueue. + */ +- (void)receivedProtoMessage:(GPBMessage *_Nullable)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. The task must be scheduled onto the dispatch queue in property + * \a dispatchQueue. + */ +- (void)closedWithTrailingMetadata:(NSDictionary *_Nullable)trailingMetadata + error:(NSError *_Nullable)error; + +@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; + +@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. + */ +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + message:(GPBMessage *)message + responseHandler:(id<GRPCProtoResponseHandler>)handler + callOptions:(GRPCCallOptions *_Nullable)callOptions + responseClass:(Class)responseClass NS_DESIGNATED_INITIALIZER; + +/** + * Cancel the request of this call at best effort. It attempts to notify the server that the RPC + * should be cancelled, and issue closedWithTrailingMetadata: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. + */ +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + responseHandler:(id<GRPCProtoResponseHandler>)handler + callOptions:(GRPCCallOptions *_Nullable)callOptions + responseClass:(Class)responseClass NS_DESIGNATED_INITIALIZER; + +/** + * Cancel the request of this call at best effort. It attempts to notify the server that the RPC + * should be cancelled, and issue closedWithTrailingMetadata: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 + __attribute__((deprecated("Please use GRPCProtoCall."))) @interface ProtoRPC : GRPCCall diff --git a/src/objective-c/ProtoRPC/ProtoRPC.m b/src/objective-c/ProtoRPC/ProtoRPC.m index 5dca971b08..34891e8953 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,181 @@ static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsing return [NSError errorWithDomain:@"io.grpc" code:13 userInfo:info]; } +@implementation GRPCUnaryProtoCall { + GRPCStreamingProtoCall *_call; +} + +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + message:(GPBMessage *)message + responseHandler:(id<GRPCProtoResponseHandler>)handler + callOptions:(GRPCCallOptions *_Nullable)callOptions + responseClass:(Class)responseClass { + if ((self = [super init])) { + _call = [[GRPCStreamingProtoCall alloc] initWithRequestOptions:requestOptions + responseHandler:handler + callOptions:callOptions + responseClass:responseClass]; + [_call writeMessage:message]; + [_call finish]; + } + return self; +} + +- (void)cancel { + [_call cancel]; + _call = nil; +} + +@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 *_Nullable)callOptions + responseClass:(Class)responseClass { + if (requestOptions.host.length == 0 || requestOptions.path.length == 0) { + [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."]; + } + if (requestOptions.safety > GRPCCallSafetyCacheableRequest) { + [NSException raise:NSInvalidArgumentException format:@"Invalid call safety value."]; + } + if (handler == nil) { + [NSException raise:NSInvalidArgumentException format:@"Response handler required."]; + } + + if ((self = [super init])) { + _requestOptions = [requestOptions copy]; + _handler = handler; + _callOptions = [callOptions copy]; + _responseClass = responseClass; + if (@available(iOS 8.0, *)) { + _dispatchQueue = dispatch_queue_create( + NULL, + dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1)); + } else { + _dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL); + } + dispatch_set_target_queue(handler.dispatchQueue, _dispatchQueue); + + [self start]; + } + return self; +} + +- (void)start { + _call = [[GRPCCall2 alloc] initWithRequestOptions:_requestOptions + responseHandler:self + callOptions:_callOptions]; + [_call start]; +} + +- (void)cancel { + dispatch_async(_dispatchQueue, ^{ + if (_call) { + [_call cancel]; + _call = nil; + } + if (_handler) { + id<GRPCProtoResponseHandler> handler = _handler; + if ([handler respondsToSelector:@selector(closedWithTrailingMetadata:error:)]) { + dispatch_async(handler.dispatchQueue, ^{ + [handler closedWithTrailingMetadata:nil + error:[NSError errorWithDomain:kGRPCErrorDomain + code:GRPCErrorCodeCancelled + userInfo:@{ + NSLocalizedDescriptionKey : + @"Canceled by app" + }]]; + }); + } + _handler = nil; + } + }); +} + +- (void)writeMessage:(GPBMessage *)message { + if (![message isKindOfClass:[GPBMessage class]]) { + [NSException raise:NSInvalidArgumentException format:@"Data must be a valid protobuf type."]; + } + + dispatch_async(_dispatchQueue, ^{ + if (_call) { + [_call writeData:[message data]]; + } + }); +} + +- (void)finish { + dispatch_async(_dispatchQueue, ^{ + if (_call) { + [_call finish]; + _call = nil; + } + }); +} + +- (void)receivedInitialMetadata:(NSDictionary *_Nullable)initialMetadata { + dispatch_async(_dispatchQueue, ^{ + if (initialMetadata != nil && [self->_handler respondsToSelector:@selector(initialMetadata:)]) { + [self->_handler receivedInitialMetadata:initialMetadata]; + } + }); +} + +- (void)receivedRawMessage:(NSData *_Nullable)message { + dispatch_async(_dispatchQueue, ^{ + if (self->_handler && message != nil) { + NSError *error = nil; + GPBMessage *parsed = [self->_responseClass parseFromData:message error:&error]; + if (parsed) { + if ([self->_handler respondsToSelector:@selector(receivedProtoMessage:)]) { + [self->_handler receivedProtoMessage:parsed]; + } + } else { + if ([self->_handler respondsToSelector:@selector(closedWithTrailingMetadata:error:)]) { + [self->_handler + closedWithTrailingMetadata:nil + error:ErrorForBadProto(message, _responseClass, error)]; + } + self->_handler = nil; + [self->_call cancel]; + self->_call = nil; + } + } + }); +} + +- (void)closedWithTrailingMetadata:(NSDictionary *_Nullable)trailingMetadata + error:(NSError *_Nullable)error { + dispatch_async(_dispatchQueue, ^{ + if ([self->_handler respondsToSelector:@selector(closedWithTrailingMetadata:error:)]) { + [self->_handler closedWithTrailingMetadata:trailingMetadata error:error]; + } + self->_handler = nil; + [self->_call cancel]; + self->_call = nil; + }); +} + +- (dispatch_queue_t)dispatchQueue { + return _dispatchQueue; +} + +@end + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" @implementation ProtoRPC { @@ -72,7 +251,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..3a16ab2402 100644 --- a/src/objective-c/ProtoRPC/ProtoService.h +++ b/src/objective-c/ProtoRPC/ProtoService.h @@ -21,16 +21,45 @@ @class GRPCProtoCall; @protocol GRXWriteable; @class GRXWriter; +@class GRPCCallOptions; +@class GRPCProtoCall; +@class GRPCUnaryProtoCall; +@class GRPCStreamingProtoCall; +@protocol GRPCProtoResponseCallbacks; __attribute__((deprecated("Please use GRPCProtoService."))) @interface ProtoService - : NSObject - - (instancetype)initWithHost : (NSString *)host packageName - : (NSString *)packageName serviceName : (NSString *)serviceName NS_DESIGNATED_INITIALIZER; + : NSObject + + - + (instancetype)init NS_UNAVAILABLE; + ++ (instancetype) new NS_UNAVAILABLE; + +- (instancetype)initWithHost:(NSString *)host + packageName:(NSString *)packageName + serviceName:(NSString *)serviceName + callOptions:(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; + +- (GRPCUnaryProtoCall *)RPCToMethod:(NSString *)method + message:(id)message + responseHandler:(id<GRPCProtoResponseCallbacks>)handler + callOptions:(GRPCCallOptions *)callOptions + responseClass:(Class)responseClass; + +- (GRPCStreamingProtoCall *)RPCToMethod:(NSString *)method + responseHandler:(id<GRPCProtoResponseCallbacks>)handler + callOptions:(GRPCCallOptions *)callOptions + responseClass:(Class)responseClass; + @end /** diff --git a/src/objective-c/ProtoRPC/ProtoService.m b/src/objective-c/ProtoRPC/ProtoService.m index 3e9cc5c0b2..6df502fb0c 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,7 +42,8 @@ // Designated initializer - (instancetype)initWithHost:(NSString *)host packageName:(NSString *)packageName - serviceName:(NSString *)serviceName { + serviceName:(NSString *)serviceName + callOptions:(GRPCCallOptions *)callOptions { if (!host || !serviceName) { [NSException raise:NSInvalidArgumentException format:@"Neither host nor serviceName can be nil."]; @@ -49,10 +52,17 @@ _host = [host copy]; _packageName = [packageName copy]; _serviceName = [serviceName copy]; + _callOptions = [callOptions copy]; } return self; } +- (instancetype)initWithHost:(NSString *)host + packageName:(NSString *)packageName + serviceName:(NSString *)serviceName { + return [self initWithHost:host packageName:packageName serviceName:serviceName callOptions:nil]; +} + - (GRPCProtoCall *)RPCToMethod:(NSString *)method requestsWriter:(GRXWriter *)requestsWriter responseClass:(Class)responseClass @@ -65,6 +75,41 @@ responseClass:responseClass responsesWriteable:responsesWriteable]; } + +- (GRPCUnaryProtoCall *)RPCToMethod:(NSString *)method + message:(id)message + responseHandler:(id<GRPCProtoResponseCallbacks>)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<GRPCProtoResponseCallbacks>)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/tests/ChannelTests/ChannelPoolTest.m b/src/objective-c/tests/ChannelTests/ChannelPoolTest.m new file mode 100644 index 0000000000..5c3f0edba0 --- /dev/null +++ b/src/objective-c/tests/ChannelTests/ChannelPoolTest.m @@ -0,0 +1,126 @@ +/* + * + * 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.h" + +#define TEST_TIMEOUT 32 + +NSString *kDummyHost = @"dummy.host"; + +@interface ChannelPoolTest : XCTestCase + +@end + +@implementation ChannelPoolTest + ++ (void)setUp { + grpc_init(); +} + +- (void)testCreateChannel { + NSString *kDummyHost = @"dummy.host"; + GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init]; + options1.transportType = GRPCTransportTypeInsecure; + GRPCCallOptions *options2 = [options1 copy]; + GRPCChannelConfiguration *config1 = + [[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options1]; + GRPCChannelConfiguration *config2 = + [[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options2]; + GRPCChannelPool *pool = [[GRPCChannelPool alloc] init]; + + GRPCChannel *channel1 = [pool channelWithConfiguration:config1]; + GRPCChannel *channel2 = [pool channelWithConfiguration:config2]; + XCTAssertEqual(channel1, channel2); +} + +- (void)testChannelRemove { + GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init]; + options1.transportType = GRPCTransportTypeInsecure; + GRPCChannelConfiguration *config1 = + [[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options1]; + GRPCChannelPool *pool = [[GRPCChannelPool alloc] init]; + GRPCChannel *channel1 = [pool channelWithConfiguration:config1]; + [pool removeChannel:channel1]; + GRPCChannel *channel2 = [pool channelWithConfiguration:config1]; + XCTAssertNotEqual(channel1, channel2); +} + +extern NSTimeInterval kChannelDestroyDelay; + +- (void)testChannelTimeoutCancel { + NSTimeInterval kOriginalInterval = kChannelDestroyDelay; + kChannelDestroyDelay = 3.0; + GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init]; + options1.transportType = GRPCTransportTypeInsecure; + GRPCChannelConfiguration *config1 = + [[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options1]; + GRPCChannelPool *pool = [[GRPCChannelPool alloc] init]; + GRPCChannel *channel1 = [pool channelWithConfiguration:config1]; + [channel1 unref]; + sleep(1); + GRPCChannel *channel2 = [pool channelWithConfiguration:config1]; + XCTAssertEqual(channel1, channel2); + sleep((int)kChannelDestroyDelay + 2); + GRPCChannel *channel3 = [pool channelWithConfiguration:config1]; + XCTAssertEqual(channel1, channel3); + kChannelDestroyDelay = kOriginalInterval; +} + +- (void)testChannelDisconnect { + NSString *kDummyHost = @"dummy.host"; + GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init]; + options1.transportType = GRPCTransportTypeInsecure; + GRPCCallOptions *options2 = [options1 copy]; + GRPCChannelConfiguration *config1 = + [[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options1]; + GRPCChannelConfiguration *config2 = + [[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options2]; + GRPCChannelPool *pool = [[GRPCChannelPool alloc] init]; + + GRPCChannel *channel1 = [pool channelWithConfiguration:config1]; + [pool removeAndCloseAllChannels]; + GRPCChannel *channel2 = [pool channelWithConfiguration:config2]; + XCTAssertNotEqual(channel1, channel2); +} + +- (void)testClearChannels { + GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init]; + options1.transportType = GRPCTransportTypeInsecure; + GRPCMutableCallOptions *options2 = [[GRPCMutableCallOptions alloc] init]; + options2.transportType = GRPCTransportTypeChttp2BoringSSL; + GRPCChannelConfiguration *config1 = + [[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options1]; + GRPCChannelConfiguration *config2 = + [[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options2]; + GRPCChannelPool *pool = [[GRPCChannelPool alloc] init]; + + GRPCChannel *channel1 = [pool channelWithConfiguration:config1]; + GRPCChannel *channel2 = [pool channelWithConfiguration:config2]; + XCTAssertNotEqual(channel1, channel2); + + [pool removeAndCloseAllChannels]; + GRPCChannel *channel3 = [pool channelWithConfiguration:config1]; + GRPCChannel *channel4 = [pool channelWithConfiguration:config2]; + XCTAssertNotEqual(channel1, channel3); + XCTAssertNotEqual(channel2, 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..64c3356b13 --- /dev/null +++ b/src/objective-c/tests/ChannelTests/ChannelTests.m @@ -0,0 +1,93 @@ +/* + * + * 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" + +@interface ChannelTests : XCTestCase + +@end + +@implementation ChannelTests + ++ (void)setUp { + grpc_init(); +} + +- (void)testSameConfiguration { + NSString *host = @"grpc-test.sandbox.googleapis.com"; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.userAgentPrefix = @"TestUAPrefix"; + NSMutableDictionary *args = [NSMutableDictionary new]; + args[@"abc"] = @"xyz"; + options.additionalChannelArgs = [args copy]; + GRPCChannel *channel1 = [GRPCChannel channelWithHost:host callOptions:options]; + GRPCChannel *channel2 = [GRPCChannel channelWithHost:host callOptions:options]; + XCTAssertEqual(channel1, channel2); + GRPCMutableCallOptions *options2 = [options mutableCopy]; + options2.additionalChannelArgs = [args copy]; + GRPCChannel *channel3 = [GRPCChannel channelWithHost:host callOptions:options2]; + XCTAssertEqual(channel1, channel3); +} + +- (void)testDifferentHost { + NSString *host1 = @"grpc-test.sandbox.googleapis.com"; + NSString *host2 = @"grpc-test2.sandbox.googleapis.com"; + NSString *host3 = @"http://grpc-test.sandbox.googleapis.com"; + NSString *host4 = @"dns://grpc-test.sandbox.googleapis.com"; + NSString *host5 = @"grpc-test.sandbox.googleapis.com:80"; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.userAgentPrefix = @"TestUAPrefix"; + NSMutableDictionary *args = [NSMutableDictionary new]; + args[@"abc"] = @"xyz"; + options.additionalChannelArgs = [args copy]; + GRPCChannel *channel1 = [GRPCChannel channelWithHost:host1 callOptions:options]; + GRPCChannel *channel2 = [GRPCChannel channelWithHost:host2 callOptions:options]; + GRPCChannel *channel3 = [GRPCChannel channelWithHost:host3 callOptions:options]; + GRPCChannel *channel4 = [GRPCChannel channelWithHost:host4 callOptions:options]; + GRPCChannel *channel5 = [GRPCChannel channelWithHost:host5 callOptions:options]; + XCTAssertNotEqual(channel1, channel2); + XCTAssertNotEqual(channel1, channel3); + XCTAssertNotEqual(channel1, channel4); + XCTAssertNotEqual(channel1, channel5); +} + +- (void)testDifferentChannelParameters { + NSString *host = @"grpc-test.sandbox.googleapis.com"; + GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init]; + options1.transportType = GRPCTransportTypeChttp2BoringSSL; + NSMutableDictionary *args = [NSMutableDictionary new]; + args[@"abc"] = @"xyz"; + options1.additionalChannelArgs = [args copy]; + GRPCMutableCallOptions *options2 = [[GRPCMutableCallOptions alloc] init]; + options2.transportType = GRPCTransportTypeInsecure; + options2.additionalChannelArgs = [args copy]; + GRPCMutableCallOptions *options3 = [[GRPCMutableCallOptions alloc] init]; + options3.transportType = GRPCTransportTypeChttp2BoringSSL; + args[@"def"] = @"uvw"; + options3.additionalChannelArgs = [args copy]; + GRPCChannel *channel1 = [GRPCChannel channelWithHost:host callOptions:options1]; + GRPCChannel *channel2 = [GRPCChannel channelWithHost:host callOptions:options2]; + GRPCChannel *channel3 = [GRPCChannel channelWithHost:host callOptions:options3]; + XCTAssertNotEqual(channel1, channel2); + XCTAssertNotEqual(channel1, channel3); +} + +@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..bbe81502dc 100644 --- a/src/objective-c/tests/GRPCClientTests.m +++ b/src/objective-c/tests/GRPCClientTests.m @@ -86,6 +86,65 @@ static GRPCProtoMethod *kFullDuplexCallMethod; @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)receivedInitialMetadata:(NSDictionary *_Nullable)initialMetadata { + dispatch_async(_dispatchQueue, ^{ + if (_initialMetadataCallback) { + _initialMetadataCallback(initialMetadata); + } + }); +} + +- (void)receivedRawMessage:(GPBMessage *_Nullable)message { + dispatch_async(_dispatchQueue, ^{ + if (_messageCallback) { + _messageCallback(message); + } + }); +} + +- (void)closedWithTrailingMetadata:(NSDictionary *_Nullable)trailingMetadata + error:(NSError *_Nullable)error { + dispatch_async(_dispatchQueue, ^{ + if (_closeCallback) { + _closeCallback(trailingMetadata, error); + } + }); +} + +- (dispatch_queue_t)dispatchQueue { + return _dispatchQueue; +} + +@end + #pragma mark Tests /** @@ -237,6 +296,55 @@ static GRPCProtoMethod *kFullDuplexCallMethod; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testMetadataWithV2API { + __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:TEST_TIMEOUT handler:nil]; +} + - (void)testResponseMetadataKVO { __weak XCTestExpectation *response = [self expectationWithDescription:@"Empty response received."]; @@ -329,6 +437,75 @@ static GRPCProtoMethod *kFullDuplexCallMethod; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testUserAgentPrefixWithV2API { + __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:TEST_TIMEOUT handler:nil]; +} + - (void)testTrailers { __weak XCTestExpectation *response = [self expectationWithDescription:@"Empty response received."]; @@ -420,6 +597,52 @@ static GRPCProtoMethod *kFullDuplexCallMethod; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testIdempotentProtoRPCWithV2API { + __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."]; + __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + request.responseSize = 100; + 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:TEST_TIMEOUT handler:nil]; +} + - (void)testAlternateDispatchQueue { const int32_t kPayloadSize = 100; RMTSimpleRequest *request = [RMTSimpleRequest message]; @@ -509,6 +732,37 @@ static GRPCProtoMethod *kFullDuplexCallMethod; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testTimeoutWithV2API { + __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:^(id 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:TEST_TIMEOUT handler:nil]; +} + - (int)findFreePort { struct sockaddr_in addr; unsigned int addr_len = sizeof(addr); @@ -555,7 +809,7 @@ 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:@"" + path:@"/dummyPath" requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; [GRPCCall setMinConnectTimeout:timeout * 1000 initialBackoff:backoff * 1000 @@ -580,15 +834,54 @@ static GRPCProtoMethod *kFullDuplexCallMethod; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testTimeoutBackoffWithOptionsWithTimeout:(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:^(id 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:TEST_TIMEOUT handler:nil]; +} + // 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]; + [self testTimeoutBackoffWithOptionsWithTimeout:0.7 Backoff:0.4]; } -- (void)testTimeoutBackoff3 { +- (void)testTimeoutBackoff2 { [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.7]; + [self testTimeoutBackoffWithOptionsWithTimeout:0.3 Backoff:0.8]; } - (void)testErrorDebugInformation { 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..c42718f15e 100644 --- a/src/objective-c/tests/InteropTests.m +++ b/src/objective-c/tests/InteropTests.m @@ -74,6 +74,65 @@ 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)receivedInitialMetadata:(NSDictionary *_Nullable)initialMetadata { + dispatch_async(_dispatchQueue, ^{ + if (_initialMetadataCallback) { + _initialMetadataCallback(initialMetadata); + } + }); +} + +- (void)receivedProtoMessage:(GPBMessage *_Nullable)message { + dispatch_async(_dispatchQueue, ^{ + if (_messageCallback) { + _messageCallback(message); + } + }); +} + +- (void)closedWithTrailingMetadata:(NSDictionary *_Nullable)trailingMetadata + error:(NSError *_Nullable)error { + dispatch_async(_dispatchQueue, ^{ + if (_closeCallback) { + _closeCallback(trailingMetadata, error); + } + }); +} + +- (dispatch_queue_t)dispatchQueue { + return _dispatchQueue; +} + +@end + #pragma mark Tests @implementation InteropTests { @@ -91,6 +150,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 +180,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 +202,35 @@ BOOL isRemoteInteropTest(NSString *host) { [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testEmptyUnaryRPCWithV2API { + XCTAssertNotNil([[self class] host]); + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"EmptyUnary"]; + + GPBEmpty *request = [GPBEmpty message]; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = [[self class] transportType]; + options.PEMRootCertificates = [[self class] PEMRootCertificates]; + options.hostNameOverride = [[self class] hostNameOverride]; + + [_service + emptyCallWithMessage:request + responseHandler:[[InteropTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + messageCallback:^(id message) { + if (message) { + id expectedResponse = [GPBEmpty message]; + XCTAssertEqualObjects(message, expectedResponse); + [expectation fulfill]; + } + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNil(error, @"Unexpected error: %@", error); + }] + callOptions:options]; + [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]; @@ -156,7 +254,7 @@ BOOL isRemoteInteropTest(NSString *host) { } - (void)testPacketCoalescing { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"]; RMTSimpleRequest *request = [RMTSimpleRequest message]; @@ -195,7 +293,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 +311,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 +337,7 @@ BOOL isRemoteInteropTest(NSString *host) { } - (void)testResponsesOver4MBAreAcceptedIfOptedIn { - XCTAssertNotNil(self.class.host); + XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = [self expectationWithDescription:@"HigherResponseSizeLimit"]; @@ -247,7 +345,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 +358,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 +393,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 +432,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 +478,59 @@ BOOL isRemoteInteropTest(NSString *host) { [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testPingPongRPCWithV2API { + XCTAssertNotNil([[self class] host]); + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"PingPong"]; + + 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 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 +543,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 +567,30 @@ BOOL isRemoteInteropTest(NSString *host) { [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testCancelAfterBeginRPCWithV2API { + 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. + __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 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 +625,45 @@ 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 writeMessage:request]; + [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 +695,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 +706,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 +725,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..e87aba235a 100644 --- a/src/objective-c/tests/Podfile +++ b/src/objective-c/tests/Podfile @@ -14,6 +14,8 @@ GRPC_LOCAL_SRC = '../../..' InteropTestsLocalSSL InteropTestsLocalCleartext InteropTestsRemoteWithCronet + InteropTestsMultipleChannels + InteropTestsCallOptions UnitTests ).each do |target_name| target target_name do @@ -30,7 +32,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 +74,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. diff --git a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj index f0d8123263..104fd0a4ea 100644 --- a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj +++ b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj @@ -11,14 +11,23 @@ 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 */; }; + 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,6 +62,8 @@ 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 */; }; 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 */; }; @@ -68,6 +79,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 +100,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,20 +176,26 @@ 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>"; }; + 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>"; }; 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>"; }; @@ -169,6 +207,9 @@ 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>"; }; + 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 +217,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 +248,32 @@ 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>"; }; + 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>"; }; 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 +283,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 +303,15 @@ ); 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 +330,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,6 +452,9 @@ 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 */, ); name = Frameworks; @@ -422,6 +509,18 @@ 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 */, @@ -439,6 +538,15 @@ path = UnitTests; sourceTree = "<group>"; }; + 5E7D71B3210B9EC9001EA6BA /* InteropTestsCallOptions */ = { + isa = PBXGroup; + children = ( + 5E7D71B4210B9EC9001EA6BA /* InteropTestsCallOptions.m */, + 5E7D71B6210B9EC9001EA6BA /* Info.plist */, + ); + path = InteropTestsCallOptions; + sourceTree = "<group>"; + }; 5E8A5DA51D3840B4000F8BC4 /* CoreCronetEnd2EndTests */ = { isa = PBXGroup; children = ( @@ -456,6 +564,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,6 +600,9 @@ 5E8A5DA51D3840B4000F8BC4 /* CoreCronetEnd2EndTests */, 5EE84BF21D4717E40050C6CC /* InteropTestsRemoteWithCronet */, 5EAD6D251E27047400002378 /* CronetUnitTests */, + 5EB2A2E52107DED300EB4B69 /* ChannelTests */, + 5EB2A2F62109284500EB4B69 /* InteropTestsMultipleChannels */, + 5E7D71B3210B9EC9001EA6BA /* InteropTestsCallOptions */, 5E0282E7215AA697007AC99D /* UnitTests */, 635697C81B14FC11007A7283 /* Products */, 51E4650F34F854F41FF053B3 /* Pods */, @@ -495,6 +625,9 @@ 5EC5E421208177CC000EF4AD /* InteropTestsRemoteCFStream.xctest */, 5EC5E4312081856B000EF4AD /* InteropTestsLocalCleartextCFStream.xctest */, 5EC5E442208185CE000EF4AD /* InteropTestsLocalSSLCFStream.xctest */, + 5EB2A2E42107DED300EB4B69 /* ChannelTests.xctest */, + 5EB2A2F52109284500EB4B69 /* InteropTestsMultipleChannels.xctest */, + 5E7D71B2210B9EC8001EA6BA /* InteropTestsCallOptions.xctest */, 5E0282E6215AA697007AC99D /* UnitTests.xctest */, ); name = Products; @@ -548,6 +681,26 @@ productReference = 5E0282E6215AA697007AC99D /* UnitTests.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 +741,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 +990,24 @@ CreatedOnToolsVersion = 9.2; 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,6 +1067,9 @@ 5EC5E420208177CC000EF4AD /* InteropTestsRemoteCFStream */, 5EC5E4302081856B000EF4AD /* InteropTestsLocalCleartextCFStream */, 5EC5E441208185CE000EF4AD /* InteropTestsLocalSSLCFStream */, + 5EB2A2E32107DED300EB4B69 /* ChannelTests */, + 5EB2A2F42109284500EB4B69 /* InteropTestsMultipleChannels */, + 5E7D71B1210B9EC8001EA6BA /* InteropTestsCallOptions */, 5E0282E5215AA697007AC99D /* UnitTests */, ); }; @@ -874,6 +1083,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5E7D71B0210B9EC8001EA6BA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5E8A5DA21D3840B4000F8BC4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -888,6 +1104,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 +1188,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 +1260,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 +1386,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 +1458,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; @@ -1418,6 +1775,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5E7D71AE210B9EC8001EA6BA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E7D71B5210B9EC9001EA6BA /* InteropTestsCallOptions.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5E8A5DA01D3840B4000F8BC4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1434,6 +1799,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 +1918,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 +1933,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 +2306,136 @@ }; name = Test; }; + 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 +2538,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 +2826,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 +3648,17 @@ 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 +3681,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/ChannelTests.xcscheme b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/ChannelTests.xcscheme new file mode 100644 index 0000000000..16ae481123 --- /dev/null +++ b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/ChannelTests.xcscheme @@ -0,0 +1,92 @@ +<?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" + language = "" + 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" + language = "" + 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..118220ce4b 100755 --- a/src/objective-c/tests/run_tests.sh +++ b/src/objective-c/tests/run_tests.sh @@ -173,4 +173,14 @@ 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)" - + exit 0 diff --git a/templates/gRPC.podspec.template b/templates/gRPC.podspec.template index a3190c2d8e..a3ed1d7858 100644 --- a/templates/gRPC.podspec.template +++ b/templates/gRPC.podspec.template @@ -60,7 +60,7 @@ ss.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}" ss.exclude_files = "#{src_dir}/GRPCCall+GID.{h,m}" - ss.private_header_files = "#{src_dir}/private/*.h" + ss.private_header_files = "#{src_dir}/private/*.h", "#{src_dir}/internal/GRPCCallOptions+Internal.h" ss.dependency 'gRPC-Core', version end |