aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gRPC.podspec2
-rw-r--r--include/grpc/impl/codegen/grpc_types.h4
-rw-r--r--src/compiler/objective_c_generator.cc103
-rw-r--r--src/compiler/objective_c_generator.h7
-rw-r--r--src/compiler/objective_c_plugin.cc21
-rw-r--r--src/core/ext/transport/cronet/client/secure/cronet_channel_create.cc14
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+ChannelArg.h34
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+ChannelArg.m5
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h10
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+Cronet.h13
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+OAuth2.h29
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+Tests.h25
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+Tests.m4
-rw-r--r--src/objective-c/GRPCClient/GRPCCall.h192
-rw-r--r--src/objective-c/GRPCClient/GRPCCall.m357
-rw-r--r--src/objective-c/GRPCClient/GRPCCallOptions.h343
-rw-r--r--src/objective-c/GRPCClient/GRPCCallOptions.m521
-rw-r--r--src/objective-c/GRPCClient/internal/GRPCCallOptions+internal.h39
-rw-r--r--src/objective-c/GRPCClient/private/ChannelArgsUtil.h38
-rw-r--r--src/objective-c/GRPCClient/private/ChannelArgsUtil.m95
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannel.h54
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannel.m394
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannelFactory.h34
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannelPool.h79
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannelPool.m241
-rw-r--r--src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h36
-rw-r--r--src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m91
-rw-r--r--src/objective-c/GRPCClient/private/GRPCHost.h29
-rw-r--r--src/objective-c/GRPCClient/private/GRPCHost.m288
-rw-r--r--src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h35
-rw-r--r--src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m48
-rw-r--r--src/objective-c/GRPCClient/private/GRPCRequestHeaders.m4
-rw-r--r--src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h38
-rw-r--r--src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m136
-rw-r--r--src/objective-c/GRPCClient/private/GRPCWrappedCall.h3
-rw-r--r--src/objective-c/GRPCClient/private/GRPCWrappedCall.m24
-rw-r--r--src/objective-c/ProtoRPC/ProtoRPC.h105
-rw-r--r--src/objective-c/ProtoRPC/ProtoRPC.m181
-rw-r--r--src/objective-c/ProtoRPC/ProtoService.h35
-rw-r--r--src/objective-c/ProtoRPC/ProtoService.m47
-rw-r--r--src/objective-c/tests/ChannelTests/ChannelPoolTest.m126
-rw-r--r--src/objective-c/tests/ChannelTests/ChannelTests.m93
-rw-r--r--src/objective-c/tests/ChannelTests/Info.plist22
-rw-r--r--src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm6
-rw-r--r--src/objective-c/tests/CronetUnitTests/CronetUnitTests.m13
-rw-r--r--src/objective-c/tests/GRPCClientTests.m299
-rw-r--r--src/objective-c/tests/InteropTests.h21
-rw-r--r--src/objective-c/tests/InteropTests.m248
-rw-r--r--src/objective-c/tests/InteropTestsCallOptions/Info.plist22
-rw-r--r--src/objective-c/tests/InteropTestsCallOptions/InteropTestsCallOptions.m116
-rw-r--r--src/objective-c/tests/InteropTestsLocalCleartext.m12
-rw-r--r--src/objective-c/tests/InteropTestsLocalSSL.m18
-rw-r--r--src/objective-c/tests/InteropTestsMultipleChannels/Info.plist22
-rw-r--r--src/objective-c/tests/InteropTestsMultipleChannels/InteropTestsMultipleChannels.m259
-rw-r--r--src/objective-c/tests/InteropTestsRemote.m18
-rw-r--r--src/objective-c/tests/Podfile10
-rw-r--r--src/objective-c/tests/Tests.xcodeproj/project.pbxproj822
-rw-r--r--src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/ChannelTests.xcscheme92
-rw-r--r--src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsCallOptions.xcscheme56
-rw-r--r--src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsLocalCleartext.xcscheme8
-rw-r--r--src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsMultipleChannels.xcscheme56
-rw-r--r--src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsRemote.xcscheme2
-rwxr-xr-xsrc/objective-c/tests/run_tests.sh10
-rw-r--r--templates/gRPC.podspec.template2
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