diff --git a/BUILD b/BUILD
index 73d91ede94a81edb7c61bcc5fd0e15f202a08e78..e4ee8e93f71681b9b8d73be98eb309ca2768e014 100644
--- a/BUILD
+++ b/BUILD
@@ -1484,6 +1484,154 @@ cc_library(
 
 
 
+cc_library(
+  name = "grpc++_cronet",
+  srcs = [
+    "src/cpp/client/create_channel_internal.h",
+    "src/cpp/common/channel_filter.h",
+    "src/cpp/server/dynamic_thread_pool.h",
+    "src/cpp/server/thread_pool_interface.h",
+    "src/cpp/client/cronet_credentials.cc",
+    "src/cpp/client/insecure_credentials.cc",
+    "src/cpp/common/insecure_create_auth_context.cc",
+    "src/cpp/server/insecure_server_credentials.cc",
+    "src/cpp/client/channel_cc.cc",
+    "src/cpp/client/client_context.cc",
+    "src/cpp/client/create_channel.cc",
+    "src/cpp/client/create_channel_internal.cc",
+    "src/cpp/client/create_channel_posix.cc",
+    "src/cpp/client/credentials_cc.cc",
+    "src/cpp/client/generic_stub.cc",
+    "src/cpp/common/channel_arguments.cc",
+    "src/cpp/common/channel_filter.cc",
+    "src/cpp/common/completion_queue_cc.cc",
+    "src/cpp/common/core_codegen.cc",
+    "src/cpp/common/rpc_method.cc",
+    "src/cpp/server/async_generic_service.cc",
+    "src/cpp/server/create_default_thread_pool.cc",
+    "src/cpp/server/dynamic_thread_pool.cc",
+    "src/cpp/server/server_builder.cc",
+    "src/cpp/server/server_cc.cc",
+    "src/cpp/server/server_context.cc",
+    "src/cpp/server/server_credentials.cc",
+    "src/cpp/server/server_posix.cc",
+    "src/cpp/util/byte_buffer_cc.cc",
+    "src/cpp/util/slice_cc.cc",
+    "src/cpp/util/status.cc",
+    "src/cpp/util/string_ref.cc",
+    "src/cpp/util/time_cc.cc",
+    "src/cpp/codegen/codegen_init.cc",
+  ],
+  hdrs = [
+    "include/grpc++/alarm.h",
+    "include/grpc++/channel.h",
+    "include/grpc++/client_context.h",
+    "include/grpc++/completion_queue.h",
+    "include/grpc++/create_channel.h",
+    "include/grpc++/create_channel_posix.h",
+    "include/grpc++/generic/async_generic_service.h",
+    "include/grpc++/generic/generic_stub.h",
+    "include/grpc++/grpc++.h",
+    "include/grpc++/impl/call.h",
+    "include/grpc++/impl/client_unary_call.h",
+    "include/grpc++/impl/codegen/core_codegen.h",
+    "include/grpc++/impl/grpc_library.h",
+    "include/grpc++/impl/method_handler_impl.h",
+    "include/grpc++/impl/rpc_method.h",
+    "include/grpc++/impl/rpc_service_method.h",
+    "include/grpc++/impl/serialization_traits.h",
+    "include/grpc++/impl/server_builder_option.h",
+    "include/grpc++/impl/server_builder_plugin.h",
+    "include/grpc++/impl/server_initializer.h",
+    "include/grpc++/impl/service_type.h",
+    "include/grpc++/impl/sync.h",
+    "include/grpc++/impl/sync_cxx11.h",
+    "include/grpc++/impl/sync_no_cxx11.h",
+    "include/grpc++/impl/thd.h",
+    "include/grpc++/impl/thd_cxx11.h",
+    "include/grpc++/impl/thd_no_cxx11.h",
+    "include/grpc++/security/auth_context.h",
+    "include/grpc++/security/auth_metadata_processor.h",
+    "include/grpc++/security/credentials.h",
+    "include/grpc++/security/server_credentials.h",
+    "include/grpc++/server.h",
+    "include/grpc++/server_builder.h",
+    "include/grpc++/server_context.h",
+    "include/grpc++/server_posix.h",
+    "include/grpc++/support/async_stream.h",
+    "include/grpc++/support/async_unary_call.h",
+    "include/grpc++/support/byte_buffer.h",
+    "include/grpc++/support/channel_arguments.h",
+    "include/grpc++/support/config.h",
+    "include/grpc++/support/slice.h",
+    "include/grpc++/support/status.h",
+    "include/grpc++/support/status_code_enum.h",
+    "include/grpc++/support/string_ref.h",
+    "include/grpc++/support/stub_options.h",
+    "include/grpc++/support/sync_stream.h",
+    "include/grpc++/support/time.h",
+    "include/grpc++/impl/codegen/async_stream.h",
+    "include/grpc++/impl/codegen/async_unary_call.h",
+    "include/grpc++/impl/codegen/call.h",
+    "include/grpc++/impl/codegen/call_hook.h",
+    "include/grpc++/impl/codegen/channel_interface.h",
+    "include/grpc++/impl/codegen/client_context.h",
+    "include/grpc++/impl/codegen/client_unary_call.h",
+    "include/grpc++/impl/codegen/completion_queue.h",
+    "include/grpc++/impl/codegen/completion_queue_tag.h",
+    "include/grpc++/impl/codegen/config.h",
+    "include/grpc++/impl/codegen/core_codegen_interface.h",
+    "include/grpc++/impl/codegen/create_auth_context.h",
+    "include/grpc++/impl/codegen/grpc_library.h",
+    "include/grpc++/impl/codegen/method_handler_impl.h",
+    "include/grpc++/impl/codegen/rpc_method.h",
+    "include/grpc++/impl/codegen/rpc_service_method.h",
+    "include/grpc++/impl/codegen/security/auth_context.h",
+    "include/grpc++/impl/codegen/serialization_traits.h",
+    "include/grpc++/impl/codegen/server_context.h",
+    "include/grpc++/impl/codegen/server_interface.h",
+    "include/grpc++/impl/codegen/service_type.h",
+    "include/grpc++/impl/codegen/status.h",
+    "include/grpc++/impl/codegen/status_code_enum.h",
+    "include/grpc++/impl/codegen/status_helper.h",
+    "include/grpc++/impl/codegen/string_ref.h",
+    "include/grpc++/impl/codegen/stub_options.h",
+    "include/grpc++/impl/codegen/sync.h",
+    "include/grpc++/impl/codegen/sync_cxx11.h",
+    "include/grpc++/impl/codegen/sync_no_cxx11.h",
+    "include/grpc++/impl/codegen/sync_stream.h",
+    "include/grpc++/impl/codegen/time.h",
+    "include/grpc/impl/codegen/byte_buffer_reader.h",
+    "include/grpc/impl/codegen/compression_types.h",
+    "include/grpc/impl/codegen/connectivity_state.h",
+    "include/grpc/impl/codegen/grpc_types.h",
+    "include/grpc/impl/codegen/propagation_bits.h",
+    "include/grpc/impl/codegen/status.h",
+    "include/grpc/impl/codegen/atm.h",
+    "include/grpc/impl/codegen/atm_gcc_atomic.h",
+    "include/grpc/impl/codegen/atm_gcc_sync.h",
+    "include/grpc/impl/codegen/atm_windows.h",
+    "include/grpc/impl/codegen/gpr_types.h",
+    "include/grpc/impl/codegen/port_platform.h",
+    "include/grpc/impl/codegen/slice.h",
+    "include/grpc/impl/codegen/sync.h",
+    "include/grpc/impl/codegen/sync_generic.h",
+    "include/grpc/impl/codegen/sync_posix.h",
+    "include/grpc/impl/codegen/sync_windows.h",
+  ],
+  includes = [
+    "include",
+    ".",
+  ],
+  deps = [
+    "//external:libssl",
+    ":gpr",
+    ":grpc_cronet",
+  ],
+)
+
+
+
 cc_library(
   name = "grpc++_reflection",
   srcs = [
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ddc0e1d0de0a221ef9c6072b2a4b75b3b8d99ac5..2f301f54b9d1b9720e7d9a164bef9b20ad1e8468 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1211,6 +1211,169 @@ if (gRPC_INSTALL)
 endif()
 
   
+add_library(grpc++_cronet
+  src/cpp/client/cronet_credentials.cc
+  src/cpp/client/insecure_credentials.cc
+  src/cpp/common/insecure_create_auth_context.cc
+  src/cpp/server/insecure_server_credentials.cc
+  src/cpp/client/channel_cc.cc
+  src/cpp/client/client_context.cc
+  src/cpp/client/create_channel.cc
+  src/cpp/client/create_channel_internal.cc
+  src/cpp/client/create_channel_posix.cc
+  src/cpp/client/credentials_cc.cc
+  src/cpp/client/generic_stub.cc
+  src/cpp/common/channel_arguments.cc
+  src/cpp/common/channel_filter.cc
+  src/cpp/common/completion_queue_cc.cc
+  src/cpp/common/core_codegen.cc
+  src/cpp/common/rpc_method.cc
+  src/cpp/server/async_generic_service.cc
+  src/cpp/server/create_default_thread_pool.cc
+  src/cpp/server/dynamic_thread_pool.cc
+  src/cpp/server/server_builder.cc
+  src/cpp/server/server_cc.cc
+  src/cpp/server/server_context.cc
+  src/cpp/server/server_credentials.cc
+  src/cpp/server/server_posix.cc
+  src/cpp/util/byte_buffer_cc.cc
+  src/cpp/util/slice_cc.cc
+  src/cpp/util/status.cc
+  src/cpp/util/string_ref.cc
+  src/cpp/util/time_cc.cc
+  src/cpp/codegen/codegen_init.cc
+)
+
+target_include_directories(grpc++_cronet
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${BORINGSSL_ROOT_DIR}/include
+  PRIVATE ${PROTOBUF_ROOT_DIR}/src
+  PRIVATE ${ZLIB_INCLUDE_DIR}
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib
+)
+
+target_link_libraries(grpc++_cronet
+  ${_gRPC_BASELIB_LIBRARIES}
+  ${_gRPC_SSL_LIBRARIES}
+  gpr
+  grpc_cronet
+)
+
+foreach(_hdr
+  include/grpc++/alarm.h
+  include/grpc++/channel.h
+  include/grpc++/client_context.h
+  include/grpc++/completion_queue.h
+  include/grpc++/create_channel.h
+  include/grpc++/create_channel_posix.h
+  include/grpc++/generic/async_generic_service.h
+  include/grpc++/generic/generic_stub.h
+  include/grpc++/grpc++.h
+  include/grpc++/impl/call.h
+  include/grpc++/impl/client_unary_call.h
+  include/grpc++/impl/codegen/core_codegen.h
+  include/grpc++/impl/grpc_library.h
+  include/grpc++/impl/method_handler_impl.h
+  include/grpc++/impl/rpc_method.h
+  include/grpc++/impl/rpc_service_method.h
+  include/grpc++/impl/serialization_traits.h
+  include/grpc++/impl/server_builder_option.h
+  include/grpc++/impl/server_builder_plugin.h
+  include/grpc++/impl/server_initializer.h
+  include/grpc++/impl/service_type.h
+  include/grpc++/impl/sync.h
+  include/grpc++/impl/sync_cxx11.h
+  include/grpc++/impl/sync_no_cxx11.h
+  include/grpc++/impl/thd.h
+  include/grpc++/impl/thd_cxx11.h
+  include/grpc++/impl/thd_no_cxx11.h
+  include/grpc++/security/auth_context.h
+  include/grpc++/security/auth_metadata_processor.h
+  include/grpc++/security/credentials.h
+  include/grpc++/security/server_credentials.h
+  include/grpc++/server.h
+  include/grpc++/server_builder.h
+  include/grpc++/server_context.h
+  include/grpc++/server_posix.h
+  include/grpc++/support/async_stream.h
+  include/grpc++/support/async_unary_call.h
+  include/grpc++/support/byte_buffer.h
+  include/grpc++/support/channel_arguments.h
+  include/grpc++/support/config.h
+  include/grpc++/support/slice.h
+  include/grpc++/support/status.h
+  include/grpc++/support/status_code_enum.h
+  include/grpc++/support/string_ref.h
+  include/grpc++/support/stub_options.h
+  include/grpc++/support/sync_stream.h
+  include/grpc++/support/time.h
+  include/grpc++/impl/codegen/async_stream.h
+  include/grpc++/impl/codegen/async_unary_call.h
+  include/grpc++/impl/codegen/call.h
+  include/grpc++/impl/codegen/call_hook.h
+  include/grpc++/impl/codegen/channel_interface.h
+  include/grpc++/impl/codegen/client_context.h
+  include/grpc++/impl/codegen/client_unary_call.h
+  include/grpc++/impl/codegen/completion_queue.h
+  include/grpc++/impl/codegen/completion_queue_tag.h
+  include/grpc++/impl/codegen/config.h
+  include/grpc++/impl/codegen/core_codegen_interface.h
+  include/grpc++/impl/codegen/create_auth_context.h
+  include/grpc++/impl/codegen/grpc_library.h
+  include/grpc++/impl/codegen/method_handler_impl.h
+  include/grpc++/impl/codegen/rpc_method.h
+  include/grpc++/impl/codegen/rpc_service_method.h
+  include/grpc++/impl/codegen/security/auth_context.h
+  include/grpc++/impl/codegen/serialization_traits.h
+  include/grpc++/impl/codegen/server_context.h
+  include/grpc++/impl/codegen/server_interface.h
+  include/grpc++/impl/codegen/service_type.h
+  include/grpc++/impl/codegen/status.h
+  include/grpc++/impl/codegen/status_code_enum.h
+  include/grpc++/impl/codegen/status_helper.h
+  include/grpc++/impl/codegen/string_ref.h
+  include/grpc++/impl/codegen/stub_options.h
+  include/grpc++/impl/codegen/sync.h
+  include/grpc++/impl/codegen/sync_cxx11.h
+  include/grpc++/impl/codegen/sync_no_cxx11.h
+  include/grpc++/impl/codegen/sync_stream.h
+  include/grpc++/impl/codegen/time.h
+  include/grpc/impl/codegen/byte_buffer_reader.h
+  include/grpc/impl/codegen/compression_types.h
+  include/grpc/impl/codegen/connectivity_state.h
+  include/grpc/impl/codegen/grpc_types.h
+  include/grpc/impl/codegen/propagation_bits.h
+  include/grpc/impl/codegen/status.h
+  include/grpc/impl/codegen/atm.h
+  include/grpc/impl/codegen/atm_gcc_atomic.h
+  include/grpc/impl/codegen/atm_gcc_sync.h
+  include/grpc/impl/codegen/atm_windows.h
+  include/grpc/impl/codegen/gpr_types.h
+  include/grpc/impl/codegen/port_platform.h
+  include/grpc/impl/codegen/slice.h
+  include/grpc/impl/codegen/sync.h
+  include/grpc/impl/codegen/sync_generic.h
+  include/grpc/impl/codegen/sync_posix.h
+  include/grpc/impl/codegen/sync_windows.h
+)
+  string(REPLACE "include/" "" _path ${_hdr})
+  get_filename_component(_path ${_path} PATH)
+  install(FILES ${_hdr}
+    DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${_path}"
+  )
+endforeach()
+
+  
+if (gRPC_INSTALL)
+  install(TARGETS grpc++_cronet EXPORT gRPCTargets
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+  )
+endif()
+
+  
 add_library(grpc++_reflection
   src/cpp/ext/proto_server_reflection.cc
   src/cpp/ext/proto_server_reflection_plugin.cc
diff --git a/Makefile b/Makefile
index 12be864d54c2a573dce819ef062fe3ddaf1092c5..892009d766e1c700a23c44910adb36f724b14eca 100644
--- a/Makefile
+++ b/Makefile
@@ -1208,12 +1208,12 @@ static: static_c static_cxx
 
 static_c: pc_c pc_c_unsecure cache.mk  $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgrpc_cronet.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a
 
-static_cxx: pc_cxx pc_cxx_unsecure cache.mk  $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc++_reflection.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a
+static_cxx: pc_cxx pc_cxx_unsecure cache.mk  $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc++_cronet.a $(LIBDIR)/$(CONFIG)/libgrpc++_reflection.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a
 
 shared: shared_c shared_cxx
 
 shared_c: pc_c pc_c_unsecure cache.mk $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)gpr$(SHARED_VERSION).$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc$(SHARED_VERSION).$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc_cronet$(SHARED_VERSION).$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc_unsecure$(SHARED_VERSION).$(SHARED_EXT)
-shared_cxx: pc_cxx pc_cxx_unsecure cache.mk $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++$(SHARED_VERSION).$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++_reflection$(SHARED_VERSION).$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++_unsecure$(SHARED_VERSION).$(SHARED_EXT)
+shared_cxx: pc_cxx pc_cxx_unsecure cache.mk $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++$(SHARED_VERSION).$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++_reflection$(SHARED_VERSION).$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++_unsecure$(SHARED_VERSION).$(SHARED_EXT)
 
 shared_csharp: shared_c  $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc_csharp_ext$(SHARED_VERSION).$(SHARED_EXT)
 grpc_csharp_ext: shared_csharp
@@ -1903,6 +1903,8 @@ strip-static_cxx: static_cxx
 ifeq ($(CONFIG),opt)
 	$(E) "[STRIP]   Stripping libgrpc++.a"
 	$(Q) $(STRIP) $(LIBDIR)/$(CONFIG)/libgrpc++.a
+	$(E) "[STRIP]   Stripping libgrpc++_cronet.a"
+	$(Q) $(STRIP) $(LIBDIR)/$(CONFIG)/libgrpc++_cronet.a
 	$(E) "[STRIP]   Stripping libgrpc++_reflection.a"
 	$(Q) $(STRIP) $(LIBDIR)/$(CONFIG)/libgrpc++_reflection.a
 	$(E) "[STRIP]   Stripping libgrpc++_unsecure.a"
@@ -1925,6 +1927,8 @@ strip-shared_cxx: shared_cxx
 ifeq ($(CONFIG),opt)
 	$(E) "[STRIP]   Stripping $(SHARED_PREFIX)grpc++$(SHARED_VERSION).$(SHARED_EXT)"
 	$(Q) $(STRIP) $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++$(SHARED_VERSION).$(SHARED_EXT)
+	$(E) "[STRIP]   Stripping $(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT)"
+	$(Q) $(STRIP) $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT)
 	$(E) "[STRIP]   Stripping $(SHARED_PREFIX)grpc++_reflection$(SHARED_VERSION).$(SHARED_EXT)"
 	$(Q) $(STRIP) $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++_reflection$(SHARED_VERSION).$(SHARED_EXT)
 	$(E) "[STRIP]   Stripping $(SHARED_PREFIX)grpc++_unsecure$(SHARED_VERSION).$(SHARED_EXT)"
@@ -2246,6 +2250,9 @@ install-static_cxx: static_cxx strip-static_cxx install-pkg-config_cxx
 	$(E) "[INSTALL] Installing libgrpc++.a"
 	$(Q) $(INSTALL) -d $(prefix)/lib
 	$(Q) $(INSTALL) $(LIBDIR)/$(CONFIG)/libgrpc++.a $(prefix)/lib/libgrpc++.a
+	$(E) "[INSTALL] Installing libgrpc++_cronet.a"
+	$(Q) $(INSTALL) -d $(prefix)/lib
+	$(Q) $(INSTALL) $(LIBDIR)/$(CONFIG)/libgrpc++_cronet.a $(prefix)/lib/libgrpc++_cronet.a
 	$(E) "[INSTALL] Installing libgrpc++_reflection.a"
 	$(Q) $(INSTALL) -d $(prefix)/lib
 	$(Q) $(INSTALL) $(LIBDIR)/$(CONFIG)/libgrpc++_reflection.a $(prefix)/lib/libgrpc++_reflection.a
@@ -2308,6 +2315,15 @@ ifeq ($(SYSTEM),MINGW32)
 else ifneq ($(SYSTEM),Darwin)
 	$(Q) ln -sf $(SHARED_PREFIX)grpc++$(SHARED_VERSION).$(SHARED_EXT) $(prefix)/lib/libgrpc++.so.1
 	$(Q) ln -sf $(SHARED_PREFIX)grpc++$(SHARED_VERSION).$(SHARED_EXT) $(prefix)/lib/libgrpc++.so
+endif
+	$(E) "[INSTALL] Installing $(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT)"
+	$(Q) $(INSTALL) -d $(prefix)/lib
+	$(Q) $(INSTALL) $(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT) $(prefix)/lib/$(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT)
+ifeq ($(SYSTEM),MINGW32)
+	$(Q) $(INSTALL) $(LIBDIR)/$(CONFIG)/libgrpc++_cronet-imp.a $(prefix)/lib/libgrpc++_cronet-imp.a
+else ifneq ($(SYSTEM),Darwin)
+	$(Q) ln -sf $(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT) $(prefix)/lib/libgrpc++_cronet.so.1
+	$(Q) ln -sf $(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT) $(prefix)/lib/libgrpc++_cronet.so
 endif
 	$(E) "[INSTALL] Installing $(SHARED_PREFIX)grpc++_reflection$(SHARED_VERSION).$(SHARED_EXT)"
 	$(Q) $(INSTALL) -d $(prefix)/lib
@@ -3847,6 +3863,198 @@ endif
 endif
 
 
+LIBGRPC++_CRONET_SRC = \
+    src/cpp/client/cronet_credentials.cc \
+    src/cpp/client/insecure_credentials.cc \
+    src/cpp/common/insecure_create_auth_context.cc \
+    src/cpp/server/insecure_server_credentials.cc \
+    src/cpp/client/channel_cc.cc \
+    src/cpp/client/client_context.cc \
+    src/cpp/client/create_channel.cc \
+    src/cpp/client/create_channel_internal.cc \
+    src/cpp/client/create_channel_posix.cc \
+    src/cpp/client/credentials_cc.cc \
+    src/cpp/client/generic_stub.cc \
+    src/cpp/common/channel_arguments.cc \
+    src/cpp/common/channel_filter.cc \
+    src/cpp/common/completion_queue_cc.cc \
+    src/cpp/common/core_codegen.cc \
+    src/cpp/common/rpc_method.cc \
+    src/cpp/server/async_generic_service.cc \
+    src/cpp/server/create_default_thread_pool.cc \
+    src/cpp/server/dynamic_thread_pool.cc \
+    src/cpp/server/server_builder.cc \
+    src/cpp/server/server_cc.cc \
+    src/cpp/server/server_context.cc \
+    src/cpp/server/server_credentials.cc \
+    src/cpp/server/server_posix.cc \
+    src/cpp/util/byte_buffer_cc.cc \
+    src/cpp/util/slice_cc.cc \
+    src/cpp/util/status.cc \
+    src/cpp/util/string_ref.cc \
+    src/cpp/util/time_cc.cc \
+    src/cpp/codegen/codegen_init.cc \
+
+PUBLIC_HEADERS_CXX += \
+    include/grpc++/alarm.h \
+    include/grpc++/channel.h \
+    include/grpc++/client_context.h \
+    include/grpc++/completion_queue.h \
+    include/grpc++/create_channel.h \
+    include/grpc++/create_channel_posix.h \
+    include/grpc++/generic/async_generic_service.h \
+    include/grpc++/generic/generic_stub.h \
+    include/grpc++/grpc++.h \
+    include/grpc++/impl/call.h \
+    include/grpc++/impl/client_unary_call.h \
+    include/grpc++/impl/codegen/core_codegen.h \
+    include/grpc++/impl/grpc_library.h \
+    include/grpc++/impl/method_handler_impl.h \
+    include/grpc++/impl/rpc_method.h \
+    include/grpc++/impl/rpc_service_method.h \
+    include/grpc++/impl/serialization_traits.h \
+    include/grpc++/impl/server_builder_option.h \
+    include/grpc++/impl/server_builder_plugin.h \
+    include/grpc++/impl/server_initializer.h \
+    include/grpc++/impl/service_type.h \
+    include/grpc++/impl/sync.h \
+    include/grpc++/impl/sync_cxx11.h \
+    include/grpc++/impl/sync_no_cxx11.h \
+    include/grpc++/impl/thd.h \
+    include/grpc++/impl/thd_cxx11.h \
+    include/grpc++/impl/thd_no_cxx11.h \
+    include/grpc++/security/auth_context.h \
+    include/grpc++/security/auth_metadata_processor.h \
+    include/grpc++/security/credentials.h \
+    include/grpc++/security/server_credentials.h \
+    include/grpc++/server.h \
+    include/grpc++/server_builder.h \
+    include/grpc++/server_context.h \
+    include/grpc++/server_posix.h \
+    include/grpc++/support/async_stream.h \
+    include/grpc++/support/async_unary_call.h \
+    include/grpc++/support/byte_buffer.h \
+    include/grpc++/support/channel_arguments.h \
+    include/grpc++/support/config.h \
+    include/grpc++/support/slice.h \
+    include/grpc++/support/status.h \
+    include/grpc++/support/status_code_enum.h \
+    include/grpc++/support/string_ref.h \
+    include/grpc++/support/stub_options.h \
+    include/grpc++/support/sync_stream.h \
+    include/grpc++/support/time.h \
+    include/grpc++/impl/codegen/async_stream.h \
+    include/grpc++/impl/codegen/async_unary_call.h \
+    include/grpc++/impl/codegen/call.h \
+    include/grpc++/impl/codegen/call_hook.h \
+    include/grpc++/impl/codegen/channel_interface.h \
+    include/grpc++/impl/codegen/client_context.h \
+    include/grpc++/impl/codegen/client_unary_call.h \
+    include/grpc++/impl/codegen/completion_queue.h \
+    include/grpc++/impl/codegen/completion_queue_tag.h \
+    include/grpc++/impl/codegen/config.h \
+    include/grpc++/impl/codegen/core_codegen_interface.h \
+    include/grpc++/impl/codegen/create_auth_context.h \
+    include/grpc++/impl/codegen/grpc_library.h \
+    include/grpc++/impl/codegen/method_handler_impl.h \
+    include/grpc++/impl/codegen/rpc_method.h \
+    include/grpc++/impl/codegen/rpc_service_method.h \
+    include/grpc++/impl/codegen/security/auth_context.h \
+    include/grpc++/impl/codegen/serialization_traits.h \
+    include/grpc++/impl/codegen/server_context.h \
+    include/grpc++/impl/codegen/server_interface.h \
+    include/grpc++/impl/codegen/service_type.h \
+    include/grpc++/impl/codegen/status.h \
+    include/grpc++/impl/codegen/status_code_enum.h \
+    include/grpc++/impl/codegen/status_helper.h \
+    include/grpc++/impl/codegen/string_ref.h \
+    include/grpc++/impl/codegen/stub_options.h \
+    include/grpc++/impl/codegen/sync.h \
+    include/grpc++/impl/codegen/sync_cxx11.h \
+    include/grpc++/impl/codegen/sync_no_cxx11.h \
+    include/grpc++/impl/codegen/sync_stream.h \
+    include/grpc++/impl/codegen/time.h \
+    include/grpc/impl/codegen/byte_buffer_reader.h \
+    include/grpc/impl/codegen/compression_types.h \
+    include/grpc/impl/codegen/connectivity_state.h \
+    include/grpc/impl/codegen/grpc_types.h \
+    include/grpc/impl/codegen/propagation_bits.h \
+    include/grpc/impl/codegen/status.h \
+    include/grpc/impl/codegen/atm.h \
+    include/grpc/impl/codegen/atm_gcc_atomic.h \
+    include/grpc/impl/codegen/atm_gcc_sync.h \
+    include/grpc/impl/codegen/atm_windows.h \
+    include/grpc/impl/codegen/gpr_types.h \
+    include/grpc/impl/codegen/port_platform.h \
+    include/grpc/impl/codegen/slice.h \
+    include/grpc/impl/codegen/sync.h \
+    include/grpc/impl/codegen/sync_generic.h \
+    include/grpc/impl/codegen/sync_posix.h \
+    include/grpc/impl/codegen/sync_windows.h \
+
+LIBGRPC++_CRONET_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBGRPC++_CRONET_SRC))))
+
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure libraries if you don't have OpenSSL.
+
+$(LIBDIR)/$(CONFIG)/libgrpc++_cronet.a: openssl_dep_error
+
+$(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT): openssl_dep_error
+
+else
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build a C++ library if you don't have protobuf - a bit overreached, but still okay.
+
+$(LIBDIR)/$(CONFIG)/libgrpc++_cronet.a: protobuf_dep_error
+
+$(LIBDIR)/$(CONFIG)/$(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT): protobuf_dep_error
+
+else
+
+$(LIBDIR)/$(CONFIG)/libgrpc++_cronet.a: $(ZLIB_DEP) $(OPENSSL_DEP) $(PROTOBUF_DEP) $(LIBGRPC++_CRONET_OBJS)  $(LIBGPR_OBJS)  $(ZLIB_MERGE_OBJS)  $(OPENSSL_MERGE_OBJS) 
+	$(E) "[AR]      Creating $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) rm -f $(LIBDIR)/$(CONFIG)/libgrpc++_cronet.a
+	$(Q) $(AR) $(AROPTS) $(LIBDIR)/$(CONFIG)/libgrpc++_cronet.a $(LIBGRPC++_CRONET_OBJS)  $(LIBGPR_OBJS)  $(ZLIB_MERGE_OBJS)  $(OPENSSL_MERGE_OBJS) 
+ifeq ($(SYSTEM),Darwin)
+	$(Q) ranlib -no_warning_for_no_symbols $(LIBDIR)/$(CONFIG)/libgrpc++_cronet.a
+endif
+
+
+
+ifeq ($(SYSTEM),MINGW32)
+$(LIBDIR)/$(CONFIG)/grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT): $(LIBGRPC++_CRONET_OBJS)  $(ZLIB_DEP) $(PROTOBUF_DEP) $(LIBDIR)/$(CONFIG)/gpr.$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/grpc_cronet.$(SHARED_EXT) $(OPENSSL_DEP)
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) -L$(LIBDIR)/$(CONFIG) -shared grpc++_cronet.def -Wl,--output-def=$(LIBDIR)/$(CONFIG)/grpc++_cronet$(SHARED_VERSION).def -Wl,--out-implib=$(LIBDIR)/$(CONFIG)/libgrpc++_cronet$(SHARED_VERSION)-dll.a -o $(LIBDIR)/$(CONFIG)/grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT) $(LIBGRPC++_CRONET_OBJS) $(LDLIBS) $(OPENSSL_MERGE_LIBS) $(LDLIBS_SECURE) $(ZLIB_MERGE_LIBS) $(LDLIBSXX) $(LDLIBS_PROTOBUF) -lgpr-imp -lgrpc_cronet-imp
+else
+$(LIBDIR)/$(CONFIG)/libgrpc++_cronet$(SHARED_VERSION).$(SHARED_EXT): $(LIBGRPC++_CRONET_OBJS)  $(ZLIB_DEP) $(PROTOBUF_DEP) $(LIBDIR)/$(CONFIG)/libgpr.$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/libgrpc_cronet.$(SHARED_EXT) $(OPENSSL_DEP)
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+ifeq ($(SYSTEM),Darwin)
+	$(Q) $(LDXX) $(LDFLAGS) -L$(LIBDIR)/$(CONFIG) -install_name $(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT) -dynamiclib -o $(LIBDIR)/$(CONFIG)/libgrpc++_cronet$(SHARED_VERSION).$(SHARED_EXT) $(LIBGRPC++_CRONET_OBJS) $(LDLIBS) $(OPENSSL_MERGE_LIBS) $(LDLIBS_SECURE) $(ZLIB_MERGE_LIBS) $(LDLIBSXX) $(LDLIBS_PROTOBUF) -lgpr -lgrpc_cronet
+else
+	$(Q) $(LDXX) $(LDFLAGS) -L$(LIBDIR)/$(CONFIG) -shared -Wl,-soname,libgrpc++_cronet.so.1 -o $(LIBDIR)/$(CONFIG)/libgrpc++_cronet$(SHARED_VERSION).$(SHARED_EXT) $(LIBGRPC++_CRONET_OBJS) $(LDLIBS) $(OPENSSL_MERGE_LIBS) $(LDLIBS_SECURE) $(ZLIB_MERGE_LIBS) $(LDLIBSXX) $(LDLIBS_PROTOBUF) -lgpr -lgrpc_cronet
+	$(Q) ln -sf $(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/libgrpc++_cronet$(SHARED_VERSION).so.1
+	$(Q) ln -sf $(SHARED_PREFIX)grpc++_cronet$(SHARED_VERSION).$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/libgrpc++_cronet$(SHARED_VERSION).so
+endif
+endif
+
+endif
+
+endif
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(LIBGRPC++_CRONET_OBJS:.o=.dep)
+endif
+endif
+
+
 LIBGRPC++_REFLECTION_SRC = \
     src/cpp/ext/proto_server_reflection.cc \
     src/cpp/ext/proto_server_reflection_plugin.cc \
@@ -12260,16 +12468,16 @@ $(BINDIR)/$(CONFIG)/interop_test: protobuf_dep_error
 
 else
 
-$(BINDIR)/$(CONFIG)/interop_test: $(PROTOBUF_DEP) $(INTEROP_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+$(BINDIR)/$(CONFIG)/interop_test: $(PROTOBUF_DEP) $(INTEROP_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
 	$(E) "[LD]      Linking $@"
 	$(Q) mkdir -p `dirname $@`
-	$(Q) $(LDXX) $(LDFLAGS) $(INTEROP_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/interop_test
+	$(Q) $(LDXX) $(LDFLAGS) $(INTEROP_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/interop_test
 
 endif
 
 endif
 
-$(OBJDIR)/$(CONFIG)/test/cpp/interop/interop_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+$(OBJDIR)/$(CONFIG)/test/cpp/interop/interop_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
 
 deps_interop_test: $(INTEROP_TEST_OBJS:.o=.dep)
 
@@ -13129,6 +13337,7 @@ STRESS_TEST_SRC = \
     $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc \
     $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc \
     $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc \
+    test/cpp/interop/client_helper.cc \
     test/cpp/interop/interop_client.cc \
     test/cpp/interop/stress_interop_client.cc \
     test/cpp/interop/stress_test.cc \
@@ -13171,6 +13380,8 @@ $(OBJDIR)/$(CONFIG)/src/proto/grpc/testing/metrics.o:  $(LIBDIR)/$(CONFIG)/libgr
 
 $(OBJDIR)/$(CONFIG)/src/proto/grpc/testing/test.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
 
+$(OBJDIR)/$(CONFIG)/test/cpp/interop/client_helper.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
+
 $(OBJDIR)/$(CONFIG)/test/cpp/interop/interop_client.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
 
 $(OBJDIR)/$(CONFIG)/test/cpp/interop/stress_interop_client.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
@@ -13186,6 +13397,7 @@ ifneq ($(NO_DEPS),true)
 -include $(STRESS_TEST_OBJS:.o=.dep)
 endif
 endif
+$(OBJDIR)/$(CONFIG)/test/cpp/interop/client_helper.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc
 $(OBJDIR)/$(CONFIG)/test/cpp/interop/interop_client.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc
 $(OBJDIR)/$(CONFIG)/test/cpp/interop/stress_interop_client.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc
 $(OBJDIR)/$(CONFIG)/test/cpp/interop/stress_test.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc
@@ -15911,6 +16123,7 @@ src/core/lib/tsi/ssl_transport_security.c: $(OPENSSL_DEP)
 src/core/lib/tsi/transport_security.c: $(OPENSSL_DEP)
 src/core/plugin_registry/grpc_cronet_plugin_registry.c: $(OPENSSL_DEP)
 src/core/plugin_registry/grpc_plugin_registry.c: $(OPENSSL_DEP)
+src/cpp/client/cronet_credentials.cc: $(OPENSSL_DEP)
 src/cpp/client/secure_credentials.cc: $(OPENSSL_DEP)
 src/cpp/common/auth_property_iterator.cc: $(OPENSSL_DEP)
 src/cpp/common/secure_auth_context.cc: $(OPENSSL_DEP)
diff --git a/build.yaml b/build.yaml
index 8df825a52df068733dcda323dc5227d55eb604c3..4332c432a09405328e58202c34f724db63a6af2b 100644
--- a/build.yaml
+++ b/build.yaml
@@ -1028,6 +1028,26 @@ libs:
   - grpc++_codegen_base_src
   secure: check
   vs_project_guid: '{C187A093-A0FE-489D-A40A-6E33DE0F9FEB}'
+- name: grpc++_cronet
+  build: all
+  language: c++
+  src:
+  - src/cpp/client/cronet_credentials.cc
+  - src/cpp/client/insecure_credentials.cc
+  - src/cpp/common/insecure_create_auth_context.cc
+  - src/cpp/server/insecure_server_credentials.cc
+  deps:
+  - gpr
+  - grpc_cronet
+  baselib: true
+  dll: true
+  filegroups:
+  - grpc++_base
+  - grpc++_codegen_base
+  - grpc++_codegen_base_src
+  platforms:
+  - linux
+  secure: true
 - name: grpc++_reflection
   build: all
   language: c++
@@ -3106,6 +3126,7 @@ targets:
   - grpc
   - gpr_test_util
   - gpr
+  - grpc++_test_config
   platforms:
   - mac
   - linux
@@ -3416,6 +3437,7 @@ targets:
   - src/proto/grpc/testing/messages.proto
   - src/proto/grpc/testing/metrics.proto
   - src/proto/grpc/testing/test.proto
+  - test/cpp/interop/client_helper.cc
   - test/cpp/interop/interop_client.cc
   - test/cpp/interop/stress_interop_client.cc
   - test/cpp/interop/stress_test.cc
diff --git a/doc/interop-test-descriptions.md b/doc/interop-test-descriptions.md
index 7c153fa3fa4cf8592ac11c79d18a6e12936175e4..96d1f2bd7526c3421e867ed94ba9986a10f2b358 100644
--- a/doc/interop-test-descriptions.md
+++ b/doc/interop-test-descriptions.md
@@ -779,21 +779,21 @@ Client asserts:
 
 ### unimplemented_method
 
-This test verifies that calling an unimplemented RPC method returns the 
+This test verifies that calling an unimplemented RPC method returns the
 UNIMPLEMENTED status code.
 
 Server features:
 N/A
 
 Procedure:
-* Client calls `grpc.testing.TestService/UnimplementedMethod` with an empty
+* Client calls `grpc.testing.TestService/UnimplementedCall` with an empty
   request (defined as `grpc.testing.Empty`):
 
     ```
     {
     }
     ```
-   
+
 Client asserts:
 * received status code is 12 (UNIMPLEMENTED)
 
diff --git a/include/grpc++/security/credentials.h b/include/grpc++/security/credentials.h
index 5439244b02363568b71f66f4d0fb517d32b06b01..59861b78d86cfb6ee9c97dafee58c44da4f4ee41 100644
--- a/include/grpc++/security/credentials.h
+++ b/include/grpc++/security/credentials.h
@@ -201,6 +201,9 @@ std::shared_ptr<CallCredentials> CompositeCallCredentials(
 /// Credentials for an unencrypted, unauthenticated channel
 std::shared_ptr<ChannelCredentials> InsecureChannelCredentials();
 
+/// Credentials for a channel using Cronet.
+std::shared_ptr<ChannelCredentials> CronetChannelCredentials(void* engine);
+
 // User defined metadata credentials.
 class MetadataCredentialsPlugin {
  public:
diff --git a/src/core/ext/client_channel/client_channel.c b/src/core/ext/client_channel/client_channel.c
index b61a0fb24bdc4e2b01b022372d594e4929f53094..55bb877576f7e4f5eb9d4663edbc15b241bb8e43 100644
--- a/src/core/ext/client_channel/client_channel.c
+++ b/src/core/ext/client_channel/client_channel.c
@@ -60,6 +60,54 @@
 
 /* Client channel implementation */
 
+/*************************************************************************
+ * METHOD-CONFIG TABLE
+ */
+
+typedef enum {
+  WAIT_FOR_READY_UNSET,
+  WAIT_FOR_READY_FALSE,
+  WAIT_FOR_READY_TRUE
+} wait_for_ready_value;
+
+typedef struct method_parameters {
+  gpr_timespec timeout;
+  wait_for_ready_value wait_for_ready;
+} method_parameters;
+
+static void *method_parameters_copy(void *value) {
+  void *new_value = gpr_malloc(sizeof(method_parameters));
+  memcpy(new_value, value, sizeof(method_parameters));
+  return new_value;
+}
+
+static int method_parameters_cmp(void *value1, void *value2) {
+  const method_parameters *v1 = value1;
+  const method_parameters *v2 = value2;
+  const int retval = gpr_time_cmp(v1->timeout, v2->timeout);
+  if (retval != 0) return retval;
+  if (v1->wait_for_ready > v2->wait_for_ready) return 1;
+  if (v1->wait_for_ready < v2->wait_for_ready) return -1;
+  return 0;
+}
+
+static const grpc_mdstr_hash_table_vtable method_parameters_vtable = {
+    gpr_free, method_parameters_copy, method_parameters_cmp};
+
+static void *method_config_convert_value(
+    const grpc_method_config *method_config) {
+  method_parameters *value = gpr_malloc(sizeof(method_parameters));
+  const gpr_timespec *timeout = grpc_method_config_get_timeout(method_config);
+  value->timeout = timeout != NULL ? *timeout : gpr_time_0(GPR_TIMESPAN);
+  const bool *wait_for_ready =
+      grpc_method_config_get_wait_for_ready(method_config);
+  value->wait_for_ready =
+      wait_for_ready == NULL
+          ? WAIT_FOR_READY_UNSET
+          : (wait_for_ready ? WAIT_FOR_READY_TRUE : WAIT_FOR_READY_FALSE);
+  return value;
+}
+
 /*************************************************************************
  * CHANNEL-WIDE FUNCTIONS
  */
@@ -76,8 +124,8 @@ typedef struct client_channel_channel_data {
   gpr_mu mu;
   /** currently active load balancer */
   grpc_lb_policy *lb_policy;
-  /** method config table */
-  grpc_method_config_table *method_config_table;
+  /** maps method names to method_parameters structs */
+  grpc_mdstr_hash_table *method_params_table;
   /** incoming resolver result - set by resolver.next() */
   grpc_resolver_result *resolver_result;
   /** a list of closures that are all waiting for config to come in */
@@ -177,7 +225,7 @@ static void on_resolver_result_changed(grpc_exec_ctx *exec_ctx, void *arg,
   channel_data *chand = arg;
   grpc_lb_policy *lb_policy = NULL;
   grpc_lb_policy *old_lb_policy;
-  grpc_method_config_table *method_config_table = NULL;
+  grpc_mdstr_hash_table *method_params_table = NULL;
   grpc_connectivity_state state = GRPC_CHANNEL_TRANSIENT_FAILURE;
   bool exit_idle = false;
   grpc_error *state_error = GRPC_ERROR_CREATE("No load balancing policy");
@@ -230,8 +278,9 @@ static void on_resolver_result_changed(grpc_exec_ctx *exec_ctx, void *arg,
         lb_policy_args.additional_args, GRPC_ARG_SERVICE_CONFIG);
     if (channel_arg != NULL) {
       GPR_ASSERT(channel_arg->type == GRPC_ARG_POINTER);
-      method_config_table = grpc_method_config_table_ref(
-          (grpc_method_config_table *)channel_arg->value.pointer.p);
+      method_params_table = grpc_method_config_table_convert(
+          (grpc_method_config_table *)channel_arg->value.pointer.p,
+          method_config_convert_value, &method_parameters_vtable);
     }
     grpc_resolver_result_unref(exec_ctx, chand->resolver_result);
     chand->resolver_result = NULL;
@@ -245,10 +294,10 @@ static void on_resolver_result_changed(grpc_exec_ctx *exec_ctx, void *arg,
   gpr_mu_lock(&chand->mu);
   old_lb_policy = chand->lb_policy;
   chand->lb_policy = lb_policy;
-  if (chand->method_config_table != NULL) {
-    grpc_method_config_table_unref(chand->method_config_table);
+  if (chand->method_params_table != NULL) {
+    grpc_mdstr_hash_table_unref(chand->method_params_table);
   }
-  chand->method_config_table = method_config_table;
+  chand->method_params_table = method_params_table;
   if (lb_policy != NULL) {
     grpc_exec_ctx_enqueue_list(exec_ctx, &chand->waiting_for_config_closures,
                                NULL);
@@ -409,8 +458,8 @@ static void cc_destroy_channel_elem(grpc_exec_ctx *exec_ctx,
                                      chand->interested_parties);
     GRPC_LB_POLICY_UNREF(exec_ctx, chand->lb_policy, "channel");
   }
-  if (chand->method_config_table != NULL) {
-    grpc_method_config_table_unref(chand->method_config_table);
+  if (chand->method_params_table != NULL) {
+    grpc_mdstr_hash_table_unref(chand->method_params_table);
   }
   grpc_connectivity_state_destroy(exec_ctx, &chand->state_tracker);
   grpc_pollset_set_destroy(chand->interested_parties);
@@ -448,11 +497,7 @@ typedef struct client_channel_call_data {
   grpc_mdstr *path;  // Request path.
   gpr_timespec call_start_time;
   gpr_timespec deadline;
-  enum {
-    WAIT_FOR_READY_UNSET,
-    WAIT_FOR_READY_FALSE,
-    WAIT_FOR_READY_TRUE
-  } wait_for_ready_from_service_config;
+  wait_for_ready_value wait_for_ready_from_service_config;
   grpc_closure read_service_config;
 
   grpc_error *cancel_error;
@@ -846,41 +891,39 @@ static void read_service_config(grpc_exec_ctx *exec_ctx, void *arg,
   if (error == GRPC_ERROR_NONE) {
     // Get the method config table from channel data.
     gpr_mu_lock(&chand->mu);
-    grpc_method_config_table *method_config_table = NULL;
-    if (chand->method_config_table != NULL) {
-      method_config_table =
-          grpc_method_config_table_ref(chand->method_config_table);
+    grpc_mdstr_hash_table *method_params_table = NULL;
+    if (chand->method_params_table != NULL) {
+      method_params_table =
+          grpc_mdstr_hash_table_ref(chand->method_params_table);
     }
     gpr_mu_unlock(&chand->mu);
     // If the method config table was present, use it.
-    if (method_config_table != NULL) {
-      const grpc_method_config *method_config =
-          grpc_method_config_table_get_method_config(method_config_table,
-                                                     calld->path);
-      if (method_config != NULL) {
-        const gpr_timespec *per_method_timeout =
-            grpc_method_config_get_timeout(method_config);
-        const bool *wait_for_ready =
-            grpc_method_config_get_wait_for_ready(method_config);
-        if (per_method_timeout != NULL || wait_for_ready != NULL) {
+    if (method_params_table != NULL) {
+      const method_parameters *method_params =
+          grpc_method_config_table_get(method_params_table, calld->path);
+      if (method_params != NULL) {
+        const bool have_method_timeout =
+            gpr_time_cmp(method_params->timeout, gpr_time_0(GPR_TIMESPAN)) != 0;
+        if (have_method_timeout ||
+            method_params->wait_for_ready != WAIT_FOR_READY_UNSET) {
           gpr_mu_lock(&calld->mu);
-          if (per_method_timeout != NULL) {
-            gpr_timespec per_method_deadline =
-                gpr_time_add(calld->call_start_time, *per_method_timeout);
+          if (have_method_timeout) {
+            const gpr_timespec per_method_deadline =
+                gpr_time_add(calld->call_start_time, method_params->timeout);
             if (gpr_time_cmp(per_method_deadline, calld->deadline) < 0) {
               calld->deadline = per_method_deadline;
               // Reset deadline timer.
               grpc_deadline_state_reset(exec_ctx, elem, calld->deadline);
             }
           }
-          if (wait_for_ready != NULL) {
+          if (method_params->wait_for_ready != WAIT_FOR_READY_UNSET) {
             calld->wait_for_ready_from_service_config =
-                *wait_for_ready ? WAIT_FOR_READY_TRUE : WAIT_FOR_READY_FALSE;
+                method_params->wait_for_ready;
           }
           gpr_mu_unlock(&calld->mu);
         }
       }
-      grpc_method_config_table_unref(method_config_table);
+      grpc_mdstr_hash_table_unref(method_params_table);
     }
   }
   GRPC_CALL_STACK_UNREF(exec_ctx, calld->owning_call, "read_service_config");
@@ -917,29 +960,25 @@ static grpc_error *cc_init_call_elem(grpc_exec_ctx *exec_ctx,
   gpr_mu_lock(&chand->mu);
   if (chand->lb_policy != NULL) {
     // We already have a resolver result, so check for service config.
-    if (chand->method_config_table != NULL) {
-      grpc_method_config_table *method_config_table =
-          grpc_method_config_table_ref(chand->method_config_table);
+    if (chand->method_params_table != NULL) {
+      grpc_mdstr_hash_table *method_params_table =
+          grpc_mdstr_hash_table_ref(chand->method_params_table);
       gpr_mu_unlock(&chand->mu);
-      grpc_method_config *method_config =
-          grpc_method_config_table_get_method_config(method_config_table,
-                                                     args->path);
-      if (method_config != NULL) {
-        const gpr_timespec *per_method_timeout =
-            grpc_method_config_get_timeout(method_config);
-        if (per_method_timeout != NULL) {
+      method_parameters *method_params =
+          grpc_method_config_table_get(method_params_table, args->path);
+      if (method_params != NULL) {
+        if (gpr_time_cmp(method_params->timeout,
+                         gpr_time_0(GPR_CLOCK_MONOTONIC)) != 0) {
           gpr_timespec per_method_deadline =
-              gpr_time_add(calld->call_start_time, *per_method_timeout);
+              gpr_time_add(calld->call_start_time, method_params->timeout);
           calld->deadline = gpr_time_min(calld->deadline, per_method_deadline);
         }
-        const bool *wait_for_ready =
-            grpc_method_config_get_wait_for_ready(method_config);
-        if (wait_for_ready != NULL) {
+        if (method_params->wait_for_ready != WAIT_FOR_READY_UNSET) {
           calld->wait_for_ready_from_service_config =
-              *wait_for_ready ? WAIT_FOR_READY_TRUE : WAIT_FOR_READY_FALSE;
+              method_params->wait_for_ready;
         }
       }
-      grpc_method_config_table_unref(method_config_table);
+      grpc_mdstr_hash_table_unref(method_params_table);
     } else {
       gpr_mu_unlock(&chand->mu);
     }
diff --git a/src/core/ext/client_channel/method_config.c b/src/core/ext/client_channel/method_config.c
index 49d1ad344e6f391c65eaf5932feaf9d4edfbf58f..4313ad5e0e6599ad625d8955d31fba6b15a5d122 100644
--- a/src/core/ext/client_channel/method_config.c
+++ b/src/core/ext/client_channel/method_config.c
@@ -254,12 +254,12 @@ int grpc_method_config_table_cmp(const grpc_method_config_table* table1,
   return grpc_mdstr_hash_table_cmp(table1, table2);
 }
 
-grpc_method_config* grpc_method_config_table_get_method_config(
-    const grpc_method_config_table* table, const grpc_mdstr* path) {
-  grpc_method_config* method_config = grpc_mdstr_hash_table_get(table, path);
+void* grpc_method_config_table_get(const grpc_mdstr_hash_table* table,
+                                   const grpc_mdstr* path) {
+  void* value = grpc_mdstr_hash_table_get(table, path);
   // If we didn't find a match for the path, try looking for a wildcard
   // entry (i.e., change "/service/method" to "/service/*").
-  if (method_config == NULL) {
+  if (value == NULL) {
     const char* path_str = grpc_mdstr_as_c_string(path);
     const char* sep = strrchr(path_str, '/') + 1;
     const size_t len = (size_t)(sep - path_str);
@@ -269,10 +269,10 @@ grpc_method_config* grpc_method_config_table_get_method_config(
     buf[len + 1] = '\0';
     grpc_mdstr* wildcard_path = grpc_mdstr_from_string(buf);
     gpr_free(buf);
-    method_config = grpc_mdstr_hash_table_get(table, wildcard_path);
+    value = grpc_mdstr_hash_table_get(table, wildcard_path);
     GRPC_MDSTR_UNREF(wildcard_path);
   }
-  return method_config;
+  return value;
 }
 
 static void* copy_arg(void* p) { return grpc_method_config_table_ref(p); }
@@ -294,3 +294,47 @@ grpc_arg grpc_method_config_table_create_channel_arg(
   arg.value.pointer.vtable = &arg_vtable;
   return arg;
 }
+
+// State used by convert_entry() below.
+typedef struct conversion_state {
+  void* (*convert_value)(const grpc_method_config* method_config);
+  const grpc_mdstr_hash_table_vtable* vtable;
+  size_t num_entries;
+  grpc_mdstr_hash_table_entry* entries;
+} conversion_state;
+
+// A function to be passed to grpc_mdstr_hash_table_iterate() to create
+// a copy of the entries.
+static void convert_entry(const grpc_mdstr_hash_table_entry* entry,
+                          void* user_data) {
+  conversion_state* state = user_data;
+  state->entries[state->num_entries].key = GRPC_MDSTR_REF(entry->key);
+  state->entries[state->num_entries].value = state->convert_value(entry->value);
+  state->entries[state->num_entries].vtable = state->vtable;
+  ++state->num_entries;
+}
+
+grpc_mdstr_hash_table* grpc_method_config_table_convert(
+    const grpc_method_config_table* table,
+    void* (*convert_value)(const grpc_method_config* method_config),
+    const grpc_mdstr_hash_table_vtable* vtable) {
+  // Create an array of the entries in the table with converted values.
+  conversion_state state;
+  state.convert_value = convert_value;
+  state.vtable = vtable;
+  state.num_entries = 0;
+  state.entries = gpr_malloc(sizeof(grpc_mdstr_hash_table_entry) *
+                             grpc_mdstr_hash_table_num_entries(table));
+  grpc_mdstr_hash_table_iterate(table, convert_entry, &state);
+  // Create a new table based on the array we just constructed.
+  grpc_mdstr_hash_table* new_table =
+      grpc_mdstr_hash_table_create(state.num_entries, state.entries);
+  // Clean up the array.
+  for (size_t i = 0; i < state.num_entries; ++i) {
+    GRPC_MDSTR_UNREF(state.entries[i].key);
+    vtable->destroy_value(state.entries[i].value);
+  }
+  gpr_free(state.entries);
+  // Return the new table.
+  return new_table;
+}
diff --git a/src/core/ext/client_channel/method_config.h b/src/core/ext/client_channel/method_config.h
index 75b32fcb170c71f80b9c2bd4aff0a10bfc2738b3..4cbeee562572aff47ecba5e4e5979cda23f6b8c1 100644
--- a/src/core/ext/client_channel/method_config.h
+++ b/src/core/ext/client_channel/method_config.h
@@ -106,11 +106,31 @@ int grpc_method_config_table_cmp(const grpc_method_config_table* table1,
 /// the form "/service/method".
 /// Returns NULL if the method has no config.
 /// Caller does NOT own a reference to the result.
-grpc_method_config* grpc_method_config_table_get_method_config(
-    const grpc_method_config_table* table, const grpc_mdstr* path);
+///
+/// Note: This returns a void* instead of a grpc_method_config* so that
+/// it can also be used for tables constructed via
+/// grpc_method_config_table_convert().
+void* grpc_method_config_table_get(const grpc_mdstr_hash_table* table,
+                                   const grpc_mdstr* path);
 
 /// Returns a channel arg containing \a table.
 grpc_arg grpc_method_config_table_create_channel_arg(
     grpc_method_config_table* table);
 
+/// Generates a new table from \a table whose values are converted to a
+/// new form via the \a convert_value function.  The new table will use
+/// \a vtable for its values.
+///
+/// This is generally used to convert the table's value type from
+/// grpc_method_config to a simple struct containing only the parameters
+/// relevant to a particular filter, thus avoiding the need for a hash
+/// table lookup on the fast path.  In that scenario, \a convert_value
+/// will return a new instance of the struct containing the values from
+/// the grpc_method_config, and \a vtable provides the methods for
+/// operating on the struct type.
+grpc_mdstr_hash_table* grpc_method_config_table_convert(
+    const grpc_method_config_table* table,
+    void* (*convert_value)(const grpc_method_config* method_config),
+    const grpc_mdstr_hash_table_vtable* vtable);
+
 #endif /* GRPC_CORE_EXT_CLIENT_CHANNEL_METHOD_CONFIG_H */
diff --git a/src/core/ext/transport/chttp2/transport/chttp2_transport.c b/src/core/ext/transport/chttp2/transport/chttp2_transport.c
index 97780d90f25416b8d784478e5d96aa3deb4c135f..ecf3aea870178b0e473c2c741acab1550fa8635e 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_transport.c
+++ b/src/core/ext/transport/chttp2/transport/chttp2_transport.c
@@ -853,53 +853,51 @@ static bool contains_non_ok_status(grpc_metadata_batch *batch) {
 
 static void add_fetched_slice_locked(grpc_exec_ctx *exec_ctx,
                                      grpc_chttp2_transport *t,
-                                     grpc_chttp2_stream *s);
+                                     grpc_chttp2_stream *s) {
+  s->fetched_send_message_length +=
+      (uint32_t)GPR_SLICE_LENGTH(s->fetching_slice);
+  gpr_slice_buffer_add(&s->flow_controlled_buffer, s->fetching_slice);
+  if (s->id != 0) {
+    grpc_chttp2_become_writable(exec_ctx, t, s, true, "op.send_message");
+  }
+}
 
 static void continue_fetching_send_locked(grpc_exec_ctx *exec_ctx,
                                           grpc_chttp2_transport *t,
                                           grpc_chttp2_stream *s) {
-  if (s->fetching_send_message == NULL) {
-    /* Stream was cancelled before message fetch completed */
-    abort(); /* TODO(ctiller): what cleanup here? */
-    return;
-  }
-  if (s->fetched_send_message_length == s->fetching_send_message->length) {
-    int64_t notify_offset = s->next_message_end_offset;
-    if (notify_offset <= s->flow_controlled_bytes_written) {
-      grpc_chttp2_complete_closure_step(
-          exec_ctx, t, s, &s->fetching_send_message_finished, GRPC_ERROR_NONE,
-          "fetching_send_message_finished");
-    } else {
-      grpc_chttp2_write_cb *cb = t->write_cb_pool;
-      if (cb == NULL) {
-        cb = gpr_malloc(sizeof(*cb));
+  for (;;) {
+    if (s->fetching_send_message == NULL) {
+      /* Stream was cancelled before message fetch completed */
+      abort(); /* TODO(ctiller): what cleanup here? */
+      return;  /* early out */
+    }
+    if (s->fetched_send_message_length == s->fetching_send_message->length) {
+      int64_t notify_offset = s->next_message_end_offset;
+      if (notify_offset <= s->flow_controlled_bytes_written) {
+        grpc_chttp2_complete_closure_step(
+            exec_ctx, t, s, &s->fetching_send_message_finished, GRPC_ERROR_NONE,
+            "fetching_send_message_finished");
       } else {
-        t->write_cb_pool = cb->next;
+        grpc_chttp2_write_cb *cb = t->write_cb_pool;
+        if (cb == NULL) {
+          cb = gpr_malloc(sizeof(*cb));
+        } else {
+          t->write_cb_pool = cb->next;
+        }
+        cb->call_at_byte = notify_offset;
+        cb->closure = s->fetching_send_message_finished;
+        s->fetching_send_message_finished = NULL;
+        cb->next = s->on_write_finished_cbs;
+        s->on_write_finished_cbs = cb;
       }
-      cb->call_at_byte = notify_offset;
-      cb->closure = s->fetching_send_message_finished;
-      s->fetching_send_message_finished = NULL;
-      cb->next = s->on_write_finished_cbs;
-      s->on_write_finished_cbs = cb;
+      s->fetching_send_message = NULL;
+      return; /* early out */
+    } else if (grpc_byte_stream_next(exec_ctx, s->fetching_send_message,
+                                     &s->fetching_slice, UINT32_MAX,
+                                     &s->complete_fetch)) {
+      add_fetched_slice_locked(exec_ctx, t, s);
     }
-    s->fetching_send_message = NULL;
-  } else if (grpc_byte_stream_next(exec_ctx, s->fetching_send_message,
-                                   &s->fetching_slice, UINT32_MAX,
-                                   &s->complete_fetch)) {
-    add_fetched_slice_locked(exec_ctx, t, s);
-  }
-}
-
-static void add_fetched_slice_locked(grpc_exec_ctx *exec_ctx,
-                                     grpc_chttp2_transport *t,
-                                     grpc_chttp2_stream *s) {
-  s->fetched_send_message_length +=
-      (uint32_t)GPR_SLICE_LENGTH(s->fetching_slice);
-  gpr_slice_buffer_add(&s->flow_controlled_buffer, s->fetching_slice);
-  if (s->id != 0) {
-    grpc_chttp2_become_writable(exec_ctx, t, s, true, "op.send_message");
   }
-  continue_fetching_send_locked(exec_ctx, t, s);
 }
 
 static void complete_fetch_locked(grpc_exec_ctx *exec_ctx, void *gs,
@@ -908,6 +906,7 @@ static void complete_fetch_locked(grpc_exec_ctx *exec_ctx, void *gs,
   grpc_chttp2_transport *t = s->t;
   if (error == GRPC_ERROR_NONE) {
     add_fetched_slice_locked(exec_ctx, t, s);
+    continue_fetching_send_locked(exec_ctx, t, s);
   } else {
     /* TODO(ctiller): what to do here */
     abort();
diff --git a/src/core/lib/channel/message_size_filter.c b/src/core/lib/channel/message_size_filter.c
index 69fb3819ffbc838f371df442df61ba7ae84410b5..b8b25460356c3a5fd3be55acf7fd857ada01c781 100644
--- a/src/core/lib/channel/message_size_filter.c
+++ b/src/core/lib/channel/message_size_filter.c
@@ -45,6 +45,44 @@
 // The protobuf library will (by default) start warning at 100 megs.
 #define DEFAULT_MAX_RECV_MESSAGE_LENGTH (4 * 1024 * 1024)
 
+typedef struct message_size_limits {
+  int max_send_size;
+  int max_recv_size;
+} message_size_limits;
+
+static void* message_size_limits_copy(void* value) {
+  void* new_value = gpr_malloc(sizeof(message_size_limits));
+  memcpy(new_value, value, sizeof(message_size_limits));
+  return new_value;
+}
+
+static int message_size_limits_cmp(void* value1, void* value2) {
+  const message_size_limits* v1 = value1;
+  const message_size_limits* v2 = value2;
+  if (v1->max_send_size > v2->max_send_size) return 1;
+  if (v1->max_send_size < v2->max_send_size) return -1;
+  if (v1->max_recv_size > v2->max_recv_size) return 1;
+  if (v1->max_recv_size < v2->max_recv_size) return -1;
+  return 0;
+}
+
+static const grpc_mdstr_hash_table_vtable message_size_limits_vtable = {
+    gpr_free, message_size_limits_copy, message_size_limits_cmp};
+
+static void* method_config_convert_value(
+    const grpc_method_config* method_config) {
+  message_size_limits* value = gpr_malloc(sizeof(message_size_limits));
+  const int32_t* max_request_message_bytes =
+      grpc_method_config_get_max_request_message_bytes(method_config);
+  value->max_send_size =
+      max_request_message_bytes != NULL ? *max_request_message_bytes : -1;
+  const int32_t* max_response_message_bytes =
+      grpc_method_config_get_max_response_message_bytes(method_config);
+  value->max_recv_size =
+      max_response_message_bytes != NULL ? *max_response_message_bytes : -1;
+  return value;
+}
+
 typedef struct call_data {
   int max_send_size;
   int max_recv_size;
@@ -61,8 +99,8 @@ typedef struct call_data {
 typedef struct channel_data {
   int max_send_size;
   int max_recv_size;
-  // Method config table.
-  grpc_method_config_table* method_config_table;
+  // Maps path names to message_size_limits structs.
+  grpc_mdstr_hash_table* method_limit_table;
 } channel_data;
 
 // Callback invoked when we receive a message.  Here we check the max
@@ -132,24 +170,19 @@ static grpc_error* init_call_elem(grpc_exec_ctx* exec_ctx,
   // size to the receive limit.
   calld->max_send_size = chand->max_send_size;
   calld->max_recv_size = chand->max_recv_size;
-  if (chand->method_config_table != NULL) {
-    grpc_method_config* method_config =
-        grpc_method_config_table_get_method_config(chand->method_config_table,
-                                                   args->path);
-    if (method_config != NULL) {
-      const int32_t* max_request_message_bytes =
-          grpc_method_config_get_max_request_message_bytes(method_config);
-      if (max_request_message_bytes != NULL &&
-          (*max_request_message_bytes < calld->max_send_size ||
+  if (chand->method_limit_table != NULL) {
+    message_size_limits* limits =
+        grpc_method_config_table_get(chand->method_limit_table, args->path);
+    if (limits != NULL) {
+      if (limits->max_send_size >= 0 &&
+          (limits->max_send_size < calld->max_send_size ||
            calld->max_send_size < 0)) {
-        calld->max_send_size = *max_request_message_bytes;
+        calld->max_send_size = limits->max_send_size;
       }
-      const int32_t* max_response_message_bytes =
-          grpc_method_config_get_max_response_message_bytes(method_config);
-      if (max_response_message_bytes != NULL &&
-          (*max_response_message_bytes < calld->max_recv_size ||
+      if (limits->max_recv_size >= 0 &&
+          (limits->max_recv_size < calld->max_recv_size ||
            calld->max_recv_size < 0)) {
-        calld->max_recv_size = *max_response_message_bytes;
+        calld->max_recv_size = limits->max_recv_size;
       }
     }
   }
@@ -191,8 +224,9 @@ static void init_channel_elem(grpc_exec_ctx* exec_ctx,
       grpc_channel_args_find(args->channel_args, GRPC_ARG_SERVICE_CONFIG);
   if (channel_arg != NULL) {
     GPR_ASSERT(channel_arg->type == GRPC_ARG_POINTER);
-    chand->method_config_table = grpc_method_config_table_ref(
-        (grpc_method_config_table*)channel_arg->value.pointer.p);
+    chand->method_limit_table = grpc_method_config_table_convert(
+        (grpc_method_config_table*)channel_arg->value.pointer.p,
+        method_config_convert_value, &message_size_limits_vtable);
   }
 }
 
@@ -200,7 +234,7 @@ static void init_channel_elem(grpc_exec_ctx* exec_ctx,
 static void destroy_channel_elem(grpc_exec_ctx* exec_ctx,
                                  grpc_channel_element* elem) {
   channel_data* chand = elem->channel_data;
-  grpc_method_config_table_unref(chand->method_config_table);
+  grpc_mdstr_hash_table_unref(chand->method_limit_table);
 }
 
 const grpc_channel_filter grpc_message_size_filter = {
diff --git a/src/core/lib/iomgr/udp_server.c b/src/core/lib/iomgr/udp_server.c
index 0d31b255e979171bfa138f2682422c3c8ca8352c..a465c2815af4307a8bda197820cfe18cfeb73298 100644
--- a/src/core/lib/iomgr/udp_server.c
+++ b/src/core/lib/iomgr/udp_server.c
@@ -65,12 +65,12 @@
 #include "src/core/lib/iomgr/sockaddr.h"
 #include "src/core/lib/iomgr/sockaddr_utils.h"
 #include "src/core/lib/iomgr/socket_utils_posix.h"
+#include "src/core/lib/iomgr/unix_sockets_posix.h"
 #include "src/core/lib/support/string.h"
 
-#define INIT_PORT_CAP 2
-
 /* one listening port */
-typedef struct {
+typedef struct grpc_udp_listener grpc_udp_listener;
+struct grpc_udp_listener {
   int fd;
   grpc_fd *emfd;
   grpc_udp_server *server;
@@ -79,12 +79,13 @@ typedef struct {
   grpc_closure destroyed_closure;
   grpc_udp_server_read_cb read_cb;
   grpc_udp_server_orphan_cb orphan_cb;
-} server_port;
+
+  struct grpc_udp_listener *next;
+};
 
 /* the overall server */
 struct grpc_udp_server {
   gpr_mu mu;
-  gpr_cv cv;
 
   /* active port count: how many ports are actually still listening */
   size_t active_ports;
@@ -94,10 +95,10 @@ struct grpc_udp_server {
   /* is this server shutting down? (boolean) */
   int shutdown;
 
-  /* all listening ports */
-  server_port *ports;
-  size_t nports;
-  size_t port_capacity;
+  /* linked list of server ports */
+  grpc_udp_listener *head;
+  grpc_udp_listener *tail;
+  unsigned nports;
 
   /* shutdown callback */
   grpc_closure *shutdown_complete;
@@ -113,24 +114,29 @@ struct grpc_udp_server {
 grpc_udp_server *grpc_udp_server_create(void) {
   grpc_udp_server *s = gpr_malloc(sizeof(grpc_udp_server));
   gpr_mu_init(&s->mu);
-  gpr_cv_init(&s->cv);
   s->active_ports = 0;
   s->destroyed_ports = 0;
   s->shutdown = 0;
-  s->ports = gpr_malloc(sizeof(server_port) * INIT_PORT_CAP);
+  s->head = NULL;
+  s->tail = NULL;
   s->nports = 0;
-  s->port_capacity = INIT_PORT_CAP;
 
   return s;
 }
 
 static void finish_shutdown(grpc_exec_ctx *exec_ctx, grpc_udp_server *s) {
-  grpc_exec_ctx_sched(exec_ctx, s->shutdown_complete, GRPC_ERROR_NONE, NULL);
+  if (s->shutdown_complete != NULL) {
+    grpc_exec_ctx_sched(exec_ctx, s->shutdown_complete, GRPC_ERROR_NONE, NULL);
+  }
 
   gpr_mu_destroy(&s->mu);
-  gpr_cv_destroy(&s->cv);
 
-  gpr_free(s->ports);
+  while (s->head) {
+    grpc_udp_listener *sp = s->head;
+    s->head = sp->next;
+    gpr_free(sp);
+  }
+
   gpr_free(s);
 }
 
@@ -151,8 +157,6 @@ static void destroyed_port(grpc_exec_ctx *exec_ctx, void *server,
    events will be received on them - at this point it's safe to destroy
    things */
 static void deactivated_all_ports(grpc_exec_ctx *exec_ctx, grpc_udp_server *s) {
-  size_t i;
-
   /* delete ALL the things */
   gpr_mu_lock(&s->mu);
 
@@ -161,9 +165,11 @@ static void deactivated_all_ports(grpc_exec_ctx *exec_ctx, grpc_udp_server *s) {
     return;
   }
 
-  if (s->nports) {
-    for (i = 0; i < s->nports; i++) {
-      server_port *sp = &s->ports[i];
+  if (s->head) {
+    grpc_udp_listener *sp;
+    for (sp = s->head; sp; sp = sp->next) {
+      grpc_unlink_if_unix_domain_socket(&sp->addr.sockaddr);
+
       sp->destroyed_closure.cb = destroyed_port;
       sp->destroyed_closure.cb_arg = s;
 
@@ -184,7 +190,7 @@ static void deactivated_all_ports(grpc_exec_ctx *exec_ctx, grpc_udp_server *s) {
 
 void grpc_udp_server_destroy(grpc_exec_ctx *exec_ctx, grpc_udp_server *s,
                              grpc_closure *on_done) {
-  size_t i;
+  grpc_udp_listener *sp;
   gpr_mu_lock(&s->mu);
 
   GPR_ASSERT(!s->shutdown);
@@ -194,14 +200,10 @@ void grpc_udp_server_destroy(grpc_exec_ctx *exec_ctx, grpc_udp_server *s,
 
   /* shutdown all fd's */
   if (s->active_ports) {
-    for (i = 0; i < s->nports; i++) {
-      server_port *sp = &s->ports[i];
-      /* Call the orphan_cb to signal that the FD is about to be closed and
-       * should no longer be used. */
+    for (sp = s->head; sp; sp = sp->next) {
       GPR_ASSERT(sp->orphan_cb);
       sp->orphan_cb(sp->emfd);
-
-      grpc_fd_shutdown(exec_ctx, s->ports[i].emfd);
+      grpc_fd_shutdown(exec_ctx, sp->emfd);
     }
     gpr_mu_unlock(&s->mu);
   } else {
@@ -279,10 +281,10 @@ error:
 
 /* event manager callback when reads are ready */
 static void on_read(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error) {
-  server_port *sp = arg;
+  grpc_udp_listener *sp = arg;
 
+  gpr_mu_lock(&sp->server->mu);
   if (error != GRPC_ERROR_NONE) {
-    gpr_mu_lock(&sp->server->mu);
     if (0 == --sp->server->active_ports) {
       gpr_mu_unlock(&sp->server->mu);
       deactivated_all_ports(exec_ctx, sp->server);
@@ -298,13 +300,14 @@ static void on_read(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error) {
 
   /* Re-arm the notification event so we get another chance to read. */
   grpc_fd_notify_on_read(exec_ctx, sp->emfd, &sp->read_closure);
+  gpr_mu_unlock(&sp->server->mu);
 }
 
 static int add_socket_to_server(grpc_udp_server *s, int fd,
                                 const grpc_resolved_address *addr,
                                 grpc_udp_server_read_cb read_cb,
                                 grpc_udp_server_orphan_cb orphan_cb) {
-  server_port *sp;
+  grpc_udp_listener *sp;
   int port;
   char *addr_str;
   char *name;
@@ -315,12 +318,15 @@ static int add_socket_to_server(grpc_udp_server *s, int fd,
     gpr_asprintf(&name, "udp-server-listener:%s", addr_str);
     gpr_free(addr_str);
     gpr_mu_lock(&s->mu);
-    /* append it to the list under a lock */
-    if (s->nports == s->port_capacity) {
-      s->port_capacity *= 2;
-      s->ports = gpr_realloc(s->ports, sizeof(server_port) * s->port_capacity);
+    s->nports++;
+    sp = gpr_malloc(sizeof(grpc_udp_listener));
+    sp->next = NULL;
+    if (s->head == NULL) {
+      s->head = sp;
+    } else {
+      s->tail->next = sp;
     }
-    sp = &s->ports[s->nports++];
+    s->tail = sp;
     sp->server = s;
     sp->fd = fd;
     sp->emfd = grpc_fd_create(fd, name);
@@ -339,9 +345,9 @@ int grpc_udp_server_add_port(grpc_udp_server *s,
                              const grpc_resolved_address *addr,
                              grpc_udp_server_read_cb read_cb,
                              grpc_udp_server_orphan_cb orphan_cb) {
+  grpc_udp_listener *sp;
   int allocated_port1 = -1;
   int allocated_port2 = -1;
-  unsigned i;
   int fd;
   grpc_dualstack_mode dsmode;
   grpc_resolved_address addr6_v4mapped;
@@ -355,9 +361,9 @@ int grpc_udp_server_add_port(grpc_udp_server *s,
   /* Check if this is a wildcard port, and if so, try to keep the port the same
      as some previously created listener. */
   if (grpc_sockaddr_get_port(addr) == 0) {
-    for (i = 0; i < s->nports; i++) {
+    for (sp = s->head; sp; sp = sp->next) {
       sockname_temp.len = sizeof(struct sockaddr_storage);
-      if (0 == getsockname(s->ports[i].fd,
+      if (0 == getsockname(sp->fd,
                            (struct sockaddr *)sockname_temp.addr,
                            (socklen_t *)&sockname_temp.len)) {
         port = grpc_sockaddr_get_port(&sockname_temp);
@@ -413,27 +419,40 @@ done:
 }
 
 int grpc_udp_server_get_fd(grpc_udp_server *s, unsigned port_index) {
-  return (port_index < s->nports) ? s->ports[port_index].fd : -1;
+  grpc_udp_listener *sp;
+  if (port_index >= s->nports) {
+    return -1;
+  }
+
+  for (sp = s->head; sp && port_index != 0; sp = sp->next) {
+    --port_index;
+  }
+  return sp->fd;
 }
 
 void grpc_udp_server_start(grpc_exec_ctx *exec_ctx, grpc_udp_server *s,
                            grpc_pollset **pollsets, size_t pollset_count,
                            grpc_server *server) {
-  size_t i, j;
+  size_t i;
   gpr_mu_lock(&s->mu);
+  grpc_udp_listener *sp;
   GPR_ASSERT(s->active_ports == 0);
   s->pollsets = pollsets;
   s->grpc_server = server;
-  for (i = 0; i < s->nports; i++) {
-    for (j = 0; j < pollset_count; j++) {
-      grpc_pollset_add_fd(exec_ctx, pollsets[j], s->ports[i].emfd);
+
+  sp = s->head;
+  while (sp != NULL) {
+    for (i = 0; i < pollset_count; i++) {
+      grpc_pollset_add_fd(exec_ctx, pollsets[i], sp->emfd);
     }
-    s->ports[i].read_closure.cb = on_read;
-    s->ports[i].read_closure.cb_arg = &s->ports[i];
-    grpc_fd_notify_on_read(exec_ctx, s->ports[i].emfd,
-                           &s->ports[i].read_closure);
+    sp->read_closure.cb = on_read;
+    sp->read_closure.cb_arg = sp;
+    grpc_fd_notify_on_read(exec_ctx, sp->emfd, &sp->read_closure);
+
     s->active_ports++;
+    sp = sp->next;
   }
+
   gpr_mu_unlock(&s->mu);
 }
 
diff --git a/src/core/lib/iomgr/udp_server.h b/src/core/lib/iomgr/udp_server.h
index a95086e9aecc568423f428cfc2daa9850d980071..f3c466a031d2ded7195ea229f60ba251d48aed2c 100644
--- a/src/core/lib/iomgr/udp_server.h
+++ b/src/core/lib/iomgr/udp_server.h
@@ -60,7 +60,7 @@ void grpc_udp_server_start(grpc_exec_ctx *exec_ctx, grpc_udp_server *udp_server,
                            grpc_pollset **pollsets, size_t pollset_count,
                            struct grpc_server *server);
 
-int grpc_udp_server_get_fd(grpc_udp_server *s, unsigned index);
+int grpc_udp_server_get_fd(grpc_udp_server *s, unsigned port_index);
 
 /* Add a port to the server, returning port number on success, or negative
    on failure.
diff --git a/src/core/lib/transport/mdstr_hash_table.c b/src/core/lib/transport/mdstr_hash_table.c
index 4be0536dd765b2ad4b453c707af9dd0c338e9ef1..8e914c420b13a198b641a0065baaa38a9b14986f 100644
--- a/src/core/lib/transport/mdstr_hash_table.c
+++ b/src/core/lib/transport/mdstr_hash_table.c
@@ -42,6 +42,7 @@
 struct grpc_mdstr_hash_table {
   gpr_refcount refs;
   size_t num_entries;
+  size_t size;
   grpc_mdstr_hash_table_entry* entries;
 };
 
@@ -50,13 +51,12 @@ struct grpc_mdstr_hash_table {
 static size_t grpc_mdstr_hash_table_find_index(
     const grpc_mdstr_hash_table* table, const grpc_mdstr* key,
     bool find_empty) {
-  for (size_t i = 0; i < table->num_entries; ++i) {
-    const size_t idx = (key->hash + i * i) % table->num_entries;
-    if (table->entries[idx].key == NULL)
-      return find_empty ? idx : table->num_entries;
+  for (size_t i = 0; i < table->size; ++i) {
+    const size_t idx = (key->hash + i * i) % table->size;
+    if (table->entries[idx].key == NULL) return find_empty ? idx : table->size;
     if (table->entries[idx].key == key) return idx;
   }
-  return table->num_entries;  // Not found.
+  return table->size;  // Not found.
 }
 
 static void grpc_mdstr_hash_table_add(
@@ -65,7 +65,7 @@ static void grpc_mdstr_hash_table_add(
   GPR_ASSERT(value != NULL);
   const size_t idx =
       grpc_mdstr_hash_table_find_index(table, key, true /* find_empty */);
-  GPR_ASSERT(idx != table->num_entries);  // Table should never be full.
+  GPR_ASSERT(idx != table->size);  // Table should never be full.
   grpc_mdstr_hash_table_entry* entry = &table->entries[idx];
   entry->key = GRPC_MDSTR_REF(key);
   entry->value = vtable->copy_value(value);
@@ -77,11 +77,11 @@ grpc_mdstr_hash_table* grpc_mdstr_hash_table_create(
   grpc_mdstr_hash_table* table = gpr_malloc(sizeof(*table));
   memset(table, 0, sizeof(*table));
   gpr_ref_init(&table->refs, 1);
+  table->num_entries = num_entries;
   // Quadratic probing gets best performance when the table is no more
   // than half full.
-  table->num_entries = num_entries * 2;
-  const size_t entry_size =
-      sizeof(grpc_mdstr_hash_table_entry) * table->num_entries;
+  table->size = num_entries * 2;
+  const size_t entry_size = sizeof(grpc_mdstr_hash_table_entry) * table->size;
   table->entries = gpr_malloc(entry_size);
   memset(table->entries, 0, entry_size);
   for (size_t i = 0; i < num_entries; ++i) {
@@ -98,7 +98,7 @@ grpc_mdstr_hash_table* grpc_mdstr_hash_table_ref(grpc_mdstr_hash_table* table) {
 
 int grpc_mdstr_hash_table_unref(grpc_mdstr_hash_table* table) {
   if (table != NULL && gpr_unref(&table->refs)) {
-    for (size_t i = 0; i < table->num_entries; ++i) {
+    for (size_t i = 0; i < table->size; ++i) {
       grpc_mdstr_hash_table_entry* entry = &table->entries[i];
       if (entry->key != NULL) {
         GRPC_MDSTR_UNREF(entry->key);
@@ -112,11 +112,15 @@ int grpc_mdstr_hash_table_unref(grpc_mdstr_hash_table* table) {
   return 0;
 }
 
+size_t grpc_mdstr_hash_table_num_entries(const grpc_mdstr_hash_table* table) {
+  return table->num_entries;
+}
+
 void* grpc_mdstr_hash_table_get(const grpc_mdstr_hash_table* table,
                                 const grpc_mdstr* key) {
   const size_t idx =
       grpc_mdstr_hash_table_find_index(table, key, false /* find_empty */);
-  if (idx == table->num_entries) return NULL;  // Not found.
+  if (idx == table->size) return NULL;  // Not found.
   return table->entries[idx].value;
 }
 
@@ -140,3 +144,14 @@ int grpc_mdstr_hash_table_cmp(const grpc_mdstr_hash_table* table1,
   }
   return 0;
 }
+
+void grpc_mdstr_hash_table_iterate(
+    const grpc_mdstr_hash_table* table,
+    void (*func)(const grpc_mdstr_hash_table_entry* entry, void* user_data),
+    void* user_data) {
+  for (size_t i = 0; i < table->size; ++i) {
+    if (table->entries[i].key != NULL) {
+      func(&table->entries[i], user_data);
+    }
+  }
+}
diff --git a/src/core/lib/transport/mdstr_hash_table.h b/src/core/lib/transport/mdstr_hash_table.h
index 52e5b023db0a6e47f6682e34b314138fdcd5400f..bceb4df93de7a1d050b2a4a77aed1ba9cbfb2e90 100644
--- a/src/core/lib/transport/mdstr_hash_table.h
+++ b/src/core/lib/transport/mdstr_hash_table.h
@@ -70,6 +70,9 @@ grpc_mdstr_hash_table* grpc_mdstr_hash_table_ref(grpc_mdstr_hash_table* table);
 /** Returns 1 when \a table is destroyed. */
 int grpc_mdstr_hash_table_unref(grpc_mdstr_hash_table* table);
 
+/** Returns the number of entries in \a table. */
+size_t grpc_mdstr_hash_table_num_entries(const grpc_mdstr_hash_table* table);
+
 /** Returns the value from \a table associated with \a key.
     Returns NULL if \a key is not found. */
 void* grpc_mdstr_hash_table_get(const grpc_mdstr_hash_table* table,
@@ -80,4 +83,10 @@ void* grpc_mdstr_hash_table_get(const grpc_mdstr_hash_table* table,
 int grpc_mdstr_hash_table_cmp(const grpc_mdstr_hash_table* table1,
                               const grpc_mdstr_hash_table* table2);
 
+/** Iterates over the entries in \a table, calling \a func for each entry. */
+void grpc_mdstr_hash_table_iterate(
+    const grpc_mdstr_hash_table* table,
+    void (*func)(const grpc_mdstr_hash_table_entry* entry, void* user_data),
+    void* user_data);
+
 #endif /* GRPC_CORE_LIB_TRANSPORT_MDSTR_HASH_TABLE_H */
diff --git a/src/cpp/client/cronet_credentials.cc b/src/cpp/client/cronet_credentials.cc
new file mode 100644
index 0000000000000000000000000000000000000000..60cad097db57e96091cecbc5ec4d827810d6f5f5
--- /dev/null
+++ b/src/cpp/client/cronet_credentials.cc
@@ -0,0 +1,69 @@
+/*
+ *
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <grpc++/security/credentials.h>
+
+#include <grpc++/channel.h>
+#include <grpc++/support/channel_arguments.h>
+#include <grpc/grpc_cronet.h>
+#include "src/cpp/client/create_channel_internal.h"
+
+namespace grpc {
+
+class CronetChannelCredentialsImpl GRPC_FINAL : public ChannelCredentials {
+ public:
+  CronetChannelCredentialsImpl(void* engine) : engine_(engine) {}
+
+  std::shared_ptr<grpc::Channel> CreateChannel(
+      const string& target, const grpc::ChannelArguments& args) GRPC_OVERRIDE {
+    grpc_channel_args channel_args;
+    args.SetChannelArgs(&channel_args);
+    return CreateChannelInternal(
+        "", grpc_cronet_secure_channel_create(engine_, target.c_str(),
+                                              &channel_args, nullptr));
+  }
+
+  SecureChannelCredentials* AsSecureCredentials() GRPC_OVERRIDE {
+    return nullptr;
+  }
+
+ private:
+  void* engine_;
+};
+
+std::shared_ptr<ChannelCredentials> CronetChannelCredentials(void* engine) {
+  return std::shared_ptr<ChannelCredentials>(
+      new CronetChannelCredentialsImpl(engine));
+}
+
+}  // namespace grpc
diff --git a/src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs b/src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs
index 96d6ee87ae4d4a0113ed98228ffdbc3043eab497..722b51f470cd5af896fed7787f170e8850828d62 100644
--- a/src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs
+++ b/src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs
@@ -33,6 +33,7 @@
 
 using System;
 using System.Threading;
+using System.Threading.Tasks;
 
 using Google.Apis.Auth.OAuth2;
 using Grpc.Core;
@@ -72,9 +73,10 @@ namespace Grpc.Auth
         public static AsyncAuthInterceptor FromAccessToken(string accessToken)
         {
             GrpcPreconditions.CheckNotNull(accessToken);
-            return new AsyncAuthInterceptor(async (context, metadata) =>
+            return new AsyncAuthInterceptor((context, metadata) =>
             {
                 metadata.Add(CreateBearerTokenHeader(accessToken));
+                return Task.FromResult<object>(null);
             });
         }
 
diff --git a/src/csharp/Grpc.Core/DefaultCallInvoker.cs b/src/csharp/Grpc.Core/DefaultCallInvoker.cs
index 1a99e41153b3bd5c0419bbe141c97006aaa8676e..b15aaefcd6a6aa5758590ca0bce0533cdeb022e1 100644
--- a/src/csharp/Grpc.Core/DefaultCallInvoker.cs
+++ b/src/csharp/Grpc.Core/DefaultCallInvoker.cs
@@ -102,6 +102,7 @@ namespace Grpc.Core
             return Calls.AsyncDuplexStreamingCall(call);
         }
 
+        /// <summary>Creates call invocation details for given method.</summary>
         protected virtual CallInvocationDetails<TRequest, TResponse> CreateCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
                 where TRequest : class
                 where TResponse : class
diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj
index 622813fb3813680007e93186fcfdf9fbea275e16..e75dc9faf1ec865ea37d46ba6c44d56f08497e62 100644
--- a/src/csharp/Grpc.Core/Grpc.Core.csproj
+++ b/src/csharp/Grpc.Core/Grpc.Core.csproj
@@ -138,6 +138,7 @@
     <Compile Include="Internal\CallError.cs" />
     <Compile Include="Logging\LogLevel.cs" />
     <Compile Include="Logging\LogLevelFilterLogger.cs" />
+    <Compile Include="Internal\RequestCallContextSafeHandle.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="Grpc.Core.nuspec" />
diff --git a/src/csharp/Grpc.Core/GrpcEnvironment.cs b/src/csharp/Grpc.Core/GrpcEnvironment.cs
index 3ed2df203de3c182c8896b6ab06f6099d3cf3dd8..c57c904eb9d4ad4e478e4046310357bcafb8ed2a 100644
--- a/src/csharp/Grpc.Core/GrpcEnvironment.cs
+++ b/src/csharp/Grpc.Core/GrpcEnvironment.cs
@@ -59,7 +59,6 @@ namespace Grpc.Core
 
         static ILogger logger = new NullLogger();
 
-        readonly object myLock = new object();
         readonly GrpcThreadPool threadPool;
         readonly DebugStats debugStats = new DebugStats();
         readonly AtomicCounter cqPickerCounter = new AtomicCounter();
diff --git a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
index c28a6f64d39f3a4b170d06247f51b41466bb9556..26449ee53937156ad06e9a71c387025e63ef95bd 100644
--- a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
@@ -93,21 +93,6 @@ namespace Grpc.Core.Internal
             return data;
         }
 
-        // Gets data of server_rpc_new completion.
-        public ServerRpcNew GetServerRpcNew(Server server)
-        {
-            var call = Native.grpcsharp_batch_context_server_rpc_new_call(this);
-
-            var method = Marshal.PtrToStringAnsi(Native.grpcsharp_batch_context_server_rpc_new_method(this));
-            var host = Marshal.PtrToStringAnsi(Native.grpcsharp_batch_context_server_rpc_new_host(this));
-            var deadline = Native.grpcsharp_batch_context_server_rpc_new_deadline(this);
-
-            IntPtr metadataArrayPtr = Native.grpcsharp_batch_context_server_rpc_new_request_metadata(this);
-            var metadata = MetadataArraySafeHandle.ReadMetadataFromPtrUnsafe(metadataArrayPtr);
-
-            return new ServerRpcNew(server, call, method, host, deadline, metadata);
-        }
-
         // Gets data of receive_close_on_server completion.
         public bool GetReceivedCloseOnServerCancelled()
         {
diff --git a/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs b/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs
index 628844f242218d9228bbb5536356fbab70e05f40..7e2f0e9c6c9ed4803309e76e1a2cdc459d9ae892 100644
--- a/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs
+++ b/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs
@@ -44,6 +44,8 @@ namespace Grpc.Core.Internal
 
     internal delegate void BatchCompletionDelegate(bool success, BatchContextSafeHandle ctx);
 
+    internal delegate void RequestCallCompletionDelegate(bool success, RequestCallContextSafeHandle ctx);
+
     internal class CompletionRegistry
     {
         static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<CompletionRegistry>();
@@ -68,6 +70,12 @@ namespace Grpc.Core.Internal
             Register(ctx.Handle, opCallback);
         }
 
+        public void RegisterRequestCallCompletion(RequestCallContextSafeHandle ctx, RequestCallCompletionDelegate callback)
+        {
+            OpCompletionDelegate opCallback = ((success) => HandleRequestCallCompletion(success, ctx, callback));
+            Register(ctx.Handle, opCallback);
+        }
+
         public OpCompletionDelegate Extract(IntPtr key)
         {
             OpCompletionDelegate value;
@@ -84,7 +92,26 @@ namespace Grpc.Core.Internal
             }
             catch (Exception e)
             {
-                Logger.Error(e, "Exception occured while invoking completion delegate.");
+                Logger.Error(e, "Exception occured while invoking batch completion delegate.");
+            }
+            finally
+            {
+                if (ctx != null)
+                {
+                    ctx.Dispose();
+                }
+            }
+        }
+
+        private static void HandleRequestCallCompletion(bool success, RequestCallContextSafeHandle ctx, RequestCallCompletionDelegate callback)
+        {
+            try
+            {
+                callback(success, ctx);
+            }
+            catch (Exception e)
+            {
+                Logger.Error(e, "Exception occured while invoking request call completion delegate.");
             }
             finally
             {
diff --git a/src/csharp/Grpc.Core/Internal/InterceptingCallInvoker.cs b/src/csharp/Grpc.Core/Internal/InterceptingCallInvoker.cs
index ef48dc712121c048e392a418c3f6955c738cd569..0c63e2092a5b20a81d6f3a462154050b0a2078d9 100644
--- a/src/csharp/Grpc.Core/Internal/InterceptingCallInvoker.cs
+++ b/src/csharp/Grpc.Core/Internal/InterceptingCallInvoker.cs
@@ -48,7 +48,7 @@ namespace Grpc.Core.Internal
         readonly Func<CallOptions, CallOptions> callOptionsInterceptor;
 
         /// <summary>
-        /// Initializes a new instance of the <see cref="Grpc.Core.InterceptingCallInvoker"/> class.
+        /// Initializes a new instance of the <see cref="Grpc.Core.Internal.InterceptingCallInvoker"/> class.
         /// </summary>
         public InterceptingCallInvoker(CallInvoker callInvoker,
             Func<string, string> hostInterceptor = null,
diff --git a/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs b/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs
index 26af6311d529b3997dd83b6bccf9d621d63eb256..b3714481eb28c5d35e532cfb3be9db6d2b8dbf05 100644
--- a/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs
+++ b/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs
@@ -78,7 +78,10 @@ namespace Grpc.Core.Internal
             {
                 var context = new AuthInterceptorContext(Marshal.PtrToStringAnsi(serviceUrlPtr),
                                                          Marshal.PtrToStringAnsi(methodNamePtr));
-                StartGetMetadata(context, callbackPtr, userDataPtr);
+                // Don't await, we are in a native callback and need to return.
+                #pragma warning disable 4014
+                GetMetadataAsync(context, callbackPtr, userDataPtr);
+                #pragma warning restore 4014
             }
             catch (Exception e)
             {
@@ -87,7 +90,7 @@ namespace Grpc.Core.Internal
             }
         }
 
-        private async Task StartGetMetadata(AuthInterceptorContext context, IntPtr callbackPtr, IntPtr userDataPtr)
+        private async Task GetMetadataAsync(AuthInterceptorContext context, IntPtr callbackPtr, IntPtr userDataPtr)
         {
             try
             {
diff --git a/src/csharp/Grpc.Core/Internal/NativeMethods.cs b/src/csharp/Grpc.Core/Internal/NativeMethods.cs
index f457c9dbf1e8b3093079b1bf613301bf76f0eaba..40ba7e30cb02b125ac016d34aa736aa917216b4a 100644
--- a/src/csharp/Grpc.Core/Internal/NativeMethods.cs
+++ b/src/csharp/Grpc.Core/Internal/NativeMethods.cs
@@ -64,14 +64,17 @@ namespace Grpc.Core.Internal
         public readonly Delegates.grpcsharp_batch_context_recv_status_on_client_status_delegate grpcsharp_batch_context_recv_status_on_client_status;
         public readonly Delegates.grpcsharp_batch_context_recv_status_on_client_details_delegate grpcsharp_batch_context_recv_status_on_client_details;
         public readonly Delegates.grpcsharp_batch_context_recv_status_on_client_trailing_metadata_delegate grpcsharp_batch_context_recv_status_on_client_trailing_metadata;
-        public readonly Delegates.grpcsharp_batch_context_server_rpc_new_call_delegate grpcsharp_batch_context_server_rpc_new_call;
-        public readonly Delegates.grpcsharp_batch_context_server_rpc_new_method_delegate grpcsharp_batch_context_server_rpc_new_method;
-        public readonly Delegates.grpcsharp_batch_context_server_rpc_new_host_delegate grpcsharp_batch_context_server_rpc_new_host;
-        public readonly Delegates.grpcsharp_batch_context_server_rpc_new_deadline_delegate grpcsharp_batch_context_server_rpc_new_deadline;
-        public readonly Delegates.grpcsharp_batch_context_server_rpc_new_request_metadata_delegate grpcsharp_batch_context_server_rpc_new_request_metadata;
         public readonly Delegates.grpcsharp_batch_context_recv_close_on_server_cancelled_delegate grpcsharp_batch_context_recv_close_on_server_cancelled;
         public readonly Delegates.grpcsharp_batch_context_destroy_delegate grpcsharp_batch_context_destroy;
 
+        public readonly Delegates.grpcsharp_request_call_context_create_delegate grpcsharp_request_call_context_create;
+        public readonly Delegates.grpcsharp_request_call_context_call_delegate grpcsharp_request_call_context_call;
+        public readonly Delegates.grpcsharp_request_call_context_method_delegate grpcsharp_request_call_context_method;
+        public readonly Delegates.grpcsharp_request_call_context_host_delegate grpcsharp_request_call_context_host;
+        public readonly Delegates.grpcsharp_request_call_context_deadline_delegate grpcsharp_request_call_context_deadline;
+        public readonly Delegates.grpcsharp_request_call_context_request_metadata_delegate grpcsharp_request_call_context_request_metadata;
+        public readonly Delegates.grpcsharp_request_call_context_destroy_delegate grpcsharp_request_call_context_destroy;
+
         public readonly Delegates.grpcsharp_composite_call_credentials_create_delegate grpcsharp_composite_call_credentials_create;
         public readonly Delegates.grpcsharp_call_credentials_release_delegate grpcsharp_call_credentials_release;
 
@@ -170,14 +173,17 @@ namespace Grpc.Core.Internal
             this.grpcsharp_batch_context_recv_status_on_client_status = GetMethodDelegate<Delegates.grpcsharp_batch_context_recv_status_on_client_status_delegate>(library);
             this.grpcsharp_batch_context_recv_status_on_client_details = GetMethodDelegate<Delegates.grpcsharp_batch_context_recv_status_on_client_details_delegate>(library);
             this.grpcsharp_batch_context_recv_status_on_client_trailing_metadata = GetMethodDelegate<Delegates.grpcsharp_batch_context_recv_status_on_client_trailing_metadata_delegate>(library);
-            this.grpcsharp_batch_context_server_rpc_new_call = GetMethodDelegate<Delegates.grpcsharp_batch_context_server_rpc_new_call_delegate>(library);
-            this.grpcsharp_batch_context_server_rpc_new_method = GetMethodDelegate<Delegates.grpcsharp_batch_context_server_rpc_new_method_delegate>(library);
-            this.grpcsharp_batch_context_server_rpc_new_host = GetMethodDelegate<Delegates.grpcsharp_batch_context_server_rpc_new_host_delegate>(library);
-            this.grpcsharp_batch_context_server_rpc_new_deadline = GetMethodDelegate<Delegates.grpcsharp_batch_context_server_rpc_new_deadline_delegate>(library);
-            this.grpcsharp_batch_context_server_rpc_new_request_metadata = GetMethodDelegate<Delegates.grpcsharp_batch_context_server_rpc_new_request_metadata_delegate>(library);
             this.grpcsharp_batch_context_recv_close_on_server_cancelled = GetMethodDelegate<Delegates.grpcsharp_batch_context_recv_close_on_server_cancelled_delegate>(library);
             this.grpcsharp_batch_context_destroy = GetMethodDelegate<Delegates.grpcsharp_batch_context_destroy_delegate>(library);
 
+            this.grpcsharp_request_call_context_create = GetMethodDelegate<Delegates.grpcsharp_request_call_context_create_delegate>(library);
+            this.grpcsharp_request_call_context_call = GetMethodDelegate<Delegates.grpcsharp_request_call_context_call_delegate>(library);
+            this.grpcsharp_request_call_context_method = GetMethodDelegate<Delegates.grpcsharp_request_call_context_method_delegate>(library);
+            this.grpcsharp_request_call_context_host = GetMethodDelegate<Delegates.grpcsharp_request_call_context_host_delegate>(library);
+            this.grpcsharp_request_call_context_deadline = GetMethodDelegate<Delegates.grpcsharp_request_call_context_deadline_delegate>(library);
+            this.grpcsharp_request_call_context_request_metadata = GetMethodDelegate<Delegates.grpcsharp_request_call_context_request_metadata_delegate>(library);
+            this.grpcsharp_request_call_context_destroy = GetMethodDelegate<Delegates.grpcsharp_request_call_context_destroy_delegate>(library);
+
             this.grpcsharp_composite_call_credentials_create = GetMethodDelegate<Delegates.grpcsharp_composite_call_credentials_create_delegate>(library);
             this.grpcsharp_call_credentials_release = GetMethodDelegate<Delegates.grpcsharp_call_credentials_release_delegate>(library);
 
@@ -302,14 +308,17 @@ namespace Grpc.Core.Internal
             public delegate StatusCode grpcsharp_batch_context_recv_status_on_client_status_delegate(BatchContextSafeHandle ctx);
             public delegate IntPtr grpcsharp_batch_context_recv_status_on_client_details_delegate(BatchContextSafeHandle ctx);  // returns const char*
             public delegate IntPtr grpcsharp_batch_context_recv_status_on_client_trailing_metadata_delegate(BatchContextSafeHandle ctx);
-            public delegate CallSafeHandle grpcsharp_batch_context_server_rpc_new_call_delegate(BatchContextSafeHandle ctx);
-            public delegate IntPtr grpcsharp_batch_context_server_rpc_new_method_delegate(BatchContextSafeHandle ctx);  // returns const char*
-            public delegate IntPtr grpcsharp_batch_context_server_rpc_new_host_delegate(BatchContextSafeHandle ctx);  // returns const char*
-            public delegate Timespec grpcsharp_batch_context_server_rpc_new_deadline_delegate(BatchContextSafeHandle ctx);
-            public delegate IntPtr grpcsharp_batch_context_server_rpc_new_request_metadata_delegate(BatchContextSafeHandle ctx);
             public delegate int grpcsharp_batch_context_recv_close_on_server_cancelled_delegate(BatchContextSafeHandle ctx);
             public delegate void grpcsharp_batch_context_destroy_delegate(IntPtr ctx);
 
+            public delegate RequestCallContextSafeHandle grpcsharp_request_call_context_create_delegate();
+            public delegate CallSafeHandle grpcsharp_request_call_context_call_delegate(RequestCallContextSafeHandle ctx);
+            public delegate IntPtr grpcsharp_request_call_context_method_delegate(RequestCallContextSafeHandle ctx);  // returns const char*
+            public delegate IntPtr grpcsharp_request_call_context_host_delegate(RequestCallContextSafeHandle ctx);  // returns const char*
+            public delegate Timespec grpcsharp_request_call_context_deadline_delegate(RequestCallContextSafeHandle ctx);
+            public delegate IntPtr grpcsharp_request_call_context_request_metadata_delegate(RequestCallContextSafeHandle ctx);
+            public delegate void grpcsharp_request_call_context_destroy_delegate(IntPtr ctx);
+
             public delegate CallCredentialsSafeHandle grpcsharp_composite_call_credentials_create_delegate(CallCredentialsSafeHandle creds1, CallCredentialsSafeHandle creds2);
             public delegate void grpcsharp_call_credentials_release_delegate(IntPtr credentials);
 
@@ -393,7 +402,7 @@ namespace Grpc.Core.Internal
             public delegate int grpcsharp_server_add_insecure_http2_port_delegate(ServerSafeHandle server, string addr);
             public delegate int grpcsharp_server_add_secure_http2_port_delegate(ServerSafeHandle server, string addr, ServerCredentialsSafeHandle creds);
             public delegate void grpcsharp_server_start_delegate(ServerSafeHandle server);
-            public delegate CallError grpcsharp_server_request_call_delegate(ServerSafeHandle server, CompletionQueueSafeHandle cq, BatchContextSafeHandle ctx);
+            public delegate CallError grpcsharp_server_request_call_delegate(ServerSafeHandle server, CompletionQueueSafeHandle cq, RequestCallContextSafeHandle ctx);
             public delegate void grpcsharp_server_cancel_all_calls_delegate(ServerSafeHandle server);
             public delegate void grpcsharp_server_shutdown_and_notify_callback_delegate(ServerSafeHandle server, CompletionQueueSafeHandle cq, BatchContextSafeHandle ctx);
             public delegate void grpcsharp_server_destroy_delegate(IntPtr server);
diff --git a/src/csharp/Grpc.Core/Internal/RequestCallContextSafeHandle.cs b/src/csharp/Grpc.Core/Internal/RequestCallContextSafeHandle.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ea7819d7b1f2406f7f4d3bc3b8c55d6a2018dd61
--- /dev/null
+++ b/src/csharp/Grpc.Core/Internal/RequestCallContextSafeHandle.cs
@@ -0,0 +1,85 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+// 
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Runtime.InteropServices;
+using Grpc.Core;
+
+namespace Grpc.Core.Internal
+{
+    /// <summary>
+    /// grpcsharp_request_call_context
+    /// </summary>
+    internal class RequestCallContextSafeHandle : SafeHandleZeroIsInvalid
+    {
+        static readonly NativeMethods Native = NativeMethods.Get();
+
+        private RequestCallContextSafeHandle()
+        {
+        }
+
+        public static RequestCallContextSafeHandle Create()
+        {
+            return Native.grpcsharp_request_call_context_create();
+        }
+
+        public IntPtr Handle
+        {
+            get
+            {
+                return handle;
+            }
+        }
+
+        // Gets data of server_rpc_new completion.
+        public ServerRpcNew GetServerRpcNew(Server server)
+        {
+            var call = Native.grpcsharp_request_call_context_call(this);
+
+            var method = Marshal.PtrToStringAnsi(Native.grpcsharp_request_call_context_method(this));
+            var host = Marshal.PtrToStringAnsi(Native.grpcsharp_request_call_context_host(this));
+            var deadline = Native.grpcsharp_request_call_context_deadline(this);
+
+            IntPtr metadataArrayPtr = Native.grpcsharp_request_call_context_request_metadata(this);
+            var metadata = MetadataArraySafeHandle.ReadMetadataFromPtrUnsafe(metadataArrayPtr);
+
+            return new ServerRpcNew(server, call, method, host, deadline, metadata);
+        }
+
+        protected override bool ReleaseHandle()
+        {
+            Native.grpcsharp_request_call_context_destroy(handle);
+            return true;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs
index 014a8db78f2bb6629b9336377fbf18ee35a5b7e6..7d7b83861623cf66328c4f27e6cdbc1174f2acf0 100644
--- a/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs
@@ -85,12 +85,12 @@ namespace Grpc.Core.Internal
             }
         }
 
-        public void RequestCall(BatchCompletionDelegate callback, CompletionQueueSafeHandle completionQueue)
+        public void RequestCall(RequestCallCompletionDelegate callback, CompletionQueueSafeHandle completionQueue)
         {
             using (completionQueue.NewScope())
             {
-                var ctx = BatchContextSafeHandle.Create();
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
+                var ctx = RequestCallContextSafeHandle.Create();
+                completionQueue.CompletionRegistry.RegisterRequestCallCompletion(ctx, callback);
                 Native.grpcsharp_server_request_call(this, completionQueue, ctx).CheckOk();
             }
         }
diff --git a/src/csharp/Grpc.Core/Internal/UnmanagedLibrary.cs b/src/csharp/Grpc.Core/Internal/UnmanagedLibrary.cs
index 31e14028494719085a11d12c37c7b6239d38be46..1553bdd687e6e2631851974a591cd8f18ee60d15 100644
--- a/src/csharp/Grpc.Core/Internal/UnmanagedLibrary.cs
+++ b/src/csharp/Grpc.Core/Internal/UnmanagedLibrary.cs
@@ -134,7 +134,11 @@ namespace Grpc.Core.Internal
             {
                 throw new MissingMethodException(string.Format("The native method \"{0}\" does not exist", methodName));
             }
-            return Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)) as T;
+#if NETSTANDARD1_5
+            return Marshal.GetDelegateForFunctionPointer<T>(ptr);  // non-generic version is obsolete
+#else
+            return Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)) as T;  // generic version not available in .NET45
+#endif
         }
 
         /// <summary>
diff --git a/src/csharp/Grpc.Core/Metadata.cs b/src/csharp/Grpc.Core/Metadata.cs
index 915bec146c9878d5d7c5646b5e0d46d821ae5cf4..6fc715d6ee1a8c75a935044f194faf25bd7ba476 100644
--- a/src/csharp/Grpc.Core/Metadata.cs
+++ b/src/csharp/Grpc.Core/Metadata.cs
@@ -95,11 +95,18 @@ namespace Grpc.Core
 
         #region IList members
 
+
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public int IndexOf(Metadata.Entry item)
         {
             return entries.IndexOf(item);
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public void Insert(int index, Metadata.Entry item)
         {
             GrpcPreconditions.CheckNotNull(item);
@@ -107,12 +114,18 @@ namespace Grpc.Core
             entries.Insert(index, item);
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public void RemoveAt(int index)
         {
             CheckWriteable();
             entries.RemoveAt(index);
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public Metadata.Entry this[int index]
         {
             get
@@ -128,6 +141,9 @@ namespace Grpc.Core
             }
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public void Add(Metadata.Entry item)
         {
             GrpcPreconditions.CheckNotNull(item);
@@ -135,48 +151,75 @@ namespace Grpc.Core
             entries.Add(item);
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public void Add(string key, string value)
         {
             Add(new Entry(key, value));
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public void Add(string key, byte[] valueBytes)
         {
             Add(new Entry(key, valueBytes));
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public void Clear()
         {
             CheckWriteable();
             entries.Clear();
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public bool Contains(Metadata.Entry item)
         {
             return entries.Contains(item);
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public void CopyTo(Metadata.Entry[] array, int arrayIndex)
         {
             entries.CopyTo(array, arrayIndex);
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public int Count
         {
             get { return entries.Count; }
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public bool IsReadOnly
         {
             get { return readOnly; }
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public bool Remove(Metadata.Entry item)
         {
             CheckWriteable();
             return entries.Remove(item);
         }
 
+        /// <summary>
+        /// <see cref="T:IList`1"/>
+        /// </summary>
         public IEnumerator<Metadata.Entry> GetEnumerator()
         {
             return entries.GetEnumerator();
@@ -221,7 +264,7 @@ namespace Grpc.Core
             public Entry(string key, byte[] valueBytes)
             {
                 this.key = NormalizeKey(key);
-                GrpcPreconditions.CheckArgument(this.key.EndsWith(BinaryHeaderSuffix),
+                GrpcPreconditions.CheckArgument(HasBinaryHeaderSuffix(this.key),
                     "Key for binary valued metadata entry needs to have suffix indicating binary value.");
                 this.value = null;
                 GrpcPreconditions.CheckNotNull(valueBytes, "valueBytes");
@@ -237,7 +280,7 @@ namespace Grpc.Core
             public Entry(string key, string value)
             {
                 this.key = NormalizeKey(key);
-                GrpcPreconditions.CheckArgument(!this.key.EndsWith(BinaryHeaderSuffix),
+                GrpcPreconditions.CheckArgument(!HasBinaryHeaderSuffix(this.key),
                     "Key for ASCII valued metadata entry cannot have suffix indicating binary value.");
                 this.value = GrpcPreconditions.CheckNotNull(value, "value");
                 this.valueBytes = null;
@@ -324,7 +367,7 @@ namespace Grpc.Core
             /// </summary>
             internal static Entry CreateUnsafe(string key, byte[] valueBytes)
             {
-                if (key.EndsWith(BinaryHeaderSuffix))
+                if (HasBinaryHeaderSuffix(key))
                 {
                     return new Entry(key, null, valueBytes);
                 }
@@ -338,6 +381,27 @@ namespace Grpc.Core
                     "Metadata entry key not valid. Keys can only contain lowercase alphanumeric characters, underscores and hyphens.");
                 return normalized;
             }
+
+            /// <summary>
+            /// Returns <c>true</c> if the key has "-bin" binary header suffix.
+            /// </summary>
+            private static bool HasBinaryHeaderSuffix(string key)
+            {
+                // We don't use just string.EndsWith because its implementation is extremely slow
+                // on CoreCLR and we've seen significant differences in gRPC benchmarks caused by it.
+                // See https://github.com/dotnet/coreclr/issues/5612
+
+                int len = key.Length;
+                if (len >= 4 &&
+                    key[len - 4] == '-' &&
+                    key[len - 3] == 'b' &&
+                    key[len - 2] == 'i' &&
+                    key[len - 1] == 'n')
+                {
+                    return true;
+                }
+                return false;
+            }
         }
     }
 }
diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs
index dd4a405ed92d74dd6e8927beb65e5ea307f154ea..63c1d9cd00f3047dc6129813bd3c70a1af60c718 100644
--- a/src/csharp/Grpc.Core/Server.cs
+++ b/src/csharp/Grpc.Core/Server.cs
@@ -47,7 +47,7 @@ namespace Grpc.Core
     /// </summary>
     public class Server
     {
-        const int InitialAllowRpcTokenCountPerCq = 10;
+        const int DefaultRequestCallTokensPerCq = 2000;
         static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Server>();
 
         readonly AtomicCounter activeCallCounter = new AtomicCounter();
@@ -66,7 +66,7 @@ namespace Grpc.Core
 
         bool startRequested;
         volatile bool shutdownRequested;
-
+        int requestCallTokensPerCq = DefaultRequestCallTokensPerCq;
 
         /// <summary>
         /// Creates a new server.
@@ -132,6 +132,27 @@ namespace Grpc.Core
             }
         }
 
+        /// <summary>
+        /// Experimental API. Might anytime change without prior notice.
+        /// Number or calls requested via grpc_server_request_call at any given time for each completion queue.
+        /// </summary>
+        public int RequestCallTokensPerCompletionQueue
+        {
+            get
+            {
+                return requestCallTokensPerCq;
+            }
+            set
+            {
+                lock (myLock)
+                {
+                    GrpcPreconditions.CheckState(!startRequested);
+                    GrpcPreconditions.CheckArgument(value > 0);
+                    requestCallTokensPerCq = value;
+                }
+            }
+        }
+
         /// <summary>
         /// Starts the server.
         /// </summary>
@@ -145,9 +166,7 @@ namespace Grpc.Core
                 
                 handle.Start();
 
-                // Starting with more than one AllowOneRpc tokens can significantly increase
-                // unary RPC throughput.
-                for (int i = 0; i < InitialAllowRpcTokenCountPerCq; i++)
+                for (int i = 0; i < requestCallTokensPerCq; i++)
                 {
                     foreach (var cq in environment.CompletionQueues)
                     {
@@ -310,7 +329,7 @@ namespace Grpc.Core
         /// <summary>
         /// Selects corresponding handler for given call and handles the call.
         /// </summary>
-        private async Task HandleCallAsync(ServerRpcNew newRpc, CompletionQueueSafeHandle cq)
+        private async Task HandleCallAsync(ServerRpcNew newRpc, CompletionQueueSafeHandle cq, Action continuation)
         {
             try
             {
@@ -325,25 +344,40 @@ namespace Grpc.Core
             {
                 Logger.Warning(e, "Exception while handling RPC.");
             }
+            finally
+            {
+                continuation();
+            }
         }
 
         /// <summary>
         /// Handles the native callback.
         /// </summary>
-        private void HandleNewServerRpc(bool success, BatchContextSafeHandle ctx, CompletionQueueSafeHandle cq)
+        private void HandleNewServerRpc(bool success, RequestCallContextSafeHandle ctx, CompletionQueueSafeHandle cq)
         {
-			Task.Run(() => AllowOneRpc(cq));
-
+            bool nextRpcRequested = false;
             if (success)
             {
-                ServerRpcNew newRpc = ctx.GetServerRpcNew(this);
+                var newRpc = ctx.GetServerRpcNew(this);
 
                 // after server shutdown, the callback returns with null call
                 if (!newRpc.Call.IsInvalid)
                 {
-                    HandleCallAsync(newRpc, cq);  // we don't need to await.
+                    nextRpcRequested = true;
+
+                    // Start asynchronous handler for the call.
+                    // Don't await, the continuations will run on gRPC thread pool once triggered
+                    // by cq.Next().
+                    #pragma warning disable 4014
+                    HandleCallAsync(newRpc, cq, () => AllowOneRpc(cq));
+                    #pragma warning restore 4014
                 }
             }
+
+            if (!nextRpcRequested)
+            {
+                AllowOneRpc(cq);
+            }
         }
 
         /// <summary>
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
index 7a46a55a5b8b31d03c55f0653a151b672624c79d..5ba83b143ed19d879462132a32b612fcd7a45c89 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
@@ -195,8 +195,11 @@ namespace Grpc.IntegrationTesting
                 case "status_code_and_message":
                     await RunStatusCodeAndMessageAsync(client);
                     break;
+                case "unimplemented_service":
+                    RunUnimplementedService(new UnimplementedService.UnimplementedServiceClient(channel));
+                    break;
                 case "unimplemented_method":
-                    RunUnimplementedMethod(new UnimplementedService.UnimplementedServiceClient(channel));
+                    RunUnimplementedMethod(client);
                     break;
                 case "client_compressed_unary":
                     RunClientCompressedUnary(client);
@@ -577,7 +580,16 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static void RunUnimplementedMethod(UnimplementedService.UnimplementedServiceClient client)
+        public static void RunUnimplementedService(UnimplementedService.UnimplementedServiceClient client)
+        {
+            Console.WriteLine("running unimplemented_service");
+            var e = Assert.Throws<RpcException>(() => client.UnimplementedCall(new Empty()));
+
+            Assert.AreEqual(StatusCode.Unimplemented, e.Status.StatusCode);
+            Console.WriteLine("Passed!");
+        }
+
+        public static void RunUnimplementedMethod(TestService.TestServiceClient client)
         {
             Console.WriteLine("running unimplemented_method");
             var e = Assert.Throws<RpcException>(() => client.UnimplementedCall(new Empty()));
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
index f907f630dabd3aed25e73d37e09dfb33b7ee7e80..4960a53f92d6c3ca9ec43ada9277899c79368020 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
@@ -145,10 +145,16 @@ namespace Grpc.IntegrationTesting
             await InteropClient.RunStatusCodeAndMessageAsync(client);
         }
 
+        [Test]
+        public void UnimplementedService()
+        {
+            InteropClient.RunUnimplementedService(new UnimplementedService.UnimplementedServiceClient(channel));
+        }
+
         [Test]
         public void UnimplementedMethod()
         {
-            InteropClient.RunUnimplementedMethod(new UnimplementedService.UnimplementedServiceClient(channel));
+            InteropClient.RunUnimplementedMethod(client);
         }
     }
 }
diff --git a/src/csharp/Grpc.IntegrationTesting/QpsWorker.cs b/src/csharp/Grpc.IntegrationTesting/QpsWorker.cs
index 865556c2426fe62ec17cee22a9dae43199b5b20f..62a7347d420074cdd29ab831f9550f3b0d6b79b5 100644
--- a/src/csharp/Grpc.IntegrationTesting/QpsWorker.cs
+++ b/src/csharp/Grpc.IntegrationTesting/QpsWorker.cs
@@ -76,6 +76,11 @@ namespace Grpc.IntegrationTesting
 
         private async Task RunAsync()
         {
+            // (ThreadPoolSize == ProcessorCount) gives best throughput in benchmarks
+            // and doesn't seem to harm performance even when server and client
+            // are running on the same machine.
+            GrpcEnvironment.SetThreadPoolSize(Environment.ProcessorCount);
+
             string host = "0.0.0.0";
             int port = options.DriverPort;
 
diff --git a/src/csharp/Grpc.IntegrationTesting/Test.cs b/src/csharp/Grpc.IntegrationTesting/Test.cs
index 88c2b8a921f45367763d5aa23e2b4fe00de00ef6..d2fa9f8013a845f30433b60f04a74feefb95cae0 100644
--- a/src/csharp/Grpc.IntegrationTesting/Test.cs
+++ b/src/csharp/Grpc.IntegrationTesting/Test.cs
@@ -24,25 +24,28 @@ namespace Grpc.Testing {
           string.Concat(
             "CiFzcmMvcHJvdG8vZ3JwYy90ZXN0aW5nL3Rlc3QucHJvdG8SDGdycGMudGVz",
             "dGluZxoic3JjL3Byb3RvL2dycGMvdGVzdGluZy9lbXB0eS5wcm90bxolc3Jj",
-            "L3Byb3RvL2dycGMvdGVzdGluZy9tZXNzYWdlcy5wcm90bzK7BAoLVGVzdFNl",
+            "L3Byb3RvL2dycGMvdGVzdGluZy9tZXNzYWdlcy5wcm90bzLLBQoLVGVzdFNl",
             "cnZpY2USNQoJRW1wdHlDYWxsEhMuZ3JwYy50ZXN0aW5nLkVtcHR5GhMuZ3Jw",
             "Yy50ZXN0aW5nLkVtcHR5EkYKCVVuYXJ5Q2FsbBIbLmdycGMudGVzdGluZy5T",
-            "aW1wbGVSZXF1ZXN0GhwuZ3JwYy50ZXN0aW5nLlNpbXBsZVJlc3BvbnNlEmwK",
-            "E1N0cmVhbWluZ091dHB1dENhbGwSKC5ncnBjLnRlc3RpbmcuU3RyZWFtaW5n",
-            "T3V0cHV0Q2FsbFJlcXVlc3QaKS5ncnBjLnRlc3RpbmcuU3RyZWFtaW5nT3V0",
-            "cHV0Q2FsbFJlc3BvbnNlMAESaQoSU3RyZWFtaW5nSW5wdXRDYWxsEicuZ3Jw",
-            "Yy50ZXN0aW5nLlN0cmVhbWluZ0lucHV0Q2FsbFJlcXVlc3QaKC5ncnBjLnRl",
-            "c3RpbmcuU3RyZWFtaW5nSW5wdXRDYWxsUmVzcG9uc2UoARJpCg5GdWxsRHVw",
-            "bGV4Q2FsbBIoLmdycGMudGVzdGluZy5TdHJlYW1pbmdPdXRwdXRDYWxsUmVx",
-            "dWVzdBopLmdycGMudGVzdGluZy5TdHJlYW1pbmdPdXRwdXRDYWxsUmVzcG9u",
-            "c2UoATABEmkKDkhhbGZEdXBsZXhDYWxsEiguZ3JwYy50ZXN0aW5nLlN0cmVh",
-            "bWluZ091dHB1dENhbGxSZXF1ZXN0GikuZ3JwYy50ZXN0aW5nLlN0cmVhbWlu",
-            "Z091dHB1dENhbGxSZXNwb25zZSgBMAEyVQoUVW5pbXBsZW1lbnRlZFNlcnZp",
-            "Y2USPQoRVW5pbXBsZW1lbnRlZENhbGwSEy5ncnBjLnRlc3RpbmcuRW1wdHka",
-            "Ey5ncnBjLnRlc3RpbmcuRW1wdHkyiQEKEFJlY29ubmVjdFNlcnZpY2USOwoF",
-            "U3RhcnQSHS5ncnBjLnRlc3RpbmcuUmVjb25uZWN0UGFyYW1zGhMuZ3JwYy50",
-            "ZXN0aW5nLkVtcHR5EjgKBFN0b3ASEy5ncnBjLnRlc3RpbmcuRW1wdHkaGy5n",
-            "cnBjLnRlc3RpbmcuUmVjb25uZWN0SW5mb2IGcHJvdG8z"));
+            "aW1wbGVSZXF1ZXN0GhwuZ3JwYy50ZXN0aW5nLlNpbXBsZVJlc3BvbnNlEk8K",
+            "EkNhY2hlYWJsZVVuYXJ5Q2FsbBIbLmdycGMudGVzdGluZy5TaW1wbGVSZXF1",
+            "ZXN0GhwuZ3JwYy50ZXN0aW5nLlNpbXBsZVJlc3BvbnNlEmwKE1N0cmVhbWlu",
+            "Z091dHB1dENhbGwSKC5ncnBjLnRlc3RpbmcuU3RyZWFtaW5nT3V0cHV0Q2Fs",
+            "bFJlcXVlc3QaKS5ncnBjLnRlc3RpbmcuU3RyZWFtaW5nT3V0cHV0Q2FsbFJl",
+            "c3BvbnNlMAESaQoSU3RyZWFtaW5nSW5wdXRDYWxsEicuZ3JwYy50ZXN0aW5n",
+            "LlN0cmVhbWluZ0lucHV0Q2FsbFJlcXVlc3QaKC5ncnBjLnRlc3RpbmcuU3Ry",
+            "ZWFtaW5nSW5wdXRDYWxsUmVzcG9uc2UoARJpCg5GdWxsRHVwbGV4Q2FsbBIo",
+            "LmdycGMudGVzdGluZy5TdHJlYW1pbmdPdXRwdXRDYWxsUmVxdWVzdBopLmdy",
+            "cGMudGVzdGluZy5TdHJlYW1pbmdPdXRwdXRDYWxsUmVzcG9uc2UoATABEmkK",
+            "DkhhbGZEdXBsZXhDYWxsEiguZ3JwYy50ZXN0aW5nLlN0cmVhbWluZ091dHB1",
+            "dENhbGxSZXF1ZXN0GikuZ3JwYy50ZXN0aW5nLlN0cmVhbWluZ091dHB1dENh",
+            "bGxSZXNwb25zZSgBMAESPQoRVW5pbXBsZW1lbnRlZENhbGwSEy5ncnBjLnRl",
+            "c3RpbmcuRW1wdHkaEy5ncnBjLnRlc3RpbmcuRW1wdHkyVQoUVW5pbXBsZW1l",
+            "bnRlZFNlcnZpY2USPQoRVW5pbXBsZW1lbnRlZENhbGwSEy5ncnBjLnRlc3Rp",
+            "bmcuRW1wdHkaEy5ncnBjLnRlc3RpbmcuRW1wdHkyiQEKEFJlY29ubmVjdFNl",
+            "cnZpY2USOwoFU3RhcnQSHS5ncnBjLnRlc3RpbmcuUmVjb25uZWN0UGFyYW1z",
+            "GhMuZ3JwYy50ZXN0aW5nLkVtcHR5EjgKBFN0b3ASEy5ncnBjLnRlc3Rpbmcu",
+            "RW1wdHkaGy5ncnBjLnRlc3RpbmcuUmVjb25uZWN0SW5mb2IGcHJvdG8z"));
       descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
           new pbr::FileDescriptor[] { global::Grpc.Testing.EmptyReflection.Descriptor, global::Grpc.Testing.MessagesReflection.Descriptor, },
           new pbr::GeneratedClrTypeInfo(null, null));
diff --git a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
index 61f2ed4015a0234a2c47b2621906b9c5d447b44d..8d649bf5c521a9edc75cd82c32789d1ff1a56693 100644
--- a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
+++ b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
@@ -71,6 +71,13 @@ namespace Grpc.Testing {
         __Marshaller_SimpleRequest,
         __Marshaller_SimpleResponse);
 
+    static readonly Method<global::Grpc.Testing.SimpleRequest, global::Grpc.Testing.SimpleResponse> __Method_CacheableUnaryCall = new Method<global::Grpc.Testing.SimpleRequest, global::Grpc.Testing.SimpleResponse>(
+        MethodType.Unary,
+        __ServiceName,
+        "CacheableUnaryCall",
+        __Marshaller_SimpleRequest,
+        __Marshaller_SimpleResponse);
+
     static readonly Method<global::Grpc.Testing.StreamingOutputCallRequest, global::Grpc.Testing.StreamingOutputCallResponse> __Method_StreamingOutputCall = new Method<global::Grpc.Testing.StreamingOutputCallRequest, global::Grpc.Testing.StreamingOutputCallResponse>(
         MethodType.ServerStreaming,
         __ServiceName,
@@ -99,6 +106,13 @@ namespace Grpc.Testing {
         __Marshaller_StreamingOutputCallRequest,
         __Marshaller_StreamingOutputCallResponse);
 
+    static readonly Method<global::Grpc.Testing.Empty, global::Grpc.Testing.Empty> __Method_UnimplementedCall = new Method<global::Grpc.Testing.Empty, global::Grpc.Testing.Empty>(
+        MethodType.Unary,
+        __ServiceName,
+        "UnimplementedCall",
+        __Marshaller_Empty,
+        __Marshaller_Empty);
+
     /// <summary>Service descriptor</summary>
     public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor
     {
@@ -124,6 +138,16 @@ namespace Grpc.Testing {
         throw new RpcException(new Status(StatusCode.Unimplemented, ""));
       }
 
+      /// <summary>
+      ///  One request followed by one response. Response has cache control
+      ///  headers set such that a caching HTTP proxy (such as GFE) can
+      ///  satisfy subsequent requests.
+      /// </summary>
+      public virtual global::System.Threading.Tasks.Task<global::Grpc.Testing.SimpleResponse> CacheableUnaryCall(global::Grpc.Testing.SimpleRequest request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
       /// <summary>
       ///  One request followed by a sequence of responses (streamed download).
       ///  The server returns the payload with client desired type and sizes.
@@ -163,6 +187,15 @@ namespace Grpc.Testing {
         throw new RpcException(new Status(StatusCode.Unimplemented, ""));
       }
 
+      /// <summary>
+      ///  The test server will not implement this method. It will be used
+      ///  to test the behavior when clients call unimplemented methods.
+      /// </summary>
+      public virtual global::System.Threading.Tasks.Task<global::Grpc.Testing.Empty> UnimplementedCall(global::Grpc.Testing.Empty request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
     }
 
     /// <summary>Client for TestService</summary>
@@ -245,6 +278,42 @@ namespace Grpc.Testing {
         return CallInvoker.AsyncUnaryCall(__Method_UnaryCall, null, options, request);
       }
       /// <summary>
+      ///  One request followed by one response. Response has cache control
+      ///  headers set such that a caching HTTP proxy (such as GFE) can
+      ///  satisfy subsequent requests.
+      /// </summary>
+      public virtual global::Grpc.Testing.SimpleResponse CacheableUnaryCall(global::Grpc.Testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        return CacheableUnaryCall(request, new CallOptions(headers, deadline, cancellationToken));
+      }
+      /// <summary>
+      ///  One request followed by one response. Response has cache control
+      ///  headers set such that a caching HTTP proxy (such as GFE) can
+      ///  satisfy subsequent requests.
+      /// </summary>
+      public virtual global::Grpc.Testing.SimpleResponse CacheableUnaryCall(global::Grpc.Testing.SimpleRequest request, CallOptions options)
+      {
+        return CallInvoker.BlockingUnaryCall(__Method_CacheableUnaryCall, null, options, request);
+      }
+      /// <summary>
+      ///  One request followed by one response. Response has cache control
+      ///  headers set such that a caching HTTP proxy (such as GFE) can
+      ///  satisfy subsequent requests.
+      /// </summary>
+      public virtual AsyncUnaryCall<global::Grpc.Testing.SimpleResponse> CacheableUnaryCallAsync(global::Grpc.Testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        return CacheableUnaryCallAsync(request, new CallOptions(headers, deadline, cancellationToken));
+      }
+      /// <summary>
+      ///  One request followed by one response. Response has cache control
+      ///  headers set such that a caching HTTP proxy (such as GFE) can
+      ///  satisfy subsequent requests.
+      /// </summary>
+      public virtual AsyncUnaryCall<global::Grpc.Testing.SimpleResponse> CacheableUnaryCallAsync(global::Grpc.Testing.SimpleRequest request, CallOptions options)
+      {
+        return CallInvoker.AsyncUnaryCall(__Method_CacheableUnaryCall, null, options, request);
+      }
+      /// <summary>
       ///  One request followed by a sequence of responses (streamed download).
       ///  The server returns the payload with client desired type and sizes.
       /// </summary>
@@ -314,6 +383,38 @@ namespace Grpc.Testing {
       {
         return CallInvoker.AsyncDuplexStreamingCall(__Method_HalfDuplexCall, null, options);
       }
+      /// <summary>
+      ///  The test server will not implement this method. It will be used
+      ///  to test the behavior when clients call unimplemented methods.
+      /// </summary>
+      public virtual global::Grpc.Testing.Empty UnimplementedCall(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        return UnimplementedCall(request, new CallOptions(headers, deadline, cancellationToken));
+      }
+      /// <summary>
+      ///  The test server will not implement this method. It will be used
+      ///  to test the behavior when clients call unimplemented methods.
+      /// </summary>
+      public virtual global::Grpc.Testing.Empty UnimplementedCall(global::Grpc.Testing.Empty request, CallOptions options)
+      {
+        return CallInvoker.BlockingUnaryCall(__Method_UnimplementedCall, null, options, request);
+      }
+      /// <summary>
+      ///  The test server will not implement this method. It will be used
+      ///  to test the behavior when clients call unimplemented methods.
+      /// </summary>
+      public virtual AsyncUnaryCall<global::Grpc.Testing.Empty> UnimplementedCallAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        return UnimplementedCallAsync(request, new CallOptions(headers, deadline, cancellationToken));
+      }
+      /// <summary>
+      ///  The test server will not implement this method. It will be used
+      ///  to test the behavior when clients call unimplemented methods.
+      /// </summary>
+      public virtual AsyncUnaryCall<global::Grpc.Testing.Empty> UnimplementedCallAsync(global::Grpc.Testing.Empty request, CallOptions options)
+      {
+        return CallInvoker.AsyncUnaryCall(__Method_UnimplementedCall, null, options, request);
+      }
       /// <summary>Creates a new instance of client from given <c>ClientBaseConfiguration</c>.</summary>
       protected override TestServiceClient NewInstance(ClientBaseConfiguration configuration)
       {
@@ -327,10 +428,12 @@ namespace Grpc.Testing {
       return ServerServiceDefinition.CreateBuilder()
           .AddMethod(__Method_EmptyCall, serviceImpl.EmptyCall)
           .AddMethod(__Method_UnaryCall, serviceImpl.UnaryCall)
+          .AddMethod(__Method_CacheableUnaryCall, serviceImpl.CacheableUnaryCall)
           .AddMethod(__Method_StreamingOutputCall, serviceImpl.StreamingOutputCall)
           .AddMethod(__Method_StreamingInputCall, serviceImpl.StreamingInputCall)
           .AddMethod(__Method_FullDuplexCall, serviceImpl.FullDuplexCall)
-          .AddMethod(__Method_HalfDuplexCall, serviceImpl.HalfDuplexCall).Build();
+          .AddMethod(__Method_HalfDuplexCall, serviceImpl.HalfDuplexCall)
+          .AddMethod(__Method_UnimplementedCall, serviceImpl.UnimplementedCall).Build();
     }
 
   }
diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c
index 068bf709b81b8d55ea2469063e5e0afbb7a1c0f0..9a5d7869d34e0ae583f08313822b05fc4772bfc6 100644
--- a/src/csharp/ext/grpc_csharp_ext.c
+++ b/src/csharp/ext/grpc_csharp_ext.c
@@ -84,11 +84,6 @@ typedef struct grpcsharp_batch_context {
     size_t status_details_capacity;
   } recv_status_on_client;
   int recv_close_on_server_cancelled;
-  struct {
-    grpc_call *call;
-    grpc_call_details call_details;
-    grpc_metadata_array request_metadata;
-  } server_rpc_new;
 } grpcsharp_batch_context;
 
 GPR_EXPORT grpcsharp_batch_context *GPR_CALLTYPE grpcsharp_batch_context_create() {
@@ -97,6 +92,18 @@ GPR_EXPORT grpcsharp_batch_context *GPR_CALLTYPE grpcsharp_batch_context_create(
   return ctx;
 }
 
+typedef struct {
+  grpc_call *call;
+  grpc_call_details call_details;
+  grpc_metadata_array request_metadata;
+} grpcsharp_request_call_context;
+
+GPR_EXPORT grpcsharp_request_call_context *GPR_CALLTYPE grpcsharp_request_call_context_create() {
+  grpcsharp_request_call_context *ctx = gpr_malloc(sizeof(grpcsharp_request_call_context));
+  memset(ctx, 0, sizeof(grpcsharp_request_call_context));
+  return ctx;
+}
+
 /*
  * Destroys array->metadata.
  * The array pointer itself is not freed.
@@ -230,13 +237,20 @@ GPR_EXPORT void GPR_CALLTYPE grpcsharp_batch_context_destroy(grpcsharp_batch_con
       &(ctx->recv_status_on_client.trailing_metadata));
   gpr_free((void *)ctx->recv_status_on_client.status_details);
 
+  gpr_free(ctx);
+}
+
+GPR_EXPORT void GPR_CALLTYPE grpcsharp_request_call_context_destroy(grpcsharp_request_call_context *ctx) {
+  if (!ctx) {
+    return;
+  }
   /* NOTE: ctx->server_rpc_new.call is not destroyed because callback handler is
      supposed
      to take its ownership. */
 
-  grpc_call_details_destroy(&(ctx->server_rpc_new.call_details));
+  grpc_call_details_destroy(&(ctx->call_details));
   grpcsharp_metadata_array_destroy_metadata_only(
-      &(ctx->server_rpc_new.request_metadata));
+      &(ctx->request_metadata));
 
   gpr_free(ctx);
 }
@@ -303,32 +317,32 @@ grpcsharp_batch_context_recv_status_on_client_trailing_metadata(
   return &(ctx->recv_status_on_client.trailing_metadata);
 }
 
-GPR_EXPORT grpc_call *GPR_CALLTYPE grpcsharp_batch_context_server_rpc_new_call(
-    const grpcsharp_batch_context *ctx) {
-  return ctx->server_rpc_new.call;
+GPR_EXPORT grpc_call *GPR_CALLTYPE grpcsharp_request_call_context_call(
+    const grpcsharp_request_call_context *ctx) {
+  return ctx->call;
 }
 
 GPR_EXPORT const char *GPR_CALLTYPE
-grpcsharp_batch_context_server_rpc_new_method(
-    const grpcsharp_batch_context *ctx) {
-  return ctx->server_rpc_new.call_details.method;
+grpcsharp_request_call_context_method(
+    const grpcsharp_request_call_context *ctx) {
+  return ctx->call_details.method;
 }
 
-GPR_EXPORT const char *GPR_CALLTYPE grpcsharp_batch_context_server_rpc_new_host(
-    const grpcsharp_batch_context *ctx) {
-  return ctx->server_rpc_new.call_details.host;
+GPR_EXPORT const char *GPR_CALLTYPE grpcsharp_request_call_context_host(
+    const grpcsharp_request_call_context *ctx) {
+  return ctx->call_details.host;
 }
 
 GPR_EXPORT gpr_timespec GPR_CALLTYPE
-grpcsharp_batch_context_server_rpc_new_deadline(
-    const grpcsharp_batch_context *ctx) {
-  return ctx->server_rpc_new.call_details.deadline;
+grpcsharp_request_call_context_deadline(
+    const grpcsharp_request_call_context *ctx) {
+  return ctx->call_details.deadline;
 }
 
 GPR_EXPORT const grpc_metadata_array *GPR_CALLTYPE
-grpcsharp_batch_context_server_rpc_new_request_metadata(
-    const grpcsharp_batch_context *ctx) {
-  return &(ctx->server_rpc_new.request_metadata);
+grpcsharp_request_call_context_request_metadata(
+    const grpcsharp_request_call_context *ctx) {
+  return &(ctx->request_metadata);
 }
 
 GPR_EXPORT int32_t GPR_CALLTYPE
@@ -853,10 +867,10 @@ GPR_EXPORT void GPR_CALLTYPE grpcsharp_server_destroy(grpc_server *server) {
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE
 grpcsharp_server_request_call(grpc_server *server, grpc_completion_queue *cq,
-                              grpcsharp_batch_context *ctx) {
+                              grpcsharp_request_call_context *ctx) {
   return grpc_server_request_call(
-      server, &(ctx->server_rpc_new.call), &(ctx->server_rpc_new.call_details),
-      &(ctx->server_rpc_new.request_metadata), cq, cq, ctx);
+      server, &(ctx->call), &(ctx->call_details),
+      &(ctx->request_metadata), cq, cq, ctx);
 }
 
 /* Security */
diff --git a/src/node/interop/interop_client.js b/src/node/interop/interop_client.js
index e8f2d37bd8b0e891ce36d1fc7b2b10dce9a66c85..a59a66b2aa9dc173a6b7ef7fc303e75ba7dd91f3 100644
--- a/src/node/interop/interop_client.js
+++ b/src/node/interop/interop_client.js
@@ -375,7 +375,8 @@ function statusCodeAndMessage(client, done) {
   duplex.end();
 }
 
-function unimplementedMethod(client, done) {
+// NOTE: the client param to this function is from UnimplementedService
+function unimplementedService(client, done) {
   client.unimplementedCall({}, function(err, resp) {
     assert(err);
     assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
@@ -384,6 +385,15 @@ function unimplementedMethod(client, done) {
   });
 }
 
+// NOTE: the client param to this function is from TestService
+function unimplementedMethod(client, done) {
+  client.unimplementedCall({}, function(err, resp) {
+    assert(err);
+    assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
+    done();
+  });
+}
+
 /**
  * Run one of the authentication tests.
  * @param {string} expected_user The expected username in the response
@@ -527,8 +537,10 @@ var test_cases = {
                     Client: testProto.TestService},
   status_code_and_message: {run: statusCodeAndMessage,
                             Client: testProto.TestService},
-  unimplemented_method: {run: unimplementedMethod,
+  unimplemented_service: {run: unimplementedService,
                          Client: testProto.UnimplementedService},
+  unimplemented_method: {run: unimplementedMethod,
+                         Client: testProto.TestService},
   compute_engine_creds: {run: computeEngineCreds,
                          Client: testProto.TestService,
                          getCreds: getApplicationCreds},
diff --git a/src/node/test/interop_sanity_test.js b/src/node/test/interop_sanity_test.js
index f008a87585cbc9776116b0a032c5f738b181595e..58f8842c0d8a66a0438cd3231ee969d2b82d997c 100644
--- a/src/node/test/interop_sanity_test.js
+++ b/src/node/test/interop_sanity_test.js
@@ -98,6 +98,10 @@ describe('Interop tests', function() {
     interop_client.runTest(port, name_override, 'status_code_and_message',
                            true, true, done);
   });
+  it('should pass unimplemented_service', function(done) {
+    interop_client.runTest(port, name_override, 'unimplemented_service',
+                           true, true, done);
+  });
   it('should pass unimplemented_method', function(done) {
     interop_client.runTest(port, name_override, 'unimplemented_method',
                            true, true, done);
diff --git a/test/cpp/interop/interop_test.cc b/test/cpp/interop/interop_test.cc
index 8e71a2bb295f72087c02dfea48f2fb30ded0fa36..c066598d363d2e57398aa700a11ff144762f0538 100644
--- a/test/cpp/interop/interop_test.cc
+++ b/test/cpp/interop/interop_test.cc
@@ -44,17 +44,21 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <gflags/gflags.h>
 #include <grpc/support/alloc.h>
 #include <grpc/support/host_port.h>
 #include <grpc/support/log.h>
 #include <grpc/support/string_util.h>
 #include "test/core/util/port.h"
+#include "test/cpp/util/test_config.h"
 
 extern "C" {
 #include "src/core/lib/iomgr/socket_utils_posix.h"
 #include "src/core/lib/support/string.h"
 }
 
+DEFINE_string(extra_server_flags, "", "Extra flags to pass to server.");
+
 int test_client(const char* root, const char* host, int port) {
   int status;
   pid_t cli;
@@ -80,6 +84,7 @@ int test_client(const char* root, const char* host, int port) {
 }
 
 int main(int argc, char** argv) {
+  grpc::testing::InitTest(&argc, &argv, true);
   char* me = argv[0];
   char* lslash = strrchr(me, '/');
   char root[1024];
@@ -105,15 +110,19 @@ int main(int argc, char** argv) {
   /* start the server */
   svr = fork();
   if (svr == 0) {
-    char* binary_path;
-    char* port_arg;
-    gpr_asprintf(&binary_path, "%s/interop_server", root);
-    gpr_asprintf(&port_arg, "--port=%d", port);
-
-    execl(binary_path, binary_path, port_arg, NULL);
-
-    gpr_free(binary_path);
-    gpr_free(port_arg);
+    const size_t num_args = 3 + !FLAGS_extra_server_flags.empty();
+    char** args = (char**)gpr_malloc(sizeof(char*) * num_args);
+    memset(args, 0, sizeof(char*) * num_args);
+    gpr_asprintf(&args[0], "%s/interop_server", root);
+    gpr_asprintf(&args[1], "--port=%d", port);
+    if (!FLAGS_extra_server_flags.empty()) {
+      args[2] = gpr_strdup(FLAGS_extra_server_flags.c_str());
+    }
+    execv(args[0], args);
+    for (size_t i = 0; i < num_args - 1; ++i) {
+      gpr_free(args[i]);
+    }
+    gpr_free(args);
     return 1;
   }
   /* wait a little */
diff --git a/test/cpp/interop/stress_test.cc b/test/cpp/interop/stress_test.cc
index 5647cb5531ce85cbd985dc471b40546b2a519cc1..46d09b7f285010665092191de8790448c721c431 100644
--- a/test/cpp/interop/stress_test.cc
+++ b/test/cpp/interop/stress_test.cc
@@ -46,6 +46,7 @@
 
 #include "src/proto/grpc/testing/metrics.grpc.pb.h"
 #include "src/proto/grpc/testing/metrics.pb.h"
+#include "test/cpp/interop/client_helper.h"
 #include "test/cpp/interop/interop_client.h"
 #include "test/cpp/interop/stress_interop_client.h"
 #include "test/cpp/util/metrics_server.h"
@@ -68,7 +69,8 @@ DEFINE_int32(test_duration_secs, -1,
 
 DEFINE_string(server_addresses, "localhost:8080",
               "The list of server"
-              " addresses in the format:\n"
+              "addresses. This option is ignored if either\n"
+              "server_port or server_host is specified. The format is: \n"
               " \"<name_1>:<port_1>,<name_2>:<port_1>...<name_N>:<port_N>\"\n"
               " Note: <name> can be servername or IP address.");
 
@@ -79,6 +81,34 @@ DEFINE_int32(num_stubs_per_channel, 1,
              "indicates the max number of parallel RPC calls on each channel "
              "at any given time.");
 
+DEFINE_string(test_case, "",
+              "Configure different test cases. Valid options are:\n\n"
+              "all : all test cases;\n"
+              "cancel_after_begin : cancel stream after starting it;\n"
+              "cancel_after_first_response: cancel on first response;\n"
+              "client_compressed_streaming : compressed request streaming with "
+              "client_compressed_unary : single compressed request;\n"
+              "client_streaming : request streaming with single response;\n"
+              "compute_engine_creds: large_unary with compute engine auth;\n"
+              "custom_metadata: server will echo custom metadata;\n"
+              "empty_stream : bi-di stream with no request/response;\n"
+              "empty_unary : empty (zero bytes) request and response;\n"
+              "half_duplex : half-duplex streaming;\n"
+              "jwt_token_creds: large_unary with JWT token auth;\n"
+              "large_unary : single request and (large) response;\n"
+              "oauth2_auth_token: raw oauth2 access token auth;\n"
+              "per_rpc_creds: raw oauth2 access token on a single rpc;\n"
+              "ping_pong : full-duplex streaming;\n"
+              "response streaming;\n"
+              "server_compressed_streaming : single request with compressed "
+              "server_compressed_unary : single compressed response;\n"
+              "server_streaming : single request with response streaming;\n"
+              "slow_consumer : single request with response streaming with "
+              "slow client consumer;\n"
+              "status_code_and_message: verify status code & message;\n"
+              "timeout_on_sleeping_server: deadline exceeds on stream;\n"
+              "unimplemented_method: client calls an unimplemented_method;\n");
+
 // TODO(sreek): Add more test cases here in future
 DEFINE_string(test_cases, "",
               "List of test cases to call along with the"
@@ -115,6 +145,17 @@ DEFINE_bool(do_not_abort_on_transient_failures, true,
             "If set to 'true', abort() is not called in case of transient "
             "failures like temporary connection failures.");
 
+// Options from client.cc (for compatibility with interop test).
+// TODO(sreek): Consolidate overlapping options
+DEFINE_bool(use_tls, false, "Whether to use tls.");
+DEFINE_bool(use_test_ca, false, "False to use SSL roots for google");
+DEFINE_int32(server_port, 0, "Server port.");
+DEFINE_string(server_host, "127.0.0.1", "Server host to connect to");
+DEFINE_string(server_host_override, "foo.test.google.fr",
+              "Override the server host which is sent in HTTP header");
+DEFINE_string(service_account_key_file, "",
+              "Path to service account json key file.");
+
 using grpc::testing::kTestCaseList;
 using grpc::testing::MetricsService;
 using grpc::testing::MetricsServiceImpl;
@@ -200,6 +241,8 @@ bool ParseTestCasesString(const grpc::string& test_cases,
 void LogParameterInfo(const std::vector<grpc::string>& addresses,
                       const std::vector<std::pair<TestCaseType, int>>& tests) {
   gpr_log(GPR_INFO, "server_addresses: %s", FLAGS_server_addresses.c_str());
+  gpr_log(GPR_INFO, "server_host: %s", FLAGS_server_host.c_str());
+  gpr_log(GPR_INFO, "server_port: %d", FLAGS_server_port);
   gpr_log(GPR_INFO, "test_cases : %s", FLAGS_test_cases.c_str());
   gpr_log(GPR_INFO, "sleep_duration_ms: %d", FLAGS_sleep_duration_ms);
   gpr_log(GPR_INFO, "test_duration_secs: %d", FLAGS_test_duration_secs);
@@ -243,11 +286,24 @@ int main(int argc, char** argv) {
 
   // Parse the server addresses
   std::vector<grpc::string> server_addresses;
-  ParseCommaDelimitedString(FLAGS_server_addresses, server_addresses);
+  if (FLAGS_server_port != 0) {
+    // We are using interop_client style cmdline options.
+    const int host_port_buf_size = 1024;
+    char host_port[host_port_buf_size];
+    snprintf(host_port, host_port_buf_size, "%s:%d", FLAGS_server_host.c_str(),
+             FLAGS_server_port);
+    std::string host_port_str(host_port);
+    ParseCommaDelimitedString(host_port_str, server_addresses);
+  } else {
+    ParseCommaDelimitedString(FLAGS_server_addresses, server_addresses);
+  }
 
   // Parse test cases and weights
   if (FLAGS_test_cases.length() == 0) {
-    gpr_log(GPR_ERROR, "Not running tests. The 'test_cases' string is empty");
+    // We are using interop_client style test_case option
+    FLAGS_test_cases = FLAGS_test_case + ":100";
+  } else if (FLAGS_test_case != "") {
+    gpr_log(GPR_ERROR, "specify --test_case or --test_cases but not both.");
     return 1;
   }
 
@@ -283,9 +339,14 @@ int main(int argc, char** argv) {
     // Create channel(s) for each server
     for (int channel_idx = 0; channel_idx < FLAGS_num_channels_per_server;
          channel_idx++) {
-      // TODO (sreek). This won't work for tests that require Authentication
-      std::shared_ptr<grpc::Channel> channel(
-          grpc::CreateChannel(*it, grpc::InsecureChannelCredentials()));
+      gpr_log(GPR_INFO, "Starting test with %s channel_idx=%d..", it->c_str(),
+              channel_idx);
+      std::shared_ptr<grpc::Channel> channel;
+      if (FLAGS_use_tls) {
+        channel = grpc::testing::CreateChannelForTestCase(FLAGS_test_case);
+      } else {
+        channel = grpc::CreateChannel(*it, grpc::InsecureChannelCredentials());
+      }
 
       // Create stub(s) for each channel
       for (int stub_idx = 0; stub_idx < FLAGS_num_stubs_per_channel;
@@ -311,8 +372,10 @@ int main(int argc, char** argv) {
   }
 
   // Start metrics server before waiting for the stress test threads
-  std::unique_ptr<grpc::Server> metrics_server =
-      metrics_service.StartServer(FLAGS_metrics_port);
+  if (FLAGS_metrics_port > 0) {
+    std::unique_ptr<grpc::Server> metrics_server =
+        metrics_service.StartServer(FLAGS_metrics_port);
+  }
 
   // Wait for the stress test threads to complete
   for (auto it = test_threads.begin(); it != test_threads.end(); it++) {
diff --git a/tools/run_tests/run_performance_tests.py b/tools/run_tests/run_performance_tests.py
index 92149115fe2387ac6e2d79cef5a54d4c67ec1339..099ab89ddffb89fac4fb28a68d2534daff51da90 100755
--- a/tools/run_tests/run_performance_tests.py
+++ b/tools/run_tests/run_performance_tests.py
@@ -91,12 +91,11 @@ def create_qpsworker_job(language, shortname=None,
   else:
     host_and_port='localhost:%s' % port
 
-  # TODO(jtattermusch): with some care, we can calculate the right timeout
-  # of a worker from the sum of warmup + benchmark times for all the scenarios
   jobspec = jobset.JobSpec(
       cmdline=cmdline,
       shortname=shortname,
-      timeout_seconds=2*60*60)
+      timeout_seconds=5*60,  # workers get restarted after each scenario
+      verbose_success=True)
   return QpsWorkerJob(jobspec, language, host_and_port)
 
 
@@ -357,6 +356,7 @@ def create_scenarios(languages, workers_by_lang, remote_host=None, regex='.*',
 def finish_qps_workers(jobs):
   """Waits for given jobs to finish and eventually kills them."""
   retries = 0
+  num_killed = 0
   while any(job.is_running() for job in jobs):
     for job in qpsworker_jobs:
       if job.is_running():
@@ -365,10 +365,11 @@ def finish_qps_workers(jobs):
       print('Killing all QPS workers.')
       for job in jobs:
         job.kill()
+        num_killed += 1
     retries += 1
     time.sleep(3)
   print('All QPS workers finished.')
-
+  return num_killed
 
 argp = argparse.ArgumentParser(description='Run performance tests.')
 argp.add_argument('-l', '--language',
@@ -450,6 +451,8 @@ scenarios = create_scenarios(languages,
 if not scenarios:
   raise Exception('No scenarios to run')
 
+total_scenario_failures = 0
+qps_workers_killed = 0
 for scenario in scenarios:
   if args.dry_run:
     print(scenario.name)
@@ -457,8 +460,14 @@ for scenario in scenarios:
     try:
       for worker in scenario.workers:
         worker.start()
-      jobset.run([scenario.jobspec,
-                  create_quit_jobspec(scenario.workers, remote_host=args.remote_driver_host)],
-                 newline_on_success=True, maxjobs=1)
+      scenario_failures, _ = jobset.run([scenario.jobspec,
+                                 create_quit_jobspec(scenario.workers, remote_host=args.remote_driver_host)],
+                                 newline_on_success=True, maxjobs=1)
+      total_scenario_failures += scenario_failures
     finally:
-      finish_qps_workers(scenario.workers)
+      # Consider qps workers that need to be killed as failures
+      qps_workers_killed += finish_qps_workers(scenario.workers)
+
+if total_scenario_failures > 0 or qps_workers_killed > 0:
+  print ("%s scenarios failed and %s qps worker jobs killed" % (total_scenario_failures, qps_workers_killed))
+  sys.exit(1)
diff --git a/tools/run_tests/sources_and_headers.json b/tools/run_tests/sources_and_headers.json
index 2122e9632ab00b8d370f3d66e2e0354896948ae8..02cf4eb00f881c0330cc91029984095c08fe87d6 100644
--- a/tools/run_tests/sources_and_headers.json
+++ b/tools/run_tests/sources_and_headers.json
@@ -2695,6 +2695,7 @@
       "gpr", 
       "gpr_test_util", 
       "grpc", 
+      "grpc++_test_config", 
       "grpc_test_util"
     ], 
     "headers": [], 
@@ -3135,6 +3136,7 @@
     "language": "c++", 
     "name": "stress_test", 
     "src": [
+      "test/cpp/interop/client_helper.cc", 
       "test/cpp/interop/client_helper.h", 
       "test/cpp/interop/interop_client.cc", 
       "test/cpp/interop/interop_client.h", 
@@ -4966,6 +4968,27 @@
     "third_party": false, 
     "type": "lib"
   }, 
+  {
+    "deps": [
+      "gpr", 
+      "grpc++_base", 
+      "grpc++_codegen_base", 
+      "grpc++_codegen_base_src", 
+      "grpc_cronet"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "grpc++_cronet", 
+    "src": [
+      "src/cpp/client/cronet_credentials.cc", 
+      "src/cpp/client/insecure_credentials.cc", 
+      "src/cpp/common/insecure_create_auth_context.cc", 
+      "src/cpp/server/insecure_server_credentials.cc"
+    ], 
+    "third_party": false, 
+    "type": "lib"
+  }, 
   {
     "deps": [
       "grpc++", 
diff --git a/vsprojects/vcxproj/test/stress_test/stress_test.vcxproj b/vsprojects/vcxproj/test/stress_test/stress_test.vcxproj
index ba2ed468b99e9c4c9abfb8169721358bf2bd0593..8e1b6bb3a6629fef2630e7d63ed81ec618823408 100644
--- a/vsprojects/vcxproj/test/stress_test/stress_test.vcxproj
+++ b/vsprojects/vcxproj/test/stress_test/stress_test.vcxproj
@@ -198,6 +198,8 @@
     </ClCompile>
     <ClInclude Include="$(SolutionDir)\..\src\proto\grpc\testing\test.grpc.pb.h">
     </ClInclude>
+    <ClCompile Include="$(SolutionDir)\..\test\cpp\interop\client_helper.cc">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\test\cpp\interop\interop_client.cc">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\test\cpp\interop\stress_interop_client.cc">
diff --git a/vsprojects/vcxproj/test/stress_test/stress_test.vcxproj.filters b/vsprojects/vcxproj/test/stress_test/stress_test.vcxproj.filters
index 08367b79af39b1e535ff9a1f39eaae87f0f7fe71..476c04ec956c44706d837303baad9727bcb73496 100644
--- a/vsprojects/vcxproj/test/stress_test/stress_test.vcxproj.filters
+++ b/vsprojects/vcxproj/test/stress_test/stress_test.vcxproj.filters
@@ -13,6 +13,9 @@
     <ClCompile Include="$(SolutionDir)\..\src\proto\grpc\testing\test.proto">
       <Filter>src\proto\grpc\testing</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\test\cpp\interop\client_helper.cc">
+      <Filter>test\cpp\interop</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\test\cpp\interop\interop_client.cc">
       <Filter>test\cpp\interop</Filter>
     </ClCompile>