Merge remote-tracking branch 'upstream/master' into lb_trailing_metadata

reviewable/pr17577/r2
Mark D. Roth 6 years ago
commit 925323c41e
  1. 4
      BUILD
  2. 52
      CMakeLists.txt
  3. 53
      Makefile
  4. 6
      Rakefile
  5. 25
      build.yaml
  6. 3
      doc/g_stands_for.md
  7. 5
      doc/python/sphinx/grpc.rst
  8. 5
      examples/ruby/greeter_server.rb
  9. 5
      examples/ruby/route_guide/route_guide_server.rb
  10. 4
      gRPC-C++.podspec
  11. 4
      gRPC-Core.podspec
  12. 2
      gRPC-ProtoRPC.podspec
  13. 2
      gRPC-RxLibrary.podspec
  14. 4
      gRPC.podspec
  15. 6
      include/grpc/impl/codegen/grpc_types.h
  16. 1
      include/grpc/impl/codegen/port_platform.h
  17. 108
      include/grpcpp/impl/codegen/call_op_set.h
  18. 12
      include/grpcpp/impl/codegen/client_callback.h
  19. 2
      include/grpcpp/impl/codegen/client_unary_call.h
  20. 31
      include/grpcpp/impl/codegen/interceptor.h
  21. 88
      include/grpcpp/impl/codegen/interceptor_common.h
  22. 4
      include/grpcpp/impl/codegen/method_handler_impl.h
  23. 12
      include/grpcpp/impl/codegen/server_callback.h
  24. 2
      include/grpcpp/impl/codegen/server_interface.h
  25. 10
      include/grpcpp/impl/codegen/sync_stream.h
  26. 4
      package.xml
  27. 2
      setup.py
  28. 100
      src/compiler/objective_c_generator.cc
  29. 7
      src/compiler/objective_c_generator.h
  30. 21
      src/compiler/objective_c_plugin.cc
  31. 13
      src/core/ext/filters/client_channel/subchannel.cc
  32. 5
      src/core/ext/filters/client_channel/subchannel.h
  33. 17
      src/core/ext/filters/client_channel/subchannel_index.cc
  34. 7
      src/core/ext/transport/chttp2/transport/chttp2_transport.cc
  35. 38
      src/core/ext/transport/chttp2/transport/context_list.cc
  36. 35
      src/core/ext/transport/chttp2/transport/context_list.h
  37. 16
      src/core/ext/transport/cronet/client/secure/cronet_channel_create.cc
  38. 97
      src/core/lib/iomgr/cfstream_handle.cc
  39. 2
      src/core/lib/iomgr/cfstream_handle.h
  40. 1
      src/core/lib/iomgr/resource_quota.cc
  41. 94
      src/core/lib/iomgr/tcp_windows.cc
  42. 5
      src/core/lib/surface/channel_init.h
  43. 104
      src/core/lib/surface/server.cc
  44. 2
      src/core/lib/surface/version.cc
  45. 1
      src/core/lib/transport/metadata.cc
  46. 2
      src/cpp/common/channel_arguments.cc
  47. 5
      src/cpp/common/channel_filter.h
  48. 2
      src/cpp/common/version_cc.cc
  49. 4
      src/cpp/server/server_cc.cc
  50. 2
      src/csharp/Grpc.Core/Interceptors/ServerServiceDefinitionExtensions.cs
  51. 2
      src/csharp/Grpc.Core/Version.csproj.include
  52. 4
      src/csharp/Grpc.Core/VersionInfo.cs
  53. 2
      src/csharp/build_packages_dotnetcli.bat
  54. 2
      src/csharp/build_unitypackage.bat
  55. 2
      src/objective-c/!ProtoCompiler-gRPCPlugin.podspec
  56. 34
      src/objective-c/GRPCClient/GRPCCall+ChannelArg.h
  57. 5
      src/objective-c/GRPCClient/GRPCCall+ChannelArg.m
  58. 10
      src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h
  59. 13
      src/objective-c/GRPCClient/GRPCCall+Cronet.h
  60. 29
      src/objective-c/GRPCClient/GRPCCall+OAuth2.h
  61. 25
      src/objective-c/GRPCClient/GRPCCall+Tests.h
  62. 4
      src/objective-c/GRPCClient/GRPCCall+Tests.m
  63. 174
      src/objective-c/GRPCClient/GRPCCall.h
  64. 470
      src/objective-c/GRPCClient/GRPCCall.m
  65. 348
      src/objective-c/GRPCClient/GRPCCallOptions.h
  66. 525
      src/objective-c/GRPCClient/GRPCCallOptions.m
  67. 39
      src/objective-c/GRPCClient/internal/GRPCCallOptions+Internal.h
  68. 38
      src/objective-c/GRPCClient/private/ChannelArgsUtil.h
  69. 94
      src/objective-c/GRPCClient/private/ChannelArgsUtil.m
  70. 71
      src/objective-c/GRPCClient/private/GRPCChannel.h
  71. 339
      src/objective-c/GRPCClient/private/GRPCChannel.m
  72. 34
      src/objective-c/GRPCClient/private/GRPCChannelFactory.h
  73. 51
      src/objective-c/GRPCClient/private/GRPCChannelPool+Test.h
  74. 101
      src/objective-c/GRPCClient/private/GRPCChannelPool.h
  75. 276
      src/objective-c/GRPCClient/private/GRPCChannelPool.m
  76. 4
      src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m
  77. 36
      src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h
  78. 79
      src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m
  79. 29
      src/objective-c/GRPCClient/private/GRPCHost.h
  80. 288
      src/objective-c/GRPCClient/private/GRPCHost.m
  81. 35
      src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h
  82. 43
      src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m
  83. 4
      src/objective-c/GRPCClient/private/GRPCRequestHeaders.m
  84. 38
      src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h
  85. 135
      src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m
  86. 14
      src/objective-c/GRPCClient/private/GRPCWrappedCall.h
  87. 122
      src/objective-c/GRPCClient/private/GRPCWrappedCall.m
  88. 2
      src/objective-c/GRPCClient/private/version.h
  89. 118
      src/objective-c/ProtoRPC/ProtoRPC.h
  90. 227
      src/objective-c/ProtoRPC/ProtoRPC.m
  91. 35
      src/objective-c/ProtoRPC/ProtoService.h
  92. 67
      src/objective-c/ProtoRPC/ProtoService.m
  93. 10
      src/objective-c/examples/SwiftSample/ViewController.swift
  94. 478
      src/objective-c/tests/APIv2Tests/APIv2Tests.m
  95. 22
      src/objective-c/tests/APIv2Tests/Info.plist
  96. 63
      src/objective-c/tests/ChannelTests/ChannelPoolTest.m
  97. 112
      src/objective-c/tests/ChannelTests/ChannelTests.m
  98. 22
      src/objective-c/tests/ChannelTests/Info.plist
  99. 6
      src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm
  100. 13
      src/objective-c/tests/CronetUnitTests/CronetUnitTests.m
  101. Some files were not shown because too many files have changed in this diff Show More

@ -64,11 +64,11 @@ config_setting(
)
# This should be updated along with build.yaml
g_stands_for = "goose"
g_stands_for = "gold"
core_version = "7.0.0-dev"
version = "1.18.0-dev"
version = "1.19.0-dev"
GPR_PUBLIC_HDRS = [
"include/grpc/support/alloc.h",

@ -24,7 +24,7 @@
cmake_minimum_required(VERSION 2.8)
set(PACKAGE_NAME "grpc")
set(PACKAGE_VERSION "1.18.0-dev")
set(PACKAGE_VERSION "1.19.0-dev")
set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}")
set(PACKAGE_TARNAME "${PACKAGE_NAME}-${PACKAGE_VERSION}")
set(PACKAGE_BUGREPORT "https://github.com/grpc/grpc/issues/")
@ -94,7 +94,7 @@ endif()
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
add_definitions(-DPB_FIELD_16BIT)
add_definitions(-DPB_FIELD_32BIT)
if (MSVC)
include(cmake/msvc_static_runtime.cmake)
@ -576,6 +576,9 @@ endif()
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
add_dependencies(buildtests_cxx bm_pollset)
endif()
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
add_dependencies(buildtests_cxx bm_timer)
endif()
add_dependencies(buildtests_cxx byte_stream_test)
add_dependencies(buildtests_cxx channel_arguments_test)
add_dependencies(buildtests_cxx channel_filter_test)
@ -11790,6 +11793,51 @@ target_link_libraries(bm_pollset
)
endif()
endif (gRPC_BUILD_TESTS)
if (gRPC_BUILD_TESTS)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
add_executable(bm_timer
test/cpp/microbenchmarks/bm_timer.cc
third_party/googletest/googletest/src/gtest-all.cc
third_party/googletest/googlemock/src/gmock-all.cc
)
target_include_directories(bm_timer
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
PRIVATE ${_gRPC_NANOPB_INCLUDE_DIR}
PRIVATE third_party/googletest/googletest/include
PRIVATE third_party/googletest/googletest
PRIVATE third_party/googletest/googlemock/include
PRIVATE third_party/googletest/googlemock
PRIVATE ${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(bm_timer
${_gRPC_PROTOBUF_LIBRARIES}
${_gRPC_ALLTARGETS_LIBRARIES}
grpc_benchmark
${_gRPC_BENCHMARK_LIBRARIES}
grpc++_test_util_unsecure
grpc_test_util_unsecure
grpc++_unsecure
grpc_unsecure
gpr
grpc++_test_config
${_gRPC_GFLAGS_LIBRARIES}
)
endif()
endif (gRPC_BUILD_TESTS)
if (gRPC_BUILD_TESTS)

@ -438,8 +438,8 @@ Q = @
endif
CORE_VERSION = 7.0.0-dev
CPP_VERSION = 1.18.0-dev
CSHARP_VERSION = 1.18.0-dev
CPP_VERSION = 1.19.0-dev
CSHARP_VERSION = 1.19.0-dev
CPPFLAGS_NO_ARCH += $(addprefix -I, $(INCLUDES)) $(addprefix -D, $(DEFINES))
CPPFLAGS += $(CPPFLAGS_NO_ARCH) $(ARCH_FLAGS)
@ -1150,6 +1150,7 @@ bm_fullstack_trickle: $(BINDIR)/$(CONFIG)/bm_fullstack_trickle
bm_fullstack_unary_ping_pong: $(BINDIR)/$(CONFIG)/bm_fullstack_unary_ping_pong
bm_metadata: $(BINDIR)/$(CONFIG)/bm_metadata
bm_pollset: $(BINDIR)/$(CONFIG)/bm_pollset
bm_timer: $(BINDIR)/$(CONFIG)/bm_timer
byte_stream_test: $(BINDIR)/$(CONFIG)/byte_stream_test
channel_arguments_test: $(BINDIR)/$(CONFIG)/channel_arguments_test
channel_filter_test: $(BINDIR)/$(CONFIG)/channel_filter_test
@ -1661,6 +1662,7 @@ buildtests_cxx: privatelibs_cxx \
$(BINDIR)/$(CONFIG)/bm_fullstack_unary_ping_pong \
$(BINDIR)/$(CONFIG)/bm_metadata \
$(BINDIR)/$(CONFIG)/bm_pollset \
$(BINDIR)/$(CONFIG)/bm_timer \
$(BINDIR)/$(CONFIG)/byte_stream_test \
$(BINDIR)/$(CONFIG)/channel_arguments_test \
$(BINDIR)/$(CONFIG)/channel_filter_test \
@ -1846,6 +1848,7 @@ buildtests_cxx: privatelibs_cxx \
$(BINDIR)/$(CONFIG)/bm_fullstack_unary_ping_pong \
$(BINDIR)/$(CONFIG)/bm_metadata \
$(BINDIR)/$(CONFIG)/bm_pollset \
$(BINDIR)/$(CONFIG)/bm_timer \
$(BINDIR)/$(CONFIG)/byte_stream_test \
$(BINDIR)/$(CONFIG)/channel_arguments_test \
$(BINDIR)/$(CONFIG)/channel_filter_test \
@ -2296,6 +2299,8 @@ test_cxx: buildtests_cxx
$(Q) $(BINDIR)/$(CONFIG)/bm_metadata || ( echo test bm_metadata failed ; exit 1 )
$(E) "[RUN] Testing bm_pollset"
$(Q) $(BINDIR)/$(CONFIG)/bm_pollset || ( echo test bm_pollset failed ; exit 1 )
$(E) "[RUN] Testing bm_timer"
$(Q) $(BINDIR)/$(CONFIG)/bm_timer || ( echo test bm_timer failed ; exit 1 )
$(E) "[RUN] Testing byte_stream_test"
$(Q) $(BINDIR)/$(CONFIG)/byte_stream_test || ( echo test byte_stream_test failed ; exit 1 )
$(E) "[RUN] Testing channel_arguments_test"
@ -16796,6 +16801,50 @@ endif
endif
BM_TIMER_SRC = \
test/cpp/microbenchmarks/bm_timer.cc \
BM_TIMER_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(BM_TIMER_SRC))))
ifeq ($(NO_SECURE),true)
# You can't build secure targets if you don't have OpenSSL.
$(BINDIR)/$(CONFIG)/bm_timer: openssl_dep_error
else
ifeq ($(NO_PROTOBUF),true)
# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.5.0+.
$(BINDIR)/$(CONFIG)/bm_timer: protobuf_dep_error
else
$(BINDIR)/$(CONFIG)/bm_timer: $(PROTOBUF_DEP) $(BM_TIMER_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_benchmark.a $(LIBDIR)/$(CONFIG)/libbenchmark.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
$(E) "[LD] Linking $@"
$(Q) mkdir -p `dirname $@`
$(Q) $(LDXX) $(LDFLAGS) $(BM_TIMER_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_benchmark.a $(LIBDIR)/$(CONFIG)/libbenchmark.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/bm_timer
endif
endif
$(BM_TIMER_OBJS): CPPFLAGS += -Ithird_party/benchmark/include -DHAVE_POSIX_REGEX
$(OBJDIR)/$(CONFIG)/test/cpp/microbenchmarks/bm_timer.o: $(LIBDIR)/$(CONFIG)/libgrpc_benchmark.a $(LIBDIR)/$(CONFIG)/libbenchmark.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
deps_bm_timer: $(BM_TIMER_OBJS:.o=.dep)
ifneq ($(NO_SECURE),true)
ifneq ($(NO_DEPS),true)
-include $(BM_TIMER_OBJS:.o=.dep)
endif
endif
BYTE_STREAM_TEST_SRC = \
test/core/transport/byte_stream_test.cc \

@ -105,7 +105,7 @@ task 'dlls' do
env_comp = "CC=#{opt[:cross]}-gcc "
env_comp += "CXX=#{opt[:cross]}-g++ "
env_comp += "LD=#{opt[:cross]}-gcc "
docker_for_windows "gem update --system --no-ri --no-doc && #{env} #{env_comp} make -j #{out} && #{opt[:cross]}-strip -x -S #{out} && cp #{out} #{opt[:out]}"
docker_for_windows "gem update --system --no-document && #{env} #{env_comp} make -j #{out} && #{opt[:cross]}-strip -x -S #{out} && cp #{out} #{opt[:out]}"
end
end
@ -124,10 +124,10 @@ task 'gem:native' do
"invoked on macos with ruby #{RUBY_VERSION}. The ruby macos artifact " \
"build should be running on ruby 2.5."
end
system "rake cross native gem RUBY_CC_VERSION=2.5.0:2.4.0:2.3.0:2.2.2:2.1.6:2.0.0 V=#{verbose} GRPC_CONFIG=#{grpc_config}"
system "rake cross native gem RUBY_CC_VERSION=2.6.0:2.5.0:2.4.0:2.3.0:2.2.2 V=#{verbose} GRPC_CONFIG=#{grpc_config}"
else
Rake::Task['dlls'].execute
docker_for_windows "gem update --system --no-ri --no-doc && bundle && rake cross native gem RUBY_CC_VERSION=2.5.0:2.4.0:2.3.0:2.2.2:2.1.6:2.0.0 V=#{verbose} GRPC_CONFIG=#{grpc_config}"
docker_for_windows "gem update --system --no-document && bundle && rake cross native gem RUBY_CC_VERSION=2.6.0:2.5.0:2.4.0:2.3.0:2.2.2 V=#{verbose} GRPC_CONFIG=#{grpc_config}"
end
end

@ -13,8 +13,8 @@ settings:
'#09': Per-language overrides are possible with (eg) ruby_version tag here
'#10': See the expand_version.py for all the quirks here
core_version: 7.0.0-dev
g_stands_for: goose
version: 1.18.0-dev
g_stands_for: gold
version: 1.19.0-dev
filegroups:
- name: alts_proto
headers:
@ -4240,6 +4240,27 @@ targets:
- mac
- linux
- posix
- name: bm_timer
build: test
language: c++
src:
- test/cpp/microbenchmarks/bm_timer.cc
deps:
- grpc_benchmark
- benchmark
- grpc++_test_util_unsecure
- grpc_test_util_unsecure
- grpc++_unsecure
- grpc_unsecure
- gpr
- grpc++_test_config
benchmark: true
defaults: benchmark
platforms:
- mac
- linux
- posix
uses_polling: false
- name: byte_stream_test
gtest: true
build: test

@ -17,4 +17,5 @@
- 1.15 'g' stands for ['glider'](https://github.com/grpc/grpc/tree/v1.15.x)
- 1.16 'g' stands for ['gao'](https://github.com/grpc/grpc/tree/v1.16.x)
- 1.17 'g' stands for ['gizmo'](https://github.com/grpc/grpc/tree/v1.17.x)
- 1.18 'g' stands for ['goose'](https://github.com/grpc/grpc/tree/master)
- 1.18 'g' stands for ['goose'](https://github.com/grpc/grpc/tree/v1.18.x)
- 1.19 'g' stands for ['gold'](https://github.com/grpc/grpc/tree/master)

@ -19,6 +19,11 @@ Go to `gRPC Python Examples <https://github.com/grpc/grpc/tree/master/examples/p
Module Contents
---------------
Version
^^^^^^^
The version string is available as :code:`grpc.__version__`.
Create Client
^^^^^^^^^^^^^

@ -39,7 +39,10 @@ def main
s = GRPC::RpcServer.new
s.add_http2_port('0.0.0.0:50051', :this_port_is_insecure)
s.handle(GreeterServer)
s.run_till_terminated
# Runs the server with SIGHUP, SIGINT and SIGQUIT signal handlers to
# gracefully shutdown.
# User could also choose to run server via call to run_till_terminated
s.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT'])
end
main

@ -172,7 +172,10 @@ def main
s.add_http2_port(port, :this_port_is_insecure)
GRPC.logger.info("... running insecurely on #{port}")
s.handle(ServerImpl.new(feature_db))
s.run_till_terminated
# Runs the server with SIGHUP, SIGINT and SIGQUIT signal handlers to
# gracefully shutdown.
# User could also choose to run server via call to run_till_terminated
s.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT'])
end
main

@ -23,7 +23,7 @@
Pod::Spec.new do |s|
s.name = 'gRPC-C++'
# TODO (mxyan): use version that match gRPC version when pod is stabilized
# version = '1.18.0-dev'
# version = '1.19.0-dev'
version = '0.0.6-dev'
s.version = version
s.summary = 'gRPC C++ library'
@ -31,7 +31,7 @@ Pod::Spec.new do |s|
s.license = 'Apache License, Version 2.0'
s.authors = { 'The gRPC contributors' => 'grpc-packages@google.com' }
grpc_version = '1.18.0-dev'
grpc_version = '1.19.0-dev'
s.source = {
:git => 'https://github.com/grpc/grpc.git',

@ -22,7 +22,7 @@
Pod::Spec.new do |s|
s.name = 'gRPC-Core'
version = '1.18.0-dev'
version = '1.19.0-dev'
s.version = version
s.summary = 'Core cross-platform gRPC library, written in C'
s.homepage = 'https://grpc.io'
@ -93,7 +93,7 @@ Pod::Spec.new do |s|
}
s.default_subspecs = 'Interface', 'Implementation'
s.compiler_flags = '-DGRPC_ARES=0', '-DPB_FIELD_16BIT'
s.compiler_flags = '-DGRPC_ARES=0', '-DPB_FIELD_32BIT'
s.libraries = 'c++'
# Like many other C libraries, gRPC-Core has its public headers under `include/<libname>/` and its

@ -21,7 +21,7 @@
Pod::Spec.new do |s|
s.name = 'gRPC-ProtoRPC'
version = '1.18.0-dev'
version = '1.19.0-dev'
s.version = version
s.summary = 'RPC library for Protocol Buffers, based on gRPC'
s.homepage = 'https://grpc.io'

@ -21,7 +21,7 @@
Pod::Spec.new do |s|
s.name = 'gRPC-RxLibrary'
version = '1.18.0-dev'
version = '1.19.0-dev'
s.version = version
s.summary = 'Reactive Extensions library for iOS/OSX.'
s.homepage = 'https://grpc.io'

@ -20,7 +20,7 @@
Pod::Spec.new do |s|
s.name = 'gRPC'
version = '1.18.0-dev'
version = '1.19.0-dev'
s.version = version
s.summary = 'gRPC client library for iOS/OSX'
s.homepage = 'https://grpc.io'
@ -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/*.h"
ss.dependency 'gRPC-Core', version
end

@ -163,7 +163,7 @@ typedef struct {
/** Maximum time that a channel may exist. Int valued, milliseconds.
* INT_MAX means unlimited. */
#define GRPC_ARG_MAX_CONNECTION_AGE_MS "grpc.max_connection_age_ms"
/** Grace period after the chennel reaches its max age. Int valued,
/** Grace period after the channel reaches its max age. Int valued,
milliseconds. INT_MAX means unlimited. */
#define GRPC_ARG_MAX_CONNECTION_AGE_GRACE_MS "grpc.max_connection_age_grace_ms"
/** Enable/disable support for per-message compression. Defaults to 1, unless
@ -355,6 +355,10 @@ typedef struct {
* is 10000. Setting this to "0" will disable c-ares query timeouts
* entirely. */
#define GRPC_ARG_DNS_ARES_QUERY_TIMEOUT_MS "grpc.dns_ares_query_timeout"
/** 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

@ -121,6 +121,7 @@
#else /* _LP64 */
#define GPR_ARCH_32 1
#endif /* _LP64 */
#include <linux/version.h>
#elif defined(ANDROID) || defined(__ANDROID__)
#define GPR_PLATFORM_STRING "android"
#define GPR_ANDROID 1

@ -303,9 +303,29 @@ class CallOpSendMessage {
template <class M>
Status SendMessage(const M& message) GRPC_MUST_USE_RESULT;
/// Send \a message using \a options for the write. The \a options are cleared
/// after use. This form of SendMessage allows gRPC to reference \a message
/// beyond the lifetime of SendMessage.
template <class M>
Status SendMessagePtr(const M* message,
WriteOptions options) GRPC_MUST_USE_RESULT;
/// This form of SendMessage allows gRPC to reference \a message beyond the
/// lifetime of SendMessage.
template <class M>
Status SendMessagePtr(const M* message) GRPC_MUST_USE_RESULT;
protected:
void AddOp(grpc_op* ops, size_t* nops) {
if (!send_buf_.Valid() || hijacked_) return;
if (msg_ == nullptr && !send_buf_.Valid()) return;
if (hijacked_) {
serializer_ = nullptr;
return;
}
if (msg_ != nullptr) {
GPR_CODEGEN_ASSERT(serializer_(msg_).ok());
}
serializer_ = nullptr;
grpc_op* op = &ops[(*nops)++];
op->op = GRPC_OP_SEND_MESSAGE;
op->flags = write_options_.flags();
@ -314,21 +334,38 @@ class CallOpSendMessage {
// Flags are per-message: clear them after use.
write_options_.Clear();
}
void FinishOp(bool* status) { send_buf_.Clear(); }
void FinishOp(bool* status) {
if (msg_ == nullptr && !send_buf_.Valid()) return;
if (hijacked_ && failed_send_) {
// Hijacking interceptor failed this Op
*status = false;
} else if (!*status) {
// This Op was passed down to core and the Op failed
failed_send_ = true;
}
}
void SetInterceptionHookPoint(
InterceptorBatchMethodsImpl* interceptor_methods) {
if (!send_buf_.Valid()) return;
if (msg_ == nullptr && !send_buf_.Valid()) return;
interceptor_methods->AddInterceptionHookPoint(
experimental::InterceptionHookPoints::PRE_SEND_MESSAGE);
interceptor_methods->SetSendMessage(&send_buf_);
interceptor_methods->SetSendMessage(&send_buf_, &msg_, &failed_send_,
serializer_);
}
void SetFinishInterceptionHookPoint(
InterceptorBatchMethodsImpl* interceptor_methods) {
if (msg_ != nullptr || send_buf_.Valid()) {
interceptor_methods->AddInterceptionHookPoint(
experimental::InterceptionHookPoints::POST_SEND_MESSAGE);
}
send_buf_.Clear();
msg_ = nullptr;
// The contents of the SendMessage value that was previously set
// has had its references stolen by core's operations
interceptor_methods->SetSendMessage(nullptr);
interceptor_methods->SetSendMessage(nullptr, nullptr, &failed_send_,
nullptr);
}
void SetHijackingState(InterceptorBatchMethodsImpl* interceptor_methods) {
@ -336,25 +373,38 @@ class CallOpSendMessage {
}
private:
const void* msg_ = nullptr; // The original non-serialized message
bool hijacked_ = false;
bool failed_send_ = false;
ByteBuffer send_buf_;
WriteOptions write_options_;
std::function<Status(const void*)> serializer_;
};
template <class M>
Status CallOpSendMessage::SendMessage(const M& message, WriteOptions options) {
write_options_ = options;
bool own_buf;
// TODO(vjpai): Remove the void below when possible
// The void in the template parameter below should not be needed
// (since it should be implicit) but is needed due to an observed
// difference in behavior between clang and gcc for certain internal users
Status result = SerializationTraits<M, void>::Serialize(
message, send_buf_.bbuf_ptr(), &own_buf);
if (!own_buf) {
send_buf_.Duplicate();
}
return result;
serializer_ = [this](const void* message) {
bool own_buf;
send_buf_.Clear();
// TODO(vjpai): Remove the void below when possible
// The void in the template parameter below should not be needed
// (since it should be implicit) but is needed due to an observed
// difference in behavior between clang and gcc for certain internal users
Status result = SerializationTraits<M, void>::Serialize(
*static_cast<const M*>(message), send_buf_.bbuf_ptr(), &own_buf);
if (!own_buf) {
send_buf_.Duplicate();
}
return result;
};
// Serialize immediately only if we do not have access to the message pointer
if (msg_ == nullptr) {
Status result = serializer_(&message);
serializer_ = nullptr;
return result;
}
return Status();
}
template <class M>
@ -362,6 +412,19 @@ Status CallOpSendMessage::SendMessage(const M& message) {
return SendMessage(message, WriteOptions());
}
template <class M>
Status CallOpSendMessage::SendMessagePtr(const M* message,
WriteOptions options) {
msg_ = message;
return SendMessage(*message, options);
}
template <class M>
Status CallOpSendMessage::SendMessagePtr(const M* message) {
msg_ = message;
return SendMessage(*message, WriteOptions());
}
template <class R>
class CallOpRecvMessage {
public:
@ -410,14 +473,16 @@ class CallOpRecvMessage {
void SetInterceptionHookPoint(
InterceptorBatchMethodsImpl* interceptor_methods) {
interceptor_methods->SetRecvMessage(message_);
if (message_ == nullptr) return;
interceptor_methods->SetRecvMessage(message_, &got_message);
}
void SetFinishInterceptionHookPoint(
InterceptorBatchMethodsImpl* interceptor_methods) {
if (!got_message) return;
if (message_ == nullptr) return;
interceptor_methods->AddInterceptionHookPoint(
experimental::InterceptionHookPoints::POST_RECV_MESSAGE);
if (!got_message) interceptor_methods->SetRecvMessage(nullptr, nullptr);
}
void SetHijackingState(InterceptorBatchMethodsImpl* interceptor_methods) {
hijacked_ = true;
@ -505,20 +570,23 @@ class CallOpGenericRecvMessage {
void SetInterceptionHookPoint(
InterceptorBatchMethodsImpl* interceptor_methods) {
interceptor_methods->SetRecvMessage(message_);
if (!deserialize_) return;
interceptor_methods->SetRecvMessage(message_, &got_message);
}
void SetFinishInterceptionHookPoint(
InterceptorBatchMethodsImpl* interceptor_methods) {
if (!got_message) return;
if (!deserialize_) return;
interceptor_methods->AddInterceptionHookPoint(
experimental::InterceptionHookPoints::POST_RECV_MESSAGE);
if (!got_message) interceptor_methods->SetRecvMessage(nullptr, nullptr);
}
void SetHijackingState(InterceptorBatchMethodsImpl* interceptor_methods) {
hijacked_ = true;
if (!deserialize_) return;
interceptor_methods->AddInterceptionHookPoint(
experimental::InterceptionHookPoints::PRE_RECV_MESSAGE);
got_message = true;
}
private:

@ -73,7 +73,7 @@ class CallbackUnaryCallImpl {
CallbackWithStatusTag(call.call(), on_completion, ops);
// TODO(vjpai): Unify code with sync API as much as possible
Status s = ops->SendMessage(*request);
Status s = ops->SendMessagePtr(request);
if (!s.ok()) {
tag->force_run(s);
return;
@ -340,13 +340,13 @@ class ClientCallbackReaderWriterImpl
context_->initial_metadata_flags());
start_corked_ = false;
}
// TODO(vjpai): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(*msg).ok());
if (options.is_last_message()) {
options.set_buffer_hint();
write_ops_.ClientSendClose();
}
// TODO(vjpai): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessagePtr(msg, options).ok());
callbacks_outstanding_++;
if (started_) {
call_.PerformOps(&write_ops_);
@ -524,7 +524,7 @@ class ClientCallbackReaderImpl
: context_(context), call_(call), reactor_(reactor) {
this->BindReactor(reactor);
// TODO(vjpai): don't assert
GPR_CODEGEN_ASSERT(start_ops_.SendMessage(*request).ok());
GPR_CODEGEN_ASSERT(start_ops_.SendMessagePtr(request).ok());
start_ops_.ClientSendClose();
}
@ -649,13 +649,13 @@ class ClientCallbackWriterImpl
context_->initial_metadata_flags());
start_corked_ = false;
}
// TODO(vjpai): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(*msg).ok());
if (options.is_last_message()) {
options.set_buffer_hint();
write_ops_.ClientSendClose();
}
// TODO(vjpai): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessagePtr(msg, options).ok());
callbacks_outstanding_++;
if (started_) {
call_.PerformOps(&write_ops_);

@ -57,7 +57,7 @@ class BlockingUnaryCallImpl {
CallOpRecvInitialMetadata, CallOpRecvMessage<OutputMessage>,
CallOpClientSendClose, CallOpClientRecvStatus>
ops;
status_ = ops.SendMessage(request);
status_ = ops.SendMessagePtr(&request);
if (!status_.ok()) {
return;
}

@ -46,9 +46,10 @@ namespace experimental {
/// operation has been requested and it is available. POST_RECV means that a
/// result is available but has not yet been passed back to the application.
enum class InterceptionHookPoints {
/// The first two in this list are for clients and servers
/// The first three in this list are for clients and servers
PRE_SEND_INITIAL_METADATA,
PRE_SEND_MESSAGE,
POST_SEND_MESSAGE,
PRE_SEND_STATUS, // server only
PRE_SEND_CLOSE, // client only: WritesDone for stream; after write in unary
/// The following three are for hijacked clients only and can only be
@ -109,7 +110,24 @@ class InterceptorBatchMethods {
/// Returns a modifable ByteBuffer holding the serialized form of the message
/// that is going to be sent. Valid for PRE_SEND_MESSAGE interceptions.
/// A return value of nullptr indicates that this ByteBuffer is not valid.
virtual ByteBuffer* GetSendMessage() = 0;
virtual ByteBuffer* GetSerializedSendMessage() = 0;
/// Returns a non-modifiable pointer to the non-serialized form of the message
/// to be sent. Valid for PRE_SEND_MESSAGE interceptions. A return value of
/// nullptr indicates that this field is not valid. Also note that this is
/// only supported for sync and callback APIs at the present moment.
virtual const void* GetSendMessage() = 0;
/// Overwrites the message to be sent with \a message. \a message should be in
/// the non-serialized form expected by the method. Valid for PRE_SEND_MESSAGE
/// interceptions. Note that the interceptor is responsible for maintaining
/// the life of the message for the duration on the send operation, i.e., till
/// POST_SEND_MESSAGE.
virtual void ModifySendMessage(const void* message) = 0;
/// Checks whether the SEND MESSAGE op succeeded. Valid for POST_SEND_MESSAGE
/// interceptions.
virtual bool GetSendMessageStatus() = 0;
/// Returns a modifiable multimap of the initial metadata to be sent. Valid
/// for PRE_SEND_INITIAL_METADATA interceptions. A value of nullptr indicates
@ -156,6 +174,15 @@ class InterceptorBatchMethods {
/// started from interceptors without infinite regress through the interceptor
/// list.
virtual std::unique_ptr<ChannelInterface> GetInterceptedChannel() = 0;
/// On a hijacked RPC, an interceptor can decide to fail a PRE_RECV_MESSAGE
/// op. This would be a signal to the reader that there will be no more
/// messages, or the stream has failed or been cancelled.
virtual void FailHijackedRecvMessage() = 0;
/// On a hijacked RPC/ to-be hijacked RPC, this can be called to fail a SEND
/// MESSAGE op
virtual void FailHijackedSendMessage() = 0;
};
/// Interface for an interceptor. Interceptor authors must create a class

@ -79,7 +79,26 @@ class InterceptorBatchMethodsImpl
hooks_[static_cast<size_t>(type)] = true;
}
ByteBuffer* GetSendMessage() override { return send_message_; }
ByteBuffer* GetSerializedSendMessage() override {
GPR_CODEGEN_ASSERT(orig_send_message_ != nullptr);
if (*orig_send_message_ != nullptr) {
GPR_CODEGEN_ASSERT(serializer_(*orig_send_message_).ok());
*orig_send_message_ = nullptr;
}
return send_message_;
}
const void* GetSendMessage() override {
GPR_CODEGEN_ASSERT(orig_send_message_ != nullptr);
return *orig_send_message_;
}
void ModifySendMessage(const void* message) override {
GPR_CODEGEN_ASSERT(orig_send_message_ != nullptr);
*orig_send_message_ = message;
}
bool GetSendMessageStatus() override { return !*fail_send_message_; }
std::multimap<grpc::string, grpc::string>* GetSendInitialMetadata() override {
return send_initial_metadata_;
@ -110,12 +129,25 @@ class InterceptorBatchMethodsImpl
Status* GetRecvStatus() override { return recv_status_; }
void FailHijackedSendMessage() override {
GPR_CODEGEN_ASSERT(hooks_[static_cast<size_t>(
experimental::InterceptionHookPoints::PRE_SEND_MESSAGE)]);
*fail_send_message_ = true;
}
std::multimap<grpc::string_ref, grpc::string_ref>* GetRecvTrailingMetadata()
override {
return recv_trailing_metadata_->map();
}
void SetSendMessage(ByteBuffer* buf) { send_message_ = buf; }
void SetSendMessage(ByteBuffer* buf, const void** msg,
bool* fail_send_message,
std::function<Status(const void*)> serializer) {
send_message_ = buf;
orig_send_message_ = msg;
fail_send_message_ = fail_send_message;
serializer_ = serializer;
}
void SetSendInitialMetadata(
std::multimap<grpc::string, grpc::string>* metadata) {
@ -134,7 +166,10 @@ class InterceptorBatchMethodsImpl
send_trailing_metadata_ = metadata;
}
void SetRecvMessage(void* message) { recv_message_ = message; }
void SetRecvMessage(void* message, bool* got_message) {
recv_message_ = message;
got_message_ = got_message;
}
void SetRecvInitialMetadata(MetadataMap* map) {
recv_initial_metadata_ = map;
@ -157,6 +192,12 @@ class InterceptorBatchMethodsImpl
info->channel(), current_interceptor_index_ + 1));
}
void FailHijackedRecvMessage() override {
GPR_CODEGEN_ASSERT(hooks_[static_cast<size_t>(
experimental::InterceptionHookPoints::PRE_RECV_MESSAGE)]);
*got_message_ = false;
}
// Clears all state
void ClearState() {
reverse_ = false;
@ -334,6 +375,9 @@ class InterceptorBatchMethodsImpl
std::function<void(void)> callback_;
ByteBuffer* send_message_ = nullptr;
bool* fail_send_message_ = nullptr;
const void** orig_send_message_ = nullptr;
std::function<Status(const void*)> serializer_;
std::multimap<grpc::string, grpc::string>* send_initial_metadata_;
@ -345,6 +389,7 @@ class InterceptorBatchMethodsImpl
std::multimap<grpc::string, grpc::string>* send_trailing_metadata_ = nullptr;
void* recv_message_ = nullptr;
bool* got_message_ = nullptr;
MetadataMap* recv_initial_metadata_ = nullptr;
@ -379,13 +424,36 @@ class CancelInterceptorBatchMethods
"Cancel notification");
}
ByteBuffer* GetSendMessage() override {
ByteBuffer* GetSerializedSendMessage() override {
GPR_CODEGEN_ASSERT(false &&
"It is illegal to call GetSendMessage on a method which "
"has a Cancel notification");
return nullptr;
}
bool GetSendMessageStatus() override {
GPR_CODEGEN_ASSERT(
false &&
"It is illegal to call GetSendMessageStatus on a method which "
"has a Cancel notification");
return false;
}
const void* GetSendMessage() override {
GPR_CODEGEN_ASSERT(
false &&
"It is illegal to call GetOriginalSendMessage on a method which "
"has a Cancel notification");
return nullptr;
}
void ModifySendMessage(const void* message) override {
GPR_CODEGEN_ASSERT(
false &&
"It is illegal to call ModifySendMessage on a method which "
"has a Cancel notification");
}
std::multimap<grpc::string, grpc::string>* GetSendInitialMetadata() override {
GPR_CODEGEN_ASSERT(false &&
"It is illegal to call GetSendInitialMetadata on a "
@ -451,6 +519,18 @@ class CancelInterceptorBatchMethods
"method which has a Cancel notification");
return std::unique_ptr<ChannelInterface>(nullptr);
}
void FailHijackedRecvMessage() override {
GPR_CODEGEN_ASSERT(false &&
"It is illegal to call FailHijackedRecvMessage on a "
"method which has a Cancel notification");
}
void FailHijackedSendMessage() override {
GPR_CODEGEN_ASSERT(false &&
"It is illegal to call FailHijackedSendMessage on a "
"method which has a Cancel notification");
}
};
} // namespace internal
} // namespace grpc

@ -79,7 +79,7 @@ class RpcMethodHandler : public MethodHandler {
ops.set_compression_level(param.server_context->compression_level());
}
if (status.ok()) {
status = ops.SendMessage(rsp);
status = ops.SendMessagePtr(&rsp);
}
ops.ServerSendStatus(&param.server_context->trailing_metadata_, status);
param.call->PerformOps(&ops);
@ -139,7 +139,7 @@ class ClientStreamingHandler : public MethodHandler {
}
}
if (status.ok()) {
status = ops.SendMessage(rsp);
status = ops.SendMessagePtr(&rsp);
}
ops.ServerSendStatus(&param.server_context->trailing_metadata_, status);
param.call->PerformOps(&ops);

@ -320,7 +320,7 @@ class CallbackUnaryHandler : public MethodHandler {
// The response is dropped if the status is not OK.
if (s.ok()) {
finish_ops_.ServerSendStatus(&ctx_->trailing_metadata_,
finish_ops_.SendMessage(resp_));
finish_ops_.SendMessagePtr(&resp_));
} else {
finish_ops_.ServerSendStatus(&ctx_->trailing_metadata_, s);
}
@ -449,7 +449,7 @@ class CallbackClientStreamingHandler : public MethodHandler {
// The response is dropped if the status is not OK.
if (s.ok()) {
finish_ops_.ServerSendStatus(&ctx_->trailing_metadata_,
finish_ops_.SendMessage(resp_));
finish_ops_.SendMessagePtr(&resp_));
} else {
finish_ops_.ServerSendStatus(&ctx_->trailing_metadata_, s);
}
@ -642,7 +642,7 @@ class CallbackServerStreamingHandler : public MethodHandler {
ctx_->sent_initial_metadata_ = true;
}
// TODO(vjpai): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(*resp, options).ok());
GPR_CODEGEN_ASSERT(write_ops_.SendMessagePtr(resp, options).ok());
call_.PerformOps(&write_ops_);
}
@ -652,7 +652,7 @@ class CallbackServerStreamingHandler : public MethodHandler {
// Don't send any message if the status is bad
if (s.ok()) {
// TODO(vjpai): don't assert
GPR_CODEGEN_ASSERT(finish_ops_.SendMessage(*resp, options).ok());
GPR_CODEGEN_ASSERT(finish_ops_.SendMessagePtr(resp, options).ok());
}
Finish(std::move(s));
}
@ -804,7 +804,7 @@ class CallbackBidiHandler : public MethodHandler {
ctx_->sent_initial_metadata_ = true;
}
// TODO(vjpai): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(*resp, options).ok());
GPR_CODEGEN_ASSERT(write_ops_.SendMessagePtr(resp, options).ok());
call_.PerformOps(&write_ops_);
}
@ -813,7 +813,7 @@ class CallbackBidiHandler : public MethodHandler {
// Don't send any message if the status is bad
if (s.ok()) {
// TODO(vjpai): don't assert
GPR_CODEGEN_ASSERT(finish_ops_.SendMessage(*resp, options).ok());
GPR_CODEGEN_ASSERT(finish_ops_.SendMessagePtr(resp, options).ok());
}
Finish(std::move(s));
}

@ -272,7 +272,7 @@ class ServerInterface : public internal::CallHook {
/* Set interception point for recv message */
interceptor_methods_.AddInterceptionHookPoint(
experimental::InterceptionHookPoints::POST_RECV_MESSAGE);
interceptor_methods_.SetRecvMessage(request_);
interceptor_methods_.SetRecvMessage(request_, nullptr);
return RegisteredAsyncRequest::FinalizeResult(tag, status);
}

@ -253,7 +253,7 @@ class ClientReader final : public ClientReaderInterface<R> {
ops.SendInitialMetadata(&context->send_initial_metadata_,
context->initial_metadata_flags());
// TODO(ctiller): don't assert
GPR_CODEGEN_ASSERT(ops.SendMessage(request).ok());
GPR_CODEGEN_ASSERT(ops.SendMessagePtr(&request).ok());
ops.ClientSendClose();
call_.PerformOps(&ops);
cq_.Pluck(&ops);
@ -331,7 +331,7 @@ class ClientWriter : public ClientWriterInterface<W> {
context_->initial_metadata_flags());
context_->set_initial_metadata_corked(false);
}
if (!ops.SendMessage(msg, options).ok()) {
if (!ops.SendMessagePtr(&msg, options).ok()) {
return false;
}
@ -502,7 +502,7 @@ class ClientReaderWriter final : public ClientReaderWriterInterface<W, R> {
context_->initial_metadata_flags());
context_->set_initial_metadata_corked(false);
}
if (!ops.SendMessage(msg, options).ok()) {
if (!ops.SendMessagePtr(&msg, options).ok()) {
return false;
}
@ -656,7 +656,7 @@ class ServerWriter final : public ServerWriterInterface<W> {
options.set_buffer_hint();
}
if (!ctx_->pending_ops_.SendMessage(msg, options).ok()) {
if (!ctx_->pending_ops_.SendMessagePtr(&msg, options).ok()) {
return false;
}
if (!ctx_->sent_initial_metadata_) {
@ -734,7 +734,7 @@ class ServerReaderWriterBody final {
if (options.is_last_message()) {
options.set_buffer_hint();
}
if (!ctx_->pending_ops_.SendMessage(msg, options).ok()) {
if (!ctx_->pending_ops_.SendMessagePtr(&msg, options).ok()) {
return false;
}
if (!ctx_->sent_initial_metadata_) {

@ -13,8 +13,8 @@
<date>2018-01-19</date>
<time>16:06:07</time>
<version>
<release>1.18.0dev</release>
<api>1.18.0dev</api>
<release>1.19.0dev</release>
<api>1.19.0dev</api>
</version>
<stability>
<release>beta</release>

@ -160,7 +160,7 @@ if EXTRA_ENV_COMPILE_ARGS is None:
EXTRA_ENV_COMPILE_ARGS += ' -std=gnu99 -fvisibility=hidden -fno-wrapv -fno-exceptions'
elif "darwin" in sys.platform:
EXTRA_ENV_COMPILE_ARGS += ' -fvisibility=hidden -fno-wrapv -fno-exceptions'
EXTRA_ENV_COMPILE_ARGS += ' -DPB_FIELD_16BIT'
EXTRA_ENV_COMPILE_ARGS += ' -DPB_FIELD_32BIT'
if EXTRA_ENV_LINK_ARGS is None:
EXTRA_ENV_LINK_ARGS = ''

@ -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,18 @@ 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 [super initWithHost:host\n"
" packageName:@\"$package$\"\n"
" serviceName:@\"$service_name$\"];\n"
"}\n\n");
printer.Print(
@ -293,6 +381,10 @@ void PrintMethodImplementations(Printer* printer,
"#pragma mark - Class Methods\n\n"
"+ (instancetype)serviceWithHost:(NSString *)host {\n"
" return [[self alloc] initWithHost:host];\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");

@ -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);

@ -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");
}
{

@ -101,9 +101,6 @@ struct grpc_subchannel {
keep the subchannel open */
gpr_atm ref_pair;
/** non-transport related channel filters */
const grpc_channel_filter** filters;
size_t num_filters;
/** channel arguments */
grpc_channel_args* args;
@ -384,7 +381,6 @@ static void subchannel_destroy(void* arg, grpc_error* error) {
c->channelz_subchannel->MarkSubchannelDestroyed();
c->channelz_subchannel.reset();
}
gpr_free((void*)c->filters);
c->health_check_service_name.reset();
grpc_channel_args_destroy(c->args);
grpc_connectivity_state_destroy(&c->state_tracker);
@ -567,15 +563,6 @@ grpc_subchannel* grpc_subchannel_create(grpc_connector* connector,
gpr_atm_no_barrier_store(&c->ref_pair, 1 << INTERNAL_REF_BITS);
c->connector = connector;
grpc_connector_ref(c->connector);
c->num_filters = args->filter_count;
if (c->num_filters > 0) {
c->filters = static_cast<const grpc_channel_filter**>(
gpr_malloc(sizeof(grpc_channel_filter*) * c->num_filters));
memcpy((void*)c->filters, args->filters,
sizeof(grpc_channel_filter*) * c->num_filters);
} else {
c->filters = nullptr;
}
c->pollset_set = grpc_pollset_set_create();
grpc_resolved_address* addr =
static_cast<grpc_resolved_address*>(gpr_malloc(sizeof(*addr)));

@ -189,11 +189,6 @@ grpc_call_stack* grpc_subchannel_call_get_call_stack(
struct grpc_subchannel_args {
/* When updating this struct, also update subchannel_index.c */
/** Channel filters for this channel - wrapped factories will likely
want to mutate this */
const grpc_channel_filter** filters;
/** The number of filters in the above array */
size_t filter_count;
/** Channel arguments to be supplied to the newly created channel */
const grpc_channel_args* args;
};

@ -49,15 +49,6 @@ static grpc_subchannel_key* create_key(
grpc_channel_args* (*copy_channel_args)(const grpc_channel_args* args)) {
grpc_subchannel_key* k =
static_cast<grpc_subchannel_key*>(gpr_malloc(sizeof(*k)));
k->args.filter_count = args->filter_count;
if (k->args.filter_count > 0) {
k->args.filters = static_cast<const grpc_channel_filter**>(
gpr_malloc(sizeof(*k->args.filters) * k->args.filter_count));
memcpy(reinterpret_cast<grpc_channel_filter*>(k->args.filters),
args->filters, sizeof(*k->args.filters) * k->args.filter_count);
} else {
k->args.filters = nullptr;
}
k->args.args = copy_channel_args(args->args);
return k;
}
@ -75,18 +66,10 @@ int grpc_subchannel_key_compare(const grpc_subchannel_key* a,
const grpc_subchannel_key* b) {
// To pretend the keys are different, return a non-zero value.
if (GPR_UNLIKELY(g_force_creation)) return 1;
int c = GPR_ICMP(a->args.filter_count, b->args.filter_count);
if (c != 0) return c;
if (a->args.filter_count > 0) {
c = memcmp(a->args.filters, b->args.filters,
a->args.filter_count * sizeof(*a->args.filters));
if (c != 0) return c;
}
return grpc_channel_args_compare(a->args.args, b->args.args);
}
void grpc_subchannel_key_destroy(grpc_subchannel_key* k) {
gpr_free(reinterpret_cast<grpc_channel_args*>(k->args.filters));
grpc_channel_args_destroy(const_cast<grpc_channel_args*>(k->args.args));
gpr_free(k);
}

@ -170,7 +170,12 @@ grpc_chttp2_transport::~grpc_chttp2_transport() {
grpc_slice_buffer_destroy_internal(&outbuf);
grpc_chttp2_hpack_compressor_destroy(&hpack_compressor);
grpc_core::ContextList::Execute(cl, nullptr, GRPC_ERROR_NONE);
grpc_error* error =
GRPC_ERROR_CREATE_FROM_STATIC_STRING("Transport destroyed");
// ContextList::Execute follows semantics of a callback function and does not
// take a ref on error
grpc_core::ContextList::Execute(cl, nullptr, error);
GRPC_ERROR_UNREF(error);
cl = nullptr;
grpc_slice_buffer_destroy_internal(&read_buffer);

@ -21,31 +21,47 @@
#include "src/core/ext/transport/chttp2/transport/context_list.h"
namespace {
void (*write_timestamps_callback_g)(void*, grpc_core::Timestamps*) = nullptr;
}
void (*write_timestamps_callback_g)(void*, grpc_core::Timestamps*,
grpc_error* error) = nullptr;
void* (*get_copied_context_fn_g)(void*) = nullptr;
} // namespace
namespace grpc_core {
void ContextList::Append(ContextList** head, grpc_chttp2_stream* s) {
if (get_copied_context_fn_g == nullptr ||
write_timestamps_callback_g == nullptr) {
return;
}
/* Create a new element in the list and add it at the front */
ContextList* elem = grpc_core::New<ContextList>();
elem->trace_context_ = get_copied_context_fn_g(s->context);
elem->byte_offset_ = s->byte_counter;
elem->next_ = *head;
*head = elem;
}
void ContextList::Execute(void* arg, grpc_core::Timestamps* ts,
grpc_error* error) {
ContextList* head = static_cast<ContextList*>(arg);
ContextList* to_be_freed;
while (head != nullptr) {
if (error == GRPC_ERROR_NONE && ts != nullptr) {
if (write_timestamps_callback_g) {
ts->byte_offset = static_cast<uint32_t>(head->byte_offset_);
write_timestamps_callback_g(head->s_->context, ts);
}
if (write_timestamps_callback_g) {
ts->byte_offset = static_cast<uint32_t>(head->byte_offset_);
write_timestamps_callback_g(head->trace_context_, ts, error);
}
GRPC_CHTTP2_STREAM_UNREF(static_cast<grpc_chttp2_stream*>(head->s_),
"timestamp");
to_be_freed = head;
head = head->next_;
grpc_core::Delete(to_be_freed);
}
}
void grpc_http2_set_write_timestamps_callback(
void (*fn)(void*, grpc_core::Timestamps*)) {
void grpc_http2_set_write_timestamps_callback(void (*fn)(void*,
grpc_core::Timestamps*,
grpc_error* error)) {
write_timestamps_callback_g = fn;
}
void grpc_http2_set_fn_get_copied_context(void* (*fn)(void*)) {
get_copied_context_fn_g = fn;
}
} /* namespace grpc_core */

@ -31,42 +31,23 @@ class ContextList {
public:
/* Creates a new element with \a context as the value and appends it to the
* list. */
static void Append(ContextList** head, grpc_chttp2_stream* s) {
/* Make sure context is not already present */
GRPC_CHTTP2_STREAM_REF(s, "timestamp");
#ifndef NDEBUG
ContextList* ptr = *head;
while (ptr != nullptr) {
if (ptr->s_ == s) {
GPR_ASSERT(
false &&
"Trying to append a stream that is already present in the list");
}
ptr = ptr->next_;
}
#endif
/* Create a new element in the list and add it at the front */
ContextList* elem = grpc_core::New<ContextList>();
elem->s_ = s;
elem->byte_offset_ = s->byte_counter;
elem->next_ = *head;
*head = elem;
}
static void Append(ContextList** head, grpc_chttp2_stream* s);
/* Executes a function \a fn with each context in the list and \a ts. It also
* frees up the entire list after this operation. */
* frees up the entire list after this operation. It is intended as a callback
* and hence does not take a ref on \a error */
static void Execute(void* arg, grpc_core::Timestamps* ts, grpc_error* error);
private:
grpc_chttp2_stream* s_ = nullptr;
void* trace_context_ = nullptr;
ContextList* next_ = nullptr;
size_t byte_offset_ = 0;
};
void grpc_http2_set_write_timestamps_callback(
void (*fn)(void*, grpc_core::Timestamps*));
void grpc_http2_set_write_timestamps_callback(void (*fn)(void*,
grpc_core::Timestamps*,
grpc_error* error));
void grpc_http2_set_fn_get_copied_context(void* (*fn)(void*));
} /* namespace grpc_core */
#endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_CONTEXT_LIST_H */

@ -46,9 +46,21 @@ 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 disable_client_authority_filter_arg;
disable_client_authority_filter_arg.key =
const_cast<char*>(GRPC_ARG_DISABLE_CLIENT_AUTHORITY_FILTER);
disable_client_authority_filter_arg.type = GRPC_ARG_INTEGER;
disable_client_authority_filter_arg.value.integer = 1;
grpc_channel_args* new_args = grpc_channel_args_copy_and_add(
args, &disable_client_authority_filter_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;
}

@ -52,62 +52,52 @@ CFStreamHandle* CFStreamHandle::CreateStreamHandle(
void CFStreamHandle::ReadCallback(CFReadStreamRef stream,
CFStreamEventType type,
void* client_callback_info) {
grpc_core::ExecCtx exec_ctx;
CFStreamHandle* handle = static_cast<CFStreamHandle*>(client_callback_info);
CFSTREAM_HANDLE_REF(handle, "read callback");
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
grpc_core::ExecCtx exec_ctx;
if (grpc_tcp_trace.enabled()) {
gpr_log(GPR_DEBUG, "CFStream ReadCallback (%p, %p, %lu, %p)", handle,
stream, type, client_callback_info);
}
switch (type) {
case kCFStreamEventOpenCompleted:
handle->open_event_.SetReady();
break;
case kCFStreamEventHasBytesAvailable:
case kCFStreamEventEndEncountered:
handle->read_event_.SetReady();
break;
case kCFStreamEventErrorOccurred:
handle->open_event_.SetReady();
handle->read_event_.SetReady();
break;
default:
GPR_UNREACHABLE_CODE(return );
}
CFSTREAM_HANDLE_UNREF(handle, "read callback");
});
if (grpc_tcp_trace.enabled()) {
gpr_log(GPR_DEBUG, "CFStream ReadCallback (%p, %p, %lu, %p)", handle,
stream, type, client_callback_info);
}
switch (type) {
case kCFStreamEventOpenCompleted:
handle->open_event_.SetReady();
break;
case kCFStreamEventHasBytesAvailable:
case kCFStreamEventEndEncountered:
handle->read_event_.SetReady();
break;
case kCFStreamEventErrorOccurred:
handle->open_event_.SetReady();
handle->read_event_.SetReady();
break;
default:
GPR_UNREACHABLE_CODE(return );
}
}
void CFStreamHandle::WriteCallback(CFWriteStreamRef stream,
CFStreamEventType type,
void* clientCallBackInfo) {
grpc_core::ExecCtx exec_ctx;
CFStreamHandle* handle = static_cast<CFStreamHandle*>(clientCallBackInfo);
CFSTREAM_HANDLE_REF(handle, "write callback");
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
grpc_core::ExecCtx exec_ctx;
if (grpc_tcp_trace.enabled()) {
gpr_log(GPR_DEBUG, "CFStream WriteCallback (%p, %p, %lu, %p)", handle,
stream, type, clientCallBackInfo);
}
switch (type) {
case kCFStreamEventOpenCompleted:
handle->open_event_.SetReady();
break;
case kCFStreamEventCanAcceptBytes:
case kCFStreamEventEndEncountered:
handle->write_event_.SetReady();
break;
case kCFStreamEventErrorOccurred:
handle->open_event_.SetReady();
handle->write_event_.SetReady();
break;
default:
GPR_UNREACHABLE_CODE(return );
}
CFSTREAM_HANDLE_UNREF(handle, "write callback");
});
if (grpc_tcp_trace.enabled()) {
gpr_log(GPR_DEBUG, "CFStream WriteCallback (%p, %p, %lu, %p)", handle,
stream, type, clientCallBackInfo);
}
switch (type) {
case kCFStreamEventOpenCompleted:
handle->open_event_.SetReady();
break;
case kCFStreamEventCanAcceptBytes:
case kCFStreamEventEndEncountered:
handle->write_event_.SetReady();
break;
case kCFStreamEventErrorOccurred:
handle->open_event_.SetReady();
handle->write_event_.SetReady();
break;
default:
GPR_UNREACHABLE_CODE(return );
}
}
CFStreamHandle::CFStreamHandle(CFReadStreamRef read_stream,
@ -116,6 +106,7 @@ CFStreamHandle::CFStreamHandle(CFReadStreamRef read_stream,
open_event_.InitEvent();
read_event_.InitEvent();
write_event_.InitEvent();
dispatch_queue_ = dispatch_queue_create(nullptr, DISPATCH_QUEUE_SERIAL);
CFStreamClientContext ctx = {0, static_cast<void*>(this),
CFStreamHandle::Retain, CFStreamHandle::Release,
nil};
@ -129,10 +120,8 @@ CFStreamHandle::CFStreamHandle(CFReadStreamRef read_stream,
kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes |
kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,
CFStreamHandle::WriteCallback, &ctx);
CFReadStreamScheduleWithRunLoop(read_stream, CFRunLoopGetMain(),
kCFRunLoopCommonModes);
CFWriteStreamScheduleWithRunLoop(write_stream, CFRunLoopGetMain(),
kCFRunLoopCommonModes);
CFReadStreamSetDispatchQueue(read_stream, dispatch_queue_);
CFWriteStreamSetDispatchQueue(write_stream, dispatch_queue_);
}
CFStreamHandle::~CFStreamHandle() {

@ -62,6 +62,8 @@ class CFStreamHandle final {
grpc_core::LockfreeEvent read_event_;
grpc_core::LockfreeEvent write_event_;
dispatch_queue_t dispatch_queue_;
gpr_refcount refcount_;
};

@ -665,6 +665,7 @@ void grpc_resource_quota_unref_internal(grpc_resource_quota* resource_quota) {
GPR_ASSERT(resource_quota->num_threads_allocated == 0);
GRPC_COMBINER_UNREF(resource_quota->combiner, "resource_quota");
gpr_free(resource_quota->name);
gpr_mu_destroy(&resource_quota->thread_count_mu);
gpr_free(resource_quota);
}
}

@ -42,6 +42,7 @@
#include "src/core/lib/iomgr/tcp_windows.h"
#include "src/core/lib/iomgr/timer.h"
#include "src/core/lib/slice/slice_internal.h"
#include "src/core/lib/slice/slice_string_helpers.h"
#if defined(__MSYS__) && defined(GPR_ARCH_64)
/* Nasty workaround for nasty bug when using the 64 bits msys compiler
@ -112,7 +113,10 @@ typedef struct grpc_tcp {
grpc_closure* read_cb;
grpc_closure* write_cb;
grpc_slice read_slice;
/* garbage after the last read */
grpc_slice_buffer last_read_buffer;
grpc_slice_buffer* write_slices;
grpc_slice_buffer* read_slices;
@ -131,6 +135,7 @@ static void tcp_free(grpc_tcp* tcp) {
grpc_winsocket_destroy(tcp->socket);
gpr_mu_destroy(&tcp->mu);
gpr_free(tcp->peer_string);
grpc_slice_buffer_destroy_internal(&tcp->last_read_buffer);
grpc_resource_user_unref(tcp->resource_user);
if (tcp->shutting_down) GRPC_ERROR_UNREF(tcp->shutdown_error);
gpr_free(tcp);
@ -179,9 +184,12 @@ static void on_read(void* tcpp, grpc_error* error) {
grpc_tcp* tcp = (grpc_tcp*)tcpp;
grpc_closure* cb = tcp->read_cb;
grpc_winsocket* socket = tcp->socket;
grpc_slice sub;
grpc_winsocket_callback_info* info = &socket->read_info;
if (grpc_tcp_trace.enabled()) {
gpr_log(GPR_INFO, "TCP:%p on_read", tcp);
}
GRPC_ERROR_REF(error);
if (error == GRPC_ERROR_NONE) {
@ -189,13 +197,35 @@ static void on_read(void* tcpp, grpc_error* error) {
char* utf8_message = gpr_format_message(info->wsa_error);
error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(utf8_message);
gpr_free(utf8_message);
grpc_slice_unref_internal(tcp->read_slice);
grpc_slice_buffer_reset_and_unref_internal(tcp->read_slices);
} else {
if (info->bytes_transfered != 0 && !tcp->shutting_down) {
sub = grpc_slice_sub_no_ref(tcp->read_slice, 0, info->bytes_transfered);
grpc_slice_buffer_add(tcp->read_slices, sub);
GPR_ASSERT((size_t)info->bytes_transfered <= tcp->read_slices->length);
if (static_cast<size_t>(info->bytes_transfered) !=
tcp->read_slices->length) {
grpc_slice_buffer_trim_end(
tcp->read_slices,
tcp->read_slices->length -
static_cast<size_t>(info->bytes_transfered),
&tcp->last_read_buffer);
}
GPR_ASSERT((size_t)info->bytes_transfered == tcp->read_slices->length);
if (grpc_tcp_trace.enabled()) {
size_t i;
for (i = 0; i < tcp->read_slices->count; i++) {
char* dump = grpc_dump_slice(tcp->read_slices->slices[i],
GPR_DUMP_HEX | GPR_DUMP_ASCII);
gpr_log(GPR_INFO, "READ %p (peer=%s): %s", tcp, tcp->peer_string,
dump);
gpr_free(dump);
}
}
} else {
grpc_slice_unref_internal(tcp->read_slice);
if (grpc_tcp_trace.enabled()) {
gpr_log(GPR_INFO, "TCP:%p unref read_slice", tcp);
}
grpc_slice_buffer_reset_and_unref_internal(tcp->read_slices);
error = tcp->shutting_down
? GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
"TCP stream shutting down", &tcp->shutdown_error, 1)
@ -209,6 +239,8 @@ static void on_read(void* tcpp, grpc_error* error) {
GRPC_CLOSURE_SCHED(cb, error);
}
#define DEFAULT_TARGET_READ_SIZE 8192
#define MAX_WSABUF_COUNT 16
static void win_read(grpc_endpoint* ep, grpc_slice_buffer* read_slices,
grpc_closure* cb) {
grpc_tcp* tcp = (grpc_tcp*)ep;
@ -217,7 +249,12 @@ static void win_read(grpc_endpoint* ep, grpc_slice_buffer* read_slices,
int status;
DWORD bytes_read = 0;
DWORD flags = 0;
WSABUF buffer;
WSABUF buffers[MAX_WSABUF_COUNT];
size_t i;
if (grpc_tcp_trace.enabled()) {
gpr_log(GPR_INFO, "TCP:%p win_read", tcp);
}
if (tcp->shutting_down) {
GRPC_CLOSURE_SCHED(
@ -229,18 +266,27 @@ static void win_read(grpc_endpoint* ep, grpc_slice_buffer* read_slices,
tcp->read_cb = cb;
tcp->read_slices = read_slices;
grpc_slice_buffer_reset_and_unref_internal(read_slices);
grpc_slice_buffer_swap(read_slices, &tcp->last_read_buffer);
tcp->read_slice = GRPC_SLICE_MALLOC(8192);
if (tcp->read_slices->length < DEFAULT_TARGET_READ_SIZE / 2 &&
tcp->read_slices->count < MAX_WSABUF_COUNT) {
// TODO(jtattermusch): slice should be allocated using resource quota
grpc_slice_buffer_add(tcp->read_slices,
GRPC_SLICE_MALLOC(DEFAULT_TARGET_READ_SIZE));
}
buffer.len = (ULONG)GRPC_SLICE_LENGTH(
tcp->read_slice); // we know slice size fits in 32bit.
buffer.buf = (char*)GRPC_SLICE_START_PTR(tcp->read_slice);
GPR_ASSERT(tcp->read_slices->count <= MAX_WSABUF_COUNT);
for (i = 0; i < tcp->read_slices->count; i++) {
buffers[i].len = (ULONG)GRPC_SLICE_LENGTH(
tcp->read_slices->slices[i]); // we know slice size fits in 32bit.
buffers[i].buf = (char*)GRPC_SLICE_START_PTR(tcp->read_slices->slices[i]);
}
TCP_REF(tcp, "read");
/* First let's try a synchronous, non-blocking read. */
status =
WSARecv(tcp->socket->socket, &buffer, 1, &bytes_read, &flags, NULL, NULL);
status = WSARecv(tcp->socket->socket, buffers, (DWORD)tcp->read_slices->count,
&bytes_read, &flags, NULL, NULL);
info->wsa_error = status == 0 ? 0 : WSAGetLastError();
/* Did we get data immediately ? Yay. */
@ -252,8 +298,8 @@ static void win_read(grpc_endpoint* ep, grpc_slice_buffer* read_slices,
/* Otherwise, let's retry, by queuing a read. */
memset(&tcp->socket->read_info.overlapped, 0, sizeof(OVERLAPPED));
status = WSARecv(tcp->socket->socket, &buffer, 1, &bytes_read, &flags,
&info->overlapped, NULL);
status = WSARecv(tcp->socket->socket, buffers, (DWORD)tcp->read_slices->count,
&bytes_read, &flags, &info->overlapped, NULL);
if (status != 0) {
int wsa_error = WSAGetLastError();
@ -275,6 +321,10 @@ static void on_write(void* tcpp, grpc_error* error) {
grpc_winsocket_callback_info* info = &handle->write_info;
grpc_closure* cb;
if (grpc_tcp_trace.enabled()) {
gpr_log(GPR_INFO, "TCP:%p on_write", tcp);
}
GRPC_ERROR_REF(error);
gpr_mu_lock(&tcp->mu);
@ -303,11 +353,21 @@ static void win_write(grpc_endpoint* ep, grpc_slice_buffer* slices,
unsigned i;
DWORD bytes_sent;
int status;
WSABUF local_buffers[16];
WSABUF local_buffers[MAX_WSABUF_COUNT];
WSABUF* allocated = NULL;
WSABUF* buffers = local_buffers;
size_t len;
if (grpc_tcp_trace.enabled()) {
size_t i;
for (i = 0; i < slices->count; i++) {
char* data =
grpc_dump_slice(slices->slices[i], GPR_DUMP_HEX | GPR_DUMP_ASCII);
gpr_log(GPR_INFO, "WRITE %p (peer=%s): %s", tcp, tcp->peer_string, data);
gpr_free(data);
}
}
if (tcp->shutting_down) {
GRPC_CLOSURE_SCHED(
cb, GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
@ -412,6 +472,7 @@ static void win_shutdown(grpc_endpoint* ep, grpc_error* why) {
static void win_destroy(grpc_endpoint* ep) {
grpc_network_status_unregister_endpoint(ep);
grpc_tcp* tcp = (grpc_tcp*)ep;
grpc_slice_buffer_reset_and_unref_internal(&tcp->last_read_buffer);
TCP_UNREF(tcp, "destroy");
}
@ -463,6 +524,7 @@ grpc_endpoint* grpc_tcp_create(grpc_winsocket* socket,
GRPC_CLOSURE_INIT(&tcp->on_read, on_read, tcp, grpc_schedule_on_exec_ctx);
GRPC_CLOSURE_INIT(&tcp->on_write, on_write, tcp, grpc_schedule_on_exec_ctx);
tcp->peer_string = gpr_strdup(peer_string);
grpc_slice_buffer_init(&tcp->last_read_buffer);
tcp->resource_user = grpc_resource_user_create(resource_quota, peer_string);
/* Tell network status tracking code about the new endpoint */
grpc_network_status_register_endpoint(&tcp->base);

@ -45,6 +45,11 @@ void grpc_channel_init_init(void);
/// registration order (in the case of a tie).
/// Stages are registered against one of the pre-determined channel stack
/// types.
/// If the channel stack type is GRPC_CLIENT_SUBCHANNEL, the caller should
/// ensure that subchannels with different filter lists will always have
/// different channel args. This requires setting a channel arg in case the
/// registration function relies on some condition other than channel args to
/// decide whether to add a filter or not.
void grpc_channel_init_register_stage(grpc_channel_stack_type type,
int priority,
grpc_channel_init_stage stage_fn,

@ -194,13 +194,10 @@ struct call_data {
};
struct request_matcher {
request_matcher(grpc_server* server);
~request_matcher();
grpc_server* server;
std::atomic<call_data*> pending_head{nullptr};
call_data* pending_tail = nullptr;
gpr_locked_mpscq* requests_per_cq = nullptr;
call_data* pending_head;
call_data* pending_tail;
gpr_locked_mpscq* requests_per_cq;
};
struct registered_method {
@ -349,30 +346,22 @@ static void channel_broadcaster_shutdown(channel_broadcaster* cb,
* request_matcher
*/
namespace {
request_matcher::request_matcher(grpc_server* server) : server(server) {
requests_per_cq = static_cast<gpr_locked_mpscq*>(
gpr_malloc(sizeof(*requests_per_cq) * server->cq_count));
for (size_t i = 0; i < server->cq_count; i++) {
gpr_locked_mpscq_init(&requests_per_cq[i]);
}
}
request_matcher::~request_matcher() {
static void request_matcher_init(request_matcher* rm, grpc_server* server) {
memset(rm, 0, sizeof(*rm));
rm->server = server;
rm->requests_per_cq = static_cast<gpr_locked_mpscq*>(
gpr_malloc(sizeof(*rm->requests_per_cq) * server->cq_count));
for (size_t i = 0; i < server->cq_count; i++) {
GPR_ASSERT(gpr_locked_mpscq_pop(&requests_per_cq[i]) == nullptr);
gpr_locked_mpscq_destroy(&requests_per_cq[i]);
gpr_locked_mpscq_init(&rm->requests_per_cq[i]);
}
gpr_free(requests_per_cq);
}
} // namespace
static void request_matcher_init(request_matcher* rm, grpc_server* server) {
new (rm) request_matcher(server);
}
static void request_matcher_destroy(request_matcher* rm) {
rm->~request_matcher();
for (size_t i = 0; i < rm->server->cq_count; i++) {
GPR_ASSERT(gpr_locked_mpscq_pop(&rm->requests_per_cq[i]) == nullptr);
gpr_locked_mpscq_destroy(&rm->requests_per_cq[i]);
}
gpr_free(rm->requests_per_cq);
}
static void kill_zombie(void* elem, grpc_error* error) {
@ -381,10 +370,9 @@ static void kill_zombie(void* elem, grpc_error* error) {
}
static void request_matcher_zombify_all_pending_calls(request_matcher* rm) {
call_data* calld;
while ((calld = rm->pending_head.load(std::memory_order_relaxed)) !=
nullptr) {
rm->pending_head.store(calld->pending_next, std::memory_order_relaxed);
while (rm->pending_head) {
call_data* calld = rm->pending_head;
rm->pending_head = calld->pending_next;
gpr_atm_no_barrier_store(&calld->state, ZOMBIED);
GRPC_CLOSURE_INIT(
&calld->kill_zombie_closure, kill_zombie,
@ -582,9 +570,8 @@ static void publish_new_rpc(void* arg, grpc_error* error) {
}
gpr_atm_no_barrier_store(&calld->state, PENDING);
if (rm->pending_head.load(std::memory_order_relaxed) == nullptr) {
rm->pending_head.store(calld, std::memory_order_relaxed);
rm->pending_tail = calld;
if (rm->pending_head == nullptr) {
rm->pending_tail = rm->pending_head = calld;
} else {
rm->pending_tail->pending_next = calld;
rm->pending_tail = calld;
@ -1448,39 +1435,30 @@ static grpc_call_error queue_call_request(grpc_server* server, size_t cq_idx,
rm = &rc->data.registered.method->matcher;
break;
}
// Fast path: if there is no pending request to be processed, immediately
// return.
if (!gpr_locked_mpscq_push(&rm->requests_per_cq[cq_idx], &rc->request_link) ||
// Note: We are reading the pending_head without holding the server's call
// mutex. Even if we read a non-null value here due to reordering,
// we will check it below again after grabbing the lock.
rm->pending_head.load(std::memory_order_relaxed) == nullptr) {
return GRPC_CALL_OK;
}
// Slow path: This was the first queued request and there are pendings:
// We need to lock and start matching calls.
gpr_mu_lock(&server->mu_call);
while ((calld = rm->pending_head.load(std::memory_order_relaxed)) !=
nullptr) {
rc = reinterpret_cast<requested_call*>(
gpr_locked_mpscq_pop(&rm->requests_per_cq[cq_idx]));
if (rc == nullptr) break;
rm->pending_head.store(calld->pending_next, std::memory_order_relaxed);
gpr_mu_unlock(&server->mu_call);
if (!gpr_atm_full_cas(&calld->state, PENDING, ACTIVATED)) {
// Zombied Call
GRPC_CLOSURE_INIT(
&calld->kill_zombie_closure, kill_zombie,
grpc_call_stack_element(grpc_call_get_call_stack(calld->call), 0),
grpc_schedule_on_exec_ctx);
GRPC_CLOSURE_SCHED(&calld->kill_zombie_closure, GRPC_ERROR_NONE);
} else {
publish_call(server, calld, cq_idx, rc);
}
if (gpr_locked_mpscq_push(&rm->requests_per_cq[cq_idx], &rc->request_link)) {
/* this was the first queued request: we need to lock and start
matching calls */
gpr_mu_lock(&server->mu_call);
while ((calld = rm->pending_head) != nullptr) {
rc = reinterpret_cast<requested_call*>(
gpr_locked_mpscq_pop(&rm->requests_per_cq[cq_idx]));
if (rc == nullptr) break;
rm->pending_head = calld->pending_next;
gpr_mu_unlock(&server->mu_call);
if (!gpr_atm_full_cas(&calld->state, PENDING, ACTIVATED)) {
// Zombied Call
GRPC_CLOSURE_INIT(
&calld->kill_zombie_closure, kill_zombie,
grpc_call_stack_element(grpc_call_get_call_stack(calld->call), 0),
grpc_schedule_on_exec_ctx);
GRPC_CLOSURE_SCHED(&calld->kill_zombie_closure, GRPC_ERROR_NONE);
} else {
publish_call(server, calld, cq_idx, rc);
}
gpr_mu_lock(&server->mu_call);
}
gpr_mu_unlock(&server->mu_call);
}
gpr_mu_unlock(&server->mu_call);
return GRPC_CALL_OK;
}

@ -25,4 +25,4 @@
const char* grpc_version_string(void) { return "7.0.0-dev"; }
const char* grpc_g_stands_for(void) { return "goose"; }
const char* grpc_g_stands_for(void) { return "gold"; }

@ -187,6 +187,7 @@ static void gc_mdtab(mdtab_shard* shard) {
((destroy_user_data_func)gpr_atm_no_barrier_load(
&md->destroy_user_data))(user_data);
}
gpr_mu_destroy(&md->mu_user_data);
gpr_free(md);
*prev_next = next;
num_freed++;

@ -106,7 +106,9 @@ void ChannelArguments::SetSocketMutator(grpc_socket_mutator* mutator) {
}
if (!replaced) {
strings_.push_back(grpc::string(mutator_arg.key));
args_.push_back(mutator_arg);
args_.back().key = const_cast<char*>(strings_.back().c_str());
}
}

@ -366,6 +366,11 @@ void ChannelFilterPluginShutdown();
/// The \a include_filter argument specifies a function that will be called
/// to determine at run-time whether or not to add the filter. If the
/// value is nullptr, the filter will be added unconditionally.
/// If the channel stack type is GRPC_CLIENT_SUBCHANNEL, the caller should
/// ensure that subchannels with different filter lists will always have
/// different channel args. This requires setting a channel arg in case the
/// registration function relies on some condition other than channel args to
/// decide whether to add a filter or not.
template <typename ChannelDataType, typename CallDataType>
void RegisterChannelFilter(
const char* name, grpc_channel_stack_type stack_type, int priority,

@ -22,5 +22,5 @@
#include <grpcpp/grpcpp.h>
namespace grpc {
grpc::string Version() { return "1.18.0-dev"; }
grpc::string Version() { return "1.19.0-dev"; }
} // namespace grpc

@ -278,7 +278,7 @@ class Server::SyncRequest final : public internal::CompletionQueueTag {
request_payload_ = nullptr;
interceptor_methods_.AddInterceptionHookPoint(
experimental::InterceptionHookPoints::POST_RECV_MESSAGE);
interceptor_methods_.SetRecvMessage(request_);
interceptor_methods_.SetRecvMessage(request_, nullptr);
}
if (interceptor_methods_.RunInterceptors(
@ -446,7 +446,7 @@ class Server::CallbackRequest final : public internal::CompletionQueueTag {
req_->request_payload_ = nullptr;
req_->interceptor_methods_.AddInterceptionHookPoint(
experimental::InterceptionHookPoints::POST_RECV_MESSAGE);
req_->interceptor_methods_.SetRecvMessage(req_->request_);
req_->interceptor_methods_.SetRecvMessage(req_->request_, nullptr);
}
if (req_->interceptor_methods_.RunInterceptors(

@ -76,4 +76,4 @@ namespace Grpc.Core.Interceptors
return serverServiceDefinition;
}
}
}
}

@ -1,7 +1,7 @@
<!-- This file is generated -->
<Project>
<PropertyGroup>
<GrpcCsharpVersion>1.18.0-dev</GrpcCsharpVersion>
<GrpcCsharpVersion>1.19.0-dev</GrpcCsharpVersion>
<GoogleProtobufVersion>3.6.1</GoogleProtobufVersion>
</PropertyGroup>
</Project>

@ -33,11 +33,11 @@ namespace Grpc.Core
/// <summary>
/// Current <c>AssemblyFileVersion</c> of gRPC C# assemblies
/// </summary>
public const string CurrentAssemblyFileVersion = "1.18.0.0";
public const string CurrentAssemblyFileVersion = "1.19.0.0";
/// <summary>
/// Current version of gRPC C#
/// </summary>
public const string CurrentVersion = "1.18.0-dev";
public const string CurrentVersion = "1.19.0-dev";
}
}

@ -13,7 +13,7 @@
@rem limitations under the License.
@rem Current package versions
set VERSION=1.18.0-dev
set VERSION=1.19.0-dev
@rem Adjust the location of nuget.exe
set NUGET=C:\nuget\nuget.exe

@ -13,7 +13,7 @@
@rem limitations under the License.
@rem Current package versions
set VERSION=1.18.0-dev
set VERSION=1.19.0-dev
@rem Adjust the location of nuget.exe
set NUGET=C:\nuget\nuget.exe

@ -42,7 +42,7 @@ Pod::Spec.new do |s|
# exclamation mark ensures that other "regular" pods will be able to find it as it'll be installed
# before them.
s.name = '!ProtoCompiler-gRPCPlugin'
v = '1.18.0-dev'
v = '1.19.0-dev'
s.version = v
s.summary = 'The gRPC ProtoC plugin generates Objective-C files from .proto services.'
s.description = <<-DESC

@ -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

@ -18,6 +18,7 @@
#import "GRPCCall+ChannelArg.h"
#import "private/GRPCChannelPool.h"
#import "private/GRPCHost.h"
#import <grpc/impl/codegen/compression_types.h>
@ -31,11 +32,11 @@
+ (void)setResponseSizeLimit:(NSUInteger)limit forHost:(nonnull NSString *)host {
GRPCHost *hostConfig = [GRPCHost hostWithAddress:host];
hostConfig.responseSizeLimitOverride = @(limit);
hostConfig.responseSizeLimitOverride = limit;
}
+ (void)closeOpenConnections {
[GRPCHost flushChannelCache];
[[GRPCChannelPool sharedInstance] disconnectAllChannels];
}
+ (void)setDefaultCompressMethod:(GRPCCompressAlgorithm)algorithm forhost:(nonnull NSString *)host {

@ -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

@ -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

@ -18,34 +18,13 @@
#import "GRPCCall.h"
/**
* The protocol of an OAuth2 token object from which GRPCCall can acquire a token.
*/
@protocol GRPCAuthorizationProtocol
- (void)getTokenWithHandler:(void (^)(NSString *token))hander;
@end
#import "GRPCCallOptions.h"
/** Helpers for setting and reading headers compatible with OAuth2. */
// Deprecated interface. Please use GRPCCallOptions instead.
@interface GRPCCall (OAuth2)
/**
* Setting this property is equivalent to setting "Bearer <passed token>" as the value of the
* request header with key "authorization" (the authorization header). Setting it to nil removes the
* authorization header from the request.
* The value obtained by getting the property is the OAuth2 bearer token if the authorization header
* of the request has the form "Bearer <token>", or nil otherwise.
*/
@property(atomic, copy) NSString *oauth2AccessToken;
/** Returns the value (if any) of the "www-authenticate" response header (the challenge header). */
@property(atomic, readonly) NSString *oauth2ChallengeHeader;
/**
* The authorization token object to be used when starting the call. If the value is set to nil, no
* oauth authentication will be used.
*
* If tokenProvider exists, it takes precedence over the token set by oauth2AccessToken.
*/
@property(atomic, copy) NSString* oauth2AccessToken;
@property(atomic, copy, readonly) NSString* oauth2ChallengeHeader;
@property(atomic, strong) id<GRPCAuthorizationProtocol> tokenProvider;
@end

@ -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

@ -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 {

@ -37,6 +37,10 @@
#include <AvailabilityMacros.h>
#include "GRPCCallOptions.h"
NS_ASSUME_NONNULL_BEGIN
#pragma mark gRPC errors
/** Domain of NSError objects produced by gRPC. */
@ -140,42 +144,148 @@ typedef NS_ENUM(NSUInteger, GRPCErrorCode) {
};
/**
* Safety remark of a gRPC method as defined in RFC 2616 Section 9.1
* Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by
* the server.
*/
typedef NS_ENUM(NSUInteger, GRPCCallSafety) {
/** Signal that there is no guarantees on how the call affects the server state. */
GRPCCallSafetyDefault = 0,
/** Signal that the call is idempotent. gRPC is free to use PUT verb. */
GRPCCallSafetyIdempotentRequest = 1,
/** Signal that the call is cacheable and will not affect server state. gRPC is free to use GET
verb. */
GRPCCallSafetyCacheableRequest = 2,
};
extern NSString *const kGRPCHeadersKey;
extern NSString *const kGRPCTrailersKey;
/** An object can implement this protocol to receive responses from server from a call. */
@protocol GRPCResponseHandler<NSObject>
@required
/**
* Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by
* the server.
* All the responses must be issued to a user-provided dispatch queue. This property specifies the
* dispatch queue to be used for issuing the notifications.
*/
@property(atomic, readonly) dispatch_queue_t dispatchQueue;
@optional
/**
* Issued when initial metadata is received from the server.
*/
- (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata;
/**
* Issued when a message is received from the server. The message is the raw data received from the
* server, with decompression and without proto deserialization.
*/
extern id const kGRPCHeadersKey;
extern id const kGRPCTrailersKey;
- (void)didReceiveRawMessage:(nullable NSData *)message;
/**
* Issued when a call finished. If the call finished successfully, \a error is nil and \a
* trainingMetadata consists any trailing metadata received from the server. Otherwise, \a error
* is non-nil and contains the corresponding error information, including gRPC error codes and
* error descriptions.
*/
- (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata
error:(nullable NSError *)error;
@end
/**
* Call related parameters. These parameters are automatically specified by Protobuf. If directly
* using the \a GRPCCall2 class, users should specify these parameters manually.
*/
@interface GRPCRequestOptions : NSObject<NSCopying>
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype) new NS_UNAVAILABLE;
/** Initialize with all properties. */
- (instancetype)initWithHost:(NSString *)host
path:(NSString *)path
safety:(GRPCCallSafety)safety NS_DESIGNATED_INITIALIZER;
/** The host serving the RPC service. */
@property(copy, readonly) NSString *host;
/** The path to the RPC call. */
@property(copy, readonly) NSString *path;
/**
* Specify whether the call is idempotent or cachable. gRPC may select different HTTP verbs for the
* call based on this information. The default verb used by gRPC is POST.
*/
@property(readonly) GRPCCallSafety safety;
@end
#pragma mark GRPCCall
/** Represents a single gRPC remote call. */
@interface GRPCCall : GRXWriter
/**
* A \a GRPCCall2 object represents an RPC call.
*/
@interface GRPCCall2 : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype) new NS_UNAVAILABLE;
/**
* The authority for the RPC. If nil, the default authority will be used. This property must be nil
* when Cronet transport is enabled.
* Designated initializer for a call.
* \param requestOptions Protobuf generated parameters for the call.
* \param responseHandler The object to which responses should be issued.
* \param callOptions Options for the call.
*/
@property(atomic, copy, readwrite) NSString *serverName;
- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
responseHandler:(id<GRPCResponseHandler>)responseHandler
callOptions:(nullable GRPCCallOptions *)callOptions
NS_DESIGNATED_INITIALIZER;
/**
* Convenience initializer for a call that uses default call options (see GRPCCallOptions.m for
* the default options).
*/
- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
responseHandler:(id<GRPCResponseHandler>)responseHandler;
/**
* The timeout for the RPC call in seconds. If set to 0, the call will not timeout. If set to
* positive, the gRPC call returns with status GRPCErrorCodeDeadlineExceeded if it is not completed
* within \a timeout seconds. A negative value is not allowed.
* Starts the call. This function must only be called once for each instance.
*/
@property NSTimeInterval timeout;
- (void)start;
/**
* Cancel the request of this call at best effort. It attempts to notify the server that the RPC
* should be cancelled, and issue didCloseWithTrailingMetadata:error: callback with error code
* CANCELED if no other error code has already been issued.
*/
- (void)cancel;
/**
* Send a message to the server. Data are sent as raw bytes in gRPC message frames.
*/
- (void)writeData:(NSData *)data;
/**
* Finish the RPC request and half-close the call. The server may still send messages and/or
* trailers to the client. The method must only be called once and after start is called.
*/
- (void)finish;
/**
* Get a copy of the original call options.
*/
@property(readonly, copy) GRPCCallOptions *callOptions;
/** Get a copy of the original request options. */
@property(readonly, copy) GRPCRequestOptions *requestOptions;
@end
NS_ASSUME_NONNULL_END
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullability-completeness"
/**
* This interface is deprecated. Please use \a GRPCcall2.
*
* Represents a single gRPC remote call.
*/
@interface GRPCCall : GRXWriter
- (instancetype)init NS_UNAVAILABLE;
/**
* The container of the request headers of an RPC conforms to this protocol, which is a subset of
@ -236,7 +346,7 @@ extern id const kGRPCTrailersKey;
*/
- (instancetype)initWithHost:(NSString *)host
path:(NSString *)path
requestsWriter:(GRXWriter *)requestsWriter NS_DESIGNATED_INITIALIZER;
requestsWriter:(GRXWriter *)requestWriter;
/**
* Finishes the request side of this call, notifies the server that the RPC should be cancelled, and
@ -245,22 +355,13 @@ extern id const kGRPCTrailersKey;
- (void)cancel;
/**
* Set the call flag for a specific host path.
*
* Host parameter should not contain the scheme (http:// or https://), only the name or IP addr
* and the port number, for example @"localhost:5050".
* The following methods are deprecated.
*/
+ (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path;
/**
* Set the dispatch queue to be used for callbacks. Current implementation requires \a queue to be a
* serial queue.
*
* This configuration is only effective before the call starts.
*/
@property(atomic, copy, readwrite) NSString *serverName;
@property NSTimeInterval timeout;
- (void)setResponseDispatchQueue:(dispatch_queue_t)queue;
// TODO(jcanizales): Let specify a deadline. As a category of GRXWriter?
@end
#pragma mark Backwards compatibiity
@ -283,3 +384,4 @@ DEPRECATED_MSG_ATTRIBUTE("Use NSDictionary or NSMutableDictionary instead.")
@interface NSMutableDictionary (GRPCRequestHeaders)<GRPCRequestHeaders>
@end
#pragma clang diagnostic pop
#pragma clang diagnostic pop

@ -20,11 +20,16 @@
#import "GRPCCall+OAuth2.h"
#import <RxLibrary/GRXBufferedPipe.h>
#import <RxLibrary/GRXConcurrentWriteable.h>
#import <RxLibrary/GRXImmediateSingleWriter.h>
#import <RxLibrary/GRXWriter+Immediate.h>
#include <grpc/grpc.h>
#include <grpc/support/time.h>
#import "GRPCCallOptions.h"
#import "private/GRPCChannelPool.h"
#import "private/GRPCCompletionQueue.h"
#import "private/GRPCConnectivityMonitor.h"
#import "private/GRPCHost.h"
#import "private/GRPCRequestHeaders.h"
@ -52,6 +57,313 @@ const char *kCFStreamVarName = "grpc_cfstream";
@property(atomic, strong) NSDictionary *responseHeaders;
@property(atomic, strong) NSDictionary *responseTrailers;
@property(atomic) BOOL isWaitingForToken;
- (instancetype)initWithHost:(NSString *)host
path:(NSString *)path
callSafety:(GRPCCallSafety)safety
requestsWriter:(GRXWriter *)requestsWriter
callOptions:(GRPCCallOptions *)callOptions;
@end
@implementation GRPCRequestOptions
- (instancetype)initWithHost:(NSString *)host path:(NSString *)path safety:(GRPCCallSafety)safety {
NSAssert(host.length != 0 && path.length != 0, @"host and path cannot be empty");
if (host.length == 0 || path.length == 0) {
return nil;
}
if ((self = [super init])) {
_host = [host copy];
_path = [path copy];
_safety = safety;
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
GRPCRequestOptions *request =
[[GRPCRequestOptions alloc] initWithHost:_host path:_path safety:_safety];
return request;
}
@end
@implementation GRPCCall2 {
/** Options for the call. */
GRPCCallOptions *_callOptions;
/** The handler of responses. */
id<GRPCResponseHandler> _handler;
// Thread safety of ivars below are protected by _dispatchQueue.
/**
* Make use of legacy GRPCCall to make calls. Nullified when call is finished.
*/
GRPCCall *_call;
/** Flags whether initial metadata has been published to response handler. */
BOOL _initialMetadataPublished;
/** Streaming call writeable to the underlying call. */
GRXBufferedPipe *_pipe;
/** Serial dispatch queue for tasks inside the call. */
dispatch_queue_t _dispatchQueue;
/** Flags whether call has started. */
BOOL _started;
/** Flags whether call has been canceled. */
BOOL _canceled;
/** Flags whether call has been finished. */
BOOL _finished;
}
- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
responseHandler:(id<GRPCResponseHandler>)responseHandler
callOptions:(GRPCCallOptions *)callOptions {
NSAssert(requestOptions.host.length != 0 && requestOptions.path.length != 0,
@"Neither host nor path can be nil.");
NSAssert(requestOptions.safety <= GRPCCallSafetyCacheableRequest, @"Invalid call safety value.");
NSAssert(responseHandler != nil, @"Response handler required.");
if (requestOptions.host.length == 0 || requestOptions.path.length == 0) {
return nil;
}
if (requestOptions.safety > GRPCCallSafetyCacheableRequest) {
return nil;
}
if (responseHandler == nil) {
return nil;
}
if ((self = [super init])) {
_requestOptions = [requestOptions copy];
if (callOptions == nil) {
_callOptions = [[GRPCCallOptions alloc] init];
} else {
_callOptions = [callOptions copy];
}
_handler = responseHandler;
_initialMetadataPublished = NO;
_pipe = [GRXBufferedPipe pipe];
// Set queue QoS only when iOS version is 8.0 or above and Xcode version is 9.0 or above
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
if (@available(iOS 8.0, macOS 10.10, *)) {
_dispatchQueue = dispatch_queue_create(
NULL,
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
} else {
#else
{
#endif
_dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
}
dispatch_set_target_queue(_dispatchQueue, responseHandler.dispatchQueue);
_started = NO;
_canceled = NO;
_finished = NO;
}
return self;
}
- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
responseHandler:(id<GRPCResponseHandler>)responseHandler {
return
[self initWithRequestOptions:requestOptions responseHandler:responseHandler callOptions:nil];
}
- (void)start {
GRPCCall *copiedCall = nil;
@synchronized(self) {
NSAssert(!_started, @"Call already started.");
NSAssert(!_canceled, @"Call already canceled.");
if (_started) {
return;
}
if (_canceled) {
return;
}
_started = YES;
if (!_callOptions) {
_callOptions = [[GRPCCallOptions alloc] init];
}
_call = [[GRPCCall alloc] initWithHost:_requestOptions.host
path:_requestOptions.path
callSafety:_requestOptions.safety
requestsWriter:_pipe
callOptions:_callOptions];
if (_callOptions.initialMetadata) {
[_call.requestHeaders addEntriesFromDictionary:_callOptions.initialMetadata];
}
copiedCall = _call;
}
void (^valueHandler)(id value) = ^(id value) {
@synchronized(self) {
if (self->_handler) {
if (!self->_initialMetadataPublished) {
self->_initialMetadataPublished = YES;
[self issueInitialMetadata:self->_call.responseHeaders];
}
if (value) {
[self issueMessage:value];
}
}
}
};
void (^completionHandler)(NSError *errorOrNil) = ^(NSError *errorOrNil) {
@synchronized(self) {
if (self->_handler) {
if (!self->_initialMetadataPublished) {
self->_initialMetadataPublished = YES;
[self issueInitialMetadata:self->_call.responseHeaders];
}
[self issueClosedWithTrailingMetadata:self->_call.responseTrailers error:errorOrNil];
}
// Clearing _call must happen *after* dispatching close in order to get trailing
// metadata from _call.
if (self->_call) {
// Clean up the request writers. This should have no effect to _call since its
// response writeable is already nullified.
[self->_pipe writesFinishedWithError:nil];
self->_call = nil;
self->_pipe = nil;
}
}
};
id<GRXWriteable> responseWriteable =
[[GRXWriteable alloc] initWithValueHandler:valueHandler completionHandler:completionHandler];
[copiedCall startWithWriteable:responseWriteable];
}
- (void)cancel {
GRPCCall *copiedCall = nil;
@synchronized(self) {
if (_canceled) {
return;
}
_canceled = YES;
copiedCall = _call;
_call = nil;
_pipe = nil;
if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
dispatch_async(_dispatchQueue, ^{
// Copy to local so that block is freed after cancellation completes.
id<GRPCResponseHandler> copiedHandler = nil;
@synchronized(self) {
copiedHandler = self->_handler;
self->_handler = nil;
}
[copiedHandler didCloseWithTrailingMetadata:nil
error:[NSError errorWithDomain:kGRPCErrorDomain
code:GRPCErrorCodeCancelled
userInfo:@{
NSLocalizedDescriptionKey :
@"Canceled by app"
}]];
});
} else {
_handler = nil;
}
}
[copiedCall cancel];
}
- (void)writeData:(NSData *)data {
GRXBufferedPipe *copiedPipe = nil;
@synchronized(self) {
NSAssert(!_canceled, @"Call already canceled.");
NSAssert(!_finished, @"Call is half-closed before sending data.");
if (_canceled) {
return;
}
if (_finished) {
return;
}
if (_pipe) {
copiedPipe = _pipe;
}
}
[copiedPipe writeValue:data];
}
- (void)finish {
GRXBufferedPipe *copiedPipe = nil;
@synchronized(self) {
NSAssert(_started, @"Call not started.");
NSAssert(!_canceled, @"Call already canceled.");
NSAssert(!_finished, @"Call already half-closed.");
if (!_started) {
return;
}
if (_canceled) {
return;
}
if (_finished) {
return;
}
if (_pipe) {
copiedPipe = _pipe;
_pipe = nil;
}
_finished = YES;
}
[copiedPipe writesFinishedWithError:nil];
}
- (void)issueInitialMetadata:(NSDictionary *)initialMetadata {
@synchronized(self) {
if (initialMetadata != nil &&
[_handler respondsToSelector:@selector(didReceiveInitialMetadata:)]) {
dispatch_async(_dispatchQueue, ^{
id<GRPCResponseHandler> copiedHandler = nil;
@synchronized(self) {
copiedHandler = self->_handler;
}
[copiedHandler didReceiveInitialMetadata:initialMetadata];
});
}
}
}
- (void)issueMessage:(id)message {
@synchronized(self) {
if (message != nil && [_handler respondsToSelector:@selector(didReceiveRawMessage:)]) {
dispatch_async(_dispatchQueue, ^{
id<GRPCResponseHandler> copiedHandler = nil;
@synchronized(self) {
copiedHandler = self->_handler;
}
[copiedHandler didReceiveRawMessage:message];
});
}
}
}
- (void)issueClosedWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
@synchronized(self) {
if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
dispatch_async(_dispatchQueue, ^{
id<GRPCResponseHandler> copiedHandler = nil;
@synchronized(self) {
copiedHandler = self->_handler;
// Clean up _handler so that no more responses are reported to the handler.
self->_handler = nil;
}
[copiedHandler didCloseWithTrailingMetadata:trailingMetadata error:error];
});
} else {
_handler = nil;
}
}
}
@end
// The following methods of a C gRPC call object aren't reentrant, and thus
@ -75,6 +387,8 @@ const char *kCFStreamVarName = "grpc_cfstream";
NSString *_host;
NSString *_path;
GRPCCallSafety _callSafety;
GRPCCallOptions *_callOptions;
GRPCWrappedCall *_wrappedCall;
GRPCConnectivityMonitor *_connectivityMonitor;
@ -113,6 +427,9 @@ const char *kCFStreamVarName = "grpc_cfstream";
// Whether the call is finished. If it is, should not call finishWithError again.
BOOL _finished;
// The OAuth2 token fetched from a token provider.
NSString *_fetchedOauth2AccessToken;
}
@synthesize state = _state;
@ -127,6 +444,9 @@ const char *kCFStreamVarName = "grpc_cfstream";
}
+ (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path {
if (host.length == 0 || path.length == 0) {
return;
}
NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path];
switch (callSafety) {
case GRPCCallSafetyDefault:
@ -148,24 +468,42 @@ const char *kCFStreamVarName = "grpc_cfstream";
return [callFlags[hostAndPath] intValue];
}
- (instancetype)init {
return [self initWithHost:nil path:nil requestsWriter:nil];
}
// Designated initializer
- (instancetype)initWithHost:(NSString *)host
path:(NSString *)path
requestsWriter:(GRXWriter *)requestWriter {
return [self initWithHost:host
path:path
callSafety:GRPCCallSafetyDefault
requestsWriter:requestWriter
callOptions:nil];
}
- (instancetype)initWithHost:(NSString *)host
path:(NSString *)path
callSafety:(GRPCCallSafety)safety
requestsWriter:(GRXWriter *)requestWriter
callOptions:(GRPCCallOptions *)callOptions {
// Purposely using pointer rather than length (host.length == 0) for backwards compatibility.
NSAssert(host != nil && path != nil, @"Neither host nor path can be nil.");
NSAssert(safety <= GRPCCallSafetyCacheableRequest, @"Invalid call safety value.");
NSAssert(requestWriter.state == GRXWriterStateNotStarted,
@"The requests writer can't be already started.");
if (!host || !path) {
[NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."];
return nil;
}
if (safety > GRPCCallSafetyCacheableRequest) {
return nil;
}
if (requestWriter.state != GRXWriterStateNotStarted) {
[NSException raise:NSInvalidArgumentException
format:@"The requests writer can't be already started."];
return nil;
}
if ((self = [super init])) {
_host = [host copy];
_path = [path copy];
_callSafety = safety;
_callOptions = [callOptions copy];
// Serial queue to invoke the non-reentrant methods of the grpc_call object.
_callQueue = dispatch_queue_create("io.grpc.call", NULL);
@ -209,11 +547,7 @@ const char *kCFStreamVarName = "grpc_cfstream";
[_responseWriteable enqueueSuccessfulCompletion];
}
// Connectivity monitor is not required for CFStream
char *enableCFStream = getenv(kCFStreamVarName);
if (enableCFStream == nil || enableCFStream[0] != '1') {
[GRPCConnectivityMonitor unregisterObserver:self];
}
[GRPCConnectivityMonitor unregisterObserver:self];
// If the call isn't retained anywhere else, it can be deallocated now.
_retainSelf = nil;
@ -221,13 +555,14 @@ const char *kCFStreamVarName = "grpc_cfstream";
- (void)cancelCall {
// Can be called from any thread, any number of times.
[_wrappedCall cancel];
@synchronized(self) {
[_wrappedCall cancel];
}
}
- (void)cancel {
if (!self.isWaitingForToken) {
@synchronized(self) {
[self cancelCall];
} else {
self.isWaitingForToken = NO;
}
[self
@ -317,11 +652,37 @@ const char *kCFStreamVarName = "grpc_cfstream";
#pragma mark Send headers
- (void)sendHeaders:(NSDictionary *)headers {
- (void)sendHeaders {
// TODO (mxyan): Remove after deprecated methods are removed
uint32_t callSafetyFlags = 0;
switch (_callSafety) {
case GRPCCallSafetyDefault:
callSafetyFlags = 0;
break;
case GRPCCallSafetyIdempotentRequest:
callSafetyFlags = GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST;
break;
case GRPCCallSafetyCacheableRequest:
callSafetyFlags = GRPC_INITIAL_METADATA_CACHEABLE_REQUEST;
break;
}
NSMutableDictionary *headers = [_requestHeaders mutableCopy];
NSString *fetchedOauth2AccessToken;
@synchronized(self) {
fetchedOauth2AccessToken = _fetchedOauth2AccessToken;
}
if (fetchedOauth2AccessToken != nil) {
headers[@"authorization"] = [kBearerPrefix stringByAppendingString:fetchedOauth2AccessToken];
} else if (_callOptions.oauth2AccessToken != nil) {
headers[@"authorization"] =
[kBearerPrefix stringByAppendingString:_callOptions.oauth2AccessToken];
}
// TODO(jcanizales): Add error handlers for async failures
GRPCOpSendMetadata *op = [[GRPCOpSendMetadata alloc]
initWithMetadata:headers
flags:[GRPCCall callFlagsForHost:_host path:_path]
flags:callSafetyFlags
handler:nil]; // No clean-up needed after SEND_INITIAL_METADATA
if (!_unaryCall) {
[_wrappedCall startBatchWithOperations:@[ op ]];
@ -458,13 +819,27 @@ const char *kCFStreamVarName = "grpc_cfstream";
_responseWriteable =
[[GRXConcurrentWriteable alloc] initWithWriteable:writeable dispatchQueue:_responseQueue];
_wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host
serverName:_serverName
path:_path
timeout:_timeout];
NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?");
GRPCPooledChannel *channel =
[[GRPCChannelPool sharedInstance] channelWithHost:_host callOptions:_callOptions];
GRPCWrappedCall *wrappedCall = [channel wrappedCallWithPath:_path
completionQueue:[GRPCCompletionQueue completionQueue]
callOptions:_callOptions];
if (wrappedCall == nil) {
[self maybeFinishWithError:[NSError errorWithDomain:kGRPCErrorDomain
code:GRPCErrorCodeUnavailable
userInfo:@{
NSLocalizedDescriptionKey :
@"Failed to create call or channel."
}]];
return;
}
@synchronized(self) {
_wrappedCall = wrappedCall;
}
[self sendHeaders:_requestHeaders];
[self sendHeaders];
[self invokeCall];
// Connectivity monitor is not required for CFStream
@ -486,18 +861,45 @@ const char *kCFStreamVarName = "grpc_cfstream";
// that the life of the instance is determined by this retain cycle.
_retainSelf = self;
if (self.tokenProvider != nil) {
self.isWaitingForToken = YES;
__weak typeof(self) weakSelf = self;
[self.tokenProvider getTokenWithHandler:^(NSString *token) {
typeof(self) strongSelf = weakSelf;
if (strongSelf && strongSelf.isWaitingForToken) {
if (token) {
NSString *t = [kBearerPrefix stringByAppendingString:token];
strongSelf.requestHeaders[kAuthorizationHeader] = t;
if (_callOptions == nil) {
GRPCMutableCallOptions *callOptions = [[GRPCHost callOptionsForHost:_host] mutableCopy];
if (_serverName.length != 0) {
callOptions.serverAuthority = _serverName;
}
if (_timeout > 0) {
callOptions.timeout = _timeout;
}
uint32_t callFlags = [GRPCCall callFlagsForHost:_host path:_path];
if (callFlags != 0) {
if (callFlags == GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST) {
_callSafety = GRPCCallSafetyIdempotentRequest;
} else if (callFlags == GRPC_INITIAL_METADATA_CACHEABLE_REQUEST) {
_callSafety = GRPCCallSafetyCacheableRequest;
}
}
id<GRPCAuthorizationProtocol> tokenProvider = self.tokenProvider;
if (tokenProvider != nil) {
callOptions.authTokenProvider = tokenProvider;
}
_callOptions = callOptions;
}
NSAssert(_callOptions.authTokenProvider == nil || _callOptions.oauth2AccessToken == nil,
@"authTokenProvider and oauth2AccessToken cannot be set at the same time");
if (_callOptions.authTokenProvider != nil) {
@synchronized(self) {
self.isWaitingForToken = YES;
}
[_callOptions.authTokenProvider getTokenWithHandler:^(NSString *token) {
@synchronized(self) {
if (self.isWaitingForToken) {
if (token) {
self->_fetchedOauth2AccessToken = [token copy];
}
[self startCallWithWriteable:writeable];
self.isWaitingForToken = NO;
}
[strongSelf startCallWithWriteable:writeable];
strongSelf.isWaitingForToken = NO;
}
}];
} else {

@ -0,0 +1,348 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Safety remark of a gRPC method as defined in RFC 2616 Section 9.1
*/
typedef NS_ENUM(NSUInteger, GRPCCallSafety) {
/** Signal that there is no guarantees on how the call affects the server state. */
GRPCCallSafetyDefault = 0,
/** Signal that the call is idempotent. gRPC is free to use PUT verb. */
GRPCCallSafetyIdempotentRequest = 1,
/**
* Signal that the call is cacheable and will not affect server state. gRPC is free to use GET
* verb.
*/
GRPCCallSafetyCacheableRequest = 2,
};
// Compression algorithm to be used by a gRPC call
typedef NS_ENUM(NSUInteger, GRPCCompressionAlgorithm) {
GRPCCompressNone = 0,
GRPCCompressDeflate,
GRPCCompressGzip,
GRPCStreamCompressGzip,
};
// GRPCCompressAlgorithm is deprecated; use GRPCCompressionAlgorithm
typedef GRPCCompressionAlgorithm GRPCCompressAlgorithm;
/** The transport to be used by a gRPC call */
typedef NS_ENUM(NSUInteger, GRPCTransportType) {
GRPCTransportTypeDefault = 0,
/** gRPC internal HTTP/2 stack with BoringSSL */
GRPCTransportTypeChttp2BoringSSL = 0,
/** Cronet stack */
GRPCTransportTypeCronet,
/** Insecure channel. FOR TEST ONLY! */
GRPCTransportTypeInsecure,
};
/**
* Implement this protocol to provide a token to gRPC when a call is initiated.
*/
@protocol GRPCAuthorizationProtocol
/**
* This method is called when gRPC is about to start the call. When OAuth token is acquired,
* \a handler is expected to be called with \a token being the new token to be used for this call.
*/
- (void)getTokenWithHandler:(void (^)(NSString *_Nullable token))handler;
@end
@interface GRPCCallOptions : NSObject<NSCopying, NSMutableCopying>
// Call parameters
/**
* The authority for the RPC. If nil, the default authority will be used.
*
* Note: This property does not have effect on Cronet transport and will be ignored.
* Note: This property cannot be used to validate a self-signed server certificate. It control the
* :authority header field of the call and performs an extra check that server's certificate
* matches the :authority header.
*/
@property(copy, readonly, nullable) NSString *serverAuthority;
/**
* The timeout for the RPC call in seconds. If set to 0, the call will not timeout. If set to
* positive, the gRPC call returns with status GRPCErrorCodeDeadlineExceeded if it is not completed
* within \a timeout seconds. A negative value is not allowed.
*/
@property(readonly) NSTimeInterval timeout;
// OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
/**
* The OAuth2 access token string. The string is prefixed with "Bearer " then used as value of the
* request's "authorization" header field. This parameter should not be used simultaneously with
* \a authTokenProvider.
*/
@property(copy, readonly, nullable) NSString *oauth2AccessToken;
/**
* The interface to get the OAuth2 access token string. gRPC will attempt to acquire token when
* initiating the call. This parameter should not be used simultaneously with \a oauth2AccessToken.
*/
@property(readonly, nullable) id<GRPCAuthorizationProtocol> authTokenProvider;
/**
* Initial metadata key-value pairs that should be included in the request.
*/
@property(copy, readonly, nullable) NSDictionary *initialMetadata;
// Channel parameters; take into account of channel signature.
/**
* Custom string that is prefixed to a request's user-agent header field before gRPC's internal
* user-agent string.
*/
@property(copy, readonly, nullable) NSString *userAgentPrefix;
/**
* The size limit for the response received from server. If it is exceeded, an error with status
* code GRPCErrorCodeResourceExhausted is returned.
*/
@property(readonly) NSUInteger responseSizeLimit;
/**
* The compression algorithm to be used by the gRPC call. For more details refer to
* https://github.com/grpc/grpc/blob/master/doc/compression.md
*/
@property(readonly) GRPCCompressionAlgorithm compressionAlgorithm;
/**
* Enable/Disable gRPC call's retry feature. The default is enabled. For details of this feature
* refer to
* https://github.com/grpc/proposal/blob/master/A6-client-retries.md
*/
@property(readonly) BOOL retryEnabled;
// HTTP/2 keep-alive feature. The parameter \a keepaliveInterval specifies the interval between two
// PING frames. The parameter \a keepaliveTimeout specifies the length of the period for which the
// call should wait for PING ACK. If PING ACK is not received after this period, the call fails.
// Negative values are not allowed.
@property(readonly) NSTimeInterval keepaliveInterval;
@property(readonly) NSTimeInterval keepaliveTimeout;
// Parameters for connection backoff. Negative values are not allowed.
// For details of gRPC's backoff behavior, refer to
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
@property(readonly) NSTimeInterval connectMinTimeout;
@property(readonly) NSTimeInterval connectInitialBackoff;
@property(readonly) NSTimeInterval connectMaxBackoff;
/**
* Specify channel args to be used for this call. For a list of channel args available, see
* grpc/grpc_types.h
*/
@property(copy, readonly, nullable) NSDictionary *additionalChannelArgs;
// Parameters for SSL authentication.
/**
* PEM format root certifications that is trusted. If set to nil, gRPC uses a list of default
* root certificates.
*/
@property(copy, readonly, nullable) NSString *PEMRootCertificates;
/**
* PEM format private key for client authentication, if required by the server.
*/
@property(copy, readonly, nullable) NSString *PEMPrivateKey;
/**
* PEM format certificate chain for client authentication, if required by the server.
*/
@property(copy, readonly, nullable) NSString *PEMCertificateChain;
/**
* Select the transport type to be used for this call.
*/
@property(readonly) GRPCTransportType transportType;
/**
* Override the hostname during the TLS hostname validation process.
*/
@property(copy, readonly, nullable) NSString *hostNameOverride;
/**
* A string that specify the domain where channel is being cached. Channels with different domains
* will not get cached to the same connection.
*/
@property(copy, readonly, nullable) NSString *channelPoolDomain;
/**
* Channel id allows control of channel caching within a channelPoolDomain. A call with a unique
* channelID will create a new channel (connection) instead of reusing an existing one. Multiple
* calls in the same channelPoolDomain using identical channelID are allowed to share connection
* if other channel options are also the same.
*/
@property(readonly) NSUInteger channelID;
/**
* Return if the channel options are equal to another object.
*/
- (BOOL)hasChannelOptionsEqualTo:(GRPCCallOptions *)callOptions;
/**
* Hash for channel options.
*/
@property(readonly) NSUInteger channelOptionsHash;
@end
@interface GRPCMutableCallOptions : GRPCCallOptions<NSCopying, NSMutableCopying>
// Call parameters
/**
* The authority for the RPC. If nil, the default authority will be used.
*
* Note: This property does not have effect on Cronet transport and will be ignored.
* Note: This property cannot be used to validate a self-signed server certificate. It control the
* :authority header field of the call and performs an extra check that server's certificate
* matches the :authority header.
*/
@property(copy, readwrite, nullable) NSString *serverAuthority;
/**
* The timeout for the RPC call in seconds. If set to 0, the call will not timeout. If set to
* positive, the gRPC call returns with status GRPCErrorCodeDeadlineExceeded if it is not completed
* within \a timeout seconds. Negative value is invalid; setting the parameter to negative value
* will reset the parameter to 0.
*/
@property(readwrite) NSTimeInterval timeout;
// OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
/**
* The OAuth2 access token string. The string is prefixed with "Bearer " then used as value of the
* request's "authorization" header field. This parameter should not be used simultaneously with
* \a authTokenProvider.
*/
@property(copy, readwrite, nullable) NSString *oauth2AccessToken;
/**
* The interface to get the OAuth2 access token string. gRPC will attempt to acquire token when
* initiating the call. This parameter should not be used simultaneously with \a oauth2AccessToken.
*/
@property(readwrite, nullable) id<GRPCAuthorizationProtocol> authTokenProvider;
/**
* Initial metadata key-value pairs that should be included in the request.
*/
@property(copy, readwrite, nullable) NSDictionary *initialMetadata;
// Channel parameters; take into account of channel signature.
/**
* Custom string that is prefixed to a request's user-agent header field before gRPC's internal
* user-agent string.
*/
@property(copy, readwrite, nullable) NSString *userAgentPrefix;
/**
* The size limit for the response received from server. If it is exceeded, an error with status
* code GRPCErrorCodeResourceExhausted is returned.
*/
@property(readwrite) NSUInteger responseSizeLimit;
/**
* The compression algorithm to be used by the gRPC call. For more details refer to
* https://github.com/grpc/grpc/blob/master/doc/compression.md
*/
@property(readwrite) GRPCCompressionAlgorithm compressionAlgorithm;
/**
* Enable/Disable gRPC call's retry feature. The default is enabled. For details of this feature
* refer to
* https://github.com/grpc/proposal/blob/master/A6-client-retries.md
*/
@property(readwrite) BOOL retryEnabled;
// HTTP/2 keep-alive feature. The parameter \a keepaliveInterval specifies the interval between two
// PING frames. The parameter \a keepaliveTimeout specifies the length of the period for which the
// call should wait for PING ACK. If PING ACK is not received after this period, the call fails.
// Negative values are invalid; setting these parameters to negative value will reset the
// corresponding parameter to 0.
@property(readwrite) NSTimeInterval keepaliveInterval;
@property(readwrite) NSTimeInterval keepaliveTimeout;
// Parameters for connection backoff. Negative value is invalid; setting the parameters to negative
// value will reset corresponding parameter to 0.
// For details of gRPC's backoff behavior, refer to
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
@property(readwrite) NSTimeInterval connectMinTimeout;
@property(readwrite) NSTimeInterval connectInitialBackoff;
@property(readwrite) NSTimeInterval connectMaxBackoff;
/**
* Specify channel args to be used for this call. For a list of channel args available, see
* grpc/grpc_types.h
*/
@property(copy, readwrite, nullable) NSDictionary *additionalChannelArgs;
// Parameters for SSL authentication.
/**
* PEM format root certifications that is trusted. If set to nil, gRPC uses a list of default
* root certificates.
*/
@property(copy, readwrite, nullable) NSString *PEMRootCertificates;
/**
* PEM format private key for client authentication, if required by the server.
*/
@property(copy, readwrite, nullable) NSString *PEMPrivateKey;
/**
* PEM format certificate chain for client authentication, if required by the server.
*/
@property(copy, readwrite, nullable) NSString *PEMCertificateChain;
/**
* Select the transport type to be used for this call.
*/
@property(readwrite) GRPCTransportType transportType;
/**
* Override the hostname during the TLS hostname validation process.
*/
@property(copy, readwrite, nullable) NSString *hostNameOverride;
/**
* A string that specify the domain where channel is being cached. Channels with different domains
* will not get cached to the same channel. For example, a gRPC example app may use the channel pool
* domain 'io.grpc.example' so that its calls do not reuse the channel created by other modules in
* the same process.
*/
@property(copy, readwrite, nullable) NSString *channelPoolDomain;
/**
* Channel id allows a call to force creating a new channel (connection) rather than using a cached
* channel. Calls using distinct channelID's will not get cached to the same channel.
*/
@property(readwrite) NSUInteger channelID;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,525 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import "GRPCCallOptions.h"
#import "internal/GRPCCallOptions+Internal.h"
// The default values for the call options.
static NSString *const kDefaultServerAuthority = nil;
static const NSTimeInterval kDefaultTimeout = 0;
static NSDictionary *const kDefaultInitialMetadata = nil;
static NSString *const kDefaultUserAgentPrefix = nil;
static const NSUInteger kDefaultResponseSizeLimit = 0;
static const GRPCCompressionAlgorithm kDefaultCompressionAlgorithm = GRPCCompressNone;
static const BOOL kDefaultRetryEnabled = YES;
static const NSTimeInterval kDefaultKeepaliveInterval = 0;
static const NSTimeInterval kDefaultKeepaliveTimeout = 0;
static const NSTimeInterval kDefaultConnectMinTimeout = 0;
static const NSTimeInterval kDefaultConnectInitialBackoff = 0;
static const NSTimeInterval kDefaultConnectMaxBackoff = 0;
static NSDictionary *const kDefaultAdditionalChannelArgs = nil;
static NSString *const kDefaultPEMRootCertificates = nil;
static NSString *const kDefaultPEMPrivateKey = nil;
static NSString *const kDefaultPEMCertificateChain = nil;
static NSString *const kDefaultOauth2AccessToken = nil;
static const id<GRPCAuthorizationProtocol> kDefaultAuthTokenProvider = nil;
static const GRPCTransportType kDefaultTransportType = GRPCTransportTypeChttp2BoringSSL;
static NSString *const kDefaultHostNameOverride = nil;
static const id kDefaultLogContext = nil;
static NSString *const kDefaultChannelPoolDomain = nil;
static const NSUInteger kDefaultChannelID = 0;
// Check if two objects are equal. Returns YES if both are nil;
static BOOL areObjectsEqual(id obj1, id obj2) {
if (obj1 == obj2) {
return YES;
}
if (obj1 == nil || obj2 == nil) {
return NO;
}
return [obj1 isEqual:obj2];
}
@implementation GRPCCallOptions {
@protected
NSString *_serverAuthority;
NSTimeInterval _timeout;
NSString *_oauth2AccessToken;
id<GRPCAuthorizationProtocol> _authTokenProvider;
NSDictionary *_initialMetadata;
NSString *_userAgentPrefix;
NSUInteger _responseSizeLimit;
GRPCCompressionAlgorithm _compressionAlgorithm;
BOOL _retryEnabled;
NSTimeInterval _keepaliveInterval;
NSTimeInterval _keepaliveTimeout;
NSTimeInterval _connectMinTimeout;
NSTimeInterval _connectInitialBackoff;
NSTimeInterval _connectMaxBackoff;
NSDictionary *_additionalChannelArgs;
NSString *_PEMRootCertificates;
NSString *_PEMPrivateKey;
NSString *_PEMCertificateChain;
GRPCTransportType _transportType;
NSString *_hostNameOverride;
id<NSObject> _logContext;
NSString *_channelPoolDomain;
NSUInteger _channelID;
}
@synthesize serverAuthority = _serverAuthority;
@synthesize timeout = _timeout;
@synthesize oauth2AccessToken = _oauth2AccessToken;
@synthesize authTokenProvider = _authTokenProvider;
@synthesize initialMetadata = _initialMetadata;
@synthesize userAgentPrefix = _userAgentPrefix;
@synthesize responseSizeLimit = _responseSizeLimit;
@synthesize compressionAlgorithm = _compressionAlgorithm;
@synthesize retryEnabled = _retryEnabled;
@synthesize keepaliveInterval = _keepaliveInterval;
@synthesize keepaliveTimeout = _keepaliveTimeout;
@synthesize connectMinTimeout = _connectMinTimeout;
@synthesize connectInitialBackoff = _connectInitialBackoff;
@synthesize connectMaxBackoff = _connectMaxBackoff;
@synthesize additionalChannelArgs = _additionalChannelArgs;
@synthesize PEMRootCertificates = _PEMRootCertificates;
@synthesize PEMPrivateKey = _PEMPrivateKey;
@synthesize PEMCertificateChain = _PEMCertificateChain;
@synthesize transportType = _transportType;
@synthesize hostNameOverride = _hostNameOverride;
@synthesize logContext = _logContext;
@synthesize channelPoolDomain = _channelPoolDomain;
@synthesize channelID = _channelID;
- (instancetype)init {
return [self initWithServerAuthority:kDefaultServerAuthority
timeout:kDefaultTimeout
oauth2AccessToken:kDefaultOauth2AccessToken
authTokenProvider:kDefaultAuthTokenProvider
initialMetadata:kDefaultInitialMetadata
userAgentPrefix:kDefaultUserAgentPrefix
responseSizeLimit:kDefaultResponseSizeLimit
compressionAlgorithm:kDefaultCompressionAlgorithm
retryEnabled:kDefaultRetryEnabled
keepaliveInterval:kDefaultKeepaliveInterval
keepaliveTimeout:kDefaultKeepaliveTimeout
connectMinTimeout:kDefaultConnectMinTimeout
connectInitialBackoff:kDefaultConnectInitialBackoff
connectMaxBackoff:kDefaultConnectMaxBackoff
additionalChannelArgs:kDefaultAdditionalChannelArgs
PEMRootCertificates:kDefaultPEMRootCertificates
PEMPrivateKey:kDefaultPEMPrivateKey
PEMCertificateChain:kDefaultPEMCertificateChain
transportType:kDefaultTransportType
hostNameOverride:kDefaultHostNameOverride
logContext:kDefaultLogContext
channelPoolDomain:kDefaultChannelPoolDomain
channelID:kDefaultChannelID];
}
- (instancetype)initWithServerAuthority:(NSString *)serverAuthority
timeout:(NSTimeInterval)timeout
oauth2AccessToken:(NSString *)oauth2AccessToken
authTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider
initialMetadata:(NSDictionary *)initialMetadata
userAgentPrefix:(NSString *)userAgentPrefix
responseSizeLimit:(NSUInteger)responseSizeLimit
compressionAlgorithm:(GRPCCompressionAlgorithm)compressionAlgorithm
retryEnabled:(BOOL)retryEnabled
keepaliveInterval:(NSTimeInterval)keepaliveInterval
keepaliveTimeout:(NSTimeInterval)keepaliveTimeout
connectMinTimeout:(NSTimeInterval)connectMinTimeout
connectInitialBackoff:(NSTimeInterval)connectInitialBackoff
connectMaxBackoff:(NSTimeInterval)connectMaxBackoff
additionalChannelArgs:(NSDictionary *)additionalChannelArgs
PEMRootCertificates:(NSString *)PEMRootCertificates
PEMPrivateKey:(NSString *)PEMPrivateKey
PEMCertificateChain:(NSString *)PEMCertificateChain
transportType:(GRPCTransportType)transportType
hostNameOverride:(NSString *)hostNameOverride
logContext:(id)logContext
channelPoolDomain:(NSString *)channelPoolDomain
channelID:(NSUInteger)channelID {
if ((self = [super init])) {
_serverAuthority = [serverAuthority copy];
_timeout = timeout < 0 ? 0 : timeout;
_oauth2AccessToken = [oauth2AccessToken copy];
_authTokenProvider = authTokenProvider;
_initialMetadata =
initialMetadata == nil
? nil
: [[NSDictionary alloc] initWithDictionary:initialMetadata copyItems:YES];
_userAgentPrefix = [userAgentPrefix copy];
_responseSizeLimit = responseSizeLimit;
_compressionAlgorithm = compressionAlgorithm;
_retryEnabled = retryEnabled;
_keepaliveInterval = keepaliveInterval < 0 ? 0 : keepaliveInterval;
_keepaliveTimeout = keepaliveTimeout < 0 ? 0 : keepaliveTimeout;
_connectMinTimeout = connectMinTimeout < 0 ? 0 : connectMinTimeout;
_connectInitialBackoff = connectInitialBackoff < 0 ? 0 : connectInitialBackoff;
_connectMaxBackoff = connectMaxBackoff < 0 ? 0 : connectMaxBackoff;
_additionalChannelArgs =
additionalChannelArgs == nil
? nil
: [[NSDictionary alloc] initWithDictionary:additionalChannelArgs copyItems:YES];
_PEMRootCertificates = [PEMRootCertificates copy];
_PEMPrivateKey = [PEMPrivateKey copy];
_PEMCertificateChain = [PEMCertificateChain copy];
_transportType = transportType;
_hostNameOverride = [hostNameOverride copy];
_logContext = logContext;
_channelPoolDomain = [channelPoolDomain copy];
_channelID = channelID;
}
return self;
}
- (nonnull id)copyWithZone:(NSZone *)zone {
GRPCCallOptions *newOptions =
[[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority
timeout:_timeout
oauth2AccessToken:_oauth2AccessToken
authTokenProvider:_authTokenProvider
initialMetadata:_initialMetadata
userAgentPrefix:_userAgentPrefix
responseSizeLimit:_responseSizeLimit
compressionAlgorithm:_compressionAlgorithm
retryEnabled:_retryEnabled
keepaliveInterval:_keepaliveInterval
keepaliveTimeout:_keepaliveTimeout
connectMinTimeout:_connectMinTimeout
connectInitialBackoff:_connectInitialBackoff
connectMaxBackoff:_connectMaxBackoff
additionalChannelArgs:_additionalChannelArgs
PEMRootCertificates:_PEMRootCertificates
PEMPrivateKey:_PEMPrivateKey
PEMCertificateChain:_PEMCertificateChain
transportType:_transportType
hostNameOverride:_hostNameOverride
logContext:_logContext
channelPoolDomain:_channelPoolDomain
channelID:_channelID];
return newOptions;
}
- (nonnull id)mutableCopyWithZone:(NSZone *)zone {
GRPCMutableCallOptions *newOptions = [[GRPCMutableCallOptions allocWithZone:zone]
initWithServerAuthority:[_serverAuthority copy]
timeout:_timeout
oauth2AccessToken:[_oauth2AccessToken copy]
authTokenProvider:_authTokenProvider
initialMetadata:[[NSDictionary alloc] initWithDictionary:_initialMetadata
copyItems:YES]
userAgentPrefix:[_userAgentPrefix copy]
responseSizeLimit:_responseSizeLimit
compressionAlgorithm:_compressionAlgorithm
retryEnabled:_retryEnabled
keepaliveInterval:_keepaliveInterval
keepaliveTimeout:_keepaliveTimeout
connectMinTimeout:_connectMinTimeout
connectInitialBackoff:_connectInitialBackoff
connectMaxBackoff:_connectMaxBackoff
additionalChannelArgs:[[NSDictionary alloc] initWithDictionary:_additionalChannelArgs
copyItems:YES]
PEMRootCertificates:[_PEMRootCertificates copy]
PEMPrivateKey:[_PEMPrivateKey copy]
PEMCertificateChain:[_PEMCertificateChain copy]
transportType:_transportType
hostNameOverride:[_hostNameOverride copy]
logContext:_logContext
channelPoolDomain:[_channelPoolDomain copy]
channelID:_channelID];
return newOptions;
}
- (BOOL)hasChannelOptionsEqualTo:(GRPCCallOptions *)callOptions {
if (callOptions == nil) return NO;
if (!areObjectsEqual(callOptions.userAgentPrefix, _userAgentPrefix)) return NO;
if (!(callOptions.responseSizeLimit == _responseSizeLimit)) return NO;
if (!(callOptions.compressionAlgorithm == _compressionAlgorithm)) return NO;
if (!(callOptions.retryEnabled == _retryEnabled)) return NO;
if (!(callOptions.keepaliveInterval == _keepaliveInterval)) return NO;
if (!(callOptions.keepaliveTimeout == _keepaliveTimeout)) return NO;
if (!(callOptions.connectMinTimeout == _connectMinTimeout)) return NO;
if (!(callOptions.connectInitialBackoff == _connectInitialBackoff)) return NO;
if (!(callOptions.connectMaxBackoff == _connectMaxBackoff)) return NO;
if (!areObjectsEqual(callOptions.additionalChannelArgs, _additionalChannelArgs)) return NO;
if (!areObjectsEqual(callOptions.PEMRootCertificates, _PEMRootCertificates)) return NO;
if (!areObjectsEqual(callOptions.PEMPrivateKey, _PEMPrivateKey)) return NO;
if (!areObjectsEqual(callOptions.PEMCertificateChain, _PEMCertificateChain)) return NO;
if (!areObjectsEqual(callOptions.hostNameOverride, _hostNameOverride)) return NO;
if (!(callOptions.transportType == _transportType)) return NO;
if (!areObjectsEqual(callOptions.logContext, _logContext)) return NO;
if (!areObjectsEqual(callOptions.channelPoolDomain, _channelPoolDomain)) return NO;
if (!(callOptions.channelID == _channelID)) return NO;
return YES;
}
- (NSUInteger)channelOptionsHash {
NSUInteger result = 0;
result ^= _userAgentPrefix.hash;
result ^= _responseSizeLimit;
result ^= _compressionAlgorithm;
result ^= _retryEnabled;
result ^= (unsigned int)(_keepaliveInterval * 1000);
result ^= (unsigned int)(_keepaliveTimeout * 1000);
result ^= (unsigned int)(_connectMinTimeout * 1000);
result ^= (unsigned int)(_connectInitialBackoff * 1000);
result ^= (unsigned int)(_connectMaxBackoff * 1000);
result ^= _additionalChannelArgs.hash;
result ^= _PEMRootCertificates.hash;
result ^= _PEMPrivateKey.hash;
result ^= _PEMCertificateChain.hash;
result ^= _hostNameOverride.hash;
result ^= _transportType;
result ^= _logContext.hash;
result ^= _channelPoolDomain.hash;
result ^= _channelID;
return result;
}
@end
@implementation GRPCMutableCallOptions
@dynamic serverAuthority;
@dynamic timeout;
@dynamic oauth2AccessToken;
@dynamic authTokenProvider;
@dynamic initialMetadata;
@dynamic userAgentPrefix;
@dynamic responseSizeLimit;
@dynamic compressionAlgorithm;
@dynamic retryEnabled;
@dynamic keepaliveInterval;
@dynamic keepaliveTimeout;
@dynamic connectMinTimeout;
@dynamic connectInitialBackoff;
@dynamic connectMaxBackoff;
@dynamic additionalChannelArgs;
@dynamic PEMRootCertificates;
@dynamic PEMPrivateKey;
@dynamic PEMCertificateChain;
@dynamic transportType;
@dynamic hostNameOverride;
@dynamic logContext;
@dynamic channelPoolDomain;
@dynamic channelID;
- (instancetype)init {
return [self initWithServerAuthority:kDefaultServerAuthority
timeout:kDefaultTimeout
oauth2AccessToken:kDefaultOauth2AccessToken
authTokenProvider:kDefaultAuthTokenProvider
initialMetadata:kDefaultInitialMetadata
userAgentPrefix:kDefaultUserAgentPrefix
responseSizeLimit:kDefaultResponseSizeLimit
compressionAlgorithm:kDefaultCompressionAlgorithm
retryEnabled:kDefaultRetryEnabled
keepaliveInterval:kDefaultKeepaliveInterval
keepaliveTimeout:kDefaultKeepaliveTimeout
connectMinTimeout:kDefaultConnectMinTimeout
connectInitialBackoff:kDefaultConnectInitialBackoff
connectMaxBackoff:kDefaultConnectMaxBackoff
additionalChannelArgs:kDefaultAdditionalChannelArgs
PEMRootCertificates:kDefaultPEMRootCertificates
PEMPrivateKey:kDefaultPEMPrivateKey
PEMCertificateChain:kDefaultPEMCertificateChain
transportType:kDefaultTransportType
hostNameOverride:kDefaultHostNameOverride
logContext:kDefaultLogContext
channelPoolDomain:kDefaultChannelPoolDomain
channelID:kDefaultChannelID];
}
- (nonnull id)copyWithZone:(NSZone *)zone {
GRPCCallOptions *newOptions =
[[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority
timeout:_timeout
oauth2AccessToken:_oauth2AccessToken
authTokenProvider:_authTokenProvider
initialMetadata:_initialMetadata
userAgentPrefix:_userAgentPrefix
responseSizeLimit:_responseSizeLimit
compressionAlgorithm:_compressionAlgorithm
retryEnabled:_retryEnabled
keepaliveInterval:_keepaliveInterval
keepaliveTimeout:_keepaliveTimeout
connectMinTimeout:_connectMinTimeout
connectInitialBackoff:_connectInitialBackoff
connectMaxBackoff:_connectMaxBackoff
additionalChannelArgs:_additionalChannelArgs
PEMRootCertificates:_PEMRootCertificates
PEMPrivateKey:_PEMPrivateKey
PEMCertificateChain:_PEMCertificateChain
transportType:_transportType
hostNameOverride:_hostNameOverride
logContext:_logContext
channelPoolDomain:_channelPoolDomain
channelID:_channelID];
return newOptions;
}
- (nonnull id)mutableCopyWithZone:(NSZone *)zone {
GRPCMutableCallOptions *newOptions = [[GRPCMutableCallOptions allocWithZone:zone]
initWithServerAuthority:_serverAuthority
timeout:_timeout
oauth2AccessToken:_oauth2AccessToken
authTokenProvider:_authTokenProvider
initialMetadata:_initialMetadata
userAgentPrefix:_userAgentPrefix
responseSizeLimit:_responseSizeLimit
compressionAlgorithm:_compressionAlgorithm
retryEnabled:_retryEnabled
keepaliveInterval:_keepaliveInterval
keepaliveTimeout:_keepaliveTimeout
connectMinTimeout:_connectMinTimeout
connectInitialBackoff:_connectInitialBackoff
connectMaxBackoff:_connectMaxBackoff
additionalChannelArgs:[_additionalChannelArgs copy]
PEMRootCertificates:_PEMRootCertificates
PEMPrivateKey:_PEMPrivateKey
PEMCertificateChain:_PEMCertificateChain
transportType:_transportType
hostNameOverride:_hostNameOverride
logContext:_logContext
channelPoolDomain:_channelPoolDomain
channelID:_channelID];
return newOptions;
}
- (void)setServerAuthority:(NSString *)serverAuthority {
_serverAuthority = [serverAuthority copy];
}
- (void)setTimeout:(NSTimeInterval)timeout {
if (timeout < 0) {
_timeout = 0;
} else {
_timeout = timeout;
}
}
- (void)setOauth2AccessToken:(NSString *)oauth2AccessToken {
_oauth2AccessToken = [oauth2AccessToken copy];
}
- (void)setAuthTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider {
_authTokenProvider = authTokenProvider;
}
- (void)setInitialMetadata:(NSDictionary *)initialMetadata {
_initialMetadata = [[NSDictionary alloc] initWithDictionary:initialMetadata copyItems:YES];
}
- (void)setUserAgentPrefix:(NSString *)userAgentPrefix {
_userAgentPrefix = [userAgentPrefix copy];
}
- (void)setResponseSizeLimit:(NSUInteger)responseSizeLimit {
_responseSizeLimit = responseSizeLimit;
}
- (void)setCompressionAlgorithm:(GRPCCompressionAlgorithm)compressionAlgorithm {
_compressionAlgorithm = compressionAlgorithm;
}
- (void)setRetryEnabled:(BOOL)retryEnabled {
_retryEnabled = retryEnabled;
}
- (void)setKeepaliveInterval:(NSTimeInterval)keepaliveInterval {
if (keepaliveInterval < 0) {
_keepaliveInterval = 0;
} else {
_keepaliveInterval = keepaliveInterval;
}
}
- (void)setKeepaliveTimeout:(NSTimeInterval)keepaliveTimeout {
if (keepaliveTimeout < 0) {
_keepaliveTimeout = 0;
} else {
_keepaliveTimeout = keepaliveTimeout;
}
}
- (void)setConnectMinTimeout:(NSTimeInterval)connectMinTimeout {
if (connectMinTimeout < 0) {
_connectMinTimeout = 0;
} else {
_connectMinTimeout = connectMinTimeout;
}
}
- (void)setConnectInitialBackoff:(NSTimeInterval)connectInitialBackoff {
if (connectInitialBackoff < 0) {
_connectInitialBackoff = 0;
} else {
_connectInitialBackoff = connectInitialBackoff;
}
}
- (void)setConnectMaxBackoff:(NSTimeInterval)connectMaxBackoff {
if (connectMaxBackoff < 0) {
_connectMaxBackoff = 0;
} else {
_connectMaxBackoff = connectMaxBackoff;
}
}
- (void)setAdditionalChannelArgs:(NSDictionary *)additionalChannelArgs {
_additionalChannelArgs =
[[NSDictionary alloc] initWithDictionary:additionalChannelArgs copyItems:YES];
}
- (void)setPEMRootCertificates:(NSString *)PEMRootCertificates {
_PEMRootCertificates = [PEMRootCertificates copy];
}
- (void)setPEMPrivateKey:(NSString *)PEMPrivateKey {
_PEMPrivateKey = [PEMPrivateKey copy];
}
- (void)setPEMCertificateChain:(NSString *)PEMCertificateChain {
_PEMCertificateChain = [PEMCertificateChain copy];
}
- (void)setTransportType:(GRPCTransportType)transportType {
_transportType = transportType;
}
- (void)setHostNameOverride:(NSString *)hostNameOverride {
_hostNameOverride = [hostNameOverride copy];
}
- (void)setLogContext:(id)logContext {
_logContext = logContext;
}
- (void)setChannelPoolDomain:(NSString *)channelPoolDomain {
_channelPoolDomain = [channelPoolDomain copy];
}
- (void)setChannelID:(NSUInteger)channelID {
_channelID = channelID;
}
@end

@ -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

@ -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);

@ -0,0 +1,94 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import "ChannelArgsUtil.h"
#include <grpc/support/alloc.h>
#include <grpc/support/string_util.h>
#include <limits.h>
static void *copy_pointer_arg(void *p) {
// Add ref count to the object when making copy
id obj = (__bridge id)p;
return (__bridge_retained void *)obj;
}
static void destroy_pointer_arg(void *p) {
// Decrease ref count to the object when destroying
CFRelease((CFTypeRef)p);
}
static int cmp_pointer_arg(void *p, void *q) { return p == q; }
static const grpc_arg_pointer_vtable objc_arg_vtable = {copy_pointer_arg, destroy_pointer_arg,
cmp_pointer_arg};
void GRPCFreeChannelArgs(grpc_channel_args *channel_args) {
for (size_t i = 0; i < channel_args->num_args; ++i) {
grpc_arg *arg = &channel_args->args[i];
gpr_free(arg->key);
if (arg->type == GRPC_ARG_STRING) {
gpr_free(arg->value.string);
}
}
gpr_free(channel_args->args);
gpr_free(channel_args);
}
grpc_channel_args *GRPCBuildChannelArgs(NSDictionary *dictionary) {
if (dictionary.count == 0) {
return NULL;
}
NSArray *keys = [dictionary allKeys];
NSUInteger argCount = [keys count];
grpc_channel_args *channelArgs = gpr_malloc(sizeof(grpc_channel_args));
channelArgs->args = gpr_malloc(argCount * sizeof(grpc_arg));
// TODO(kriswuollett) Check that keys adhere to GRPC core library requirements
NSUInteger j = 0;
for (NSUInteger i = 0; i < argCount; ++i) {
grpc_arg *arg = &channelArgs->args[j];
arg->key = gpr_strdup([keys[i] UTF8String]);
id value = dictionary[keys[i]];
if ([value respondsToSelector:@selector(UTF8String)]) {
arg->type = GRPC_ARG_STRING;
arg->value.string = gpr_strdup([value UTF8String]);
j++;
} else if ([value respondsToSelector:@selector(intValue)]) {
int64_t value64 = [value longLongValue];
if (value64 <= INT_MAX || value64 >= INT_MIN) {
arg->type = GRPC_ARG_INTEGER;
arg->value.integer = (int)value64;
j++;
}
} else if (value != nil) {
arg->type = GRPC_ARG_POINTER;
arg->value.pointer.p = (__bridge_retained void *)value;
arg->value.pointer.vtable = &objc_arg_vtable;
j++;
}
}
channelArgs->num_args = j;
return channelArgs;
}

@ -20,49 +20,68 @@
#include <grpc/grpc.h>
@protocol GRPCChannelFactory;
@class GRPCCompletionQueue;
@class GRPCCallOptions;
@class GRPCChannelConfiguration;
struct grpc_channel_credentials;
NS_ASSUME_NONNULL_BEGIN
/**
* Each separate instance of this class represents at least one TCP connection to the provided host.
* Signature for the channel. If two channel's signatures are the same and connect to the same
* remote, they share the same underlying \a GRPCChannel object.
*/
@interface GRPCChannel : NSObject
@interface GRPCChannelConfiguration : NSObject<NSCopying>
@property(nonatomic, readonly, nonnull) struct grpc_channel *unmanagedChannel;
- (instancetype)init NS_UNAVAILABLE;
- (nullable instancetype)init NS_UNAVAILABLE;
+ (instancetype) new NS_UNAVAILABLE;
/** The host that this channel is connected to. */
@property(copy, readonly) NSString *host;
/**
* Creates a secure channel to the specified @c host using default credentials and channel
* arguments. If certificates could not be found to create a secure channel, then @c nil is
* returned.
* Options of the corresponding call. Note that only the channel-related options are of interest to
* this class.
*/
+ (nullable GRPCChannel *)secureChannelWithHost:(nonnull NSString *)host;
@property(readonly) GRPCCallOptions *callOptions;
/** Acquire the factory to generate a new channel with current configurations. */
@property(readonly) id<GRPCChannelFactory> channelFactory;
/** Acquire the dictionary of channel args with current configurations. */
@property(copy, readonly) NSDictionary *channelArgs;
- (nullable instancetype)initWithHost:(NSString *)host
callOptions:(GRPCCallOptions *)callOptions NS_DESIGNATED_INITIALIZER;
@end
/**
* Creates a secure channel to the specified @c host using Cronet as a transport mechanism.
* Each separate instance of this class represents at least one TCP connection to the provided host.
*/
#ifdef GRPC_COMPILE_WITH_CRONET
+ (nullable GRPCChannel *)secureCronetChannelWithHost:(nonnull NSString *)host
channelArgs:(nonnull NSDictionary *)channelArgs;
#endif
@interface GRPCChannel : NSObject
- (nullable instancetype)init NS_UNAVAILABLE;
+ (nullable instancetype) new NS_UNAVAILABLE;
/**
* Creates a secure channel to the specified @c host using the specified @c credentials and
* @c channelArgs. Only in tests should @c GRPC_SSL_TARGET_NAME_OVERRIDE_ARG channel arg be set.
* Create a channel with remote \a host and signature \a channelConfigurations.
*/
+ (nonnull GRPCChannel *)secureChannelWithHost:(nonnull NSString *)host
credentials:
(nonnull struct grpc_channel_credentials *)credentials
channelArgs:(nullable NSDictionary *)channelArgs;
- (nullable instancetype)initWithChannelConfiguration:
(GRPCChannelConfiguration *)channelConfiguration NS_DESIGNATED_INITIALIZER;
/**
* Creates an insecure channel to the specified @c host using the specified @c channelArgs.
* Create a grpc core call object (grpc_call) from this channel. If no call is created, NULL is
* returned.
*/
+ (nonnull GRPCChannel *)insecureChannelWithHost:(nonnull NSString *)host
channelArgs:(nullable NSDictionary *)channelArgs;
- (nullable grpc_call *)unmanagedCallWithPath:(NSString *)path
completionQueue:(GRPCCompletionQueue *)queue
callOptions:(GRPCCallOptions *)callOptions;
- (nullable grpc_call *)unmanagedCallWithPath:(nonnull NSString *)path
serverName:(nonnull NSString *)serverName
timeout:(NSTimeInterval)timeout
completionQueue:(nonnull GRPCCompletionQueue *)queue;
@end
NS_ASSUME_NONNULL_END

@ -18,206 +18,243 @@
#import "GRPCChannel.h"
#include <grpc/grpc_security.h>
#ifdef GRPC_COMPILE_WITH_CRONET
#include <grpc/grpc_cronet.h>
#endif
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#ifdef GRPC_COMPILE_WITH_CRONET
#import <Cronet/Cronet.h>
#import <GRPCClient/GRPCCall+Cronet.h>
#endif
#import "../internal/GRPCCallOptions+Internal.h"
#import "ChannelArgsUtil.h"
#import "GRPCChannelFactory.h"
#import "GRPCChannelPool.h"
#import "GRPCCompletionQueue.h"
#import "GRPCCronetChannelFactory.h"
#import "GRPCInsecureChannelFactory.h"
#import "GRPCSecureChannelFactory.h"
#import "version.h"
static void *copy_pointer_arg(void *p) {
// Add ref count to the object when making copy
id obj = (__bridge id)p;
return (__bridge_retained void *)obj;
}
#import <GRPCClient/GRPCCall+Cronet.h>
#import <GRPCClient/GRPCCallOptions.h>
static void destroy_pointer_arg(void *p) {
// Decrease ref count to the object when destroying
CFRelease((CFTreeRef)p);
}
@implementation GRPCChannelConfiguration
static int cmp_pointer_arg(void *p, void *q) { return p == q; }
- (instancetype)initWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions {
NSAssert(host.length > 0, @"Host must not be empty.");
NSAssert(callOptions != nil, @"callOptions must not be empty.");
if (host.length == 0 || callOptions == nil) {
return nil;
}
static const grpc_arg_pointer_vtable objc_arg_vtable = {copy_pointer_arg, destroy_pointer_arg,
cmp_pointer_arg};
if ((self = [super init])) {
_host = [host copy];
_callOptions = [callOptions copy];
}
return self;
}
static void FreeChannelArgs(grpc_channel_args *channel_args) {
for (size_t i = 0; i < channel_args->num_args; ++i) {
grpc_arg *arg = &channel_args->args[i];
gpr_free(arg->key);
if (arg->type == GRPC_ARG_STRING) {
gpr_free(arg->value.string);
}
- (id<GRPCChannelFactory>)channelFactory {
GRPCTransportType type = _callOptions.transportType;
switch (type) {
case GRPCTransportTypeChttp2BoringSSL:
// TODO (mxyan): Remove when the API is deprecated
#ifdef GRPC_COMPILE_WITH_CRONET
if (![GRPCCall isUsingCronet]) {
#else
{
#endif
NSError *error;
id<GRPCChannelFactory> factory = [GRPCSecureChannelFactory
factoryWithPEMRootCertificates:_callOptions.PEMRootCertificates
privateKey:_callOptions.PEMPrivateKey
certChain:_callOptions.PEMCertificateChain
error:&error];
NSAssert(factory != nil, @"Failed to create secure channel factory");
if (factory == nil) {
NSLog(@"Error creating secure channel factory: %@", error);
}
return factory;
}
// fallthrough
case GRPCTransportTypeCronet:
return [GRPCCronetChannelFactory sharedInstance];
case GRPCTransportTypeInsecure:
return [GRPCInsecureChannelFactory sharedInstance];
}
gpr_free(channel_args->args);
gpr_free(channel_args);
}
/**
* Allocates a @c grpc_channel_args and populates it with the options specified in the
* @c dictionary. Keys must be @c NSString. If the value responds to @c @selector(UTF8String) then
* it will be mapped to @c GRPC_ARG_STRING. If not, it will be mapped to @c GRPC_ARG_INTEGER if the
* value responds to @c @selector(intValue). Otherwise, an exception will be raised. The caller of
* this function is responsible for calling @c freeChannelArgs on a non-NULL returned value.
*/
static grpc_channel_args *BuildChannelArgs(NSDictionary *dictionary) {
if (!dictionary) {
return NULL;
}
NSArray *keys = [dictionary allKeys];
NSUInteger argCount = [keys count];
grpc_channel_args *channelArgs = gpr_malloc(sizeof(grpc_channel_args));
channelArgs->num_args = argCount;
channelArgs->args = gpr_malloc(argCount * sizeof(grpc_arg));
// TODO(kriswuollett) Check that keys adhere to GRPC core library requirements
for (NSUInteger i = 0; i < argCount; ++i) {
grpc_arg *arg = &channelArgs->args[i];
arg->key = gpr_strdup([keys[i] UTF8String]);
id value = dictionary[keys[i]];
if ([value respondsToSelector:@selector(UTF8String)]) {
arg->type = GRPC_ARG_STRING;
arg->value.string = gpr_strdup([value UTF8String]);
} else if ([value respondsToSelector:@selector(intValue)]) {
arg->type = GRPC_ARG_INTEGER;
arg->value.integer = [value intValue];
} else if (value != nil) {
arg->type = GRPC_ARG_POINTER;
arg->value.pointer.p = (__bridge_retained void *)value;
arg->value.pointer.vtable = &objc_arg_vtable;
} else {
[NSException raise:NSInvalidArgumentException
format:@"Invalid value type: %@", [value class]];
}
- (NSDictionary *)channelArgs {
NSMutableDictionary *args = [NSMutableDictionary new];
NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING;
NSString *userAgentPrefix = _callOptions.userAgentPrefix;
if (userAgentPrefix.length != 0) {
args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] =
[_callOptions.userAgentPrefix stringByAppendingFormat:@" %@", userAgent];
} else {
args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent;
}
return channelArgs;
}
NSString *hostNameOverride = _callOptions.hostNameOverride;
if (hostNameOverride) {
args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = hostNameOverride;
}
@implementation GRPCChannel {
// Retain arguments to channel_create because they may not be used on the thread that invoked
// the channel_create function.
NSString *_host;
grpc_channel_args *_channelArgs;
}
if (_callOptions.responseSizeLimit) {
args[@GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH] =
[NSNumber numberWithUnsignedInteger:_callOptions.responseSizeLimit];
}
#ifdef GRPC_COMPILE_WITH_CRONET
- (instancetype)initWithHost:(NSString *)host
cronetEngine:(stream_engine *)cronetEngine
channelArgs:(NSDictionary *)channelArgs {
if (!host) {
[NSException raise:NSInvalidArgumentException format:@"host argument missing"];
if (_callOptions.compressionAlgorithm != GRPC_COMPRESS_NONE) {
args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] =
[NSNumber numberWithInt:_callOptions.compressionAlgorithm];
}
if (self = [super init]) {
_channelArgs = BuildChannelArgs(channelArgs);
_host = [host copy];
_unmanagedChannel =
grpc_cronet_secure_channel_create(cronetEngine, _host.UTF8String, _channelArgs, NULL);
if (_callOptions.keepaliveInterval != 0) {
args[@GRPC_ARG_KEEPALIVE_TIME_MS] =
[NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.keepaliveInterval * 1000)];
args[@GRPC_ARG_KEEPALIVE_TIMEOUT_MS] =
[NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.keepaliveTimeout * 1000)];
}
return self;
}
#endif
if (!_callOptions.retryEnabled) {
args[@GRPC_ARG_ENABLE_RETRIES] = [NSNumber numberWithInt:_callOptions.retryEnabled ? 1 : 0];
}
- (instancetype)initWithHost:(NSString *)host
secure:(BOOL)secure
credentials:(struct grpc_channel_credentials *)credentials
channelArgs:(NSDictionary *)channelArgs {
if (!host) {
[NSException raise:NSInvalidArgumentException format:@"host argument missing"];
if (_callOptions.connectMinTimeout > 0) {
args[@GRPC_ARG_MIN_RECONNECT_BACKOFF_MS] =
[NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectMinTimeout * 1000)];
}
if (_callOptions.connectInitialBackoff > 0) {
args[@GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS] = [NSNumber
numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectInitialBackoff * 1000)];
}
if (_callOptions.connectMaxBackoff > 0) {
args[@GRPC_ARG_MAX_RECONNECT_BACKOFF_MS] =
[NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectMaxBackoff * 1000)];
}
if (secure && !credentials) {
return nil;
if (_callOptions.logContext != nil) {
args[@GRPC_ARG_MOBILE_LOG_CONTEXT] = _callOptions.logContext;
}
if (self = [super init]) {
_channelArgs = BuildChannelArgs(channelArgs);
_host = [host copy];
if (secure) {
_unmanagedChannel =
grpc_secure_channel_create(credentials, _host.UTF8String, _channelArgs, NULL);
} else {
_unmanagedChannel = grpc_insecure_channel_create(_host.UTF8String, _channelArgs, NULL);
}
if (_callOptions.channelPoolDomain.length != 0) {
args[@GRPC_ARG_CHANNEL_POOL_DOMAIN] = _callOptions.channelPoolDomain;
}
return self;
[args addEntriesFromDictionary:_callOptions.additionalChannelArgs];
return args;
}
- (void)dealloc {
// TODO(jcanizales): Be sure to add a test with a server that closes the connection prematurely,
// as in the past that made this call to crash.
grpc_channel_destroy(_unmanagedChannel);
FreeChannelArgs(_channelArgs);
- (id)copyWithZone:(NSZone *)zone {
GRPCChannelConfiguration *newConfig =
[[GRPCChannelConfiguration alloc] initWithHost:_host callOptions:_callOptions];
return newConfig;
}
#ifdef GRPC_COMPILE_WITH_CRONET
+ (GRPCChannel *)secureCronetChannelWithHost:(NSString *)host
channelArgs:(NSDictionary *)channelArgs {
stream_engine *engine = [GRPCCall cronetEngine];
if (!engine) {
[NSException raise:NSInvalidArgumentException format:@"cronet_engine is NULL. Set it first."];
return nil;
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[GRPCChannelConfiguration class]]) {
return NO;
}
return [[GRPCChannel alloc] initWithHost:host cronetEngine:engine channelArgs:channelArgs];
}
#endif
GRPCChannelConfiguration *obj = (GRPCChannelConfiguration *)object;
if (!(obj.host == _host || (_host != nil && [obj.host isEqualToString:_host]))) return NO;
if (!(obj.callOptions == _callOptions || [obj.callOptions hasChannelOptionsEqualTo:_callOptions]))
return NO;
+ (GRPCChannel *)secureChannelWithHost:(NSString *)host {
return [[GRPCChannel alloc] initWithHost:host secure:YES credentials:NULL channelArgs:NULL];
return YES;
}
+ (GRPCChannel *)secureChannelWithHost:(NSString *)host
credentials:(struct grpc_channel_credentials *)credentials
channelArgs:(NSDictionary *)channelArgs {
return [[GRPCChannel alloc] initWithHost:host
secure:YES
credentials:credentials
channelArgs:channelArgs];
- (NSUInteger)hash {
NSUInteger result = 31;
result ^= _host.hash;
result ^= _callOptions.channelOptionsHash;
return result;
}
+ (GRPCChannel *)insecureChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)channelArgs {
return [[GRPCChannel alloc] initWithHost:host secure:NO credentials:NULL channelArgs:channelArgs];
@end
@implementation GRPCChannel {
GRPCChannelConfiguration *_configuration;
grpc_channel *_unmanagedChannel;
}
- (grpc_call *)unmanagedCallWithPath:(NSString *)path
serverName:(NSString *)serverName
timeout:(NSTimeInterval)timeout
completionQueue:(GRPCCompletionQueue *)queue {
GPR_ASSERT(timeout >= 0);
if (timeout < 0) {
timeout = 0;
- (instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration {
NSAssert(channelConfiguration != nil, @"channelConfiguration must not be empty.");
if (channelConfiguration == nil) {
return nil;
}
grpc_slice host_slice = grpc_empty_slice();
if (serverName) {
host_slice = grpc_slice_from_copied_string(serverName.UTF8String);
if ((self = [super init])) {
_configuration = [channelConfiguration copy];
// Create gRPC core channel object.
NSString *host = channelConfiguration.host;
NSAssert(host.length != 0, @"host cannot be nil");
NSDictionary *channelArgs;
if (channelConfiguration.callOptions.additionalChannelArgs.count != 0) {
NSMutableDictionary *args = [channelConfiguration.channelArgs mutableCopy];
[args addEntriesFromDictionary:channelConfiguration.callOptions.additionalChannelArgs];
channelArgs = args;
} else {
channelArgs = channelConfiguration.channelArgs;
}
id<GRPCChannelFactory> factory = channelConfiguration.channelFactory;
_unmanagedChannel = [factory createChannelWithHost:host channelArgs:channelArgs];
NSAssert(_unmanagedChannel != NULL, @"Failed to create channel");
if (_unmanagedChannel == NULL) {
NSLog(@"Unable to create channel.");
return nil;
}
}
return self;
}
- (grpc_call *)unmanagedCallWithPath:(NSString *)path
completionQueue:(GRPCCompletionQueue *)queue
callOptions:(GRPCCallOptions *)callOptions {
NSAssert(path.length > 0, @"path must not be empty.");
NSAssert(queue != nil, @"completionQueue must not be empty.");
NSAssert(callOptions != nil, @"callOptions must not be empty.");
if (path.length == 0) return NULL;
if (queue == nil) return NULL;
if (callOptions == nil) return NULL;
grpc_call *call = NULL;
// No need to lock here since _unmanagedChannel is only changed in _dealloc
NSAssert(_unmanagedChannel != NULL, @"Channel should have valid unmanaged channel.");
if (_unmanagedChannel == NULL) return NULL;
NSString *serverAuthority =
callOptions.transportType == GRPCTransportTypeCronet ? nil : callOptions.serverAuthority;
NSTimeInterval timeout = callOptions.timeout;
NSAssert(timeout >= 0, @"Invalid timeout");
if (timeout < 0) return NULL;
grpc_slice host_slice = serverAuthority
? grpc_slice_from_copied_string(serverAuthority.UTF8String)
: grpc_empty_slice();
grpc_slice path_slice = grpc_slice_from_copied_string(path.UTF8String);
gpr_timespec deadline_ms =
timeout == 0 ? gpr_inf_future(GPR_CLOCK_REALTIME)
: gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
gpr_time_from_millis((int64_t)(timeout * 1000), GPR_TIMESPAN));
grpc_call *call = grpc_channel_create_call(_unmanagedChannel, NULL, GRPC_PROPAGATE_DEFAULTS,
queue.unmanagedQueue, path_slice,
serverName ? &host_slice : NULL, deadline_ms, NULL);
if (serverName) {
call = grpc_channel_create_call(_unmanagedChannel, NULL, GRPC_PROPAGATE_DEFAULTS,
queue.unmanagedQueue, path_slice,
serverAuthority ? &host_slice : NULL, deadline_ms, NULL);
if (serverAuthority) {
grpc_slice_unref(host_slice);
}
grpc_slice_unref(path_slice);
NSAssert(call != nil, @"Unable to create call.");
if (call == NULL) {
NSLog(@"Unable to create call.");
}
return call;
}
- (void)dealloc {
if (_unmanagedChannel) {
grpc_channel_destroy(_unmanagedChannel);
}
}
@end

@ -0,0 +1,34 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import <Foundation/Foundation.h>
#include <grpc/impl/codegen/grpc_types.h>
NS_ASSUME_NONNULL_BEGIN
/** A factory interface which generates new channel. */
@protocol GRPCChannelFactory
/** Create a channel with specific channel args to a specific host. */
- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
channelArgs:(nullable NSDictionary *)args;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,51 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import "GRPCChannelPool.h"
NS_ASSUME_NONNULL_BEGIN
/** Test-only interface for \a GRPCPooledChannel. */
@interface GRPCPooledChannel (Test)
/**
* Initialize a pooled channel with non-default destroy delay for testing purpose.
*/
- (nullable instancetype)initWithChannelConfiguration:
(GRPCChannelConfiguration *)channelConfiguration
destroyDelay:(NSTimeInterval)destroyDelay;
/**
* Return the pointer to the raw channel wrapped.
*/
@property(atomic, readonly, nullable) GRPCChannel *wrappedChannel;
@end
/** Test-only interface for \a GRPCChannelPool. */
@interface GRPCChannelPool (Test)
/**
* Get an instance of pool isolated from the global shared pool with channels' destroy delay being
* \a destroyDelay.
*/
- (nullable instancetype)initTestPool;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,101 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import <GRPCClient/GRPCCallOptions.h>
#import "GRPCChannelFactory.h"
NS_ASSUME_NONNULL_BEGIN
@protocol GRPCChannel;
@class GRPCChannel;
@class GRPCChannelPool;
@class GRPCCompletionQueue;
@class GRPCChannelConfiguration;
@class GRPCWrappedCall;
/**
* A proxied channel object that can be retained and used to create GRPCWrappedCall object
* regardless of the current connection status. If a connection is not established when a
* GRPCWrappedCall object is requested, it issues a connection/reconnection. This behavior is to
* follow that of gRPC core's channel object.
*/
@interface GRPCPooledChannel : NSObject
- (nullable instancetype)init NS_UNAVAILABLE;
+ (nullable instancetype) new NS_UNAVAILABLE;
/**
* Initialize with an actual channel object \a channel and a reference to the channel pool.
*/
- (nullable instancetype)initWithChannelConfiguration:
(GRPCChannelConfiguration *)channelConfiguration;
/**
* Create a GRPCWrappedCall object (grpc_call) from this channel. If channel is disconnected, get a
* new channel object from the channel pool.
*/
- (nullable GRPCWrappedCall *)wrappedCallWithPath:(NSString *)path
completionQueue:(GRPCCompletionQueue *)queue
callOptions:(GRPCCallOptions *)callOptions;
/**
* Notify the pooled channel that a wrapped call object is no longer referenced and will be
* dealloc'ed.
*/
- (void)notifyWrappedCallDealloc:(GRPCWrappedCall *)wrappedCall;
/**
* Force the channel to disconnect immediately. GRPCWrappedCall objects previously created with
* \a wrappedCallWithPath are failed if not already finished. Subsequent calls to
* unmanagedCallWithPath: will attempt to reconnect to the remote channel.
*/
- (void)disconnect;
@end
/**
* Manage the pool of connected channels. When a channel is no longer referenced by any call,
* destroy the channel after a certain period of time elapsed.
*/
@interface GRPCChannelPool : NSObject
- (nullable instancetype)init NS_UNAVAILABLE;
+ (nullable instancetype) new NS_UNAVAILABLE;
/**
* Get the global channel pool.
*/
+ (nullable instancetype)sharedInstance;
/**
* Return a channel with a particular configuration. The channel may be a cached channel.
*/
- (nullable GRPCPooledChannel *)channelWithHost:(NSString *)host
callOptions:(GRPCCallOptions *)callOptions;
/**
* Disconnect all channels in this pool.
*/
- (void)disconnectAllChannels;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,276 @@
/*
*
* Copyright 2015 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import <Foundation/Foundation.h>
#import "../internal/GRPCCallOptions+Internal.h"
#import "GRPCChannel.h"
#import "GRPCChannelFactory.h"
#import "GRPCChannelPool+Test.h"
#import "GRPCChannelPool.h"
#import "GRPCCompletionQueue.h"
#import "GRPCConnectivityMonitor.h"
#import "GRPCCronetChannelFactory.h"
#import "GRPCInsecureChannelFactory.h"
#import "GRPCSecureChannelFactory.h"
#import "GRPCWrappedCall.h"
#import "version.h"
#import <GRPCClient/GRPCCall+Cronet.h>
#include <grpc/support/log.h>
extern const char *kCFStreamVarName;
static GRPCChannelPool *gChannelPool;
static dispatch_once_t gInitChannelPool;
/** When all calls of a channel are destroyed, destroy the channel after this much seconds. */
static const NSTimeInterval kDefaultChannelDestroyDelay = 30;
@implementation GRPCPooledChannel {
GRPCChannelConfiguration *_channelConfiguration;
NSTimeInterval _destroyDelay;
NSHashTable<GRPCWrappedCall *> *_wrappedCalls;
GRPCChannel *_wrappedChannel;
NSDate *_lastTimedDestroy;
dispatch_queue_t _timerQueue;
}
- (instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration {
return [self initWithChannelConfiguration:channelConfiguration
destroyDelay:kDefaultChannelDestroyDelay];
}
- (nullable instancetype)initWithChannelConfiguration:
(GRPCChannelConfiguration *)channelConfiguration
destroyDelay:(NSTimeInterval)destroyDelay {
NSAssert(channelConfiguration != nil, @"channelConfiguration cannot be empty.");
if (channelConfiguration == nil) {
return nil;
}
if ((self = [super init])) {
_channelConfiguration = [channelConfiguration copy];
_destroyDelay = destroyDelay;
_wrappedCalls = [NSHashTable weakObjectsHashTable];
_wrappedChannel = nil;
_lastTimedDestroy = nil;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
if (@available(iOS 8.0, macOS 10.10, *)) {
_timerQueue = dispatch_queue_create(NULL, dispatch_queue_attr_make_with_qos_class(
DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
} else {
#else
{
#endif
_timerQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
}
}
return self;
}
- (void)dealloc {
// Disconnect GRPCWrappedCall objects created but not yet removed
if (_wrappedCalls.allObjects.count != 0) {
for (GRPCWrappedCall *wrappedCall in _wrappedCalls.allObjects) {
[wrappedCall channelDisconnected];
};
}
}
- (GRPCWrappedCall *)wrappedCallWithPath:(NSString *)path
completionQueue:(GRPCCompletionQueue *)queue
callOptions:(GRPCCallOptions *)callOptions {
NSAssert(path.length > 0, @"path must not be empty.");
NSAssert(queue != nil, @"completionQueue must not be empty.");
NSAssert(callOptions, @"callOptions must not be empty.");
if (path.length == 0 || queue == nil || callOptions == nil) {
return nil;
}
GRPCWrappedCall *call = nil;
@synchronized(self) {
if (_wrappedChannel == nil) {
_wrappedChannel = [[GRPCChannel alloc] initWithChannelConfiguration:_channelConfiguration];
if (_wrappedChannel == nil) {
NSAssert(_wrappedChannel != nil, @"Unable to get a raw channel for proxy.");
return nil;
}
}
_lastTimedDestroy = nil;
grpc_call *unmanagedCall =
[_wrappedChannel unmanagedCallWithPath:path
completionQueue:[GRPCCompletionQueue completionQueue]
callOptions:callOptions];
if (unmanagedCall == NULL) {
NSAssert(unmanagedCall != NULL, @"Unable to create grpc_call object");
return nil;
}
call = [[GRPCWrappedCall alloc] initWithUnmanagedCall:unmanagedCall pooledChannel:self];
if (call == nil) {
NSAssert(call != nil, @"Unable to create GRPCWrappedCall object");
grpc_call_unref(unmanagedCall);
return nil;
}
[_wrappedCalls addObject:call];
}
return call;
}
- (void)notifyWrappedCallDealloc:(GRPCWrappedCall *)wrappedCall {
NSAssert(wrappedCall != nil, @"wrappedCall cannot be empty.");
if (wrappedCall == nil) {
return;
}
@synchronized(self) {
// Detect if all objects weakly referenced in _wrappedCalls are (implicitly) removed.
// _wrappedCalls.count does not work here since the hash table may include deallocated weak
// references. _wrappedCalls.allObjects forces removal of those objects.
if (_wrappedCalls.allObjects.count == 0) {
// No more call has reference to this channel. We may start the timer for destroying the
// channel now.
NSDate *now = [NSDate date];
NSAssert(now != nil, @"Unable to create NSDate object 'now'.");
_lastTimedDestroy = now;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)_destroyDelay * NSEC_PER_SEC),
_timerQueue, ^{
@synchronized(self) {
// Check _lastTimedDestroy against now in case more calls are created (and
// maybe destroyed) after this dispatch_async. In that case the current
// dispatch_after block should be discarded; the channel should be
// destroyed in a later dispatch_after block.
if (now != nil && self->_lastTimedDestroy == now) {
self->_wrappedChannel = nil;
self->_lastTimedDestroy = nil;
}
}
});
}
}
}
- (void)disconnect {
NSArray<GRPCWrappedCall *> *copiedWrappedCalls = nil;
@synchronized(self) {
if (_wrappedChannel != nil) {
_wrappedChannel = nil;
copiedWrappedCalls = _wrappedCalls.allObjects;
[_wrappedCalls removeAllObjects];
}
}
for (GRPCWrappedCall *wrappedCall in copiedWrappedCalls) {
[wrappedCall channelDisconnected];
}
}
- (GRPCChannel *)wrappedChannel {
GRPCChannel *channel = nil;
@synchronized(self) {
channel = _wrappedChannel;
}
return channel;
}
@end
@interface GRPCChannelPool ()
- (instancetype)initPrivate NS_DESIGNATED_INITIALIZER;
@end
@implementation GRPCChannelPool {
NSMutableDictionary<GRPCChannelConfiguration *, GRPCPooledChannel *> *_channelPool;
}
+ (instancetype)sharedInstance {
dispatch_once(&gInitChannelPool, ^{
gChannelPool = [[GRPCChannelPool alloc] initPrivate];
NSAssert(gChannelPool != nil, @"Cannot initialize global channel pool.");
});
return gChannelPool;
}
- (instancetype)initPrivate {
if ((self = [super init])) {
_channelPool = [NSMutableDictionary dictionary];
// Connectivity monitor is not required for CFStream
char *enableCFStream = getenv(kCFStreamVarName);
if (enableCFStream == nil || enableCFStream[0] != '1') {
[GRPCConnectivityMonitor registerObserver:self selector:@selector(connectivityChange:)];
}
}
return self;
}
- (void)dealloc {
[GRPCConnectivityMonitor unregisterObserver:self];
}
- (GRPCPooledChannel *)channelWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions {
NSAssert(host.length > 0, @"Host must not be empty.");
NSAssert(callOptions != nil, @"callOptions must not be empty.");
if (host.length == 0 || callOptions == nil) {
return nil;
}
GRPCPooledChannel *pooledChannel = nil;
GRPCChannelConfiguration *configuration =
[[GRPCChannelConfiguration alloc] initWithHost:host callOptions:callOptions];
@synchronized(self) {
pooledChannel = _channelPool[configuration];
if (pooledChannel == nil) {
pooledChannel = [[GRPCPooledChannel alloc] initWithChannelConfiguration:configuration];
_channelPool[configuration] = pooledChannel;
}
}
return pooledChannel;
}
- (void)disconnectAllChannels {
NSArray<GRPCPooledChannel *> *copiedPooledChannels;
@synchronized(self) {
copiedPooledChannels = _channelPool.allValues;
}
// Disconnect pooled channels.
for (GRPCPooledChannel *pooledChannel in copiedPooledChannels) {
[pooledChannel disconnect];
}
}
- (void)connectivityChange:(NSNotification *)note {
[self disconnectAllChannels];
}
@end
@implementation GRPCChannelPool (Test)
- (instancetype)initTestPool {
return [self initPrivate];
}
@end

@ -76,14 +76,14 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
}
}
+ (void)registerObserver:(_Nonnull id)observer selector:(SEL)selector {
+ (void)registerObserver:(id)observer selector:(SEL)selector {
[[NSNotificationCenter defaultCenter] addObserver:observer
selector:selector
name:kGRPCConnectivityNotification
object:nil];
}
+ (void)unregisterObserver:(_Nonnull id)observer {
+ (void)unregisterObserver:(id)observer {
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}

@ -0,0 +1,36 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import "GRPCChannelFactory.h"
@class GRPCChannel;
typedef struct stream_engine stream_engine;
NS_ASSUME_NONNULL_BEGIN
@interface GRPCCronetChannelFactory : NSObject<GRPCChannelFactory>
+ (nullable instancetype)sharedInstance;
- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
channelArgs:(nullable NSDictionary *)args;
- (nullable instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,79 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import "GRPCCronetChannelFactory.h"
#import "ChannelArgsUtil.h"
#import "GRPCChannel.h"
#ifdef GRPC_COMPILE_WITH_CRONET
#import <Cronet/Cronet.h>
#include <grpc/grpc_cronet.h>
@implementation GRPCCronetChannelFactory {
stream_engine *_cronetEngine;
}
+ (instancetype)sharedInstance {
static GRPCCronetChannelFactory *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] initWithEngine:[Cronet getGlobalEngine]];
});
return instance;
}
- (instancetype)initWithEngine:(stream_engine *)engine {
NSAssert(engine != NULL, @"Cronet engine cannot be empty.");
if (!engine) {
return nil;
}
if ((self = [super init])) {
_cronetEngine = engine;
}
return self;
}
- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args {
grpc_channel_args *channelArgs = GRPCBuildChannelArgs(args);
grpc_channel *unmanagedChannel =
grpc_cronet_secure_channel_create(_cronetEngine, host.UTF8String, channelArgs, NULL);
GRPCFreeChannelArgs(channelArgs);
return unmanagedChannel;
}
@end
#else
@implementation GRPCCronetChannelFactory
+ (instancetype)sharedInstance {
NSAssert(NO, @"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel.");
return nil;
}
- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args {
NSAssert(NO, @"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel.");
return NULL;
}
@end
#endif

@ -20,6 +20,10 @@
#import <grpc/impl/codegen/compression_types.h>
#import "GRPCChannelFactory.h"
#import <GRPCClient/GRPCCallOptions.h>
NS_ASSUME_NONNULL_BEGIN
@class GRPCCompletionQueue;
@ -28,12 +32,10 @@ struct grpc_channel_credentials;
@interface GRPCHost : NSObject
+ (void)flushChannelCache;
+ (void)resetAllHostSettings;
@property(nonatomic, readonly) NSString *address;
@property(nonatomic, copy, nullable) NSString *userAgentPrefix;
@property(nonatomic, nullable) struct grpc_channel_credentials *channelCreds;
@property(nonatomic) grpc_compression_algorithm compressAlgorithm;
@property(nonatomic) int keepaliveInterval;
@property(nonatomic) int keepaliveTimeout;
@ -44,14 +46,14 @@ struct grpc_channel_credentials;
@property(nonatomic) unsigned int initialConnectBackoff;
@property(nonatomic) unsigned int maxConnectBackoff;
/** The following properties should only be modified for testing: */
@property(nonatomic) id<GRPCChannelFactory> channelFactory;
@property(nonatomic, getter=isSecure) BOOL secure;
/** The following properties should only be modified for testing: */
@property(nonatomic, copy, nullable) NSString *hostNameOverride;
/** The default response size limit is 4MB. Set this to override that default. */
@property(nonatomic, strong, nullable) NSNumber *responseSizeLimitOverride;
@property(nonatomic) NSUInteger responseSizeLimitOverride;
- (nullable instancetype)init NS_UNAVAILABLE;
/** Host objects initialized with the same address are the same. */
@ -62,19 +64,10 @@ struct grpc_channel_credentials;
withCertChain:(nullable NSString *)pemCertChain
error:(NSError **)errorPtr;
/** Create a grpc_call object to the provided path on this host. */
- (nullable struct grpc_call *)unmanagedCallWithPath:(NSString *)path
serverName:(NSString *)serverName
timeout:(NSTimeInterval)timeout
completionQueue:(GRPCCompletionQueue *)queue;
// TODO: There's a race when a new RPC is coming through just as an existing one is getting
// notified that there's no connectivity. If connectivity comes back at that moment, the new RPC
// will have its channel destroyed by the other RPC, and will never get notified of a problem, so
// it'll hang (the C layer logs a timeout, with exponential back off). One solution could be to pass
// the GRPCChannel to the GRPCCall, renaming this as "disconnectChannel:channel", which would only
// act on that specific channel.
- (void)disconnect;
@property(atomic) GRPCTransportType transportType;
+ (GRPCCallOptions *)callOptionsForHost:(NSString *)host;
@end
NS_ASSUME_NONNULL_END

@ -18,46 +18,36 @@
#import "GRPCHost.h"
#import <GRPCClient/GRPCCall+Cronet.h>
#import <GRPCClient/GRPCCall.h>
#import <GRPCClient/GRPCCallOptions.h>
#include <grpc/grpc.h>
#include <grpc/grpc_security.h>
#ifdef GRPC_COMPILE_WITH_CRONET
#import <GRPCClient/GRPCCall+ChannelArg.h>
#import <GRPCClient/GRPCCall+Cronet.h>
#endif
#import "GRPCChannel.h"
#import "../internal/GRPCCallOptions+Internal.h"
#import "GRPCChannelFactory.h"
#import "GRPCCompletionQueue.h"
#import "GRPCConnectivityMonitor.h"
#import "GRPCCronetChannelFactory.h"
#import "GRPCSecureChannelFactory.h"
#import "NSDictionary+GRPC.h"
#import "version.h"
NS_ASSUME_NONNULL_BEGIN
extern const char *kCFStreamVarName;
static NSMutableDictionary *kHostCache;
static NSMutableDictionary *gHostCache;
@implementation GRPCHost {
// TODO(mlumish): Investigate whether caching channels with strong links is a good idea.
GRPCChannel *_channel;
NSString *_PEMRootCertificates;
NSString *_PEMPrivateKey;
NSString *_PEMCertificateChain;
}
+ (nullable instancetype)hostWithAddress:(NSString *)address {
return [[self alloc] initWithAddress:address];
}
- (void)dealloc {
if (_channelCreds != nil) {
grpc_channel_credentials_release(_channelCreds);
}
// Connectivity monitor is not required for CFStream
char *enableCFStream = getenv(kCFStreamVarName);
if (enableCFStream == nil || enableCFStream[0] != '1') {
[GRPCConnectivityMonitor unregisterObserver:self];
}
}
// Default initializer.
- (nullable instancetype)initWithAddress:(NSString *)address {
if (!address) {
@ -76,241 +66,89 @@ static NSMutableDictionary *kHostCache;
// Look up the GRPCHost in the cache.
static dispatch_once_t cacheInitialization;
dispatch_once(&cacheInitialization, ^{
kHostCache = [NSMutableDictionary dictionary];
gHostCache = [NSMutableDictionary dictionary];
});
@synchronized(kHostCache) {
GRPCHost *cachedHost = kHostCache[address];
@synchronized(gHostCache) {
GRPCHost *cachedHost = gHostCache[address];
if (cachedHost) {
return cachedHost;
}
if ((self = [super init])) {
_address = address;
_secure = YES;
kHostCache[address] = self;
_compressAlgorithm = GRPC_COMPRESS_NONE;
_address = [address copy];
_retryEnabled = YES;
}
// Connectivity monitor is not required for CFStream
char *enableCFStream = getenv(kCFStreamVarName);
if (enableCFStream == nil || enableCFStream[0] != '1') {
[GRPCConnectivityMonitor registerObserver:self selector:@selector(connectivityChange:)];
gHostCache[address] = self;
}
}
return self;
}
+ (void)flushChannelCache {
@synchronized(kHostCache) {
[kHostCache enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, GRPCHost *_Nonnull host,
BOOL *_Nonnull stop) {
[host disconnect];
}];
}
}
+ (void)resetAllHostSettings {
@synchronized(kHostCache) {
kHostCache = [NSMutableDictionary dictionary];
}
}
- (nullable grpc_call *)unmanagedCallWithPath:(NSString *)path
serverName:(NSString *)serverName
timeout:(NSTimeInterval)timeout
completionQueue:(GRPCCompletionQueue *)queue {
// The __block attribute is to allow channel take refcount inside @synchronized block. Without
// this attribute, retain of channel object happens after objc_sync_exit in release builds, which
// may result in channel released before used. See grpc/#15033.
__block GRPCChannel *channel;
// This is racing -[GRPCHost disconnect].
@synchronized(self) {
if (!_channel) {
_channel = [self newChannel];
}
channel = _channel;
@synchronized(gHostCache) {
gHostCache = [NSMutableDictionary dictionary];
}
return [channel unmanagedCallWithPath:path
serverName:serverName
timeout:timeout
completionQueue:queue];
}
- (NSData *)nullTerminatedDataWithString:(NSString *)string {
// dataUsingEncoding: does not return a null-terminated string.
NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSMutableData *nullTerminated = [NSMutableData dataWithData:data];
[nullTerminated appendBytes:"\0" length:1];
return nullTerminated;
}
- (BOOL)setTLSPEMRootCerts:(nullable NSString *)pemRootCerts
withPrivateKey:(nullable NSString *)pemPrivateKey
withCertChain:(nullable NSString *)pemCertChain
error:(NSError **)errorPtr {
static NSData *kDefaultRootsASCII;
static NSError *kDefaultRootsError;
static dispatch_once_t loading;
dispatch_once(&loading, ^{
NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem
// Do not use NSBundle.mainBundle, as it's nil for tests of library projects.
NSBundle *bundle = [NSBundle bundleForClass:self.class];
NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"];
NSError *error;
// Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the
// issuer). Load them as UTF8 and produce an ASCII equivalent.
NSString *contentInUTF8 =
[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (contentInUTF8 == nil) {
kDefaultRootsError = error;
return;
}
kDefaultRootsASCII = [self nullTerminatedDataWithString:contentInUTF8];
});
NSData *rootsASCII;
if (pemRootCerts != nil) {
rootsASCII = [self nullTerminatedDataWithString:pemRootCerts];
} else {
if (kDefaultRootsASCII == nil) {
if (errorPtr) {
*errorPtr = kDefaultRootsError;
}
NSAssert(
kDefaultRootsASCII,
@"Could not read gRPCCertificates.bundle/roots.pem. This file, "
"with the root certificates, is needed to establish secure (TLS) connections. "
"Because the file is distributed with the gRPC library, this error is usually a sign "
"that the library wasn't configured correctly for your project. Error: %@",
kDefaultRootsError);
return NO;
}
rootsASCII = kDefaultRootsASCII;
}
grpc_channel_credentials *creds;
if (pemPrivateKey == nil && pemCertChain == nil) {
creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL);
} else {
assert(pemPrivateKey != nil && pemCertChain != nil);
grpc_ssl_pem_key_cert_pair key_cert_pair;
NSData *privateKeyASCII = [self nullTerminatedDataWithString:pemPrivateKey];
NSData *certChainASCII = [self nullTerminatedDataWithString:pemCertChain];
key_cert_pair.private_key = privateKeyASCII.bytes;
key_cert_pair.cert_chain = certChainASCII.bytes;
creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL, NULL);
}
@synchronized(self) {
if (_channelCreds != nil) {
grpc_channel_credentials_release(_channelCreds);
}
_channelCreds = creds;
}
_PEMRootCertificates = [pemRootCerts copy];
_PEMPrivateKey = [pemPrivateKey copy];
_PEMCertificateChain = [pemCertChain copy];
return YES;
}
- (NSDictionary *)channelArgsUsingCronet:(BOOL)useCronet {
NSMutableDictionary *args = [NSMutableDictionary dictionary];
// TODO(jcanizales): Add OS and device information (see
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents ).
NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING;
if (_userAgentPrefix) {
userAgent = [_userAgentPrefix stringByAppendingFormat:@" %@", userAgent];
}
args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent;
if (_secure && _hostNameOverride) {
args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = _hostNameOverride;
}
if (_responseSizeLimitOverride != nil) {
args[@GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH] = _responseSizeLimitOverride;
}
if (_compressAlgorithm != GRPC_COMPRESS_NONE) {
args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] = [NSNumber numberWithInt:_compressAlgorithm];
}
if (_keepaliveInterval != 0) {
args[@GRPC_ARG_KEEPALIVE_TIME_MS] = [NSNumber numberWithInt:_keepaliveInterval];
args[@GRPC_ARG_KEEPALIVE_TIMEOUT_MS] = [NSNumber numberWithInt:_keepaliveTimeout];
}
id logContext = self.logContext;
if (logContext != nil) {
args[@GRPC_ARG_MOBILE_LOG_CONTEXT] = logContext;
}
if (useCronet) {
args[@GRPC_ARG_DISABLE_CLIENT_AUTHORITY_FILTER] = [NSNumber numberWithInt:1];
}
if (_retryEnabled == NO) {
args[@GRPC_ARG_ENABLE_RETRIES] = [NSNumber numberWithInt:0];
}
if (_minConnectTimeout > 0) {
args[@GRPC_ARG_MIN_RECONNECT_BACKOFF_MS] = [NSNumber numberWithInt:_minConnectTimeout];
}
if (_initialConnectBackoff > 0) {
args[@GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS] = [NSNumber numberWithInt:_initialConnectBackoff];
}
if (_maxConnectBackoff > 0) {
args[@GRPC_ARG_MAX_RECONNECT_BACKOFF_MS] = [NSNumber numberWithInt:_maxConnectBackoff];
}
return args;
}
- (GRPCChannel *)newChannel {
BOOL useCronet = NO;
#ifdef GRPC_COMPILE_WITH_CRONET
useCronet = [GRPCCall isUsingCronet];
#endif
NSDictionary *args = [self channelArgsUsingCronet:useCronet];
if (_secure) {
GRPCChannel *channel;
@synchronized(self) {
if (_channelCreds == nil) {
[self setTLSPEMRootCerts:nil withPrivateKey:nil withCertChain:nil error:nil];
}
- (GRPCCallOptions *)callOptions {
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.userAgentPrefix = _userAgentPrefix;
options.responseSizeLimit = _responseSizeLimitOverride;
options.compressionAlgorithm = (GRPCCompressionAlgorithm)_compressAlgorithm;
options.retryEnabled = _retryEnabled;
options.keepaliveInterval = (NSTimeInterval)_keepaliveInterval / 1000;
options.keepaliveTimeout = (NSTimeInterval)_keepaliveTimeout / 1000;
options.connectMinTimeout = (NSTimeInterval)_minConnectTimeout / 1000;
options.connectInitialBackoff = (NSTimeInterval)_initialConnectBackoff / 1000;
options.connectMaxBackoff = (NSTimeInterval)_maxConnectBackoff / 1000;
options.PEMRootCertificates = _PEMRootCertificates;
options.PEMPrivateKey = _PEMPrivateKey;
options.PEMCertificateChain = _PEMCertificateChain;
options.hostNameOverride = _hostNameOverride;
#ifdef GRPC_COMPILE_WITH_CRONET
if (useCronet) {
channel = [GRPCChannel secureCronetChannelWithHost:_address channelArgs:args];
} else
#endif
{
channel =
[GRPCChannel secureChannelWithHost:_address credentials:_channelCreds channelArgs:args];
}
// By old API logic, insecure channel precedes Cronet channel; Cronet channel preceeds default
// channel.
if ([GRPCCall isUsingCronet]) {
if (_transportType == GRPCTransportTypeInsecure) {
options.transportType = GRPCTransportTypeInsecure;
} else {
NSAssert(_transportType == GRPCTransportTypeDefault, @"Invalid transport type");
options.transportType = GRPCTransportTypeCronet;
}
return channel;
} else {
return [GRPCChannel insecureChannelWithHost:_address channelArgs:args];
} else
#endif
{
options.transportType = _transportType;
}
}
options.logContext = _logContext;
- (NSString *)hostName {
// TODO(jcanizales): Default to nil instead of _address when Issue #2635 is clarified.
return _hostNameOverride ?: _address;
return options;
}
- (void)disconnect {
// This is racing -[GRPCHost unmanagedCallWithPath:completionQueue:].
@synchronized(self) {
_channel = nil;
+ (GRPCCallOptions *)callOptionsForHost:(NSString *)host {
// TODO (mxyan): Remove when old API is deprecated
NSURL *hostURL = [NSURL URLWithString:[@"https://" stringByAppendingString:host]];
if (hostURL.host && hostURL.port == nil) {
host = [hostURL.host stringByAppendingString:@":443"];
}
}
// Flushes the host cache when connectivity status changes or when connection switch between Wifi
// and Cellular data, so that a new call will use a new channel. Otherwise, a new call will still
// use the cached channel which is no longer available and will cause gRPC to hang.
- (void)connectivityChange:(NSNotification *)note {
[self disconnect];
GRPCCallOptions *callOptions = nil;
@synchronized(gHostCache) {
callOptions = [gHostCache[host] callOptions];
}
if (callOptions == nil) {
callOptions = [[GRPCCallOptions alloc] init];
}
return callOptions;
}
@end

@ -0,0 +1,35 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import "GRPCChannelFactory.h"
@class GRPCChannel;
NS_ASSUME_NONNULL_BEGIN
@interface GRPCInsecureChannelFactory : NSObject<GRPCChannelFactory>
+ (nullable instancetype)sharedInstance;
- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
channelArgs:(nullable NSDictionary *)args;
- (nullable instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,43 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import "GRPCInsecureChannelFactory.h"
#import "ChannelArgsUtil.h"
#import "GRPCChannel.h"
@implementation GRPCInsecureChannelFactory
+ (instancetype)sharedInstance {
static GRPCInsecureChannelFactory *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args {
grpc_channel_args *coreChannelArgs = GRPCBuildChannelArgs(args);
grpc_channel *unmanagedChannel =
grpc_insecure_channel_create(host.UTF8String, coreChannelArgs, NULL);
GRPCFreeChannelArgs(coreChannelArgs);
return unmanagedChannel;
}
@end

@ -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\", "

@ -0,0 +1,38 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import "GRPCChannelFactory.h"
@class GRPCChannel;
NS_ASSUME_NONNULL_BEGIN
@interface GRPCSecureChannelFactory : NSObject<GRPCChannelFactory>
+ (nullable instancetype)factoryWithPEMRootCertificates:(nullable NSString *)rootCerts
privateKey:(nullable NSString *)privateKey
certChain:(nullable NSString *)certChain
error:(NSError **)errorPtr;
- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
channelArgs:(nullable NSDictionary *)args;
- (nullable instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,135 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import "GRPCSecureChannelFactory.h"
#include <grpc/grpc_security.h>
#import "ChannelArgsUtil.h"
#import "GRPCChannel.h"
@implementation GRPCSecureChannelFactory {
grpc_channel_credentials *_channelCreds;
}
+ (instancetype)factoryWithPEMRootCertificates:(NSString *)rootCerts
privateKey:(NSString *)privateKey
certChain:(NSString *)certChain
error:(NSError **)errorPtr {
return [[self alloc] initWithPEMRootCerts:rootCerts
privateKey:privateKey
certChain:certChain
error:errorPtr];
}
- (NSData *)nullTerminatedDataWithString:(NSString *)string {
// dataUsingEncoding: does not return a null-terminated string.
NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
if (data == nil) {
return nil;
}
NSMutableData *nullTerminated = [NSMutableData dataWithData:data];
[nullTerminated appendBytes:"\0" length:1];
return nullTerminated;
}
- (instancetype)initWithPEMRootCerts:(NSString *)rootCerts
privateKey:(NSString *)privateKey
certChain:(NSString *)certChain
error:(NSError **)errorPtr {
static NSData *defaultRootsASCII;
static NSError *defaultRootsError;
static dispatch_once_t loading;
dispatch_once(&loading, ^{
NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem
// Do not use NSBundle.mainBundle, as it's nil for tests of library projects.
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"];
NSError *error;
// Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the
// issuer). Load them as UTF8 and produce an ASCII equivalent.
NSString *contentInUTF8 =
[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (contentInUTF8 == nil) {
defaultRootsError = error;
return;
}
defaultRootsASCII = [self nullTerminatedDataWithString:contentInUTF8];
});
NSData *rootsASCII;
if (rootCerts != nil) {
rootsASCII = [self nullTerminatedDataWithString:rootCerts];
} else {
if (defaultRootsASCII == nil) {
if (errorPtr) {
*errorPtr = defaultRootsError;
}
NSAssert(
defaultRootsASCII, NSObjectNotAvailableException,
@"Could not read gRPCCertificates.bundle/roots.pem. This file, "
"with the root certificates, is needed to establish secure (TLS) connections. "
"Because the file is distributed with the gRPC library, this error is usually a sign "
"that the library wasn't configured correctly for your project. Error: %@",
defaultRootsError);
return nil;
}
rootsASCII = defaultRootsASCII;
}
grpc_channel_credentials *creds = NULL;
if (privateKey.length == 0 && certChain.length == 0) {
creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL);
} else {
grpc_ssl_pem_key_cert_pair key_cert_pair;
NSData *privateKeyASCII = [self nullTerminatedDataWithString:privateKey];
NSData *certChainASCII = [self nullTerminatedDataWithString:certChain];
key_cert_pair.private_key = privateKeyASCII.bytes;
key_cert_pair.cert_chain = certChainASCII.bytes;
if (key_cert_pair.private_key == NULL || key_cert_pair.cert_chain == NULL) {
creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL);
} else {
creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL, NULL);
}
}
if ((self = [super init])) {
_channelCreds = creds;
}
return self;
}
- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args {
NSAssert(host.length != 0, @"host cannot be empty");
if (host.length == 0) {
return NULL;
}
grpc_channel_args *coreChannelArgs = GRPCBuildChannelArgs(args);
grpc_channel *unmanagedChannel =
grpc_secure_channel_create(_channelCreds, host.UTF8String, coreChannelArgs, NULL);
GRPCFreeChannelArgs(coreChannelArgs);
return unmanagedChannel;
}
- (void)dealloc {
if (_channelCreds != NULL) {
grpc_channel_credentials_release(_channelCreds);
}
}
@end

@ -71,12 +71,16 @@
#pragma mark GRPCWrappedCall
@class GRPCPooledChannel;
@interface GRPCWrappedCall : NSObject
- (instancetype)initWithHost:(NSString *)host
serverName:(NSString *)serverName
path:(NSString *)path
timeout:(NSTimeInterval)timeout NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype) new NS_UNAVAILABLE;
- (instancetype)initWithUnmanagedCall:(grpc_call *)unmanagedCall
pooledChannel:(GRPCPooledChannel *)pooledChannel NS_DESIGNATED_INITIALIZER;
- (void)startBatchWithOperations:(NSArray *)ops errorHandler:(void (^)(void))errorHandler;
@ -84,4 +88,6 @@
- (void)cancel;
- (void)channelDisconnected;
@end

@ -23,6 +23,8 @@
#include <grpc/grpc.h>
#include <grpc/support/alloc.h>
#import "GRPCChannel.h"
#import "GRPCChannelPool.h"
#import "GRPCCompletionQueue.h"
#import "GRPCHost.h"
#import "NSData+GRPC.h"
@ -234,35 +236,22 @@
#pragma mark GRPCWrappedCall
@implementation GRPCWrappedCall {
GRPCCompletionQueue *_queue;
// pooledChannel holds weak reference to this object so this is ok
GRPCPooledChannel *_pooledChannel;
grpc_call *_call;
}
- (instancetype)init {
return [self initWithHost:nil serverName:nil path:nil timeout:0];
}
- (instancetype)initWithHost:(NSString *)host
serverName:(NSString *)serverName
path:(NSString *)path
timeout:(NSTimeInterval)timeout {
if (!path || !host) {
[NSException raise:NSInvalidArgumentException format:@"path and host cannot be nil."];
- (instancetype)initWithUnmanagedCall:(grpc_call *)unmanagedCall
pooledChannel:(GRPCPooledChannel *)pooledChannel {
NSAssert(unmanagedCall != NULL, @"unmanagedCall cannot be empty.");
NSAssert(pooledChannel != nil, @"pooledChannel cannot be empty.");
if (unmanagedCall == NULL || pooledChannel == nil) {
return nil;
}
if (self = [super init]) {
// Each completion queue consumes one thread. There's a trade to be made between creating and
// consuming too many threads and having contention of multiple calls in a single completion
// queue. Currently we use a singleton queue.
_queue = [GRPCCompletionQueue completionQueue];
_call = [[GRPCHost hostWithAddress:host] unmanagedCallWithPath:path
serverName:serverName
timeout:timeout
completionQueue:_queue];
if (_call == NULL) {
return nil;
}
if ((self = [super init])) {
_call = unmanagedCall;
_pooledChannel = pooledChannel;
}
return self;
}
@ -278,41 +267,70 @@
[GRPCOpBatchLog addOpBatchToLog:operations];
#endif
size_t nops = operations.count;
grpc_op *ops_array = gpr_malloc(nops * sizeof(grpc_op));
size_t i = 0;
for (GRPCOperation *operation in operations) {
ops_array[i++] = operation.op;
}
grpc_call_error error =
grpc_call_start_batch(_call, ops_array, nops, (__bridge_retained void *)(^(bool success) {
if (!success) {
if (errorHandler) {
errorHandler();
} else {
return;
}
}
for (GRPCOperation *operation in operations) {
[operation finish];
}
}),
NULL);
gpr_free(ops_array);
if (error != GRPC_CALL_OK) {
[NSException
raise:NSInternalInconsistencyException
format:@"A precondition for calling grpc_call_start_batch wasn't met. Error %i", error];
@synchronized(self) {
if (_call != NULL) {
size_t nops = operations.count;
grpc_op *ops_array = gpr_malloc(nops * sizeof(grpc_op));
size_t i = 0;
for (GRPCOperation *operation in operations) {
ops_array[i++] = operation.op;
}
grpc_call_error error =
grpc_call_start_batch(_call, ops_array, nops, (__bridge_retained void *)(^(bool success) {
if (!success) {
if (errorHandler) {
errorHandler();
} else {
return;
}
}
for (GRPCOperation *operation in operations) {
[operation finish];
}
}),
NULL);
gpr_free(ops_array);
NSAssert(error == GRPC_CALL_OK, @"Error starting a batch of operations: %i", error);
// To avoid compiler complaint when NSAssert is disabled.
if (error != GRPC_CALL_OK) {
return;
}
}
}
}
- (void)cancel {
grpc_call_cancel(_call, NULL);
@synchronized(self) {
if (_call != NULL) {
grpc_call_cancel(_call, NULL);
}
}
}
- (void)channelDisconnected {
@synchronized(self) {
if (_call != NULL) {
// Unreference the call will lead to its cancellation in the core. Note that since
// this function is only called with a network state change, any existing GRPCCall object will
// also receive the same notification and cancel themselves with GRPCErrorCodeUnavailable, so
// the user gets GRPCErrorCodeUnavailable in this case.
grpc_call_unref(_call);
_call = NULL;
}
}
}
- (void)dealloc {
grpc_call_unref(_call);
@synchronized(self) {
if (_call != NULL) {
grpc_call_unref(_call);
_call = NULL;
}
}
// Explicitly converting weak reference _pooledChannel to strong.
__strong GRPCPooledChannel *channel = _pooledChannel;
[channel notifyWrappedCallDealloc:self];
}
@end

@ -22,4 +22,4 @@
// instead. This file can be regenerated from the template by running
// `tools/buildgen/generate_projects.sh`.
#define GRPC_OBJC_VERSION_STRING @"1.18.0-dev"
#define GRPC_OBJC_VERSION_STRING @"1.19.0-dev"

@ -21,6 +21,122 @@
#import "ProtoMethod.h"
NS_ASSUME_NONNULL_BEGIN
@class GPBMessage;
/** An object can implement this protocol to receive responses from server from a call. */
@protocol GRPCProtoResponseHandler<NSObject>
@required
/**
* All the responses must be issued to a user-provided dispatch queue. This property specifies the
* dispatch queue to be used for issuing the notifications.
*/
@property(atomic, readonly) dispatch_queue_t dispatchQueue;
@optional
/**
* Issued when initial metadata is received from the server.
*/
- (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata;
/**
* Issued when a message is received from the server. The message is the deserialized proto object.
*/
- (void)didReceiveProtoMessage:(nullable GPBMessage *)message;
/**
* Issued when a call finished. If the call finished successfully, \a error is nil and \a
* trainingMetadata consists any trailing metadata received from the server. Otherwise, \a error
* is non-nil and contains the corresponding error information, including gRPC error codes and
* error descriptions.
*/
- (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata
error:(nullable NSError *)error;
@end
/** A unary-request RPC call with Protobuf. */
@interface GRPCUnaryProtoCall : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype) new NS_UNAVAILABLE;
/**
* Users should not use this initializer directly. Call objects will be created, initialized, and
* returned to users by methods of the generated service.
*/
- (nullable instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
message:(GPBMessage *)message
responseHandler:(id<GRPCProtoResponseHandler>)handler
callOptions:(nullable GRPCCallOptions *)callOptions
responseClass:(Class)responseClass NS_DESIGNATED_INITIALIZER;
/**
* Start the call. This function must only be called once for each instance.
*/
- (void)start;
/**
* Cancel the request of this call at best effort. It attempts to notify the server that the RPC
* should be cancelled, and issue didCloseWithTrailingMetadata:error: callback with error code
* CANCELED if no other error code has already been issued.
*/
- (void)cancel;
@end
/** A client-streaming RPC call with Protobuf. */
@interface GRPCStreamingProtoCall : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype) new NS_UNAVAILABLE;
/**
* Users should not use this initializer directly. Call objects will be created, initialized, and
* returned to users by methods of the generated service.
*/
- (nullable instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
responseHandler:(id<GRPCProtoResponseHandler>)handler
callOptions:(nullable GRPCCallOptions *)callOptions
responseClass:(Class)responseClass NS_DESIGNATED_INITIALIZER;
/**
* Start the call. This function must only be called once for each instance.
*/
- (void)start;
/**
* Cancel the request of this call at best effort. It attempts to notify the server that the RPC
* should be cancelled, and issue didCloseWithTrailingMetadata:error: callback with error code
* CANCELED if no other error code has already been issued.
*/
- (void)cancel;
/**
* Send a message to the server. The message should be a Protobuf message which will be serialized
* internally.
*/
- (void)writeMessage:(GPBMessage *)message;
/**
* Finish the RPC request and half-close the call. The server may still send messages and/or
* trailers to the client.
*/
- (void)finish;
@end
NS_ASSUME_NONNULL_END
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullability-completeness"
__attribute__((deprecated("Please use GRPCProtoCall."))) @interface ProtoRPC
: GRPCCall
@ -47,3 +163,5 @@ __attribute__((deprecated("Please use GRPCProtoCall."))) @interface ProtoRPC
#pragma clang diagnostic pop
@end
#pragma clang diagnostic pop

@ -23,9 +23,13 @@
#else
#import <GPBProtocolBuffers.h>
#endif
#import <GRPCClient/GRPCCall.h>
#import <RxLibrary/GRXWriteable.h>
#import <RxLibrary/GRXWriter+Transformations.h>
/**
* Generate an NSError object that represents a failure in parsing a proto class.
*/
static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsingError) {
NSDictionary *info = @{
NSLocalizedDescriptionKey : @"Unable to parse response from the server",
@ -41,6 +45,227 @@ static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsing
return [NSError errorWithDomain:@"io.grpc" code:13 userInfo:info];
}
@implementation GRPCUnaryProtoCall {
GRPCStreamingProtoCall *_call;
GPBMessage *_message;
}
- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
message:(GPBMessage *)message
responseHandler:(id<GRPCProtoResponseHandler>)handler
callOptions:(GRPCCallOptions *)callOptions
responseClass:(Class)responseClass {
NSAssert(message != nil, @"message cannot be empty.");
NSAssert(responseClass != nil, @"responseClass cannot be empty.");
if (message == nil || responseClass == nil) {
return nil;
}
if ((self = [super init])) {
_call = [[GRPCStreamingProtoCall alloc] initWithRequestOptions:requestOptions
responseHandler:handler
callOptions:callOptions
responseClass:responseClass];
_message = [message copy];
}
return self;
}
- (void)start {
[_call start];
[_call writeMessage:_message];
[_call finish];
}
- (void)cancel {
[_call cancel];
}
@end
@interface GRPCStreamingProtoCall ()<GRPCResponseHandler>
@end
@implementation GRPCStreamingProtoCall {
GRPCRequestOptions *_requestOptions;
id<GRPCProtoResponseHandler> _handler;
GRPCCallOptions *_callOptions;
Class _responseClass;
GRPCCall2 *_call;
dispatch_queue_t _dispatchQueue;
}
- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
responseHandler:(id<GRPCProtoResponseHandler>)handler
callOptions:(GRPCCallOptions *)callOptions
responseClass:(Class)responseClass {
NSAssert(requestOptions.host.length != 0 && requestOptions.path.length != 0 &&
requestOptions.safety <= GRPCCallSafetyCacheableRequest,
@"Invalid callOptions.");
NSAssert(handler != nil, @"handler cannot be empty.");
if (requestOptions.host.length == 0 || requestOptions.path.length == 0 ||
requestOptions.safety > GRPCCallSafetyCacheableRequest) {
return nil;
}
if (handler == nil) {
return nil;
}
if ((self = [super init])) {
_requestOptions = [requestOptions copy];
_handler = handler;
_callOptions = [callOptions copy];
_responseClass = responseClass;
// Set queue QoS only when iOS version is 8.0 or above and Xcode version is 9.0 or above
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED < 101300
if (@available(iOS 8.0, macOS 10.10, *)) {
_dispatchQueue = dispatch_queue_create(
NULL,
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
} else {
#else
{
#endif
_dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
}
dispatch_set_target_queue(_dispatchQueue, handler.dispatchQueue);
_call = [[GRPCCall2 alloc] initWithRequestOptions:_requestOptions
responseHandler:self
callOptions:_callOptions];
}
return self;
}
- (void)start {
GRPCCall2 *copiedCall;
@synchronized(self) {
copiedCall = _call;
}
[copiedCall start];
}
- (void)cancel {
GRPCCall2 *copiedCall;
@synchronized(self) {
copiedCall = _call;
_call = nil;
if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
dispatch_async(_dispatchQueue, ^{
id<GRPCProtoResponseHandler> copiedHandler = nil;
@synchronized(self) {
copiedHandler = self->_handler;
self->_handler = nil;
}
[copiedHandler didCloseWithTrailingMetadata:nil
error:[NSError errorWithDomain:kGRPCErrorDomain
code:GRPCErrorCodeCancelled
userInfo:@{
NSLocalizedDescriptionKey :
@"Canceled by app"
}]];
});
} else {
_handler = nil;
}
}
[copiedCall cancel];
}
- (void)writeMessage:(GPBMessage *)message {
NSAssert([message isKindOfClass:[GPBMessage class]], @"Parameter message must be a GPBMessage");
if (![message isKindOfClass:[GPBMessage class]]) {
NSLog(@"Failed to send a message that is non-proto.");
return;
}
GRPCCall2 *copiedCall;
@synchronized(self) {
copiedCall = _call;
}
[copiedCall writeData:[message data]];
}
- (void)finish {
GRPCCall2 *copiedCall;
@synchronized(self) {
copiedCall = _call;
_call = nil;
}
[copiedCall finish];
}
- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
@synchronized(self) {
if (initialMetadata != nil &&
[_handler respondsToSelector:@selector(didReceiveInitialMetadata:)]) {
dispatch_async(_dispatchQueue, ^{
id<GRPCProtoResponseHandler> copiedHandler = nil;
@synchronized(self) {
copiedHandler = self->_handler;
}
[copiedHandler didReceiveInitialMetadata:initialMetadata];
});
}
}
}
- (void)didReceiveRawMessage:(NSData *)message {
if (message == nil) return;
NSError *error = nil;
GPBMessage *parsed = [_responseClass parseFromData:message error:&error];
@synchronized(self) {
if (parsed && [_handler respondsToSelector:@selector(didReceiveProtoMessage:)]) {
dispatch_async(_dispatchQueue, ^{
id<GRPCProtoResponseHandler> copiedHandler = nil;
@synchronized(self) {
copiedHandler = self->_handler;
}
[copiedHandler didReceiveProtoMessage:parsed];
});
} else if (!parsed &&
[_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
dispatch_async(_dispatchQueue, ^{
id<GRPCProtoResponseHandler> copiedHandler = nil;
@synchronized(self) {
copiedHandler = self->_handler;
self->_handler = nil;
}
[copiedHandler
didCloseWithTrailingMetadata:nil
error:ErrorForBadProto(message, self->_responseClass, error)];
});
[_call cancel];
_call = nil;
}
}
}
- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
@synchronized(self) {
if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
dispatch_async(_dispatchQueue, ^{
id<GRPCProtoResponseHandler> copiedHandler = nil;
@synchronized(self) {
copiedHandler = self->_handler;
self->_handler = nil;
}
[copiedHandler didCloseWithTrailingMetadata:trailingMetadata error:error];
});
}
_call = nil;
}
}
- (dispatch_queue_t)dispatchQueue {
return _dispatchQueue;
}
@end
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
@implementation ProtoRPC {
@ -72,7 +297,7 @@ static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsing
}
// A writer that serializes the proto messages to send.
GRXWriter *bytesWriter = [requestsWriter map:^id(GPBMessage *proto) {
if (![proto isKindOfClass:GPBMessage.class]) {
if (![proto isKindOfClass:[GPBMessage class]]) {
[NSException raise:NSInvalidArgumentException
format:@"Request must be a proto message: %@", proto];
}

@ -21,18 +21,47 @@
@class GRPCProtoCall;
@protocol GRXWriteable;
@class GRXWriter;
@class GRPCCallOptions;
@class GRPCProtoCall;
@class GRPCUnaryProtoCall;
@class GRPCStreamingProtoCall;
@protocol GRPCProtoResponseHandler;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullability-completeness"
__attribute__((deprecated("Please use GRPCProtoService."))) @interface ProtoService
: NSObject -
(instancetype)initWithHost : (NSString *)host packageName
: (NSString *)packageName serviceName : (NSString *)serviceName NS_DESIGNATED_INITIALIZER;
: NSObject
-
(nullable instancetype)initWithHost : (nonnull NSString *)host packageName
: (nonnull NSString *)packageName serviceName : (nonnull NSString *)serviceName callOptions
: (nullable GRPCCallOptions *)callOptions NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithHost:(NSString *)host
packageName:(NSString *)packageName
serviceName:(NSString *)serviceName;
- (GRPCProtoCall *)RPCToMethod:(NSString *)method
requestsWriter:(GRXWriter *)requestsWriter
responseClass:(Class)responseClass
responsesWriteable:(id<GRXWriteable>)responsesWriteable;
- (nullable GRPCUnaryProtoCall *)RPCToMethod:(nonnull NSString *)method
message:(nonnull id)message
responseHandler:(nonnull id<GRPCProtoResponseHandler>)handler
callOptions:(nullable GRPCCallOptions *)callOptions
responseClass:(nonnull Class)responseClass;
- (nullable GRPCStreamingProtoCall *)RPCToMethod:(nonnull NSString *)method
responseHandler:(nonnull id<GRPCProtoResponseHandler>)handler
callOptions:(nullable GRPCCallOptions *)callOptions
responseClass:(nonnull Class)responseClass;
@end
#pragma clang diagnostic pop
/**
* This subclass is empty now. Eventually we'll remove ProtoService class
* to avoid potential naming conflict

@ -18,6 +18,7 @@
#import "ProtoService.h"
#import <GRPCClient/GRPCCall.h>
#import <RxLibrary/GRXWriteable.h>
#import <RxLibrary/GRXWriter.h>
@ -31,6 +32,7 @@
NSString *_host;
NSString *_packageName;
NSString *_serviceName;
GRPCCallOptions *_callOptions;
}
- (instancetype)init {
@ -40,19 +42,41 @@
// Designated initializer
- (instancetype)initWithHost:(NSString *)host
packageName:(NSString *)packageName
serviceName:(NSString *)serviceName {
if (!host || !serviceName) {
[NSException raise:NSInvalidArgumentException
format:@"Neither host nor serviceName can be nil."];
serviceName:(NSString *)serviceName
callOptions:(GRPCCallOptions *)callOptions {
NSAssert(host.length != 0 && packageName.length != 0 && serviceName.length != 0,
@"Invalid parameter.");
if (host.length == 0 || packageName.length == 0 || serviceName.length == 0) {
return nil;
}
if ((self = [super init])) {
_host = [host copy];
_packageName = [packageName copy];
_serviceName = [serviceName copy];
_callOptions = [callOptions copy];
}
return self;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
// Do not call designated initializer here due to nullability incompatibility. This method is from
// old API and does not assert on nullability of the parameters.
- (instancetype)initWithHost:(NSString *)host
packageName:(NSString *)packageName
serviceName:(NSString *)serviceName {
if ((self = [super init])) {
_host = [host copy];
_packageName = [packageName copy];
_serviceName = [serviceName copy];
_callOptions = nil;
}
return self;
}
#pragma clang diagnostic pop
- (GRPCProtoCall *)RPCToMethod:(NSString *)method
requestsWriter:(GRXWriter *)requestsWriter
responseClass:(Class)responseClass
@ -65,6 +89,41 @@
responseClass:responseClass
responsesWriteable:responsesWriteable];
}
- (GRPCUnaryProtoCall *)RPCToMethod:(NSString *)method
message:(id)message
responseHandler:(id<GRPCProtoResponseHandler>)handler
callOptions:(GRPCCallOptions *)callOptions
responseClass:(Class)responseClass {
GRPCProtoMethod *methodName =
[[GRPCProtoMethod alloc] initWithPackage:_packageName service:_serviceName method:method];
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:_host
path:methodName.HTTPPath
safety:GRPCCallSafetyDefault];
return [[GRPCUnaryProtoCall alloc] initWithRequestOptions:requestOptions
message:message
responseHandler:handler
callOptions:callOptions ?: _callOptions
responseClass:responseClass];
}
- (GRPCStreamingProtoCall *)RPCToMethod:(NSString *)method
responseHandler:(id<GRPCProtoResponseHandler>)handler
callOptions:(GRPCCallOptions *)callOptions
responseClass:(Class)responseClass {
GRPCProtoMethod *methodName =
[[GRPCProtoMethod alloc] initWithPackage:_packageName service:_serviceName method:method];
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:_host
path:methodName.HTTPPath
safety:GRPCCallSafetyDefault];
return [[GRPCStreamingProtoCall alloc] initWithRequestOptions:requestOptions
responseHandler:handler
callOptions:callOptions ?: _callOptions
responseClass:responseClass];
}
@end
@implementation GRPCProtoService

@ -54,8 +54,8 @@ class ViewController: UIViewController {
} else {
NSLog("2. Finished with error: \(error!)")
}
NSLog("2. Response headers: \(RPC.responseHeaders)")
NSLog("2. Response trailers: \(RPC.responseTrailers)")
NSLog("2. Response headers: \(String(describing: RPC.responseHeaders))")
NSLog("2. Response trailers: \(String(describing: RPC.responseTrailers))")
}
// TODO(jcanizales): Revert to using subscript syntax once XCode 8 is released.
@ -68,7 +68,7 @@ class ViewController: UIViewController {
let method = GRPCProtoMethod(package: "grpc.testing", service: "TestService", method: "UnaryCall")!
let requestsWriter = GRXWriter(value: request.data())
let requestsWriter = GRXWriter(value: request.data())!
let call = GRPCCall(host: RemoteHost, path: method.httpPath, requestsWriter: requestsWriter)!
@ -80,8 +80,8 @@ class ViewController: UIViewController {
} else {
NSLog("3. Finished with error: \(error!)")
}
NSLog("3. Response headers: \(call.responseHeaders)")
NSLog("3. Response trailers: \(call.responseTrailers)")
NSLog("3. Response headers: \(String(describing: call.responseHeaders))")
NSLog("3. Response trailers: \(String(describing: call.responseTrailers))")
})
}
}

@ -0,0 +1,478 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import <GRPCClient/GRPCCall.h>
#import <ProtoRPC/ProtoMethod.h>
#import <RemoteTest/Messages.pbobjc.h>
#import <XCTest/XCTest.h>
#include <grpc/grpc.h>
#import "../version.h"
// The server address is derived from preprocessor macro, which is
// in turn derived from environment variable of the same name.
#define NSStringize_helper(x) #x
#define NSStringize(x) @NSStringize_helper(x)
static NSString *const kHostAddress = NSStringize(HOST_PORT_LOCAL);
static NSString *const kRemoteSSLHost = NSStringize(HOST_PORT_REMOTE);
// Package and service name of test server
static NSString *const kPackage = @"grpc.testing";
static NSString *const kService = @"TestService";
static GRPCProtoMethod *kInexistentMethod;
static GRPCProtoMethod *kEmptyCallMethod;
static GRPCProtoMethod *kUnaryCallMethod;
static GRPCProtoMethod *kFullDuplexCallMethod;
static const int kSimpleDataLength = 100;
static const NSTimeInterval kTestTimeout = 16;
// Reveal the _class ivar for testing access
@interface GRPCCall2 () {
@public
GRPCCall *_call;
}
@end
// Convenience class to use blocks as callbacks
@interface ClientTestsBlockCallbacks : NSObject<GRPCResponseHandler>
- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
messageCallback:(void (^)(id))messageCallback
closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback;
@end
@implementation ClientTestsBlockCallbacks {
void (^_initialMetadataCallback)(NSDictionary *);
void (^_messageCallback)(id);
void (^_closeCallback)(NSDictionary *, NSError *);
dispatch_queue_t _dispatchQueue;
}
- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
messageCallback:(void (^)(id))messageCallback
closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback {
if ((self = [super init])) {
_initialMetadataCallback = initialMetadataCallback;
_messageCallback = messageCallback;
_closeCallback = closeCallback;
_dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
if (self->_initialMetadataCallback) {
self->_initialMetadataCallback(initialMetadata);
}
}
- (void)didReceiveRawMessage:(GPBMessage *)message {
if (self->_messageCallback) {
self->_messageCallback(message);
}
}
- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
if (self->_closeCallback) {
self->_closeCallback(trailingMetadata, error);
}
}
- (dispatch_queue_t)dispatchQueue {
return _dispatchQueue;
}
@end
@interface CallAPIv2Tests : XCTestCase<GRPCAuthorizationProtocol>
@end
@implementation CallAPIv2Tests
- (void)setUp {
// This method isn't implemented by the remote server.
kInexistentMethod =
[[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"Inexistent"];
kEmptyCallMethod =
[[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"EmptyCall"];
kUnaryCallMethod =
[[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"UnaryCall"];
kFullDuplexCallMethod =
[[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"FullDuplexCall"];
}
- (void)testMetadata {
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."];
RMTSimpleRequest *request = [RMTSimpleRequest message];
request.fillUsername = YES;
request.fillOauthScope = YES;
GRPCRequestOptions *callRequest =
[[GRPCRequestOptions alloc] initWithHost:(NSString *)kRemoteSSLHost
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
__block NSDictionary *init_md;
__block NSDictionary *trailing_md;
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.oauth2AccessToken = @"bogusToken";
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:callRequest
responseHandler:[[ClientTestsBlockCallbacks alloc]
initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) {
init_md = initialMetadata;
}
messageCallback:^(id message) {
XCTFail(@"Received unexpected response.");
}
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
trailing_md = trailingMetadata;
if (error) {
XCTAssertEqual(error.code, 16,
@"Finished with unexpected error: %@", error);
XCTAssertEqualObjects(init_md,
error.userInfo[kGRPCHeadersKey]);
XCTAssertEqualObjects(trailing_md,
error.userInfo[kGRPCTrailersKey]);
NSString *challengeHeader = init_md[@"www-authenticate"];
XCTAssertGreaterThan(challengeHeader.length, 0,
@"No challenge in response headers %@",
init_md);
[expectation fulfill];
}
}]
callOptions:options];
[call start];
[call writeData:[request data]];
[call finish];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testUserAgentPrefix {
__weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
__weak XCTestExpectation *recvInitialMd =
[self expectationWithDescription:@"Did not receive initial md."];
GRPCRequestOptions *request = [[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kEmptyCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
NSDictionary *headers =
[NSDictionary dictionaryWithObjectsAndKeys:@"", @"x-grpc-test-echo-useragent", nil];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
options.userAgentPrefix = @"Foo";
options.initialMetadata = headers;
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:request
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:^(
NSDictionary *initialMetadata) {
NSString *userAgent = initialMetadata[@"x-grpc-test-echo-useragent"];
// Test the regex is correct
NSString *expectedUserAgent = @"Foo grpc-objc/";
expectedUserAgent =
[expectedUserAgent stringByAppendingString:GRPC_OBJC_VERSION_STRING];
expectedUserAgent = [expectedUserAgent stringByAppendingString:@" grpc-c/"];
expectedUserAgent =
[expectedUserAgent stringByAppendingString:GRPC_C_VERSION_STRING];
expectedUserAgent = [expectedUserAgent stringByAppendingString:@" (ios; chttp2; "];
expectedUserAgent = [expectedUserAgent
stringByAppendingString:[NSString stringWithUTF8String:grpc_g_stands_for()]];
expectedUserAgent = [expectedUserAgent stringByAppendingString:@")"];
XCTAssertEqualObjects(userAgent, expectedUserAgent);
NSError *error = nil;
// Change in format of user-agent field in a direction that does not match
// the regex will likely cause problem for certain gRPC users. For details,
// refer to internal doc https://goo.gl/c2diBc
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:
@" grpc-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)?/[^ ,]+( \\([^)]*\\))?"
options:0
error:&error];
NSString *customUserAgent =
[regex stringByReplacingMatchesInString:userAgent
options:0
range:NSMakeRange(0, [userAgent length])
withTemplate:@""];
XCTAssertEqualObjects(customUserAgent, @"Foo");
[recvInitialMd fulfill];
}
messageCallback:^(id message) {
XCTAssertNotNil(message);
XCTAssertEqual([message length], 0,
@"Non-empty response received: %@", message);
}
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
if (error) {
XCTFail(@"Finished with unexpected error: %@", error);
} else {
[completion fulfill];
}
}]
callOptions:options];
[call writeData:[NSData data]];
[call start];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)getTokenWithHandler:(void (^)(NSString *token))handler {
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
handler(@"test-access-token");
});
}
- (void)testOAuthToken {
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kEmptyCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
options.authTokenProvider = self;
__block GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:[[ClientTestsBlockCallbacks alloc]
initWithInitialMetadataCallback:nil
messageCallback:nil
closeCallback:^(NSDictionary *trailingMetadata,
NSError *error) {
[completion fulfill];
}]
callOptions:options];
[call writeData:[NSData data]];
[call start];
[call finish];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testResponseSizeLimitExceeded {
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.responseSizeLimit = kSimpleDataLength;
options.transportType = GRPCTransportTypeInsecure;
RMTSimpleRequest *request = [RMTSimpleRequest message];
request.payload.body = [NSMutableData dataWithLength:options.responseSizeLimit];
request.responseSize = (int32_t)(options.responseSizeLimit * 2);
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:[[ClientTestsBlockCallbacks alloc]
initWithInitialMetadataCallback:nil
messageCallback:nil
closeCallback:^(NSDictionary *trailingMetadata,
NSError *error) {
XCTAssertNotNil(error,
@"Expecting non-nil error");
XCTAssertEqual(error.code,
GRPCErrorCodeResourceExhausted);
[completion fulfill];
}]
callOptions:options];
[call writeData:[request data]];
[call start];
[call finish];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testIdempotentProtoRPC {
__weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."];
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
RMTSimpleRequest *request = [RMTSimpleRequest message];
request.responseSize = kSimpleDataLength;
request.fillUsername = YES;
request.fillOauthScope = YES;
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyIdempotentRequest];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
messageCallback:^(id message) {
NSData *data = (NSData *)message;
XCTAssertNotNil(data, @"nil value received as response.");
XCTAssertGreaterThan(data.length, 0,
@"Empty response received.");
RMTSimpleResponse *responseProto =
[RMTSimpleResponse parseFromData:data error:NULL];
// We expect empty strings, not nil:
XCTAssertNotNil(responseProto.username,
@"Response's username is nil.");
XCTAssertNotNil(responseProto.oauthScope,
@"Response's OAuth scope is nil.");
[response fulfill];
}
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
XCTAssertNil(error, @"Finished with unexpected error: %@",
error);
[completion fulfill];
}]
callOptions:options];
[call start];
[call writeData:[request data]];
[call finish];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testTimeout {
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.timeout = 0.001;
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kFullDuplexCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:
[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
messageCallback:^(NSData *data) {
XCTFail(@"Failure: response received; Expect: no response received.");
}
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
XCTAssertNotNil(error,
@"Failure: no error received; Expect: receive "
@"deadline exceeded.");
XCTAssertEqual(error.code, GRPCErrorCodeDeadlineExceeded);
[completion fulfill];
}]
callOptions:options];
[call start];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testTimeoutBackoffWithTimeout:(double)timeout Backoff:(double)backoff {
const double maxConnectTime = timeout > backoff ? timeout : backoff;
const double kMargin = 0.1;
__weak XCTestExpectation *completion = [self expectationWithDescription:@"Timeout in a second."];
NSString *const kDummyAddress = [NSString stringWithFormat:@"127.0.0.1:10000"];
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kDummyAddress
path:@"/dummy/path"
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.connectMinTimeout = timeout;
options.connectInitialBackoff = backoff;
options.connectMaxBackoff = 0;
NSDate *startTime = [NSDate date];
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
messageCallback:^(NSData *data) {
XCTFail(@"Received message. Should not reach here.");
}
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
XCTAssertNotNil(error,
@"Finished with no error; expecting error");
XCTAssertLessThan(
[[NSDate date] timeIntervalSinceDate:startTime],
maxConnectTime + kMargin);
[completion fulfill];
}]
callOptions:options];
[call start];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testTimeoutBackoff1 {
[self testTimeoutBackoffWithTimeout:0.7 Backoff:0.4];
}
- (void)testTimeoutBackoff2 {
[self testTimeoutBackoffWithTimeout:0.3 Backoff:0.8];
}
- (void)testCompression {
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
RMTSimpleRequest *request = [RMTSimpleRequest message];
request.expectCompressed = [RMTBoolValue message];
request.expectCompressed.value = YES;
request.responseCompressed = [RMTBoolValue message];
request.expectCompressed.value = YES;
request.responseSize = kSimpleDataLength;
request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
options.compressionAlgorithm = GRPCCompressGzip;
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
messageCallback:^(NSData *data) {
NSError *error;
RMTSimpleResponse *response =
[RMTSimpleResponse parseFromData:data error:&error];
XCTAssertNil(error, @"Error when parsing response: %@", error);
XCTAssertEqual(response.payload.body.length, kSimpleDataLength);
}
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
XCTAssertNil(error, @"Received failure: %@", error);
[completion fulfill];
}]
callOptions:options];
[call start];
[call writeData:[request data]];
[call finish];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
@end

@ -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>

@ -0,0 +1,63 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import <XCTest/XCTest.h>
#import "../../GRPCClient/private/GRPCChannel.h"
#import "../../GRPCClient/private/GRPCChannelPool+Test.h"
#import "../../GRPCClient/private/GRPCCompletionQueue.h"
#define TEST_TIMEOUT 32
static NSString *kDummyHost = @"dummy.host";
static NSString *kDummyHost2 = @"dummy.host.2";
static NSString *kDummyPath = @"/dummy/path";
@interface ChannelPoolTest : XCTestCase
@end
@implementation ChannelPoolTest
+ (void)setUp {
grpc_init();
}
- (void)testCreateAndCacheChannel {
GRPCChannelPool *pool = [[GRPCChannelPool alloc] initTestPool];
GRPCCallOptions *options1 = [[GRPCCallOptions alloc] init];
GRPCCallOptions *options2 = [options1 copy];
GRPCMutableCallOptions *options3 = [options1 mutableCopy];
options3.transportType = GRPCTransportTypeInsecure;
GRPCPooledChannel *channel1 = [pool channelWithHost:kDummyHost callOptions:options1];
GRPCPooledChannel *channel2 = [pool channelWithHost:kDummyHost callOptions:options2];
GRPCPooledChannel *channel3 = [pool channelWithHost:kDummyHost callOptions:options3];
GRPCPooledChannel *channel4 = [pool channelWithHost:kDummyHost2 callOptions:options1];
XCTAssertNotNil(channel1);
XCTAssertNotNil(channel2);
XCTAssertNotNil(channel3);
XCTAssertNotNil(channel4);
XCTAssertEqual(channel1, channel2);
XCTAssertNotEqual(channel1, channel3);
XCTAssertNotEqual(channel1, channel4);
XCTAssertNotEqual(channel3, channel4);
}
@end

@ -0,0 +1,112 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import <XCTest/XCTest.h>
#import "../../GRPCClient/GRPCCallOptions.h"
#import "../../GRPCClient/private/GRPCChannel.h"
#import "../../GRPCClient/private/GRPCChannelPool+Test.h"
#import "../../GRPCClient/private/GRPCChannelPool.h"
#import "../../GRPCClient/private/GRPCCompletionQueue.h"
#import "../../GRPCClient/private/GRPCWrappedCall.h"
static NSString *kDummyHost = @"dummy.host";
static NSString *kDummyPath = @"/dummy/path";
@interface ChannelTests : XCTestCase
@end
@implementation ChannelTests
+ (void)setUp {
grpc_init();
}
- (void)testPooledChannelCreatingChannel {
GRPCCallOptions *options = [[GRPCCallOptions alloc] init];
GRPCChannelConfiguration *config =
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options];
GRPCPooledChannel *channel = [[GRPCPooledChannel alloc] initWithChannelConfiguration:config];
GRPCCompletionQueue *cq = [GRPCCompletionQueue completionQueue];
GRPCWrappedCall *wrappedCall =
[channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options];
XCTAssertNotNil(channel.wrappedChannel);
(void)wrappedCall;
}
- (void)testTimedDestroyChannel {
const NSTimeInterval kDestroyDelay = 1.0;
GRPCCallOptions *options = [[GRPCCallOptions alloc] init];
GRPCChannelConfiguration *config =
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options];
GRPCPooledChannel *channel =
[[GRPCPooledChannel alloc] initWithChannelConfiguration:config destroyDelay:kDestroyDelay];
GRPCCompletionQueue *cq = [GRPCCompletionQueue completionQueue];
GRPCWrappedCall *wrappedCall;
GRPCChannel *wrappedChannel;
@autoreleasepool {
wrappedCall = [channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options];
XCTAssertNotNil(channel.wrappedChannel);
// Unref and ref channel immediately; expect using the same raw channel.
wrappedChannel = channel.wrappedChannel;
wrappedCall = nil;
wrappedCall = [channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options];
XCTAssertEqual(channel.wrappedChannel, wrappedChannel);
// Unref and ref channel after destroy delay; expect a new raw channel.
wrappedCall = nil;
}
sleep(kDestroyDelay + 1);
XCTAssertNil(channel.wrappedChannel);
wrappedCall = [channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options];
XCTAssertNotEqual(channel.wrappedChannel, wrappedChannel);
}
- (void)testDisconnect {
const NSTimeInterval kDestroyDelay = 1.0;
GRPCCallOptions *options = [[GRPCCallOptions alloc] init];
GRPCChannelConfiguration *config =
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options];
GRPCPooledChannel *channel =
[[GRPCPooledChannel alloc] initWithChannelConfiguration:config destroyDelay:kDestroyDelay];
GRPCCompletionQueue *cq = [GRPCCompletionQueue completionQueue];
GRPCWrappedCall *wrappedCall =
[channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options];
XCTAssertNotNil(channel.wrappedChannel);
// Disconnect; expect wrapped channel to be dropped
[channel disconnect];
XCTAssertNil(channel.wrappedChannel);
// Create a new call and unref the old call; confirm that destroy of the old call does not make
// the channel disconnect, even after the destroy delay.
GRPCWrappedCall *wrappedCall2 =
[channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options];
XCTAssertNotNil(channel.wrappedChannel);
GRPCChannel *wrappedChannel = channel.wrappedChannel;
wrappedCall = nil;
sleep(kDestroyDelay + 1);
XCTAssertNotNil(channel.wrappedChannel);
XCTAssertEqual(wrappedChannel, channel.wrappedChannel);
(void)wrappedCall2;
}
@end

@ -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>

@ -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);
}

@ -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");

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save