Merge pull request #15203 from sreecha/fd-trace

fd tracing support
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 24587a5..5b2ac80 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,12 +1,12 @@
-Please answer these questions before submitting your issue. 
- 
-### Should this be an issue in the gRPC issue tracker?
- 
-Create new issues for bugs and feature requests. An issue needs to be actionable. General gRPC discussions and usage questions belong to:
-- [grpc.io mailing list](https://groups.google.com/forum/#!forum/grpc-io)
-- [StackOverflow, with `grpc` tag](http://stackoverflow.com/questions/tagged/grpc)
- 
-*Please don't double post your questions in more locations; we are monitoring both channels, and the time spent de-duplicating questions is better spent answering more user questions.*
+<!--
+
+This form is for bug reports and feature requests ONLY!
+For general questions and troubleshooting, please ask/look for answers here:
+- grpc.io mailing list: https://groups.google.com/forum/#!forum/grpc-io
+- StackOverflow, with "grpc" tag: http://stackoverflow.com/questions/tagged/grpc
+
+Issues specific to *grpc-java*, *grpc-go*, *grpc-node*, *grpc-dart*, *grpc-web* should be created in the repository they belong to (e.g. https://github.com/grpc/grpc-LANGUAGE/issues/new)
+-->
  
 ### What version of gRPC and what language are you using?
  
diff --git a/BUILD b/BUILD
index e04d0df..c35a7d6 100644
--- a/BUILD
+++ b/BUILD
@@ -64,11 +64,11 @@
 )
 
 # This should be updated along with build.yaml
-g_stands_for = "glorious"
+g_stands_for = "gloriosa"
 
 core_version = "6.0.0-dev"
 
-version = "1.12.0-dev"
+version = "1.13.0-dev"
 
 GPR_PUBLIC_HDRS = [
     "include/grpc/support/alloc.h",
@@ -1183,6 +1183,7 @@
     ],
     hdrs = [
         "src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h",
+        "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h",
         "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h",
         "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h",
         "src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h",
@@ -1211,6 +1212,7 @@
     ],
     hdrs = [
         "src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h",
+        "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h",
         "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h",
         "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h",
         "src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h",
@@ -1230,9 +1232,6 @@
 
 grpc_cc_library(
     name = "grpc_lb_subchannel_list",
-    srcs = [
-        "src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc",
-    ],
     hdrs = [
         "src/core/ext/filters/client_channel/lb_policy/subchannel_list.h",
     ],
@@ -1286,6 +1285,20 @@
 )
 
 grpc_cc_library(
+    name = "lb_load_data_store",
+    srcs = [
+        "src/cpp/server/load_reporter/load_data_store.cc",
+    ],
+    hdrs = [
+        "src/cpp/server/load_reporter/load_data_store.h",
+    ],
+    language = "c++",
+    deps = [
+        "grpc++",
+    ],
+)
+
+grpc_cc_library(
     name = "grpc_resolver_dns_native",
     srcs = [
         "src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc",
@@ -1375,6 +1388,7 @@
         "src/core/lib/surface/init_secure.cc",
     ],
     hdrs = [
+        "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h",
         "src/core/lib/security/context/security_context.h",
         "src/core/lib/security/credentials/alts/alts_credentials.h",
         "src/core/lib/security/credentials/composite/composite_credentials.h",
@@ -1611,11 +1625,9 @@
     name = "tsi_interface",
     srcs = [
         "src/core/tsi/transport_security.cc",
-        "src/core/tsi/transport_security_adapter.cc",
     ],
     hdrs = [
         "src/core/tsi/transport_security.h",
-        "src/core/tsi/transport_security_adapter.h",
         "src/core/tsi/transport_security_interface.h",
     ],
     language = "c++",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3e0a93a..93f51e7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,7 +24,7 @@
 cmake_minimum_required(VERSION 2.8)
 
 set(PACKAGE_NAME      "grpc")
-set(PACKAGE_VERSION   "1.12.0-dev")
+set(PACKAGE_VERSION   "1.13.0-dev")
 set(PACKAGE_STRING    "${PACKAGE_NAME} ${PACKAGE_VERSION}")
 set(PACKAGE_TARNAME   "${PACKAGE_NAME}-${PACKAGE_VERSION}")
 set(PACKAGE_BUGREPORT "https://github.com/grpc/grpc/issues/")
@@ -34,6 +34,7 @@
 set(gRPC_INSTALL_LIBDIR "lib" CACHE STRING "Installation directory for libraries")
 set(gRPC_INSTALL_INCLUDEDIR "include" CACHE STRING "Installation directory for headers")
 set(gRPC_INSTALL_CMAKEDIR "lib/cmake/${PACKAGE_NAME}" CACHE STRING "Installation directory for cmake config files")
+set(gRPC_INSTALL_SHAREDIR "share/grpc" CACHE STRING "Installation directory for root certificates")
 
 # Options
 option(gRPC_BUILD_TESTS "Build tests" OFF)
@@ -454,6 +455,7 @@
 add_dependencies(buildtests_c h2_uds_nosec_test)
 endif()
 add_dependencies(buildtests_c inproc_nosec_test)
+add_dependencies(buildtests_c alts_credentials_fuzzer_one_entry)
 add_dependencies(buildtests_c api_fuzzer_one_entry)
 add_dependencies(buildtests_c client_fuzzer_one_entry)
 add_dependencies(buildtests_c hpack_parser_fuzzer_test_one_entry)
@@ -584,6 +586,7 @@
 if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
 add_dependencies(buildtests_cxx json_run_localhost)
 endif()
+add_dependencies(buildtests_cxx lb_load_data_store_test)
 add_dependencies(buildtests_cxx memory_test)
 add_dependencies(buildtests_cxx metrics_client)
 add_dependencies(buildtests_cxx mock_test)
@@ -611,6 +614,9 @@
 endif()
 add_dependencies(buildtests_cxx server_builder_plugin_test)
 add_dependencies(buildtests_cxx server_builder_test)
+if(_gRPC_PLATFORM_POSIX)
+add_dependencies(buildtests_cxx server_builder_with_socket_mutator_test)
+endif()
 add_dependencies(buildtests_cxx server_context_test_spouse_test)
 if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
 add_dependencies(buildtests_cxx server_crash_test)
@@ -1145,7 +1151,6 @@
   third_party/nanopb/pb_decode.c
   third_party/nanopb/pb_encode.c
   src/core/tsi/transport_security.cc
-  src/core/tsi/transport_security_adapter.cc
   src/core/ext/transport/chttp2/client/insecure/channel_create.cc
   src/core/ext/transport/chttp2/client/insecure/channel_create_posix.cc
   src/core/ext/transport/chttp2/client/authority.cc
@@ -1193,7 +1198,6 @@
   src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/load_balancer.pb.c
   src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc
   src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc
-  src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc
   src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc
@@ -1565,7 +1569,6 @@
   third_party/nanopb/pb_decode.c
   third_party/nanopb/pb_encode.c
   src/core/tsi/transport_security.cc
-  src/core/tsi/transport_security_adapter.cc
   src/core/ext/transport/chttp2/client/insecure/channel_create.cc
   src/core/ext/transport/chttp2/client/insecure/channel_create_posix.cc
   src/core/ext/transport/chttp2/client/authority.cc
@@ -1672,6 +1675,7 @@
   test/core/end2end/fixtures/proxy.cc
   test/core/iomgr/endpoint_tests.cc
   test/core/util/debugger_macros.cc
+  test/core/util/fuzzer_util.cc
   test/core/util/grpc_profiler.cc
   test/core/util/histogram.cc
   test/core/util/memory_counters.cc
@@ -1975,6 +1979,7 @@
   test/core/end2end/fixtures/proxy.cc
   test/core/iomgr/endpoint_tests.cc
   test/core/util/debugger_macros.cc
+  test/core/util/fuzzer_util.cc
   test/core/util/grpc_profiler.cc
   test/core/util/histogram.cc
   test/core/util/memory_counters.cc
@@ -2499,7 +2504,6 @@
   third_party/nanopb/pb_decode.c
   third_party/nanopb/pb_encode.c
   src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc
-  src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc
   src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc
   src/core/ext/census/grpc_context.cc
   src/core/ext/filters/max_age/max_age_filter.cc
@@ -4975,6 +4979,49 @@
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 
+add_library(lb_load_data_store
+  src/cpp/server/load_reporter/load_data_store.cc
+)
+
+if(WIN32 AND MSVC)
+  set_target_properties(lb_load_data_store PROPERTIES COMPILE_PDB_NAME "lb_load_data_store"
+    COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
+  )
+  if (gRPC_INSTALL)
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lb_load_data_store.pdb
+      DESTINATION ${gRPC_INSTALL_LIBDIR} OPTIONAL
+    )
+  endif()
+endif()
+
+
+target_include_directories(lb_load_data_store
+  PUBLIC $<INSTALL_INTERFACE:${gRPC_INSTALL_INCLUDEDIR}> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
+  PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
+  PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
+  PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
+  PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+  PRIVATE third_party/googletest/googletest/include
+  PRIVATE third_party/googletest/googletest
+  PRIVATE third_party/googletest/googlemock/include
+  PRIVATE third_party/googletest/googlemock
+  PRIVATE ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(lb_load_data_store
+  ${_gRPC_PROTOBUF_LIBRARIES}
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc++
+)
+
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
 if (gRPC_BUILD_CODEGEN)
 add_library(qps
   ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/messages.pb.cc
@@ -12271,6 +12318,46 @@
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 
+add_executable(lb_load_data_store_test
+  test/cpp/server/load_reporter/load_data_store_test.cc
+  third_party/googletest/googletest/src/gtest-all.cc
+  third_party/googletest/googlemock/src/gmock-all.cc
+)
+
+
+target_include_directories(lb_load_data_store_test
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
+  PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
+  PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
+  PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
+  PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+  PRIVATE third_party/googletest/googletest/include
+  PRIVATE third_party/googletest/googletest
+  PRIVATE third_party/googletest/googlemock/include
+  PRIVATE third_party/googletest/googlemock
+  PRIVATE ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(lb_load_data_store_test
+  ${_gRPC_PROTOBUF_LIBRARIES}
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  lb_load_data_store
+  grpc++_test_util
+  grpc_test_util
+  grpc++
+  grpc
+  gpr_test_util
+  gpr
+  ${_gRPC_GFLAGS_LIBRARIES}
+)
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
 add_executable(memory_test
   test/core/gprpp/memory_test.cc
   third_party/googletest/googletest/src/gtest-all.cc
@@ -13161,6 +13248,61 @@
 
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
+if(_gRPC_PLATFORM_POSIX)
+
+add_executable(server_builder_with_socket_mutator_test
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.grpc.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.grpc.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.grpc.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.grpc.pb.h
+  test/cpp/server/server_builder_with_socket_mutator_test.cc
+  third_party/googletest/googletest/src/gtest-all.cc
+  third_party/googletest/googlemock/src/gmock-all.cc
+)
+
+protobuf_generate_grpc_cpp(
+  src/proto/grpc/testing/echo_messages.proto
+)
+protobuf_generate_grpc_cpp(
+  src/proto/grpc/testing/echo.proto
+)
+
+target_include_directories(server_builder_with_socket_mutator_test
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
+  PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
+  PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
+  PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
+  PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+  PRIVATE third_party/googletest/googletest/include
+  PRIVATE third_party/googletest/googletest
+  PRIVATE third_party/googletest/googlemock/include
+  PRIVATE third_party/googletest/googlemock
+  PRIVATE ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(server_builder_with_socket_mutator_test
+  ${_gRPC_PROTOBUF_LIBRARIES}
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc++_test_util_unsecure
+  grpc_test_util_unsecure
+  gpr_test_util
+  grpc++_unsecure
+  grpc_unsecure
+  gpr
+  ${_gRPC_GFLAGS_LIBRARIES}
+)
+
+endif()
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
 
 add_executable(server_context_test_spouse_test
   test/cpp/test/server_context_test_spouse_test.cc
@@ -15630,6 +15772,35 @@
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 
+add_executable(alts_credentials_fuzzer_one_entry
+  test/core/security/alts_credentials_fuzzer.cc
+  test/core/util/one_corpus_entry_fuzzer.cc
+)
+
+
+target_include_directories(alts_credentials_fuzzer_one_entry
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
+  PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
+  PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
+  PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
+  PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+)
+
+target_link_libraries(alts_credentials_fuzzer_one_entry
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc_test_util
+  grpc
+  gpr_test_util
+  gpr
+)
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
 add_executable(api_fuzzer_one_entry
   test/core/end2end/fuzzers/api_fuzzer.cc
   test/core/util/one_corpus_entry_fuzzer.cc
@@ -16026,3 +16197,6 @@
     DESTINATION ${gRPC_INSTALL_CMAKEDIR}
   )
 endforeach()
+
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/etc/roots.pem
+  DESTINATION ${gRPC_INSTALL_SHAREDIR})
diff --git a/INSTALL.md b/INSTALL.md
index dde937e..a61af34 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -39,8 +39,8 @@
  $ [sudo] xcode-select --install
 ```
 
-To build gRPC from source, you may also need to install the following
-packages, which you can get from [Homebrew](https://brew.sh):
+To build gRPC from source, you may need to install the following
+packages from [Homebrew](https://brew.sh):
 
 ```sh
  $ brew install autoconf automake libtool shtool
@@ -66,11 +66,11 @@
 you will need the `protoc` compiler to generate stub server and client code.
 
 If you compile gRPC from source, as described below, the Makefile will
-automatically try and compile the `protoc` in third_party if you cloned the
-repository recursively and it detects that you don't already have it
+automatically try compiling the `protoc` in third_party if you cloned the
+repository recursively and it detects that you do not already have 'protoc' compiler
 installed.
 
-If it hasn't been installed, you can run the following commands to install it.
+If 'protoc' compiler has not been installed, following commands can be used for installation.
 
 ```sh
 $ cd grpc/third_party/protobuf
@@ -79,7 +79,7 @@
 
 # Build from Source
 
-For developers who are interested to contribute, here is how to compile the
+For developers who are interested to contribute, the following commands show how to compile the
 gRPC C Core library.
 
 ```sh
@@ -123,7 +123,7 @@
 cmake will generate a solution (`grpc.sln`) that contains a VS project for 
 every target defined in `CMakeLists.txt` (+ few extra convenience projects
 added automatically by cmake). After opening the solution with Visual Studio 
-you will be able to browse and build the code as usual.
+you will be able to browse and build the code.
 ```
 > @rem Run from grpc directory after cloning the repo with --recursive or updating submodules.
 > md .build
@@ -133,7 +133,7 @@
 ```
 
 #### cmake: Using Ninja (faster build, supports boringssl's assembly optimizations).
-Please note that when using Ninja, you'll still need Visual C++ (part of Visual Studio)
+Please note that when using Ninja, you will still need Visual C++ (part of Visual Studio)
 installed to be able to compile the C/C++ sources.
 ```
 > @rem Run from grpc directory after cloning the repo with --recursive or updating submodules.
@@ -142,13 +142,12 @@
 > call "%VS140COMNTOOLS%..\..\VC\vcvarsall.bat" x64
 > cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release
 > cmake --build .
-> ninja install
 ```
 
 ### msys2 (with mingw)
 
 The Makefile (and source code) should support msys2's mingw32 and mingw64
-compilers. Building with msys2's native compiler is also possible, but
+compilers. Building with msys2's native compiler is possible, but
 difficult.
 
 This approach requires having [msys2](https://msys2.github.io/) installed.
@@ -165,7 +164,7 @@
 MINGW64$ make
 ```
 
-NOTE: While most of the make targets are buildable under Mingw, some haven't been ported to Windows yet
+NOTE: Though most of the make targets are buildable under Mingw, some haven't been ported to Windows yet
 and may fail to build (mostly trying to include POSIX headers not available on Mingw).
 
 ### Pre-generated Visual Studio solution (DELETED)
diff --git a/Makefile b/Makefile
index c646826..ba765f0 100644
--- a/Makefile
+++ b/Makefile
@@ -421,8 +421,8 @@
 endif
 
 CORE_VERSION = 6.0.0-dev
-CPP_VERSION = 1.12.0-dev
-CSHARP_VERSION = 1.12.0-dev
+CPP_VERSION = 1.13.0-dev
+CSHARP_VERSION = 1.13.0-dev
 
 CPPFLAGS_NO_ARCH += $(addprefix -I, $(INCLUDES)) $(addprefix -D, $(DEFINES))
 CPPFLAGS += $(CPPFLAGS_NO_ARCH) $(ARCH_FLAGS)
@@ -960,6 +960,7 @@
 algorithm_test: $(BINDIR)/$(CONFIG)/algorithm_test
 alloc_test: $(BINDIR)/$(CONFIG)/alloc_test
 alpn_test: $(BINDIR)/$(CONFIG)/alpn_test
+alts_credentials_fuzzer: $(BINDIR)/$(CONFIG)/alts_credentials_fuzzer
 api_fuzzer: $(BINDIR)/$(CONFIG)/api_fuzzer
 arena_test: $(BINDIR)/$(CONFIG)/arena_test
 avl_test: $(BINDIR)/$(CONFIG)/avl_test
@@ -1178,6 +1179,7 @@
 interop_server: $(BINDIR)/$(CONFIG)/interop_server
 interop_test: $(BINDIR)/$(CONFIG)/interop_test
 json_run_localhost: $(BINDIR)/$(CONFIG)/json_run_localhost
+lb_load_data_store_test: $(BINDIR)/$(CONFIG)/lb_load_data_store_test
 memory_test: $(BINDIR)/$(CONFIG)/memory_test
 metrics_client: $(BINDIR)/$(CONFIG)/metrics_client
 mock_test: $(BINDIR)/$(CONFIG)/mock_test
@@ -1199,6 +1201,7 @@
 secure_sync_unary_ping_pong_test: $(BINDIR)/$(CONFIG)/secure_sync_unary_ping_pong_test
 server_builder_plugin_test: $(BINDIR)/$(CONFIG)/server_builder_plugin_test
 server_builder_test: $(BINDIR)/$(CONFIG)/server_builder_test
+server_builder_with_socket_mutator_test: $(BINDIR)/$(CONFIG)/server_builder_with_socket_mutator_test
 server_context_test_spouse_test: $(BINDIR)/$(CONFIG)/server_context_test_spouse_test
 server_crash_test: $(BINDIR)/$(CONFIG)/server_crash_test
 server_crash_test_client: $(BINDIR)/$(CONFIG)/server_crash_test_client
@@ -1324,6 +1327,7 @@
 resolver_component_tests_runner_invoker: $(BINDIR)/$(CONFIG)/resolver_component_tests_runner_invoker
 address_sorting_test_unsecure: $(BINDIR)/$(CONFIG)/address_sorting_test_unsecure
 address_sorting_test: $(BINDIR)/$(CONFIG)/address_sorting_test
+alts_credentials_fuzzer_one_entry: $(BINDIR)/$(CONFIG)/alts_credentials_fuzzer_one_entry
 api_fuzzer_one_entry: $(BINDIR)/$(CONFIG)/api_fuzzer_one_entry
 client_fuzzer_one_entry: $(BINDIR)/$(CONFIG)/client_fuzzer_one_entry
 hpack_parser_fuzzer_test_one_entry: $(BINDIR)/$(CONFIG)/hpack_parser_fuzzer_test_one_entry
@@ -1390,9 +1394,9 @@
 pc_cxx_unsecure: $(LIBDIR)/$(CONFIG)/pkgconfig/grpc++_unsecure.pc
 
 ifeq ($(EMBED_OPENSSL),true)
-privatelibs_cxx:  $(LIBDIR)/$(CONFIG)/libgrpc++_core_stats.a $(LIBDIR)/$(CONFIG)/libgrpc++_proto_reflection_desc_db.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_cli_libs.a $(LIBDIR)/$(CONFIG)/libhttp2_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_client_helper.a $(LIBDIR)/$(CONFIG)/libinterop_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_server_helper.a $(LIBDIR)/$(CONFIG)/libinterop_server_lib.a $(LIBDIR)/$(CONFIG)/libinterop_server_main.a $(LIBDIR)/$(CONFIG)/libqps.a $(LIBDIR)/$(CONFIG)/libboringssl_test_util.a $(LIBDIR)/$(CONFIG)/libboringssl_crypto_test_data_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_asn1_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_base64_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_bio_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_buf_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_bytestring_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_chacha_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_aead_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_cipher_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_cmac_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_compiler_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_constant_time_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_ed25519_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_spake25519_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_x25519_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_dh_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_digest_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_dsa_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_ecdh_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_err_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_evp_extra_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_evp_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_pbkdf_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_scrypt_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_aes_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_bn_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_ec_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_p256-x86_64_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_ecdsa_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_gcm_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_ctrdrbg_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_hkdf_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_hmac_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_lhash_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_obj_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_pkcs7_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_pkcs12_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_pkcs8_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_poly1305_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_pool_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_refcount_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_rsa_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_file_test_gtest_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_gtest_main_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_thread_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_x509_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_tab_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_v3name_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_span_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_ssl_test_lib.a $(LIBDIR)/$(CONFIG)/libbenchmark.a
+privatelibs_cxx:  $(LIBDIR)/$(CONFIG)/libgrpc++_core_stats.a $(LIBDIR)/$(CONFIG)/libgrpc++_proto_reflection_desc_db.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_cli_libs.a $(LIBDIR)/$(CONFIG)/libhttp2_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_client_helper.a $(LIBDIR)/$(CONFIG)/libinterop_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_server_helper.a $(LIBDIR)/$(CONFIG)/libinterop_server_lib.a $(LIBDIR)/$(CONFIG)/libinterop_server_main.a $(LIBDIR)/$(CONFIG)/liblb_load_data_store.a $(LIBDIR)/$(CONFIG)/libqps.a $(LIBDIR)/$(CONFIG)/libboringssl_test_util.a $(LIBDIR)/$(CONFIG)/libboringssl_crypto_test_data_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_asn1_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_base64_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_bio_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_buf_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_bytestring_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_chacha_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_aead_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_cipher_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_cmac_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_compiler_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_constant_time_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_ed25519_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_spake25519_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_x25519_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_dh_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_digest_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_dsa_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_ecdh_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_err_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_evp_extra_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_evp_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_pbkdf_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_scrypt_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_aes_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_bn_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_ec_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_p256-x86_64_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_ecdsa_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_gcm_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_ctrdrbg_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_hkdf_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_hmac_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_lhash_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_obj_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_pkcs7_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_pkcs12_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_pkcs8_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_poly1305_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_pool_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_refcount_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_rsa_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_file_test_gtest_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_gtest_main_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_thread_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_x509_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_tab_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_v3name_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_span_test_lib.a $(LIBDIR)/$(CONFIG)/libboringssl_ssl_test_lib.a $(LIBDIR)/$(CONFIG)/libbenchmark.a
 else
-privatelibs_cxx:  $(LIBDIR)/$(CONFIG)/libgrpc++_core_stats.a $(LIBDIR)/$(CONFIG)/libgrpc++_proto_reflection_desc_db.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_cli_libs.a $(LIBDIR)/$(CONFIG)/libhttp2_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_client_helper.a $(LIBDIR)/$(CONFIG)/libinterop_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_server_helper.a $(LIBDIR)/$(CONFIG)/libinterop_server_lib.a $(LIBDIR)/$(CONFIG)/libinterop_server_main.a $(LIBDIR)/$(CONFIG)/libqps.a $(LIBDIR)/$(CONFIG)/libbenchmark.a
+privatelibs_cxx:  $(LIBDIR)/$(CONFIG)/libgrpc++_core_stats.a $(LIBDIR)/$(CONFIG)/libgrpc++_proto_reflection_desc_db.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_cli_libs.a $(LIBDIR)/$(CONFIG)/libhttp2_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_client_helper.a $(LIBDIR)/$(CONFIG)/libinterop_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_server_helper.a $(LIBDIR)/$(CONFIG)/libinterop_server_lib.a $(LIBDIR)/$(CONFIG)/libinterop_server_main.a $(LIBDIR)/$(CONFIG)/liblb_load_data_store.a $(LIBDIR)/$(CONFIG)/libqps.a $(LIBDIR)/$(CONFIG)/libbenchmark.a
 endif
 
 
@@ -1570,6 +1574,7 @@
   $(BINDIR)/$(CONFIG)/h2_sockpair_1byte_nosec_test \
   $(BINDIR)/$(CONFIG)/h2_uds_nosec_test \
   $(BINDIR)/$(CONFIG)/inproc_nosec_test \
+  $(BINDIR)/$(CONFIG)/alts_credentials_fuzzer_one_entry \
   $(BINDIR)/$(CONFIG)/api_fuzzer_one_entry \
   $(BINDIR)/$(CONFIG)/client_fuzzer_one_entry \
   $(BINDIR)/$(CONFIG)/hpack_parser_fuzzer_test_one_entry \
@@ -1660,6 +1665,7 @@
   $(BINDIR)/$(CONFIG)/interop_server \
   $(BINDIR)/$(CONFIG)/interop_test \
   $(BINDIR)/$(CONFIG)/json_run_localhost \
+  $(BINDIR)/$(CONFIG)/lb_load_data_store_test \
   $(BINDIR)/$(CONFIG)/memory_test \
   $(BINDIR)/$(CONFIG)/metrics_client \
   $(BINDIR)/$(CONFIG)/mock_test \
@@ -1681,6 +1687,7 @@
   $(BINDIR)/$(CONFIG)/secure_sync_unary_ping_pong_test \
   $(BINDIR)/$(CONFIG)/server_builder_plugin_test \
   $(BINDIR)/$(CONFIG)/server_builder_test \
+  $(BINDIR)/$(CONFIG)/server_builder_with_socket_mutator_test \
   $(BINDIR)/$(CONFIG)/server_context_test_spouse_test \
   $(BINDIR)/$(CONFIG)/server_crash_test \
   $(BINDIR)/$(CONFIG)/server_crash_test_client \
@@ -1831,6 +1838,7 @@
   $(BINDIR)/$(CONFIG)/interop_server \
   $(BINDIR)/$(CONFIG)/interop_test \
   $(BINDIR)/$(CONFIG)/json_run_localhost \
+  $(BINDIR)/$(CONFIG)/lb_load_data_store_test \
   $(BINDIR)/$(CONFIG)/memory_test \
   $(BINDIR)/$(CONFIG)/metrics_client \
   $(BINDIR)/$(CONFIG)/mock_test \
@@ -1852,6 +1860,7 @@
   $(BINDIR)/$(CONFIG)/secure_sync_unary_ping_pong_test \
   $(BINDIR)/$(CONFIG)/server_builder_plugin_test \
   $(BINDIR)/$(CONFIG)/server_builder_test \
+  $(BINDIR)/$(CONFIG)/server_builder_with_socket_mutator_test \
   $(BINDIR)/$(CONFIG)/server_context_test_spouse_test \
   $(BINDIR)/$(CONFIG)/server_crash_test \
   $(BINDIR)/$(CONFIG)/server_crash_test_client \
@@ -2283,6 +2292,8 @@
 	$(Q) $(BINDIR)/$(CONFIG)/inproc_sync_unary_ping_pong_test || ( echo test inproc_sync_unary_ping_pong_test failed ; exit 1 )
 	$(E) "[RUN]     Testing interop_test"
 	$(Q) $(BINDIR)/$(CONFIG)/interop_test || ( echo test interop_test failed ; exit 1 )
+	$(E) "[RUN]     Testing lb_load_data_store_test"
+	$(Q) $(BINDIR)/$(CONFIG)/lb_load_data_store_test || ( echo test lb_load_data_store_test failed ; exit 1 )
 	$(E) "[RUN]     Testing memory_test"
 	$(Q) $(BINDIR)/$(CONFIG)/memory_test || ( echo test memory_test failed ; exit 1 )
 	$(E) "[RUN]     Testing mock_test"
@@ -2313,6 +2324,8 @@
 	$(Q) $(BINDIR)/$(CONFIG)/server_builder_plugin_test || ( echo test server_builder_plugin_test failed ; exit 1 )
 	$(E) "[RUN]     Testing server_builder_test"
 	$(Q) $(BINDIR)/$(CONFIG)/server_builder_test || ( echo test server_builder_test failed ; exit 1 )
+	$(E) "[RUN]     Testing server_builder_with_socket_mutator_test"
+	$(Q) $(BINDIR)/$(CONFIG)/server_builder_with_socket_mutator_test || ( echo test server_builder_with_socket_mutator_test failed ; exit 1 )
 	$(E) "[RUN]     Testing server_context_test_spouse_test"
 	$(Q) $(BINDIR)/$(CONFIG)/server_context_test_spouse_test || ( echo test server_context_test_spouse_test failed ; exit 1 )
 	$(E) "[RUN]     Testing server_crash_test"
@@ -3527,7 +3540,6 @@
     third_party/nanopb/pb_decode.c \
     third_party/nanopb/pb_encode.c \
     src/core/tsi/transport_security.cc \
-    src/core/tsi/transport_security_adapter.cc \
     src/core/ext/transport/chttp2/client/insecure/channel_create.cc \
     src/core/ext/transport/chttp2/client/insecure/channel_create_posix.cc \
     src/core/ext/transport/chttp2/client/authority.cc \
@@ -3575,7 +3587,6 @@
     src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/load_balancer.pb.c \
     src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc \
     src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc \
-    src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc \
     src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc \
@@ -3947,7 +3958,6 @@
     third_party/nanopb/pb_decode.c \
     third_party/nanopb/pb_encode.c \
     src/core/tsi/transport_security.cc \
-    src/core/tsi/transport_security_adapter.cc \
     src/core/ext/transport/chttp2/client/insecure/channel_create.cc \
     src/core/ext/transport/chttp2/client/insecure/channel_create_posix.cc \
     src/core/ext/transport/chttp2/client/authority.cc \
@@ -4053,6 +4063,7 @@
     test/core/end2end/fixtures/proxy.cc \
     test/core/iomgr/endpoint_tests.cc \
     test/core/util/debugger_macros.cc \
+    test/core/util/fuzzer_util.cc \
     test/core/util/grpc_profiler.cc \
     test/core/util/histogram.cc \
     test/core/util/memory_counters.cc \
@@ -4348,6 +4359,7 @@
     test/core/end2end/fixtures/proxy.cc \
     test/core/iomgr/endpoint_tests.cc \
     test/core/util/debugger_macros.cc \
+    test/core/util/fuzzer_util.cc \
     test/core/util/grpc_profiler.cc \
     test/core/util/histogram.cc \
     test/core/util/memory_counters.cc \
@@ -4851,7 +4863,6 @@
     third_party/nanopb/pb_decode.c \
     third_party/nanopb/pb_encode.c \
     src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc \
-    src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc \
     src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc \
     src/core/ext/census/grpc_context.cc \
     src/core/ext/filters/max_age/max_age_filter.cc \
@@ -7207,6 +7218,55 @@
 endif
 
 
+LIBLB_LOAD_DATA_STORE_SRC = \
+    src/cpp/server/load_reporter/load_data_store.cc \
+
+PUBLIC_HEADERS_CXX += \
+
+LIBLB_LOAD_DATA_STORE_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBLB_LOAD_DATA_STORE_SRC))))
+
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure libraries if you don't have OpenSSL.
+
+$(LIBDIR)/$(CONFIG)/liblb_load_data_store.a: 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)/liblb_load_data_store.a: protobuf_dep_error
+
+
+else
+
+$(LIBDIR)/$(CONFIG)/liblb_load_data_store.a: $(ZLIB_DEP) $(OPENSSL_DEP) $(CARES_DEP) $(ADDRESS_SORTING_DEP) $(PROTOBUF_DEP) $(LIBLB_LOAD_DATA_STORE_OBJS) 
+	$(E) "[AR]      Creating $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) rm -f $(LIBDIR)/$(CONFIG)/liblb_load_data_store.a
+	$(Q) $(AR) $(AROPTS) $(LIBDIR)/$(CONFIG)/liblb_load_data_store.a $(LIBLB_LOAD_DATA_STORE_OBJS) 
+ifeq ($(SYSTEM),Darwin)
+	$(Q) ranlib -no_warning_for_no_symbols $(LIBDIR)/$(CONFIG)/liblb_load_data_store.a
+endif
+
+
+
+
+endif
+
+endif
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(LIBLB_LOAD_DATA_STORE_OBJS:.o=.dep)
+endif
+endif
+
+
 LIBQPS_SRC = \
     $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc \
     $(GENDIR)/src/proto/grpc/testing/payloads.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.grpc.pb.cc \
@@ -10174,6 +10234,38 @@
 endif
 
 
+ALTS_CREDENTIALS_FUZZER_SRC = \
+    test/core/security/alts_credentials_fuzzer.cc \
+
+ALTS_CREDENTIALS_FUZZER_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(ALTS_CREDENTIALS_FUZZER_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/alts_credentials_fuzzer: openssl_dep_error
+
+else
+
+
+
+$(BINDIR)/$(CONFIG)/alts_credentials_fuzzer: $(ALTS_CREDENTIALS_FUZZER_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(ALTS_CREDENTIALS_FUZZER_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -lFuzzer -o $(BINDIR)/$(CONFIG)/alts_credentials_fuzzer
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/security/alts_credentials_fuzzer.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_alts_credentials_fuzzer: $(ALTS_CREDENTIALS_FUZZER_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(ALTS_CREDENTIALS_FUZZER_OBJS:.o=.dep)
+endif
+endif
+
+
 API_FUZZER_SRC = \
     test/core/end2end/fuzzers/api_fuzzer.cc \
 
@@ -18027,6 +18119,49 @@
 endif
 
 
+LB_LOAD_DATA_STORE_TEST_SRC = \
+    test/cpp/server/load_reporter/load_data_store_test.cc \
+
+LB_LOAD_DATA_STORE_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(LB_LOAD_DATA_STORE_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/lb_load_data_store_test: openssl_dep_error
+
+else
+
+
+
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.5.0+.
+
+$(BINDIR)/$(CONFIG)/lb_load_data_store_test: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/lb_load_data_store_test: $(PROTOBUF_DEP) $(LB_LOAD_DATA_STORE_TEST_OBJS) $(LIBDIR)/$(CONFIG)/liblb_load_data_store.a $(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
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(LB_LOAD_DATA_STORE_TEST_OBJS) $(LIBDIR)/$(CONFIG)/liblb_load_data_store.a $(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 $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/lb_load_data_store_test
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/cpp/server/load_reporter/load_data_store_test.o:  $(LIBDIR)/$(CONFIG)/liblb_load_data_store.a $(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
+
+deps_lb_load_data_store_test: $(LB_LOAD_DATA_STORE_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(LB_LOAD_DATA_STORE_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 MEMORY_TEST_SRC = \
     test/core/gprpp/memory_test.cc \
 
@@ -18962,6 +19097,56 @@
 $(OBJDIR)/$(CONFIG)/test/cpp/server/server_builder_test.o: $(GENDIR)/src/proto/grpc/testing/echo_messages.pb.cc $(GENDIR)/src/proto/grpc/testing/echo_messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/echo.pb.cc $(GENDIR)/src/proto/grpc/testing/echo.grpc.pb.cc
 
 
+SERVER_BUILDER_WITH_SOCKET_MUTATOR_TEST_SRC = \
+    $(GENDIR)/src/proto/grpc/testing/echo_messages.pb.cc $(GENDIR)/src/proto/grpc/testing/echo_messages.grpc.pb.cc \
+    $(GENDIR)/src/proto/grpc/testing/echo.pb.cc $(GENDIR)/src/proto/grpc/testing/echo.grpc.pb.cc \
+    test/cpp/server/server_builder_with_socket_mutator_test.cc \
+
+SERVER_BUILDER_WITH_SOCKET_MUTATOR_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(SERVER_BUILDER_WITH_SOCKET_MUTATOR_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/server_builder_with_socket_mutator_test: openssl_dep_error
+
+else
+
+
+
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.5.0+.
+
+$(BINDIR)/$(CONFIG)/server_builder_with_socket_mutator_test: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/server_builder_with_socket_mutator_test: $(PROTOBUF_DEP) $(SERVER_BUILDER_WITH_SOCKET_MUTATOR_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(SERVER_BUILDER_WITH_SOCKET_MUTATOR_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/server_builder_with_socket_mutator_test
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/src/proto/grpc/testing/echo_messages.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+$(OBJDIR)/$(CONFIG)/src/proto/grpc/testing/echo.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+$(OBJDIR)/$(CONFIG)/test/cpp/server/server_builder_with_socket_mutator_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_server_builder_with_socket_mutator_test: $(SERVER_BUILDER_WITH_SOCKET_MUTATOR_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(SERVER_BUILDER_WITH_SOCKET_MUTATOR_TEST_OBJS:.o=.dep)
+endif
+endif
+$(OBJDIR)/$(CONFIG)/test/cpp/server/server_builder_with_socket_mutator_test.o: $(GENDIR)/src/proto/grpc/testing/echo_messages.pb.cc $(GENDIR)/src/proto/grpc/testing/echo_messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/echo.pb.cc $(GENDIR)/src/proto/grpc/testing/echo.grpc.pb.cc
+
+
 SERVER_CONTEXT_TEST_SPOUSE_TEST_SRC = \
     test/cpp/test/server_context_test_spouse_test.cc \
 
@@ -23350,6 +23535,41 @@
 endif
 
 
+ALTS_CREDENTIALS_FUZZER_ONE_ENTRY_SRC = \
+    test/core/security/alts_credentials_fuzzer.cc \
+    test/core/util/one_corpus_entry_fuzzer.cc \
+
+ALTS_CREDENTIALS_FUZZER_ONE_ENTRY_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(ALTS_CREDENTIALS_FUZZER_ONE_ENTRY_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/alts_credentials_fuzzer_one_entry: openssl_dep_error
+
+else
+
+
+
+$(BINDIR)/$(CONFIG)/alts_credentials_fuzzer_one_entry: $(ALTS_CREDENTIALS_FUZZER_ONE_ENTRY_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(ALTS_CREDENTIALS_FUZZER_ONE_ENTRY_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o $(BINDIR)/$(CONFIG)/alts_credentials_fuzzer_one_entry
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/security/alts_credentials_fuzzer.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+$(OBJDIR)/$(CONFIG)/test/core/util/one_corpus_entry_fuzzer.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_alts_credentials_fuzzer_one_entry: $(ALTS_CREDENTIALS_FUZZER_ONE_ENTRY_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(ALTS_CREDENTIALS_FUZZER_ONE_ENTRY_OBJS:.o=.dep)
+endif
+endif
+
+
 API_FUZZER_ONE_ENTRY_SRC = \
     test/core/end2end/fuzzers/api_fuzzer.cc \
     test/core/util/one_corpus_entry_fuzzer.cc \
@@ -23885,7 +24105,6 @@
 src/core/tsi/ssl/session_cache/ssl_session_openssl.cc: $(OPENSSL_DEP)
 src/core/tsi/ssl_transport_security.cc: $(OPENSSL_DEP)
 src/core/tsi/transport_security.cc: $(OPENSSL_DEP)
-src/core/tsi/transport_security_adapter.cc: $(OPENSSL_DEP)
 src/core/tsi/transport_security_grpc.cc: $(OPENSSL_DEP)
 src/cpp/client/cronet_credentials.cc: $(OPENSSL_DEP)
 src/cpp/client/secure_credentials.cc: $(OPENSSL_DEP)
@@ -23895,6 +24114,7 @@
 src/cpp/common/secure_create_auth_context.cc: $(OPENSSL_DEP)
 src/cpp/ext/proto_server_reflection.cc: $(OPENSSL_DEP)
 src/cpp/ext/proto_server_reflection_plugin.cc: $(OPENSSL_DEP)
+src/cpp/server/load_reporter/load_data_store.cc: $(OPENSSL_DEP)
 src/cpp/server/secure_server_credentials.cc: $(OPENSSL_DEP)
 src/cpp/util/core_stats.cc: $(OPENSSL_DEP)
 src/cpp/util/error_details.cc: $(OPENSSL_DEP)
diff --git a/build.yaml b/build.yaml
index 374d5a0..1bc2adb 100644
--- a/build.yaml
+++ b/build.yaml
@@ -13,8 +13,8 @@
   '#09': Per-language overrides are possible with (eg) ruby_version tag here
   '#10': See the expand_version.py for all the quirks here
   core_version: 6.0.0-dev
-  g_stands_for: glorious
-  version: 1.12.0-dev
+  g_stands_for: gloriosa
+  version: 1.13.0-dev
 filegroups:
 - name: alts_proto
   headers:
@@ -628,6 +628,7 @@
 - name: grpc_lb_policy_grpclb
   headers:
   - src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h
+  - src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h
   - src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h
   - src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h
   - src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h
@@ -648,6 +649,7 @@
 - name: grpc_lb_policy_grpclb_secure
   headers:
   - src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h
+  - src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h
   - src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h
   - src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h
   - src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h
@@ -685,8 +687,6 @@
 - name: grpc_lb_subchannel_list
   headers:
   - src/core/ext/filters/client_channel/lb_policy/subchannel_list.h
-  src:
-  - src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc
   uses:
   - grpc_base
   - grpc_client_channel
@@ -746,6 +746,7 @@
   public_headers:
   - include/grpc/grpc_security.h
   headers:
+  - src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h
   - src/core/lib/security/context/security_context.h
   - src/core/lib/security/credentials/alts/alts_credentials.h
   - src/core/lib/security/credentials/composite/composite_credentials.h
@@ -826,6 +827,7 @@
   - test/core/end2end/fixtures/proxy.h
   - test/core/iomgr/endpoint_tests.h
   - test/core/util/debugger_macros.h
+  - test/core/util/fuzzer_util.h
   - test/core/util/grpc_profiler.h
   - test/core/util/histogram.h
   - test/core/util/memory_counters.h
@@ -845,6 +847,7 @@
   - test/core/end2end/fixtures/proxy.cc
   - test/core/iomgr/endpoint_tests.cc
   - test/core/util/debugger_macros.cc
+  - test/core/util/fuzzer_util.cc
   - test/core/util/grpc_profiler.cc
   - test/core/util/histogram.cc
   - test/core/util/memory_counters.cc
@@ -1077,11 +1080,9 @@
 - name: tsi_interface
   headers:
   - src/core/tsi/transport_security.h
-  - src/core/tsi/transport_security_adapter.h
   - src/core/tsi/transport_security_interface.h
   src:
   - src/core/tsi/transport_security.cc
-  - src/core/tsi/transport_security_adapter.cc
   deps:
   - gpr
   secure: true
@@ -1890,6 +1891,15 @@
   - test/cpp/interop/interop_server_bootstrap.cc
   deps:
   - interop_server_lib
+- name: lb_load_data_store
+  build: private
+  language: c++
+  headers:
+  - src/cpp/server/load_reporter/load_data_store.h
+  src:
+  - src/cpp/server/load_reporter/load_data_store.cc
+  deps:
+  - grpc++
 - name: qps
   build: private
   language: c++
@@ -1981,6 +1991,19 @@
   - grpc
   - gpr_test_util
   - gpr
+- name: alts_credentials_fuzzer
+  build: fuzzer
+  language: c
+  src:
+  - test/core/security/alts_credentials_fuzzer.cc
+  deps:
+  - grpc_test_util
+  - grpc
+  - gpr_test_util
+  - gpr
+  corpus_dirs:
+  - test/core/security/corpus/alts_credentials_corpus
+  maxlen: 2048
 - name: api_fuzzer
   build: fuzzer
   language: c
@@ -4766,6 +4789,20 @@
   - mac
   - linux
   - posix
+- name: lb_load_data_store_test
+  gtest: true
+  build: test
+  language: c++
+  src:
+  - test/cpp/server/load_reporter/load_data_store_test.cc
+  deps:
+  - lb_load_data_store
+  - grpc++_test_util
+  - grpc_test_util
+  - grpc++
+  - grpc
+  - gpr_test_util
+  - gpr
 - name: memory_test
   gtest: true
   build: test
@@ -5085,6 +5122,23 @@
   - grpc++_unsecure
   - grpc_unsecure
   - gpr
+- name: server_builder_with_socket_mutator_test
+  gtest: true
+  build: test
+  language: c++
+  src:
+  - src/proto/grpc/testing/echo_messages.proto
+  - src/proto/grpc/testing/echo.proto
+  - test/cpp/server/server_builder_with_socket_mutator_test.cc
+  deps:
+  - grpc++_test_util_unsecure
+  - grpc_test_util_unsecure
+  - gpr_test_util
+  - grpc++_unsecure
+  - grpc_unsecure
+  - gpr
+  platforms:
+  - posix
 - name: server_context_test_spouse_test
   gtest: true
   build: test
diff --git a/config.m4 b/config.m4
index 0dc4825..ee2aca4 100644
--- a/config.m4
+++ b/config.m4
@@ -321,7 +321,6 @@
     third_party/nanopb/pb_decode.c \
     third_party/nanopb/pb_encode.c \
     src/core/tsi/transport_security.cc \
-    src/core/tsi/transport_security_adapter.cc \
     src/core/ext/transport/chttp2/client/insecure/channel_create.cc \
     src/core/ext/transport/chttp2/client/insecure/channel_create_posix.cc \
     src/core/ext/transport/chttp2/client/authority.cc \
@@ -369,7 +368,6 @@
     src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/load_balancer.pb.c \
     src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc \
     src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc \
-    src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc \
     src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc \
@@ -650,7 +648,6 @@
   PHP_ADD_BUILD_DIR($ext_builddir/src/boringssl)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/census)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/client_channel)
-  PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/client_channel/lb_policy)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/client_channel/lb_policy/grpclb)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/client_channel/lb_policy/pick_first)
diff --git a/config.w32 b/config.w32
index 1a09acc..abca8e2 100644
--- a/config.w32
+++ b/config.w32
@@ -297,7 +297,6 @@
     "third_party\\nanopb\\pb_decode.c " +
     "third_party\\nanopb\\pb_encode.c " +
     "src\\core\\tsi\\transport_security.cc " +
-    "src\\core\\tsi\\transport_security_adapter.cc " +
     "src\\core\\ext\\transport\\chttp2\\client\\insecure\\channel_create.cc " +
     "src\\core\\ext\\transport\\chttp2\\client\\insecure\\channel_create_posix.cc " +
     "src\\core\\ext\\transport\\chttp2\\client\\authority.cc " +
@@ -345,7 +344,6 @@
     "src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb\\proto\\grpc\\lb\\v1\\load_balancer.pb.c " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\fake\\fake_resolver.cc " +
     "src\\core\\ext\\filters\\client_channel\\lb_policy\\pick_first\\pick_first.cc " +
-    "src\\core\\ext\\filters\\client_channel\\lb_policy\\subchannel_list.cc " +
     "src\\core\\ext\\filters\\client_channel\\lb_policy\\round_robin\\round_robin.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\dns_resolver_ares.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_ev_driver_posix.cc " +
diff --git a/doc/fork_support.md b/doc/fork_support.md
new file mode 100644
index 0000000..d0f59f2
--- /dev/null
+++ b/doc/fork_support.md
@@ -0,0 +1,46 @@
+# Background #
+
+In Python, multithreading is ineffective at concurrency for CPU bound tasks
+due to the GIL (global interpreter lock).  Extension modules can release
+the GIL in CPU bound tasks, but that isn't an option in pure Python.
+Users use libraries such as multiprocessing, subprocess, concurrent.futures.ProcessPoolExecutor,
+etc, to work around the GIL. These modules call ```fork()``` underneath the hood. Various issues have
+been reported when using these modules with gRPC Python.  gRPC Python wraps
+gRPC core, which uses multithreading for performance, and hence doesn't support ```fork()```.
+Historically, we didn't support forking in gRPC, but some users seemed
+to be doing fine until their code started to break on version 1.6.  This was
+likely caused by the addition of background c-threads and a background
+Python thread.
+
+# Current Status #
+
+## 1.11 ##
+The background Python thread was removed entirely.  This allows forking
+after creating a channel.  However, the channel must not have issued any
+RPCs prior to the fork.  Attempting to fork with an active channel that
+has been used can result in deadlocks/corrupted wire data.
+
+## 1.9 ##
+A regression was noted in cases where users are doing fork/exec. This
+was due to ```pthread_atfork()``` handler that was added in 1.7 to partially
+support forking in gRPC. A deadlock can happen when pthread_atfork
+handler is running, and an application thread is calling into gRPC.
+We have provided a workaround for this issue by allowing users to turn 
+off the handler using env flag ```GRPC_ENABLE_FORK_SUPPORT=False```.
+This should be set whenever a user expects to always call exec
+immediately following fork.  It will disable the fork handlers.
+
+## 1.7 ##
+A ```pthread_atfork()``` handler was added in 1.7 to automatically shut down
+the background c-threads when fork was called.  This does not shut down the
+background Python thread, so users could not have any open channels when
+forking.
+
+# Future Work #
+
+## 1.13 ##
+The workaround when using fork/exec by setting
+```GRPC_ENABLE_FORK_SUPPORT=False``` should no longer be needed.  Following
+[this PR](https://github.com/grpc/grpc/pull/14647), fork
+handlers will not automatically run when multiple threads are calling
+into gRPC.
diff --git a/doc/g_stands_for.md b/doc/g_stands_for.md
index ee8a827..8b9cf91 100644
--- a/doc/g_stands_for.md
+++ b/doc/g_stands_for.md
@@ -1,18 +1,15 @@
-Each version of gRPC gets a new description of what the 'g' stands for, since
-we've never really been able to figure it out.
+'g' stands for something different every gRPC release:
 
-Below is a list of already-used definitions (that should not be repeated in the
-future), and the corresponding version numbers that used them:
-
-- 1.0 'g' stands for 'gRPC'
-- 1.1 'g' stands for 'good'
-- 1.2 'g' stands for 'green'
-- 1.3 'g' stands for 'gentle'
-- 1.4 'g' stands for 'gregarious'
-- 1.6 'g' stands for 'garcia'
-- 1.7 'g' stands for 'gambit'
-- 1.8 'g' stands for 'generous'
-- 1.9 'g' stands for 'glossy'
-- 1.10 'g' stands for 'glamorous'
-- 1.11 'g' stands for 'gorgeous'
-- 1.12 'g' stands for 'glorious'
+- 1.0 'g' stands for ['gRPC'](https://github.com/grpc/grpc/tree/v1.0.x)
+- 1.1 'g' stands for ['good'](https://github.com/grpc/grpc/tree/v1.1.x)
+- 1.2 'g' stands for ['green'](https://github.com/grpc/grpc/tree/v1.2.x)
+- 1.3 'g' stands for ['gentle'](https://github.com/grpc/grpc/tree/v1.3.x)
+- 1.4 'g' stands for ['gregarious'](https://github.com/grpc/grpc/tree/v1.4.x)
+- 1.6 'g' stands for ['garcia'](https://github.com/grpc/grpc/tree/v1.6.x)
+- 1.7 'g' stands for ['gambit'](https://github.com/grpc/grpc/tree/v1.7.x)
+- 1.8 'g' stands for ['generous'](https://github.com/grpc/grpc/tree/v1.8.x)
+- 1.9 'g' stands for ['glossy'](https://github.com/grpc/grpc/tree/v1.9.x)
+- 1.10 'g' stands for ['glamorous'](https://github.com/grpc/grpc/tree/v1.10.x)
+- 1.11 'g' stands for ['gorgeous'](https://github.com/grpc/grpc/tree/v1.11.x)
+- 1.12 'g' stands for ['glorious'](https://github.com/grpc/grpc/tree/v1.12.x)
+- 1.13 'g' stands for ['gloriosa'](https://github.com/grpc/grpc/tree/master)
diff --git a/examples/node/dynamic_codegen/route_guide/route_guide_server.js b/examples/node/dynamic_codegen/route_guide/route_guide_server.js
index f9028e8..3819c09 100644
--- a/examples/node/dynamic_codegen/route_guide/route_guide_server.js
+++ b/examples/node/dynamic_codegen/route_guide/route_guide_server.js
@@ -122,7 +122,7 @@
   var deltalon = lon2-lon1;
   var a = Math.sin(deltalat/2) * Math.sin(deltalat/2) +
       Math.cos(lat1) * Math.cos(lat2) *
-      Math.sin(dlon/2) * Math.sin(dlon/2);
+      Math.sin(deltalon/2) * Math.sin(deltalon/2);
   var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
   return R * c;
 }
diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec
index 990b0a4..29b79e0 100644
--- a/gRPC-C++.podspec
+++ b/gRPC-C++.podspec
@@ -23,7 +23,7 @@
 Pod::Spec.new do |s|
   s.name     = 'gRPC-C++'
   # TODO (mxyan): use version that match gRPC version when pod is stabilized
-  # version = '1.12.0-dev'
+  # version = '1.13.0-dev'
   version = '0.0.2'
   s.version  = version
   s.summary  = 'gRPC C++ library'
@@ -31,7 +31,7 @@
   s.license  = 'Apache License, Version 2.0'
   s.authors  = { 'The gRPC contributors' => 'grpc-packages@google.com' }
 
-  grpc_version = '1.12.0-dev'
+  grpc_version = '1.13.0-dev'
 
   s.source = {
     :git => 'https://github.com/grpc/grpc.git',
@@ -260,6 +260,7 @@
                       'src/core/ext/filters/http/client/http_client_filter.h',
                       'src/core/ext/filters/http/message_compress/message_compress_filter.h',
                       'src/core/ext/filters/http/server/http_server_filter.h',
+                      'src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h',
                       'src/core/lib/security/context/security_context.h',
                       'src/core/lib/security/credentials/alts/alts_credentials.h',
                       'src/core/lib/security/credentials/composite/composite_credentials.h',
@@ -307,7 +308,6 @@
                       'src/core/tsi/alts/handshaker/handshaker.pb.h',
                       'src/core/tsi/alts/handshaker/transport_security_common.pb.h',
                       'src/core/tsi/transport_security.h',
-                      'src/core/tsi/transport_security_adapter.h',
                       'src/core/tsi/transport_security_interface.h',
                       'src/core/ext/transport/chttp2/client/authority.h',
                       'src/core/ext/transport/chttp2/client/chttp2_connector.h',
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index fc6b31f..ce6ff76 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -22,7 +22,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-Core'
-  version = '1.12.0-dev'
+  version = '1.13.0-dev'
   s.version  = version
   s.summary  = 'Core cross-platform gRPC library, written in C'
   s.homepage = 'https://grpc.io'
@@ -270,6 +270,7 @@
                       'src/core/ext/filters/http/client/http_client_filter.h',
                       'src/core/ext/filters/http/message_compress/message_compress_filter.h',
                       'src/core/ext/filters/http/server/http_server_filter.h',
+                      'src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h',
                       'src/core/lib/security/context/security_context.h',
                       'src/core/lib/security/credentials/alts/alts_credentials.h',
                       'src/core/lib/security/credentials/composite/composite_credentials.h',
@@ -317,7 +318,6 @@
                       'src/core/tsi/alts/handshaker/handshaker.pb.h',
                       'src/core/tsi/alts/handshaker/transport_security_common.pb.h',
                       'src/core/tsi/transport_security.h',
-                      'src/core/tsi/transport_security_adapter.h',
                       'src/core/tsi/transport_security_interface.h',
                       'src/core/ext/transport/chttp2/client/authority.h',
                       'src/core/ext/transport/chttp2/client/chttp2_connector.h',
@@ -736,7 +736,6 @@
                       'src/core/tsi/alts/handshaker/handshaker.pb.c',
                       'src/core/tsi/alts/handshaker/transport_security_common.pb.c',
                       'src/core/tsi/transport_security.cc',
-                      'src/core/tsi/transport_security_adapter.cc',
                       'src/core/ext/transport/chttp2/client/insecure/channel_create.cc',
                       'src/core/ext/transport/chttp2/client/insecure/channel_create_posix.cc',
                       'src/core/ext/transport/chttp2/client/authority.cc',
@@ -784,7 +783,6 @@
                       'src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/load_balancer.pb.c',
                       'src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc',
                       'src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc',
-                      'src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc',
                       'src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc',
@@ -850,6 +848,7 @@
                               'src/core/ext/filters/http/client/http_client_filter.h',
                               'src/core/ext/filters/http/message_compress/message_compress_filter.h',
                               'src/core/ext/filters/http/server/http_server_filter.h',
+                              'src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h',
                               'src/core/lib/security/context/security_context.h',
                               'src/core/lib/security/credentials/alts/alts_credentials.h',
                               'src/core/lib/security/credentials/composite/composite_credentials.h',
@@ -897,7 +896,6 @@
                               'src/core/tsi/alts/handshaker/handshaker.pb.h',
                               'src/core/tsi/alts/handshaker/transport_security_common.pb.h',
                               'src/core/tsi/transport_security.h',
-                              'src/core/tsi/transport_security_adapter.h',
                               'src/core/tsi/transport_security_interface.h',
                               'src/core/ext/transport/chttp2/client/authority.h',
                               'src/core/ext/transport/chttp2/client/chttp2_connector.h',
@@ -1125,6 +1123,7 @@
                       'test/core/end2end/fixtures/proxy.cc',
                       'test/core/iomgr/endpoint_tests.cc',
                       'test/core/util/debugger_macros.cc',
+                      'test/core/util/fuzzer_util.cc',
                       'test/core/util/grpc_profiler.cc',
                       'test/core/util/histogram.cc',
                       'test/core/util/memory_counters.cc',
@@ -1147,6 +1146,7 @@
                       'test/core/end2end/fixtures/proxy.h',
                       'test/core/iomgr/endpoint_tests.h',
                       'test/core/util/debugger_macros.h',
+                      'test/core/util/fuzzer_util.h',
                       'test/core/util/grpc_profiler.h',
                       'test/core/util/histogram.h',
                       'test/core/util/memory_counters.h',
diff --git a/gRPC-ProtoRPC.podspec b/gRPC-ProtoRPC.podspec
index e98dee4..17e650c 100644
--- a/gRPC-ProtoRPC.podspec
+++ b/gRPC-ProtoRPC.podspec
@@ -21,7 +21,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-ProtoRPC'
-  version = '1.12.0-dev'
+  version = '1.13.0-dev'
   s.version  = version
   s.summary  = 'RPC library for Protocol Buffers, based on gRPC'
   s.homepage = 'https://grpc.io'
diff --git a/gRPC-RxLibrary.podspec b/gRPC-RxLibrary.podspec
index 3bba14b..b9288af 100644
--- a/gRPC-RxLibrary.podspec
+++ b/gRPC-RxLibrary.podspec
@@ -21,7 +21,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-RxLibrary'
-  version = '1.12.0-dev'
+  version = '1.13.0-dev'
   s.version  = version
   s.summary  = 'Reactive Extensions library for iOS/OSX.'
   s.homepage = 'https://grpc.io'
diff --git a/gRPC.podspec b/gRPC.podspec
index bf73ffd..afc4581 100644
--- a/gRPC.podspec
+++ b/gRPC.podspec
@@ -20,7 +20,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC'
-  version = '1.12.0-dev'
+  version = '1.13.0-dev'
   s.version  = version
   s.summary  = 'gRPC client library for iOS/OSX'
   s.homepage = 'https://grpc.io'
diff --git a/grpc.gemspec b/grpc.gemspec
index 4e309f1..2a66801 100644
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -201,6 +201,7 @@
   s.files += %w( src/core/ext/filters/http/client/http_client_filter.h )
   s.files += %w( src/core/ext/filters/http/message_compress/message_compress_filter.h )
   s.files += %w( src/core/ext/filters/http/server/http_server_filter.h )
+  s.files += %w( src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h )
   s.files += %w( src/core/lib/security/context/security_context.h )
   s.files += %w( src/core/lib/security/credentials/alts/alts_credentials.h )
   s.files += %w( src/core/lib/security/credentials/composite/composite_credentials.h )
@@ -252,7 +253,6 @@
   s.files += %w( third_party/nanopb/pb_decode.h )
   s.files += %w( third_party/nanopb/pb_encode.h )
   s.files += %w( src/core/tsi/transport_security.h )
-  s.files += %w( src/core/tsi/transport_security_adapter.h )
   s.files += %w( src/core/tsi/transport_security_interface.h )
   s.files += %w( src/core/ext/transport/chttp2/client/authority.h )
   s.files += %w( src/core/ext/transport/chttp2/client/chttp2_connector.h )
@@ -674,7 +674,6 @@
   s.files += %w( third_party/nanopb/pb_decode.c )
   s.files += %w( third_party/nanopb/pb_encode.c )
   s.files += %w( src/core/tsi/transport_security.cc )
-  s.files += %w( src/core/tsi/transport_security_adapter.cc )
   s.files += %w( src/core/ext/transport/chttp2/client/insecure/channel_create.cc )
   s.files += %w( src/core/ext/transport/chttp2/client/insecure/channel_create_posix.cc )
   s.files += %w( src/core/ext/transport/chttp2/client/authority.cc )
@@ -722,7 +721,6 @@
   s.files += %w( src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/load_balancer.pb.c )
   s.files += %w( src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc )
   s.files += %w( src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc )
-  s.files += %w( src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc )
   s.files += %w( src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc )
diff --git a/grpc.gyp b/grpc.gyp
index b36b166..5726719 100644
--- a/grpc.gyp
+++ b/grpc.gyp
@@ -481,7 +481,6 @@
         'third_party/nanopb/pb_decode.c',
         'third_party/nanopb/pb_encode.c',
         'src/core/tsi/transport_security.cc',
-        'src/core/tsi/transport_security_adapter.cc',
         'src/core/ext/transport/chttp2/client/insecure/channel_create.cc',
         'src/core/ext/transport/chttp2/client/insecure/channel_create_posix.cc',
         'src/core/ext/transport/chttp2/client/authority.cc',
@@ -529,7 +528,6 @@
         'src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/load_balancer.pb.c',
         'src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc',
         'src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc',
-        'src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc',
         'src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc',
@@ -578,6 +576,7 @@
         'test/core/end2end/fixtures/proxy.cc',
         'test/core/iomgr/endpoint_tests.cc',
         'test/core/util/debugger_macros.cc',
+        'test/core/util/fuzzer_util.cc',
         'test/core/util/grpc_profiler.cc',
         'test/core/util/histogram.cc',
         'test/core/util/memory_counters.cc',
@@ -808,6 +807,7 @@
         'test/core/end2end/fixtures/proxy.cc',
         'test/core/iomgr/endpoint_tests.cc',
         'test/core/util/debugger_macros.cc',
+        'test/core/util/fuzzer_util.cc',
         'test/core/util/grpc_profiler.cc',
         'test/core/util/histogram.cc',
         'test/core/util/memory_counters.cc',
@@ -1258,7 +1258,6 @@
         'third_party/nanopb/pb_decode.c',
         'third_party/nanopb/pb_encode.c',
         'src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc',
-        'src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc',
         'src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc',
         'src/core/ext/census/grpc_context.cc',
         'src/core/ext/filters/max_age/max_age_filter.cc',
@@ -1638,6 +1637,16 @@
       ],
     },
     {
+      'target_name': 'lb_load_data_store',
+      'type': 'static_library',
+      'dependencies': [
+        'grpc++',
+      ],
+      'sources': [
+        'src/cpp/server/load_reporter/load_data_store.cc',
+      ],
+    },
+    {
       'target_name': 'qps',
       'type': 'static_library',
       'dependencies': [
diff --git a/include/grpc/support/log.h b/include/grpc/support/log.h
index b6fbbde..1837d4b 100644
--- a/include/grpc/support/log.h
+++ b/include/grpc/support/log.h
@@ -99,6 +99,12 @@
     }                                                 \
   } while (0)
 
+#ifndef NDEBUG
+#define GPR_DEBUG_ASSERT(x) GPR_ASSERT(x)
+#else
+#define GPR_DEBUG_ASSERT(x)
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/grpcpp/support/channel_arguments.h b/include/grpcpp/support/channel_arguments.h
index 1eead4e..217929d 100644
--- a/include/grpcpp/support/channel_arguments.h
+++ b/include/grpcpp/support/channel_arguments.h
@@ -70,7 +70,13 @@
   /// the resolver.
   void SetGrpclbFallbackTimeout(int fallback_timeout);
 
-  /// Set the socket mutator for the channel.
+  /// For client channel's, the socket mutator operates on
+  /// "channel" sockets. For server's, the socket mutator operates
+  /// only on "listen" sockets.
+  /// TODO(apolcyn): allow socket mutators to also operate
+  /// on server "channel" sockets, and adjust the socket mutator
+  /// object to be more speficic about which type of socket
+  /// it should operate on.
   void SetSocketMutator(grpc_socket_mutator* mutator);
 
   /// Set the string to prepend to the user agent.
diff --git a/package.xml b/package.xml
index 0777ee0..ac9b520 100644
--- a/package.xml
+++ b/package.xml
@@ -13,8 +13,8 @@
  <date>2018-01-19</date>
  <time>16:06:07</time>
  <version>
-  <release>1.12.0dev</release>
-  <api>1.12.0dev</api>
+  <release>1.13.0dev</release>
+  <api>1.13.0dev</api>
  </version>
  <stability>
   <release>beta</release>
@@ -208,6 +208,7 @@
     <file baseinstalldir="/" name="src/core/ext/filters/http/client/http_client_filter.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/http/message_compress/message_compress_filter.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/http/server/http_server_filter.h" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/security/context/security_context.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/security/credentials/alts/alts_credentials.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/security/credentials/composite/composite_credentials.h" role="src" />
@@ -259,7 +260,6 @@
     <file baseinstalldir="/" name="third_party/nanopb/pb_decode.h" role="src" />
     <file baseinstalldir="/" name="third_party/nanopb/pb_encode.h" role="src" />
     <file baseinstalldir="/" name="src/core/tsi/transport_security.h" role="src" />
-    <file baseinstalldir="/" name="src/core/tsi/transport_security_adapter.h" role="src" />
     <file baseinstalldir="/" name="src/core/tsi/transport_security_interface.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/transport/chttp2/client/authority.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/transport/chttp2/client/chttp2_connector.h" role="src" />
@@ -681,7 +681,6 @@
     <file baseinstalldir="/" name="third_party/nanopb/pb_decode.c" role="src" />
     <file baseinstalldir="/" name="third_party/nanopb/pb_encode.c" role="src" />
     <file baseinstalldir="/" name="src/core/tsi/transport_security.cc" role="src" />
-    <file baseinstalldir="/" name="src/core/tsi/transport_security_adapter.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/transport/chttp2/client/insecure/channel_create.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/transport/chttp2/client/insecure/channel_create_posix.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/transport/chttp2/client/authority.cc" role="src" />
@@ -729,7 +728,6 @@
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/load_balancer.pb.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc" role="src" />
-    <file baseinstalldir="/" name="src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc" role="src" />
diff --git a/src/compiler/node_generator.cc b/src/compiler/node_generator.cc
index 661587c..a430628 100644
--- a/src/compiler/node_generator.cc
+++ b/src/compiler/node_generator.cc
@@ -20,6 +20,7 @@
 
 #include "src/compiler/config.h"
 #include "src/compiler/generator_helpers.h"
+#include "src/compiler/node_generator.h"
 #include "src/compiler/node_generator_helpers.h"
 
 using grpc::protobuf::Descriptor;
@@ -119,7 +120,8 @@
 }
 
 // Prints out the message serializer and deserializer functions
-void PrintMessageTransformer(const Descriptor* descriptor, Printer* out) {
+void PrintMessageTransformer(const Descriptor* descriptor, Printer* out,
+                             const Parameters& params) {
   map<grpc::string, grpc::string> template_vars;
   grpc::string full_name = descriptor->full_name();
   template_vars["identifier_name"] = MessageIdentifierName(full_name);
@@ -134,7 +136,12 @@
              "throw new Error('Expected argument of type $name$');\n");
   out->Outdent();
   out->Print("}\n");
-  out->Print("return new Buffer(arg.serializeBinary());\n");
+  if (params.minimum_node_version > 5) {
+    // Node version is > 5, we should use Buffer.from
+    out->Print("return Buffer.from(arg.serializeBinary());\n");
+  } else {
+    out->Print("return new Buffer(arg.serializeBinary());\n");
+  }
   out->Outdent();
   out->Print("}\n\n");
 
@@ -219,12 +226,13 @@
   out->Print("\n");
 }
 
-void PrintTransformers(const FileDescriptor* file, Printer* out) {
+void PrintTransformers(const FileDescriptor* file, Printer* out,
+                       const Parameters& params) {
   map<grpc::string, const Descriptor*> messages = GetAllMessages(file);
   for (std::map<grpc::string, const Descriptor*>::iterator it =
            messages.begin();
        it != messages.end(); it++) {
-    PrintMessageTransformer(it->second, out);
+    PrintMessageTransformer(it->second, out, params);
   }
   out->Print("\n");
 }
@@ -236,7 +244,8 @@
 }
 }  // namespace
 
-grpc::string GenerateFile(const FileDescriptor* file) {
+grpc::string GenerateFile(const FileDescriptor* file,
+                          const Parameters& params) {
   grpc::string output;
   {
     StringOutputStream output_stream(&output);
@@ -257,7 +266,7 @@
 
     PrintImports(file, &out);
 
-    PrintTransformers(file, &out);
+    PrintTransformers(file, &out, params);
 
     PrintServices(file, &out);
 
diff --git a/src/compiler/node_generator.h b/src/compiler/node_generator.h
index a9ffe75..f3a5315 100644
--- a/src/compiler/node_generator.h
+++ b/src/compiler/node_generator.h
@@ -23,7 +23,14 @@
 
 namespace grpc_node_generator {
 
-grpc::string GenerateFile(const grpc::protobuf::FileDescriptor* file);
+// Contains all the parameters that are parsed from the command line.
+struct Parameters {
+  // Sets the earliest version of nodejs that needs to be supported.
+  int minimum_node_version;
+};
+
+grpc::string GenerateFile(const grpc::protobuf::FileDescriptor* file,
+                          const Parameters& params);
 
 }  // namespace grpc_node_generator
 
diff --git a/src/compiler/node_plugin.cc b/src/compiler/node_plugin.cc
index bc38e90..0d19d8e 100644
--- a/src/compiler/node_plugin.cc
+++ b/src/compiler/node_plugin.cc
@@ -36,7 +36,27 @@
                 const grpc::string& parameter,
                 grpc::protobuf::compiler::GeneratorContext* context,
                 grpc::string* error) const {
-    grpc::string code = GenerateFile(file);
+    grpc_node_generator::Parameters generator_parameters;
+    generator_parameters.minimum_node_version = 4;
+
+    if (!parameter.empty()) {
+      std::vector<grpc::string> parameters_list =
+          grpc_generator::tokenize(parameter, ",");
+      for (auto parameter_string = parameters_list.begin();
+           parameter_string != parameters_list.end(); parameter_string++) {
+        std::vector<grpc::string> param =
+            grpc_generator::tokenize(*parameter_string, "=");
+        if (param[0] == "minimum_node_version") {
+          sscanf(param[1].c_str(), "%d",
+                 &generator_parameters.minimum_node_version);
+        } else {
+          *error = grpc::string("Unknown parameter: ") + *parameter_string;
+          return false;
+        }
+      }
+    }
+
+    grpc::string code = GenerateFile(file, generator_parameters);
     if (code.size() == 0) {
       return true;
     }
diff --git a/src/core/ext/filters/client_channel/client_channel.cc b/src/core/ext/filters/client_channel/client_channel.cc
index 875af66..80a647f 100644
--- a/src/core/ext/filters/client_channel/client_channel.cc
+++ b/src/core/ext/filters/client_channel/client_channel.cc
@@ -3252,6 +3252,8 @@
   external_connectivity_watcher* found = nullptr;
   if (w->state != nullptr) {
     external_connectivity_watcher_list_append(w->chand, w);
+    // An assumption is being made that the closure is scheduled on the exec ctx
+    // scheduler and that GRPC_CLOSURE_RUN would run the closure immediately.
     GRPC_CLOSURE_RUN(w->watcher_timer_init, GRPC_ERROR_NONE);
     GRPC_CLOSURE_INIT(&w->my_closure, on_external_watch_complete_locked, w,
                       grpc_combiner_scheduler(w->chand->combiner));
diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc
index 1a67547..70a91b2 100644
--- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc
@@ -76,6 +76,7 @@
 #include "src/core/ext/filters/client_channel/client_channel.h"
 #include "src/core/ext/filters/client_channel/client_channel_factory.h"
 #include "src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h"
+#include "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h"
 #include "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h"
 #include "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h"
 #include "src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h"
@@ -1003,6 +1004,9 @@
       // address updates into the LB channel.
       grpc_core::FakeResolverResponseGenerator::MakeChannelArg(
           response_generator),
+      // A channel arg indicating the target is a grpclb load balancer.
+      grpc_channel_arg_integer_create(
+          const_cast<char*>(GRPC_ARG_ADDRESS_IS_GRPCLB_LOAD_BALANCER), 1),
   };
   // Construct channel args.
   grpc_channel_args* new_args = grpc_channel_args_copy_and_add_and_remove(
@@ -1698,9 +1702,11 @@
 
 grpc_channel_args* GrpcLb::CreateRoundRobinPolicyArgsLocked() {
   grpc_lb_addresses* addresses;
+  bool is_backend_from_grpclb_load_balancer = false;
   if (serverlist_ != nullptr) {
     GPR_ASSERT(serverlist_->num_servers > 0);
     addresses = ProcessServerlist(serverlist_);
+    is_backend_from_grpclb_load_balancer = true;
   } else {
     // If CreateOrUpdateRoundRobinPolicyLocked() is invoked when we haven't
     // received any serverlist from the balancer, we use the fallback backends
@@ -1714,9 +1720,18 @@
   // Replace the LB addresses in the channel args that we pass down to
   // the subchannel.
   static const char* keys_to_remove[] = {GRPC_ARG_LB_ADDRESSES};
-  const grpc_arg arg = grpc_lb_addresses_create_channel_arg(addresses);
+  const grpc_arg args_to_add[] = {
+      grpc_lb_addresses_create_channel_arg(addresses),
+      // A channel arg indicating if the target is a backend inferred from a
+      // grpclb load balancer.
+      grpc_channel_arg_integer_create(
+          const_cast<char*>(
+              GRPC_ARG_ADDRESS_IS_BACKEND_FROM_GRPCLB_LOAD_BALANCER),
+          is_backend_from_grpclb_load_balancer),
+  };
   grpc_channel_args* args = grpc_channel_args_copy_and_add_and_remove(
-      args_, keys_to_remove, GPR_ARRAY_SIZE(keys_to_remove), &arg, 1);
+      args_, keys_to_remove, GPR_ARRAY_SIZE(keys_to_remove), args_to_add,
+      GPR_ARRAY_SIZE(args_to_add));
   grpc_lb_addresses_destroy(addresses);
   return args;
 }
diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h
new file mode 100644
index 0000000..4d39c4d
--- /dev/null
+++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h
@@ -0,0 +1,36 @@
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_LB_POLICY_GRPCLB_GRPCLB_H
+#define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_LB_POLICY_GRPCLB_GRPCLB_H
+
+#include <grpc/support/port_platform.h>
+
+/** Channel arg indicating if a target corresponding to the address is grpclb
+ * loadbalancer. The type of this arg is an integer and the value is treated as
+ * a bool. */
+#define GRPC_ARG_ADDRESS_IS_GRPCLB_LOAD_BALANCER \
+  "grpc.address_is_grpclb_load_balancer"
+/** Channel arg indicating if a target corresponding to the address is a backend
+ * received from a balancer. The type of this arg is an integer and the value is
+ * treated as a bool. */
+#define GRPC_ARG_ADDRESS_IS_BACKEND_FROM_GRPCLB_LOAD_BALANCER \
+  "grpc.address_is_backend_from_grpclb_load_balancer"
+
+#endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_LB_POLICY_GRPCLB_GRPCLB_H \
+        */
diff --git a/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc b/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc
index a07e903..76df976 100644
--- a/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc
@@ -62,31 +62,65 @@
  private:
   ~PickFirst();
 
+  class PickFirstSubchannelList;
+
+  class PickFirstSubchannelData
+      : public SubchannelData<PickFirstSubchannelList,
+                              PickFirstSubchannelData> {
+   public:
+    PickFirstSubchannelData(PickFirstSubchannelList* subchannel_list,
+                            const grpc_lb_user_data_vtable* user_data_vtable,
+                            const grpc_lb_address& address,
+                            grpc_subchannel* subchannel,
+                            grpc_combiner* combiner)
+        : SubchannelData(subchannel_list, user_data_vtable, address, subchannel,
+                         combiner) {}
+
+    void ProcessConnectivityChangeLocked(
+        grpc_connectivity_state connectivity_state, grpc_error* error) override;
+  };
+
+  class PickFirstSubchannelList
+      : public SubchannelList<PickFirstSubchannelList,
+                              PickFirstSubchannelData> {
+   public:
+    PickFirstSubchannelList(PickFirst* policy, TraceFlag* tracer,
+                            const grpc_lb_addresses* addresses,
+                            grpc_combiner* combiner,
+                            grpc_client_channel_factory* client_channel_factory,
+                            const grpc_channel_args& args)
+        : SubchannelList(policy, tracer, addresses, combiner,
+                         client_channel_factory, args) {
+      // Need to maintain a ref to the LB policy as long as we maintain
+      // any references to subchannels, since the subchannels'
+      // pollset_sets will include the LB policy's pollset_set.
+      policy->Ref(DEBUG_LOCATION, "subchannel_list").release();
+    }
+
+    ~PickFirstSubchannelList() {
+      PickFirst* p = static_cast<PickFirst*>(policy());
+      p->Unref(DEBUG_LOCATION, "subchannel_list");
+    }
+  };
+
   void ShutdownLocked() override;
 
   void StartPickingLocked();
   void DestroyUnselectedSubchannelsLocked();
 
-  static void OnConnectivityChangedLocked(void* arg, grpc_error* error);
-
-  void SubchannelListRefForConnectivityWatch(
-      grpc_lb_subchannel_list* subchannel_list, const char* reason);
-  void SubchannelListUnrefForConnectivityWatch(
-      grpc_lb_subchannel_list* subchannel_list, const char* reason);
-
-  /** all our subchannels */
-  grpc_lb_subchannel_list* subchannel_list_ = nullptr;
-  /** latest pending subchannel list */
-  grpc_lb_subchannel_list* latest_pending_subchannel_list_ = nullptr;
-  /** selected subchannel in \a subchannel_list */
-  grpc_lb_subchannel_data* selected_ = nullptr;
-  /** have we started picking? */
+  // All our subchannels.
+  OrphanablePtr<PickFirstSubchannelList> subchannel_list_;
+  // Latest pending subchannel list.
+  OrphanablePtr<PickFirstSubchannelList> latest_pending_subchannel_list_;
+  // Selected subchannel in \a subchannel_list_.
+  PickFirstSubchannelData* selected_ = nullptr;
+  // Have we started picking?
   bool started_picking_ = false;
-  /** are we shut down? */
+  // Are we shut down?
   bool shutdown_ = false;
-  /** list of picks that are waiting on connectivity */
+  // List of picks that are waiting on connectivity.
   PickState* pending_picks_ = nullptr;
-  /** our connectivity state tracker */
+  // Our connectivity state tracker.
   grpc_connectivity_state_tracker state_tracker_;
 };
 
@@ -137,15 +171,8 @@
   }
   grpc_connectivity_state_set(&state_tracker_, GRPC_CHANNEL_SHUTDOWN,
                               GRPC_ERROR_REF(error), "shutdown");
-  if (subchannel_list_ != nullptr) {
-    grpc_lb_subchannel_list_shutdown_and_unref(subchannel_list_, "pf_shutdown");
-    subchannel_list_ = nullptr;
-  }
-  if (latest_pending_subchannel_list_ != nullptr) {
-    grpc_lb_subchannel_list_shutdown_and_unref(latest_pending_subchannel_list_,
-                                               "pf_shutdown");
-    latest_pending_subchannel_list_ = nullptr;
-  }
+  subchannel_list_.reset();
+  latest_pending_subchannel_list_.reset();
   TryReresolutionLocked(&grpc_lb_pick_first_trace, GRPC_ERROR_CANCELLED);
   GRPC_ERROR_UNREF(error);
 }
@@ -192,14 +219,10 @@
 
 void PickFirst::StartPickingLocked() {
   started_picking_ = true;
-  if (subchannel_list_ != nullptr && subchannel_list_->num_subchannels > 0) {
-    subchannel_list_->checking_subchannel = 0;
-    for (size_t i = 0; i < subchannel_list_->num_subchannels; ++i) {
-      if (subchannel_list_->subchannels[i].subchannel != nullptr) {
-        SubchannelListRefForConnectivityWatch(
-            subchannel_list_, "connectivity_watch+start_picking");
-        grpc_lb_subchannel_data_start_connectivity_watch(
-            &subchannel_list_->subchannels[i]);
+  if (subchannel_list_ != nullptr) {
+    for (size_t i = 0; i < subchannel_list_->num_subchannels(); ++i) {
+      if (subchannel_list_->subchannel(i)->subchannel() != nullptr) {
+        subchannel_list_->subchannel(i)->StartConnectivityWatchLocked();
         break;
       }
     }
@@ -215,7 +238,7 @@
 bool PickFirst::PickLocked(PickState* pick) {
   // If we have a selected subchannel already, return synchronously.
   if (selected_ != nullptr) {
-    pick->connected_subchannel = selected_->connected_subchannel;
+    pick->connected_subchannel = selected_->connected_subchannel()->Ref();
     return true;
   }
   // No subchannel selected yet, so handle asynchronously.
@@ -228,11 +251,10 @@
 }
 
 void PickFirst::DestroyUnselectedSubchannelsLocked() {
-  for (size_t i = 0; i < subchannel_list_->num_subchannels; ++i) {
-    grpc_lb_subchannel_data* sd = &subchannel_list_->subchannels[i];
+  for (size_t i = 0; i < subchannel_list_->num_subchannels(); ++i) {
+    PickFirstSubchannelData* sd = subchannel_list_->subchannel(i);
     if (selected_ != sd) {
-      grpc_lb_subchannel_data_unref_subchannel(sd,
-                                               "selected_different_subchannel");
+      sd->UnrefSubchannelLocked("selected_different_subchannel");
     }
   }
 }
@@ -249,7 +271,7 @@
 
 void PickFirst::PingOneLocked(grpc_closure* on_initiate, grpc_closure* on_ack) {
   if (selected_ != nullptr) {
-    selected_->connected_subchannel->Ping(on_initiate, on_ack);
+    selected_->connected_subchannel()->Ping(on_initiate, on_ack);
   } else {
     GRPC_CLOSURE_SCHED(on_initiate,
                        GRPC_ERROR_CREATE_FROM_STATIC_STRING("Not connected"));
@@ -258,24 +280,6 @@
   }
 }
 
-void PickFirst::SubchannelListRefForConnectivityWatch(
-    grpc_lb_subchannel_list* subchannel_list, const char* reason) {
-  // TODO(roth): We currently track this ref manually.  Once the new
-  // ClosureRef API is ready and the subchannel_list code has been
-  // converted to a C++ API, find a way to hold the RefCountedPtr<>
-  // somewhere (maybe in the subchannel_data object) instead of doing
-  // this manually.
-  auto self = Ref(DEBUG_LOCATION, reason);
-  self.release();
-  grpc_lb_subchannel_list_ref(subchannel_list, reason);
-}
-
-void PickFirst::SubchannelListUnrefForConnectivityWatch(
-    grpc_lb_subchannel_list* subchannel_list, const char* reason) {
-  Unref(DEBUG_LOCATION, reason);
-  grpc_lb_subchannel_list_unref(subchannel_list, reason);
-}
-
 void PickFirst::UpdateLocked(const grpc_channel_args& args) {
   const grpc_arg* arg = grpc_channel_args_find(&args, GRPC_ARG_LB_ADDRESSES);
   if (arg == nullptr || arg->type != GRPC_ARG_POINTER) {
@@ -295,75 +299,67 @@
     return;
   }
   const grpc_lb_addresses* addresses =
-      (const grpc_lb_addresses*)arg->value.pointer.p;
+      static_cast<const grpc_lb_addresses*>(arg->value.pointer.p);
   if (grpc_lb_pick_first_trace.enabled()) {
     gpr_log(GPR_INFO,
             "Pick First %p received update with %" PRIuPTR " addresses", this,
             addresses->num_addresses);
   }
-  grpc_lb_subchannel_list* subchannel_list = grpc_lb_subchannel_list_create(
+  auto subchannel_list = MakeOrphanable<PickFirstSubchannelList>(
       this, &grpc_lb_pick_first_trace, addresses, combiner(),
-      client_channel_factory(), args, &PickFirst::OnConnectivityChangedLocked);
-  if (subchannel_list->num_subchannels == 0) {
+      client_channel_factory(), args);
+  if (subchannel_list->num_subchannels() == 0) {
     // Empty update or no valid subchannels. Unsubscribe from all current
     // subchannels and put the channel in TRANSIENT_FAILURE.
     grpc_connectivity_state_set(
         &state_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE,
         GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty update"),
         "pf_update_empty");
-    if (subchannel_list_ != nullptr) {
-      grpc_lb_subchannel_list_shutdown_and_unref(subchannel_list_,
-                                                 "sl_shutdown_empty_update");
-    }
-    subchannel_list_ = subchannel_list;  // Empty list.
+    subchannel_list_ = std::move(subchannel_list);  // Empty list.
     selected_ = nullptr;
     return;
   }
   if (selected_ == nullptr) {
     // We don't yet have a selected subchannel, so replace the current
     // subchannel list immediately.
-    if (subchannel_list_ != nullptr) {
-      grpc_lb_subchannel_list_shutdown_and_unref(subchannel_list_,
-                                                 "pf_update_before_selected");
+    subchannel_list_ = std::move(subchannel_list);
+    // If we've started picking, start trying to connect to the first
+    // subchannel in the new list.
+    if (started_picking_) {
+      subchannel_list_->subchannel(0)->StartConnectivityWatchLocked();
     }
-    subchannel_list_ = subchannel_list;
   } else {
     // We do have a selected subchannel.
     // Check if it's present in the new list.  If so, we're done.
-    for (size_t i = 0; i < subchannel_list->num_subchannels; ++i) {
-      grpc_lb_subchannel_data* sd = &subchannel_list->subchannels[i];
-      if (sd->subchannel == selected_->subchannel) {
+    for (size_t i = 0; i < subchannel_list->num_subchannels(); ++i) {
+      PickFirstSubchannelData* sd = subchannel_list->subchannel(i);
+      if (sd->subchannel() == selected_->subchannel()) {
         // The currently selected subchannel is in the update: we are done.
         if (grpc_lb_pick_first_trace.enabled()) {
           gpr_log(GPR_INFO,
                   "Pick First %p found already selected subchannel %p "
                   "at update index %" PRIuPTR " of %" PRIuPTR "; update done",
-                  this, selected_->subchannel, i,
-                  subchannel_list->num_subchannels);
+                  this, selected_->subchannel(), i,
+                  subchannel_list->num_subchannels());
         }
-        if (selected_->connected_subchannel != nullptr) {
-          sd->connected_subchannel = selected_->connected_subchannel;
+        // Make sure it's in state READY.  It might not be if we grabbed
+        // the combiner while a connectivity state notification
+        // informing us otherwise is pending.
+        // Note that CheckConnectivityStateLocked() also takes a ref to
+        // the connected subchannel.
+        grpc_error* error = GRPC_ERROR_NONE;
+        if (sd->CheckConnectivityStateLocked(&error) == GRPC_CHANNEL_READY) {
+          selected_ = sd;
+          subchannel_list_ = std::move(subchannel_list);
+          DestroyUnselectedSubchannelsLocked();
+          sd->StartConnectivityWatchLocked();
+          // If there was a previously pending update (which may or may
+          // not have contained the currently selected subchannel), drop
+          // it, so that it doesn't override what we've done here.
+          latest_pending_subchannel_list_.reset();
+          return;
         }
-        selected_ = sd;
-        if (subchannel_list_ != nullptr) {
-          grpc_lb_subchannel_list_shutdown_and_unref(
-              subchannel_list_, "pf_update_includes_selected");
-        }
-        subchannel_list_ = subchannel_list;
-        DestroyUnselectedSubchannelsLocked();
-        SubchannelListRefForConnectivityWatch(
-            subchannel_list, "connectivity_watch+replace_selected");
-        grpc_lb_subchannel_data_start_connectivity_watch(sd);
-        // If there was a previously pending update (which may or may
-        // not have contained the currently selected subchannel), drop
-        // it, so that it doesn't override what we've done here.
-        if (latest_pending_subchannel_list_ != nullptr) {
-          grpc_lb_subchannel_list_shutdown_and_unref(
-              latest_pending_subchannel_list_,
-              "pf_update_includes_selected+outdated");
-          latest_pending_subchannel_list_ = nullptr;
-        }
-        return;
+        GRPC_ERROR_UNREF(error);
       }
     }
     // Not keeping the previous selected subchannel, so set the latest
@@ -375,85 +371,63 @@
         gpr_log(GPR_INFO,
                 "Pick First %p Shutting down latest pending subchannel list "
                 "%p, about to be replaced by newer latest %p",
-                this, latest_pending_subchannel_list_, subchannel_list);
+                this, latest_pending_subchannel_list_.get(),
+                subchannel_list.get());
       }
-      grpc_lb_subchannel_list_shutdown_and_unref(
-          latest_pending_subchannel_list_, "sl_outdated_dont_smash");
     }
-    latest_pending_subchannel_list_ = subchannel_list;
-  }
-  // If we've started picking, start trying to connect to the first
-  // subchannel in the new list.
-  if (started_picking_) {
-    SubchannelListRefForConnectivityWatch(subchannel_list,
-                                          "connectivity_watch+update");
-    grpc_lb_subchannel_data_start_connectivity_watch(
-        &subchannel_list->subchannels[0]);
+    latest_pending_subchannel_list_ = std::move(subchannel_list);
+    // If we've started picking, start trying to connect to the first
+    // subchannel in the new list.
+    if (started_picking_) {
+      latest_pending_subchannel_list_->subchannel(0)
+          ->StartConnectivityWatchLocked();
+    }
   }
 }
 
-void PickFirst::OnConnectivityChangedLocked(void* arg, grpc_error* error) {
-  grpc_lb_subchannel_data* sd = static_cast<grpc_lb_subchannel_data*>(arg);
-  PickFirst* p = static_cast<PickFirst*>(sd->subchannel_list->policy);
-  if (grpc_lb_pick_first_trace.enabled()) {
-    gpr_log(GPR_INFO,
-            "Pick First %p connectivity changed for subchannel %p (%" PRIuPTR
-            " of %" PRIuPTR
-            "), subchannel_list %p: state=%s p->shutdown_=%d "
-            "sd->subchannel_list->shutting_down=%d error=%s",
-            p, sd->subchannel, sd->subchannel_list->checking_subchannel,
-            sd->subchannel_list->num_subchannels, sd->subchannel_list,
-            grpc_connectivity_state_name(sd->pending_connectivity_state_unsafe),
-            p->shutdown_, sd->subchannel_list->shutting_down,
-            grpc_error_string(error));
-  }
-  // If the policy is shutting down, unref and return.
-  if (p->shutdown_) {
-    grpc_lb_subchannel_data_stop_connectivity_watch(sd);
-    grpc_lb_subchannel_data_unref_subchannel(sd, "pf_shutdown");
-    p->SubchannelListUnrefForConnectivityWatch(sd->subchannel_list,
-                                               "pf_shutdown");
-    return;
-  }
-  // If the subchannel list is shutting down, stop watching.
-  if (sd->subchannel_list->shutting_down || error == GRPC_ERROR_CANCELLED) {
-    grpc_lb_subchannel_data_stop_connectivity_watch(sd);
-    grpc_lb_subchannel_data_unref_subchannel(sd, "pf_sl_shutdown");
-    p->SubchannelListUnrefForConnectivityWatch(sd->subchannel_list,
-                                               "pf_sl_shutdown");
-    return;
-  }
-  // If we're still here, the notification must be for a subchannel in
-  // either the current or latest pending subchannel lists.
-  GPR_ASSERT(sd->subchannel_list == p->subchannel_list_ ||
-             sd->subchannel_list == p->latest_pending_subchannel_list_);
-  // Update state.
-  sd->curr_connectivity_state = sd->pending_connectivity_state_unsafe;
+void PickFirst::PickFirstSubchannelData::ProcessConnectivityChangeLocked(
+    grpc_connectivity_state connectivity_state, grpc_error* error) {
+  PickFirst* p = static_cast<PickFirst*>(subchannel_list()->policy());
+  // The notification must be for a subchannel in either the current or
+  // latest pending subchannel lists.
+  GPR_ASSERT(subchannel_list() == p->subchannel_list_.get() ||
+             subchannel_list() == p->latest_pending_subchannel_list_.get());
   // Handle updates for the currently selected subchannel.
-  if (p->selected_ == sd) {
+  if (p->selected_ == this) {
+    if (grpc_lb_pick_first_trace.enabled()) {
+      gpr_log(GPR_INFO,
+              "Pick First %p connectivity changed for selected subchannel", p);
+    }
     // If the new state is anything other than READY and there is a
     // pending update, switch to the pending update.
-    if (sd->curr_connectivity_state != GRPC_CHANNEL_READY &&
+    if (connectivity_state != GRPC_CHANNEL_READY &&
         p->latest_pending_subchannel_list_ != nullptr) {
+      if (grpc_lb_pick_first_trace.enabled()) {
+        gpr_log(GPR_INFO,
+                "Pick First %p promoting pending subchannel list %p to "
+                "replace %p",
+                p, p->latest_pending_subchannel_list_.get(),
+                p->subchannel_list_.get());
+      }
       p->selected_ = nullptr;
-      grpc_lb_subchannel_data_stop_connectivity_watch(sd);
-      p->SubchannelListUnrefForConnectivityWatch(
-          sd->subchannel_list, "selected_not_ready+switch_to_update");
-      grpc_lb_subchannel_list_shutdown_and_unref(
-          p->subchannel_list_, "selected_not_ready+switch_to_update");
-      p->subchannel_list_ = p->latest_pending_subchannel_list_;
-      p->latest_pending_subchannel_list_ = nullptr;
+      StopConnectivityWatchLocked();
+      p->subchannel_list_ = std::move(p->latest_pending_subchannel_list_);
       grpc_connectivity_state_set(
           &p->state_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE,
-          GRPC_ERROR_REF(error), "selected_not_ready+switch_to_update");
+          error != GRPC_ERROR_NONE
+              ? GRPC_ERROR_REF(error)
+              : GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                    "selected subchannel not ready; switching to pending "
+                    "update"),
+          "selected_not_ready+switch_to_update");
     } else {
       // TODO(juanlishen): we re-resolve when the selected subchannel goes to
       // TRANSIENT_FAILURE because we used to shut down in this case before
       // re-resolution is introduced. But we need to investigate whether we
       // really want to take any action instead of waiting for the selected
       // subchannel reconnecting.
-      GPR_ASSERT(sd->curr_connectivity_state != GRPC_CHANNEL_SHUTDOWN);
-      if (sd->curr_connectivity_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
+      GPR_ASSERT(connectivity_state != GRPC_CHANNEL_SHUTDOWN);
+      if (connectivity_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
         // If the selected channel goes bad, request a re-resolution.
         grpc_connectivity_state_set(&p->state_tracker_, GRPC_CHANNEL_IDLE,
                                     GRPC_ERROR_NONE,
@@ -462,19 +436,16 @@
         p->TryReresolutionLocked(&grpc_lb_pick_first_trace, GRPC_ERROR_NONE);
         // In transient failure. Rely on re-resolution to recover.
         p->selected_ = nullptr;
-        grpc_lb_subchannel_data_stop_connectivity_watch(sd);
-        p->SubchannelListUnrefForConnectivityWatch(sd->subchannel_list,
-                                                   "pf_selected_shutdown");
-        grpc_lb_subchannel_data_unref_subchannel(
-            sd, "pf_selected_shutdown");  // Unrefs connected subchannel
+        UnrefSubchannelLocked("pf_selected_shutdown");
+        StopConnectivityWatchLocked();
       } else {
-        grpc_connectivity_state_set(&p->state_tracker_,
-                                    sd->curr_connectivity_state,
+        grpc_connectivity_state_set(&p->state_tracker_, connectivity_state,
                                     GRPC_ERROR_REF(error), "selected_changed");
         // Renew notification.
-        grpc_lb_subchannel_data_start_connectivity_watch(sd);
+        RenewConnectivityWatchLocked();
       }
     }
+    GRPC_ERROR_UNREF(error);
     return;
   }
   // If we get here, there are two possible cases:
@@ -486,26 +457,27 @@
   //    for a subchannel in p->latest_pending_subchannel_list_.  The
   //    goal here is to find a subchannel from the update that we can
   //    select in place of the current one.
-  switch (sd->curr_connectivity_state) {
+  switch (connectivity_state) {
     case GRPC_CHANNEL_READY: {
       // Case 2.  Promote p->latest_pending_subchannel_list_ to
       // p->subchannel_list_.
-      sd->connected_subchannel =
-          grpc_subchannel_get_connected_subchannel(sd->subchannel);
-      if (sd->subchannel_list == p->latest_pending_subchannel_list_) {
-        GPR_ASSERT(p->subchannel_list_ != nullptr);
-        grpc_lb_subchannel_list_shutdown_and_unref(p->subchannel_list_,
-                                                   "finish_update");
-        p->subchannel_list_ = p->latest_pending_subchannel_list_;
-        p->latest_pending_subchannel_list_ = nullptr;
+      if (subchannel_list() == p->latest_pending_subchannel_list_.get()) {
+        if (grpc_lb_pick_first_trace.enabled()) {
+          gpr_log(GPR_INFO,
+                  "Pick First %p promoting pending subchannel list %p to "
+                  "replace %p",
+                  p, p->latest_pending_subchannel_list_.get(),
+                  p->subchannel_list_.get());
+        }
+        p->subchannel_list_ = std::move(p->latest_pending_subchannel_list_);
       }
       // Cases 1 and 2.
       grpc_connectivity_state_set(&p->state_tracker_, GRPC_CHANNEL_READY,
                                   GRPC_ERROR_NONE, "connecting_ready");
-      p->selected_ = sd;
+      p->selected_ = this;
       if (grpc_lb_pick_first_trace.enabled()) {
         gpr_log(GPR_INFO, "Pick First %p selected subchannel %p", p,
-                sd->subchannel);
+                subchannel());
       }
       // Drop all other subchannels, since we are now connected.
       p->DestroyUnselectedSubchannelsLocked();
@@ -513,7 +485,8 @@
       PickState* pick;
       while ((pick = p->pending_picks_)) {
         p->pending_picks_ = pick->next;
-        pick->connected_subchannel = p->selected_->connected_subchannel;
+        pick->connected_subchannel =
+            p->selected_->connected_subchannel()->Ref();
         if (grpc_lb_pick_first_trace.enabled()) {
           gpr_log(GPR_INFO,
                   "Servicing pending pick with selected subchannel %p",
@@ -522,45 +495,43 @@
         GRPC_CLOSURE_SCHED(pick->on_complete, GRPC_ERROR_NONE);
       }
       // Renew notification.
-      grpc_lb_subchannel_data_start_connectivity_watch(sd);
+      RenewConnectivityWatchLocked();
       break;
     }
     case GRPC_CHANNEL_TRANSIENT_FAILURE: {
-      grpc_lb_subchannel_data_stop_connectivity_watch(sd);
+      StopConnectivityWatchLocked();
+      PickFirstSubchannelData* sd = this;
       do {
-        sd->subchannel_list->checking_subchannel =
-            (sd->subchannel_list->checking_subchannel + 1) %
-            sd->subchannel_list->num_subchannels;
-        sd = &sd->subchannel_list
-                  ->subchannels[sd->subchannel_list->checking_subchannel];
-      } while (sd->subchannel == nullptr);
+        size_t next_index =
+            (sd->Index() + 1) % subchannel_list()->num_subchannels();
+        sd = subchannel_list()->subchannel(next_index);
+      } while (sd->subchannel() == nullptr);
       // Case 1: Only set state to TRANSIENT_FAILURE if we've tried
       // all subchannels.
-      if (sd->subchannel_list->checking_subchannel == 0 &&
-          sd->subchannel_list == p->subchannel_list_) {
+      if (sd->Index() == 0 && subchannel_list() == p->subchannel_list_.get()) {
         grpc_connectivity_state_set(
             &p->state_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE,
             GRPC_ERROR_REF(error), "connecting_transient_failure");
       }
-      // Reuses the connectivity refs from the previous watch.
-      grpc_lb_subchannel_data_start_connectivity_watch(sd);
+      sd->StartConnectivityWatchLocked();
       break;
     }
     case GRPC_CHANNEL_CONNECTING:
     case GRPC_CHANNEL_IDLE: {
       // Only update connectivity state in case 1.
-      if (sd->subchannel_list == p->subchannel_list_) {
+      if (subchannel_list() == p->subchannel_list_.get()) {
         grpc_connectivity_state_set(&p->state_tracker_, GRPC_CHANNEL_CONNECTING,
                                     GRPC_ERROR_REF(error),
                                     "connecting_changed");
       }
       // Renew notification.
-      grpc_lb_subchannel_data_start_connectivity_watch(sd);
+      RenewConnectivityWatchLocked();
       break;
     }
     case GRPC_CHANNEL_SHUTDOWN:
       GPR_UNREACHABLE_CODE(break);
   }
+  GRPC_ERROR_UNREF(error);
 }
 
 //
diff --git a/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc b/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc
index b470016..79e8ad5 100644
--- a/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc
@@ -73,23 +73,127 @@
  private:
   ~RoundRobin();
 
+  // Forward declaration.
+  class RoundRobinSubchannelList;
+
+  // Data for a particular subchannel in a subchannel list.
+  // This subclass adds the following functionality:
+  // - Tracks user_data associated with each address, which will be
+  //   returned along with picks that select the subchannel.
+  // - Tracks the previous connectivity state of the subchannel, so that
+  //   we know how many subchannels are in each state.
+  class RoundRobinSubchannelData
+      : public SubchannelData<RoundRobinSubchannelList,
+                              RoundRobinSubchannelData> {
+   public:
+    RoundRobinSubchannelData(RoundRobinSubchannelList* subchannel_list,
+                             const grpc_lb_user_data_vtable* user_data_vtable,
+                             const grpc_lb_address& address,
+                             grpc_subchannel* subchannel,
+                             grpc_combiner* combiner)
+        : SubchannelData(subchannel_list, user_data_vtable, address, subchannel,
+                         combiner),
+          user_data_vtable_(user_data_vtable),
+          user_data_(user_data_vtable_ != nullptr
+                         ? user_data_vtable_->copy(address.user_data)
+                         : nullptr) {}
+
+    void UnrefSubchannelLocked(const char* reason) override {
+      SubchannelData::UnrefSubchannelLocked(reason);
+      if (user_data_ != nullptr) {
+        GPR_ASSERT(user_data_vtable_ != nullptr);
+        user_data_vtable_->destroy(user_data_);
+        user_data_ = nullptr;
+      }
+    }
+
+    void* user_data() const { return user_data_; }
+
+    grpc_connectivity_state connectivity_state() const {
+      return last_connectivity_state_;
+    }
+
+    void UpdateConnectivityStateLocked(
+        grpc_connectivity_state connectivity_state, grpc_error* error);
+
+   private:
+    void ProcessConnectivityChangeLocked(
+        grpc_connectivity_state connectivity_state, grpc_error* error) override;
+
+    const grpc_lb_user_data_vtable* user_data_vtable_;
+    void* user_data_ = nullptr;
+    grpc_connectivity_state last_connectivity_state_ = GRPC_CHANNEL_IDLE;
+  };
+
+  // A list of subchannels.
+  class RoundRobinSubchannelList
+      : public SubchannelList<RoundRobinSubchannelList,
+                              RoundRobinSubchannelData> {
+   public:
+    RoundRobinSubchannelList(
+        RoundRobin* policy, TraceFlag* tracer,
+        const grpc_lb_addresses* addresses, grpc_combiner* combiner,
+        grpc_client_channel_factory* client_channel_factory,
+        const grpc_channel_args& args)
+        : SubchannelList(policy, tracer, addresses, combiner,
+                         client_channel_factory, args) {
+      // Need to maintain a ref to the LB policy as long as we maintain
+      // any references to subchannels, since the subchannels'
+      // pollset_sets will include the LB policy's pollset_set.
+      policy->Ref(DEBUG_LOCATION, "subchannel_list").release();
+    }
+
+    ~RoundRobinSubchannelList() {
+      GRPC_ERROR_UNREF(last_transient_failure_error_);
+      RoundRobin* p = static_cast<RoundRobin*>(policy());
+      p->Unref(DEBUG_LOCATION, "subchannel_list");
+    }
+
+    // Starts watching the subchannels in this list.
+    void StartWatchingLocked();
+
+    // Updates the counters of subchannels in each state when a
+    // subchannel transitions from old_state to new_state.
+    // transient_failure_error is the error that is reported when
+    // new_state is TRANSIENT_FAILURE.
+    void UpdateStateCountersLocked(grpc_connectivity_state old_state,
+                                   grpc_connectivity_state new_state,
+                                   grpc_error* transient_failure_error);
+
+    // If this subchannel list is the RR policy's current subchannel
+    // list, updates the RR policy's connectivity state based on the
+    // subchannel list's state counters.
+    void MaybeUpdateRoundRobinConnectivityStateLocked();
+
+    // Updates the RR policy's overall state based on the counters of
+    // subchannels in each state.
+    void UpdateRoundRobinStateFromSubchannelStateCountsLocked();
+
+    size_t GetNextReadySubchannelIndexLocked();
+    void UpdateLastReadySubchannelIndexLocked(size_t last_ready_index);
+
+   private:
+    size_t num_ready_ = 0;
+    size_t num_connecting_ = 0;
+    size_t num_transient_failure_ = 0;
+    grpc_error* last_transient_failure_error_ = GRPC_ERROR_NONE;
+    size_t last_ready_index_ = -1;  // Index into list of last pick.
+  };
+
   void ShutdownLocked() override;
 
   void StartPickingLocked();
-  size_t GetNextReadySubchannelIndexLocked();
-  void UpdateLastReadySubchannelIndexLocked(size_t last_ready_index);
-  void UpdateConnectivityStatusLocked(grpc_lb_subchannel_data* sd,
-                                      grpc_error* error);
-
-  static void OnConnectivityChangedLocked(void* arg, grpc_error* error);
-
-  void SubchannelListRefForConnectivityWatch(
-      grpc_lb_subchannel_list* subchannel_list, const char* reason);
-  void SubchannelListUnrefForConnectivityWatch(
-      grpc_lb_subchannel_list* subchannel_list, const char* reason);
+  bool DoPickLocked(PickState* pick);
+  void DrainPendingPicksLocked();
 
   /** list of subchannels */
-  grpc_lb_subchannel_list* subchannel_list_ = nullptr;
+  OrphanablePtr<RoundRobinSubchannelList> subchannel_list_;
+  /** Latest version of the subchannel list.
+   * Subchannel connectivity callbacks will only promote updated subchannel
+   * lists if they equal \a latest_pending_subchannel_list. In other words,
+   * racing callbacks that reference outdated subchannel lists won't perform any
+   * update. */
+  OrphanablePtr<RoundRobinSubchannelList> latest_pending_subchannel_list_;
   /** have we started picking? */
   bool started_picking_ = false;
   /** are we shutting down? */
@@ -98,14 +202,6 @@
   PickState* pending_picks_ = nullptr;
   /** our connectivity state tracker */
   grpc_connectivity_state_tracker state_tracker_;
-  /** Index into subchannels for last pick. */
-  size_t last_ready_subchannel_index_ = 0;
-  /** Latest version of the subchannel list.
-   * Subchannel connectivity callbacks will only promote updated subchannel
-   * lists if they equal \a latest_pending_subchannel_list. In other words,
-   * racing callbacks that reference outdated subchannel lists won't perform any
-   * update. */
-  grpc_lb_subchannel_list* latest_pending_subchannel_list_ = nullptr;
 };
 
 RoundRobin::RoundRobin(const Args& args) : LoadBalancingPolicy(args) {
@@ -115,7 +211,7 @@
   UpdateLocked(*args.args);
   if (grpc_lb_round_robin_trace.enabled()) {
     gpr_log(GPR_INFO, "[RR %p] Created with %" PRIuPTR " subchannels", this,
-            subchannel_list_->num_subchannels);
+            subchannel_list_->num_subchannels());
   }
   grpc_subchannel_index_ref();
 }
@@ -131,68 +227,6 @@
   grpc_subchannel_index_unref();
 }
 
-/** Returns the index into p->subchannel_list->subchannels of the next
- * subchannel in READY state, or p->subchannel_list->num_subchannels if no
- * subchannel is READY.
- *
- * Note that this function does *not* update p->last_ready_subchannel_index.
- * The caller must do that if it returns a pick. */
-size_t RoundRobin::GetNextReadySubchannelIndexLocked() {
-  GPR_ASSERT(subchannel_list_ != nullptr);
-  if (grpc_lb_round_robin_trace.enabled()) {
-    gpr_log(GPR_INFO,
-            "[RR %p] getting next ready subchannel (out of %" PRIuPTR
-            "), "
-            "last_ready_subchannel_index=%" PRIuPTR,
-            this, subchannel_list_->num_subchannels,
-            last_ready_subchannel_index_);
-  }
-  for (size_t i = 0; i < subchannel_list_->num_subchannels; ++i) {
-    const size_t index = (i + last_ready_subchannel_index_ + 1) %
-                         subchannel_list_->num_subchannels;
-    if (grpc_lb_round_robin_trace.enabled()) {
-      gpr_log(
-          GPR_INFO,
-          "[RR %p] checking subchannel %p, subchannel_list %p, index %" PRIuPTR
-          ": state=%s",
-          this, subchannel_list_->subchannels[index].subchannel,
-          subchannel_list_, index,
-          grpc_connectivity_state_name(
-              subchannel_list_->subchannels[index].curr_connectivity_state));
-    }
-    if (subchannel_list_->subchannels[index].curr_connectivity_state ==
-        GRPC_CHANNEL_READY) {
-      if (grpc_lb_round_robin_trace.enabled()) {
-        gpr_log(GPR_INFO,
-                "[RR %p] found next ready subchannel (%p) at index %" PRIuPTR
-                " of subchannel_list %p",
-                this, subchannel_list_->subchannels[index].subchannel, index,
-                subchannel_list_);
-      }
-      return index;
-    }
-  }
-  if (grpc_lb_round_robin_trace.enabled()) {
-    gpr_log(GPR_INFO, "[RR %p] no subchannels in ready state", this);
-  }
-  return subchannel_list_->num_subchannels;
-}
-
-// Sets last_ready_subchannel_index_ to last_ready_index.
-void RoundRobin::UpdateLastReadySubchannelIndexLocked(size_t last_ready_index) {
-  GPR_ASSERT(last_ready_index < subchannel_list_->num_subchannels);
-  last_ready_subchannel_index_ = last_ready_index;
-  if (grpc_lb_round_robin_trace.enabled()) {
-    gpr_log(GPR_INFO,
-            "[RR %p] setting last_ready_subchannel_index=%" PRIuPTR
-            " (SC %p, CSC %p)",
-            this, last_ready_index,
-            subchannel_list_->subchannels[last_ready_index].subchannel,
-            subchannel_list_->subchannels[last_ready_index]
-                .connected_subchannel.get());
-  }
-}
-
 void RoundRobin::HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) {
   PickState* pick;
   while ((pick = pending_picks_) != nullptr) {
@@ -218,16 +252,8 @@
   }
   grpc_connectivity_state_set(&state_tracker_, GRPC_CHANNEL_SHUTDOWN,
                               GRPC_ERROR_REF(error), "rr_shutdown");
-  if (subchannel_list_ != nullptr) {
-    grpc_lb_subchannel_list_shutdown_and_unref(subchannel_list_,
-                                               "sl_shutdown_rr_shutdown");
-    subchannel_list_ = nullptr;
-  }
-  if (latest_pending_subchannel_list_ != nullptr) {
-    grpc_lb_subchannel_list_shutdown_and_unref(
-        latest_pending_subchannel_list_, "sl_shutdown_pending_rr_shutdown");
-    latest_pending_subchannel_list_ = nullptr;
-  }
+  subchannel_list_.reset();
+  latest_pending_subchannel_list_.reset();
   TryReresolutionLocked(&grpc_lb_round_robin_trace, GRPC_ERROR_CANCELLED);
   GRPC_ERROR_UNREF(error);
 }
@@ -273,34 +299,9 @@
   GRPC_ERROR_UNREF(error);
 }
 
-void RoundRobin::SubchannelListRefForConnectivityWatch(
-    grpc_lb_subchannel_list* subchannel_list, const char* reason) {
-  // TODO(roth): We currently track this ref manually.  Once the new
-  // ClosureRef API is ready and the subchannel_list code has been
-  // converted to a C++ API, find a way to hold the RefCountedPtr<>
-  // somewhere (maybe in the subchannel_data object) instead of doing
-  // this manually.
-  auto self = Ref(DEBUG_LOCATION, reason);
-  self.release();
-  grpc_lb_subchannel_list_ref(subchannel_list, reason);
-}
-
-void RoundRobin::SubchannelListUnrefForConnectivityWatch(
-    grpc_lb_subchannel_list* subchannel_list, const char* reason) {
-  Unref(DEBUG_LOCATION, reason);
-  grpc_lb_subchannel_list_unref(subchannel_list, reason);
-}
-
 void RoundRobin::StartPickingLocked() {
   started_picking_ = true;
-  for (size_t i = 0; i < subchannel_list_->num_subchannels; i++) {
-    if (subchannel_list_->subchannels[i].subchannel != nullptr) {
-      SubchannelListRefForConnectivityWatch(subchannel_list_,
-                                            "connectivity_watch");
-      grpc_lb_subchannel_data_start_connectivity_watch(
-          &subchannel_list_->subchannels[i]);
-    }
-  }
+  subchannel_list_->StartWatchingLocked();
 }
 
 void RoundRobin::ExitIdleLocked() {
@@ -309,33 +310,48 @@
   }
 }
 
+bool RoundRobin::DoPickLocked(PickState* pick) {
+  const size_t next_ready_index =
+      subchannel_list_->GetNextReadySubchannelIndexLocked();
+  if (next_ready_index < subchannel_list_->num_subchannels()) {
+    /* readily available, report right away */
+    RoundRobinSubchannelData* sd =
+        subchannel_list_->subchannel(next_ready_index);
+    GPR_ASSERT(sd->connected_subchannel() != nullptr);
+    pick->connected_subchannel = sd->connected_subchannel()->Ref();
+    if (pick->user_data != nullptr) {
+      *pick->user_data = sd->user_data();
+    }
+    if (grpc_lb_round_robin_trace.enabled()) {
+      gpr_log(GPR_INFO,
+              "[RR %p] Picked target <-- Subchannel %p (connected %p) (sl %p, "
+              "index %" PRIuPTR ")",
+              this, sd->subchannel(), pick->connected_subchannel.get(),
+              sd->subchannel_list(), next_ready_index);
+    }
+    /* only advance the last picked pointer if the selection was used */
+    subchannel_list_->UpdateLastReadySubchannelIndexLocked(next_ready_index);
+    return true;
+  }
+  return false;
+}
+
+void RoundRobin::DrainPendingPicksLocked() {
+  PickState* pick;
+  while ((pick = pending_picks_)) {
+    pending_picks_ = pick->next;
+    GPR_ASSERT(DoPickLocked(pick));
+    GRPC_CLOSURE_SCHED(pick->on_complete, GRPC_ERROR_NONE);
+  }
+}
+
 bool RoundRobin::PickLocked(PickState* pick) {
   if (grpc_lb_round_robin_trace.enabled()) {
     gpr_log(GPR_INFO, "[RR %p] Trying to pick (shutdown: %d)", this, shutdown_);
   }
   GPR_ASSERT(!shutdown_);
   if (subchannel_list_ != nullptr) {
-    const size_t next_ready_index = GetNextReadySubchannelIndexLocked();
-    if (next_ready_index < subchannel_list_->num_subchannels) {
-      /* readily available, report right away */
-      grpc_lb_subchannel_data* sd =
-          &subchannel_list_->subchannels[next_ready_index];
-      pick->connected_subchannel = sd->connected_subchannel;
-      if (pick->user_data != nullptr) {
-        *pick->user_data = sd->user_data;
-      }
-      if (grpc_lb_round_robin_trace.enabled()) {
-        gpr_log(
-            GPR_INFO,
-            "[RR %p] Picked target <-- Subchannel %p (connected %p) (sl %p, "
-            "index %" PRIuPTR ")",
-            this, sd->subchannel, pick->connected_subchannel.get(),
-            sd->subchannel_list, next_ready_index);
-      }
-      /* only advance the last picked pointer if the selection was used */
-      UpdateLastReadySubchannelIndexLocked(next_ready_index);
-      return true;
-    }
+    if (DoPickLocked(pick)) return true;
   }
   /* no pick currently available. Save for later in list of pending picks */
   if (!started_picking_) {
@@ -346,36 +362,62 @@
   return false;
 }
 
-void UpdateStateCountersLocked(grpc_lb_subchannel_data* sd) {
-  grpc_lb_subchannel_list* subchannel_list = sd->subchannel_list;
-  GPR_ASSERT(sd->prev_connectivity_state != GRPC_CHANNEL_SHUTDOWN);
-  GPR_ASSERT(sd->curr_connectivity_state != GRPC_CHANNEL_SHUTDOWN);
-  if (sd->prev_connectivity_state == GRPC_CHANNEL_READY) {
-    GPR_ASSERT(subchannel_list->num_ready > 0);
-    --subchannel_list->num_ready;
-  } else if (sd->prev_connectivity_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
-    GPR_ASSERT(subchannel_list->num_transient_failures > 0);
-    --subchannel_list->num_transient_failures;
-  } else if (sd->prev_connectivity_state == GRPC_CHANNEL_IDLE) {
-    GPR_ASSERT(subchannel_list->num_idle > 0);
-    --subchannel_list->num_idle;
+void RoundRobin::RoundRobinSubchannelList::StartWatchingLocked() {
+  if (num_subchannels() == 0) return;
+  // Check current state of each subchannel synchronously, since any
+  // subchannel already used by some other channel may have a non-IDLE
+  // state.
+  for (size_t i = 0; i < num_subchannels(); ++i) {
+    grpc_error* error = GRPC_ERROR_NONE;
+    grpc_connectivity_state state =
+        subchannel(i)->CheckConnectivityStateLocked(&error);
+    if (state != GRPC_CHANNEL_IDLE) {
+      subchannel(i)->UpdateConnectivityStateLocked(state, error);
+    }
   }
-  sd->prev_connectivity_state = sd->curr_connectivity_state;
-  if (sd->curr_connectivity_state == GRPC_CHANNEL_READY) {
-    ++subchannel_list->num_ready;
-  } else if (sd->curr_connectivity_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
-    ++subchannel_list->num_transient_failures;
-  } else if (sd->curr_connectivity_state == GRPC_CHANNEL_IDLE) {
-    ++subchannel_list->num_idle;
+  // Now set the LB policy's state based on the subchannels' states.
+  UpdateRoundRobinStateFromSubchannelStateCountsLocked();
+  // Start connectivity watch for each subchannel.
+  for (size_t i = 0; i < num_subchannels(); i++) {
+    if (subchannel(i)->subchannel() != nullptr) {
+      subchannel(i)->StartConnectivityWatchLocked();
+    }
   }
 }
 
-/** Sets the policy's connectivity status based on that of the passed-in \a sd
- * (the grpc_lb_subchannel_data associated with the updated subchannel) and the
- * subchannel list \a sd belongs to (sd->subchannel_list). \a error will be used
- * only if the policy transitions to state TRANSIENT_FAILURE. */
-void RoundRobin::UpdateConnectivityStatusLocked(grpc_lb_subchannel_data* sd,
-                                                grpc_error* error) {
+void RoundRobin::RoundRobinSubchannelList::UpdateStateCountersLocked(
+    grpc_connectivity_state old_state, grpc_connectivity_state new_state,
+    grpc_error* transient_failure_error) {
+  GPR_ASSERT(old_state != GRPC_CHANNEL_SHUTDOWN);
+  GPR_ASSERT(new_state != GRPC_CHANNEL_SHUTDOWN);
+  if (old_state == GRPC_CHANNEL_READY) {
+    GPR_ASSERT(num_ready_ > 0);
+    --num_ready_;
+  } else if (old_state == GRPC_CHANNEL_CONNECTING) {
+    GPR_ASSERT(num_connecting_ > 0);
+    --num_connecting_;
+  } else if (old_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
+    GPR_ASSERT(num_transient_failure_ > 0);
+    --num_transient_failure_;
+  }
+  if (new_state == GRPC_CHANNEL_READY) {
+    ++num_ready_;
+  } else if (new_state == GRPC_CHANNEL_CONNECTING) {
+    ++num_connecting_;
+  } else if (new_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
+    ++num_transient_failure_;
+  }
+  GRPC_ERROR_UNREF(last_transient_failure_error_);
+  last_transient_failure_error_ = transient_failure_error;
+}
+
+// Sets the RR policy's connectivity state based on the current
+// subchannel list.
+void RoundRobin::RoundRobinSubchannelList::
+    MaybeUpdateRoundRobinConnectivityStateLocked() {
+  RoundRobin* p = static_cast<RoundRobin*>(policy());
+  // Only set connectivity state if this is the current subchannel list.
+  if (p->subchannel_list_.get() != this) return;
   /* In priority order. The first rule to match terminates the search (ie, if we
    * are on rule n, all previous rules were unfulfilled).
    *
@@ -390,155 +432,151 @@
    *    CHECK: subchannel_list->num_transient_failures ==
    *           subchannel_list->num_subchannels.
    */
-  grpc_lb_subchannel_list* subchannel_list = sd->subchannel_list;
-  GPR_ASSERT(sd->curr_connectivity_state != GRPC_CHANNEL_IDLE);
-  if (subchannel_list->num_ready > 0) {
+  if (num_ready_ > 0) {
     /* 1) READY */
-    grpc_connectivity_state_set(&state_tracker_, GRPC_CHANNEL_READY,
+    grpc_connectivity_state_set(&p->state_tracker_, GRPC_CHANNEL_READY,
                                 GRPC_ERROR_NONE, "rr_ready");
-  } else if (sd->curr_connectivity_state == GRPC_CHANNEL_CONNECTING) {
+  } else if (num_connecting_ > 0) {
     /* 2) CONNECTING */
-    grpc_connectivity_state_set(&state_tracker_, GRPC_CHANNEL_CONNECTING,
+    grpc_connectivity_state_set(&p->state_tracker_, GRPC_CHANNEL_CONNECTING,
                                 GRPC_ERROR_NONE, "rr_connecting");
-  } else if (subchannel_list->num_transient_failures ==
-             subchannel_list->num_subchannels) {
+  } else if (num_transient_failure_ == num_subchannels()) {
     /* 3) TRANSIENT_FAILURE */
-    grpc_connectivity_state_set(&state_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE,
-                                GRPC_ERROR_REF(error),
+    grpc_connectivity_state_set(&p->state_tracker_,
+                                GRPC_CHANNEL_TRANSIENT_FAILURE,
+                                GRPC_ERROR_REF(last_transient_failure_error_),
                                 "rr_exhausted_subchannels");
   }
-  GRPC_ERROR_UNREF(error);
 }
 
-void RoundRobin::OnConnectivityChangedLocked(void* arg, grpc_error* error) {
-  grpc_lb_subchannel_data* sd = static_cast<grpc_lb_subchannel_data*>(arg);
-  RoundRobin* p = static_cast<RoundRobin*>(sd->subchannel_list->policy);
+void RoundRobin::RoundRobinSubchannelList::
+    UpdateRoundRobinStateFromSubchannelStateCountsLocked() {
+  RoundRobin* p = static_cast<RoundRobin*>(policy());
+  if (num_ready_ > 0) {
+    if (p->subchannel_list_.get() != this) {
+      // Promote this list to p->subchannel_list_.
+      // This list must be p->latest_pending_subchannel_list_, because
+      // any previous update would have been shut down already and
+      // therefore we would not be receiving a notification for them.
+      GPR_ASSERT(p->latest_pending_subchannel_list_.get() == this);
+      GPR_ASSERT(!shutting_down());
+      if (grpc_lb_round_robin_trace.enabled()) {
+        const size_t old_num_subchannels =
+            p->subchannel_list_ != nullptr
+                ? p->subchannel_list_->num_subchannels()
+                : 0;
+        gpr_log(GPR_INFO,
+                "[RR %p] phasing out subchannel list %p (size %" PRIuPTR
+                ") in favor of %p (size %" PRIuPTR ")",
+                p, p->subchannel_list_.get(), old_num_subchannels, this,
+                num_subchannels());
+      }
+      p->subchannel_list_ = std::move(p->latest_pending_subchannel_list_);
+    }
+    // Drain pending picks.
+    p->DrainPendingPicksLocked();
+  }
+  // Update the RR policy's connectivity state if needed.
+  MaybeUpdateRoundRobinConnectivityStateLocked();
+}
+
+void RoundRobin::RoundRobinSubchannelData::UpdateConnectivityStateLocked(
+    grpc_connectivity_state connectivity_state, grpc_error* error) {
+  RoundRobin* p = static_cast<RoundRobin*>(subchannel_list()->policy());
   if (grpc_lb_round_robin_trace.enabled()) {
     gpr_log(
         GPR_INFO,
-        "[RR %p] connectivity changed for subchannel %p, subchannel_list %p: "
-        "prev_state=%s new_state=%s p->shutdown=%d "
-        "sd->subchannel_list->shutting_down=%d error=%s",
-        p, sd->subchannel, sd->subchannel_list,
-        grpc_connectivity_state_name(sd->prev_connectivity_state),
-        grpc_connectivity_state_name(sd->pending_connectivity_state_unsafe),
-        p->shutdown_, sd->subchannel_list->shutting_down,
-        grpc_error_string(error));
+        "[RR %p] connectivity changed for subchannel %p, subchannel_list %p "
+        "(index %" PRIuPTR " of %" PRIuPTR "): prev_state=%s new_state=%s",
+        p, subchannel(), subchannel_list(), Index(),
+        subchannel_list()->num_subchannels(),
+        grpc_connectivity_state_name(last_connectivity_state_),
+        grpc_connectivity_state_name(connectivity_state));
   }
-  GPR_ASSERT(sd->subchannel != nullptr);
-  // If the policy is shutting down, unref and return.
-  if (p->shutdown_) {
-    grpc_lb_subchannel_data_stop_connectivity_watch(sd);
-    grpc_lb_subchannel_data_unref_subchannel(sd, "rr_shutdown");
-    p->SubchannelListUnrefForConnectivityWatch(sd->subchannel_list,
-                                               "rr_shutdown");
-    return;
-  }
-  // If the subchannel list is shutting down, stop watching.
-  if (sd->subchannel_list->shutting_down || error == GRPC_ERROR_CANCELLED) {
-    grpc_lb_subchannel_data_stop_connectivity_watch(sd);
-    grpc_lb_subchannel_data_unref_subchannel(sd, "rr_sl_shutdown");
-    p->SubchannelListUnrefForConnectivityWatch(sd->subchannel_list,
-                                               "rr_sl_shutdown");
-    return;
-  }
-  // If we're still here, the notification must be for a subchannel in
-  // either the current or latest pending subchannel lists.
-  GPR_ASSERT(sd->subchannel_list == p->subchannel_list_ ||
-             sd->subchannel_list == p->latest_pending_subchannel_list_);
-  GPR_ASSERT(sd->pending_connectivity_state_unsafe != GRPC_CHANNEL_SHUTDOWN);
-  // Now that we're inside the combiner, copy the pending connectivity
-  // state (which was set by the connectivity state watcher) to
-  // curr_connectivity_state, which is what we use inside of the combiner.
-  sd->curr_connectivity_state = sd->pending_connectivity_state_unsafe;
-  // If the sd's new state is TRANSIENT_FAILURE, unref the *connected*
-  // subchannel, if any.
-  switch (sd->curr_connectivity_state) {
-    case GRPC_CHANNEL_TRANSIENT_FAILURE: {
-      sd->connected_subchannel.reset();
-      if (grpc_lb_round_robin_trace.enabled()) {
-        gpr_log(GPR_INFO,
-                "[RR %p] Subchannel %p has gone into TRANSIENT_FAILURE. "
-                "Requesting re-resolution",
-                p, sd->subchannel);
-      }
-      p->TryReresolutionLocked(&grpc_lb_round_robin_trace, GRPC_ERROR_NONE);
-      break;
+  subchannel_list()->UpdateStateCountersLocked(last_connectivity_state_,
+                                               connectivity_state, error);
+  last_connectivity_state_ = connectivity_state;
+}
+
+void RoundRobin::RoundRobinSubchannelData::ProcessConnectivityChangeLocked(
+    grpc_connectivity_state connectivity_state, grpc_error* error) {
+  RoundRobin* p = static_cast<RoundRobin*>(subchannel_list()->policy());
+  GPR_ASSERT(subchannel() != nullptr);
+  // If the new state is TRANSIENT_FAILURE, re-resolve.
+  // Only do this if we've started watching, not at startup time.
+  // Otherwise, if the subchannel was already in state TRANSIENT_FAILURE
+  // when the subchannel list was created, we'd wind up in a constant
+  // loop of re-resolution.
+  if (connectivity_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
+    if (grpc_lb_round_robin_trace.enabled()) {
+      gpr_log(GPR_INFO,
+              "[RR %p] Subchannel %p has gone into TRANSIENT_FAILURE. "
+              "Requesting re-resolution",
+              p, subchannel());
     }
-    case GRPC_CHANNEL_READY: {
-      if (sd->connected_subchannel == nullptr) {
-        sd->connected_subchannel =
-            grpc_subchannel_get_connected_subchannel(sd->subchannel);
-      }
-      if (sd->subchannel_list != p->subchannel_list_) {
-        // promote sd->subchannel_list to p->subchannel_list_.
-        // sd->subchannel_list must be equal to
-        // p->latest_pending_subchannel_list_ because we have already filtered
-        // for sds belonging to outdated subchannel lists.
-        GPR_ASSERT(sd->subchannel_list == p->latest_pending_subchannel_list_);
-        GPR_ASSERT(!sd->subchannel_list->shutting_down);
-        if (grpc_lb_round_robin_trace.enabled()) {
-          const size_t num_subchannels =
-              p->subchannel_list_ != nullptr
-                  ? p->subchannel_list_->num_subchannels
-                  : 0;
-          gpr_log(GPR_INFO,
-                  "[RR %p] phasing out subchannel list %p (size %" PRIuPTR
-                  ") in favor of %p (size %" PRIuPTR ")",
-                  p, p->subchannel_list_, num_subchannels, sd->subchannel_list,
-                  num_subchannels);
-        }
-        if (p->subchannel_list_ != nullptr) {
-          // dispose of the current subchannel_list
-          grpc_lb_subchannel_list_shutdown_and_unref(p->subchannel_list_,
-                                                     "sl_phase_out_shutdown");
-        }
-        p->subchannel_list_ = p->latest_pending_subchannel_list_;
-        p->latest_pending_subchannel_list_ = nullptr;
-      }
-      /* at this point we know there's at least one suitable subchannel. Go
-       * ahead and pick one and notify the pending suitors in
-       * p->pending_picks. This preemptively replicates rr_pick()'s actions. */
-      const size_t next_ready_index = p->GetNextReadySubchannelIndexLocked();
-      GPR_ASSERT(next_ready_index < p->subchannel_list_->num_subchannels);
-      grpc_lb_subchannel_data* selected =
-          &p->subchannel_list_->subchannels[next_ready_index];
-      if (p->pending_picks_ != nullptr) {
-        // if the selected subchannel is going to be used for the pending
-        // picks, update the last picked pointer
-        p->UpdateLastReadySubchannelIndexLocked(next_ready_index);
-      }
-      PickState* pick;
-      while ((pick = p->pending_picks_)) {
-        p->pending_picks_ = pick->next;
-        pick->connected_subchannel = selected->connected_subchannel;
-        if (pick->user_data != nullptr) {
-          *pick->user_data = selected->user_data;
-        }
-        if (grpc_lb_round_robin_trace.enabled()) {
-          gpr_log(GPR_INFO,
-                  "[RR %p] Fulfilling pending pick. Target <-- subchannel %p "
-                  "(subchannel_list %p, index %" PRIuPTR ")",
-                  p, selected->subchannel, p->subchannel_list_,
-                  next_ready_index);
-        }
-        GRPC_CLOSURE_SCHED(pick->on_complete, GRPC_ERROR_NONE);
-      }
-      break;
-    }
-    case GRPC_CHANNEL_SHUTDOWN:
-      GPR_UNREACHABLE_CODE(return );
-    case GRPC_CHANNEL_CONNECTING:
-    case GRPC_CHANNEL_IDLE:;  // fallthrough
+    p->TryReresolutionLocked(&grpc_lb_round_robin_trace, GRPC_ERROR_NONE);
   }
   // Update state counters.
-  UpdateStateCountersLocked(sd);
-  // Only update connectivity based on the selected subchannel list.
-  if (sd->subchannel_list == p->subchannel_list_) {
-    p->UpdateConnectivityStatusLocked(sd, GRPC_ERROR_REF(error));
+  UpdateConnectivityStateLocked(connectivity_state, error);
+  // Update overall state and renew notification.
+  subchannel_list()->UpdateRoundRobinStateFromSubchannelStateCountsLocked();
+  RenewConnectivityWatchLocked();
+}
+
+/** Returns the index into p->subchannel_list->subchannels of the next
+ * subchannel in READY state, or p->subchannel_list->num_subchannels if no
+ * subchannel is READY.
+ *
+ * Note that this function does *not* update p->last_ready_subchannel_index.
+ * The caller must do that if it returns a pick. */
+size_t
+RoundRobin::RoundRobinSubchannelList::GetNextReadySubchannelIndexLocked() {
+  if (grpc_lb_round_robin_trace.enabled()) {
+    gpr_log(GPR_INFO,
+            "[RR %p] getting next ready subchannel (out of %" PRIuPTR
+            "), last_ready_index=%" PRIuPTR,
+            policy(), num_subchannels(), last_ready_index_);
   }
-  // Renew notification.
-  grpc_lb_subchannel_data_start_connectivity_watch(sd);
+  for (size_t i = 0; i < num_subchannels(); ++i) {
+    const size_t index = (i + last_ready_index_ + 1) % num_subchannels();
+    if (grpc_lb_round_robin_trace.enabled()) {
+      gpr_log(
+          GPR_INFO,
+          "[RR %p] checking subchannel %p, subchannel_list %p, index %" PRIuPTR
+          ": state=%s",
+          policy(), subchannel(index)->subchannel(), this, index,
+          grpc_connectivity_state_name(
+              subchannel(index)->connectivity_state()));
+    }
+    if (subchannel(index)->connectivity_state() == GRPC_CHANNEL_READY) {
+      if (grpc_lb_round_robin_trace.enabled()) {
+        gpr_log(GPR_INFO,
+                "[RR %p] found next ready subchannel (%p) at index %" PRIuPTR
+                " of subchannel_list %p",
+                policy(), subchannel(index)->subchannel(), index, this);
+      }
+      return index;
+    }
+  }
+  if (grpc_lb_round_robin_trace.enabled()) {
+    gpr_log(GPR_INFO, "[RR %p] no subchannels in ready state", this);
+  }
+  return num_subchannels();
+}
+
+// Sets last_ready_index_ to last_ready_index.
+void RoundRobin::RoundRobinSubchannelList::UpdateLastReadySubchannelIndexLocked(
+    size_t last_ready_index) {
+  GPR_ASSERT(last_ready_index < num_subchannels());
+  last_ready_index_ = last_ready_index;
+  if (grpc_lb_round_robin_trace.enabled()) {
+    gpr_log(GPR_INFO,
+            "[RR %p] setting last_ready_subchannel_index=%" PRIuPTR
+            " (SC %p, CSC %p)",
+            policy(), last_ready_index,
+            subchannel(last_ready_index)->subchannel(),
+            subchannel(last_ready_index)->connected_subchannel());
+  }
 }
 
 grpc_connectivity_state RoundRobin::CheckConnectivityLocked(
@@ -554,11 +592,12 @@
 
 void RoundRobin::PingOneLocked(grpc_closure* on_initiate,
                                grpc_closure* on_ack) {
-  const size_t next_ready_index = GetNextReadySubchannelIndexLocked();
-  if (next_ready_index < subchannel_list_->num_subchannels) {
-    grpc_lb_subchannel_data* selected =
-        &subchannel_list_->subchannels[next_ready_index];
-    selected->connected_subchannel->Ping(on_initiate, on_ack);
+  const size_t next_ready_index =
+      subchannel_list_->GetNextReadySubchannelIndexLocked();
+  if (next_ready_index < subchannel_list_->num_subchannels()) {
+    RoundRobinSubchannelData* selected =
+        subchannel_list_->subchannel(next_ready_index);
+    selected->connected_subchannel()->Ping(on_initiate, on_ack);
   } else {
     GRPC_CLOSURE_SCHED(on_initiate, GRPC_ERROR_CREATE_FROM_STATIC_STRING(
                                         "Round Robin not connected"));
@@ -581,80 +620,37 @@
     }
     return;
   }
-  grpc_lb_addresses* addresses = (grpc_lb_addresses*)arg->value.pointer.p;
+  grpc_lb_addresses* addresses =
+      static_cast<grpc_lb_addresses*>(arg->value.pointer.p);
   if (grpc_lb_round_robin_trace.enabled()) {
     gpr_log(GPR_INFO, "[RR %p] received update with %" PRIuPTR " addresses",
             this, addresses->num_addresses);
   }
-  grpc_lb_subchannel_list* subchannel_list = grpc_lb_subchannel_list_create(
-      this, &grpc_lb_round_robin_trace, addresses, combiner(),
-      client_channel_factory(), args, &RoundRobin::OnConnectivityChangedLocked);
-  if (subchannel_list->num_subchannels == 0) {
-    grpc_connectivity_state_set(
-        &state_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE,
-        GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty update"),
-        "rr_update_empty");
-    if (subchannel_list_ != nullptr) {
-      grpc_lb_subchannel_list_shutdown_and_unref(subchannel_list_,
-                                                 "sl_shutdown_empty_update");
+  // Replace latest_pending_subchannel_list_.
+  if (latest_pending_subchannel_list_ != nullptr) {
+    if (grpc_lb_round_robin_trace.enabled()) {
+      gpr_log(GPR_INFO,
+              "[RR %p] Shutting down previous pending subchannel list %p", this,
+              latest_pending_subchannel_list_.get());
     }
-    subchannel_list_ = subchannel_list;  // empty list
-    return;
   }
-  if (started_picking_) {
-    for (size_t i = 0; i < subchannel_list->num_subchannels; ++i) {
-      const grpc_connectivity_state subchannel_state =
-          grpc_subchannel_check_connectivity(
-              subchannel_list->subchannels[i].subchannel, nullptr);
-      // Override the default setting of IDLE for connectivity notification
-      // purposes if the subchannel is already in transient failure. Otherwise
-      // we'd be immediately notified of the IDLE-TRANSIENT_FAILURE
-      // discrepancy, attempt to re-resolve and end up here again.
-      // TODO(roth): As part of C++-ifying the subchannel_list API, design a
-      // better API for notifying the LB policy of subchannel states, which can
-      // be used both for the subchannel's initial state and for subsequent
-      // state changes. This will allow us to handle this more generally instead
-      // of special-casing TRANSIENT_FAILURE (e.g., we can also distribute any
-      // pending picks across all READY subchannels rather than sending them all
-      // to the first one).
-      if (subchannel_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
-        subchannel_list->subchannels[i].pending_connectivity_state_unsafe =
-            subchannel_list->subchannels[i].curr_connectivity_state =
-                subchannel_list->subchannels[i].prev_connectivity_state =
-                    subchannel_state;
-        --subchannel_list->num_idle;
-        ++subchannel_list->num_transient_failures;
-      }
+  latest_pending_subchannel_list_ = MakeOrphanable<RoundRobinSubchannelList>(
+      this, &grpc_lb_round_robin_trace, addresses, combiner(),
+      client_channel_factory(), args);
+  // If we haven't started picking yet or the new list is empty,
+  // immediately promote the new list to the current list.
+  if (!started_picking_ ||
+      latest_pending_subchannel_list_->num_subchannels() == 0) {
+    if (latest_pending_subchannel_list_->num_subchannels() == 0) {
+      grpc_connectivity_state_set(
+          &state_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE,
+          GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty update"),
+          "rr_update_empty");
     }
-    if (latest_pending_subchannel_list_ != nullptr) {
-      if (grpc_lb_round_robin_trace.enabled()) {
-        gpr_log(GPR_INFO,
-                "[RR %p] Shutting down latest pending subchannel list %p, "
-                "about to be replaced by newer latest %p",
-                this, latest_pending_subchannel_list_, subchannel_list);
-      }
-      grpc_lb_subchannel_list_shutdown_and_unref(
-          latest_pending_subchannel_list_, "sl_outdated");
-    }
-    latest_pending_subchannel_list_ = subchannel_list;
-    for (size_t i = 0; i < subchannel_list->num_subchannels; ++i) {
-      /* Watch every new subchannel. A subchannel list becomes active the
-       * moment one of its subchannels is READY. At that moment, we swap
-       * p->subchannel_list for sd->subchannel_list, provided the subchannel
-       * list is still valid (ie, isn't shutting down) */
-      SubchannelListRefForConnectivityWatch(subchannel_list,
-                                            "connectivity_watch");
-      grpc_lb_subchannel_data_start_connectivity_watch(
-          &subchannel_list->subchannels[i]);
-    }
+    subchannel_list_ = std::move(latest_pending_subchannel_list_);
   } else {
-    // The policy isn't picking yet. Save the update for later, disposing of
-    // previous version if any.
-    if (subchannel_list_ != nullptr) {
-      grpc_lb_subchannel_list_shutdown_and_unref(
-          subchannel_list_, "rr_update_before_started_picking");
-    }
-    subchannel_list_ = subchannel_list;
+    // If we've started picking, start watching the new list.
+    latest_pending_subchannel_list_->StartWatchingLocked();
   }
 }
 
diff --git a/src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc b/src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc
deleted file mode 100644
index 257db57..0000000
--- a/src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- *
- * Copyright 2015 gRPC authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#include <grpc/support/port_platform.h>
-
-#include <string.h>
-
-#include <grpc/support/alloc.h>
-
-#include "src/core/ext/filters/client_channel/lb_policy/subchannel_list.h"
-#include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/debug/trace.h"
-#include "src/core/lib/iomgr/closure.h"
-#include "src/core/lib/iomgr/combiner.h"
-#include "src/core/lib/iomgr/sockaddr_utils.h"
-#include "src/core/lib/transport/connectivity_state.h"
-
-void grpc_lb_subchannel_data_unref_subchannel(grpc_lb_subchannel_data* sd,
-                                              const char* reason) {
-  if (sd->subchannel != nullptr) {
-    if (sd->subchannel_list->tracer->enabled()) {
-      gpr_log(GPR_INFO,
-              "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
-              " (subchannel %p): unreffing subchannel",
-              sd->subchannel_list->tracer->name(), sd->subchannel_list->policy,
-              sd->subchannel_list,
-              static_cast<size_t>(sd - sd->subchannel_list->subchannels),
-              sd->subchannel_list->num_subchannels, sd->subchannel);
-    }
-    GRPC_SUBCHANNEL_UNREF(sd->subchannel, reason);
-    sd->subchannel = nullptr;
-    sd->connected_subchannel.reset();
-    if (sd->user_data != nullptr) {
-      GPR_ASSERT(sd->user_data_vtable != nullptr);
-      sd->user_data_vtable->destroy(sd->user_data);
-      sd->user_data = nullptr;
-    }
-  }
-}
-
-void grpc_lb_subchannel_data_start_connectivity_watch(
-    grpc_lb_subchannel_data* sd) {
-  if (sd->subchannel_list->tracer->enabled()) {
-    gpr_log(
-        GPR_INFO,
-        "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
-        " (subchannel %p): requesting connectivity change "
-        "notification (from %s)",
-        sd->subchannel_list->tracer->name(), sd->subchannel_list->policy,
-        sd->subchannel_list,
-        static_cast<size_t>(sd - sd->subchannel_list->subchannels),
-        sd->subchannel_list->num_subchannels, sd->subchannel,
-        grpc_connectivity_state_name(sd->pending_connectivity_state_unsafe));
-  }
-  sd->connectivity_notification_pending = true;
-  grpc_subchannel_notify_on_state_change(
-      sd->subchannel, sd->subchannel_list->policy->interested_parties(),
-      &sd->pending_connectivity_state_unsafe,
-      &sd->connectivity_changed_closure);
-}
-
-void grpc_lb_subchannel_data_stop_connectivity_watch(
-    grpc_lb_subchannel_data* sd) {
-  if (sd->subchannel_list->tracer->enabled()) {
-    gpr_log(GPR_INFO,
-            "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
-            " (subchannel %p): stopping connectivity watch",
-            sd->subchannel_list->tracer->name(), sd->subchannel_list->policy,
-            sd->subchannel_list,
-            static_cast<size_t>(sd - sd->subchannel_list->subchannels),
-            sd->subchannel_list->num_subchannels, sd->subchannel);
-  }
-  GPR_ASSERT(sd->connectivity_notification_pending);
-  sd->connectivity_notification_pending = false;
-}
-
-grpc_lb_subchannel_list* grpc_lb_subchannel_list_create(
-    grpc_core::LoadBalancingPolicy* p, grpc_core::TraceFlag* tracer,
-    const grpc_lb_addresses* addresses, grpc_combiner* combiner,
-    grpc_client_channel_factory* client_channel_factory,
-    const grpc_channel_args& args, grpc_iomgr_cb_func connectivity_changed_cb) {
-  grpc_lb_subchannel_list* subchannel_list =
-      static_cast<grpc_lb_subchannel_list*>(
-          gpr_zalloc(sizeof(*subchannel_list)));
-  if (tracer->enabled()) {
-    gpr_log(GPR_INFO,
-            "[%s %p] Creating subchannel list %p for %" PRIuPTR " subchannels",
-            tracer->name(), p, subchannel_list, addresses->num_addresses);
-  }
-  subchannel_list->policy = p;
-  subchannel_list->tracer = tracer;
-  gpr_ref_init(&subchannel_list->refcount, 1);
-  subchannel_list->subchannels = static_cast<grpc_lb_subchannel_data*>(
-      gpr_zalloc(sizeof(grpc_lb_subchannel_data) * addresses->num_addresses));
-  // We need to remove the LB addresses in order to be able to compare the
-  // subchannel keys of subchannels from a different batch of addresses.
-  static const char* keys_to_remove[] = {GRPC_ARG_SUBCHANNEL_ADDRESS,
-                                         GRPC_ARG_LB_ADDRESSES};
-  // Create a subchannel for each address.
-  grpc_subchannel_args sc_args;
-  size_t subchannel_index = 0;
-  for (size_t i = 0; i < addresses->num_addresses; i++) {
-    // If there were any balancer, we would have chosen grpclb policy instead.
-    GPR_ASSERT(!addresses->addresses[i].is_balancer);
-    memset(&sc_args, 0, sizeof(grpc_subchannel_args));
-    grpc_arg addr_arg =
-        grpc_create_subchannel_address_arg(&addresses->addresses[i].address);
-    grpc_channel_args* new_args = grpc_channel_args_copy_and_add_and_remove(
-        &args, keys_to_remove, GPR_ARRAY_SIZE(keys_to_remove), &addr_arg, 1);
-    gpr_free(addr_arg.value.string);
-    sc_args.args = new_args;
-    grpc_subchannel* subchannel = grpc_client_channel_factory_create_subchannel(
-        client_channel_factory, &sc_args);
-    grpc_channel_args_destroy(new_args);
-    if (subchannel == nullptr) {
-      // Subchannel could not be created.
-      if (tracer->enabled()) {
-        char* address_uri =
-            grpc_sockaddr_to_uri(&addresses->addresses[i].address);
-        gpr_log(GPR_INFO,
-                "[%s %p] could not create subchannel for address uri %s, "
-                "ignoring",
-                tracer->name(), subchannel_list->policy, address_uri);
-        gpr_free(address_uri);
-      }
-      continue;
-    }
-    if (tracer->enabled()) {
-      char* address_uri =
-          grpc_sockaddr_to_uri(&addresses->addresses[i].address);
-      gpr_log(GPR_INFO,
-              "[%s %p] subchannel list %p index %" PRIuPTR
-              ": Created subchannel %p for address uri %s",
-              tracer->name(), p, subchannel_list, subchannel_index, subchannel,
-              address_uri);
-      gpr_free(address_uri);
-    }
-    grpc_lb_subchannel_data* sd =
-        &subchannel_list->subchannels[subchannel_index++];
-    sd->subchannel_list = subchannel_list;
-    sd->subchannel = subchannel;
-    GRPC_CLOSURE_INIT(&sd->connectivity_changed_closure,
-                      connectivity_changed_cb, sd,
-                      grpc_combiner_scheduler(combiner));
-    // We assume that the current state is IDLE.  If not, we'll get a
-    // callback telling us that.
-    sd->prev_connectivity_state = GRPC_CHANNEL_IDLE;
-    sd->curr_connectivity_state = GRPC_CHANNEL_IDLE;
-    sd->pending_connectivity_state_unsafe = GRPC_CHANNEL_IDLE;
-    sd->user_data_vtable = addresses->user_data_vtable;
-    if (sd->user_data_vtable != nullptr) {
-      sd->user_data =
-          sd->user_data_vtable->copy(addresses->addresses[i].user_data);
-    }
-  }
-  subchannel_list->num_subchannels = subchannel_index;
-  subchannel_list->num_idle = subchannel_index;
-  return subchannel_list;
-}
-
-static void subchannel_list_destroy(grpc_lb_subchannel_list* subchannel_list) {
-  if (subchannel_list->tracer->enabled()) {
-    gpr_log(GPR_INFO, "[%s %p] Destroying subchannel_list %p",
-            subchannel_list->tracer->name(), subchannel_list->policy,
-            subchannel_list);
-  }
-  for (size_t i = 0; i < subchannel_list->num_subchannels; i++) {
-    grpc_lb_subchannel_data* sd = &subchannel_list->subchannels[i];
-    grpc_lb_subchannel_data_unref_subchannel(sd, "subchannel_list_destroy");
-  }
-  gpr_free(subchannel_list->subchannels);
-  gpr_free(subchannel_list);
-}
-
-void grpc_lb_subchannel_list_ref(grpc_lb_subchannel_list* subchannel_list,
-                                 const char* reason) {
-  gpr_ref_non_zero(&subchannel_list->refcount);
-  if (subchannel_list->tracer->enabled()) {
-    const gpr_atm count = gpr_atm_acq_load(&subchannel_list->refcount.count);
-    gpr_log(GPR_INFO, "[%s %p] subchannel_list %p REF %lu->%lu (%s)",
-            subchannel_list->tracer->name(), subchannel_list->policy,
-            subchannel_list, static_cast<unsigned long>(count - 1),
-            static_cast<unsigned long>(count), reason);
-  }
-}
-
-void grpc_lb_subchannel_list_unref(grpc_lb_subchannel_list* subchannel_list,
-                                   const char* reason) {
-  const bool done = gpr_unref(&subchannel_list->refcount);
-  if (subchannel_list->tracer->enabled()) {
-    const gpr_atm count = gpr_atm_acq_load(&subchannel_list->refcount.count);
-    gpr_log(GPR_INFO, "[%s %p] subchannel_list %p UNREF %lu->%lu (%s)",
-            subchannel_list->tracer->name(), subchannel_list->policy,
-            subchannel_list, static_cast<unsigned long>(count + 1),
-            static_cast<unsigned long>(count), reason);
-  }
-  if (done) {
-    subchannel_list_destroy(subchannel_list);
-  }
-}
-
-static void subchannel_data_cancel_connectivity_watch(
-    grpc_lb_subchannel_data* sd, const char* reason) {
-  if (sd->subchannel_list->tracer->enabled()) {
-    gpr_log(GPR_INFO,
-            "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
-            " (subchannel %p): canceling connectivity watch (%s)",
-            sd->subchannel_list->tracer->name(), sd->subchannel_list->policy,
-            sd->subchannel_list,
-            static_cast<size_t>(sd - sd->subchannel_list->subchannels),
-            sd->subchannel_list->num_subchannels, sd->subchannel, reason);
-  }
-  grpc_subchannel_notify_on_state_change(sd->subchannel, nullptr, nullptr,
-                                         &sd->connectivity_changed_closure);
-}
-
-void grpc_lb_subchannel_list_shutdown_and_unref(
-    grpc_lb_subchannel_list* subchannel_list, const char* reason) {
-  if (subchannel_list->tracer->enabled()) {
-    gpr_log(GPR_INFO, "[%s %p] Shutting down subchannel_list %p (%s)",
-            subchannel_list->tracer->name(), subchannel_list->policy,
-            subchannel_list, reason);
-  }
-  GPR_ASSERT(!subchannel_list->shutting_down);
-  subchannel_list->shutting_down = true;
-  for (size_t i = 0; i < subchannel_list->num_subchannels; i++) {
-    grpc_lb_subchannel_data* sd = &subchannel_list->subchannels[i];
-    // If there's a pending notification for this subchannel, cancel it;
-    // the callback is responsible for unreffing the subchannel.
-    // Otherwise, unref the subchannel directly.
-    if (sd->connectivity_notification_pending) {
-      subchannel_data_cancel_connectivity_watch(sd, reason);
-    } else if (sd->subchannel != nullptr) {
-      grpc_lb_subchannel_data_unref_subchannel(sd, reason);
-    }
-  }
-  grpc_lb_subchannel_list_unref(subchannel_list, reason);
-}
diff --git a/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h b/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h
index 6889d59..7e2046b 100644
--- a/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h
+++ b/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h
@@ -21,116 +21,516 @@
 
 #include <grpc/support/port_platform.h>
 
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+
 #include "src/core/ext/filters/client_channel/lb_policy_registry.h"
 #include "src/core/ext/filters/client_channel/subchannel.h"
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/debug/trace.h"
+#include "src/core/lib/gprpp/abstract.h"
+#include "src/core/lib/gprpp/inlined_vector.h"
+#include "src/core/lib/gprpp/orphanable.h"
+#include "src/core/lib/gprpp/ref_counted.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/iomgr/closure.h"
+#include "src/core/lib/iomgr/combiner.h"
+#include "src/core/lib/iomgr/sockaddr_utils.h"
 #include "src/core/lib/transport/connectivity_state.h"
 
-// TODO(roth): This code is intended to be shared between pick_first and
-// round_robin.  However, the interface needs more work to provide clean
-// encapsulation.  For example, the structs here have some fields that are
-// only used in one of the two (e.g., the state counters in
-// grpc_lb_subchannel_list and the prev_connectivity_state field in
-// grpc_lb_subchannel_data are only used in round_robin, and the
-// checking_subchannel field in grpc_lb_subchannel_list is only used by
-// pick_first).  Also, there is probably some code duplication between the
-// connectivity state notification callback code in both pick_first and
-// round_robin that could be refactored and moved here.  In a future PR,
-// need to clean this up.
+// Code for maintaining a list of subchannels within an LB policy.
+//
+// To use this, callers must create their own subclasses, like so:
+/*
 
-typedef struct grpc_lb_subchannel_list grpc_lb_subchannel_list;
+class MySubchannelList;  // Forward declaration.
 
-typedef struct {
-  /** backpointer to owning subchannel list */
-  grpc_lb_subchannel_list* subchannel_list;
-  /** subchannel itself */
-  grpc_subchannel* subchannel;
-  grpc_core::RefCountedPtr<grpc_core::ConnectedSubchannel> connected_subchannel;
-  /** Is a connectivity notification pending? */
-  bool connectivity_notification_pending;
-  /** notification that connectivity has changed on subchannel */
-  grpc_closure connectivity_changed_closure;
-  /** previous and current connectivity states.  Updated by \a
-   * \a connectivity_changed_closure based on
-   * \a pending_connectivity_state_unsafe. */
-  grpc_connectivity_state prev_connectivity_state;
-  grpc_connectivity_state curr_connectivity_state;
-  /** connectivity state to be updated by
-   * grpc_subchannel_notify_on_state_change(), not guarded by
-   * the combiner.  To be copied to \a curr_connectivity_state by
-   * \a connectivity_changed_closure. */
-  grpc_connectivity_state pending_connectivity_state_unsafe;
-  /** the subchannel's target user data */
-  void* user_data;
-  /** vtable to operate over \a user_data */
-  const grpc_lb_user_data_vtable* user_data_vtable;
-} grpc_lb_subchannel_data;
-
-/// Unrefs the subchannel contained in sd.
-void grpc_lb_subchannel_data_unref_subchannel(grpc_lb_subchannel_data* sd,
-                                              const char* reason);
-
-/// Starts watching the connectivity state of the subchannel.
-/// The connectivity_changed_cb callback must invoke either
-/// grpc_lb_subchannel_data_stop_connectivity_watch() or again call
-/// grpc_lb_subchannel_data_start_connectivity_watch().
-void grpc_lb_subchannel_data_start_connectivity_watch(
-    grpc_lb_subchannel_data* sd);
-
-/// Stops watching the connectivity state of the subchannel.
-void grpc_lb_subchannel_data_stop_connectivity_watch(
-    grpc_lb_subchannel_data* sd);
-
-struct grpc_lb_subchannel_list {
-  /** backpointer to owning policy */
-  grpc_core::LoadBalancingPolicy* policy;
-
-  grpc_core::TraceFlag* tracer;
-
-  /** all our subchannels */
-  size_t num_subchannels;
-  grpc_lb_subchannel_data* subchannels;
-
-  /** Index into subchannels of the one we're currently checking.
-   * Used when connecting to subchannels serially instead of in parallel. */
-  // TODO(roth): When we have time, we can probably make this go away
-  // and compute the index dynamically by subtracting
-  // subchannel_list->subchannels from the subchannel_data pointer.
-  size_t checking_subchannel;
-
-  /** how many subchannels are in state READY */
-  size_t num_ready;
-  /** how many subchannels are in state TRANSIENT_FAILURE */
-  size_t num_transient_failures;
-  /** how many subchannels are in state IDLE */
-  size_t num_idle;
-
-  /** There will be one ref for each entry in subchannels for which there is a
-   * pending connectivity state watcher callback. */
-  gpr_refcount refcount;
-
-  /** Is this list shutting down? This may be true due to the shutdown of the
-   * policy itself or because a newer update has arrived while this one hadn't
-   * finished processing. */
-  bool shutting_down;
+class MySubchannelData
+    : public SubchannelData<MySubchannelList, MySubchannelData> {
+ public:
+  void ProcessConnectivityChangeLocked(
+      grpc_connectivity_state connectivity_state, grpc_error* error) override {
+    // ...code to handle connectivity changes...
+  }
 };
 
-grpc_lb_subchannel_list* grpc_lb_subchannel_list_create(
-    grpc_core::LoadBalancingPolicy* p, grpc_core::TraceFlag* tracer,
+class MySubchannelList
+    : public SubchannelList<MySubchannelList, MySubchannelData> {
+};
+
+*/
+// All methods with a Locked() suffix must be called from within the
+// client_channel combiner.
+
+namespace grpc_core {
+
+// Stores data for a particular subchannel in a subchannel list.
+// Callers must create a subclass that implements the
+// ProcessConnectivityChangeLocked() method.
+template <typename SubchannelListType, typename SubchannelDataType>
+class SubchannelData {
+ public:
+  // Returns a pointer to the subchannel list containing this object.
+  SubchannelListType* subchannel_list() const { return subchannel_list_; }
+
+  // Returns the index into the subchannel list of this object.
+  size_t Index() const {
+    return static_cast<size_t>(static_cast<const SubchannelDataType*>(this) -
+                               subchannel_list_->subchannel(0));
+  }
+
+  // Returns a pointer to the subchannel.
+  grpc_subchannel* subchannel() const { return subchannel_; }
+
+  // Returns the connected subchannel.  Will be null if the subchannel
+  // is not connected.
+  ConnectedSubchannel* connected_subchannel() const {
+    return connected_subchannel_.get();
+  }
+
+  // Synchronously checks the subchannel's connectivity state.
+  // Must not be called while there is a connectivity notification
+  // pending (i.e., between calling StartConnectivityWatchLocked() or
+  // RenewConnectivityWatchLocked() and the resulting invocation of
+  // ProcessConnectivityChangeLocked()).
+  grpc_connectivity_state CheckConnectivityStateLocked(grpc_error** error) {
+    GPR_ASSERT(!connectivity_notification_pending_);
+    pending_connectivity_state_unsafe_ =
+        grpc_subchannel_check_connectivity(subchannel(), error);
+    UpdateConnectedSubchannelLocked();
+    return pending_connectivity_state_unsafe_;
+  }
+
+  // Unrefs the subchannel.  May be used if an individual subchannel is
+  // no longer needed even though the subchannel list as a whole is not
+  // being unreffed.
+  virtual void UnrefSubchannelLocked(const char* reason);
+
+  // Starts watching the connectivity state of the subchannel.
+  // ProcessConnectivityChangeLocked() will be called when the
+  // connectivity state changes.
+  void StartConnectivityWatchLocked();
+
+  // Renews watching the connectivity state of the subchannel.
+  void RenewConnectivityWatchLocked();
+
+  // Stops watching the connectivity state of the subchannel.
+  void StopConnectivityWatchLocked();
+
+  // Cancels watching the connectivity state of the subchannel.
+  // Must be called only while there is a connectivity notification
+  // pending (i.e., between calling StartConnectivityWatchLocked() or
+  // RenewConnectivityWatchLocked() and the resulting invocation of
+  // ProcessConnectivityChangeLocked()).
+  // From within ProcessConnectivityChangeLocked(), use
+  // StopConnectivityWatchLocked() instead.
+  void CancelConnectivityWatchLocked(const char* reason);
+
+  // Cancels any pending connectivity watch and unrefs the subchannel.
+  void ShutdownLocked();
+
+  GRPC_ABSTRACT_BASE_CLASS
+
+ protected:
+  SubchannelData(SubchannelListType* subchannel_list,
+                 const grpc_lb_user_data_vtable* user_data_vtable,
+                 const grpc_lb_address& address, grpc_subchannel* subchannel,
+                 grpc_combiner* combiner);
+
+  virtual ~SubchannelData();
+
+  // After StartConnectivityWatchLocked() or RenewConnectivityWatchLocked()
+  // is called, this method will be invoked when the subchannel's connectivity
+  // state changes.
+  // Implementations must invoke either RenewConnectivityWatchLocked() or
+  // StopConnectivityWatchLocked() before returning.
+  virtual void ProcessConnectivityChangeLocked(
+      grpc_connectivity_state connectivity_state,
+      grpc_error* error) GRPC_ABSTRACT;
+
+ private:
+  // Updates connected_subchannel_ based on pending_connectivity_state_unsafe_.
+  // Returns true if the connectivity state should be reported.
+  bool UpdateConnectedSubchannelLocked();
+
+  static void OnConnectivityChangedLocked(void* arg, grpc_error* error);
+
+  // Backpointer to owning subchannel list.  Not owned.
+  SubchannelListType* subchannel_list_;
+
+  // The subchannel and connected subchannel.
+  grpc_subchannel* subchannel_;
+  RefCountedPtr<ConnectedSubchannel> connected_subchannel_;
+
+  // Notification that connectivity has changed on subchannel.
+  grpc_closure connectivity_changed_closure_;
+  // Is a connectivity notification pending?
+  bool connectivity_notification_pending_ = false;
+  // Connectivity state to be updated by
+  // grpc_subchannel_notify_on_state_change(), not guarded by
+  // the combiner.
+  grpc_connectivity_state pending_connectivity_state_unsafe_;
+};
+
+// A list of subchannels.
+template <typename SubchannelListType, typename SubchannelDataType>
+class SubchannelList
+    : public InternallyRefCountedWithTracing<SubchannelListType> {
+ public:
+  typedef InlinedVector<SubchannelDataType, 10> SubchannelVector;
+
+  // The number of subchannels in the list.
+  size_t num_subchannels() const { return subchannels_.size(); }
+
+  // The data for the subchannel at a particular index.
+  SubchannelDataType* subchannel(size_t index) { return &subchannels_[index]; }
+
+  // Returns true if the subchannel list is shutting down.
+  bool shutting_down() const { return shutting_down_; }
+
+  // Accessors.
+  LoadBalancingPolicy* policy() const { return policy_; }
+  TraceFlag* tracer() const { return tracer_; }
+
+  // Note: Caller must ensure that this is invoked inside of the combiner.
+  void Orphan() override {
+    ShutdownLocked();
+    InternallyRefCountedWithTracing<SubchannelListType>::Unref(DEBUG_LOCATION,
+                                                               "shutdown");
+  }
+
+  GRPC_ABSTRACT_BASE_CLASS
+
+ protected:
+  SubchannelList(LoadBalancingPolicy* policy, TraceFlag* tracer,
+                 const grpc_lb_addresses* addresses, grpc_combiner* combiner,
+                 grpc_client_channel_factory* client_channel_factory,
+                 const grpc_channel_args& args);
+
+  virtual ~SubchannelList();
+
+ private:
+  // So New() can call our private ctor.
+  template <typename T, typename... Args>
+  friend T* New(Args&&... args);
+
+  // For accessing Ref() and Unref().
+  friend class SubchannelData<SubchannelListType, SubchannelDataType>;
+
+  void ShutdownLocked();
+
+  // Backpointer to owning policy.
+  LoadBalancingPolicy* policy_;
+
+  TraceFlag* tracer_;
+
+  grpc_combiner* combiner_;
+
+  // The list of subchannels.
+  SubchannelVector subchannels_;
+
+  // Is this list shutting down? This may be true due to the shutdown of the
+  // policy itself or because a newer update has arrived while this one hadn't
+  // finished processing.
+  bool shutting_down_ = false;
+};
+
+//
+// implementation -- no user-servicable parts below
+//
+
+//
+// SubchannelData
+//
+
+template <typename SubchannelListType, typename SubchannelDataType>
+SubchannelData<SubchannelListType, SubchannelDataType>::SubchannelData(
+    SubchannelListType* subchannel_list,
+    const grpc_lb_user_data_vtable* user_data_vtable,
+    const grpc_lb_address& address, grpc_subchannel* subchannel,
+    grpc_combiner* combiner)
+    : subchannel_list_(subchannel_list),
+      subchannel_(subchannel),
+      // We assume that the current state is IDLE.  If not, we'll get a
+      // callback telling us that.
+      pending_connectivity_state_unsafe_(GRPC_CHANNEL_IDLE) {
+  GRPC_CLOSURE_INIT(
+      &connectivity_changed_closure_,
+      (&SubchannelData<SubchannelListType,
+                       SubchannelDataType>::OnConnectivityChangedLocked),
+      this, grpc_combiner_scheduler(combiner));
+}
+
+template <typename SubchannelListType, typename SubchannelDataType>
+SubchannelData<SubchannelListType, SubchannelDataType>::~SubchannelData() {
+  UnrefSubchannelLocked("subchannel_data_destroy");
+}
+
+template <typename SubchannelListType, typename SubchannelDataType>
+void SubchannelData<SubchannelListType, SubchannelDataType>::
+    UnrefSubchannelLocked(const char* reason) {
+  if (subchannel_ != nullptr) {
+    if (subchannel_list_->tracer()->enabled()) {
+      gpr_log(GPR_INFO,
+              "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
+              " (subchannel %p): unreffing subchannel",
+              subchannel_list_->tracer()->name(), subchannel_list_->policy(),
+              subchannel_list_, Index(), subchannel_list_->num_subchannels(),
+              subchannel_);
+    }
+    GRPC_SUBCHANNEL_UNREF(subchannel_, reason);
+    subchannel_ = nullptr;
+    connected_subchannel_.reset();
+  }
+}
+
+template <typename SubchannelListType, typename SubchannelDataType>
+void SubchannelData<SubchannelListType,
+                    SubchannelDataType>::StartConnectivityWatchLocked() {
+  if (subchannel_list_->tracer()->enabled()) {
+    gpr_log(GPR_INFO,
+            "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
+            " (subchannel %p): starting watch: requesting connectivity change "
+            "notification (from %s)",
+            subchannel_list_->tracer()->name(), subchannel_list_->policy(),
+            subchannel_list_, Index(), subchannel_list_->num_subchannels(),
+            subchannel_,
+            grpc_connectivity_state_name(pending_connectivity_state_unsafe_));
+  }
+  GPR_ASSERT(!connectivity_notification_pending_);
+  connectivity_notification_pending_ = true;
+  subchannel_list()->Ref(DEBUG_LOCATION, "connectivity_watch").release();
+  grpc_subchannel_notify_on_state_change(
+      subchannel_, subchannel_list_->policy()->interested_parties(),
+      &pending_connectivity_state_unsafe_, &connectivity_changed_closure_);
+}
+
+template <typename SubchannelListType, typename SubchannelDataType>
+void SubchannelData<SubchannelListType,
+                    SubchannelDataType>::RenewConnectivityWatchLocked() {
+  if (subchannel_list_->tracer()->enabled()) {
+    gpr_log(GPR_INFO,
+            "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
+            " (subchannel %p): renewing watch: requesting connectivity change "
+            "notification (from %s)",
+            subchannel_list_->tracer()->name(), subchannel_list_->policy(),
+            subchannel_list_, Index(), subchannel_list_->num_subchannels(),
+            subchannel_,
+            grpc_connectivity_state_name(pending_connectivity_state_unsafe_));
+  }
+  GPR_ASSERT(connectivity_notification_pending_);
+  grpc_subchannel_notify_on_state_change(
+      subchannel_, subchannel_list_->policy()->interested_parties(),
+      &pending_connectivity_state_unsafe_, &connectivity_changed_closure_);
+}
+
+template <typename SubchannelListType, typename SubchannelDataType>
+void SubchannelData<SubchannelListType,
+                    SubchannelDataType>::StopConnectivityWatchLocked() {
+  if (subchannel_list_->tracer()->enabled()) {
+    gpr_log(GPR_INFO,
+            "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
+            " (subchannel %p): stopping connectivity watch",
+            subchannel_list_->tracer()->name(), subchannel_list_->policy(),
+            subchannel_list_, Index(), subchannel_list_->num_subchannels(),
+            subchannel_);
+  }
+  GPR_ASSERT(connectivity_notification_pending_);
+  connectivity_notification_pending_ = false;
+  subchannel_list()->Unref(DEBUG_LOCATION, "connectivity_watch");
+}
+
+template <typename SubchannelListType, typename SubchannelDataType>
+void SubchannelData<SubchannelListType, SubchannelDataType>::
+    CancelConnectivityWatchLocked(const char* reason) {
+  if (subchannel_list_->tracer()->enabled()) {
+    gpr_log(GPR_INFO,
+            "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
+            " (subchannel %p): canceling connectivity watch (%s)",
+            subchannel_list_->tracer()->name(), subchannel_list_->policy(),
+            subchannel_list_, Index(), subchannel_list_->num_subchannels(),
+            subchannel_, reason);
+  }
+  GPR_ASSERT(connectivity_notification_pending_);
+  grpc_subchannel_notify_on_state_change(subchannel_, nullptr, nullptr,
+                                         &connectivity_changed_closure_);
+}
+
+template <typename SubchannelListType, typename SubchannelDataType>
+bool SubchannelData<SubchannelListType,
+                    SubchannelDataType>::UpdateConnectedSubchannelLocked() {
+  // If the subchannel is READY, take a ref to the connected subchannel.
+  if (pending_connectivity_state_unsafe_ == GRPC_CHANNEL_READY) {
+    connected_subchannel_ =
+        grpc_subchannel_get_connected_subchannel(subchannel_);
+    // If the subchannel became disconnected between the time that READY
+    // was reported and the time we got here (e.g., between when a
+    // notification callback is scheduled and when it was actually run in
+    // the combiner), then the connected subchannel may have disappeared out
+    // from under us.  In that case, we don't actually want to consider the
+    // subchannel to be in state READY.  Instead, we use IDLE as the
+    // basis for any future connectivity watch; this is the one state that
+    // the subchannel will never transition back into, so this ensures
+    // that we will get a notification for the next state, even if that state
+    // is READY again (e.g., if the subchannel has transitioned back to
+    // READY before the next watch gets requested).
+    if (connected_subchannel_ == nullptr) {
+      if (subchannel_list_->tracer()->enabled()) {
+        gpr_log(GPR_INFO,
+                "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
+                " (subchannel %p): state is READY but connected subchannel is "
+                "null; moving to state IDLE",
+                subchannel_list_->tracer()->name(), subchannel_list_->policy(),
+                subchannel_list_, Index(), subchannel_list_->num_subchannels(),
+                subchannel_);
+      }
+      pending_connectivity_state_unsafe_ = GRPC_CHANNEL_IDLE;
+      return false;
+    }
+  } else {
+    // For any state other than READY, unref the connected subchannel.
+    connected_subchannel_.reset();
+  }
+  return true;
+}
+
+template <typename SubchannelListType, typename SubchannelDataType>
+void SubchannelData<SubchannelListType, SubchannelDataType>::
+    OnConnectivityChangedLocked(void* arg, grpc_error* error) {
+  SubchannelData* sd = static_cast<SubchannelData*>(arg);
+  if (sd->subchannel_list_->tracer()->enabled()) {
+    gpr_log(
+        GPR_INFO,
+        "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
+        " (subchannel %p): connectivity changed: state=%s, error=%s, "
+        "shutting_down=%d",
+        sd->subchannel_list_->tracer()->name(), sd->subchannel_list_->policy(),
+        sd->subchannel_list_, sd->Index(),
+        sd->subchannel_list_->num_subchannels(), sd->subchannel_,
+        grpc_connectivity_state_name(sd->pending_connectivity_state_unsafe_),
+        grpc_error_string(error), sd->subchannel_list_->shutting_down());
+  }
+  // If shutting down, unref subchannel and stop watching.
+  if (sd->subchannel_list_->shutting_down() || error == GRPC_ERROR_CANCELLED) {
+    sd->UnrefSubchannelLocked("connectivity_shutdown");
+    sd->StopConnectivityWatchLocked();
+    return;
+  }
+  // Get or release ref to connected subchannel.
+  if (!sd->UpdateConnectedSubchannelLocked()) {
+    // We don't want to report this connectivity state, so renew the watch.
+    sd->RenewConnectivityWatchLocked();
+    return;
+  }
+  // Call the subclass's ProcessConnectivityChangeLocked() method.
+  sd->ProcessConnectivityChangeLocked(sd->pending_connectivity_state_unsafe_,
+                                      GRPC_ERROR_REF(error));
+}
+
+template <typename SubchannelListType, typename SubchannelDataType>
+void SubchannelData<SubchannelListType, SubchannelDataType>::ShutdownLocked() {
+  // If there's a pending notification for this subchannel, cancel it;
+  // the callback is responsible for unreffing the subchannel.
+  // Otherwise, unref the subchannel directly.
+  if (connectivity_notification_pending_) {
+    CancelConnectivityWatchLocked("shutdown");
+  } else if (subchannel_ != nullptr) {
+    UnrefSubchannelLocked("shutdown");
+  }
+}
+
+//
+// SubchannelList
+//
+
+template <typename SubchannelListType, typename SubchannelDataType>
+SubchannelList<SubchannelListType, SubchannelDataType>::SubchannelList(
+    LoadBalancingPolicy* policy, TraceFlag* tracer,
     const grpc_lb_addresses* addresses, grpc_combiner* combiner,
     grpc_client_channel_factory* client_channel_factory,
-    const grpc_channel_args& args, grpc_iomgr_cb_func connectivity_changed_cb);
+    const grpc_channel_args& args)
+    : InternallyRefCountedWithTracing<SubchannelListType>(tracer),
+      policy_(policy),
+      tracer_(tracer),
+      combiner_(GRPC_COMBINER_REF(combiner, "subchannel_list")) {
+  if (tracer_->enabled()) {
+    gpr_log(GPR_INFO,
+            "[%s %p] Creating subchannel list %p for %" PRIuPTR " subchannels",
+            tracer_->name(), policy, this, addresses->num_addresses);
+  }
+  subchannels_.reserve(addresses->num_addresses);
+  // We need to remove the LB addresses in order to be able to compare the
+  // subchannel keys of subchannels from a different batch of addresses.
+  static const char* keys_to_remove[] = {GRPC_ARG_SUBCHANNEL_ADDRESS,
+                                         GRPC_ARG_LB_ADDRESSES};
+  // Create a subchannel for each address.
+  grpc_subchannel_args sc_args;
+  for (size_t i = 0; i < addresses->num_addresses; i++) {
+    // If there were any balancer, we would have chosen grpclb policy instead.
+    GPR_ASSERT(!addresses->addresses[i].is_balancer);
+    memset(&sc_args, 0, sizeof(grpc_subchannel_args));
+    grpc_arg addr_arg =
+        grpc_create_subchannel_address_arg(&addresses->addresses[i].address);
+    grpc_channel_args* new_args = grpc_channel_args_copy_and_add_and_remove(
+        &args, keys_to_remove, GPR_ARRAY_SIZE(keys_to_remove), &addr_arg, 1);
+    gpr_free(addr_arg.value.string);
+    sc_args.args = new_args;
+    grpc_subchannel* subchannel = grpc_client_channel_factory_create_subchannel(
+        client_channel_factory, &sc_args);
+    grpc_channel_args_destroy(new_args);
+    if (subchannel == nullptr) {
+      // Subchannel could not be created.
+      if (tracer_->enabled()) {
+        char* address_uri =
+            grpc_sockaddr_to_uri(&addresses->addresses[i].address);
+        gpr_log(GPR_INFO,
+                "[%s %p] could not create subchannel for address uri %s, "
+                "ignoring",
+                tracer_->name(), policy_, address_uri);
+        gpr_free(address_uri);
+      }
+      continue;
+    }
+    if (tracer_->enabled()) {
+      char* address_uri =
+          grpc_sockaddr_to_uri(&addresses->addresses[i].address);
+      gpr_log(GPR_INFO,
+              "[%s %p] subchannel list %p index %" PRIuPTR
+              ": Created subchannel %p for address uri %s",
+              tracer_->name(), policy_, this, subchannels_.size(), subchannel,
+              address_uri);
+      gpr_free(address_uri);
+    }
+    subchannels_.emplace_back(static_cast<SubchannelListType*>(this),
+                              addresses->user_data_vtable,
+                              addresses->addresses[i], subchannel, combiner);
+  }
+}
 
-void grpc_lb_subchannel_list_ref(grpc_lb_subchannel_list* subchannel_list,
-                                 const char* reason);
+template <typename SubchannelListType, typename SubchannelDataType>
+SubchannelList<SubchannelListType, SubchannelDataType>::~SubchannelList() {
+  if (tracer_->enabled()) {
+    gpr_log(GPR_INFO, "[%s %p] Destroying subchannel_list %p", tracer_->name(),
+            policy_, this);
+  }
+  GRPC_COMBINER_UNREF(combiner_, "subchannel_list");
+}
 
-void grpc_lb_subchannel_list_unref(grpc_lb_subchannel_list* subchannel_list,
-                                   const char* reason);
+template <typename SubchannelListType, typename SubchannelDataType>
+void SubchannelList<SubchannelListType, SubchannelDataType>::ShutdownLocked() {
+  if (tracer_->enabled()) {
+    gpr_log(GPR_INFO, "[%s %p] Shutting down subchannel_list %p",
+            tracer_->name(), policy_, this);
+  }
+  GPR_ASSERT(!shutting_down_);
+  shutting_down_ = true;
+  for (size_t i = 0; i < subchannels_.size(); i++) {
+    SubchannelDataType* sd = &subchannels_[i];
+    sd->ShutdownLocked();
+  }
+}
 
-/// Mark subchannel_list as discarded. Unsubscribes all its subchannels. The
-/// connectivity state notification callback will ultimately unref it.
-void grpc_lb_subchannel_list_shutdown_and_unref(
-    grpc_lb_subchannel_list* subchannel_list, const char* reason);
+}  // namespace grpc_core
 
 #endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_LB_POLICY_SUBCHANNEL_LIST_H */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc b/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
index 6ac9a77..c3c62b6 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
@@ -363,6 +363,15 @@
 }
 
 void AresDnsResolver::MaybeStartResolvingLocked() {
+  // If there is an existing timer, the time it fires is the earliest time we
+  // can start the next resolution.
+  if (have_next_resolution_timer_) {
+    // TODO(dgq): remove the following two lines once Pick First stops
+    // discarding subchannels after selecting.
+    ++resolved_version_;
+    MaybeFinishNextLocked();
+    return;
+  }
   if (last_resolution_timestamp_ >= 0) {
     const grpc_millis earliest_next_resolution =
         last_resolution_timestamp_ + min_time_between_resolutions_;
@@ -375,17 +384,15 @@
               "In cooldown from last resolution (from %" PRIdPTR
               " ms ago). Will resolve again in %" PRIdPTR " ms",
               last_resolution_ago, ms_until_next_resolution);
-      if (!have_next_resolution_timer_) {
-        have_next_resolution_timer_ = true;
-        // TODO(roth): We currently deal with this ref manually.  Once the
-        // new closure API is done, find a way to track this ref with the timer
-        // callback as part of the type system.
-        RefCountedPtr<Resolver> self =
-            Ref(DEBUG_LOCATION, "next_resolution_timer_cooldown");
-        self.release();
-        grpc_timer_init(&next_resolution_timer_, ms_until_next_resolution,
-                        &on_next_resolution_);
-      }
+      have_next_resolution_timer_ = true;
+      // TODO(roth): We currently deal with this ref manually.  Once the
+      // new closure API is done, find a way to track this ref with the timer
+      // callback as part of the type system.
+      RefCountedPtr<Resolver> self =
+          Ref(DEBUG_LOCATION, "next_resolution_timer_cooldown");
+      self.release();
+      grpc_timer_init(&next_resolution_timer_, ms_until_next_resolution,
+                      &on_next_resolution_);
       // TODO(dgq): remove the following two lines once Pick First stops
       // discarding subchannels after selecting.
       ++resolved_version_;
@@ -397,6 +404,7 @@
 }
 
 void AresDnsResolver::StartResolvingLocked() {
+  gpr_log(GPR_DEBUG, "Start resolving.");
   // TODO(roth): We currently deal with this ref manually.  Once the
   // new closure API is done, find a way to track this ref with the timer
   // callback as part of the type system.
diff --git a/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc b/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc
index fbab136..e7842a7 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc
+++ b/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc
@@ -236,6 +236,15 @@
 }
 
 void NativeDnsResolver::MaybeStartResolvingLocked() {
+  // If there is an existing timer, the time it fires is the earliest time we
+  // can start the next resolution.
+  if (have_next_resolution_timer_) {
+    // TODO(dgq): remove the following two lines once Pick First stops
+    // discarding subchannels after selecting.
+    ++resolved_version_;
+    MaybeFinishNextLocked();
+    return;
+  }
   if (last_resolution_timestamp_ >= 0) {
     const grpc_millis earliest_next_resolution =
         last_resolution_timestamp_ + min_time_between_resolutions_;
@@ -248,17 +257,15 @@
               "In cooldown from last resolution (from %" PRIdPTR
               " ms ago). Will resolve again in %" PRIdPTR " ms",
               last_resolution_ago, ms_until_next_resolution);
-      if (!have_next_resolution_timer_) {
-        have_next_resolution_timer_ = true;
-        // TODO(roth): We currently deal with this ref manually.  Once the
-        // new closure API is done, find a way to track this ref with the timer
-        // callback as part of the type system.
-        RefCountedPtr<Resolver> self =
-            Ref(DEBUG_LOCATION, "next_resolution_timer_cooldown");
-        self.release();
-        grpc_timer_init(&next_resolution_timer_, ms_until_next_resolution,
-                        &on_next_resolution_);
-      }
+      have_next_resolution_timer_ = true;
+      // TODO(roth): We currently deal with this ref manually.  Once the
+      // new closure API is done, find a way to track this ref with the timer
+      // callback as part of the type system.
+      RefCountedPtr<Resolver> self =
+          Ref(DEBUG_LOCATION, "next_resolution_timer_cooldown");
+      self.release();
+      grpc_timer_init(&next_resolution_timer_, ms_until_next_resolution,
+                      &on_next_resolution_);
       // TODO(dgq): remove the following two lines once Pick First stops
       // discarding subchannels after selecting.
       ++resolved_version_;
@@ -270,6 +277,7 @@
 }
 
 void NativeDnsResolver::StartResolvingLocked() {
+  gpr_log(GPR_DEBUG, "Start resolving.");
   // TODO(roth): We currently deal with this ref manually.  Once the
   // new closure API is done, find a way to track this ref with the timer
   // callback as part of the type system.
diff --git a/src/core/lib/debug/trace.h b/src/core/lib/debug/trace.h
index bfec92c..28157c6 100644
--- a/src/core/lib/debug/trace.h
+++ b/src/core/lib/debug/trace.h
@@ -57,13 +57,22 @@
 
   const char* name() const { return name_; }
 
+// This following define may be commented out to ensure that the compiler
+// deletes any "if (tracer.enabled()) {...}" codeblocks. This is useful to
+// test the performance impact tracers have on the system.
+//
+// #define COMPILE_OUT_ALL_TRACERS_IN_OPT_BUILD
+#ifdef COMPILE_OUT_ALL_TRACERS_IN_OPT_BUILD
+  bool enabled() { return false; }
+#else
   bool enabled() {
 #ifdef GRPC_THREADSAFE_TRACER
     return gpr_atm_no_barrier_load(&value_) != 0;
 #else
     return value_;
-#endif
+#endif  // GRPC_THREADSAFE_TRACER
   }
+#endif  // COMPILE_OUT_ALL_TRACERS_IN_OPT_BUILD
 
  private:
   friend void grpc_core::testing::grpc_tracer_enable_flag(TraceFlag* flag);
diff --git a/src/core/lib/http/httpcli_security_connector.cc b/src/core/lib/http/httpcli_security_connector.cc
index 0b53d63..50078c3 100644
--- a/src/core/lib/http/httpcli_security_connector.cc
+++ b/src/core/lib/http/httpcli_security_connector.cc
@@ -32,7 +32,6 @@
 #include "src/core/lib/security/transport/security_handshaker.h"
 #include "src/core/lib/slice/slice_internal.h"
 #include "src/core/tsi/ssl_transport_security.h"
-#include "src/core/tsi/transport_security_adapter.h"
 
 typedef struct {
   grpc_channel_security_connector base;
@@ -65,8 +64,7 @@
     }
   }
   grpc_handshake_manager_add(
-      handshake_mgr, grpc_security_handshaker_create(
-                         tsi_create_adapter_handshaker(handshaker), &sc->base));
+      handshake_mgr, grpc_security_handshaker_create(handshaker, &sc->base));
 }
 
 static void httpcli_ssl_check_peer(grpc_security_connector* sc, tsi_peer peer,
diff --git a/src/core/lib/iomgr/closure.h b/src/core/lib/iomgr/closure.h
index 64527d6..34a4944 100644
--- a/src/core/lib/iomgr/closure.h
+++ b/src/core/lib/iomgr/closure.h
@@ -253,8 +253,8 @@
     c->file_initiated = file;
     c->line_initiated = line;
     c->run = true;
+    GPR_ASSERT(c->cb != nullptr);
 #endif
-    assert(c->cb);
     c->scheduler->vtable->run(c, error);
   } else {
     GRPC_ERROR_UNREF(error);
@@ -292,8 +292,8 @@
     c->file_initiated = file;
     c->line_initiated = line;
     c->run = false;
+    GPR_ASSERT(c->cb != nullptr);
 #endif
-    assert(c->cb);
     c->scheduler->vtable->sched(c, error);
   } else {
     GRPC_ERROR_UNREF(error);
@@ -330,8 +330,8 @@
     c->file_initiated = file;
     c->line_initiated = line;
     c->run = false;
+    GPR_ASSERT(c->cb != nullptr);
 #endif
-    assert(c->cb);
     c->scheduler->vtable->sched(c, c->error_data.error);
     c = next;
   }
diff --git a/src/core/lib/iomgr/fork_posix.cc b/src/core/lib/iomgr/fork_posix.cc
index f8645ab..6c506eb 100644
--- a/src/core/lib/iomgr/fork_posix.cc
+++ b/src/core/lib/iomgr/fork_posix.cc
@@ -74,7 +74,6 @@
     grpc_timer_manager_set_threading(true);
     grpc_core::ExecCtx exec_ctx;
     grpc_executor_set_threading(true);
-    grpc_core::ExecCtx::Get()->Flush();
   }
 }
 
diff --git a/src/core/lib/iomgr/tcp_posix.cc b/src/core/lib/iomgr/tcp_posix.cc
index fc2b94d..153be05 100644
--- a/src/core/lib/iomgr/tcp_posix.cc
+++ b/src/core/lib/iomgr/tcp_posix.cc
@@ -371,7 +371,7 @@
 
 #define MAX_READ_IOVEC 4
 static void tcp_do_read(grpc_tcp* tcp) {
-  GPR_TIMER_SCOPE("tcp_continue_read", 0);
+  GPR_TIMER_SCOPE("tcp_do_read", 0);
   struct msghdr msg;
   struct iovec iov[MAX_READ_IOVEC];
   ssize_t read_bytes;
diff --git a/src/core/lib/iomgr/tcp_server_posix.cc b/src/core/lib/iomgr/tcp_server_posix.cc
index 524beba..153ac63 100644
--- a/src/core/lib/iomgr/tcp_server_posix.cc
+++ b/src/core/lib/iomgr/tcp_server_posix.cc
@@ -346,7 +346,8 @@
     err = grpc_create_dualstack_socket(&listener->addr, SOCK_STREAM, 0, &dsmode,
                                        &fd);
     if (err != GRPC_ERROR_NONE) return err;
-    err = grpc_tcp_server_prepare_socket(fd, &listener->addr, true, &port);
+    err = grpc_tcp_server_prepare_socket(listener->server, fd, &listener->addr,
+                                         true, &port);
     if (err != GRPC_ERROR_NONE) return err;
     listener->server->nports++;
     grpc_sockaddr_to_string(&addr_str, &listener->addr, 1);
diff --git a/src/core/lib/iomgr/tcp_server_utils_posix.h b/src/core/lib/iomgr/tcp_server_utils_posix.h
index 34d6813..dd19909 100644
--- a/src/core/lib/iomgr/tcp_server_utils_posix.h
+++ b/src/core/lib/iomgr/tcp_server_utils_posix.h
@@ -113,7 +113,7 @@
                                                 int* out_port);
 
 /* Prepare a recently-created socket for listening. */
-grpc_error* grpc_tcp_server_prepare_socket(int fd,
+grpc_error* grpc_tcp_server_prepare_socket(grpc_tcp_server*, int fd,
                                            const grpc_resolved_address* addr,
                                            bool so_reuseport, int* port);
 /* Ruturn true if the platform supports ifaddrs */
diff --git a/src/core/lib/iomgr/tcp_server_utils_posix_common.cc b/src/core/lib/iomgr/tcp_server_utils_posix_common.cc
index 9f4e58c..2d95aa6 100644
--- a/src/core/lib/iomgr/tcp_server_utils_posix_common.cc
+++ b/src/core/lib/iomgr/tcp_server_utils_posix_common.cc
@@ -87,7 +87,7 @@
   char* name;
 
   grpc_error* err =
-      grpc_tcp_server_prepare_socket(fd, addr, s->so_reuseport, &port);
+      grpc_tcp_server_prepare_socket(s, fd, addr, s->so_reuseport, &port);
   if (err == GRPC_ERROR_NONE) {
     GPR_ASSERT(port > 0);
     grpc_sockaddr_to_string(&addr_str, addr, 1);
@@ -144,7 +144,7 @@
 }
 
 /* Prepare a recently-created socket for listening. */
-grpc_error* grpc_tcp_server_prepare_socket(int fd,
+grpc_error* grpc_tcp_server_prepare_socket(grpc_tcp_server* s, int fd,
                                            const grpc_resolved_address* addr,
                                            bool so_reuseport, int* port) {
   grpc_resolved_address sockname_temp;
@@ -170,6 +170,18 @@
   err = grpc_set_socket_no_sigpipe_if_possible(fd);
   if (err != GRPC_ERROR_NONE) goto error;
 
+  if (s->channel_args) {
+    for (size_t i = 0; i < s->channel_args->num_args; i++) {
+      if (0 == strcmp(s->channel_args->args[i].key, GRPC_ARG_SOCKET_MUTATOR)) {
+        GPR_ASSERT(s->channel_args->args[i].type == GRPC_ARG_POINTER);
+        grpc_socket_mutator* mutator = static_cast<grpc_socket_mutator*>(
+            s->channel_args->args[i].value.pointer.p);
+        err = grpc_set_socket_with_mutator(fd, mutator);
+        if (err != GRPC_ERROR_NONE) goto error;
+      }
+    }
+  }
+
   if (bind(fd, reinterpret_cast<grpc_sockaddr*>(const_cast<char*>(addr->addr)),
            addr->len) < 0) {
     err = GRPC_OS_ERROR(errno, "bind");
diff --git a/src/core/lib/iomgr/tcp_windows.cc b/src/core/lib/iomgr/tcp_windows.cc
index 04e6f11..5d316d4 100644
--- a/src/core/lib/iomgr/tcp_windows.cc
+++ b/src/core/lib/iomgr/tcp_windows.cc
@@ -74,12 +74,28 @@
              : GRPC_WSA_ERROR(WSAGetLastError(), "setsockopt(IPV6_V6ONLY)");
 }
 
+static grpc_error* enable_loopback_fast_path(SOCKET sock) {
+  int status;
+  uint32_t param = 1;
+  DWORD ret;
+  status = WSAIoctl(sock, /*SIO_LOOPBACK_FAST_PATH==*/_WSAIOW(IOC_VENDOR, 16),
+                    &param, sizeof(param), NULL, 0, &ret, 0, 0);
+  if (status == SOCKET_ERROR) {
+    status = WSAGetLastError();
+  }
+  return status == 0 || status == WSAEOPNOTSUPP
+             ? GRPC_ERROR_NONE
+             : GRPC_WSA_ERROR(status, "WSAIoctl(SIO_LOOPBACK_FAST_PATH)");
+}
+
 grpc_error* grpc_tcp_prepare_socket(SOCKET sock) {
   grpc_error* err;
   err = set_non_block(sock);
   if (err != GRPC_ERROR_NONE) return err;
   err = set_dualstack(sock);
   if (err != GRPC_ERROR_NONE) return err;
+  err = enable_loopback_fast_path(sock);
+  if (err != GRPC_ERROR_NONE) return err;
   return GRPC_ERROR_NONE;
 }
 
diff --git a/src/core/lib/security/credentials/credentials.h b/src/core/lib/security/credentials/credentials.h
index b1421e8..b486d25 100644
--- a/src/core/lib/security/credentials/credentials.h
+++ b/src/core/lib/security/credentials/credentials.h
@@ -45,6 +45,7 @@
 #define GRPC_CHANNEL_CREDENTIALS_TYPE_SSL "Ssl"
 #define GRPC_CHANNEL_CREDENTIALS_TYPE_FAKE_TRANSPORT_SECURITY \
   "FakeTransportSecurity"
+#define GRPC_CHANNEL_CREDENTIALS_TYPE_GOOGLE_DEFAULT "GoogleDefault"
 
 #define GRPC_CALL_CREDENTIALS_TYPE_OAUTH2 "Oauth2"
 #define GRPC_CALL_CREDENTIALS_TYPE_JWT "Jwt"
diff --git a/src/core/lib/security/credentials/google_default/google_default_credentials.cc b/src/core/lib/security/credentials/google_default/google_default_credentials.cc
index 70d4c3e..03e8cb3 100644
--- a/src/core/lib/security/credentials/google_default/google_default_credentials.cc
+++ b/src/core/lib/security/credentials/google_default/google_default_credentials.cc
@@ -26,12 +26,15 @@
 #include <grpc/support/log.h>
 #include <grpc/support/sync.h>
 
+#include "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h"
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/http/httpcli.h"
 #include "src/core/lib/http/parser.h"
 #include "src/core/lib/iomgr/load_file.h"
 #include "src/core/lib/iomgr/polling_entity.h"
+#include "src/core/lib/security/credentials/alts/alts_credentials.h"
 #include "src/core/lib/security/credentials/google_default/google_default_credentials.h"
 #include "src/core/lib/security/credentials/jwt/jwt_credentials.h"
 #include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h"
@@ -45,8 +48,8 @@
 
 /* -- Default credentials. -- */
 
-static grpc_channel_credentials* default_credentials = nullptr;
-static int compute_engine_detection_done = 0;
+static grpc_channel_credentials* g_default_credentials = nullptr;
+static int g_compute_engine_detection_done = 0;
 static gpr_mu g_state_mu;
 static gpr_mu* g_polling_mu;
 static gpr_once g_once = GPR_ONCE_INIT;
@@ -60,6 +63,54 @@
   grpc_http_response response;
 } compute_engine_detector;
 
+static void google_default_credentials_destruct(
+    grpc_channel_credentials* creds) {
+  grpc_google_default_channel_credentials* c =
+      reinterpret_cast<grpc_google_default_channel_credentials*>(creds);
+  grpc_channel_credentials_unref(c->alts_creds);
+  grpc_channel_credentials_unref(c->ssl_creds);
+}
+
+static grpc_security_status google_default_create_security_connector(
+    grpc_channel_credentials* creds, grpc_call_credentials* call_creds,
+    const char* target, const grpc_channel_args* args,
+    grpc_channel_security_connector** sc, grpc_channel_args** new_args) {
+  grpc_google_default_channel_credentials* c =
+      reinterpret_cast<grpc_google_default_channel_credentials*>(creds);
+  bool is_grpclb_load_balancer = grpc_channel_arg_get_bool(
+      grpc_channel_args_find(args, GRPC_ARG_ADDRESS_IS_GRPCLB_LOAD_BALANCER),
+      false);
+  bool is_backend_from_grpclb_load_balancer = grpc_channel_arg_get_bool(
+      grpc_channel_args_find(
+          args, GRPC_ARG_ADDRESS_IS_BACKEND_FROM_GRPCLB_LOAD_BALANCER),
+      false);
+  bool use_alts =
+      is_grpclb_load_balancer || is_backend_from_grpclb_load_balancer;
+  grpc_security_status status = GRPC_SECURITY_ERROR;
+  status = use_alts ? c->alts_creds->vtable->create_security_connector(
+                          c->alts_creds, call_creds, target, args, sc, new_args)
+                    : c->ssl_creds->vtable->create_security_connector(
+                          c->ssl_creds, call_creds, target, args, sc, new_args);
+  /* grpclb-specific channel args are removed from the channel args set
+   * to ensure backends and fallback adresses will have the same set of channel
+   * args. By doing that, it guarantees the connections to backends will not be
+   * torn down and re-connected when switching in and out of fallback mode.
+   */
+  if (use_alts) {
+    static const char* args_to_remove[] = {
+        GRPC_ARG_ADDRESS_IS_GRPCLB_LOAD_BALANCER,
+        GRPC_ARG_ADDRESS_IS_BACKEND_FROM_GRPCLB_LOAD_BALANCER,
+    };
+    *new_args = grpc_channel_args_copy_and_add_and_remove(
+        args, args_to_remove, GPR_ARRAY_SIZE(args_to_remove), nullptr, 0);
+  }
+  return status;
+}
+
+static grpc_channel_credentials_vtable google_default_credentials_vtable = {
+    google_default_credentials_destruct,
+    google_default_create_security_connector, nullptr};
+
 static void on_compute_engine_detection_http_response(void* user_data,
                                                       grpc_error* error) {
   compute_engine_detector* detector =
@@ -234,8 +285,8 @@
 
   gpr_mu_lock(&g_state_mu);
 
-  if (default_credentials != nullptr) {
-    result = grpc_channel_credentials_ref(default_credentials);
+  if (g_default_credentials != nullptr) {
+    result = grpc_channel_credentials_ref(g_default_credentials);
     goto end;
   }
 
@@ -253,9 +304,9 @@
 
   /* At last try to see if we're on compute engine (do the detection only once
      since it requires a network test). */
-  if (!compute_engine_detection_done) {
+  if (!g_compute_engine_detection_done) {
     int need_compute_engine_creds = is_stack_running_on_compute_engine();
-    compute_engine_detection_done = 1;
+    g_compute_engine_detection_done = 1;
     if (need_compute_engine_creds) {
       call_creds = grpc_google_compute_engine_credentials_create(nullptr);
       if (call_creds == nullptr) {
@@ -269,18 +320,25 @@
 end:
   if (result == nullptr) {
     if (call_creds != nullptr) {
-      /* Blend with default ssl credentials and add a global reference so that
-         it
-         can be cached and re-served. */
-      grpc_channel_credentials* ssl_creds =
-          grpc_ssl_credentials_create(nullptr, nullptr, nullptr);
-      default_credentials = grpc_channel_credentials_ref(
-          grpc_composite_channel_credentials_create(ssl_creds, call_creds,
-                                                    nullptr));
-      GPR_ASSERT(default_credentials != nullptr);
-      grpc_channel_credentials_unref(ssl_creds);
+      /* Create google default credentials. */
+      auto creds = static_cast<grpc_google_default_channel_credentials*>(
+          gpr_zalloc(sizeof(grpc_google_default_channel_credentials)));
+      creds->base.vtable = &google_default_credentials_vtable;
+      creds->base.type = GRPC_CHANNEL_CREDENTIALS_TYPE_GOOGLE_DEFAULT;
+      gpr_ref_init(&creds->base.refcount, 1);
+      creds->ssl_creds = grpc_ssl_credentials_create(nullptr, nullptr, nullptr);
+      GPR_ASSERT(creds->ssl_creds != nullptr);
+      grpc_alts_credentials_options* options =
+          grpc_alts_credentials_client_options_create();
+      creds->alts_creds = grpc_alts_credentials_create(options);
+      grpc_alts_credentials_options_destroy(options);
+      /* Add a global reference so that it can be cached and re-served. */
+      g_default_credentials = grpc_composite_channel_credentials_create(
+          &creds->base, call_creds, nullptr);
+      GPR_ASSERT(g_default_credentials != nullptr);
+      grpc_channel_credentials_unref(&creds->base);
       grpc_call_credentials_unref(call_creds);
-      result = default_credentials;
+      result = grpc_channel_credentials_ref(g_default_credentials);
     } else {
       gpr_log(GPR_ERROR, "Could not create google default credentials.");
     }
@@ -299,11 +357,11 @@
   grpc_core::ExecCtx exec_ctx;
   gpr_once_init(&g_once, init_default_credentials);
   gpr_mu_lock(&g_state_mu);
-  if (default_credentials != nullptr) {
-    grpc_channel_credentials_unref(default_credentials);
-    default_credentials = nullptr;
+  if (g_default_credentials != nullptr) {
+    grpc_channel_credentials_unref(g_default_credentials);
+    g_default_credentials = nullptr;
   }
-  compute_engine_detection_done = 0;
+  g_compute_engine_detection_done = 0;
   gpr_mu_unlock(&g_state_mu);
 }
 
diff --git a/src/core/lib/security/credentials/google_default/google_default_credentials.h b/src/core/lib/security/credentials/google_default/google_default_credentials.h
index b163e48..9b4063c 100644
--- a/src/core/lib/security/credentials/google_default/google_default_credentials.h
+++ b/src/core/lib/security/credentials/google_default/google_default_credentials.h
@@ -39,6 +39,12 @@
   "/" GRPC_GOOGLE_WELL_KNOWN_CREDENTIALS_FILE
 #endif
 
+typedef struct {
+  grpc_channel_credentials base;
+  grpc_channel_credentials* alts_creds;
+  grpc_channel_credentials* ssl_creds;
+} grpc_google_default_channel_credentials;
+
 void grpc_flush_cached_google_default_credentials(void);
 
 #endif /* GRPC_CORE_LIB_SECURITY_CREDENTIALS_GOOGLE_DEFAULT_GOOGLE_DEFAULT_CREDENTIALS_H \
diff --git a/src/core/lib/security/security_connector/security_connector.cc b/src/core/lib/security/security_connector/security_connector.cc
index 3551061..a306967 100644
--- a/src/core/lib/security/security_connector/security_connector.cc
+++ b/src/core/lib/security/security_connector/security_connector.cc
@@ -44,7 +44,6 @@
 #include "src/core/lib/security/transport/target_authority_table.h"
 #include "src/core/tsi/fake_transport_security.h"
 #include "src/core/tsi/ssl_transport_security.h"
-#include "src/core/tsi/transport_security_adapter.h"
 
 grpc_core::DebugOnlyTraceFlag grpc_trace_security_connector_refcount(
     false, "security_connector_refcount");
@@ -673,8 +672,7 @@
   }
   // Create handshakers.
   grpc_handshake_manager_add(
-      handshake_mgr, grpc_security_handshaker_create(
-                         tsi_create_adapter_handshaker(tsi_hs), &sc->base));
+      handshake_mgr, grpc_security_handshaker_create(tsi_hs, &sc->base));
 }
 
 static const char** fill_alpn_protocol_strings(size_t* num_alpn_protocols) {
@@ -782,27 +780,29 @@
   }
   // Create handshakers.
   grpc_handshake_manager_add(
-      handshake_mgr, grpc_security_handshaker_create(
-                         tsi_create_adapter_handshaker(tsi_hs), &sc->base));
+      handshake_mgr, grpc_security_handshaker_create(tsi_hs, &sc->base));
 }
 
-static int ssl_host_matches_name(const tsi_peer* peer, const char* peer_name) {
+int grpc_ssl_host_matches_name(const tsi_peer* peer, const char* peer_name) {
   char* allocated_name = nullptr;
   int r;
 
-  if (strchr(peer_name, ':') != nullptr) {
-    char* ignored_port;
-    gpr_split_host_port(peer_name, &allocated_name, &ignored_port);
-    gpr_free(ignored_port);
-    peer_name = allocated_name;
-    if (!peer_name) return 0;
-  }
+  char* ignored_port;
+  gpr_split_host_port(peer_name, &allocated_name, &ignored_port);
+  gpr_free(ignored_port);
+  peer_name = allocated_name;
+  if (!peer_name) return 0;
+
+  // IPv6 zone-id should not be included in comparisons.
+  char* const zone_id = strchr(allocated_name, '%');
+  if (zone_id != nullptr) *zone_id = '\0';
+
   r = tsi_ssl_peer_matches_name(peer, peer_name);
   gpr_free(allocated_name);
   return r;
 }
 
-grpc_auth_context* tsi_ssl_peer_to_auth_context(const tsi_peer* peer) {
+grpc_auth_context* grpc_ssl_peer_to_auth_context(const tsi_peer* peer) {
   size_t i;
   grpc_auth_context* ctx = nullptr;
   const char* peer_identity_property_name = nullptr;
@@ -859,14 +859,14 @@
   }
 
   /* Check the peer name if specified. */
-  if (peer_name != nullptr && !ssl_host_matches_name(peer, peer_name)) {
+  if (peer_name != nullptr && !grpc_ssl_host_matches_name(peer, peer_name)) {
     char* msg;
     gpr_asprintf(&msg, "Peer name %s is not in peer certificate", peer_name);
     grpc_error* error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
     gpr_free(msg);
     return error;
   }
-  *auth_context = tsi_ssl_peer_to_auth_context(peer);
+  *auth_context = grpc_ssl_peer_to_auth_context(peer);
   return GRPC_ERROR_NONE;
 }
 
@@ -924,7 +924,7 @@
   tsi_prop->value.length = prop->value_length;
 }
 
-tsi_peer tsi_shallow_peer_from_ssl_auth_context(
+tsi_peer grpc_shallow_peer_from_ssl_auth_context(
     const grpc_auth_context* auth_context) {
   size_t max_num_props = 0;
   grpc_auth_property_iterator it;
@@ -955,7 +955,7 @@
   return peer;
 }
 
-void tsi_shallow_peer_destruct(tsi_peer* peer) {
+void grpc_shallow_peer_destruct(tsi_peer* peer) {
   if (peer->properties != nullptr) gpr_free(peer->properties);
 }
 
@@ -967,8 +967,8 @@
   grpc_ssl_channel_security_connector* c =
       reinterpret_cast<grpc_ssl_channel_security_connector*>(sc);
   grpc_security_status status = GRPC_SECURITY_ERROR;
-  tsi_peer peer = tsi_shallow_peer_from_ssl_auth_context(auth_context);
-  if (ssl_host_matches_name(&peer, host)) status = GRPC_SECURITY_OK;
+  tsi_peer peer = grpc_shallow_peer_from_ssl_auth_context(auth_context);
+  if (grpc_ssl_host_matches_name(&peer, host)) status = GRPC_SECURITY_OK;
   /* If the target name was overridden, then the original target_name was
      'checked' transitively during the previous peer check at the end of the
      handshake. */
@@ -980,7 +980,7 @@
     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
         "call host does not match SSL server name");
   }
-  tsi_shallow_peer_destruct(&peer);
+  grpc_shallow_peer_destruct(&peer);
   return true;
 }
 
diff --git a/src/core/lib/security/security_connector/security_connector.h b/src/core/lib/security/security_connector/security_connector.h
index c4cc19d..f972316 100644
--- a/src/core/lib/security/security_connector/security_connector.h
+++ b/src/core/lib/security/security_connector/security_connector.h
@@ -239,10 +239,11 @@
                                                        const char* name);
 
 /* Exposed for testing only. */
-grpc_auth_context* tsi_ssl_peer_to_auth_context(const tsi_peer* peer);
-tsi_peer tsi_shallow_peer_from_ssl_auth_context(
+grpc_auth_context* grpc_ssl_peer_to_auth_context(const tsi_peer* peer);
+tsi_peer grpc_shallow_peer_from_ssl_auth_context(
     const grpc_auth_context* auth_context);
-void tsi_shallow_peer_destruct(tsi_peer* peer);
+void grpc_shallow_peer_destruct(tsi_peer* peer);
+int grpc_ssl_host_matches_name(const tsi_peer* peer, const char* peer_name);
 
 /* --- Default SSL Root Store. --- */
 namespace grpc_core {
diff --git a/src/core/lib/security/transport/security_handshaker.cc b/src/core/lib/security/transport/security_handshaker.cc
index 57dd340..aff723e 100644
--- a/src/core/lib/security/transport/security_handshaker.cc
+++ b/src/core/lib/security/transport/security_handshaker.cc
@@ -232,6 +232,10 @@
     const unsigned char* bytes_to_send, size_t bytes_to_send_size,
     tsi_handshaker_result* handshaker_result) {
   grpc_error* error = GRPC_ERROR_NONE;
+  // Handshaker was shutdown.
+  if (h->shutdown) {
+    return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Handshaker shutdown");
+  }
   // Read more if we need to.
   if (result == TSI_INCOMPLETE_DATA) {
     GPR_ASSERT(bytes_to_send_size == 0);
@@ -376,6 +380,7 @@
   gpr_mu_lock(&h->mu);
   if (!h->shutdown) {
     h->shutdown = true;
+    tsi_handshaker_shutdown(h->handshaker);
     grpc_endpoint_shutdown(h->args->endpoint, GRPC_ERROR_REF(why));
     cleanup_args_for_failure_locked(h);
   }
diff --git a/src/core/lib/surface/call.cc b/src/core/lib/surface/call.cc
index 0a732be..da48803 100644
--- a/src/core/lib/surface/call.cc
+++ b/src/core/lib/surface/call.cc
@@ -610,7 +610,7 @@
 // This is called via the call combiner to start sending a batch down
 // the filter stack.
 static void execute_batch_in_call_combiner(void* arg, grpc_error* ignored) {
-  GPR_TIMER_SCOPE("execute_batch", 0);
+  GPR_TIMER_SCOPE("execute_batch_in_call_combiner", 0);
   grpc_transport_stream_op_batch* batch =
       static_cast<grpc_transport_stream_op_batch*>(arg);
   grpc_call* call = static_cast<grpc_call*>(batch->handler_private.extra_arg);
@@ -1539,7 +1539,7 @@
 static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
                                         size_t nops, void* notify_tag,
                                         int is_notify_tag_closure) {
-  GPR_TIMER_SCOPE("grpc_call_start_batch", 0);
+  GPR_TIMER_SCOPE("call_start_batch", 0);
 
   size_t i;
   const grpc_op* op;
diff --git a/src/core/lib/surface/completion_queue.cc b/src/core/lib/surface/completion_queue.cc
index d036391..f751741 100644
--- a/src/core/lib/surface/completion_queue.cc
+++ b/src/core/lib/surface/completion_queue.cc
@@ -390,7 +390,6 @@
 
 static grpc_cq_completion* cq_event_queue_pop(grpc_cq_event_queue* q) {
   grpc_cq_completion* c = nullptr;
-  grpc_core::ExecCtx exec_ctx;
 
   if (gpr_spinlock_trylock(&q->queue_lock)) {
     GRPC_STATS_INC_CQ_EV_QUEUE_TRYLOCK_SUCCESSES();
diff --git a/src/core/lib/surface/version.cc b/src/core/lib/surface/version.cc
index a712e10..306b7c3 100644
--- a/src/core/lib/surface/version.cc
+++ b/src/core/lib/surface/version.cc
@@ -25,4 +25,4 @@
 
 const char* grpc_version_string(void) { return "6.0.0-dev"; }
 
-const char* grpc_g_stands_for(void) { return "glorious"; }
+const char* grpc_g_stands_for(void) { return "gloriosa"; }
diff --git a/src/core/tsi/alts/handshaker/alts_handshaker_client.cc b/src/core/tsi/alts/handshaker/alts_handshaker_client.cc
index 40f30e4..b5268ad 100644
--- a/src/core/tsi/alts/handshaker/alts_handshaker_client.cc
+++ b/src/core/tsi/alts/handshaker/alts_handshaker_client.cc
@@ -118,8 +118,7 @@
 static tsi_result handshaker_client_start_client(alts_handshaker_client* client,
                                                  alts_tsi_event* event) {
   if (client == nullptr || event == nullptr) {
-    gpr_log(GPR_ERROR,
-            "Invalid arguments to alts_grpc_handshaker_client_start_client()");
+    gpr_log(GPR_ERROR, "Invalid arguments to handshaker_client_start_client()");
     return TSI_INVALID_ARGUMENT;
   }
   grpc_byte_buffer* buffer = get_serialized_start_client(event);
@@ -167,8 +166,7 @@
                                                  alts_tsi_event* event,
                                                  grpc_slice* bytes_received) {
   if (client == nullptr || event == nullptr || bytes_received == nullptr) {
-    gpr_log(GPR_ERROR,
-            "Invalid arguments to alts_grpc_handshaker_client_start_server()");
+    gpr_log(GPR_ERROR, "Invalid arguments to handshaker_client_start_server()");
     return TSI_INVALID_ARGUMENT;
   }
   grpc_byte_buffer* buffer = get_serialized_start_server(event, bytes_received);
@@ -206,8 +204,7 @@
                                          alts_tsi_event* event,
                                          grpc_slice* bytes_received) {
   if (client == nullptr || event == nullptr || bytes_received == nullptr) {
-    gpr_log(GPR_ERROR,
-            "Invalid arguments to alts_grpc_handshaker_client_next()");
+    gpr_log(GPR_ERROR, "Invalid arguments to handshaker_client_next()");
     return TSI_INVALID_ARGUMENT;
   }
   grpc_byte_buffer* buffer = get_serialized_next(bytes_received);
@@ -223,6 +220,13 @@
   return result;
 }
 
+static void handshaker_client_shutdown(alts_handshaker_client* client) {
+  GPR_ASSERT(client != nullptr);
+  alts_grpc_handshaker_client* grpc_client =
+      reinterpret_cast<alts_grpc_handshaker_client*>(client);
+  GPR_ASSERT(grpc_call_cancel(grpc_client->call, nullptr) == GRPC_CALL_OK);
+}
+
 static void handshaker_client_destruct(alts_handshaker_client* client) {
   if (client == nullptr) {
     return;
@@ -234,7 +238,8 @@
 
 static const alts_handshaker_client_vtable vtable = {
     handshaker_client_start_client, handshaker_client_start_server,
-    handshaker_client_next, handshaker_client_destruct};
+    handshaker_client_next, handshaker_client_shutdown,
+    handshaker_client_destruct};
 
 alts_handshaker_client* alts_grpc_handshaker_client_create(
     grpc_channel* channel, grpc_completion_queue* queue,
@@ -306,6 +311,13 @@
   return TSI_INVALID_ARGUMENT;
 }
 
+void alts_handshaker_client_shutdown(alts_handshaker_client* client) {
+  if (client != nullptr && client->vtable != nullptr &&
+      client->vtable->shutdown != nullptr) {
+    client->vtable->shutdown(client);
+  }
+}
+
 void alts_handshaker_client_destroy(alts_handshaker_client* client) {
   if (client != nullptr) {
     if (client->vtable != nullptr && client->vtable->destruct != nullptr) {
diff --git a/src/core/tsi/alts/handshaker/alts_handshaker_client.h b/src/core/tsi/alts/handshaker/alts_handshaker_client.h
index fb2d2cf..8dd8fe4 100644
--- a/src/core/tsi/alts/handshaker/alts_handshaker_client.h
+++ b/src/core/tsi/alts/handshaker/alts_handshaker_client.h
@@ -51,6 +51,7 @@
                              alts_tsi_event* event, grpc_slice* bytes_received);
   tsi_result (*next)(alts_handshaker_client* client, alts_tsi_event* event,
                      grpc_slice* bytes_received);
+  void (*shutdown)(alts_handshaker_client* client);
   void (*destruct)(alts_handshaker_client* client);
 } alts_handshaker_client_vtable;
 
@@ -100,6 +101,15 @@
                                        grpc_slice* bytes_received);
 
 /**
+ * This method cancels previously scheduled, but yet executed handshaker
+ * requests to ALTS handshaker service. After this operation, the handshake
+ * will be shutdown, and no more handshaker requests will get scheduled.
+ *
+ * - client: ALTS handshaker client instance.
+ */
+void alts_handshaker_client_shutdown(alts_handshaker_client* client);
+
+/**
  * This method destroys a ALTS handshaker client.
  *
  * - client: a ALTS handshaker client instance.
diff --git a/src/core/tsi/alts/handshaker/alts_tsi_handshaker.cc b/src/core/tsi/alts/handshaker/alts_tsi_handshaker.cc
index 529f210..9676085 100644
--- a/src/core/tsi/alts/handshaker/alts_tsi_handshaker.cc
+++ b/src/core/tsi/alts/handshaker/alts_tsi_handshaker.cc
@@ -241,6 +241,10 @@
     gpr_log(GPR_ERROR, "Invalid arguments to handshaker_next()");
     return TSI_INVALID_ARGUMENT;
   }
+  if (self->handshake_shutdown) {
+    gpr_log(GPR_ERROR, "TSI handshake shutdown");
+    return TSI_HANDSHAKE_SHUTDOWN;
+  }
   alts_tsi_handshaker* handshaker =
       reinterpret_cast<alts_tsi_handshaker*>(self);
   tsi_result ok = TSI_OK;
@@ -277,6 +281,16 @@
   return TSI_ASYNC;
 }
 
+static void handshaker_shutdown(tsi_handshaker* self) {
+  GPR_ASSERT(self != nullptr);
+  if (self->handshake_shutdown) {
+    return;
+  }
+  alts_tsi_handshaker* handshaker =
+      reinterpret_cast<alts_tsi_handshaker*>(self);
+  alts_handshaker_client_shutdown(handshaker->client);
+}
+
 static void handshaker_destroy(tsi_handshaker* self) {
   if (self == nullptr) {
     return;
@@ -292,8 +306,10 @@
 }
 
 static const tsi_handshaker_vtable handshaker_vtable = {
-    nullptr,        nullptr, nullptr, nullptr, nullptr, handshaker_destroy,
-    handshaker_next};
+    nullptr,         nullptr,
+    nullptr,         nullptr,
+    nullptr,         handshaker_destroy,
+    handshaker_next, handshaker_shutdown};
 
 static void thread_worker(void* arg) {
   while (true) {
@@ -401,6 +417,11 @@
     cb(TSI_INTERNAL_ERROR, user_data, nullptr, 0, nullptr);
     return;
   }
+  if (handshaker->base.handshake_shutdown) {
+    gpr_log(GPR_ERROR, "TSI handshake shutdown");
+    cb(TSI_HANDSHAKE_SHUTDOWN, user_data, nullptr, 0, nullptr);
+    return;
+  }
   /* Failed grpc call check. */
   if (!is_ok || status != GRPC_STATUS_OK) {
     gpr_log(GPR_ERROR, "grpc call made to handshaker service failed");
@@ -479,5 +500,10 @@
   handshaker->client = client;
 }
 
+alts_handshaker_client* alts_tsi_handshaker_get_client_for_testing(
+    alts_tsi_handshaker* handshaker) {
+  return handshaker->client;
+}
+
 }  // namespace internal
 }  // namespace grpc_core
diff --git a/src/core/tsi/alts/handshaker/alts_tsi_handshaker_private.h b/src/core/tsi/alts/handshaker/alts_tsi_handshaker_private.h
index 9b7b9bb..9612071 100644
--- a/src/core/tsi/alts/handshaker/alts_tsi_handshaker_private.h
+++ b/src/core/tsi/alts/handshaker/alts_tsi_handshaker_private.h
@@ -33,6 +33,9 @@
 void alts_tsi_handshaker_set_client_for_testing(alts_tsi_handshaker* handshaker,
                                                 alts_handshaker_client* client);
 
+alts_handshaker_client* alts_tsi_handshaker_get_client_for_testing(
+    alts_tsi_handshaker* handshaker);
+
 /* For testing only. */
 bool alts_tsi_handshaker_get_has_sent_start_message_for_testing(
     alts_tsi_handshaker* handshaker);
diff --git a/src/core/tsi/fake_transport_security.cc b/src/core/tsi/fake_transport_security.cc
index ad08b50..4d4c495 100644
--- a/src/core/tsi/fake_transport_security.cc
+++ b/src/core/tsi/fake_transport_security.cc
@@ -738,6 +738,7 @@
     nullptr, /* create_frame_protector    -- deprecated */
     fake_handshaker_destroy,
     fake_handshaker_next,
+    nullptr, /* shutdown */
 };
 
 tsi_handshaker* tsi_create_fake_handshaker(int is_client) {
diff --git a/src/core/tsi/ssl_transport_security.cc b/src/core/tsi/ssl_transport_security.cc
index 0ba6587..8065a8b 100644
--- a/src/core/tsi/ssl_transport_security.cc
+++ b/src/core/tsi/ssl_transport_security.cc
@@ -57,6 +57,7 @@
 
 #define TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND 16384
 #define TSI_SSL_MAX_PROTECTED_FRAME_SIZE_LOWER_BOUND 1024
+#define TSI_SSL_HANDSHAKER_OUTGOING_BUFFER_INITIAL_SIZE 1024
 
 /* Putting a macro like this and littering the source file with #if is really
    bad practice.
@@ -105,10 +106,20 @@
   SSL* ssl;
   BIO* network_io;
   tsi_result result;
+  unsigned char* outgoing_bytes_buffer;
+  size_t outgoing_bytes_buffer_size;
   tsi_ssl_handshaker_factory* factory_ref;
 } tsi_ssl_handshaker;
 
 typedef struct {
+  tsi_handshaker_result base;
+  SSL* ssl;
+  BIO* network_io;
+  unsigned char* unused_bytes;
+  size_t unused_bytes_size;
+} tsi_ssl_handshaker_result;
+
+typedef struct {
   tsi_frame_protector base;
   SSL* ssl;
   BIO* network_io;
@@ -120,12 +131,14 @@
 /* --- Library Initialization. ---*/
 
 static gpr_once g_init_openssl_once = GPR_ONCE_INIT;
-static gpr_mu* g_openssl_mutexes = nullptr;
 static int g_ssl_ctx_ex_factory_index = -1;
+static const unsigned char kSslSessionIdContext[] = {'g', 'r', 'p', 'c'};
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000
+static gpr_mu* g_openssl_mutexes = nullptr;
 static void openssl_locking_cb(int mode, int type, const char* file,
                                int line) GRPC_UNUSED;
 static unsigned long openssl_thread_id_cb(void) GRPC_UNUSED;
-static const unsigned char kSslSessionIdContext[] = {'g', 'r', 'p', 'c'};
 
 static void openssl_locking_cb(int mode, int type, const char* file, int line) {
   if (mode & CRYPTO_LOCK) {
@@ -138,22 +151,27 @@
 static unsigned long openssl_thread_id_cb(void) {
   return static_cast<unsigned long>(gpr_thd_currentid());
 }
+#endif
 
 static void init_openssl(void) {
-  int i;
-  int num_locks;
   SSL_library_init();
   SSL_load_error_strings();
   OpenSSL_add_all_algorithms();
-  num_locks = CRYPTO_num_locks();
-  GPR_ASSERT(num_locks > 0);
-  g_openssl_mutexes = static_cast<gpr_mu*>(
-      gpr_malloc(static_cast<size_t>(num_locks) * sizeof(gpr_mu)));
-  for (i = 0; i < CRYPTO_num_locks(); i++) {
-    gpr_mu_init(&g_openssl_mutexes[i]);
+#if OPENSSL_VERSION_NUMBER < 0x10100000
+  if (!CRYPTO_get_locking_callback()) {
+    int num_locks = CRYPTO_num_locks();
+    GPR_ASSERT(num_locks > 0);
+    g_openssl_mutexes = static_cast<gpr_mu*>(
+        gpr_malloc(static_cast<size_t>(num_locks) * sizeof(gpr_mu)));
+    for (int i = 0; i < num_locks; i++) {
+      gpr_mu_init(&g_openssl_mutexes[i]);
+    }
+    CRYPTO_set_locking_callback(openssl_locking_cb);
+    CRYPTO_set_id_callback(openssl_thread_id_cb);
+  } else {
+    gpr_log(GPR_INFO, "OpenSSL callback has already been set.");
   }
-  CRYPTO_set_locking_callback(openssl_locking_cb);
-  CRYPTO_set_id_callback(openssl_thread_id_cb);
+#endif
   g_ssl_ctx_ex_factory_index =
       SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
   GPR_ASSERT(g_ssl_ctx_ex_factory_index != -1);
@@ -987,94 +1005,15 @@
   gpr_ref_init(&factory->refcount, 1);
 }
 
-/* --- tsi_handshaker methods implementation. ---*/
+/* --- tsi_handshaker_result methods implementation. ---*/
 
-static tsi_result ssl_handshaker_get_bytes_to_send_to_peer(tsi_handshaker* self,
-                                                           unsigned char* bytes,
-                                                           size_t* bytes_size) {
-  tsi_ssl_handshaker* impl = reinterpret_cast<tsi_ssl_handshaker*>(self);
-  int bytes_read_from_ssl = 0;
-  if (bytes == nullptr || bytes_size == nullptr || *bytes_size == 0 ||
-      *bytes_size > INT_MAX) {
-    return TSI_INVALID_ARGUMENT;
-  }
-  GPR_ASSERT(*bytes_size <= INT_MAX);
-  bytes_read_from_ssl =
-      BIO_read(impl->network_io, bytes, static_cast<int>(*bytes_size));
-  if (bytes_read_from_ssl < 0) {
-    *bytes_size = 0;
-    if (!BIO_should_retry(impl->network_io)) {
-      impl->result = TSI_INTERNAL_ERROR;
-      return impl->result;
-    } else {
-      return TSI_OK;
-    }
-  }
-  *bytes_size = static_cast<size_t>(bytes_read_from_ssl);
-  return BIO_pending(impl->network_io) == 0 ? TSI_OK : TSI_INCOMPLETE_DATA;
-}
-
-static tsi_result ssl_handshaker_get_result(tsi_handshaker* self) {
-  tsi_ssl_handshaker* impl = reinterpret_cast<tsi_ssl_handshaker*>(self);
-  if ((impl->result == TSI_HANDSHAKE_IN_PROGRESS) &&
-      SSL_is_init_finished(impl->ssl)) {
-    impl->result = TSI_OK;
-  }
-  return impl->result;
-}
-
-static tsi_result ssl_handshaker_process_bytes_from_peer(
-    tsi_handshaker* self, const unsigned char* bytes, size_t* bytes_size) {
-  tsi_ssl_handshaker* impl = reinterpret_cast<tsi_ssl_handshaker*>(self);
-  int bytes_written_into_ssl_size = 0;
-  if (bytes == nullptr || bytes_size == nullptr || *bytes_size > INT_MAX) {
-    return TSI_INVALID_ARGUMENT;
-  }
-  GPR_ASSERT(*bytes_size <= INT_MAX);
-  bytes_written_into_ssl_size =
-      BIO_write(impl->network_io, bytes, static_cast<int>(*bytes_size));
-  if (bytes_written_into_ssl_size < 0) {
-    gpr_log(GPR_ERROR, "Could not write to memory BIO.");
-    impl->result = TSI_INTERNAL_ERROR;
-    return impl->result;
-  }
-  *bytes_size = static_cast<size_t>(bytes_written_into_ssl_size);
-
-  if (!tsi_handshaker_is_in_progress(self)) {
-    impl->result = TSI_OK;
-    return impl->result;
-  } else {
-    /* Get ready to get some bytes from SSL. */
-    int ssl_result = SSL_do_handshake(impl->ssl);
-    ssl_result = SSL_get_error(impl->ssl, ssl_result);
-    switch (ssl_result) {
-      case SSL_ERROR_WANT_READ:
-        if (BIO_pending(impl->network_io) == 0) {
-          /* We need more data. */
-          return TSI_INCOMPLETE_DATA;
-        } else {
-          return TSI_OK;
-        }
-      case SSL_ERROR_NONE:
-        return TSI_OK;
-      default: {
-        char err_str[256];
-        ERR_error_string_n(ERR_get_error(), err_str, sizeof(err_str));
-        gpr_log(GPR_ERROR, "Handshake failed with fatal error %s: %s.",
-                ssl_error_string(ssl_result), err_str);
-        impl->result = TSI_PROTOCOL_FAILURE;
-        return impl->result;
-      }
-    }
-  }
-}
-
-static tsi_result ssl_handshaker_extract_peer(tsi_handshaker* self,
-                                              tsi_peer* peer) {
+static tsi_result ssl_handshaker_result_extract_peer(
+    const tsi_handshaker_result* self, tsi_peer* peer) {
   tsi_result result = TSI_OK;
   const unsigned char* alpn_selected = nullptr;
   unsigned int alpn_selected_len;
-  tsi_ssl_handshaker* impl = reinterpret_cast<tsi_ssl_handshaker*>(self);
+  const tsi_ssl_handshaker_result* impl =
+      reinterpret_cast<const tsi_ssl_handshaker_result*>(self);
   X509* peer_cert = SSL_get_peer_certificate(impl->ssl);
   if (peer_cert != nullptr) {
     result = peer_from_x509(peer_cert, 1, peer);
@@ -1120,12 +1059,14 @@
   return result;
 }
 
-static tsi_result ssl_handshaker_create_frame_protector(
-    tsi_handshaker* self, size_t* max_output_protected_frame_size,
+static tsi_result ssl_handshaker_result_create_frame_protector(
+    const tsi_handshaker_result* self, size_t* max_output_protected_frame_size,
     tsi_frame_protector** protector) {
   size_t actual_max_output_protected_frame_size =
       TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND;
-  tsi_ssl_handshaker* impl = reinterpret_cast<tsi_ssl_handshaker*>(self);
+  tsi_ssl_handshaker_result* impl =
+      reinterpret_cast<tsi_ssl_handshaker_result*>(
+          const_cast<tsi_handshaker_result*>(self));
   tsi_ssl_frame_protector* protector_impl =
       static_cast<tsi_ssl_frame_protector*>(
           gpr_zalloc(sizeof(*protector_impl)));
@@ -1153,35 +1094,218 @@
     return TSI_INTERNAL_ERROR;
   }
 
-  /* Transfer ownership of ssl and network_io to the frame protector. It is OK
-   * as the caller cannot call anything else but destroy on the handshaker
-   * after this call. */
+  /* Transfer ownership of ssl and network_io to the frame protector. */
   protector_impl->ssl = impl->ssl;
   impl->ssl = nullptr;
   protector_impl->network_io = impl->network_io;
   impl->network_io = nullptr;
-
   protector_impl->base.vtable = &frame_protector_vtable;
   *protector = &protector_impl->base;
   return TSI_OK;
 }
 
+static tsi_result ssl_handshaker_result_get_unused_bytes(
+    const tsi_handshaker_result* self, const unsigned char** bytes,
+    size_t* bytes_size) {
+  const tsi_ssl_handshaker_result* impl =
+      reinterpret_cast<const tsi_ssl_handshaker_result*>(self);
+  *bytes_size = impl->unused_bytes_size;
+  *bytes = impl->unused_bytes;
+  return TSI_OK;
+}
+
+static void ssl_handshaker_result_destroy(tsi_handshaker_result* self) {
+  tsi_ssl_handshaker_result* impl =
+      reinterpret_cast<tsi_ssl_handshaker_result*>(self);
+  SSL_free(impl->ssl);
+  BIO_free(impl->network_io);
+  gpr_free(impl->unused_bytes);
+  gpr_free(impl);
+}
+
+static const tsi_handshaker_result_vtable handshaker_result_vtable = {
+    ssl_handshaker_result_extract_peer,
+    nullptr, /* create_zero_copy_grpc_protector */
+    ssl_handshaker_result_create_frame_protector,
+    ssl_handshaker_result_get_unused_bytes,
+    ssl_handshaker_result_destroy,
+};
+
+static tsi_result ssl_handshaker_result_create(
+    tsi_ssl_handshaker* handshaker, const unsigned char* unused_bytes,
+    size_t unused_bytes_size, tsi_handshaker_result** handshaker_result) {
+  if (handshaker == nullptr || handshaker_result == nullptr ||
+      (unused_bytes_size > 0 && unused_bytes == nullptr)) {
+    return TSI_INVALID_ARGUMENT;
+  }
+  tsi_ssl_handshaker_result* result =
+      static_cast<tsi_ssl_handshaker_result*>(gpr_zalloc(sizeof(*result)));
+  result->base.vtable = &handshaker_result_vtable;
+  /* Transfer ownership of ssl and network_io to the handshaker result. */
+  result->ssl = handshaker->ssl;
+  handshaker->ssl = nullptr;
+  result->network_io = handshaker->network_io;
+  handshaker->network_io = nullptr;
+  if (unused_bytes_size > 0) {
+    result->unused_bytes =
+        static_cast<unsigned char*>(gpr_malloc(unused_bytes_size));
+    memcpy(result->unused_bytes, unused_bytes, unused_bytes_size);
+  }
+  result->unused_bytes_size = unused_bytes_size;
+  *handshaker_result = &result->base;
+  return TSI_OK;
+}
+
+/* --- tsi_handshaker methods implementation. ---*/
+
+static tsi_result ssl_handshaker_get_bytes_to_send_to_peer(
+    tsi_ssl_handshaker* impl, unsigned char* bytes, size_t* bytes_size) {
+  int bytes_read_from_ssl = 0;
+  if (bytes == nullptr || bytes_size == nullptr || *bytes_size == 0 ||
+      *bytes_size > INT_MAX) {
+    return TSI_INVALID_ARGUMENT;
+  }
+  GPR_ASSERT(*bytes_size <= INT_MAX);
+  bytes_read_from_ssl =
+      BIO_read(impl->network_io, bytes, static_cast<int>(*bytes_size));
+  if (bytes_read_from_ssl < 0) {
+    *bytes_size = 0;
+    if (!BIO_should_retry(impl->network_io)) {
+      impl->result = TSI_INTERNAL_ERROR;
+      return impl->result;
+    } else {
+      return TSI_OK;
+    }
+  }
+  *bytes_size = static_cast<size_t>(bytes_read_from_ssl);
+  return BIO_pending(impl->network_io) == 0 ? TSI_OK : TSI_INCOMPLETE_DATA;
+}
+
+static tsi_result ssl_handshaker_get_result(tsi_ssl_handshaker* impl) {
+  if ((impl->result == TSI_HANDSHAKE_IN_PROGRESS) &&
+      SSL_is_init_finished(impl->ssl)) {
+    impl->result = TSI_OK;
+  }
+  return impl->result;
+}
+
+static tsi_result ssl_handshaker_process_bytes_from_peer(
+    tsi_ssl_handshaker* impl, const unsigned char* bytes, size_t* bytes_size) {
+  int bytes_written_into_ssl_size = 0;
+  if (bytes == nullptr || bytes_size == nullptr || *bytes_size > INT_MAX) {
+    return TSI_INVALID_ARGUMENT;
+  }
+  GPR_ASSERT(*bytes_size <= INT_MAX);
+  bytes_written_into_ssl_size =
+      BIO_write(impl->network_io, bytes, static_cast<int>(*bytes_size));
+  if (bytes_written_into_ssl_size < 0) {
+    gpr_log(GPR_ERROR, "Could not write to memory BIO.");
+    impl->result = TSI_INTERNAL_ERROR;
+    return impl->result;
+  }
+  *bytes_size = static_cast<size_t>(bytes_written_into_ssl_size);
+
+  if (ssl_handshaker_get_result(impl) != TSI_HANDSHAKE_IN_PROGRESS) {
+    impl->result = TSI_OK;
+    return impl->result;
+  } else {
+    /* Get ready to get some bytes from SSL. */
+    int ssl_result = SSL_do_handshake(impl->ssl);
+    ssl_result = SSL_get_error(impl->ssl, ssl_result);
+    switch (ssl_result) {
+      case SSL_ERROR_WANT_READ:
+        if (BIO_pending(impl->network_io) == 0) {
+          /* We need more data. */
+          return TSI_INCOMPLETE_DATA;
+        } else {
+          return TSI_OK;
+        }
+      case SSL_ERROR_NONE:
+        return TSI_OK;
+      default: {
+        char err_str[256];
+        ERR_error_string_n(ERR_get_error(), err_str, sizeof(err_str));
+        gpr_log(GPR_ERROR, "Handshake failed with fatal error %s: %s.",
+                ssl_error_string(ssl_result), err_str);
+        impl->result = TSI_PROTOCOL_FAILURE;
+        return impl->result;
+      }
+    }
+  }
+}
+
 static void ssl_handshaker_destroy(tsi_handshaker* self) {
   tsi_ssl_handshaker* impl = reinterpret_cast<tsi_ssl_handshaker*>(self);
   SSL_free(impl->ssl);
   BIO_free(impl->network_io);
+  gpr_free(impl->outgoing_bytes_buffer);
   tsi_ssl_handshaker_factory_unref(impl->factory_ref);
   gpr_free(impl);
 }
 
+static tsi_result ssl_handshaker_next(
+    tsi_handshaker* self, const unsigned char* received_bytes,
+    size_t received_bytes_size, const unsigned char** bytes_to_send,
+    size_t* bytes_to_send_size, tsi_handshaker_result** handshaker_result,
+    tsi_handshaker_on_next_done_cb cb, void* user_data) {
+  /* Input sanity check.  */
+  if ((received_bytes_size > 0 && received_bytes == nullptr) ||
+      bytes_to_send == nullptr || bytes_to_send_size == nullptr ||
+      handshaker_result == nullptr) {
+    return TSI_INVALID_ARGUMENT;
+  }
+  /* If there are received bytes, process them first.  */
+  tsi_ssl_handshaker* impl = reinterpret_cast<tsi_ssl_handshaker*>(self);
+  tsi_result status = TSI_OK;
+  size_t bytes_consumed = received_bytes_size;
+  if (received_bytes_size > 0) {
+    status = ssl_handshaker_process_bytes_from_peer(impl, received_bytes,
+                                                    &bytes_consumed);
+    if (status != TSI_OK) return status;
+  }
+  /* Get bytes to send to the peer, if available.  */
+  size_t offset = 0;
+  do {
+    size_t to_send_size = impl->outgoing_bytes_buffer_size - offset;
+    status = ssl_handshaker_get_bytes_to_send_to_peer(
+        impl, impl->outgoing_bytes_buffer + offset, &to_send_size);
+    offset += to_send_size;
+    if (status == TSI_INCOMPLETE_DATA) {
+      impl->outgoing_bytes_buffer_size *= 2;
+      impl->outgoing_bytes_buffer = static_cast<unsigned char*>(gpr_realloc(
+          impl->outgoing_bytes_buffer, impl->outgoing_bytes_buffer_size));
+    }
+  } while (status == TSI_INCOMPLETE_DATA);
+  if (status != TSI_OK) return status;
+  *bytes_to_send = impl->outgoing_bytes_buffer;
+  *bytes_to_send_size = offset;
+  /* If handshake completes, create tsi_handshaker_result.  */
+  if (ssl_handshaker_get_result(impl) == TSI_HANDSHAKE_IN_PROGRESS) {
+    *handshaker_result = nullptr;
+  } else {
+    size_t unused_bytes_size = received_bytes_size - bytes_consumed;
+    const unsigned char* unused_bytes =
+        unused_bytes_size == 0 ? nullptr : received_bytes + bytes_consumed;
+    status = ssl_handshaker_result_create(impl, unused_bytes, unused_bytes_size,
+                                          handshaker_result);
+    if (status == TSI_OK) {
+      /* Indicates that the handshake has completed and that a handshaker_result
+       * has been created. */
+      self->handshaker_result_created = true;
+    }
+  }
+  return status;
+}
+
 static const tsi_handshaker_vtable handshaker_vtable = {
-    ssl_handshaker_get_bytes_to_send_to_peer,
-    ssl_handshaker_process_bytes_from_peer,
-    ssl_handshaker_get_result,
-    ssl_handshaker_extract_peer,
-    ssl_handshaker_create_frame_protector,
+    nullptr, /* get_bytes_to_send_to_peer -- deprecated */
+    nullptr, /* process_bytes_from_peer   -- deprecated */
+    nullptr, /* get_result                -- deprecated */
+    nullptr, /* extract_peer              -- deprecated */
+    nullptr, /* create_frame_protector    -- deprecated */
     ssl_handshaker_destroy,
-    nullptr,
+    ssl_handshaker_next,
+    nullptr, /* shutdown */
 };
 
 /* --- tsi_ssl_handshaker_factory common methods. --- */
@@ -1259,6 +1383,10 @@
   impl->ssl = ssl;
   impl->network_io = network_io;
   impl->result = TSI_HANDSHAKE_IN_PROGRESS;
+  impl->outgoing_bytes_buffer_size =
+      TSI_SSL_HANDSHAKER_OUTGOING_BUFFER_INITIAL_SIZE;
+  impl->outgoing_bytes_buffer =
+      static_cast<unsigned char*>(gpr_zalloc(impl->outgoing_bytes_buffer_size));
   impl->base.vtable = &handshaker_vtable;
   impl->factory_ref = tsi_ssl_handshaker_factory_ref(factory);
 
diff --git a/src/core/tsi/transport_security.cc b/src/core/tsi/transport_security.cc
index 129533f..99b3229 100644
--- a/src/core/tsi/transport_security.cc
+++ b/src/core/tsi/transport_security.cc
@@ -136,6 +136,7 @@
     return TSI_INVALID_ARGUMENT;
   }
   if (self->frame_protector_created) return TSI_FAILED_PRECONDITION;
+  if (self->handshake_shutdown) return TSI_HANDSHAKE_SHUTDOWN;
   if (self->vtable->get_bytes_to_send_to_peer == nullptr)
     return TSI_UNIMPLEMENTED;
   return self->vtable->get_bytes_to_send_to_peer(self, bytes, bytes_size);
@@ -149,6 +150,7 @@
     return TSI_INVALID_ARGUMENT;
   }
   if (self->frame_protector_created) return TSI_FAILED_PRECONDITION;
+  if (self->handshake_shutdown) return TSI_HANDSHAKE_SHUTDOWN;
   if (self->vtable->process_bytes_from_peer == nullptr)
     return TSI_UNIMPLEMENTED;
   return self->vtable->process_bytes_from_peer(self, bytes, bytes_size);
@@ -157,6 +159,7 @@
 tsi_result tsi_handshaker_get_result(tsi_handshaker* self) {
   if (self == nullptr || self->vtable == nullptr) return TSI_INVALID_ARGUMENT;
   if (self->frame_protector_created) return TSI_FAILED_PRECONDITION;
+  if (self->handshake_shutdown) return TSI_HANDSHAKE_SHUTDOWN;
   if (self->vtable->get_result == nullptr) return TSI_UNIMPLEMENTED;
   return self->vtable->get_result(self);
 }
@@ -167,6 +170,7 @@
   }
   memset(peer, 0, sizeof(tsi_peer));
   if (self->frame_protector_created) return TSI_FAILED_PRECONDITION;
+  if (self->handshake_shutdown) return TSI_HANDSHAKE_SHUTDOWN;
   if (tsi_handshaker_get_result(self) != TSI_OK) {
     return TSI_FAILED_PRECONDITION;
   }
@@ -182,6 +186,7 @@
     return TSI_INVALID_ARGUMENT;
   }
   if (self->frame_protector_created) return TSI_FAILED_PRECONDITION;
+  if (self->handshake_shutdown) return TSI_HANDSHAKE_SHUTDOWN;
   if (tsi_handshaker_get_result(self) != TSI_OK) return TSI_FAILED_PRECONDITION;
   if (self->vtable->create_frame_protector == nullptr) return TSI_UNIMPLEMENTED;
   result = self->vtable->create_frame_protector(self, max_protected_frame_size,
@@ -199,12 +204,21 @@
     tsi_handshaker_on_next_done_cb cb, void* user_data) {
   if (self == nullptr || self->vtable == nullptr) return TSI_INVALID_ARGUMENT;
   if (self->handshaker_result_created) return TSI_FAILED_PRECONDITION;
+  if (self->handshake_shutdown) return TSI_HANDSHAKE_SHUTDOWN;
   if (self->vtable->next == nullptr) return TSI_UNIMPLEMENTED;
   return self->vtable->next(self, received_bytes, received_bytes_size,
                             bytes_to_send, bytes_to_send_size,
                             handshaker_result, cb, user_data);
 }
 
+void tsi_handshaker_shutdown(tsi_handshaker* self) {
+  if (self == nullptr || self->vtable == nullptr) return;
+  self->handshake_shutdown = true;
+  if (self->vtable->shutdown != nullptr) {
+    self->vtable->shutdown(self);
+  }
+}
+
 void tsi_handshaker_destroy(tsi_handshaker* self) {
   if (self == nullptr) return;
   self->vtable->destroy(self);
diff --git a/src/core/tsi/transport_security.h b/src/core/tsi/transport_security.h
index b1ec82d..1923a70 100644
--- a/src/core/tsi/transport_security.h
+++ b/src/core/tsi/transport_security.h
@@ -73,12 +73,14 @@
                      size_t* bytes_to_send_size,
                      tsi_handshaker_result** handshaker_result,
                      tsi_handshaker_on_next_done_cb cb, void* user_data);
+  void (*shutdown)(tsi_handshaker* self);
 } tsi_handshaker_vtable;
 
 struct tsi_handshaker {
   const tsi_handshaker_vtable* vtable;
   bool frame_protector_created;
   bool handshaker_result_created;
+  bool handshake_shutdown;
 };
 
 /* Base for tsi_handshaker_result implementations.
diff --git a/src/core/tsi/transport_security_adapter.cc b/src/core/tsi/transport_security_adapter.cc
deleted file mode 100644
index 25608f0..0000000
--- a/src/core/tsi/transport_security_adapter.cc
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- *
- * Copyright 2017 gRPC authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#include <grpc/support/port_platform.h>
-
-#include "src/core/tsi/transport_security_adapter.h"
-
-#include <string.h>
-
-#include <grpc/support/alloc.h>
-#include <grpc/support/log.h>
-#include "src/core/tsi/transport_security.h"
-
-#define TSI_ADAPTER_INITIAL_BUFFER_SIZE 256
-
-/* --- tsi_adapter_handshaker_result implementation ---*/
-
-typedef struct {
-  tsi_handshaker_result base;
-  tsi_handshaker* wrapped;
-  unsigned char* unused_bytes;
-  size_t unused_bytes_size;
-} tsi_adapter_handshaker_result;
-
-static tsi_result adapter_result_extract_peer(const tsi_handshaker_result* self,
-                                              tsi_peer* peer) {
-  tsi_adapter_handshaker_result* impl = (tsi_adapter_handshaker_result*)self;
-  return tsi_handshaker_extract_peer(impl->wrapped, peer);
-}
-
-static tsi_result adapter_result_create_frame_protector(
-    const tsi_handshaker_result* self, size_t* max_output_protected_frame_size,
-    tsi_frame_protector** protector) {
-  tsi_adapter_handshaker_result* impl = (tsi_adapter_handshaker_result*)self;
-  return tsi_handshaker_create_frame_protector(
-      impl->wrapped, max_output_protected_frame_size, protector);
-}
-
-static tsi_result adapter_result_get_unused_bytes(
-    const tsi_handshaker_result* self, const unsigned char** bytes,
-    size_t* byte_size) {
-  tsi_adapter_handshaker_result* impl = (tsi_adapter_handshaker_result*)self;
-  *bytes = impl->unused_bytes;
-  *byte_size = impl->unused_bytes_size;
-  return TSI_OK;
-}
-
-static void adapter_result_destroy(tsi_handshaker_result* self) {
-  tsi_adapter_handshaker_result* impl =
-      reinterpret_cast<tsi_adapter_handshaker_result*>(self);
-  tsi_handshaker_destroy(impl->wrapped);
-  gpr_free(impl->unused_bytes);
-  gpr_free(self);
-}
-
-static const tsi_handshaker_result_vtable result_vtable = {
-    adapter_result_extract_peer,
-    nullptr, /* create_zero_copy_grpc_protector */
-    adapter_result_create_frame_protector,
-    adapter_result_get_unused_bytes,
-    adapter_result_destroy,
-};
-
-/* Ownership of wrapped tsi_handshaker is transferred to the result object.  */
-static tsi_result tsi_adapter_create_handshaker_result(
-    tsi_handshaker* wrapped, const unsigned char* unused_bytes,
-    size_t unused_bytes_size, tsi_handshaker_result** handshaker_result) {
-  if (wrapped == nullptr ||
-      (unused_bytes_size > 0 && unused_bytes == nullptr)) {
-    return TSI_INVALID_ARGUMENT;
-  }
-  tsi_adapter_handshaker_result* impl =
-      static_cast<tsi_adapter_handshaker_result*>(gpr_zalloc(sizeof(*impl)));
-  impl->base.vtable = &result_vtable;
-  impl->wrapped = wrapped;
-  impl->unused_bytes_size = unused_bytes_size;
-  if (unused_bytes_size > 0) {
-    impl->unused_bytes =
-        static_cast<unsigned char*>(gpr_malloc(unused_bytes_size));
-    memcpy(impl->unused_bytes, unused_bytes, unused_bytes_size);
-  } else {
-    impl->unused_bytes = nullptr;
-  }
-  *handshaker_result = &impl->base;
-  return TSI_OK;
-}
-
-/* --- tsi_adapter_handshaker implementation ---*/
-
-typedef struct {
-  tsi_handshaker base;
-  tsi_handshaker* wrapped;
-  unsigned char* adapter_buffer;
-  size_t adapter_buffer_size;
-} tsi_adapter_handshaker;
-
-static tsi_result adapter_get_bytes_to_send_to_peer(tsi_handshaker* self,
-                                                    unsigned char* bytes,
-                                                    size_t* bytes_size) {
-  return tsi_handshaker_get_bytes_to_send_to_peer(
-      tsi_adapter_handshaker_get_wrapped(self), bytes, bytes_size);
-}
-
-static tsi_result adapter_process_bytes_from_peer(tsi_handshaker* self,
-                                                  const unsigned char* bytes,
-                                                  size_t* bytes_size) {
-  return tsi_handshaker_process_bytes_from_peer(
-      tsi_adapter_handshaker_get_wrapped(self), bytes, bytes_size);
-}
-
-static tsi_result adapter_get_result(tsi_handshaker* self) {
-  return tsi_handshaker_get_result(tsi_adapter_handshaker_get_wrapped(self));
-}
-
-static tsi_result adapter_extract_peer(tsi_handshaker* self, tsi_peer* peer) {
-  return tsi_handshaker_extract_peer(tsi_adapter_handshaker_get_wrapped(self),
-                                     peer);
-}
-
-static tsi_result adapter_create_frame_protector(
-    tsi_handshaker* self, size_t* max_protected_frame_size,
-    tsi_frame_protector** protector) {
-  return tsi_handshaker_create_frame_protector(
-      tsi_adapter_handshaker_get_wrapped(self), max_protected_frame_size,
-      protector);
-}
-
-static void adapter_destroy(tsi_handshaker* self) {
-  tsi_adapter_handshaker* impl =
-      reinterpret_cast<tsi_adapter_handshaker*>(self);
-  tsi_handshaker_destroy(impl->wrapped);
-  gpr_free(impl->adapter_buffer);
-  gpr_free(self);
-}
-
-static tsi_result adapter_next(
-    tsi_handshaker* self, const unsigned char* received_bytes,
-    size_t received_bytes_size, const unsigned char** bytes_to_send,
-    size_t* bytes_to_send_size, tsi_handshaker_result** handshaker_result,
-    tsi_handshaker_on_next_done_cb cb, void* user_data) {
-  /* Input sanity check.  */
-  if ((received_bytes_size > 0 && received_bytes == nullptr) ||
-      bytes_to_send == nullptr || bytes_to_send_size == nullptr ||
-      handshaker_result == nullptr) {
-    return TSI_INVALID_ARGUMENT;
-  }
-
-  /* If there are received bytes, process them first.  */
-  tsi_adapter_handshaker* impl =
-      reinterpret_cast<tsi_adapter_handshaker*>(self);
-  tsi_result status = TSI_OK;
-  size_t bytes_consumed = received_bytes_size;
-  if (received_bytes_size > 0) {
-    status = tsi_handshaker_process_bytes_from_peer(
-        impl->wrapped, received_bytes, &bytes_consumed);
-    if (status != TSI_OK) return status;
-  }
-
-  /* Get bytes to send to the peer, if available.  */
-  size_t offset = 0;
-  do {
-    size_t to_send_size = impl->adapter_buffer_size - offset;
-    status = tsi_handshaker_get_bytes_to_send_to_peer(
-        impl->wrapped, impl->adapter_buffer + offset, &to_send_size);
-    offset += to_send_size;
-    if (status == TSI_INCOMPLETE_DATA) {
-      impl->adapter_buffer_size *= 2;
-      impl->adapter_buffer = static_cast<unsigned char*>(
-          gpr_realloc(impl->adapter_buffer, impl->adapter_buffer_size));
-    }
-  } while (status == TSI_INCOMPLETE_DATA);
-  if (status != TSI_OK) return status;
-  *bytes_to_send = impl->adapter_buffer;
-  *bytes_to_send_size = offset;
-
-  /* If handshake completes, create tsi_handshaker_result.  */
-  if (tsi_handshaker_is_in_progress(impl->wrapped)) {
-    *handshaker_result = nullptr;
-  } else {
-    size_t unused_bytes_size = received_bytes_size - bytes_consumed;
-    const unsigned char* unused_bytes =
-        unused_bytes_size == 0 ? nullptr : received_bytes + bytes_consumed;
-    status = tsi_adapter_create_handshaker_result(
-        impl->wrapped, unused_bytes, unused_bytes_size, handshaker_result);
-    if (status == TSI_OK) {
-      impl->base.handshaker_result_created = true;
-      impl->wrapped = nullptr;
-    }
-  }
-  return status;
-}
-
-static const tsi_handshaker_vtable handshaker_vtable = {
-    adapter_get_bytes_to_send_to_peer,
-    adapter_process_bytes_from_peer,
-    adapter_get_result,
-    adapter_extract_peer,
-    adapter_create_frame_protector,
-    adapter_destroy,
-    adapter_next,
-};
-
-tsi_handshaker* tsi_create_adapter_handshaker(tsi_handshaker* wrapped) {
-  GPR_ASSERT(wrapped != nullptr);
-  tsi_adapter_handshaker* impl =
-      static_cast<tsi_adapter_handshaker*>(gpr_zalloc(sizeof(*impl)));
-  impl->base.vtable = &handshaker_vtable;
-  impl->wrapped = wrapped;
-  impl->adapter_buffer_size = TSI_ADAPTER_INITIAL_BUFFER_SIZE;
-  impl->adapter_buffer =
-      static_cast<unsigned char*>(gpr_malloc(impl->adapter_buffer_size));
-  return &impl->base;
-}
-
-tsi_handshaker* tsi_adapter_handshaker_get_wrapped(tsi_handshaker* adapter) {
-  if (adapter == nullptr) return nullptr;
-  tsi_adapter_handshaker* impl =
-      reinterpret_cast<tsi_adapter_handshaker*>(adapter);
-  return impl->wrapped;
-}
diff --git a/src/core/tsi/transport_security_adapter.h b/src/core/tsi/transport_security_adapter.h
deleted file mode 100644
index f83ecc5..0000000
--- a/src/core/tsi/transport_security_adapter.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- *
- * Copyright 2017 gRPC authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#ifndef GRPC_CORE_TSI_TRANSPORT_SECURITY_ADAPTER_H
-#define GRPC_CORE_TSI_TRANSPORT_SECURITY_ADAPTER_H
-
-#include <grpc/support/port_platform.h>
-
-#include "src/core/tsi/transport_security_interface.h"
-
-/* Create a tsi handshaker that takes an implementation of old interface and
-   converts into an implementation of new interface. In the old interface,
-   there are get_bytes_to_send_to_peer, process_bytes_from_peer, get_result,
-   extract_peer, and create_frame_protector. In the new interface, only next
-   method is needed. See transport_security_interface.h for details. Note that
-   this tsi adapter handshaker is temporary. It will be removed once TSI has
-   been fully migrated to the new interface.
-   Ownership of input tsi_handshaker is transferred to this new adapter.  */
-tsi_handshaker* tsi_create_adapter_handshaker(tsi_handshaker* wrapped);
-
-/* Given a tsi adapter handshaker, return the original wrapped handshaker. The
-   adapter still owns the wrapped handshaker which should not be destroyed by
-   the caller. */
-tsi_handshaker* tsi_adapter_handshaker_get_wrapped(tsi_handshaker* adapter);
-
-#endif /* GRPC_CORE_TSI_TRANSPORT_SECURITY_ADAPTER_H */
diff --git a/src/core/tsi/transport_security_interface.h b/src/core/tsi/transport_security_interface.h
index 8c10866..7a0cdc3 100644
--- a/src/core/tsi/transport_security_interface.h
+++ b/src/core/tsi/transport_security_interface.h
@@ -42,7 +42,8 @@
   TSI_PROTOCOL_FAILURE = 10,
   TSI_HANDSHAKE_IN_PROGRESS = 11,
   TSI_OUT_OF_RESOURCES = 12,
-  TSI_ASYNC = 13
+  TSI_ASYNC = 13,
+  TSI_HANDSHAKE_SHUTDOWN = 14,
 } tsi_result;
 
 typedef enum {
@@ -332,6 +333,8 @@
    ------------------------------------------------------------------------   */
 typedef struct tsi_handshaker tsi_handshaker;
 
+/* TODO(jiangtaoli2016): Cleans up deprecated methods when we are ready. */
+
 /* TO BE DEPRECATED SOON. Use tsi_handshaker_next instead.
    Gets bytes that need to be sent to the peer.
    - bytes is the buffer that will be written with the data to be sent to the
@@ -440,6 +443,13 @@
     size_t* bytes_to_send_size, tsi_handshaker_result** handshaker_result,
     tsi_handshaker_on_next_done_cb cb, void* user_data);
 
+/* This method shuts down a TSI handshake that is in progress.
+ *
+ * This method will be invoked when TSI handshake should be terminated before
+ * being finished in order to free any resources being used.
+ */
+void tsi_handshaker_shutdown(tsi_handshaker* self);
+
 /* This method releases the tsi_handshaker object. After this method is called,
    no other method can be called on the object.  */
 void tsi_handshaker_destroy(tsi_handshaker* self);
diff --git a/src/cpp/common/version_cc.cc b/src/cpp/common/version_cc.cc
index d669ea2..54cd207 100644
--- a/src/cpp/common/version_cc.cc
+++ b/src/cpp/common/version_cc.cc
@@ -22,5 +22,5 @@
 #include <grpcpp/grpcpp.h>
 
 namespace grpc {
-grpc::string Version() { return "1.12.0-dev"; }
+grpc::string Version() { return "1.13.0-dev"; }
 }  // namespace grpc
diff --git a/src/cpp/server/load_reporter/load_data_store.cc b/src/cpp/server/load_reporter/load_data_store.cc
new file mode 100644
index 0000000..70f12c1
--- /dev/null
+++ b/src/cpp/server/load_reporter/load_data_store.cc
@@ -0,0 +1,273 @@
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <cstdlib>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+#include "src/cpp/server/load_reporter/load_data_store.h"
+
+namespace grpc {
+namespace load_reporter {
+
+// Some helper functions.
+namespace {
+
+// Given a map from type K to a set of value type V, finds the set associated
+// with the given key and erases the value from the set. If the set becomes
+// empty, also erases the key-set pair. Returns true if the value is erased
+// successfully.
+template <typename K, typename V>
+bool UnorderedMapOfSetEraseKeyValue(std::unordered_map<K, std::set<V>>& map,
+                                    const K& key, const V& value) {
+  auto it = map.find(key);
+  if (it != map.end()) {
+    size_t erased = it->second.erase(value);
+    if (it->second.size() == 0) {
+      map.erase(it);
+    }
+    return erased;
+  }
+  return false;
+};
+
+// Given a map from type K to a set of value type V, removes the given key and
+// the associated set, and returns the set. Returns an empty set if the key is
+// not found.
+template <typename K, typename V>
+std::set<V> UnorderedMapOfSetExtract(std::unordered_map<K, std::set<V>>& map,
+                                     const K& key) {
+  auto it = map.find(key);
+  if (it != map.end()) {
+    auto set = std::move(it->second);
+    map.erase(it);
+    return set;
+  }
+  return {};
+};
+
+// From a non-empty container, returns a pointer to a random element.
+template <typename C>
+const typename C::value_type* RandomElement(const C& container) {
+  GPR_ASSERT(!container.empty());
+  auto it = container.begin();
+  std::advance(it, std::rand() % container.size());
+  return &(*it);
+}
+
+}  // namespace
+
+void PerBalancerStore::MergeRow(const LoadRecordKey& key,
+                                const LoadRecordValue& value) {
+  // During suspension, the load data received will be dropped.
+  if (!suspended_) {
+    load_record_map_[key].MergeFrom(value);
+    gpr_log(GPR_DEBUG,
+            "[PerBalancerStore %p] Load data merged (Key: %s, Value: %s).",
+            this, key.ToString().c_str(), value.ToString().c_str());
+  } else {
+    gpr_log(GPR_DEBUG,
+            "[PerBalancerStore %p] Load data dropped (Key: %s, Value: %s).",
+            this, key.ToString().c_str(), value.ToString().c_str());
+  }
+  // We always keep track of num_calls_in_progress_, so that when this
+  // store is resumed, we still have a correct value of
+  // num_calls_in_progress_.
+  GPR_ASSERT(static_cast<int64_t>(num_calls_in_progress_) +
+                 value.GetNumCallsInProgressDelta() >=
+             0);
+  num_calls_in_progress_ += value.GetNumCallsInProgressDelta();
+}
+
+void PerBalancerStore::Suspend() {
+  suspended_ = true;
+  load_record_map_.clear();
+  gpr_log(GPR_DEBUG, "[PerBalancerStore %p] Suspended.", this);
+}
+
+void PerBalancerStore::Resume() {
+  suspended_ = false;
+  gpr_log(GPR_DEBUG, "[PerBalancerStore %p] Resumed.", this);
+}
+
+uint64_t PerBalancerStore::GetNumCallsInProgressForReport() {
+  GPR_ASSERT(!suspended_);
+  last_reported_num_calls_in_progress_ = num_calls_in_progress_;
+  return num_calls_in_progress_;
+}
+
+void PerHostStore::ReportStreamCreated(const grpc::string& lb_id,
+                                       const grpc::string& load_key) {
+  GPR_ASSERT(lb_id != kInvalidLbId);
+  SetUpForNewLbId(lb_id, load_key);
+  // Prior to this one, there was no load balancer receiving report, so we may
+  // have unassigned orphaned stores to assign to this new balancer.
+  // TODO(juanlishen): If the load key of this new stream is the same with
+  // some previously adopted orphan store, we may want to take the orphan to
+  // this stream. Need to discuss with LB team.
+  if (assigned_stores_.size() == 1) {
+    for (const auto& p : per_balancer_stores_) {
+      const grpc::string& other_lb_id = p.first;
+      const std::unique_ptr<PerBalancerStore>& orphaned_store = p.second;
+      if (other_lb_id != lb_id) {
+        orphaned_store->Resume();
+        AssignOrphanedStore(orphaned_store.get(), lb_id);
+      }
+    }
+  }
+  // The first connected balancer will adopt the kInvalidLbId.
+  if (per_balancer_stores_.size() == 1) {
+    SetUpForNewLbId(kInvalidLbId, "");
+    ReportStreamClosed(kInvalidLbId);
+  }
+}
+
+void PerHostStore::ReportStreamClosed(const grpc::string& lb_id) {
+  auto it_store_for_gone_lb = per_balancer_stores_.find(lb_id);
+  GPR_ASSERT(it_store_for_gone_lb != per_balancer_stores_.end());
+  // Remove this closed stream from our records.
+  GPR_ASSERT(UnorderedMapOfSetEraseKeyValue(
+      load_key_to_receiving_lb_ids_, it_store_for_gone_lb->second->load_key(),
+      lb_id));
+  std::set<PerBalancerStore*> orphaned_stores =
+      UnorderedMapOfSetExtract(assigned_stores_, lb_id);
+  // The stores that were assigned to this balancer are orphaned now. They
+  // should be re-assigned to other balancers which are still receiving reports.
+  for (PerBalancerStore* orphaned_store : orphaned_stores) {
+    const grpc::string* new_receiver = nullptr;
+    auto it = load_key_to_receiving_lb_ids_.find(orphaned_store->load_key());
+    if (it != load_key_to_receiving_lb_ids_.end()) {
+      // First, try to pick from the active balancers with the same load key.
+      new_receiver = RandomElement(it->second);
+    } else if (!assigned_stores_.empty()) {
+      // If failed, pick from all the remaining active balancers.
+      new_receiver = &(RandomElement(assigned_stores_)->first);
+    }
+    if (new_receiver != nullptr) {
+      AssignOrphanedStore(orphaned_store, *new_receiver);
+    } else {
+      // Load data for an LB ID that can't be assigned to any stream should
+      // be dropped.
+      orphaned_store->Suspend();
+    }
+  }
+}
+
+PerBalancerStore* PerHostStore::FindPerBalancerStore(
+    const grpc::string& lb_id) const {
+  return per_balancer_stores_.find(lb_id) != per_balancer_stores_.end()
+             ? per_balancer_stores_.find(lb_id)->second.get()
+             : nullptr;
+}
+
+const std::set<PerBalancerStore*>* PerHostStore::GetAssignedStores(
+    const grpc::string& lb_id) const {
+  auto it = assigned_stores_.find(lb_id);
+  if (it == assigned_stores_.end()) return nullptr;
+  return &(it->second);
+}
+
+void PerHostStore::AssignOrphanedStore(PerBalancerStore* orphaned_store,
+                                       const grpc::string& new_receiver) {
+  auto it = assigned_stores_.find(new_receiver);
+  GPR_ASSERT(it != assigned_stores_.end());
+  it->second.insert(orphaned_store);
+  gpr_log(GPR_INFO,
+          "[PerHostStore %p] Re-assigned orphaned store (%p) with original LB"
+          " ID of %s to new receiver %s",
+          this, orphaned_store, orphaned_store->lb_id().c_str(),
+          new_receiver.c_str());
+}
+
+void PerHostStore::SetUpForNewLbId(const grpc::string& lb_id,
+                                   const grpc::string& load_key) {
+  // The top-level caller (i.e., LoadReportService) should guarantee the
+  // lb_id is unique for each reporting stream.
+  GPR_ASSERT(per_balancer_stores_.find(lb_id) == per_balancer_stores_.end());
+  GPR_ASSERT(assigned_stores_.find(lb_id) == assigned_stores_.end());
+  load_key_to_receiving_lb_ids_[load_key].insert(lb_id);
+  std::unique_ptr<PerBalancerStore> per_balancer_store(
+      new PerBalancerStore(lb_id, load_key));
+  assigned_stores_[lb_id] = {per_balancer_store.get()};
+  per_balancer_stores_[lb_id] = std::move(per_balancer_store);
+}
+
+PerBalancerStore* LoadDataStore::FindPerBalancerStore(
+    const string& hostname, const string& lb_id) const {
+  auto it = per_host_stores_.find(hostname);
+  if (it != per_host_stores_.end()) {
+    const PerHostStore& per_host_store = it->second;
+    return per_host_store.FindPerBalancerStore(lb_id);
+  } else {
+    return nullptr;
+  }
+}
+
+void LoadDataStore::MergeRow(const grpc::string& hostname,
+                             const LoadRecordKey& key,
+                             const LoadRecordValue& value) {
+  PerBalancerStore* per_balancer_store =
+      FindPerBalancerStore(hostname, key.lb_id());
+  if (per_balancer_store != nullptr) {
+    per_balancer_store->MergeRow(key, value);
+    return;
+  }
+  // Unknown LB ID. Track it until its number of in-progress calls drops to
+  // zero.
+  int64_t in_progress_delta = value.GetNumCallsInProgressDelta();
+  if (in_progress_delta != 0) {
+    auto it_tracker = unknown_balancer_id_trackers_.find(key.lb_id());
+    if (it_tracker == unknown_balancer_id_trackers_.end()) {
+      gpr_log(
+          GPR_DEBUG,
+          "[LoadDataStore %p] Start tracking unknown balancer (lb_id_: %s).",
+          this, key.lb_id().c_str());
+      unknown_balancer_id_trackers_.insert(
+          {key.lb_id(), static_cast<uint64_t>(in_progress_delta)});
+    } else if ((it_tracker->second += in_progress_delta) == 0) {
+      unknown_balancer_id_trackers_.erase(it_tracker);
+      gpr_log(GPR_DEBUG,
+              "[LoadDataStore %p] Stop tracking unknown balancer (lb_id_: %s).",
+              this, key.lb_id().c_str());
+    }
+  }
+}
+
+const std::set<PerBalancerStore*>* LoadDataStore::GetAssignedStores(
+    const grpc::string& hostname, const grpc::string& lb_id) {
+  auto it = per_host_stores_.find(hostname);
+  if (it == per_host_stores_.end()) return nullptr;
+  return it->second.GetAssignedStores(lb_id);
+}
+
+void LoadDataStore::ReportStreamCreated(const grpc::string& hostname,
+                                        const grpc::string& lb_id,
+                                        const grpc::string& load_key) {
+  per_host_stores_[hostname].ReportStreamCreated(lb_id, load_key);
+}
+
+void LoadDataStore::ReportStreamClosed(const grpc::string& hostname,
+                                       const grpc::string& lb_id) {
+  auto it_per_host_store = per_host_stores_.find(hostname);
+  GPR_ASSERT(it_per_host_store != per_host_stores_.end());
+  it_per_host_store->second.ReportStreamClosed(lb_id);
+}
+
+}  // namespace load_reporter
+}  // namespace grpc
diff --git a/src/cpp/server/load_reporter/load_data_store.h b/src/cpp/server/load_reporter/load_data_store.h
new file mode 100644
index 0000000..feb8b2f
--- /dev/null
+++ b/src/cpp/server/load_reporter/load_data_store.h
@@ -0,0 +1,339 @@
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_SRC_CPP_SERVER_LOAD_REPORTER_LOAD_DATA_STORE_H
+#define GRPC_SRC_CPP_SERVER_LOAD_REPORTER_LOAD_DATA_STORE_H
+
+#include <grpc/support/port_platform.h>
+
+#include <memory>
+#include <set>
+#include <unordered_map>
+
+#include <grpc/support/log.h>
+#include <grpcpp/impl/codegen/config.h>
+
+namespace grpc {
+namespace load_reporter {
+
+constexpr char kInvalidLbId[] = "<INVALID_LBID_238dsb234890rb>";
+constexpr uint8_t kLbIdLen = 8;
+
+// The load data storage is organized in hierarchy. The LoadDataStore is the
+// top-level data store. In LoadDataStore, for each host we keep a
+// PerHostStore, in which for each balancer we keep a PerBalancerStore. Each
+// PerBalancerStore maintains a map of load records, mapping from LoadRecordKey
+// to LoadRecordValue. The LoadRecordValue contains a map of customized call
+// metrics, mapping from a call metric name to the CallMetricValue.
+
+// The value of a customized call metric.
+class CallMetricValue {
+ public:
+  explicit CallMetricValue(uint64_t num_calls = 0,
+                           double total_metric_value = 0)
+      : num_calls_(num_calls), total_metric_value_(total_metric_value) {}
+
+  void MergeFrom(CallMetricValue other) {
+    num_calls_ += other.num_calls_;
+    total_metric_value_ += other.total_metric_value_;
+  }
+
+  // Getters.
+  uint64_t num_calls() const { return num_calls_; }
+  double total_metric_value() const { return total_metric_value_; }
+
+ private:
+  // The number of calls that finished with this metric.
+  uint64_t num_calls_ = 0;
+  // The sum of metric values across all the calls that finished with this
+  // metric.
+  double total_metric_value_ = 0;
+};
+
+// The key of a load record.
+class LoadRecordKey {
+ public:
+  explicit LoadRecordKey(grpc::string lb_id, grpc::string lb_tag,
+                         grpc::string user_id, grpc::string client_ip_hex)
+      : lb_id_(std::move(lb_id)),
+        lb_tag_(std::move(lb_tag)),
+        user_id_(std::move(user_id)),
+        client_ip_hex_(std::move(client_ip_hex)) {}
+
+  grpc::string ToString() const {
+    return "[lb_id_=" + lb_id_ + ", lb_tag_=" + lb_tag_ +
+           ", user_id_=" + user_id_ + ", client_ip_hex_=" + client_ip_hex_ +
+           "]";
+  }
+
+  bool operator==(const LoadRecordKey& other) const {
+    return lb_id_ == other.lb_id_ && lb_tag_ == other.lb_tag_ &&
+           user_id_ == other.user_id_ && client_ip_hex_ == other.client_ip_hex_;
+  }
+
+  // Getters.
+  const grpc::string& lb_id() const { return lb_id_; }
+  const grpc::string& lb_tag() const { return lb_tag_; }
+  const grpc::string& user_id() const { return user_id_; }
+  const grpc::string& client_ip_hex() const { return client_ip_hex_; }
+
+  struct Hasher {
+    void hash_combine(size_t* seed, const grpc::string& k) const {
+      *seed ^= std::hash<grpc::string>()(k) + 0x9e3779b9 + (*seed << 6) +
+               (*seed >> 2);
+    }
+
+    size_t operator()(const LoadRecordKey& k) const {
+      size_t h = 0;
+      hash_combine(&h, k.lb_id_);
+      hash_combine(&h, k.lb_tag_);
+      hash_combine(&h, k.user_id_);
+      hash_combine(&h, k.client_ip_hex_);
+      return h;
+    }
+  };
+
+ private:
+  grpc::string lb_id_;
+  grpc::string lb_tag_;
+  grpc::string user_id_;
+  grpc::string client_ip_hex_;
+};
+
+// The value of a load record.
+class LoadRecordValue {
+ public:
+  explicit LoadRecordValue(uint64_t start_count = 0, uint64_t ok_count = 0,
+                           uint64_t error_count = 0, double bytes_sent = 0,
+                           double bytes_recv = 0, double latency_ms = 0)
+      : start_count_(start_count),
+        ok_count_(ok_count),
+        error_count_(error_count),
+        bytes_sent_(bytes_sent),
+        bytes_recv_(bytes_recv),
+        latency_ms_(latency_ms) {}
+
+  void MergeFrom(const LoadRecordValue& other) {
+    start_count_ += other.start_count_;
+    ok_count_ += other.ok_count_;
+    error_count_ += other.error_count_;
+    bytes_sent_ += other.bytes_sent_;
+    bytes_recv_ += other.bytes_recv_;
+    latency_ms_ += other.latency_ms_;
+    for (const auto& p : other.call_metrics_) {
+      const grpc::string& key = p.first;
+      const CallMetricValue& value = p.second;
+      call_metrics_[key].MergeFrom(value);
+    }
+  }
+
+  int64_t GetNumCallsInProgressDelta() const {
+    return static_cast<int64_t>(start_count_ - ok_count_ - error_count_);
+  }
+
+  grpc::string ToString() const {
+    return "[start_count_=" + grpc::to_string(start_count_) +
+           ", ok_count_=" + grpc::to_string(ok_count_) +
+           ", error_count_=" + grpc::to_string(error_count_) +
+           ", bytes_sent_=" + grpc::to_string(bytes_sent_) +
+           ", bytes_recv_=" + grpc::to_string(bytes_recv_) +
+           ", latency_ms_=" + grpc::to_string(latency_ms_) + "]";
+  }
+
+  bool InsertCallMetric(const grpc::string& metric_name,
+                        const CallMetricValue& metric_value) {
+    return call_metrics_.insert({metric_name, metric_value}).second;
+  }
+
+  // Getters.
+  uint64_t start_count() const { return start_count_; }
+  uint64_t ok_count() const { return ok_count_; }
+  uint64_t error_count() const { return error_count_; }
+  double bytes_sent() const { return bytes_sent_; }
+  double bytes_recv() const { return bytes_recv_; }
+  double latency_ms() const { return latency_ms_; }
+  const std::unordered_map<grpc::string, CallMetricValue>& call_metrics()
+      const {
+    return call_metrics_;
+  }
+
+ private:
+  uint64_t start_count_ = 0;
+  uint64_t ok_count_ = 0;
+  uint64_t error_count_ = 0;
+  double bytes_sent_ = 0;
+  double bytes_recv_ = 0;
+  double latency_ms_ = 0;
+  std::unordered_map<grpc::string, CallMetricValue> call_metrics_;
+};
+
+// Stores the data associated with a particular LB ID.
+class PerBalancerStore {
+ public:
+  using LoadRecordMap =
+      std::unordered_map<LoadRecordKey, LoadRecordValue, LoadRecordKey::Hasher>;
+
+  PerBalancerStore(grpc::string lb_id, grpc::string load_key)
+      : lb_id_(std::move(lb_id)), load_key_(std::move(load_key)) {}
+
+  // Merge a load record with the given key and value if the store is not
+  // suspended.
+  void MergeRow(const LoadRecordKey& key, const LoadRecordValue& value);
+
+  // Suspend this store, so that no detailed load data will be recorded.
+  void Suspend();
+  // Resume this store from suspension.
+  void Resume();
+  // Is this store suspended or not?
+  bool IsSuspended() const { return suspended_; }
+
+  bool IsNumCallsInProgressChangedSinceLastReport() const {
+    return num_calls_in_progress_ != last_reported_num_calls_in_progress_;
+  }
+
+  uint64_t GetNumCallsInProgressForReport();
+
+  grpc::string ToString() {
+    return "[PerBalancerStore lb_id_=" + lb_id_ + " load_key_=" + load_key_ +
+           "]";
+  }
+
+  void ClearLoadRecordMap() { load_record_map_.clear(); }
+
+  // Getters.
+  const grpc::string& lb_id() const { return lb_id_; }
+  const grpc::string& load_key() const { return load_key_; }
+  const LoadRecordMap& load_record_map() const { return load_record_map_; }
+
+ private:
+  grpc::string lb_id_;
+  // TODO(juanlishen): Use bytestring protobuf type?
+  grpc::string load_key_;
+  LoadRecordMap load_record_map_;
+  uint64_t num_calls_in_progress_ = 0;
+  uint64_t last_reported_num_calls_in_progress_ = 0;
+  bool suspended_ = false;
+};
+
+// Stores the data associated with a particular host.
+class PerHostStore {
+ public:
+  // When a report stream is created, a PerBalancerStore is created for the
+  // LB ID (guaranteed unique) associated with that stream. If it is the only
+  // active store, adopt all the orphaned stores. If it is the first created
+  // store, adopt the store of kInvalidLbId.
+  void ReportStreamCreated(const grpc::string& lb_id,
+                           const grpc::string& load_key);
+
+  // When a report stream is closed, the PerBalancerStores assigned to the
+  // associate LB ID need to be re-assigned to other active balancers,
+  // ideally with the same load key. If there is no active balancer, we have
+  // to suspend those stores and drop the incoming load data until they are
+  // resumed.
+  void ReportStreamClosed(const grpc::string& lb_id);
+
+  // Returns null if not found. Caller doesn't own the returned store.
+  PerBalancerStore* FindPerBalancerStore(const grpc::string& lb_id) const;
+
+  // Returns null if lb_id is not found. The returned pointer points to the
+  // underlying data structure, which is not owned by the caller.
+  const std::set<PerBalancerStore*>* GetAssignedStores(
+      const grpc::string& lb_id) const;
+
+ private:
+  // Creates a PerBalancerStore for the given LB ID, assigns the store to
+  // itself, and records the LB ID to the load key.
+  void SetUpForNewLbId(const grpc::string& lb_id, const grpc::string& load_key);
+
+  void AssignOrphanedStore(PerBalancerStore* orphaned_store,
+                           const grpc::string& new_receiver);
+
+  std::unordered_map<grpc::string, std::set<grpc::string>>
+      load_key_to_receiving_lb_ids_;
+
+  // Key: LB ID. The key set includes all the LB IDs that have been
+  // allocated for reporting streams so far.
+  // Value: the unique pointer to the PerBalancerStore of the LB ID.
+  std::unordered_map<grpc::string, std::unique_ptr<PerBalancerStore>>
+      per_balancer_stores_;
+
+  // Key: LB ID. The key set includes the LB IDs of the balancers that are
+  // currently receiving report.
+  // Value: the set of raw pointers to the PerBalancerStores assigned to the LB
+  // ID. Note that the sets in assigned_stores_ form a division of the value set
+  // of per_balancer_stores_.
+  std::unordered_map<grpc::string, std::set<PerBalancerStore*>>
+      assigned_stores_;
+};
+
+// Thread-unsafe two-level bookkeeper of all the load data.
+// Note: We never remove any store objects from this class, as per the
+// current spec. That's because premature removal of the store objects
+// may lead to loss of critical information, e.g., mapping from lb_id to
+// load_key, and the number of in-progress calls. Such loss will cause
+// information inconsistency when the balancer is re-connected. Keeping
+// all the stores should be fine for PerHostStore, since we assume there
+// should only be a few hostnames. But it's a potential problem for
+// PerBalancerStore.
+class LoadDataStore {
+ public:
+  // Returns null if not found. Caller doesn't own the returned store.
+  PerBalancerStore* FindPerBalancerStore(const grpc::string& hostname,
+                                         const grpc::string& lb_id) const;
+
+  // Returns null if hostname or lb_id is not found. The returned pointer points
+  // to the underlying data structure, which is not owned by the caller.
+  const std::set<PerBalancerStore*>* GetAssignedStores(const string& hostname,
+                                                       const string& lb_id);
+
+  // If a PerBalancerStore can be found by the hostname and LB ID in
+  // LoadRecordKey, the load data will be merged to that store. Otherwise,
+  // only track the number of the in-progress calls for this unknown LB ID.
+  void MergeRow(const grpc::string& hostname, const LoadRecordKey& key,
+                const LoadRecordValue& value);
+
+  // Is the given lb_id a tracked unknown LB ID (i.e., the LB ID was associated
+  // with some received load data but unknown to this load data store)?
+  bool IsTrackedUnknownBalancerId(const grpc::string& lb_id) const {
+    return unknown_balancer_id_trackers_.find(lb_id) !=
+           unknown_balancer_id_trackers_.end();
+  }
+
+  // Wrapper around PerHostStore::ReportStreamCreated.
+  void ReportStreamCreated(const grpc::string& hostname,
+                           const grpc::string& lb_id,
+                           const grpc::string& load_key);
+
+  // Wrapper around PerHostStore::ReportStreamClosed.
+  void ReportStreamClosed(const grpc::string& hostname,
+                          const grpc::string& lb_id);
+
+ private:
+  // Buffered data that was fetched from Census but hasn't been sent to
+  // balancer. We need to keep this data ourselves because Census will
+  // delete the data once it's returned.
+  std::unordered_map<grpc::string, PerHostStore> per_host_stores_;
+
+  // Tracks the number of in-progress calls for each unknown LB ID.
+  std::unordered_map<grpc::string, uint64_t> unknown_balancer_id_trackers_;
+};
+
+}  // namespace load_reporter
+}  // namespace grpc
+
+#endif  // GRPC_SRC_CPP_SERVER_LOAD_REPORTER_LOAD_DATA_STORE_H
diff --git a/src/csharp/Grpc.Core.Testing/TestServerCallContext.cs b/src/csharp/Grpc.Core.Testing/TestServerCallContext.cs
new file mode 100644
index 0000000..5418417
--- /dev/null
+++ b/src/csharp/Grpc.Core.Testing/TestServerCallContext.cs
@@ -0,0 +1,58 @@
+#region Copyright notice and license
+
+// Copyright 2015 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#endregion
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+
+namespace Grpc.Core.Testing
+{
+    /// <summary>
+    /// Creates test doubles for <c>ServerCallContext</c>.
+    /// </summary>
+    public static class TestServerCallContext
+    {
+        /// <summary>
+        /// Creates a test double for <c>ServerCallContext</c>. Only for testing.
+        /// Note: experimental API that can change or be removed without any prior notice.
+        /// </summary>
+        public static ServerCallContext Create(string method, string host, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken,
+            string peer, AuthContext authContext, ContextPropagationToken contextPropagationToken,
+            Func<Metadata, Task> writeHeadersFunc, Func<WriteOptions> writeOptionsGetter, Action<WriteOptions> writeOptionsSetter)
+        {
+            return new ServerCallContext(null, method, host, deadline, requestHeaders, cancellationToken,
+                writeHeadersFunc, new WriteOptionsHolder(writeOptionsGetter, writeOptionsSetter),
+                () => peer, () => authContext, () => contextPropagationToken);
+        }
+
+        private class WriteOptionsHolder : IHasWriteOptions
+        {
+            Func<WriteOptions> writeOptionsGetter;
+            Action<WriteOptions> writeOptionsSetter;
+
+            public WriteOptionsHolder(Func<WriteOptions> writeOptionsGetter, Action<WriteOptions> writeOptionsSetter)
+            {
+                this.writeOptionsGetter = writeOptionsGetter;
+                this.writeOptionsSetter = writeOptionsSetter;
+            }
+
+            public WriteOptions WriteOptions { get => writeOptionsGetter(); set => writeOptionsSetter(value); }
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/ServerCallContext.cs b/src/csharp/Grpc.Core/ServerCallContext.cs
index c63a4c4..74a7dea 100644
--- a/src/csharp/Grpc.Core/ServerCallContext.cs
+++ b/src/csharp/Grpc.Core/ServerCallContext.cs
@@ -36,14 +36,25 @@
         private readonly Metadata requestHeaders;
         private readonly CancellationToken cancellationToken;
         private readonly Metadata responseTrailers = new Metadata();
+        private readonly Func<Metadata, Task> writeHeadersFunc;
+        private readonly IHasWriteOptions writeOptionsHolder;
+        private readonly Lazy<AuthContext> authContext;
+        private readonly Func<string> testingOnlyPeerGetter;
+        private readonly Func<AuthContext> testingOnlyAuthContextGetter;
+        private readonly Func<ContextPropagationToken> testingOnlyContextPropagationTokenFactory;
 
         private Status status = Status.DefaultSuccess;
-        private Func<Metadata, Task> writeHeadersFunc;
-        private IHasWriteOptions writeOptionsHolder;
-        private Lazy<AuthContext> authContext;
 
         internal ServerCallContext(CallSafeHandle callHandle, string method, string host, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken,
             Func<Metadata, Task> writeHeadersFunc, IHasWriteOptions writeOptionsHolder)
+            : this(callHandle, method, host, deadline, requestHeaders, cancellationToken, writeHeadersFunc, writeOptionsHolder, null, null, null)
+        {
+        }
+
+        // Additional constructor params should be used for testing only
+        internal ServerCallContext(CallSafeHandle callHandle, string method, string host, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken,
+            Func<Metadata, Task> writeHeadersFunc, IHasWriteOptions writeOptionsHolder,
+            Func<string> testingOnlyPeerGetter, Func<AuthContext> testingOnlyAuthContextGetter, Func<ContextPropagationToken> testingOnlyContextPropagationTokenFactory)
         {
             this.callHandle = callHandle;
             this.method = method;
@@ -54,6 +65,9 @@
             this.writeHeadersFunc = writeHeadersFunc;
             this.writeOptionsHolder = writeOptionsHolder;
             this.authContext = new Lazy<AuthContext>(GetAuthContextEager);
+            this.testingOnlyPeerGetter = testingOnlyPeerGetter;
+            this.testingOnlyAuthContextGetter = testingOnlyAuthContextGetter;
+            this.testingOnlyContextPropagationTokenFactory = testingOnlyContextPropagationTokenFactory;
         }
 
         /// <summary>
@@ -73,6 +87,10 @@
         /// </summary>
         public ContextPropagationToken CreatePropagationToken(ContextPropagationOptions options = null)
         {
+            if (testingOnlyContextPropagationTokenFactory != null)
+            {
+                return testingOnlyContextPropagationTokenFactory();
+            }
             return new ContextPropagationToken(callHandle, deadline, cancellationToken, options);
         }
             
@@ -99,6 +117,10 @@
         {
             get
             {
+                if (testingOnlyPeerGetter != null)
+                {
+                    return testingOnlyPeerGetter();
+                }
                 // Getting the peer lazily is fine as the native call is guaranteed
                 // not to be disposed before user-supplied server side handler returns.
                 // Most users won't need to read this field anyway.
@@ -182,6 +204,10 @@
         {
             get
             {
+                if (testingOnlyAuthContextGetter != null)
+                {
+                    return testingOnlyAuthContextGetter();
+                }
                 return authContext.Value;
             }
         }
@@ -198,7 +224,7 @@
     /// <summary>
     /// Allows sharing write options between ServerCallContext and other objects.
     /// </summary>
-    public interface IHasWriteOptions
+    internal interface IHasWriteOptions
     {
         /// <summary>
         /// Gets or sets the write options.
diff --git a/src/csharp/Grpc.Core/Version.csproj.include b/src/csharp/Grpc.Core/Version.csproj.include
index 6e28c11..f5d63b7 100755
--- a/src/csharp/Grpc.Core/Version.csproj.include
+++ b/src/csharp/Grpc.Core/Version.csproj.include
@@ -1,7 +1,7 @@
 <!-- This file is generated -->
 <Project>
   <PropertyGroup>
-    <GrpcCsharpVersion>1.12.0-dev</GrpcCsharpVersion>
+    <GrpcCsharpVersion>1.13.0-dev</GrpcCsharpVersion>
     <GoogleProtobufVersion>3.5.1</GoogleProtobufVersion>
   </PropertyGroup>
 </Project>
diff --git a/src/csharp/Grpc.Core/VersionInfo.cs b/src/csharp/Grpc.Core/VersionInfo.cs
index 06a0396..87eddda 100644
--- a/src/csharp/Grpc.Core/VersionInfo.cs
+++ b/src/csharp/Grpc.Core/VersionInfo.cs
@@ -33,11 +33,11 @@
         /// <summary>
         /// Current <c>AssemblyFileVersion</c> of gRPC C# assemblies
         /// </summary>
-        public const string CurrentAssemblyFileVersion = "1.12.0.0";
+        public const string CurrentAssemblyFileVersion = "1.13.0.0";
 
         /// <summary>
         /// Current version of gRPC C#
         /// </summary>
-        public const string CurrentVersion = "1.12.0-dev";
+        public const string CurrentVersion = "1.13.0-dev";
     }
 }
diff --git a/src/csharp/Grpc.Examples/Math.cs b/src/csharp/Grpc.Examples/Math.cs
index e5b76f8..4c3879f 100644
--- a/src/csharp/Grpc.Examples/Math.cs
+++ b/src/csharp/Grpc.Examples/Math.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: math/math.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: math/math.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -47,6 +49,7 @@
   #region Messages
   public sealed partial class DivArgs : pb::IMessage<DivArgs> {
     private static readonly pb::MessageParser<DivArgs> _parser = new pb::MessageParser<DivArgs>(() => new DivArgs());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<DivArgs> Parser { get { return _parser; } }
 
@@ -71,6 +74,7 @@
     public DivArgs(DivArgs other) : this() {
       dividend_ = other.dividend_;
       divisor_ = other.divisor_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -115,7 +119,7 @@
       }
       if (Dividend != other.Dividend) return false;
       if (Divisor != other.Divisor) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -123,6 +127,9 @@
       int hash = 1;
       if (Dividend != 0L) hash ^= Dividend.GetHashCode();
       if (Divisor != 0L) hash ^= Divisor.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -141,6 +148,9 @@
         output.WriteRawTag(16);
         output.WriteInt64(Divisor);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -152,6 +162,9 @@
       if (Divisor != 0L) {
         size += 1 + pb::CodedOutputStream.ComputeInt64Size(Divisor);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -166,6 +179,7 @@
       if (other.Divisor != 0L) {
         Divisor = other.Divisor;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -174,7 +188,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             Dividend = input.ReadInt64();
@@ -192,6 +206,7 @@
 
   public sealed partial class DivReply : pb::IMessage<DivReply> {
     private static readonly pb::MessageParser<DivReply> _parser = new pb::MessageParser<DivReply>(() => new DivReply());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<DivReply> Parser { get { return _parser; } }
 
@@ -216,6 +231,7 @@
     public DivReply(DivReply other) : this() {
       quotient_ = other.quotient_;
       remainder_ = other.remainder_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -260,7 +276,7 @@
       }
       if (Quotient != other.Quotient) return false;
       if (Remainder != other.Remainder) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -268,6 +284,9 @@
       int hash = 1;
       if (Quotient != 0L) hash ^= Quotient.GetHashCode();
       if (Remainder != 0L) hash ^= Remainder.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -286,6 +305,9 @@
         output.WriteRawTag(16);
         output.WriteInt64(Remainder);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -297,6 +319,9 @@
       if (Remainder != 0L) {
         size += 1 + pb::CodedOutputStream.ComputeInt64Size(Remainder);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -311,6 +336,7 @@
       if (other.Remainder != 0L) {
         Remainder = other.Remainder;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -319,7 +345,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             Quotient = input.ReadInt64();
@@ -337,6 +363,7 @@
 
   public sealed partial class FibArgs : pb::IMessage<FibArgs> {
     private static readonly pb::MessageParser<FibArgs> _parser = new pb::MessageParser<FibArgs>(() => new FibArgs());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<FibArgs> Parser { get { return _parser; } }
 
@@ -360,6 +387,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public FibArgs(FibArgs other) : this() {
       limit_ = other.limit_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -392,13 +420,16 @@
         return true;
       }
       if (Limit != other.Limit) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (Limit != 0L) hash ^= Limit.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -413,6 +444,9 @@
         output.WriteRawTag(8);
         output.WriteInt64(Limit);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -421,6 +455,9 @@
       if (Limit != 0L) {
         size += 1 + pb::CodedOutputStream.ComputeInt64Size(Limit);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -432,6 +469,7 @@
       if (other.Limit != 0L) {
         Limit = other.Limit;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -440,7 +478,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             Limit = input.ReadInt64();
@@ -454,6 +492,7 @@
 
   public sealed partial class Num : pb::IMessage<Num> {
     private static readonly pb::MessageParser<Num> _parser = new pb::MessageParser<Num>(() => new Num());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<Num> Parser { get { return _parser; } }
 
@@ -477,6 +516,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public Num(Num other) : this() {
       num_ = other.num_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -509,13 +549,16 @@
         return true;
       }
       if (Num_ != other.Num_) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (Num_ != 0L) hash ^= Num_.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -530,6 +573,9 @@
         output.WriteRawTag(8);
         output.WriteInt64(Num_);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -538,6 +584,9 @@
       if (Num_ != 0L) {
         size += 1 + pb::CodedOutputStream.ComputeInt64Size(Num_);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -549,6 +598,7 @@
       if (other.Num_ != 0L) {
         Num_ = other.Num_;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -557,7 +607,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             Num_ = input.ReadInt64();
@@ -571,6 +621,7 @@
 
   public sealed partial class FibReply : pb::IMessage<FibReply> {
     private static readonly pb::MessageParser<FibReply> _parser = new pb::MessageParser<FibReply>(() => new FibReply());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<FibReply> Parser { get { return _parser; } }
 
@@ -594,6 +645,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public FibReply(FibReply other) : this() {
       count_ = other.count_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -626,13 +678,16 @@
         return true;
       }
       if (Count != other.Count) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (Count != 0L) hash ^= Count.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -647,6 +702,9 @@
         output.WriteRawTag(8);
         output.WriteInt64(Count);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -655,6 +713,9 @@
       if (Count != 0L) {
         size += 1 + pb::CodedOutputStream.ComputeInt64Size(Count);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -666,6 +727,7 @@
       if (other.Count != 0L) {
         Count = other.Count;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -674,7 +736,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             Count = input.ReadInt64();
diff --git a/src/csharp/Grpc.HealthCheck/Health.cs b/src/csharp/Grpc.HealthCheck/Health.cs
index b9880d9..a90f261 100644
--- a/src/csharp/Grpc.HealthCheck/Health.cs
+++ b/src/csharp/Grpc.HealthCheck/Health.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: grpc/health/v1/health.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: grpc/health/v1/health.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -29,7 +31,9 @@
             "ZXJ2aW5nU3RhdHVzEgsKB1VOS05PV04QABILCgdTRVJWSU5HEAESDwoLTk9U",
             "X1NFUlZJTkcQAjJaCgZIZWFsdGgSUAoFQ2hlY2sSIi5ncnBjLmhlYWx0aC52",
             "MS5IZWFsdGhDaGVja1JlcXVlc3QaIy5ncnBjLmhlYWx0aC52MS5IZWFsdGhD",
-            "aGVja1Jlc3BvbnNlQhGqAg5HcnBjLkhlYWx0aC5WMWIGcHJvdG8z"));
+            "aGVja1Jlc3BvbnNlQmEKEWlvLmdycGMuaGVhbHRoLnYxQgtIZWFsdGhQcm90",
+            "b1ABWixnb29nbGUuZ29sYW5nLm9yZy9ncnBjL2hlYWx0aC9ncnBjX2hlYWx0",
+            "aF92MaoCDkdycGMuSGVhbHRoLlYxYgZwcm90bzM="));
       descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
           new pbr::FileDescriptor[] { },
           new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] {
@@ -43,6 +47,7 @@
   #region Messages
   public sealed partial class HealthCheckRequest : pb::IMessage<HealthCheckRequest> {
     private static readonly pb::MessageParser<HealthCheckRequest> _parser = new pb::MessageParser<HealthCheckRequest>(() => new HealthCheckRequest());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<HealthCheckRequest> Parser { get { return _parser; } }
 
@@ -66,6 +71,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public HealthCheckRequest(HealthCheckRequest other) : this() {
       service_ = other.service_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -98,13 +104,16 @@
         return true;
       }
       if (Service != other.Service) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (Service.Length != 0) hash ^= Service.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -119,6 +128,9 @@
         output.WriteRawTag(10);
         output.WriteString(Service);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -127,6 +139,9 @@
       if (Service.Length != 0) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(Service);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -138,6 +153,7 @@
       if (other.Service.Length != 0) {
         Service = other.Service;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -146,7 +162,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             Service = input.ReadString();
@@ -160,6 +176,7 @@
 
   public sealed partial class HealthCheckResponse : pb::IMessage<HealthCheckResponse> {
     private static readonly pb::MessageParser<HealthCheckResponse> _parser = new pb::MessageParser<HealthCheckResponse>(() => new HealthCheckResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<HealthCheckResponse> Parser { get { return _parser; } }
 
@@ -183,6 +200,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public HealthCheckResponse(HealthCheckResponse other) : this() {
       status_ = other.status_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -215,13 +233,16 @@
         return true;
       }
       if (Status != other.Status) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (Status != 0) hash ^= Status.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -236,6 +257,9 @@
         output.WriteRawTag(8);
         output.WriteEnum((int) Status);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -244,6 +268,9 @@
       if (Status != 0) {
         size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Status);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -255,6 +282,7 @@
       if (other.Status != 0) {
         Status = other.Status;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -263,7 +291,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             status_ = (global::Grpc.Health.V1.HealthCheckResponse.Types.ServingStatus) input.ReadEnum();
diff --git a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs
index 1d80bcd..a26f483 100644
--- a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs
+++ b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs
@@ -3,7 +3,7 @@
 //     source: grpc/health/v1/health.proto
 // </auto-generated>
 // Original file comments:
-// Copyright 2015 gRPC authors.
+// Copyright 2015 The gRPC Authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,6 +17,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
+// The canonical version of this proto can be found at
+// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto
+//
 #pragma warning disable 1591
 #region Designer generated code
 
diff --git a/src/csharp/Grpc.IntegrationTesting/BenchmarkService.cs b/src/csharp/Grpc.IntegrationTesting/BenchmarkService.cs
index 11d34c6..bc8c103 100644
--- a/src/csharp/Grpc.IntegrationTesting/BenchmarkService.cs
+++ b/src/csharp/Grpc.IntegrationTesting/BenchmarkService.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: src/proto/grpc/testing/benchmark_service.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: src/proto/grpc/testing/benchmark_service.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
diff --git a/src/csharp/Grpc.IntegrationTesting/Control.cs b/src/csharp/Grpc.IntegrationTesting/Control.cs
index 8795728..f3284a5 100644
--- a/src/csharp/Grpc.IntegrationTesting/Control.cs
+++ b/src/csharp/Grpc.IntegrationTesting/Control.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: src/proto/grpc/testing/control.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: src/proto/grpc/testing/control.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -167,6 +169,7 @@
   /// </summary>
   public sealed partial class PoissonParams : pb::IMessage<PoissonParams> {
     private static readonly pb::MessageParser<PoissonParams> _parser = new pb::MessageParser<PoissonParams>(() => new PoissonParams());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<PoissonParams> Parser { get { return _parser; } }
 
@@ -190,6 +193,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public PoissonParams(PoissonParams other) : this() {
       offeredLoad_ = other.offeredLoad_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -224,14 +228,17 @@
       if (ReferenceEquals(other, this)) {
         return true;
       }
-      if (OfferedLoad != other.OfferedLoad) return false;
-      return true;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(OfferedLoad, other.OfferedLoad)) return false;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
-      if (OfferedLoad != 0D) hash ^= OfferedLoad.GetHashCode();
+      if (OfferedLoad != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(OfferedLoad);
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -246,6 +253,9 @@
         output.WriteRawTag(9);
         output.WriteDouble(OfferedLoad);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -254,6 +264,9 @@
       if (OfferedLoad != 0D) {
         size += 1 + 8;
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -265,6 +278,7 @@
       if (other.OfferedLoad != 0D) {
         OfferedLoad = other.OfferedLoad;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -273,7 +287,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 9: {
             OfferedLoad = input.ReadDouble();
@@ -291,6 +305,7 @@
   /// </summary>
   public sealed partial class ClosedLoopParams : pb::IMessage<ClosedLoopParams> {
     private static readonly pb::MessageParser<ClosedLoopParams> _parser = new pb::MessageParser<ClosedLoopParams>(() => new ClosedLoopParams());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ClosedLoopParams> Parser { get { return _parser; } }
 
@@ -313,6 +328,7 @@
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public ClosedLoopParams(ClosedLoopParams other) : this() {
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -333,12 +349,15 @@
       if (ReferenceEquals(other, this)) {
         return true;
       }
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -349,11 +368,17 @@
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public void WriteTo(pb::CodedOutputStream output) {
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public int CalculateSize() {
       int size = 0;
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -362,6 +387,7 @@
       if (other == null) {
         return;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -370,7 +396,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
         }
       }
@@ -380,6 +406,7 @@
 
   public sealed partial class LoadParams : pb::IMessage<LoadParams> {
     private static readonly pb::MessageParser<LoadParams> _parser = new pb::MessageParser<LoadParams>(() => new LoadParams());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<LoadParams> Parser { get { return _parser; } }
 
@@ -411,6 +438,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -475,7 +503,7 @@
       if (!object.Equals(ClosedLoop, other.ClosedLoop)) return false;
       if (!object.Equals(Poisson, other.Poisson)) return false;
       if (LoadCase != other.LoadCase) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -484,6 +512,9 @@
       if (loadCase_ == LoadOneofCase.ClosedLoop) hash ^= ClosedLoop.GetHashCode();
       if (loadCase_ == LoadOneofCase.Poisson) hash ^= Poisson.GetHashCode();
       hash ^= (int) loadCase_;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -502,6 +533,9 @@
         output.WriteRawTag(18);
         output.WriteMessage(Poisson);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -513,6 +547,9 @@
       if (loadCase_ == LoadOneofCase.Poisson) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Poisson);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -536,6 +573,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -544,7 +582,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             global::Grpc.Testing.ClosedLoopParams subBuilder = new global::Grpc.Testing.ClosedLoopParams();
@@ -575,6 +613,7 @@
   /// </summary>
   public sealed partial class SecurityParams : pb::IMessage<SecurityParams> {
     private static readonly pb::MessageParser<SecurityParams> _parser = new pb::MessageParser<SecurityParams>(() => new SecurityParams());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<SecurityParams> Parser { get { return _parser; } }
 
@@ -600,6 +639,7 @@
       useTestCa_ = other.useTestCa_;
       serverHostOverride_ = other.serverHostOverride_;
       credType_ = other.credType_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -656,7 +696,7 @@
       if (UseTestCa != other.UseTestCa) return false;
       if (ServerHostOverride != other.ServerHostOverride) return false;
       if (CredType != other.CredType) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -665,6 +705,9 @@
       if (UseTestCa != false) hash ^= UseTestCa.GetHashCode();
       if (ServerHostOverride.Length != 0) hash ^= ServerHostOverride.GetHashCode();
       if (CredType.Length != 0) hash ^= CredType.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -687,6 +730,9 @@
         output.WriteRawTag(26);
         output.WriteString(CredType);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -701,6 +747,9 @@
       if (CredType.Length != 0) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(CredType);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -718,6 +767,7 @@
       if (other.CredType.Length != 0) {
         CredType = other.CredType;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -726,7 +776,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             UseTestCa = input.ReadBool();
@@ -748,6 +798,7 @@
 
   public sealed partial class ChannelArg : pb::IMessage<ChannelArg> {
     private static readonly pb::MessageParser<ChannelArg> _parser = new pb::MessageParser<ChannelArg>(() => new ChannelArg());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ChannelArg> Parser { get { return _parser; } }
 
@@ -780,6 +831,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -856,7 +908,7 @@
       if (StrValue != other.StrValue) return false;
       if (IntValue != other.IntValue) return false;
       if (ValueCase != other.ValueCase) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -866,6 +918,9 @@
       if (valueCase_ == ValueOneofCase.StrValue) hash ^= StrValue.GetHashCode();
       if (valueCase_ == ValueOneofCase.IntValue) hash ^= IntValue.GetHashCode();
       hash ^= (int) valueCase_;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -888,6 +943,9 @@
         output.WriteRawTag(24);
         output.WriteInt32(IntValue);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -902,6 +960,9 @@
       if (valueCase_ == ValueOneofCase.IntValue) {
         size += 1 + pb::CodedOutputStream.ComputeInt32Size(IntValue);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -922,6 +983,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -930,7 +992,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             Name = input.ReadString();
@@ -952,6 +1014,7 @@
 
   public sealed partial class ClientConfig : pb::IMessage<ClientConfig> {
     private static readonly pb::MessageParser<ClientConfig> _parser = new pb::MessageParser<ClientConfig>(() => new ClientConfig());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ClientConfig> Parser { get { return _parser; } }
 
@@ -991,6 +1054,7 @@
       threadsPerCq_ = other.threadsPerCq_;
       messagesPerStream_ = other.messagesPerStream_;
       useCoalesceApi_ = other.useCoalesceApi_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1244,7 +1308,7 @@
       if (ThreadsPerCq != other.ThreadsPerCq) return false;
       if (MessagesPerStream != other.MessagesPerStream) return false;
       if (UseCoalesceApi != other.UseCoalesceApi) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1267,6 +1331,9 @@
       if (ThreadsPerCq != 0) hash ^= ThreadsPerCq.GetHashCode();
       if (MessagesPerStream != 0) hash ^= MessagesPerStream.GetHashCode();
       if (UseCoalesceApi != false) hash ^= UseCoalesceApi.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1336,6 +1403,9 @@
         output.WriteRawTag(152, 1);
         output.WriteBool(UseCoalesceApi);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1386,6 +1456,9 @@
       if (UseCoalesceApi != false) {
         size += 2 + 1;
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1451,6 +1524,7 @@
       if (other.UseCoalesceApi != false) {
         UseCoalesceApi = other.UseCoalesceApi;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1459,7 +1533,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             serverTargets_.AddEntriesFrom(input, _repeated_serverTargets_codec);
@@ -1550,6 +1624,7 @@
 
   public sealed partial class ClientStatus : pb::IMessage<ClientStatus> {
     private static readonly pb::MessageParser<ClientStatus> _parser = new pb::MessageParser<ClientStatus>(() => new ClientStatus());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ClientStatus> Parser { get { return _parser; } }
 
@@ -1573,6 +1648,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public ClientStatus(ClientStatus other) : this() {
       Stats = other.stats_ != null ? other.Stats.Clone() : null;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1605,13 +1681,16 @@
         return true;
       }
       if (!object.Equals(Stats, other.Stats)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (stats_ != null) hash ^= Stats.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1626,6 +1705,9 @@
         output.WriteRawTag(10);
         output.WriteMessage(Stats);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1634,6 +1716,9 @@
       if (stats_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Stats);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1648,6 +1733,7 @@
         }
         Stats.MergeFrom(other.Stats);
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1656,7 +1742,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             if (stats_ == null) {
@@ -1676,6 +1762,7 @@
   /// </summary>
   public sealed partial class Mark : pb::IMessage<Mark> {
     private static readonly pb::MessageParser<Mark> _parser = new pb::MessageParser<Mark>(() => new Mark());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<Mark> Parser { get { return _parser; } }
 
@@ -1699,6 +1786,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public Mark(Mark other) : this() {
       reset_ = other.reset_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1734,13 +1822,16 @@
         return true;
       }
       if (Reset != other.Reset) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (Reset != false) hash ^= Reset.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1755,6 +1846,9 @@
         output.WriteRawTag(8);
         output.WriteBool(Reset);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1763,6 +1857,9 @@
       if (Reset != false) {
         size += 1 + 1;
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1774,6 +1871,7 @@
       if (other.Reset != false) {
         Reset = other.Reset;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1782,7 +1880,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             Reset = input.ReadBool();
@@ -1796,6 +1894,7 @@
 
   public sealed partial class ClientArgs : pb::IMessage<ClientArgs> {
     private static readonly pb::MessageParser<ClientArgs> _parser = new pb::MessageParser<ClientArgs>(() => new ClientArgs());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ClientArgs> Parser { get { return _parser; } }
 
@@ -1827,6 +1926,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1891,7 +1991,7 @@
       if (!object.Equals(Setup, other.Setup)) return false;
       if (!object.Equals(Mark, other.Mark)) return false;
       if (ArgtypeCase != other.ArgtypeCase) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1900,6 +2000,9 @@
       if (argtypeCase_ == ArgtypeOneofCase.Setup) hash ^= Setup.GetHashCode();
       if (argtypeCase_ == ArgtypeOneofCase.Mark) hash ^= Mark.GetHashCode();
       hash ^= (int) argtypeCase_;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1918,6 +2021,9 @@
         output.WriteRawTag(18);
         output.WriteMessage(Mark);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1929,6 +2035,9 @@
       if (argtypeCase_ == ArgtypeOneofCase.Mark) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Mark);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1952,6 +2061,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1960,7 +2070,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             global::Grpc.Testing.ClientConfig subBuilder = new global::Grpc.Testing.ClientConfig();
@@ -1988,6 +2098,7 @@
 
   public sealed partial class ServerConfig : pb::IMessage<ServerConfig> {
     private static readonly pb::MessageParser<ServerConfig> _parser = new pb::MessageParser<ServerConfig>(() => new ServerConfig());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ServerConfig> Parser { get { return _parser; } }
 
@@ -2021,6 +2132,7 @@
       threadsPerCq_ = other.threadsPerCq_;
       resourceQuotaSize_ = other.resourceQuotaSize_;
       channelArgs_ = other.channelArgs_.Clone();
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2198,7 +2310,7 @@
       if (ThreadsPerCq != other.ThreadsPerCq) return false;
       if (ResourceQuotaSize != other.ResourceQuotaSize) return false;
       if(!channelArgs_.Equals(other.channelArgs_)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2215,6 +2327,9 @@
       if (ThreadsPerCq != 0) hash ^= ThreadsPerCq.GetHashCode();
       if (ResourceQuotaSize != 0) hash ^= ResourceQuotaSize.GetHashCode();
       hash ^= channelArgs_.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -2263,6 +2378,9 @@
         output.WriteInt32(ResourceQuotaSize);
       }
       channelArgs_.WriteTo(output, _repeated_channelArgs_codec);
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2297,6 +2415,9 @@
         size += 2 + pb::CodedOutputStream.ComputeInt32Size(ResourceQuotaSize);
       }
       size += channelArgs_.CalculateSize(_repeated_channelArgs_codec);
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -2340,6 +2461,7 @@
         ResourceQuotaSize = other.ResourceQuotaSize;
       }
       channelArgs_.Add(other.channelArgs_);
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2348,7 +2470,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             serverType_ = (global::Grpc.Testing.ServerType) input.ReadEnum();
@@ -2409,6 +2531,7 @@
 
   public sealed partial class ServerArgs : pb::IMessage<ServerArgs> {
     private static readonly pb::MessageParser<ServerArgs> _parser = new pb::MessageParser<ServerArgs>(() => new ServerArgs());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ServerArgs> Parser { get { return _parser; } }
 
@@ -2440,6 +2563,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2504,7 +2628,7 @@
       if (!object.Equals(Setup, other.Setup)) return false;
       if (!object.Equals(Mark, other.Mark)) return false;
       if (ArgtypeCase != other.ArgtypeCase) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2513,6 +2637,9 @@
       if (argtypeCase_ == ArgtypeOneofCase.Setup) hash ^= Setup.GetHashCode();
       if (argtypeCase_ == ArgtypeOneofCase.Mark) hash ^= Mark.GetHashCode();
       hash ^= (int) argtypeCase_;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -2531,6 +2658,9 @@
         output.WriteRawTag(18);
         output.WriteMessage(Mark);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2542,6 +2672,9 @@
       if (argtypeCase_ == ArgtypeOneofCase.Mark) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Mark);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -2565,6 +2698,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2573,7 +2707,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             global::Grpc.Testing.ServerConfig subBuilder = new global::Grpc.Testing.ServerConfig();
@@ -2601,6 +2735,7 @@
 
   public sealed partial class ServerStatus : pb::IMessage<ServerStatus> {
     private static readonly pb::MessageParser<ServerStatus> _parser = new pb::MessageParser<ServerStatus>(() => new ServerStatus());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ServerStatus> Parser { get { return _parser; } }
 
@@ -2626,6 +2761,7 @@
       Stats = other.stats_ != null ? other.Stats.Clone() : null;
       port_ = other.port_;
       cores_ = other.cores_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2688,7 +2824,7 @@
       if (!object.Equals(Stats, other.Stats)) return false;
       if (Port != other.Port) return false;
       if (Cores != other.Cores) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2697,6 +2833,9 @@
       if (stats_ != null) hash ^= Stats.GetHashCode();
       if (Port != 0) hash ^= Port.GetHashCode();
       if (Cores != 0) hash ^= Cores.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -2719,6 +2858,9 @@
         output.WriteRawTag(24);
         output.WriteInt32(Cores);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2733,6 +2875,9 @@
       if (Cores != 0) {
         size += 1 + pb::CodedOutputStream.ComputeInt32Size(Cores);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -2753,6 +2898,7 @@
       if (other.Cores != 0) {
         Cores = other.Cores;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2761,7 +2907,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             if (stats_ == null) {
@@ -2786,6 +2932,7 @@
 
   public sealed partial class CoreRequest : pb::IMessage<CoreRequest> {
     private static readonly pb::MessageParser<CoreRequest> _parser = new pb::MessageParser<CoreRequest>(() => new CoreRequest());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<CoreRequest> Parser { get { return _parser; } }
 
@@ -2808,6 +2955,7 @@
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public CoreRequest(CoreRequest other) : this() {
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2828,12 +2976,15 @@
       if (ReferenceEquals(other, this)) {
         return true;
       }
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -2844,11 +2995,17 @@
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public void WriteTo(pb::CodedOutputStream output) {
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public int CalculateSize() {
       int size = 0;
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -2857,6 +3014,7 @@
       if (other == null) {
         return;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2865,7 +3023,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
         }
       }
@@ -2875,6 +3033,7 @@
 
   public sealed partial class CoreResponse : pb::IMessage<CoreResponse> {
     private static readonly pb::MessageParser<CoreResponse> _parser = new pb::MessageParser<CoreResponse>(() => new CoreResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<CoreResponse> Parser { get { return _parser; } }
 
@@ -2898,6 +3057,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public CoreResponse(CoreResponse other) : this() {
       cores_ = other.cores_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2933,13 +3093,16 @@
         return true;
       }
       if (Cores != other.Cores) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (Cores != 0) hash ^= Cores.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -2954,6 +3117,9 @@
         output.WriteRawTag(8);
         output.WriteInt32(Cores);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2962,6 +3128,9 @@
       if (Cores != 0) {
         size += 1 + pb::CodedOutputStream.ComputeInt32Size(Cores);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -2973,6 +3142,7 @@
       if (other.Cores != 0) {
         Cores = other.Cores;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2981,7 +3151,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             Cores = input.ReadInt32();
@@ -2995,6 +3165,7 @@
 
   public sealed partial class Void : pb::IMessage<Void> {
     private static readonly pb::MessageParser<Void> _parser = new pb::MessageParser<Void>(() => new Void());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<Void> Parser { get { return _parser; } }
 
@@ -3017,6 +3188,7 @@
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public Void(Void other) : this() {
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -3037,12 +3209,15 @@
       if (ReferenceEquals(other, this)) {
         return true;
       }
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -3053,11 +3228,17 @@
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public void WriteTo(pb::CodedOutputStream output) {
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public int CalculateSize() {
       int size = 0;
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -3066,6 +3247,7 @@
       if (other == null) {
         return;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -3074,7 +3256,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
         }
       }
@@ -3087,6 +3269,7 @@
   /// </summary>
   public sealed partial class Scenario : pb::IMessage<Scenario> {
     private static readonly pb::MessageParser<Scenario> _parser = new pb::MessageParser<Scenario>(() => new Scenario());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<Scenario> Parser { get { return _parser; } }
 
@@ -3117,6 +3300,7 @@
       warmupSeconds_ = other.warmupSeconds_;
       benchmarkSeconds_ = other.benchmarkSeconds_;
       spawnLocalWorkerCount_ = other.spawnLocalWorkerCount_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -3257,7 +3441,7 @@
       if (WarmupSeconds != other.WarmupSeconds) return false;
       if (BenchmarkSeconds != other.BenchmarkSeconds) return false;
       if (SpawnLocalWorkerCount != other.SpawnLocalWorkerCount) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -3271,6 +3455,9 @@
       if (WarmupSeconds != 0) hash ^= WarmupSeconds.GetHashCode();
       if (BenchmarkSeconds != 0) hash ^= BenchmarkSeconds.GetHashCode();
       if (SpawnLocalWorkerCount != 0) hash ^= SpawnLocalWorkerCount.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -3313,6 +3500,9 @@
         output.WriteRawTag(64);
         output.WriteInt32(SpawnLocalWorkerCount);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -3342,6 +3532,9 @@
       if (SpawnLocalWorkerCount != 0) {
         size += 1 + pb::CodedOutputStream.ComputeInt32Size(SpawnLocalWorkerCount);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -3380,6 +3573,7 @@
       if (other.SpawnLocalWorkerCount != 0) {
         SpawnLocalWorkerCount = other.SpawnLocalWorkerCount;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -3388,7 +3582,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             Name = input.ReadString();
@@ -3439,6 +3633,7 @@
   /// </summary>
   public sealed partial class Scenarios : pb::IMessage<Scenarios> {
     private static readonly pb::MessageParser<Scenarios> _parser = new pb::MessageParser<Scenarios>(() => new Scenarios());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<Scenarios> Parser { get { return _parser; } }
 
@@ -3462,6 +3657,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public Scenarios(Scenarios other) : this() {
       scenarios_ = other.scenarios_.Clone();
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -3493,13 +3689,16 @@
         return true;
       }
       if(!scenarios_.Equals(other.scenarios_)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       hash ^= scenarios_.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -3511,12 +3710,18 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public void WriteTo(pb::CodedOutputStream output) {
       scenarios_.WriteTo(output, _repeated_scenarios_codec);
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public int CalculateSize() {
       int size = 0;
       size += scenarios_.CalculateSize(_repeated_scenarios_codec);
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -3526,6 +3731,7 @@
         return;
       }
       scenarios_.Add(other.scenarios_);
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -3534,7 +3740,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             scenarios_.AddEntriesFrom(input, _repeated_scenarios_codec);
@@ -3552,6 +3758,7 @@
   /// </summary>
   public sealed partial class ScenarioResultSummary : pb::IMessage<ScenarioResultSummary> {
     private static readonly pb::MessageParser<ScenarioResultSummary> _parser = new pb::MessageParser<ScenarioResultSummary>(() => new ScenarioResultSummary());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ScenarioResultSummary> Parser { get { return _parser; } }
 
@@ -3592,6 +3799,7 @@
       serverPollsPerRequest_ = other.serverPollsPerRequest_;
       serverQueriesPerCpuSec_ = other.serverQueriesPerCpuSec_;
       clientQueriesPerCpuSec_ = other.clientQueriesPerCpuSec_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -3843,48 +4051,51 @@
       if (ReferenceEquals(other, this)) {
         return true;
       }
-      if (Qps != other.Qps) return false;
-      if (QpsPerServerCore != other.QpsPerServerCore) return false;
-      if (ServerSystemTime != other.ServerSystemTime) return false;
-      if (ServerUserTime != other.ServerUserTime) return false;
-      if (ClientSystemTime != other.ClientSystemTime) return false;
-      if (ClientUserTime != other.ClientUserTime) return false;
-      if (Latency50 != other.Latency50) return false;
-      if (Latency90 != other.Latency90) return false;
-      if (Latency95 != other.Latency95) return false;
-      if (Latency99 != other.Latency99) return false;
-      if (Latency999 != other.Latency999) return false;
-      if (ServerCpuUsage != other.ServerCpuUsage) return false;
-      if (SuccessfulRequestsPerSecond != other.SuccessfulRequestsPerSecond) return false;
-      if (FailedRequestsPerSecond != other.FailedRequestsPerSecond) return false;
-      if (ClientPollsPerRequest != other.ClientPollsPerRequest) return false;
-      if (ServerPollsPerRequest != other.ServerPollsPerRequest) return false;
-      if (ServerQueriesPerCpuSec != other.ServerQueriesPerCpuSec) return false;
-      if (ClientQueriesPerCpuSec != other.ClientQueriesPerCpuSec) return false;
-      return true;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(Qps, other.Qps)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(QpsPerServerCore, other.QpsPerServerCore)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(ServerSystemTime, other.ServerSystemTime)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(ServerUserTime, other.ServerUserTime)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(ClientSystemTime, other.ClientSystemTime)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(ClientUserTime, other.ClientUserTime)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(Latency50, other.Latency50)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(Latency90, other.Latency90)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(Latency95, other.Latency95)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(Latency99, other.Latency99)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(Latency999, other.Latency999)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(ServerCpuUsage, other.ServerCpuUsage)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(SuccessfulRequestsPerSecond, other.SuccessfulRequestsPerSecond)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(FailedRequestsPerSecond, other.FailedRequestsPerSecond)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(ClientPollsPerRequest, other.ClientPollsPerRequest)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(ServerPollsPerRequest, other.ServerPollsPerRequest)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(ServerQueriesPerCpuSec, other.ServerQueriesPerCpuSec)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(ClientQueriesPerCpuSec, other.ClientQueriesPerCpuSec)) return false;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
-      if (Qps != 0D) hash ^= Qps.GetHashCode();
-      if (QpsPerServerCore != 0D) hash ^= QpsPerServerCore.GetHashCode();
-      if (ServerSystemTime != 0D) hash ^= ServerSystemTime.GetHashCode();
-      if (ServerUserTime != 0D) hash ^= ServerUserTime.GetHashCode();
-      if (ClientSystemTime != 0D) hash ^= ClientSystemTime.GetHashCode();
-      if (ClientUserTime != 0D) hash ^= ClientUserTime.GetHashCode();
-      if (Latency50 != 0D) hash ^= Latency50.GetHashCode();
-      if (Latency90 != 0D) hash ^= Latency90.GetHashCode();
-      if (Latency95 != 0D) hash ^= Latency95.GetHashCode();
-      if (Latency99 != 0D) hash ^= Latency99.GetHashCode();
-      if (Latency999 != 0D) hash ^= Latency999.GetHashCode();
-      if (ServerCpuUsage != 0D) hash ^= ServerCpuUsage.GetHashCode();
-      if (SuccessfulRequestsPerSecond != 0D) hash ^= SuccessfulRequestsPerSecond.GetHashCode();
-      if (FailedRequestsPerSecond != 0D) hash ^= FailedRequestsPerSecond.GetHashCode();
-      if (ClientPollsPerRequest != 0D) hash ^= ClientPollsPerRequest.GetHashCode();
-      if (ServerPollsPerRequest != 0D) hash ^= ServerPollsPerRequest.GetHashCode();
-      if (ServerQueriesPerCpuSec != 0D) hash ^= ServerQueriesPerCpuSec.GetHashCode();
-      if (ClientQueriesPerCpuSec != 0D) hash ^= ClientQueriesPerCpuSec.GetHashCode();
+      if (Qps != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Qps);
+      if (QpsPerServerCore != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(QpsPerServerCore);
+      if (ServerSystemTime != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(ServerSystemTime);
+      if (ServerUserTime != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(ServerUserTime);
+      if (ClientSystemTime != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(ClientSystemTime);
+      if (ClientUserTime != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(ClientUserTime);
+      if (Latency50 != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Latency50);
+      if (Latency90 != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Latency90);
+      if (Latency95 != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Latency95);
+      if (Latency99 != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Latency99);
+      if (Latency999 != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Latency999);
+      if (ServerCpuUsage != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(ServerCpuUsage);
+      if (SuccessfulRequestsPerSecond != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(SuccessfulRequestsPerSecond);
+      if (FailedRequestsPerSecond != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(FailedRequestsPerSecond);
+      if (ClientPollsPerRequest != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(ClientPollsPerRequest);
+      if (ServerPollsPerRequest != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(ServerPollsPerRequest);
+      if (ServerQueriesPerCpuSec != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(ServerQueriesPerCpuSec);
+      if (ClientQueriesPerCpuSec != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(ClientQueriesPerCpuSec);
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -3967,6 +4178,9 @@
         output.WriteRawTag(145, 1);
         output.WriteDouble(ClientQueriesPerCpuSec);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -4026,6 +4240,9 @@
       if (ClientQueriesPerCpuSec != 0D) {
         size += 2 + 8;
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -4088,6 +4305,7 @@
       if (other.ClientQueriesPerCpuSec != 0D) {
         ClientQueriesPerCpuSec = other.ClientQueriesPerCpuSec;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -4096,7 +4314,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 9: {
             Qps = input.ReadDouble();
@@ -4181,6 +4399,7 @@
   /// </summary>
   public sealed partial class ScenarioResult : pb::IMessage<ScenarioResult> {
     private static readonly pb::MessageParser<ScenarioResult> _parser = new pb::MessageParser<ScenarioResult>(() => new ScenarioResult());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ScenarioResult> Parser { get { return _parser; } }
 
@@ -4212,6 +4431,7 @@
       clientSuccess_ = other.clientSuccess_.Clone();
       serverSuccess_ = other.serverSuccess_.Clone();
       requestResults_ = other.requestResults_.Clone();
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -4358,7 +4578,7 @@
       if(!clientSuccess_.Equals(other.clientSuccess_)) return false;
       if(!serverSuccess_.Equals(other.serverSuccess_)) return false;
       if(!requestResults_.Equals(other.requestResults_)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -4373,6 +4593,9 @@
       hash ^= clientSuccess_.GetHashCode();
       hash ^= serverSuccess_.GetHashCode();
       hash ^= requestResults_.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -4401,6 +4624,9 @@
       clientSuccess_.WriteTo(output, _repeated_clientSuccess_codec);
       serverSuccess_.WriteTo(output, _repeated_serverSuccess_codec);
       requestResults_.WriteTo(output, _repeated_requestResults_codec);
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -4421,6 +4647,9 @@
       size += clientSuccess_.CalculateSize(_repeated_clientSuccess_codec);
       size += serverSuccess_.CalculateSize(_repeated_serverSuccess_codec);
       size += requestResults_.CalculateSize(_repeated_requestResults_codec);
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -4453,6 +4682,7 @@
       clientSuccess_.Add(other.clientSuccess_);
       serverSuccess_.Add(other.serverSuccess_);
       requestResults_.Add(other.requestResults_);
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -4461,7 +4691,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             if (scenario_ == null) {
diff --git a/src/csharp/Grpc.IntegrationTesting/CoreStats/Stats.cs b/src/csharp/Grpc.IntegrationTesting/CoreStats/Stats.cs
index 380294e..4ff56cd 100644
--- a/src/csharp/Grpc.IntegrationTesting/CoreStats/Stats.cs
+++ b/src/csharp/Grpc.IntegrationTesting/CoreStats/Stats.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: grpc/core/stats.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: grpc/core/stats.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -43,6 +45,7 @@
   #region Messages
   public sealed partial class Bucket : pb::IMessage<Bucket> {
     private static readonly pb::MessageParser<Bucket> _parser = new pb::MessageParser<Bucket>(() => new Bucket());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<Bucket> Parser { get { return _parser; } }
 
@@ -67,6 +70,7 @@
     public Bucket(Bucket other) : this() {
       start_ = other.start_;
       count_ = other.count_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -109,16 +113,19 @@
       if (ReferenceEquals(other, this)) {
         return true;
       }
-      if (Start != other.Start) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(Start, other.Start)) return false;
       if (Count != other.Count) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
-      if (Start != 0D) hash ^= Start.GetHashCode();
+      if (Start != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Start);
       if (Count != 0UL) hash ^= Count.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -137,6 +144,9 @@
         output.WriteRawTag(16);
         output.WriteUInt64(Count);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -148,6 +158,9 @@
       if (Count != 0UL) {
         size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Count);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -162,6 +175,7 @@
       if (other.Count != 0UL) {
         Count = other.Count;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -170,7 +184,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 9: {
             Start = input.ReadDouble();
@@ -188,6 +202,7 @@
 
   public sealed partial class Histogram : pb::IMessage<Histogram> {
     private static readonly pb::MessageParser<Histogram> _parser = new pb::MessageParser<Histogram>(() => new Histogram());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<Histogram> Parser { get { return _parser; } }
 
@@ -211,6 +226,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public Histogram(Histogram other) : this() {
       buckets_ = other.buckets_.Clone();
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -242,13 +258,16 @@
         return true;
       }
       if(!buckets_.Equals(other.buckets_)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       hash ^= buckets_.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -260,12 +279,18 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public void WriteTo(pb::CodedOutputStream output) {
       buckets_.WriteTo(output, _repeated_buckets_codec);
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public int CalculateSize() {
       int size = 0;
       size += buckets_.CalculateSize(_repeated_buckets_codec);
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -275,6 +300,7 @@
         return;
       }
       buckets_.Add(other.buckets_);
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -283,7 +309,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             buckets_.AddEntriesFrom(input, _repeated_buckets_codec);
@@ -297,6 +323,7 @@
 
   public sealed partial class Metric : pb::IMessage<Metric> {
     private static readonly pb::MessageParser<Metric> _parser = new pb::MessageParser<Metric>(() => new Metric());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<Metric> Parser { get { return _parser; } }
 
@@ -329,6 +356,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -405,7 +433,7 @@
       if (Count != other.Count) return false;
       if (!object.Equals(Histogram, other.Histogram)) return false;
       if (ValueCase != other.ValueCase) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -415,6 +443,9 @@
       if (valueCase_ == ValueOneofCase.Count) hash ^= Count.GetHashCode();
       if (valueCase_ == ValueOneofCase.Histogram) hash ^= Histogram.GetHashCode();
       hash ^= (int) valueCase_;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -437,6 +468,9 @@
         output.WriteRawTag(90);
         output.WriteMessage(Histogram);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -451,6 +485,9 @@
       if (valueCase_ == ValueOneofCase.Histogram) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Histogram);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -474,6 +511,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -482,7 +520,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             Name = input.ReadString();
@@ -509,6 +547,7 @@
 
   public sealed partial class Stats : pb::IMessage<Stats> {
     private static readonly pb::MessageParser<Stats> _parser = new pb::MessageParser<Stats>(() => new Stats());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<Stats> Parser { get { return _parser; } }
 
@@ -532,6 +571,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public Stats(Stats other) : this() {
       metrics_ = other.metrics_.Clone();
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -563,13 +603,16 @@
         return true;
       }
       if(!metrics_.Equals(other.metrics_)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       hash ^= metrics_.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -581,12 +624,18 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public void WriteTo(pb::CodedOutputStream output) {
       metrics_.WriteTo(output, _repeated_metrics_codec);
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public int CalculateSize() {
       int size = 0;
       size += metrics_.CalculateSize(_repeated_metrics_codec);
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -596,6 +645,7 @@
         return;
       }
       metrics_.Add(other.metrics_);
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -604,7 +654,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             metrics_.AddEntriesFrom(input, _repeated_metrics_codec);
diff --git a/src/csharp/Grpc.IntegrationTesting/EchoMessages.cs b/src/csharp/Grpc.IntegrationTesting/EchoMessages.cs
index 9581ade..39c3d76 100644
--- a/src/csharp/Grpc.IntegrationTesting/EchoMessages.cs
+++ b/src/csharp/Grpc.IntegrationTesting/EchoMessages.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: src/proto/grpc/testing/echo_messages.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: src/proto/grpc/testing/echo_messages.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -63,6 +65,7 @@
   /// </summary>
   public sealed partial class DebugInfo : pb::IMessage<DebugInfo> {
     private static readonly pb::MessageParser<DebugInfo> _parser = new pb::MessageParser<DebugInfo>(() => new DebugInfo());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<DebugInfo> Parser { get { return _parser; } }
 
@@ -87,6 +90,7 @@
     public DebugInfo(DebugInfo other) : this() {
       stackEntries_ = other.stackEntries_.Clone();
       detail_ = other.detail_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -130,7 +134,7 @@
       }
       if(!stackEntries_.Equals(other.stackEntries_)) return false;
       if (Detail != other.Detail) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -138,6 +142,9 @@
       int hash = 1;
       hash ^= stackEntries_.GetHashCode();
       if (Detail.Length != 0) hash ^= Detail.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -153,6 +160,9 @@
         output.WriteRawTag(18);
         output.WriteString(Detail);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -162,6 +172,9 @@
       if (Detail.Length != 0) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(Detail);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -174,6 +187,7 @@
       if (other.Detail.Length != 0) {
         Detail = other.Detail;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -182,7 +196,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             stackEntries_.AddEntriesFrom(input, _repeated_stackEntries_codec);
@@ -203,6 +217,7 @@
   /// </summary>
   public sealed partial class ErrorStatus : pb::IMessage<ErrorStatus> {
     private static readonly pb::MessageParser<ErrorStatus> _parser = new pb::MessageParser<ErrorStatus>(() => new ErrorStatus());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ErrorStatus> Parser { get { return _parser; } }
 
@@ -228,6 +243,7 @@
       code_ = other.code_;
       errorMessage_ = other.errorMessage_;
       binaryErrorDetails_ = other.binaryErrorDetails_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -284,7 +300,7 @@
       if (Code != other.Code) return false;
       if (ErrorMessage != other.ErrorMessage) return false;
       if (BinaryErrorDetails != other.BinaryErrorDetails) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -293,6 +309,9 @@
       if (Code != 0) hash ^= Code.GetHashCode();
       if (ErrorMessage.Length != 0) hash ^= ErrorMessage.GetHashCode();
       if (BinaryErrorDetails.Length != 0) hash ^= BinaryErrorDetails.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -315,6 +334,9 @@
         output.WriteRawTag(26);
         output.WriteString(BinaryErrorDetails);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -329,6 +351,9 @@
       if (BinaryErrorDetails.Length != 0) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(BinaryErrorDetails);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -346,6 +371,7 @@
       if (other.BinaryErrorDetails.Length != 0) {
         BinaryErrorDetails = other.BinaryErrorDetails;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -354,7 +380,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             Code = input.ReadInt32();
@@ -376,6 +402,7 @@
 
   public sealed partial class RequestParams : pb::IMessage<RequestParams> {
     private static readonly pb::MessageParser<RequestParams> _parser = new pb::MessageParser<RequestParams>(() => new RequestParams());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<RequestParams> Parser { get { return _parser; } }
 
@@ -413,6 +440,7 @@
       binaryErrorDetails_ = other.binaryErrorDetails_;
       ExpectedError = other.expectedError_ != null ? other.ExpectedError.Clone() : null;
       serverSleepUs_ = other.serverSleepUs_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -622,7 +650,7 @@
       if (BinaryErrorDetails != other.BinaryErrorDetails) return false;
       if (!object.Equals(ExpectedError, other.ExpectedError)) return false;
       if (ServerSleepUs != other.ServerSleepUs) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -643,6 +671,9 @@
       if (BinaryErrorDetails.Length != 0) hash ^= BinaryErrorDetails.GetHashCode();
       if (expectedError_ != null) hash ^= ExpectedError.GetHashCode();
       if (ServerSleepUs != 0) hash ^= ServerSleepUs.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -713,6 +744,9 @@
         output.WriteRawTag(120);
         output.WriteInt32(ServerSleepUs);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -763,6 +797,9 @@
       if (ServerSleepUs != 0) {
         size += 1 + pb::CodedOutputStream.ComputeInt32Size(ServerSleepUs);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -822,6 +859,7 @@
       if (other.ServerSleepUs != 0) {
         ServerSleepUs = other.ServerSleepUs;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -830,7 +868,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             EchoDeadline = input.ReadBool();
@@ -906,6 +944,7 @@
 
   public sealed partial class EchoRequest : pb::IMessage<EchoRequest> {
     private static readonly pb::MessageParser<EchoRequest> _parser = new pb::MessageParser<EchoRequest>(() => new EchoRequest());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<EchoRequest> Parser { get { return _parser; } }
 
@@ -930,6 +969,7 @@
     public EchoRequest(EchoRequest other) : this() {
       message_ = other.message_;
       Param = other.param_ != null ? other.Param.Clone() : null;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -974,7 +1014,7 @@
       }
       if (Message != other.Message) return false;
       if (!object.Equals(Param, other.Param)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -982,6 +1022,9 @@
       int hash = 1;
       if (Message.Length != 0) hash ^= Message.GetHashCode();
       if (param_ != null) hash ^= Param.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1000,6 +1043,9 @@
         output.WriteRawTag(18);
         output.WriteMessage(Param);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1011,6 +1057,9 @@
       if (param_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Param);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1028,6 +1077,7 @@
         }
         Param.MergeFrom(other.Param);
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1036,7 +1086,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             Message = input.ReadString();
@@ -1057,6 +1107,7 @@
 
   public sealed partial class ResponseParams : pb::IMessage<ResponseParams> {
     private static readonly pb::MessageParser<ResponseParams> _parser = new pb::MessageParser<ResponseParams>(() => new ResponseParams());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ResponseParams> Parser { get { return _parser; } }
 
@@ -1082,6 +1133,7 @@
       requestDeadline_ = other.requestDeadline_;
       host_ = other.host_;
       peer_ = other.peer_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1138,7 +1190,7 @@
       if (RequestDeadline != other.RequestDeadline) return false;
       if (Host != other.Host) return false;
       if (Peer != other.Peer) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1147,6 +1199,9 @@
       if (RequestDeadline != 0L) hash ^= RequestDeadline.GetHashCode();
       if (Host.Length != 0) hash ^= Host.GetHashCode();
       if (Peer.Length != 0) hash ^= Peer.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1169,6 +1224,9 @@
         output.WriteRawTag(26);
         output.WriteString(Peer);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1183,6 +1241,9 @@
       if (Peer.Length != 0) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(Peer);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1200,6 +1261,7 @@
       if (other.Peer.Length != 0) {
         Peer = other.Peer;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1208,7 +1270,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             RequestDeadline = input.ReadInt64();
@@ -1230,6 +1292,7 @@
 
   public sealed partial class EchoResponse : pb::IMessage<EchoResponse> {
     private static readonly pb::MessageParser<EchoResponse> _parser = new pb::MessageParser<EchoResponse>(() => new EchoResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<EchoResponse> Parser { get { return _parser; } }
 
@@ -1254,6 +1317,7 @@
     public EchoResponse(EchoResponse other) : this() {
       message_ = other.message_;
       Param = other.param_ != null ? other.Param.Clone() : null;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1298,7 +1362,7 @@
       }
       if (Message != other.Message) return false;
       if (!object.Equals(Param, other.Param)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1306,6 +1370,9 @@
       int hash = 1;
       if (Message.Length != 0) hash ^= Message.GetHashCode();
       if (param_ != null) hash ^= Param.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1324,6 +1391,9 @@
         output.WriteRawTag(18);
         output.WriteMessage(Param);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1335,6 +1405,9 @@
       if (param_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Param);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1352,6 +1425,7 @@
         }
         Param.MergeFrom(other.Param);
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1360,7 +1434,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             Message = input.ReadString();
diff --git a/src/csharp/Grpc.IntegrationTesting/Empty.cs b/src/csharp/Grpc.IntegrationTesting/Empty.cs
index 24ffd61..0d4c28b 100644
--- a/src/csharp/Grpc.IntegrationTesting/Empty.cs
+++ b/src/csharp/Grpc.IntegrationTesting/Empty.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: src/proto/grpc/testing/empty.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: src/proto/grpc/testing/empty.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -45,6 +47,7 @@
   /// </summary>
   public sealed partial class Empty : pb::IMessage<Empty> {
     private static readonly pb::MessageParser<Empty> _parser = new pb::MessageParser<Empty>(() => new Empty());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<Empty> Parser { get { return _parser; } }
 
@@ -67,6 +70,7 @@
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public Empty(Empty other) : this() {
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -87,12 +91,15 @@
       if (ReferenceEquals(other, this)) {
         return true;
       }
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -103,11 +110,17 @@
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public void WriteTo(pb::CodedOutputStream output) {
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public int CalculateSize() {
       int size = 0;
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -116,6 +129,7 @@
       if (other == null) {
         return;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -124,7 +138,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
         }
       }
diff --git a/src/csharp/Grpc.IntegrationTesting/Messages.cs b/src/csharp/Grpc.IntegrationTesting/Messages.cs
index 278ef66..b5c93ba 100644
--- a/src/csharp/Grpc.IntegrationTesting/Messages.cs
+++ b/src/csharp/Grpc.IntegrationTesting/Messages.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: src/proto/grpc/testing/messages.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: src/proto/grpc/testing/messages.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -95,6 +97,7 @@
   /// </summary>
   public sealed partial class BoolValue : pb::IMessage<BoolValue> {
     private static readonly pb::MessageParser<BoolValue> _parser = new pb::MessageParser<BoolValue>(() => new BoolValue());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<BoolValue> Parser { get { return _parser; } }
 
@@ -118,6 +121,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public BoolValue(BoolValue other) : this() {
       value_ = other.value_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -153,13 +157,16 @@
         return true;
       }
       if (Value != other.Value) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (Value != false) hash ^= Value.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -174,6 +181,9 @@
         output.WriteRawTag(8);
         output.WriteBool(Value);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -182,6 +192,9 @@
       if (Value != false) {
         size += 1 + 1;
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -193,6 +206,7 @@
       if (other.Value != false) {
         Value = other.Value;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -201,7 +215,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             Value = input.ReadBool();
@@ -218,6 +232,7 @@
   /// </summary>
   public sealed partial class Payload : pb::IMessage<Payload> {
     private static readonly pb::MessageParser<Payload> _parser = new pb::MessageParser<Payload>(() => new Payload());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<Payload> Parser { get { return _parser; } }
 
@@ -242,6 +257,7 @@
     public Payload(Payload other) : this() {
       type_ = other.type_;
       body_ = other.body_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -293,7 +309,7 @@
       }
       if (Type != other.Type) return false;
       if (Body != other.Body) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -301,6 +317,9 @@
       int hash = 1;
       if (Type != 0) hash ^= Type.GetHashCode();
       if (Body.Length != 0) hash ^= Body.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -319,6 +338,9 @@
         output.WriteRawTag(18);
         output.WriteBytes(Body);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -330,6 +352,9 @@
       if (Body.Length != 0) {
         size += 1 + pb::CodedOutputStream.ComputeBytesSize(Body);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -344,6 +369,7 @@
       if (other.Body.Length != 0) {
         Body = other.Body;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -352,7 +378,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             type_ = (global::Grpc.Testing.PayloadType) input.ReadEnum();
@@ -374,6 +400,7 @@
   /// </summary>
   public sealed partial class EchoStatus : pb::IMessage<EchoStatus> {
     private static readonly pb::MessageParser<EchoStatus> _parser = new pb::MessageParser<EchoStatus>(() => new EchoStatus());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<EchoStatus> Parser { get { return _parser; } }
 
@@ -398,6 +425,7 @@
     public EchoStatus(EchoStatus other) : this() {
       code_ = other.code_;
       message_ = other.message_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -442,7 +470,7 @@
       }
       if (Code != other.Code) return false;
       if (Message != other.Message) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -450,6 +478,9 @@
       int hash = 1;
       if (Code != 0) hash ^= Code.GetHashCode();
       if (Message.Length != 0) hash ^= Message.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -468,6 +499,9 @@
         output.WriteRawTag(18);
         output.WriteString(Message);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -479,6 +513,9 @@
       if (Message.Length != 0) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(Message);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -493,6 +530,7 @@
       if (other.Message.Length != 0) {
         Message = other.Message;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -501,7 +539,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             Code = input.ReadInt32();
@@ -522,6 +560,7 @@
   /// </summary>
   public sealed partial class SimpleRequest : pb::IMessage<SimpleRequest> {
     private static readonly pb::MessageParser<SimpleRequest> _parser = new pb::MessageParser<SimpleRequest>(() => new SimpleRequest());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<SimpleRequest> Parser { get { return _parser; } }
 
@@ -552,6 +591,7 @@
       ResponseCompressed = other.responseCompressed_ != null ? other.ResponseCompressed.Clone() : null;
       ResponseStatus = other.responseStatus_ != null ? other.ResponseStatus.Clone() : null;
       ExpectCompressed = other.expectCompressed_ != null ? other.ExpectCompressed.Clone() : null;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -697,7 +737,7 @@
       if (!object.Equals(ResponseCompressed, other.ResponseCompressed)) return false;
       if (!object.Equals(ResponseStatus, other.ResponseStatus)) return false;
       if (!object.Equals(ExpectCompressed, other.ExpectCompressed)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -711,6 +751,9 @@
       if (responseCompressed_ != null) hash ^= ResponseCompressed.GetHashCode();
       if (responseStatus_ != null) hash ^= ResponseStatus.GetHashCode();
       if (expectCompressed_ != null) hash ^= ExpectCompressed.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -753,6 +796,9 @@
         output.WriteRawTag(66);
         output.WriteMessage(ExpectCompressed);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -782,6 +828,9 @@
       if (expectCompressed_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(ExpectCompressed);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -826,6 +875,7 @@
         }
         ExpectCompressed.MergeFrom(other.ExpectCompressed);
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -834,7 +884,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             responseType_ = (global::Grpc.Testing.PayloadType) input.ReadEnum();
@@ -891,6 +941,7 @@
   /// </summary>
   public sealed partial class SimpleResponse : pb::IMessage<SimpleResponse> {
     private static readonly pb::MessageParser<SimpleResponse> _parser = new pb::MessageParser<SimpleResponse>(() => new SimpleResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<SimpleResponse> Parser { get { return _parser; } }
 
@@ -916,6 +967,7 @@
       Payload = other.payload_ != null ? other.Payload.Clone() : null;
       username_ = other.username_;
       oauthScope_ = other.oauthScope_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -982,7 +1034,7 @@
       if (!object.Equals(Payload, other.Payload)) return false;
       if (Username != other.Username) return false;
       if (OauthScope != other.OauthScope) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -991,6 +1043,9 @@
       if (payload_ != null) hash ^= Payload.GetHashCode();
       if (Username.Length != 0) hash ^= Username.GetHashCode();
       if (OauthScope.Length != 0) hash ^= OauthScope.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1013,6 +1068,9 @@
         output.WriteRawTag(26);
         output.WriteString(OauthScope);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1027,6 +1085,9 @@
       if (OauthScope.Length != 0) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(OauthScope);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1047,6 +1108,7 @@
       if (other.OauthScope.Length != 0) {
         OauthScope = other.OauthScope;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1055,7 +1117,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             if (payload_ == null) {
@@ -1083,6 +1145,7 @@
   /// </summary>
   public sealed partial class StreamingInputCallRequest : pb::IMessage<StreamingInputCallRequest> {
     private static readonly pb::MessageParser<StreamingInputCallRequest> _parser = new pb::MessageParser<StreamingInputCallRequest>(() => new StreamingInputCallRequest());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<StreamingInputCallRequest> Parser { get { return _parser; } }
 
@@ -1107,6 +1170,7 @@
     public StreamingInputCallRequest(StreamingInputCallRequest other) : this() {
       Payload = other.payload_ != null ? other.Payload.Clone() : null;
       ExpectCompressed = other.expectCompressed_ != null ? other.ExpectCompressed.Clone() : null;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1160,7 +1224,7 @@
       }
       if (!object.Equals(Payload, other.Payload)) return false;
       if (!object.Equals(ExpectCompressed, other.ExpectCompressed)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1168,6 +1232,9 @@
       int hash = 1;
       if (payload_ != null) hash ^= Payload.GetHashCode();
       if (expectCompressed_ != null) hash ^= ExpectCompressed.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1186,6 +1253,9 @@
         output.WriteRawTag(18);
         output.WriteMessage(ExpectCompressed);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1197,6 +1267,9 @@
       if (expectCompressed_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(ExpectCompressed);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1217,6 +1290,7 @@
         }
         ExpectCompressed.MergeFrom(other.ExpectCompressed);
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1225,7 +1299,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             if (payload_ == null) {
@@ -1252,6 +1326,7 @@
   /// </summary>
   public sealed partial class StreamingInputCallResponse : pb::IMessage<StreamingInputCallResponse> {
     private static readonly pb::MessageParser<StreamingInputCallResponse> _parser = new pb::MessageParser<StreamingInputCallResponse>(() => new StreamingInputCallResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<StreamingInputCallResponse> Parser { get { return _parser; } }
 
@@ -1275,6 +1350,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public StreamingInputCallResponse(StreamingInputCallResponse other) : this() {
       aggregatedPayloadSize_ = other.aggregatedPayloadSize_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1310,13 +1386,16 @@
         return true;
       }
       if (AggregatedPayloadSize != other.AggregatedPayloadSize) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (AggregatedPayloadSize != 0) hash ^= AggregatedPayloadSize.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1331,6 +1410,9 @@
         output.WriteRawTag(8);
         output.WriteInt32(AggregatedPayloadSize);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1339,6 +1421,9 @@
       if (AggregatedPayloadSize != 0) {
         size += 1 + pb::CodedOutputStream.ComputeInt32Size(AggregatedPayloadSize);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1350,6 +1435,7 @@
       if (other.AggregatedPayloadSize != 0) {
         AggregatedPayloadSize = other.AggregatedPayloadSize;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1358,7 +1444,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             AggregatedPayloadSize = input.ReadInt32();
@@ -1375,6 +1461,7 @@
   /// </summary>
   public sealed partial class ResponseParameters : pb::IMessage<ResponseParameters> {
     private static readonly pb::MessageParser<ResponseParameters> _parser = new pb::MessageParser<ResponseParameters>(() => new ResponseParameters());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ResponseParameters> Parser { get { return _parser; } }
 
@@ -1400,6 +1487,7 @@
       size_ = other.size_;
       intervalUs_ = other.intervalUs_;
       Compressed = other.compressed_ != null ? other.Compressed.Clone() : null;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1469,7 +1557,7 @@
       if (Size != other.Size) return false;
       if (IntervalUs != other.IntervalUs) return false;
       if (!object.Equals(Compressed, other.Compressed)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1478,6 +1566,9 @@
       if (Size != 0) hash ^= Size.GetHashCode();
       if (IntervalUs != 0) hash ^= IntervalUs.GetHashCode();
       if (compressed_ != null) hash ^= Compressed.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1500,6 +1591,9 @@
         output.WriteRawTag(26);
         output.WriteMessage(Compressed);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1514,6 +1608,9 @@
       if (compressed_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Compressed);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1534,6 +1631,7 @@
         }
         Compressed.MergeFrom(other.Compressed);
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1542,7 +1640,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             Size = input.ReadInt32();
@@ -1570,6 +1668,7 @@
   /// </summary>
   public sealed partial class StreamingOutputCallRequest : pb::IMessage<StreamingOutputCallRequest> {
     private static readonly pb::MessageParser<StreamingOutputCallRequest> _parser = new pb::MessageParser<StreamingOutputCallRequest>(() => new StreamingOutputCallRequest());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<StreamingOutputCallRequest> Parser { get { return _parser; } }
 
@@ -1596,6 +1695,7 @@
       responseParameters_ = other.responseParameters_.Clone();
       Payload = other.payload_ != null ? other.Payload.Clone() : null;
       ResponseStatus = other.responseStatus_ != null ? other.ResponseStatus.Clone() : null;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1679,7 +1779,7 @@
       if(!responseParameters_.Equals(other.responseParameters_)) return false;
       if (!object.Equals(Payload, other.Payload)) return false;
       if (!object.Equals(ResponseStatus, other.ResponseStatus)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1689,6 +1789,9 @@
       hash ^= responseParameters_.GetHashCode();
       if (payload_ != null) hash ^= Payload.GetHashCode();
       if (responseStatus_ != null) hash ^= ResponseStatus.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1712,6 +1815,9 @@
         output.WriteRawTag(58);
         output.WriteMessage(ResponseStatus);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1727,6 +1833,9 @@
       if (responseStatus_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(ResponseStatus);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1751,6 +1860,7 @@
         }
         ResponseStatus.MergeFrom(other.ResponseStatus);
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1759,7 +1869,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             responseType_ = (global::Grpc.Testing.PayloadType) input.ReadEnum();
@@ -1794,6 +1904,7 @@
   /// </summary>
   public sealed partial class StreamingOutputCallResponse : pb::IMessage<StreamingOutputCallResponse> {
     private static readonly pb::MessageParser<StreamingOutputCallResponse> _parser = new pb::MessageParser<StreamingOutputCallResponse>(() => new StreamingOutputCallResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<StreamingOutputCallResponse> Parser { get { return _parser; } }
 
@@ -1817,6 +1928,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public StreamingOutputCallResponse(StreamingOutputCallResponse other) : this() {
       Payload = other.payload_ != null ? other.Payload.Clone() : null;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1852,13 +1964,16 @@
         return true;
       }
       if (!object.Equals(Payload, other.Payload)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (payload_ != null) hash ^= Payload.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1873,6 +1988,9 @@
         output.WriteRawTag(10);
         output.WriteMessage(Payload);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1881,6 +1999,9 @@
       if (payload_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Payload);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1895,6 +2016,7 @@
         }
         Payload.MergeFrom(other.Payload);
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1903,7 +2025,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             if (payload_ == null) {
@@ -1924,6 +2046,7 @@
   /// </summary>
   public sealed partial class ReconnectParams : pb::IMessage<ReconnectParams> {
     private static readonly pb::MessageParser<ReconnectParams> _parser = new pb::MessageParser<ReconnectParams>(() => new ReconnectParams());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ReconnectParams> Parser { get { return _parser; } }
 
@@ -1947,6 +2070,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public ReconnectParams(ReconnectParams other) : this() {
       maxReconnectBackoffMs_ = other.maxReconnectBackoffMs_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1979,13 +2103,16 @@
         return true;
       }
       if (MaxReconnectBackoffMs != other.MaxReconnectBackoffMs) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (MaxReconnectBackoffMs != 0) hash ^= MaxReconnectBackoffMs.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -2000,6 +2127,9 @@
         output.WriteRawTag(8);
         output.WriteInt32(MaxReconnectBackoffMs);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2008,6 +2138,9 @@
       if (MaxReconnectBackoffMs != 0) {
         size += 1 + pb::CodedOutputStream.ComputeInt32Size(MaxReconnectBackoffMs);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -2019,6 +2152,7 @@
       if (other.MaxReconnectBackoffMs != 0) {
         MaxReconnectBackoffMs = other.MaxReconnectBackoffMs;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2027,7 +2161,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             MaxReconnectBackoffMs = input.ReadInt32();
@@ -2046,6 +2180,7 @@
   /// </summary>
   public sealed partial class ReconnectInfo : pb::IMessage<ReconnectInfo> {
     private static readonly pb::MessageParser<ReconnectInfo> _parser = new pb::MessageParser<ReconnectInfo>(() => new ReconnectInfo());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ReconnectInfo> Parser { get { return _parser; } }
 
@@ -2070,6 +2205,7 @@
     public ReconnectInfo(ReconnectInfo other) : this() {
       passed_ = other.passed_;
       backoffMs_ = other.backoffMs_.Clone();
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2113,7 +2249,7 @@
       }
       if (Passed != other.Passed) return false;
       if(!backoffMs_.Equals(other.backoffMs_)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2121,6 +2257,9 @@
       int hash = 1;
       if (Passed != false) hash ^= Passed.GetHashCode();
       hash ^= backoffMs_.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -2136,6 +2275,9 @@
         output.WriteBool(Passed);
       }
       backoffMs_.WriteTo(output, _repeated_backoffMs_codec);
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2145,6 +2287,9 @@
         size += 1 + 1;
       }
       size += backoffMs_.CalculateSize(_repeated_backoffMs_codec);
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -2157,6 +2302,7 @@
         Passed = other.Passed;
       }
       backoffMs_.Add(other.backoffMs_);
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -2165,7 +2311,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             Passed = input.ReadBool();
diff --git a/src/csharp/Grpc.IntegrationTesting/Metrics.cs b/src/csharp/Grpc.IntegrationTesting/Metrics.cs
index 84eb09a..b5d8b87 100644
--- a/src/csharp/Grpc.IntegrationTesting/Metrics.cs
+++ b/src/csharp/Grpc.IntegrationTesting/Metrics.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: src/proto/grpc/testing/metrics.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: src/proto/grpc/testing/metrics.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -48,6 +50,7 @@
   /// </summary>
   public sealed partial class GaugeResponse : pb::IMessage<GaugeResponse> {
     private static readonly pb::MessageParser<GaugeResponse> _parser = new pb::MessageParser<GaugeResponse>(() => new GaugeResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<GaugeResponse> Parser { get { return _parser; } }
 
@@ -83,6 +86,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -169,10 +173,10 @@
       }
       if (Name != other.Name) return false;
       if (LongValue != other.LongValue) return false;
-      if (DoubleValue != other.DoubleValue) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(DoubleValue, other.DoubleValue)) return false;
       if (StringValue != other.StringValue) return false;
       if (ValueCase != other.ValueCase) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -180,9 +184,12 @@
       int hash = 1;
       if (Name.Length != 0) hash ^= Name.GetHashCode();
       if (valueCase_ == ValueOneofCase.LongValue) hash ^= LongValue.GetHashCode();
-      if (valueCase_ == ValueOneofCase.DoubleValue) hash ^= DoubleValue.GetHashCode();
+      if (valueCase_ == ValueOneofCase.DoubleValue) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(DoubleValue);
       if (valueCase_ == ValueOneofCase.StringValue) hash ^= StringValue.GetHashCode();
       hash ^= (int) valueCase_;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -209,6 +216,9 @@
         output.WriteRawTag(34);
         output.WriteString(StringValue);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -226,6 +236,9 @@
       if (valueCase_ == ValueOneofCase.StringValue) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(StringValue);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -249,6 +262,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -257,7 +271,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             Name = input.ReadString();
@@ -286,6 +300,7 @@
   /// </summary>
   public sealed partial class GaugeRequest : pb::IMessage<GaugeRequest> {
     private static readonly pb::MessageParser<GaugeRequest> _parser = new pb::MessageParser<GaugeRequest>(() => new GaugeRequest());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<GaugeRequest> Parser { get { return _parser; } }
 
@@ -309,6 +324,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public GaugeRequest(GaugeRequest other) : this() {
       name_ = other.name_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -341,13 +357,16 @@
         return true;
       }
       if (Name != other.Name) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (Name.Length != 0) hash ^= Name.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -362,6 +381,9 @@
         output.WriteRawTag(10);
         output.WriteString(Name);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -370,6 +392,9 @@
       if (Name.Length != 0) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(Name);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -381,6 +406,7 @@
       if (other.Name.Length != 0) {
         Name = other.Name;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -389,7 +415,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             Name = input.ReadString();
@@ -403,6 +429,7 @@
 
   public sealed partial class EmptyMessage : pb::IMessage<EmptyMessage> {
     private static readonly pb::MessageParser<EmptyMessage> _parser = new pb::MessageParser<EmptyMessage>(() => new EmptyMessage());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<EmptyMessage> Parser { get { return _parser; } }
 
@@ -425,6 +452,7 @@
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public EmptyMessage(EmptyMessage other) : this() {
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -445,12 +473,15 @@
       if (ReferenceEquals(other, this)) {
         return true;
       }
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -461,11 +492,17 @@
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public void WriteTo(pb::CodedOutputStream output) {
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public int CalculateSize() {
       int size = 0;
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -474,6 +511,7 @@
       if (other == null) {
         return;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -482,7 +520,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
         }
       }
diff --git a/src/csharp/Grpc.IntegrationTesting/Payloads.cs b/src/csharp/Grpc.IntegrationTesting/Payloads.cs
index fca8cda..25f34ff 100644
--- a/src/csharp/Grpc.IntegrationTesting/Payloads.cs
+++ b/src/csharp/Grpc.IntegrationTesting/Payloads.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: src/proto/grpc/testing/payloads.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: src/proto/grpc/testing/payloads.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -46,6 +48,7 @@
   #region Messages
   public sealed partial class ByteBufferParams : pb::IMessage<ByteBufferParams> {
     private static readonly pb::MessageParser<ByteBufferParams> _parser = new pb::MessageParser<ByteBufferParams>(() => new ByteBufferParams());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ByteBufferParams> Parser { get { return _parser; } }
 
@@ -70,6 +73,7 @@
     public ByteBufferParams(ByteBufferParams other) : this() {
       reqSize_ = other.reqSize_;
       respSize_ = other.respSize_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -114,7 +118,7 @@
       }
       if (ReqSize != other.ReqSize) return false;
       if (RespSize != other.RespSize) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -122,6 +126,9 @@
       int hash = 1;
       if (ReqSize != 0) hash ^= ReqSize.GetHashCode();
       if (RespSize != 0) hash ^= RespSize.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -140,6 +147,9 @@
         output.WriteRawTag(16);
         output.WriteInt32(RespSize);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -151,6 +161,9 @@
       if (RespSize != 0) {
         size += 1 + pb::CodedOutputStream.ComputeInt32Size(RespSize);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -165,6 +178,7 @@
       if (other.RespSize != 0) {
         RespSize = other.RespSize;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -173,7 +187,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             ReqSize = input.ReadInt32();
@@ -191,6 +205,7 @@
 
   public sealed partial class SimpleProtoParams : pb::IMessage<SimpleProtoParams> {
     private static readonly pb::MessageParser<SimpleProtoParams> _parser = new pb::MessageParser<SimpleProtoParams>(() => new SimpleProtoParams());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<SimpleProtoParams> Parser { get { return _parser; } }
 
@@ -215,6 +230,7 @@
     public SimpleProtoParams(SimpleProtoParams other) : this() {
       reqSize_ = other.reqSize_;
       respSize_ = other.respSize_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -259,7 +275,7 @@
       }
       if (ReqSize != other.ReqSize) return false;
       if (RespSize != other.RespSize) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -267,6 +283,9 @@
       int hash = 1;
       if (ReqSize != 0) hash ^= ReqSize.GetHashCode();
       if (RespSize != 0) hash ^= RespSize.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -285,6 +304,9 @@
         output.WriteRawTag(16);
         output.WriteInt32(RespSize);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -296,6 +318,9 @@
       if (RespSize != 0) {
         size += 1 + pb::CodedOutputStream.ComputeInt32Size(RespSize);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -310,6 +335,7 @@
       if (other.RespSize != 0) {
         RespSize = other.RespSize;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -318,7 +344,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             ReqSize = input.ReadInt32();
@@ -340,6 +366,7 @@
   /// </summary>
   public sealed partial class ComplexProtoParams : pb::IMessage<ComplexProtoParams> {
     private static readonly pb::MessageParser<ComplexProtoParams> _parser = new pb::MessageParser<ComplexProtoParams>(() => new ComplexProtoParams());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ComplexProtoParams> Parser { get { return _parser; } }
 
@@ -362,6 +389,7 @@
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public ComplexProtoParams(ComplexProtoParams other) : this() {
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -382,12 +410,15 @@
       if (ReferenceEquals(other, this)) {
         return true;
       }
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -398,11 +429,17 @@
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public void WriteTo(pb::CodedOutputStream output) {
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public int CalculateSize() {
       int size = 0;
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -411,6 +448,7 @@
       if (other == null) {
         return;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -419,7 +457,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
         }
       }
@@ -429,6 +467,7 @@
 
   public sealed partial class PayloadConfig : pb::IMessage<PayloadConfig> {
     private static readonly pb::MessageParser<PayloadConfig> _parser = new pb::MessageParser<PayloadConfig>(() => new PayloadConfig());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<PayloadConfig> Parser { get { return _parser; } }
 
@@ -463,6 +502,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -540,7 +580,7 @@
       if (!object.Equals(SimpleParams, other.SimpleParams)) return false;
       if (!object.Equals(ComplexParams, other.ComplexParams)) return false;
       if (PayloadCase != other.PayloadCase) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -550,6 +590,9 @@
       if (payloadCase_ == PayloadOneofCase.SimpleParams) hash ^= SimpleParams.GetHashCode();
       if (payloadCase_ == PayloadOneofCase.ComplexParams) hash ^= ComplexParams.GetHashCode();
       hash ^= (int) payloadCase_;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -572,6 +615,9 @@
         output.WriteRawTag(26);
         output.WriteMessage(ComplexParams);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -586,6 +632,9 @@
       if (payloadCase_ == PayloadOneofCase.ComplexParams) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(ComplexParams);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -615,6 +664,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -623,7 +673,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             global::Grpc.Testing.ByteBufferParams subBuilder = new global::Grpc.Testing.ByteBufferParams();
diff --git a/src/csharp/Grpc.IntegrationTesting/ReportQpsScenarioService.cs b/src/csharp/Grpc.IntegrationTesting/ReportQpsScenarioService.cs
index b82cb52..707c443 100644
--- a/src/csharp/Grpc.IntegrationTesting/ReportQpsScenarioService.cs
+++ b/src/csharp/Grpc.IntegrationTesting/ReportQpsScenarioService.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: src/proto/grpc/testing/report_qps_scenario_service.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: src/proto/grpc/testing/report_qps_scenario_service.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
diff --git a/src/csharp/Grpc.IntegrationTesting/Stats.cs b/src/csharp/Grpc.IntegrationTesting/Stats.cs
index e082ae7..8160646 100644
--- a/src/csharp/Grpc.IntegrationTesting/Stats.cs
+++ b/src/csharp/Grpc.IntegrationTesting/Stats.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: src/proto/grpc/testing/stats.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: src/proto/grpc/testing/stats.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -55,6 +57,7 @@
   #region Messages
   public sealed partial class ServerStats : pb::IMessage<ServerStats> {
     private static readonly pb::MessageParser<ServerStats> _parser = new pb::MessageParser<ServerStats>(() => new ServerStats());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ServerStats> Parser { get { return _parser; } }
 
@@ -84,6 +87,7 @@
       idleCpuTime_ = other.idleCpuTime_;
       cqPollCount_ = other.cqPollCount_;
       CoreStats = other.coreStats_ != null ? other.CoreStats.Clone() : null;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -203,26 +207,29 @@
       if (ReferenceEquals(other, this)) {
         return true;
       }
-      if (TimeElapsed != other.TimeElapsed) return false;
-      if (TimeUser != other.TimeUser) return false;
-      if (TimeSystem != other.TimeSystem) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(TimeElapsed, other.TimeElapsed)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(TimeUser, other.TimeUser)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(TimeSystem, other.TimeSystem)) return false;
       if (TotalCpuTime != other.TotalCpuTime) return false;
       if (IdleCpuTime != other.IdleCpuTime) return false;
       if (CqPollCount != other.CqPollCount) return false;
       if (!object.Equals(CoreStats, other.CoreStats)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
-      if (TimeElapsed != 0D) hash ^= TimeElapsed.GetHashCode();
-      if (TimeUser != 0D) hash ^= TimeUser.GetHashCode();
-      if (TimeSystem != 0D) hash ^= TimeSystem.GetHashCode();
+      if (TimeElapsed != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(TimeElapsed);
+      if (TimeUser != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(TimeUser);
+      if (TimeSystem != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(TimeSystem);
       if (TotalCpuTime != 0UL) hash ^= TotalCpuTime.GetHashCode();
       if (IdleCpuTime != 0UL) hash ^= IdleCpuTime.GetHashCode();
       if (CqPollCount != 0UL) hash ^= CqPollCount.GetHashCode();
       if (coreStats_ != null) hash ^= CoreStats.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -261,6 +268,9 @@
         output.WriteRawTag(58);
         output.WriteMessage(CoreStats);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -287,6 +297,9 @@
       if (coreStats_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(CoreStats);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -319,6 +332,7 @@
         }
         CoreStats.MergeFrom(other.CoreStats);
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -327,7 +341,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 9: {
             TimeElapsed = input.ReadDouble();
@@ -371,6 +385,7 @@
   /// </summary>
   public sealed partial class HistogramParams : pb::IMessage<HistogramParams> {
     private static readonly pb::MessageParser<HistogramParams> _parser = new pb::MessageParser<HistogramParams>(() => new HistogramParams());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<HistogramParams> Parser { get { return _parser; } }
 
@@ -395,6 +410,7 @@
     public HistogramParams(HistogramParams other) : this() {
       resolution_ = other.resolution_;
       maxPossible_ = other.maxPossible_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -443,16 +459,19 @@
       if (ReferenceEquals(other, this)) {
         return true;
       }
-      if (Resolution != other.Resolution) return false;
-      if (MaxPossible != other.MaxPossible) return false;
-      return true;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(Resolution, other.Resolution)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(MaxPossible, other.MaxPossible)) return false;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
-      if (Resolution != 0D) hash ^= Resolution.GetHashCode();
-      if (MaxPossible != 0D) hash ^= MaxPossible.GetHashCode();
+      if (Resolution != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Resolution);
+      if (MaxPossible != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(MaxPossible);
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -471,6 +490,9 @@
         output.WriteRawTag(17);
         output.WriteDouble(MaxPossible);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -482,6 +504,9 @@
       if (MaxPossible != 0D) {
         size += 1 + 8;
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -496,6 +521,7 @@
       if (other.MaxPossible != 0D) {
         MaxPossible = other.MaxPossible;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -504,7 +530,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 9: {
             Resolution = input.ReadDouble();
@@ -525,6 +551,7 @@
   /// </summary>
   public sealed partial class HistogramData : pb::IMessage<HistogramData> {
     private static readonly pb::MessageParser<HistogramData> _parser = new pb::MessageParser<HistogramData>(() => new HistogramData());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<HistogramData> Parser { get { return _parser; } }
 
@@ -553,6 +580,7 @@
       sum_ = other.sum_;
       sumOfSquares_ = other.sumOfSquares_;
       count_ = other.count_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -639,23 +667,26 @@
         return true;
       }
       if(!bucket_.Equals(other.bucket_)) return false;
-      if (MinSeen != other.MinSeen) return false;
-      if (MaxSeen != other.MaxSeen) return false;
-      if (Sum != other.Sum) return false;
-      if (SumOfSquares != other.SumOfSquares) return false;
-      if (Count != other.Count) return false;
-      return true;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(MinSeen, other.MinSeen)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(MaxSeen, other.MaxSeen)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(Sum, other.Sum)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(SumOfSquares, other.SumOfSquares)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(Count, other.Count)) return false;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       hash ^= bucket_.GetHashCode();
-      if (MinSeen != 0D) hash ^= MinSeen.GetHashCode();
-      if (MaxSeen != 0D) hash ^= MaxSeen.GetHashCode();
-      if (Sum != 0D) hash ^= Sum.GetHashCode();
-      if (SumOfSquares != 0D) hash ^= SumOfSquares.GetHashCode();
-      if (Count != 0D) hash ^= Count.GetHashCode();
+      if (MinSeen != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(MinSeen);
+      if (MaxSeen != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(MaxSeen);
+      if (Sum != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Sum);
+      if (SumOfSquares != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(SumOfSquares);
+      if (Count != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Count);
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -687,6 +718,9 @@
         output.WriteRawTag(49);
         output.WriteDouble(Count);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -708,6 +742,9 @@
       if (Count != 0D) {
         size += 1 + 8;
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -732,6 +769,7 @@
       if (other.Count != 0D) {
         Count = other.Count;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -740,7 +778,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10:
           case 8: {
@@ -775,6 +813,7 @@
 
   public sealed partial class RequestResultCount : pb::IMessage<RequestResultCount> {
     private static readonly pb::MessageParser<RequestResultCount> _parser = new pb::MessageParser<RequestResultCount>(() => new RequestResultCount());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<RequestResultCount> Parser { get { return _parser; } }
 
@@ -799,6 +838,7 @@
     public RequestResultCount(RequestResultCount other) : this() {
       statusCode_ = other.statusCode_;
       count_ = other.count_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -843,7 +883,7 @@
       }
       if (StatusCode != other.StatusCode) return false;
       if (Count != other.Count) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -851,6 +891,9 @@
       int hash = 1;
       if (StatusCode != 0) hash ^= StatusCode.GetHashCode();
       if (Count != 0L) hash ^= Count.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -869,6 +912,9 @@
         output.WriteRawTag(16);
         output.WriteInt64(Count);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -880,6 +926,9 @@
       if (Count != 0L) {
         size += 1 + pb::CodedOutputStream.ComputeInt64Size(Count);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -894,6 +943,7 @@
       if (other.Count != 0L) {
         Count = other.Count;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -902,7 +952,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             StatusCode = input.ReadInt32();
@@ -920,6 +970,7 @@
 
   public sealed partial class ClientStats : pb::IMessage<ClientStats> {
     private static readonly pb::MessageParser<ClientStats> _parser = new pb::MessageParser<ClientStats>(() => new ClientStats());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ClientStats> Parser { get { return _parser; } }
 
@@ -949,6 +1000,7 @@
       requestResults_ = other.requestResults_.Clone();
       cqPollCount_ = other.cqPollCount_;
       CoreStats = other.coreStats_ != null ? other.CoreStats.Clone() : null;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1061,25 +1113,28 @@
         return true;
       }
       if (!object.Equals(Latencies, other.Latencies)) return false;
-      if (TimeElapsed != other.TimeElapsed) return false;
-      if (TimeUser != other.TimeUser) return false;
-      if (TimeSystem != other.TimeSystem) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(TimeElapsed, other.TimeElapsed)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(TimeUser, other.TimeUser)) return false;
+      if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(TimeSystem, other.TimeSystem)) return false;
       if(!requestResults_.Equals(other.requestResults_)) return false;
       if (CqPollCount != other.CqPollCount) return false;
       if (!object.Equals(CoreStats, other.CoreStats)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (latencies_ != null) hash ^= Latencies.GetHashCode();
-      if (TimeElapsed != 0D) hash ^= TimeElapsed.GetHashCode();
-      if (TimeUser != 0D) hash ^= TimeUser.GetHashCode();
-      if (TimeSystem != 0D) hash ^= TimeSystem.GetHashCode();
+      if (TimeElapsed != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(TimeElapsed);
+      if (TimeUser != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(TimeUser);
+      if (TimeSystem != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(TimeSystem);
       hash ^= requestResults_.GetHashCode();
       if (CqPollCount != 0UL) hash ^= CqPollCount.GetHashCode();
       if (coreStats_ != null) hash ^= CoreStats.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1115,6 +1170,9 @@
         output.WriteRawTag(58);
         output.WriteMessage(CoreStats);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1139,6 +1197,9 @@
       if (coreStats_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(CoreStats);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1172,6 +1233,7 @@
         }
         CoreStats.MergeFrom(other.CoreStats);
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1180,7 +1242,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             if (latencies_ == null) {
diff --git a/src/csharp/Grpc.IntegrationTesting/Test.cs b/src/csharp/Grpc.IntegrationTesting/Test.cs
index d2fa9f8..03f92c7 100644
--- a/src/csharp/Grpc.IntegrationTesting/Test.cs
+++ b/src/csharp/Grpc.IntegrationTesting/Test.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: src/proto/grpc/testing/test.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: src/proto/grpc/testing/test.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
diff --git a/src/csharp/Grpc.IntegrationTesting/WorkerService.cs b/src/csharp/Grpc.IntegrationTesting/WorkerService.cs
index 7c82954..6de3193 100644
--- a/src/csharp/Grpc.IntegrationTesting/WorkerService.cs
+++ b/src/csharp/Grpc.IntegrationTesting/WorkerService.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: src/proto/grpc/testing/worker_service.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: src/proto/grpc/testing/worker_service.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
diff --git a/src/csharp/Grpc.Reflection/Reflection.cs b/src/csharp/Grpc.Reflection/Reflection.cs
index 60090e5..84b2a0a 100644
--- a/src/csharp/Grpc.Reflection/Reflection.cs
+++ b/src/csharp/Grpc.Reflection/Reflection.cs
@@ -1,5 +1,7 @@
-// Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: grpc/reflection/v1alpha/reflection.proto
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: grpc/reflection/v1alpha/reflection.proto
+// </auto-generated>
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -74,6 +76,7 @@
   /// </summary>
   public sealed partial class ServerReflectionRequest : pb::IMessage<ServerReflectionRequest> {
     private static readonly pb::MessageParser<ServerReflectionRequest> _parser = new pb::MessageParser<ServerReflectionRequest>(() => new ServerReflectionRequest());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ServerReflectionRequest> Parser { get { return _parser; } }
 
@@ -115,6 +118,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -256,7 +260,7 @@
       if (AllExtensionNumbersOfType != other.AllExtensionNumbersOfType) return false;
       if (ListServices != other.ListServices) return false;
       if (MessageRequestCase != other.MessageRequestCase) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -269,6 +273,9 @@
       if (messageRequestCase_ == MessageRequestOneofCase.AllExtensionNumbersOfType) hash ^= AllExtensionNumbersOfType.GetHashCode();
       if (messageRequestCase_ == MessageRequestOneofCase.ListServices) hash ^= ListServices.GetHashCode();
       hash ^= (int) messageRequestCase_;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -303,6 +310,9 @@
         output.WriteRawTag(58);
         output.WriteString(ListServices);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -326,6 +336,9 @@
       if (messageRequestCase_ == MessageRequestOneofCase.ListServices) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(ListServices);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -358,6 +371,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -366,7 +380,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             Host = input.ReadString();
@@ -409,6 +423,7 @@
   /// </summary>
   public sealed partial class ExtensionRequest : pb::IMessage<ExtensionRequest> {
     private static readonly pb::MessageParser<ExtensionRequest> _parser = new pb::MessageParser<ExtensionRequest>(() => new ExtensionRequest());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ExtensionRequest> Parser { get { return _parser; } }
 
@@ -433,6 +448,7 @@
     public ExtensionRequest(ExtensionRequest other) : this() {
       containingType_ = other.containingType_;
       extensionNumber_ = other.extensionNumber_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -480,7 +496,7 @@
       }
       if (ContainingType != other.ContainingType) return false;
       if (ExtensionNumber != other.ExtensionNumber) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -488,6 +504,9 @@
       int hash = 1;
       if (ContainingType.Length != 0) hash ^= ContainingType.GetHashCode();
       if (ExtensionNumber != 0) hash ^= ExtensionNumber.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -506,6 +525,9 @@
         output.WriteRawTag(16);
         output.WriteInt32(ExtensionNumber);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -517,6 +539,9 @@
       if (ExtensionNumber != 0) {
         size += 1 + pb::CodedOutputStream.ComputeInt32Size(ExtensionNumber);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -531,6 +556,7 @@
       if (other.ExtensionNumber != 0) {
         ExtensionNumber = other.ExtensionNumber;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -539,7 +565,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             ContainingType = input.ReadString();
@@ -560,6 +586,7 @@
   /// </summary>
   public sealed partial class ServerReflectionResponse : pb::IMessage<ServerReflectionResponse> {
     private static readonly pb::MessageParser<ServerReflectionResponse> _parser = new pb::MessageParser<ServerReflectionResponse>(() => new ServerReflectionResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ServerReflectionResponse> Parser { get { return _parser; } }
 
@@ -599,6 +626,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -730,7 +758,7 @@
       if (!object.Equals(ListServicesResponse, other.ListServicesResponse)) return false;
       if (!object.Equals(ErrorResponse, other.ErrorResponse)) return false;
       if (MessageResponseCase != other.MessageResponseCase) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -743,6 +771,9 @@
       if (messageResponseCase_ == MessageResponseOneofCase.ListServicesResponse) hash ^= ListServicesResponse.GetHashCode();
       if (messageResponseCase_ == MessageResponseOneofCase.ErrorResponse) hash ^= ErrorResponse.GetHashCode();
       hash ^= (int) messageResponseCase_;
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -777,6 +808,9 @@
         output.WriteRawTag(58);
         output.WriteMessage(ErrorResponse);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -800,6 +834,9 @@
       if (messageResponseCase_ == MessageResponseOneofCase.ErrorResponse) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(ErrorResponse);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -844,6 +881,7 @@
           break;
       }
 
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -852,7 +890,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             ValidHost = input.ReadString();
@@ -914,6 +952,7 @@
   /// </summary>
   public sealed partial class FileDescriptorResponse : pb::IMessage<FileDescriptorResponse> {
     private static readonly pb::MessageParser<FileDescriptorResponse> _parser = new pb::MessageParser<FileDescriptorResponse>(() => new FileDescriptorResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<FileDescriptorResponse> Parser { get { return _parser; } }
 
@@ -937,6 +976,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public FileDescriptorResponse(FileDescriptorResponse other) : this() {
       fileDescriptorProto_ = other.fileDescriptorProto_.Clone();
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -973,13 +1013,16 @@
         return true;
       }
       if(!fileDescriptorProto_.Equals(other.fileDescriptorProto_)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       hash ^= fileDescriptorProto_.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -991,12 +1034,18 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public void WriteTo(pb::CodedOutputStream output) {
       fileDescriptorProto_.WriteTo(output, _repeated_fileDescriptorProto_codec);
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public int CalculateSize() {
       int size = 0;
       size += fileDescriptorProto_.CalculateSize(_repeated_fileDescriptorProto_codec);
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1006,6 +1055,7 @@
         return;
       }
       fileDescriptorProto_.Add(other.fileDescriptorProto_);
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1014,7 +1064,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             fileDescriptorProto_.AddEntriesFrom(input, _repeated_fileDescriptorProto_codec);
@@ -1032,6 +1082,7 @@
   /// </summary>
   public sealed partial class ExtensionNumberResponse : pb::IMessage<ExtensionNumberResponse> {
     private static readonly pb::MessageParser<ExtensionNumberResponse> _parser = new pb::MessageParser<ExtensionNumberResponse>(() => new ExtensionNumberResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ExtensionNumberResponse> Parser { get { return _parser; } }
 
@@ -1056,6 +1107,7 @@
     public ExtensionNumberResponse(ExtensionNumberResponse other) : this() {
       baseTypeName_ = other.baseTypeName_;
       extensionNumber_ = other.extensionNumber_.Clone();
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1103,7 +1155,7 @@
       }
       if (BaseTypeName != other.BaseTypeName) return false;
       if(!extensionNumber_.Equals(other.extensionNumber_)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1111,6 +1163,9 @@
       int hash = 1;
       if (BaseTypeName.Length != 0) hash ^= BaseTypeName.GetHashCode();
       hash ^= extensionNumber_.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1126,6 +1181,9 @@
         output.WriteString(BaseTypeName);
       }
       extensionNumber_.WriteTo(output, _repeated_extensionNumber_codec);
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1135,6 +1193,9 @@
         size += 1 + pb::CodedOutputStream.ComputeStringSize(BaseTypeName);
       }
       size += extensionNumber_.CalculateSize(_repeated_extensionNumber_codec);
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1147,6 +1208,7 @@
         BaseTypeName = other.BaseTypeName;
       }
       extensionNumber_.Add(other.extensionNumber_);
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1155,7 +1217,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             BaseTypeName = input.ReadString();
@@ -1177,6 +1239,7 @@
   /// </summary>
   public sealed partial class ListServiceResponse : pb::IMessage<ListServiceResponse> {
     private static readonly pb::MessageParser<ListServiceResponse> _parser = new pb::MessageParser<ListServiceResponse>(() => new ListServiceResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ListServiceResponse> Parser { get { return _parser; } }
 
@@ -1200,6 +1263,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public ListServiceResponse(ListServiceResponse other) : this() {
       service_ = other.service_.Clone();
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1235,13 +1299,16 @@
         return true;
       }
       if(!service_.Equals(other.service_)) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       hash ^= service_.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1253,12 +1320,18 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public void WriteTo(pb::CodedOutputStream output) {
       service_.WriteTo(output, _repeated_service_codec);
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public int CalculateSize() {
       int size = 0;
       size += service_.CalculateSize(_repeated_service_codec);
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1268,6 +1341,7 @@
         return;
       }
       service_.Add(other.service_);
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1276,7 +1350,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             service_.AddEntriesFrom(input, _repeated_service_codec);
@@ -1294,6 +1368,7 @@
   /// </summary>
   public sealed partial class ServiceResponse : pb::IMessage<ServiceResponse> {
     private static readonly pb::MessageParser<ServiceResponse> _parser = new pb::MessageParser<ServiceResponse>(() => new ServiceResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ServiceResponse> Parser { get { return _parser; } }
 
@@ -1317,6 +1392,7 @@
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public ServiceResponse(ServiceResponse other) : this() {
       name_ = other.name_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1353,13 +1429,16 @@
         return true;
       }
       if (Name != other.Name) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override int GetHashCode() {
       int hash = 1;
       if (Name.Length != 0) hash ^= Name.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1374,6 +1453,9 @@
         output.WriteRawTag(10);
         output.WriteString(Name);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1382,6 +1464,9 @@
       if (Name.Length != 0) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(Name);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1393,6 +1478,7 @@
       if (other.Name.Length != 0) {
         Name = other.Name;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1401,7 +1487,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 10: {
             Name = input.ReadString();
@@ -1418,6 +1504,7 @@
   /// </summary>
   public sealed partial class ErrorResponse : pb::IMessage<ErrorResponse> {
     private static readonly pb::MessageParser<ErrorResponse> _parser = new pb::MessageParser<ErrorResponse>(() => new ErrorResponse());
+    private pb::UnknownFieldSet _unknownFields;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public static pb::MessageParser<ErrorResponse> Parser { get { return _parser; } }
 
@@ -1442,6 +1529,7 @@
     public ErrorResponse(ErrorResponse other) : this() {
       errorCode_ = other.errorCode_;
       errorMessage_ = other.errorMessage_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1489,7 +1577,7 @@
       }
       if (ErrorCode != other.ErrorCode) return false;
       if (ErrorMessage != other.ErrorMessage) return false;
-      return true;
+      return Equals(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1497,6 +1585,9 @@
       int hash = 1;
       if (ErrorCode != 0) hash ^= ErrorCode.GetHashCode();
       if (ErrorMessage.Length != 0) hash ^= ErrorMessage.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
       return hash;
     }
 
@@ -1515,6 +1606,9 @@
         output.WriteRawTag(18);
         output.WriteString(ErrorMessage);
       }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1526,6 +1620,9 @@
       if (ErrorMessage.Length != 0) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(ErrorMessage);
       }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
       return size;
     }
 
@@ -1540,6 +1637,7 @@
       if (other.ErrorMessage.Length != 0) {
         ErrorMessage = other.ErrorMessage;
       }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
@@ -1548,7 +1646,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            input.SkipLastField();
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
             break;
           case 8: {
             ErrorCode = input.ReadInt32();
diff --git a/src/csharp/README.md b/src/csharp/README.md
index 6821ad2..e117e66 100644
--- a/src/csharp/README.md
+++ b/src/csharp/README.md
@@ -16,16 +16,17 @@
 
 When using gRPC C# under .NET Core you only need to [install .NET Core](https://www.microsoft.com/net/core).
 
-- Windows: .NET Framework 4.5+, Visual Studio 2013, 2015, 2017
-- Linux: Mono 4+, MonoDevelop 5.9+ (with NuGet add-in installed)
-- Mac OS X: Xamarin Studio 5.9+
+In addition to that, you can also use gRPC C# with these runtimes / IDEs
+- Windows: .NET Framework 4.5+, Visual Studio 2013, 2015, 2017, Visual Studio Code
+- Linux: Mono 4+, Visual Studio Code, MonoDevelop 5.9+ 
+- Mac OS X: Mono 4+, Visual Studio Code, Xamarin Studio 5.9+
 
 HOW TO USE
 --------------
 
 **Windows, Linux, Mac OS X**
 
-- Open Visual Studio / MonoDevelop / Xamarin Studio and start a new project/solution.
+- Open Visual Studio / MonoDevelop / Xamarin Studio and start a new project/solution (alternatively, you can create a new project from command line with `dotnet` SDK)
 
 - Add the [Grpc](https://www.nuget.org/packages/Grpc/) NuGet package as a dependency (Project options -> Manage NuGet Packages). 
 
@@ -37,12 +38,23 @@
 You only need to go through these steps if you are planning to develop gRPC C#.
 If you are a user of gRPC C#, go to Usage section above.
 
+**Prerequisites for contributors**
+
+- [dotnet SDK](https://www.microsoft.com/net/core)
+- [Mono 4+](https://www.mono-project.com/) (only needed for Linux and MacOS)
+- Prerequisites mentioned in [INSTALL.md](../../INSTALL.md#pre-requisites)
+  to be able to compile the native code.
+
 **Windows, Linux or Mac OS X**
 
-- The easiest way to build is using the `run_tests.py` script that will take care of building the `grpc_csharp_ext` native library:
+- The easiest way to build is using the `run_tests.py` script that will take care of building the `grpc_csharp_ext` native library.
+  
   ```
+  # NOTE: make sure all necessary git submodules with dependencies 
+  # are available by running "git submodule update --init"
+  
   # from the gRPC repository root
-  $ python tools/run_tests/run_tests.py -c dbg -l csharp --build_only
+  $ python tools/run_tests/run_tests.py -l csharp -c dbg --build_only
   ```
 
 - Use Visual Studio 2017 (on Windows) to open the solution `Grpc.sln` or use Visual Studio Code with C# extension (on Linux and Mac). gRPC C# code has been migrated to
@@ -57,11 +69,12 @@
 Under Visual Studio, make sure NUnit test adapter is installed (under "Extensions and Updates").
 Then you should be able to run all the tests using Test Explorer.
 
-gRPC team uses a Python script to simplify facilitate running tests for
+gRPC team uses a Python script to facilitate running tests for
 different languages.
 
 ```
-tools/run_tests/run_tests.py -l csharp
+# from the gRPC repository root
+$ python tools/run_tests/run_tests.py -l csharp -c dbg
 ```
 
 DOCUMENTATION
diff --git a/src/csharp/build_packages_dotnetcli.bat b/src/csharp/build_packages_dotnetcli.bat
index 0d65748..924d7b1 100755
--- a/src/csharp/build_packages_dotnetcli.bat
+++ b/src/csharp/build_packages_dotnetcli.bat
@@ -13,7 +13,7 @@
 @rem limitations under the License.
 
 @rem Current package versions
-set VERSION=1.12.0-dev
+set VERSION=1.13.0-dev
 
 @rem Adjust the location of nuget.exe
 set NUGET=C:\nuget\nuget.exe
diff --git a/src/csharp/build_packages_dotnetcli.sh b/src/csharp/build_packages_dotnetcli.sh
index 66aba36..5c73a8f 100755
--- a/src/csharp/build_packages_dotnetcli.sh
+++ b/src/csharp/build_packages_dotnetcli.sh
@@ -45,7 +45,7 @@
 dotnet pack --configuration Release Grpc.HealthCheck --output ../../../artifacts
 dotnet pack --configuration Release Grpc.Reflection --output ../../../artifacts
 
-nuget pack Grpc.nuspec -Version "1.12.0-dev" -OutputDirectory ../../artifacts
-nuget pack Grpc.Tools.nuspec -Version "1.12.0-dev" -OutputDirectory ../../artifacts
+nuget pack Grpc.nuspec -Version "1.13.0-dev" -OutputDirectory ../../artifacts
+nuget pack Grpc.Tools.nuspec -Version "1.13.0-dev" -OutputDirectory ../../artifacts
 
 (cd ../../artifacts && zip csharp_nugets_dotnetcli.zip *.nupkg)
diff --git a/src/csharp/experimental/build_native_ext_for_android.sh b/src/csharp/experimental/build_native_ext_for_android.sh
index 958778e..8197df7 100755
--- a/src/csharp/experimental/build_native_ext_for_android.sh
+++ b/src/csharp/experimental/build_native_ext_for_android.sh
@@ -23,7 +23,7 @@
 cd build
 
 # set to the location where Android SDK is installed
-ANDROID_NDK_PATH="$HOME/android-ndk-r16b"
+# e.g. ANDROID_NDK_PATH="$HOME/android-ndk-r16b"
 
 cmake ../.. \
   -DCMAKE_SYSTEM_NAME=Android \
diff --git "a/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec" "b/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec"
index f06312b..515dc91 100644
--- "a/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec"
+++ "b/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec"
@@ -42,7 +42,7 @@
   # exclamation mark ensures that other "regular" pods will be able to find it as it'll be installed
   # before them.
   s.name     = '!ProtoCompiler-gRPCPlugin'
-  v = '1.12.0-dev'
+  v = '1.13.0-dev'
   s.version  = v
   s.summary  = 'The gRPC ProtoC plugin generates Objective-C files from .proto services.'
   s.description = <<-DESC
diff --git a/src/objective-c/GRPCClient/private/version.h b/src/objective-c/GRPCClient/private/version.h
index 1298e7e..6fe4a7d 100644
--- a/src/objective-c/GRPCClient/private/version.h
+++ b/src/objective-c/GRPCClient/private/version.h
@@ -22,4 +22,4 @@
 // instead. This file can be regenerated from the template by running
 // `tools/buildgen/generate_projects.sh`.
 
-#define GRPC_OBJC_VERSION_STRING @"1.12.0-dev"
+#define GRPC_OBJC_VERSION_STRING @"1.13.0-dev"
diff --git a/src/objective-c/tests/version.h b/src/objective-c/tests/version.h
index d36545f..e963709 100644
--- a/src/objective-c/tests/version.h
+++ b/src/objective-c/tests/version.h
@@ -22,5 +22,5 @@
 // instead. This file can be regenerated from the template by running
 // `tools/buildgen/generate_projects.sh`.
 
-#define GRPC_OBJC_VERSION_STRING @"1.12.0-dev"
+#define GRPC_OBJC_VERSION_STRING @"1.13.0-dev"
 #define GRPC_C_VERSION_STRING @"6.0.0-dev"
diff --git a/src/php/composer.json b/src/php/composer.json
index 57d911d..03dffb4 100644
--- a/src/php/composer.json
+++ b/src/php/composer.json
@@ -2,7 +2,7 @@
   "name": "grpc/grpc-dev",
   "description": "gRPC library for PHP - for Developement use only",
   "license": "Apache-2.0",
-  "version": "1.12.0",
+  "version": "1.13.0",
   "require": {
     "php": ">=5.5.0",
     "google/protobuf": "^v3.3.0"
diff --git a/src/php/ext/grpc/php7_wrapper.h b/src/php/ext/grpc/php7_wrapper.h
index d4fa859..0239e04 100644
--- a/src/php/ext/grpc/php7_wrapper.h
+++ b/src/php/ext/grpc/php7_wrapper.h
@@ -30,6 +30,8 @@
   add_property_string(arg, name, context, b)
 #define php_grpc_add_property_stringl(res, name, str, len, b) \
   add_property_stringl(res, name, str, len, b)
+#define php_grpc_add_property_zval(res, name, val) \
+  add_property_zval(res, name, val)
 #define php_grpc_add_next_index_stringl(data, str, len, b) \
   add_next_index_stringl(data, str, len, b)
 
@@ -154,6 +156,11 @@
   add_property_string(arg, name, context)
 #define php_grpc_add_property_stringl(res, name, str, len, b) \
   add_property_stringl(res, name, str, len)
+#define php_grpc_add_property_zval(res, name, val) do { \
+  zval tmp; \
+  tmp = *val; \
+  add_property_zval(res, name, &tmp); \
+  } while(0)
 #define php_grpc_add_next_index_stringl(data, str, len, b) \
   add_next_index_stringl(data, str, len)
 
diff --git a/src/php/ext/grpc/server.c b/src/php/ext/grpc/server.c
index a65d233..292d512 100644
--- a/src/php/ext/grpc/server.c
+++ b/src/php/ext/grpc/server.c
@@ -89,8 +89,6 @@
   if (args_array == NULL) {
     server->wrapped = grpc_server_create(NULL, NULL);
   } else {
-    //TODO(thinkerou): deal it if key of array is long, crash now on php7
-    // and update unit test case
     php_grpc_read_args_array(args_array, &args TSRMLS_CC);
     server->wrapped = grpc_server_create(&args, NULL);
     efree(args.args);
@@ -118,8 +116,8 @@
   grpc_call_details_init(&details);
   grpc_metadata_array_init(&metadata);
   error_code =
-      grpc_server_request_call(server->wrapped, &call, &details, &metadata,
-                               completion_queue, completion_queue, NULL);
+    grpc_server_request_call(server->wrapped, &call, &details, &metadata,
+                             completion_queue, completion_queue, NULL);
   if (error_code != GRPC_CALL_OK) {
     zend_throw_exception(spl_ce_LogicException, "request_call failed",
                          (long)error_code TSRMLS_CC);
@@ -140,25 +138,12 @@
   php_grpc_add_property_string(result, "host", host_text, true);
   gpr_free(method_text);
   gpr_free(host_text);
-#if PHP_MAJOR_VERSION < 7
-  add_property_zval(result, "call", grpc_php_wrap_call(call, true TSRMLS_CC));
-  add_property_zval(result, "absolute_deadline",
-                    grpc_php_wrap_timeval(details.deadline TSRMLS_CC));
-  add_property_zval(result, "metadata", grpc_parse_metadata_array(&metadata
-                                                                  TSRMLS_CC));
-#else
-  zval zv_call;
-  zval zv_timeval;
-  zval zv_md;
-  //TODO(thinkerou): why use zval* to unit test error?
-  zv_call = *grpc_php_wrap_call(call, true);
-  zv_timeval = *grpc_php_wrap_timeval(details.deadline);
-  zv_md = *grpc_parse_metadata_array(&metadata);
-
-  add_property_zval(result, "call", &zv_call);
-  add_property_zval(result, "absolute_deadline", &zv_timeval);
-  add_property_zval(result, "metadata", &zv_md);
-#endif
+  php_grpc_add_property_zval(result, "call",
+                             grpc_php_wrap_call(call, true TSRMLS_CC));
+  php_grpc_add_property_zval(result, "absolute_deadline",
+                             grpc_php_wrap_timeval(details.deadline TSRMLS_CC));
+  php_grpc_add_property_zval(result, "metadata",
+                             grpc_parse_metadata_array(&metadata TSRMLS_CC));
 
  cleanup:
   grpc_call_details_destroy(&details);
@@ -202,9 +187,9 @@
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sO", &addr, &addr_len,
                             &creds_obj, grpc_ce_server_credentials) ==
       FAILURE) {
-    zend_throw_exception(
-        spl_ce_InvalidArgumentException,
-        "add_http2_port expects a string and a ServerCredentials", 1 TSRMLS_CC);
+    zend_throw_exception(spl_ce_InvalidArgumentException,
+                         "add_http2_port expects a string and a "
+                         "ServerCredentials", 1 TSRMLS_CC);
     return;
   }
   wrapped_grpc_server_credentials *creds =
diff --git a/src/php/ext/grpc/version.h b/src/php/ext/grpc/version.h
index 37df276..407d634 100644
--- a/src/php/ext/grpc/version.h
+++ b/src/php/ext/grpc/version.h
@@ -20,6 +20,6 @@
 #ifndef VERSION_H
 #define VERSION_H
 
-#define PHP_GRPC_VERSION "1.12.0dev"
+#define PHP_GRPC_VERSION "1.13.0dev"
 
 #endif /* VERSION_H */
diff --git a/src/php/lib/Grpc/BaseStub.php b/src/php/lib/Grpc/BaseStub.php
index 5f3a96f..7860233 100644
--- a/src/php/lib/Grpc/BaseStub.php
+++ b/src/php/lib/Grpc/BaseStub.php
@@ -38,12 +38,13 @@
      *  - 'update_metadata': (optional) a callback function which takes in a
      * metadata array, and returns an updated metadata array
      *  - 'grpc.primary_user_agent': (optional) a user-agent string
-     * @param Channel $channel An already created Channel object (optional)
+     * @param Channel|InterceptorChannel $channel An already created Channel or InterceptorChannel object (optional)
      */
-    public function __construct($hostname, $opts, Channel $channel = null)
+    public function __construct($hostname, $opts, $channel = null)
     {
         $ssl_roots = file_get_contents(
-            dirname(__FILE__).'/../../../../etc/roots.pem');
+            dirname(__FILE__).'/../../../../etc/roots.pem'
+        );
         ChannelCredentials::setDefaultRootsPem($ssl_roots);
 
         $this->hostname = $hostname;
@@ -58,16 +59,20 @@
             $this->hostname_override = $opts['grpc.ssl_target_name_override'];
         }
         if ($channel) {
-            if (!is_a($channel, 'Grpc\Channel')) {
-                throw new \Exception('The channel argument is not a'.
-                                     'Channel object');
+            if (!is_a($channel, 'Grpc\Channel') &&
+                !is_a($channel, 'Grpc\InterceptorChannel')) {
+                throw new \Exception('The channel argument is not a Channel object '.
+                    'or an InterceptorChannel object created by '.
+                    'Interceptor::intercept($channel, Interceptor|Interceptor[] $interceptors)');
             }
             $this->channel = $channel;
             return;
         }
 
         $package_config = json_decode(
-            file_get_contents(dirname(__FILE__).'/../../composer.json'), true);
+            file_get_contents(dirname(__FILE__).'/../../composer.json'),
+            true
+        );
         if (!empty($opts['grpc.primary_user_agent'])) {
             $opts['grpc.primary_user_agent'] .= ' ';
         } else {
@@ -77,8 +82,8 @@
             'grpc-php/'.$package_config['version'];
         if (!array_key_exists('credentials', $opts)) {
             throw new \Exception("The opts['credentials'] key is now ".
-                                 'required. Please see one of the '.
-                                 'ChannelCredentials::create methods');
+                'required. Please see one of the '.
+                'ChannelCredentials::create methods');
         }
         $this->channel = new Channel($hostname, $opts);
     }
@@ -169,7 +174,8 @@
         $last_slash_idx = strrpos($method, '/');
         if ($last_slash_idx === false) {
             throw new \InvalidArgumentException(
-                'service name must have a slash');
+                'service name must have a slash'
+            );
         }
         $service_name = substr($method, 0, $last_slash_idx);
 
@@ -197,7 +203,8 @@
             if (!preg_match('/^[A-Za-z\d_-]+$/', $key)) {
                 throw new \InvalidArgumentException(
                     'Metadata keys must be nonempty strings containing only '.
-                    'alphanumeric characters, hyphens and underscores');
+                    'alphanumeric characters, hyphens and underscores'
+                );
             }
             $metadata_copy[strtolower($key)] = $value;
         }
@@ -205,9 +212,255 @@
         return $metadata_copy;
     }
 
+    /**
+     * Create a function which can be used to create UnaryCall
+     *
+     * @param Channel|InterceptorChannel   $channel
+     * @param callable $deserialize A function that deserializes the response
+     *
+     * @return \Closure
+     */
+    private function _GrpcUnaryUnary($channel, $deserialize)
+    {
+        return function ($method,
+                         $argument,
+                         array $metadata = [],
+                         array $options = []) use ($channel, $deserialize) {
+            $call = new UnaryCall(
+                $channel,
+                $method,
+                $deserialize,
+                $options
+            );
+            $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
+            if (is_callable($this->update_metadata)) {
+                $metadata = call_user_func(
+                    $this->update_metadata,
+                    $metadata,
+                    $jwt_aud_uri
+                );
+            }
+            $metadata = $this->_validate_and_normalize_metadata(
+                $metadata
+            );
+            $call->start($argument, $metadata, $options);
+            return $call;
+        };
+    }
+
+    /**
+     * Create a function which can be used to create ServerStreamingCall
+     *
+     * @param Channel|InterceptorChannel   $channel
+     * @param callable $deserialize A function that deserializes the response
+     *
+     * @return \Closure
+     */
+    private function _GrpcStreamUnary($channel, $deserialize)
+    {
+        return function ($method,
+                         array $metadata = [],
+                         array $options = []) use ($channel, $deserialize) {
+            $call = new ClientStreamingCall(
+                $channel,
+                $method,
+                $deserialize,
+                $options
+            );
+            $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
+            if (is_callable($this->update_metadata)) {
+                $metadata = call_user_func(
+                    $this->update_metadata,
+                    $metadata,
+                    $jwt_aud_uri
+                );
+            }
+            $metadata = $this->_validate_and_normalize_metadata(
+                $metadata
+            );
+            $call->start($metadata);
+            return $call;
+        };
+    }
+
+    /**
+     * Create a function which can be used to create ClientStreamingCall
+     *
+     * @param Channel|InterceptorChannel   $channel
+     * @param callable $deserialize A function that deserializes the response
+     *
+     * @return \Closure
+     */
+    private function _GrpcUnaryStream($channel, $deserialize)
+    {
+        return function ($method,
+                         $argument,
+                         array $metadata = [],
+                         array $options = []) use ($channel, $deserialize) {
+            $call = new ServerStreamingCall(
+                $channel,
+                $method,
+                $deserialize,
+                $options
+            );
+            $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
+            if (is_callable($this->update_metadata)) {
+                $metadata = call_user_func(
+                    $this->update_metadata,
+                    $metadata,
+                    $jwt_aud_uri
+                );
+            }
+            $metadata = $this->_validate_and_normalize_metadata(
+                $metadata
+            );
+            $call->start($argument, $metadata, $options);
+            return $call;
+        };
+    }
+
+    /**
+     * Create a function which can be used to create BidiStreamingCall
+     *
+     * @param Channel|InterceptorChannel   $channel
+     * @param callable $deserialize A function that deserializes the response
+     *
+     * @return \Closure
+     */
+    private function _GrpcStreamStream($channel, $deserialize)
+    {
+        return function ($method,
+                         array $metadata = [],
+                         array $options = []) use ($channel ,$deserialize) {
+            $call = new BidiStreamingCall(
+                $channel,
+                $method,
+                $deserialize,
+                $options
+            );
+            $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
+            if (is_callable($this->update_metadata)) {
+                $metadata = call_user_func(
+                    $this->update_metadata,
+                    $metadata,
+                    $jwt_aud_uri
+                );
+            }
+            $metadata = $this->_validate_and_normalize_metadata(
+                $metadata
+            );
+            $call->start($metadata);
+
+            return $call;
+        };
+    }
+
+    /**
+     * Create a function which can be used to create UnaryCall
+     *
+     * @param Channel|InterceptorChannel   $channel
+     * @param callable $deserialize A function that deserializes the response
+     *
+     * @return \Closure
+     */
+    private function _UnaryUnaryCallFactory($channel, $deserialize)
+    {
+        if (is_a($channel, 'Grpc\InterceptorChannel')) {
+            return function ($method,
+                             $argument,
+                             array $metadata = [],
+                             array $options = []) use ($channel, $deserialize) {
+                return $channel->getInterceptor()->interceptUnaryUnary(
+                    $method,
+                    $argument,
+                    $metadata,
+                    $options,
+                    $this->_UnaryUnaryCallFactory($channel->getNext(), $deserialize)
+                );
+            };
+        }
+        return $this->_GrpcUnaryUnary($channel, $deserialize);
+    }
+
+    /**
+     * Create a function which can be used to create ServerStreamingCall
+     *
+     * @param Channel|InterceptorChannel   $channel
+     * @param callable $deserialize A function that deserializes the response
+     *
+     * @return \Closure
+     */
+    private function _UnaryStreamCallFactory($channel, $deserialize)
+    {
+        if (is_a($channel, 'Grpc\InterceptorChannel')) {
+            return function ($method,
+                             $argument,
+                             array $metadata = [],
+                             array $options = []) use ($channel, $deserialize) {
+                return $channel->getInterceptor()->interceptUnaryStream(
+                    $method,
+                    $argument,
+                    $metadata,
+                    $options,
+                    $this->_UnaryStreamCallFactory($channel->getNext(), $deserialize)
+                );
+            };
+        }
+        return $this->_GrpcUnaryStream($channel, $deserialize);
+    }
+
+    /**
+     * Create a function which can be used to create ClientStreamingCall
+     *
+     * @param Channel|InterceptorChannel   $channel
+     * @param callable $deserialize A function that deserializes the response
+     *
+     * @return \Closure
+     */
+    private function _StreamUnaryCallFactory($channel, $deserialize)
+    {
+        if (is_a($channel, 'Grpc\InterceptorChannel')) {
+            return function ($method,
+                             array $metadata = [],
+                             array $options = []) use ($channel, $deserialize) {
+                return $channel->getInterceptor()->interceptStreamUnary(
+                    $method,
+                    $metadata,
+                    $options,
+                    $this->_StreamUnaryCallFactory($channel->getNext(), $deserialize)
+                );
+            };
+        }
+        return $this->_GrpcStreamUnary($channel, $deserialize);
+    }
+
+    /**
+     * Create a function which can be used to create BidiStreamingCall
+     *
+     * @param Channel|InterceptorChannel   $channel
+     * @param callable $deserialize A function that deserializes the response
+     *
+     * @return \Closure
+     */
+    private function _StreamStreamCallFactory($channel, $deserialize)
+    {
+        if (is_a($channel, 'Grpc\InterceptorChannel')) {
+            return function ($method,
+                             array $metadata = [],
+                             array $options = []) use ($channel, $deserialize) {
+                return $channel->getInterceptor()->interceptStreamStream(
+                    $method,
+                    $metadata,
+                    $options,
+                    $this->_StreamStreamCallFactory($channel->getNext(), $deserialize)
+                );
+            };
+        }
+        return $this->_GrpcStreamStream($channel, $deserialize);
+    }
+
     /* This class is intended to be subclassed by generated code, so
      * all functions begin with "_" to avoid name collisions. */
-
     /**
      * Call a remote method that takes a single argument and has a
      * single output.
@@ -221,26 +474,15 @@
      *
      * @return UnaryCall The active call object
      */
-    protected function _simpleRequest($method,
-                                   $argument,
-                                   $deserialize,
-                                   array $metadata = [],
-                                   array $options = [])
-    {
-        $call = new UnaryCall($this->channel,
-                              $method,
-                              $deserialize,
-                              $options);
-        $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
-        if (is_callable($this->update_metadata)) {
-            $metadata = call_user_func($this->update_metadata,
-                                        $metadata,
-                                        $jwt_aud_uri);
-        }
-        $metadata = $this->_validate_and_normalize_metadata(
-            $metadata);
-        $call->start($argument, $metadata, $options);
-
+    protected function _simpleRequest(
+        $method,
+        $argument,
+        $deserialize,
+        array $metadata = [],
+        array $options = []
+    ) {
+        $call_factory = $this->_UnaryUnaryCallFactory($this->channel, $deserialize);
+        $call = $call_factory($method, $argument, $metadata, $options);
         return $call;
     }
 
@@ -256,25 +498,14 @@
      *
      * @return ClientStreamingCall The active call object
      */
-    protected function _clientStreamRequest($method,
-                                         $deserialize,
-                                         array $metadata = [],
-                                         array $options = [])
-    {
-        $call = new ClientStreamingCall($this->channel,
-                                        $method,
-                                        $deserialize,
-                                        $options);
-        $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
-        if (is_callable($this->update_metadata)) {
-            $metadata = call_user_func($this->update_metadata,
-                                        $metadata,
-                                        $jwt_aud_uri);
-        }
-        $metadata = $this->_validate_and_normalize_metadata(
-            $metadata);
-        $call->start($metadata);
-
+    protected function _clientStreamRequest(
+        $method,
+        $deserialize,
+        array $metadata = [],
+        array $options = []
+    ) {
+        $call_factory = $this->_StreamUnaryCallFactory($this->channel, $deserialize);
+        $call = $call_factory($method, $metadata, $options);
         return $call;
     }
 
@@ -291,26 +522,15 @@
      *
      * @return ServerStreamingCall The active call object
      */
-    protected function _serverStreamRequest($method,
-                                         $argument,
-                                         $deserialize,
-                                         array $metadata = [],
-                                         array $options = [])
-    {
-        $call = new ServerStreamingCall($this->channel,
-                                        $method,
-                                        $deserialize,
-                                        $options);
-        $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
-        if (is_callable($this->update_metadata)) {
-            $metadata = call_user_func($this->update_metadata,
-                                        $metadata,
-                                        $jwt_aud_uri);
-        }
-        $metadata = $this->_validate_and_normalize_metadata(
-            $metadata);
-        $call->start($argument, $metadata, $options);
-
+    protected function _serverStreamRequest(
+        $method,
+        $argument,
+        $deserialize,
+        array $metadata = [],
+        array $options = []
+    ) {
+        $call_factory = $this->_UnaryStreamCallFactory($this->channel, $deserialize);
+        $call = $call_factory($method, $argument, $metadata, $options);
         return $call;
     }
 
@@ -325,25 +545,14 @@
      *
      * @return BidiStreamingCall The active call object
      */
-    protected function _bidiRequest($method,
-                                 $deserialize,
-                                 array $metadata = [],
-                                 array $options = [])
-    {
-        $call = new BidiStreamingCall($this->channel,
-                                      $method,
-                                      $deserialize,
-                                      $options);
-        $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
-        if (is_callable($this->update_metadata)) {
-            $metadata = call_user_func($this->update_metadata,
-                                        $metadata,
-                                        $jwt_aud_uri);
-        }
-        $metadata = $this->_validate_and_normalize_metadata(
-            $metadata);
-        $call->start($metadata);
-
+    protected function _bidiRequest(
+        $method,
+        $deserialize,
+        array $metadata = [],
+        array $options = []
+    ) {
+        $call_factory = $this->_StreamStreamCallFactory($this->channel, $deserialize);
+        $call = $call_factory($method, $metadata, $options);
         return $call;
     }
 }
diff --git a/src/php/lib/Grpc/Interceptor.php b/src/php/lib/Grpc/Interceptor.php
new file mode 100644
index 0000000..9c1b561
--- /dev/null
+++ b/src/php/lib/Grpc/Interceptor.php
@@ -0,0 +1,86 @@
+<?php
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+namespace Grpc;
+
+/**
+ * Represents an interceptor that intercept RPC invocations before call starts.
+ * This is an EXPERIMENTAL API.
+ */
+class Interceptor
+{
+    public function interceptUnaryUnary(
+        $method,
+        $argument,
+        array $metadata = [],
+        array $options = [],
+        $continuation
+    ) {
+        return $continuation($method, $argument, $metadata, $options);
+    }
+
+    public function interceptStreamUnary(
+        $method,
+        array $metadata = [],
+        array $options = [],
+        $continuation
+    ) {
+        return $continuation($method, $metadata, $options);
+    }
+
+    public function interceptUnaryStream(
+        $method,
+        $argument,
+        array $metadata = [],
+        array $options = [],
+        $continuation
+    ) {
+        return $continuation($method, $argument, $metadata, $options);
+    }
+
+    public function interceptStreamStream(
+        $method,
+        array $metadata = [],
+        array $options = [],
+        $continuation
+    ) {
+        return $continuation($method, $metadata, $options);
+    }
+
+    /**
+     * Intercept the methods with Channel
+     *
+     * @param Channel|InterceptorChannel $channel An already created Channel or InterceptorChannel object (optional)
+     * @param Interceptor|Interceptor[] $interceptors interceptors to be added
+     *
+     * @return InterceptorChannel
+     */
+    public static function intercept($channel, $interceptors)
+    {
+        if (is_array($interceptors)) {
+            for ($i = count($interceptors) - 1; $i >= 0; $i--) {
+                $channel = new InterceptorChannel($channel, $interceptors[$i]);
+            }
+        } else {
+            $channel =  new InterceptorChannel($channel, $interceptors);
+        }
+        return $channel;
+    }
+}
+
diff --git a/src/php/lib/Grpc/Internal/InterceptorChannel.php b/src/php/lib/Grpc/Internal/InterceptorChannel.php
new file mode 100644
index 0000000..9ac0574
--- /dev/null
+++ b/src/php/lib/Grpc/Internal/InterceptorChannel.php
@@ -0,0 +1,76 @@
+<?php
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+namespace Grpc;
+
+/**
+ * This is a PRIVATE API and can change without notice.
+ */
+class InterceptorChannel
+{
+    private $next = null;
+    private $interceptor;
+
+    /**
+     * @param Channel|InterceptorChannel $channel An already created Channel
+     * or InterceptorChannel object (optional)
+     * @param Interceptor  $interceptor
+     */
+    public function __construct($channel, $interceptor)
+    {
+        if (!is_a($channel, 'Grpc\Channel') &&
+            !is_a($channel, 'Grpc\InterceptorChannel')) {
+            throw new \Exception('The channel argument is not a Channel object '.
+                'or an InterceptorChannel object created by '.
+                'Interceptor::intercept($channel, Interceptor|Interceptor[] $interceptors)');
+        }
+        $this->interceptor = $interceptor;
+        $this->next = $channel;
+    }
+
+    public function getNext()
+    {
+        return $this->next;
+    }
+
+    public function getInterceptor()
+    {
+        return $this->interceptor;
+    }
+
+    public function getTarget()
+    {
+        return $this->getNext()->getTarget();
+    }
+
+    public function watchConnectivityState($new_state, $deadline)
+    {
+        return $this->getNext()->watchConnectivityState($new_state, $deadline);
+    }
+
+    public function getConnectivityState($try_to_connect = false)
+    {
+        return $this->getNext()->getConnectivityState($try_to_connect);
+    }
+
+    public function close()
+    {
+        return $this->getNext()->close();
+    }
+}
diff --git a/src/php/tests/unit_tests/InterceptorTest.php b/src/php/tests/unit_tests/InterceptorTest.php
new file mode 100644
index 0000000..08f5abb
--- /dev/null
+++ b/src/php/tests/unit_tests/InterceptorTest.php
@@ -0,0 +1,427 @@
+<?php
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+/**
+ * Interface exported by the server.
+ */
+require_once(dirname(__FILE__).'/../../lib/Grpc/BaseStub.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/AbstractCall.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/UnaryCall.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/ClientStreamingCall.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/Interceptor.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/Internal/InterceptorChannel.php');
+
+class SimpleRequest
+{
+    private $data;
+    public function __construct($data)
+    {
+        $this->data = $data;
+    }
+    public function setData($data)
+    {
+        $this->data = $data;
+    }
+    public function serializeToString()
+    {
+        return $this->data;
+    }
+}
+
+class InterceptorClient extends Grpc\BaseStub
+{
+
+    /**
+     * @param string $hostname hostname
+     * @param array $opts channel options
+     * @param Channel|InterceptorChannel $channel (optional) re-use channel object
+     */
+    public function __construct($hostname, $opts, $channel = null)
+    {
+        parent::__construct($hostname, $opts, $channel);
+    }
+
+    /**
+     * A simple RPC.
+     * @param \Routeguide\Point $argument input argument
+     * @param array $metadata metadata
+     * @param array $options call options
+     */
+    public function UnaryCall(
+        SimpleRequest $argument,
+        $metadata = [],
+        $options = []
+    ) {
+        return $this->_simpleRequest(
+            '/dummy_method',
+            $argument,
+            [],
+            $metadata,
+            $options
+        );
+    }
+
+    /**
+     * A client-to-server streaming RPC.
+     * @param array $metadata metadata
+     * @param array $options call options
+     */
+    public function StreamCall(
+        $metadata = [],
+        $options = []
+    ) {
+        return $this->_clientStreamRequest('/dummy_method', [], $metadata, $options);
+    }
+}
+
+
+class ChangeMetadataInterceptor extends Grpc\Interceptor
+{
+    public function interceptUnaryUnary($method,
+                                        $argument,
+                                        array $metadata = [],
+                                        array $options = [],
+                                        $continuation)
+    {
+        $metadata["foo"] = array('interceptor_from_unary_request');
+        return $continuation($method, $argument, $metadata, $options);
+    }
+    public function interceptStreamUnary($method, array $metadata = [], array $options = [], $continuation)
+    {
+        $metadata["foo"] = array('interceptor_from_stream_request');
+        return $continuation($method, $metadata, $options);
+    }
+}
+
+class ChangeMetadataInterceptor2 extends Grpc\Interceptor
+{
+    public function interceptUnaryUnary($method,
+                                        $argument,
+                                        array $metadata = [],
+                                        array $options = [],
+                                        $continuation)
+    {
+        if (array_key_exists('foo', $metadata)) {
+            $metadata['bar'] = array('ChangeMetadataInterceptor should be executed first');
+        } else {
+            $metadata["bar"] = array('interceptor_from_unary_request');
+        }
+        return $continuation($method, $argument, $metadata, $options);
+    }
+    public function interceptStreamUnary($method,
+                                         array $metadata = [],
+                                         array $options = [],
+                                         $continuation)
+    {
+        if (array_key_exists('foo', $metadata)) {
+            $metadata['bar'] = array('ChangeMetadataInterceptor should be executed first');
+        } else {
+            $metadata["bar"] = array('interceptor_from_stream_request');
+        }
+        return $continuation($method, $metadata, $options);
+    }
+}
+
+class ChangeRequestCall
+{
+    private $call;
+
+    public function __construct($call)
+    {
+        $this->call = $call;
+    }
+    public function getCall()
+    {
+        return $this->call;
+    }
+
+    public function write($request)
+    {
+        $request->setData('intercepted_stream_request');
+        $this->getCall()->write($request);
+    }
+
+    public function wait()
+    {
+        return $this->getCall()->wait();
+    }
+}
+
+class ChangeRequestInterceptor extends Grpc\Interceptor
+{
+    public function interceptUnaryUnary($method,
+                                        $argument,
+                                        array $metadata = [],
+                                        array $options = [],
+                                        $continuation)
+    {
+        $argument->setData('intercepted_unary_request');
+        return $continuation($method, $argument, $metadata, $options);
+    }
+    public function interceptStreamUnary($method, array $metadata = [], array $options = [], $continuation)
+    {
+        return new ChangeRequestCall(
+            $continuation($method, $metadata, $options)
+        );
+    }
+}
+
+class StopCallInterceptor extends Grpc\Interceptor
+{
+    public function interceptUnaryUnary($method,
+                                        $argument,
+                                        array $metadata = [],
+                                        array $options = [],
+                                        $continuation)
+    {
+        $metadata["foo"] = array('interceptor_from_request_response');
+    }
+    public function interceptStreamUnary($method,
+                                         array $metadata = [],
+                                         array $options = [],
+                                         $continuation)
+    {
+        $metadata["foo"] = array('interceptor_from_request_response');
+    }
+}
+
+class InterceptorTest extends PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+        $this->server = new Grpc\Server([]);
+        $this->port = $this->server->addHttp2Port('0.0.0.0:0');
+        $this->channel = new Grpc\Channel('localhost:'.$this->port, ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
+        $this->server->start();
+    }
+
+    public function tearDown()
+    {
+        $this->channel->close();
+    }
+
+
+    public function testClientChangeMetadataOneInterceptor()
+    {
+        $req_text = 'client_request';
+        $channel_matadata_interceptor = new ChangeMetadataInterceptor();
+        $intercept_channel = Grpc\Interceptor::intercept($this->channel, $channel_matadata_interceptor);
+        echo "create Client\n";
+        $client = new InterceptorClient('localhost:'.$this->port, [
+            'credentials' => Grpc\ChannelCredentials::createInsecure(),
+        ], $intercept_channel);
+        echo "create Call\n";
+        $req = new SimpleRequest($req_text);
+        echo "Call created\n";
+        $unary_call = $client->UnaryCall($req);
+        echo "start call\n";
+        $event = $this->server->requestCall();
+        $this->assertSame('/dummy_method', $event->method);
+        $this->assertSame(['interceptor_from_unary_request'], $event->metadata['foo']);
+
+        $stream_call = $client->StreamCall();
+        $stream_call->write($req);
+        $event = $this->server->requestCall();
+        $this->assertSame('/dummy_method', $event->method);
+        $this->assertSame(['interceptor_from_stream_request'], $event->metadata['foo']);
+
+        unset($unary_call);
+        unset($stream_call);
+        unset($server_call);
+    }
+
+    public function testClientChangeMetadataTwoInterceptor()
+    {
+        $req_text = 'client_request';
+        $channel_matadata_interceptor = new ChangeMetadataInterceptor();
+        $channel_matadata_intercepto2 = new ChangeMetadataInterceptor2();
+        // test intercept separately.
+        $intercept_channel1 = Grpc\Interceptor::intercept($this->channel, $channel_matadata_interceptor);
+        $intercept_channel2 = Grpc\Interceptor::intercept($intercept_channel1, $channel_matadata_intercepto2);
+        $client = new InterceptorClient('localhost:'.$this->port, [
+            'credentials' => Grpc\ChannelCredentials::createInsecure(),
+        ], $intercept_channel2);
+
+        $req = new SimpleRequest($req_text);
+        $unary_call = $client->UnaryCall($req);
+        $event = $this->server->requestCall();
+        $this->assertSame('/dummy_method', $event->method);
+        $this->assertSame(['interceptor_from_unary_request'], $event->metadata['foo']);
+        $this->assertSame(['interceptor_from_unary_request'], $event->metadata['bar']);
+
+        $stream_call = $client->StreamCall();
+        $stream_call->write($req);
+        $event = $this->server->requestCall();
+        $this->assertSame('/dummy_method', $event->method);
+        $this->assertSame(['interceptor_from_stream_request'], $event->metadata['foo']);
+        $this->assertSame(['interceptor_from_stream_request'], $event->metadata['bar']);
+
+        unset($unary_call);
+        unset($stream_call);
+        unset($server_call);
+
+        // test intercept by array.
+        $intercept_channel3 = Grpc\Interceptor::intercept($this->channel,
+            [$channel_matadata_intercepto2, $channel_matadata_interceptor]);
+        $client = new InterceptorClient('localhost:'.$this->port, [
+            'credentials' => Grpc\ChannelCredentials::createInsecure(),
+        ], $intercept_channel3);
+
+        $req = new SimpleRequest($req_text);
+        $unary_call = $client->UnaryCall($req);
+        $event = $this->server->requestCall();
+        $this->assertSame('/dummy_method', $event->method);
+        $this->assertSame(['interceptor_from_unary_request'], $event->metadata['foo']);
+        $this->assertSame(['interceptor_from_unary_request'], $event->metadata['bar']);
+
+        $stream_call = $client->StreamCall();
+        $stream_call->write($req);
+        $event = $this->server->requestCall();
+        $this->assertSame('/dummy_method', $event->method);
+        $this->assertSame(['interceptor_from_stream_request'], $event->metadata['foo']);
+        $this->assertSame(['interceptor_from_stream_request'], $event->metadata['bar']);
+
+        unset($unary_call);
+        unset($stream_call);
+        unset($server_call);
+    }
+
+    public function testClientChangeRequestInterceptor()
+    {
+        $req_text = 'client_request';
+        $change_request_interceptor = new ChangeRequestInterceptor();
+        $intercept_channel = Grpc\Interceptor::intercept($this->channel,
+            $change_request_interceptor);
+        $client = new InterceptorClient('localhost:'.$this->port, [
+            'credentials' => Grpc\ChannelCredentials::createInsecure(),
+        ], $intercept_channel);
+
+        $req = new SimpleRequest($req_text);
+        $unary_call = $client->UnaryCall($req);
+
+        $event = $this->server->requestCall();
+        $this->assertSame('/dummy_method', $event->method);
+        $server_call = $event->call;
+        $event = $server_call->startBatch([
+            Grpc\OP_SEND_INITIAL_METADATA => [],
+            Grpc\OP_SEND_STATUS_FROM_SERVER => [
+                'metadata' => [],
+                'code' => Grpc\STATUS_OK,
+                'details' => '',
+            ],
+            Grpc\OP_RECV_MESSAGE => true,
+            Grpc\OP_RECV_CLOSE_ON_SERVER => true,
+        ]);
+        $this->assertSame('intercepted_unary_request', $event->message);
+
+        $stream_call = $client->StreamCall();
+        $stream_call->write($req);
+        $event = $this->server->requestCall();
+        $this->assertSame('/dummy_method', $event->method);
+        $server_call = $event->call;
+        $event = $server_call->startBatch([
+            Grpc\OP_SEND_INITIAL_METADATA => [],
+            Grpc\OP_SEND_STATUS_FROM_SERVER => [
+                'metadata' => [],
+                'code' => Grpc\STATUS_OK,
+                'details' => '',
+            ],
+            Grpc\OP_RECV_MESSAGE => true,
+            Grpc\OP_RECV_CLOSE_ON_SERVER => true,
+        ]);
+        $this->assertSame('intercepted_stream_request', $event->message);
+
+        unset($unary_call);
+        unset($stream_call);
+        unset($server_call);
+    }
+
+    public function testClientChangeStopCallInterceptor()
+    {
+        $req_text = 'client_request';
+        $channel_request_interceptor = new StopCallInterceptor();
+        $intercept_channel = Grpc\Interceptor::intercept($this->channel,
+            $channel_request_interceptor);
+        $client = new InterceptorClient('localhost:'.$this->port, [
+            'credentials' => Grpc\ChannelCredentials::createInsecure(),
+        ], $intercept_channel);
+
+        $req = new SimpleRequest($req_text);
+        $unary_call = $client->UnaryCall($req);
+        $this->assertNull($unary_call);
+
+
+        $stream_call = $client->StreamCall();
+        $this->assertNull($stream_call);
+
+        unset($unary_call);
+        unset($stream_call);
+        unset($server_call);
+    }
+
+    public function testGetInterceptorChannelConnectivityState()
+    {
+        $channel = new Grpc\Channel(
+            'localhost:0',
+            ['credentials' => Grpc\ChannelCredentials::createInsecure()]
+        );
+        $interceptor_channel = Grpc\Interceptor::intercept($channel, new Grpc\Interceptor());
+        $state = $interceptor_channel->getConnectivityState();
+        $this->assertEquals(0, $state);
+        $channel->close();
+    }
+
+    public function testInterceptorChannelWatchConnectivityState()
+    {
+        $channel = new Grpc\Channel(
+            'localhost:0',
+            ['credentials' => Grpc\ChannelCredentials::createInsecure()]
+        );
+        $interceptor_channel = Grpc\Interceptor::intercept($channel, new Grpc\Interceptor());
+        $now = Grpc\Timeval::now();
+        $deadline = $now->add(new Grpc\Timeval(100*1000));
+        $state = $interceptor_channel->watchConnectivityState(1, $deadline);
+        $this->assertTrue($state);
+        unset($time);
+        unset($deadline);
+        $channel->close();
+    }
+
+    public function testInterceptorChannelClose()
+    {
+        $channel = new Grpc\Channel(
+            'localhost:0',
+            ['credentials' => Grpc\ChannelCredentials::createInsecure()]
+        );
+        $interceptor_channel = Grpc\Interceptor::intercept($channel, new Grpc\Interceptor());
+        $this->assertNotNull($interceptor_channel);
+        $channel->close();
+    }
+
+    public function testInterceptorChannelGetTarget()
+    {
+        $channel = new Grpc\Channel(
+            'localhost:8888',
+            ['credentials' => Grpc\ChannelCredentials::createInsecure()]
+        );
+        $interceptor_channel = Grpc\Interceptor::intercept($channel, new Grpc\Interceptor());
+        $target = $interceptor_channel->getTarget();
+        $this->assertTrue(is_string($target));
+        $channel->close();
+    }
+}
diff --git a/src/php/tests/unit_tests/ServerTest.php b/src/php/tests/unit_tests/ServerTest.php
index fee9f1e..ac6f2f0 100644
--- a/src/php/tests/unit_tests/ServerTest.php
+++ b/src/php/tests/unit_tests/ServerTest.php
@@ -99,6 +99,24 @@
     /**
      * @expectedException InvalidArgumentException
      */
+    public function testInvalidConstructorWithNumKeyOfArray()
+    {
+        $this->server = new Grpc\Server([10 => '127.0.0.1',
+                                         20 => '8080', ]);
+        $this->assertNull($this->server);
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testInvalidConstructorWithList()
+    {
+        $this->server = new Grpc\Server(['127.0.0.1', '8080']);
+        $this->assertNull($this->server);
+    }
+    /**
+     * @expectedException InvalidArgumentException
+     */
     public function testInvalidAddHttp2Port()
     {
         $this->server = new Grpc\Server([]);
diff --git a/src/proto/grpc/health/v1/health.proto b/src/proto/grpc/health/v1/health.proto
index 3fda02c..4b4677b 100644
--- a/src/proto/grpc/health/v1/health.proto
+++ b/src/proto/grpc/health/v1/health.proto
@@ -1,4 +1,4 @@
-// Copyright 2015 gRPC authors.
+// Copyright 2015 The gRPC Authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,10 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// The canonical version of this proto can be found at
+// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto
+
 syntax = "proto3";
 
 package grpc.health.v1;
+
 option csharp_namespace = "Grpc.Health.V1";
+option go_package = "google.golang.org/grpc/health/grpc_health_v1";
+option java_multiple_files = true;
+option java_outer_classname = "HealthProto";
+option java_package = "io.grpc.health.v1";
 
 message HealthCheckRequest {
   string service = 1;
diff --git a/src/python/grpcio/grpc/__init__.py b/src/python/grpcio/grpc/__init__.py
index 7fa7303..b7ed0c8 100644
--- a/src/python/grpcio/grpc/__init__.py
+++ b/src/python/grpcio/grpc/__init__.py
@@ -813,7 +813,11 @@
 
 
 class Channel(six.with_metaclass(abc.ABCMeta)):
-    """Affords RPC invocation via generic methods on client-side."""
+    """Affords RPC invocation via generic methods on client-side.
+
+    Channel objects implement the Context Manager type, although they need not
+    support being entered and exited multiple times.
+    """
 
     @abc.abstractmethod
     def subscribe(self, callback, try_to_connect=False):
@@ -926,6 +930,17 @@
         """
         raise NotImplementedError()
 
+    @abc.abstractmethod
+    def close(self):
+        """Closes this Channel and releases all resources held by it.
+
+        Closing the Channel will immediately terminate all RPCs active with the
+        Channel and it is not valid to invoke new RPCs with the Channel.
+
+        This method is idempotent.
+        """
+        raise NotImplementedError()
+
 
 ##########################  Service-Side Context  ##############################
 
diff --git a/src/python/grpcio/grpc/_channel.py b/src/python/grpcio/grpc/_channel.py
index 2eff08a..8cc0e98 100644
--- a/src/python/grpcio/grpc/_channel.py
+++ b/src/python/grpcio/grpc/_channel.py
@@ -79,27 +79,6 @@
             condition.wait(timeout=remaining)
 
 
-_INTERNAL_CALL_ERROR_MESSAGE_FORMAT = (
-    'Internal gRPC call error %d. ' +
-    'Please report to https://github.com/grpc/grpc/issues')
-
-
-def _check_call_error(call_error, metadata):
-    if call_error == cygrpc.CallError.invalid_metadata:
-        raise ValueError('metadata was invalid: %s' % metadata)
-    elif call_error != cygrpc.CallError.ok:
-        raise ValueError(_INTERNAL_CALL_ERROR_MESSAGE_FORMAT % call_error)
-
-
-def _call_error_set_RPCstate(state, call_error, metadata):
-    if call_error == cygrpc.CallError.invalid_metadata:
-        _abort(state, grpc.StatusCode.INTERNAL,
-               'metadata was invalid: %s' % metadata)
-    else:
-        _abort(state, grpc.StatusCode.INTERNAL,
-               _INTERNAL_CALL_ERROR_MESSAGE_FORMAT % call_error)
-
-
 class _RPCState(object):
 
     def __init__(self, due, initial_metadata, trailing_metadata, code, details):
@@ -163,7 +142,7 @@
     return callbacks
 
 
-def _event_handler(state, call, response_deserializer):
+def _event_handler(state, response_deserializer):
 
     def handle_event(event):
         with state.condition:
@@ -172,40 +151,47 @@
             done = not state.due
         for callback in callbacks:
             callback()
-        return call if done else None
+        return done
 
     return handle_event
 
 
-def _consume_request_iterator(request_iterator, state, call,
-                              request_serializer):
-    event_handler = _event_handler(state, call, None)
+def _consume_request_iterator(request_iterator, state, call, request_serializer,
+                              event_handler):
 
-    def consume_request_iterator():
+    def consume_request_iterator():  # pylint: disable=too-many-branches
         while True:
             try:
                 request = next(request_iterator)
             except StopIteration:
                 break
             except Exception:  # pylint: disable=broad-except
-                logging.exception("Exception iterating requests!")
-                call.cancel()
-                _abort(state, grpc.StatusCode.UNKNOWN,
-                       "Exception iterating requests!")
+                code = grpc.StatusCode.UNKNOWN
+                details = 'Exception iterating requests!'
+                logging.exception(details)
+                call.cancel(_common.STATUS_CODE_TO_CYGRPC_STATUS_CODE[code],
+                            details)
+                _abort(state, code, details)
                 return
             serialized_request = _common.serialize(request, request_serializer)
             with state.condition:
                 if state.code is None and not state.cancelled:
                     if serialized_request is None:
-                        call.cancel()
+                        code = grpc.StatusCode.INTERNAL  # pylint: disable=redefined-variable-type
                         details = 'Exception serializing request!'
-                        _abort(state, grpc.StatusCode.INTERNAL, details)
+                        call.cancel(
+                            _common.STATUS_CODE_TO_CYGRPC_STATUS_CODE[code],
+                            details)
+                        _abort(state, code, details)
                         return
                     else:
                         operations = (cygrpc.SendMessageOperation(
                             serialized_request, _EMPTY_FLAGS),)
-                        call.start_client_batch(operations, event_handler)
-                        state.due.add(cygrpc.OperationType.send_message)
+                        operating = call.operate(operations, event_handler)
+                        if operating:
+                            state.due.add(cygrpc.OperationType.send_message)
+                        else:
+                            return
                         while True:
                             state.condition.wait()
                             if state.code is None:
@@ -219,19 +205,12 @@
             if state.code is None:
                 operations = (
                     cygrpc.SendCloseFromClientOperation(_EMPTY_FLAGS),)
-                call.start_client_batch(operations, event_handler)
-                state.due.add(cygrpc.OperationType.send_close_from_client)
+                operating = call.operate(operations, event_handler)
+                if operating:
+                    state.due.add(cygrpc.OperationType.send_close_from_client)
 
-    def stop_consumption_thread(timeout):  # pylint: disable=unused-argument
-        with state.condition:
-            if state.code is None:
-                call.cancel()
-                state.cancelled = True
-                _abort(state, grpc.StatusCode.CANCELLED, 'Cancelled!')
-                state.condition.notify_all()
-
-    consumption_thread = _common.CleanupThread(
-        stop_consumption_thread, target=consume_request_iterator)
+    consumption_thread = threading.Thread(target=consume_request_iterator)
+    consumption_thread.daemon = True
     consumption_thread.start()
 
 
@@ -247,9 +226,12 @@
     def cancel(self):
         with self._state.condition:
             if self._state.code is None:
-                self._call.cancel()
+                code = grpc.StatusCode.CANCELLED
+                details = 'Locally cancelled by application!'
+                self._call.cancel(
+                    _common.STATUS_CODE_TO_CYGRPC_STATUS_CODE[code], details)
                 self._state.cancelled = True
-                _abort(self._state, grpc.StatusCode.CANCELLED, 'Cancelled!')
+                _abort(self._state, code, details)
                 self._state.condition.notify_all()
             return False
 
@@ -318,12 +300,13 @@
     def _next(self):
         with self._state.condition:
             if self._state.code is None:
-                event_handler = _event_handler(self._state, self._call,
+                event_handler = _event_handler(self._state,
                                                self._response_deserializer)
-                self._call.start_client_batch(
+                operating = self._call.operate(
                     (cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),),
                     event_handler)
-                self._state.due.add(cygrpc.OperationType.receive_message)
+                if operating:
+                    self._state.due.add(cygrpc.OperationType.receive_message)
             elif self._state.code is grpc.StatusCode.OK:
                 raise StopIteration()
             else:
@@ -408,9 +391,12 @@
     def __del__(self):
         with self._state.condition:
             if self._state.code is None:
-                self._call.cancel()
-                self._state.cancelled = True
                 self._state.code = grpc.StatusCode.CANCELLED
+                self._state.details = 'Cancelled upon garbage collection!'
+                self._state.cancelled = True
+                self._call.cancel(
+                    _common.STATUS_CODE_TO_CYGRPC_STATUS_CODE[self._state.code],
+                    self._state.details)
                 self._state.condition.notify_all()
 
 
@@ -437,6 +423,24 @@
         raise _Rendezvous(state, None, None, deadline)
 
 
+def _stream_unary_invocation_operationses(metadata):
+    return (
+        (
+            cygrpc.SendInitialMetadataOperation(metadata, _EMPTY_FLAGS),
+            cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),
+            cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
+        ),
+        (cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),),
+    )
+
+
+def _stream_unary_invocation_operationses_and_tags(metadata):
+    return tuple((
+        operations,
+        None,
+    ) for operations in _stream_unary_invocation_operationses(metadata))
+
+
 class _UnaryUnaryMultiCallable(grpc.UnaryUnaryMultiCallable):
 
     def __init__(self, channel, managed_call, method, request_serializer,
@@ -448,8 +452,8 @@
         self._response_deserializer = response_deserializer
 
     def _prepare(self, request, timeout, metadata):
-        deadline, serialized_request, rendezvous = (_start_unary_request(
-            request, timeout, self._request_serializer))
+        deadline, serialized_request, rendezvous = _start_unary_request(
+            request, timeout, self._request_serializer)
         if serialized_request is None:
             return None, None, None, rendezvous
         else:
@@ -467,48 +471,38 @@
     def _blocking(self, request, timeout, metadata, credentials):
         state, operations, deadline, rendezvous = self._prepare(
             request, timeout, metadata)
-        if rendezvous:
+        if state is None:
             raise rendezvous
         else:
-            completion_queue = cygrpc.CompletionQueue()
-            call = self._channel.create_call(None, 0, completion_queue,
-                                             self._method, None, deadline)
-            if credentials is not None:
-                call.set_credentials(credentials._credentials)
-            call_error = call.start_client_batch(operations, None)
-            _check_call_error(call_error, metadata)
-            _handle_event(completion_queue.poll(), state,
-                          self._response_deserializer)
-            return state, call, deadline
+            call = self._channel.segregated_call(
+                0, self._method, None, deadline, metadata, None
+                if credentials is None else credentials._credentials, ((
+                    operations,
+                    None,
+                ),))
+            event = call.next_event()
+            _handle_event(event, state, self._response_deserializer)
+            return state, call,
 
     def __call__(self, request, timeout=None, metadata=None, credentials=None):
-        state, call, deadline = self._blocking(request, timeout, metadata,
-                                               credentials)
-        return _end_unary_response_blocking(state, call, False, deadline)
+        state, call, = self._blocking(request, timeout, metadata, credentials)
+        return _end_unary_response_blocking(state, call, False, None)
 
     def with_call(self, request, timeout=None, metadata=None, credentials=None):
-        state, call, deadline = self._blocking(request, timeout, metadata,
-                                               credentials)
-        return _end_unary_response_blocking(state, call, True, deadline)
+        state, call, = self._blocking(request, timeout, metadata, credentials)
+        return _end_unary_response_blocking(state, call, True, None)
 
     def future(self, request, timeout=None, metadata=None, credentials=None):
         state, operations, deadline, rendezvous = self._prepare(
             request, timeout, metadata)
-        if rendezvous:
-            return rendezvous
+        if state is None:
+            raise rendezvous
         else:
-            call, drive_call = self._managed_call(None, 0, self._method, None,
-                                                  deadline)
-            if credentials is not None:
-                call.set_credentials(credentials._credentials)
-            event_handler = _event_handler(state, call,
-                                           self._response_deserializer)
-            with state.condition:
-                call_error = call.start_client_batch(operations, event_handler)
-                if call_error != cygrpc.CallError.ok:
-                    _call_error_set_RPCstate(state, call_error, metadata)
-                    return _Rendezvous(state, None, None, deadline)
-                drive_call()
+            event_handler = _event_handler(state, self._response_deserializer)
+            call = self._managed_call(
+                0, self._method, None, deadline, metadata, None
+                if credentials is None else credentials._credentials,
+                (operations,), event_handler)
             return _Rendezvous(state, call, self._response_deserializer,
                                deadline)
 
@@ -524,34 +518,27 @@
         self._response_deserializer = response_deserializer
 
     def __call__(self, request, timeout=None, metadata=None, credentials=None):
-        deadline, serialized_request, rendezvous = (_start_unary_request(
-            request, timeout, self._request_serializer))
+        deadline, serialized_request, rendezvous = _start_unary_request(
+            request, timeout, self._request_serializer)
         if serialized_request is None:
             raise rendezvous
         else:
             state = _RPCState(_UNARY_STREAM_INITIAL_DUE, None, None, None, None)
-            call, drive_call = self._managed_call(None, 0, self._method, None,
-                                                  deadline)
-            if credentials is not None:
-                call.set_credentials(credentials._credentials)
-            event_handler = _event_handler(state, call,
-                                           self._response_deserializer)
-            with state.condition:
-                call.start_client_batch(
-                    (cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),),
-                    event_handler)
-                operations = (
+            operationses = (
+                (
                     cygrpc.SendInitialMetadataOperation(metadata, _EMPTY_FLAGS),
                     cygrpc.SendMessageOperation(serialized_request,
                                                 _EMPTY_FLAGS),
                     cygrpc.SendCloseFromClientOperation(_EMPTY_FLAGS),
                     cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
-                )
-                call_error = call.start_client_batch(operations, event_handler)
-                if call_error != cygrpc.CallError.ok:
-                    _call_error_set_RPCstate(state, call_error, metadata)
-                    return _Rendezvous(state, None, None, deadline)
-                drive_call()
+                ),
+                (cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),),
+            )
+            event_handler = _event_handler(state, self._response_deserializer)
+            call = self._managed_call(
+                0, self._method, None, deadline, metadata, None
+                if credentials is None else credentials._credentials,
+                operationses, event_handler)
             return _Rendezvous(state, call, self._response_deserializer,
                                deadline)
 
@@ -569,49 +556,38 @@
     def _blocking(self, request_iterator, timeout, metadata, credentials):
         deadline = _deadline(timeout)
         state = _RPCState(_STREAM_UNARY_INITIAL_DUE, None, None, None, None)
-        completion_queue = cygrpc.CompletionQueue()
-        call = self._channel.create_call(None, 0, completion_queue,
-                                         self._method, None, deadline)
-        if credentials is not None:
-            call.set_credentials(credentials._credentials)
-        with state.condition:
-            call.start_client_batch(
-                (cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),), None)
-            operations = (
-                cygrpc.SendInitialMetadataOperation(metadata, _EMPTY_FLAGS),
-                cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),
-                cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
-            )
-            call_error = call.start_client_batch(operations, None)
-            _check_call_error(call_error, metadata)
-            _consume_request_iterator(request_iterator, state, call,
-                                      self._request_serializer)
+        call = self._channel.segregated_call(
+            0, self._method, None, deadline, metadata, None
+            if credentials is None else credentials._credentials,
+            _stream_unary_invocation_operationses_and_tags(metadata))
+        _consume_request_iterator(request_iterator, state, call,
+                                  self._request_serializer, None)
         while True:
-            event = completion_queue.poll()
+            event = call.next_event()
             with state.condition:
                 _handle_event(event, state, self._response_deserializer)
                 state.condition.notify_all()
                 if not state.due:
                     break
-        return state, call, deadline
+        return state, call,
 
     def __call__(self,
                  request_iterator,
                  timeout=None,
                  metadata=None,
                  credentials=None):
-        state, call, deadline = self._blocking(request_iterator, timeout,
-                                               metadata, credentials)
-        return _end_unary_response_blocking(state, call, False, deadline)
+        state, call, = self._blocking(request_iterator, timeout, metadata,
+                                      credentials)
+        return _end_unary_response_blocking(state, call, False, None)
 
     def with_call(self,
                   request_iterator,
                   timeout=None,
                   metadata=None,
                   credentials=None):
-        state, call, deadline = self._blocking(request_iterator, timeout,
-                                               metadata, credentials)
-        return _end_unary_response_blocking(state, call, True, deadline)
+        state, call, = self._blocking(request_iterator, timeout, metadata,
+                                      credentials)
+        return _end_unary_response_blocking(state, call, True, None)
 
     def future(self,
                request_iterator,
@@ -620,27 +596,13 @@
                credentials=None):
         deadline = _deadline(timeout)
         state = _RPCState(_STREAM_UNARY_INITIAL_DUE, None, None, None, None)
-        call, drive_call = self._managed_call(None, 0, self._method, None,
-                                              deadline)
-        if credentials is not None:
-            call.set_credentials(credentials._credentials)
-        event_handler = _event_handler(state, call, self._response_deserializer)
-        with state.condition:
-            call.start_client_batch(
-                (cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),),
-                event_handler)
-            operations = (
-                cygrpc.SendInitialMetadataOperation(metadata, _EMPTY_FLAGS),
-                cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),
-                cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
-            )
-            call_error = call.start_client_batch(operations, event_handler)
-            if call_error != cygrpc.CallError.ok:
-                _call_error_set_RPCstate(state, call_error, metadata)
-                return _Rendezvous(state, None, None, deadline)
-            drive_call()
-            _consume_request_iterator(request_iterator, state, call,
-                                      self._request_serializer)
+        event_handler = _event_handler(state, self._response_deserializer)
+        call = self._managed_call(
+            0, self._method, None, deadline, metadata, None
+            if credentials is None else credentials._credentials,
+            _stream_unary_invocation_operationses(metadata), event_handler)
+        _consume_request_iterator(request_iterator, state, call,
+                                  self._request_serializer, event_handler)
         return _Rendezvous(state, call, self._response_deserializer, deadline)
 
 
@@ -661,26 +623,20 @@
                  credentials=None):
         deadline = _deadline(timeout)
         state = _RPCState(_STREAM_STREAM_INITIAL_DUE, None, None, None, None)
-        call, drive_call = self._managed_call(None, 0, self._method, None,
-                                              deadline)
-        if credentials is not None:
-            call.set_credentials(credentials._credentials)
-        event_handler = _event_handler(state, call, self._response_deserializer)
-        with state.condition:
-            call.start_client_batch(
-                (cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),),
-                event_handler)
-            operations = (
+        operationses = (
+            (
                 cygrpc.SendInitialMetadataOperation(metadata, _EMPTY_FLAGS),
                 cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
-            )
-            call_error = call.start_client_batch(operations, event_handler)
-            if call_error != cygrpc.CallError.ok:
-                _call_error_set_RPCstate(state, call_error, metadata)
-                return _Rendezvous(state, None, None, deadline)
-            drive_call()
-            _consume_request_iterator(request_iterator, state, call,
-                                      self._request_serializer)
+            ),
+            (cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),),
+        )
+        event_handler = _event_handler(state, self._response_deserializer)
+        call = self._managed_call(
+            0, self._method, None, deadline, metadata, None
+            if credentials is None else credentials._credentials, operationses,
+            event_handler)
+        _consume_request_iterator(request_iterator, state, call,
+                                  self._request_serializer, event_handler)
         return _Rendezvous(state, call, self._response_deserializer, deadline)
 
 
@@ -689,67 +645,63 @@
     def __init__(self, channel):
         self.lock = threading.Lock()
         self.channel = channel
-        self.completion_queue = cygrpc.CompletionQueue()
-        self.managed_calls = None
+        self.managed_calls = 0
 
 
 def _run_channel_spin_thread(state):
 
     def channel_spin():
         while True:
-            event = state.completion_queue.poll()
-            completed_call = event.tag(event)
-            if completed_call is not None:
+            event = state.channel.next_call_event()
+            call_completed = event.tag(event)
+            if call_completed:
                 with state.lock:
-                    state.managed_calls.remove(completed_call)
-                    if not state.managed_calls:
-                        state.managed_calls = None
+                    state.managed_calls -= 1
+                    if state.managed_calls == 0:
                         return
 
-    def stop_channel_spin(timeout):  # pylint: disable=unused-argument
-        with state.lock:
-            if state.managed_calls is not None:
-                for call in state.managed_calls:
-                    call.cancel()
-
-    channel_spin_thread = _common.CleanupThread(
-        stop_channel_spin, target=channel_spin)
+    channel_spin_thread = threading.Thread(target=channel_spin)
+    channel_spin_thread.daemon = True
     channel_spin_thread.start()
 
 
 def _channel_managed_call_management(state):
 
-    def create(parent, flags, method, host, deadline):
-        """Creates a managed cygrpc.Call and a function to call to drive it.
+    # pylint: disable=too-many-arguments
+    def create(flags, method, host, deadline, metadata, credentials,
+               operationses, event_handler):
+        """Creates a cygrpc.IntegratedCall.
 
-    If operations are successfully added to the returned cygrpc.Call, the
-    returned function must be called. If operations are not successfully added
-    to the returned cygrpc.Call, the returned function must not be called.
+        Args:
+          flags: An integer bitfield of call flags.
+          method: The RPC method.
+          host: A host string for the created call.
+          deadline: A float to be the deadline of the created call or None if
+            the call is to have an infinite deadline.
+          metadata: The metadata for the call or None.
+          credentials: A cygrpc.CallCredentials or None.
+          operationses: An iterable of iterables of cygrpc.Operations to be
+            started on the call.
+          event_handler: A behavior to call to handle the events resultant from
+            the operations on the call.
 
-    Args:
-      parent: A cygrpc.Call to be used as the parent of the created call.
-      flags: An integer bitfield of call flags.
-      method: The RPC method.
-      host: A host string for the created call.
-      deadline: A float to be the deadline of the created call or None if the
-        call is to have an infinite deadline.
-
-    Returns:
-      A cygrpc.Call with which to conduct an RPC and a function to call if
-        operations are successfully started on the call.
-    """
-        call = state.channel.create_call(parent, flags, state.completion_queue,
-                                         method, host, deadline)
-
-        def drive():
-            with state.lock:
-                if state.managed_calls is None:
-                    state.managed_calls = set((call,))
-                    _run_channel_spin_thread(state)
-                else:
-                    state.managed_calls.add(call)
-
-        return call, drive
+        Returns:
+          A cygrpc.IntegratedCall with which to conduct an RPC.
+        """
+        operationses_and_tags = tuple((
+            operations,
+            event_handler,
+        ) for operations in operationses)
+        with state.lock:
+            call = state.channel.integrated_call(flags, method, host, deadline,
+                                                 metadata, credentials,
+                                                 operationses_and_tags)
+            if state.managed_calls == 0:
+                state.managed_calls = 1
+                _run_channel_spin_thread(state)
+            else:
+                state.managed_calls += 1
+            return call
 
     return create
 
@@ -819,12 +771,9 @@
             callback_and_connectivity[1] = state.connectivity
         if callbacks:
             _spawn_delivery(state, callbacks)
-    completion_queue = cygrpc.CompletionQueue()
     while True:
-        channel.watch_connectivity_state(connectivity,
-                                         time.time() + 0.2, completion_queue,
-                                         None)
-        event = completion_queue.poll()
+        event = channel.watch_connectivity_state(connectivity,
+                                                 time.time() + 0.2)
         with state.lock:
             if not state.callbacks_and_connectivities and not state.try_to_connect:
                 state.polling = False
@@ -855,10 +804,10 @@
 def _subscribe(state, callback, try_to_connect):
     with state.lock:
         if not state.callbacks_and_connectivities and not state.polling:
-            polling_thread = _common.CleanupThread(
-                lambda timeout: _moot(state),
+            polling_thread = threading.Thread(
                 target=_poll_connectivity,
                 args=(state, state.channel, bool(try_to_connect)))
+            polling_thread.daemon = True
             polling_thread.start()
             state.polling = True
             state.callbacks_and_connectivities.append([callback, None])
@@ -944,5 +893,28 @@
             self._channel, _channel_managed_call_management(self._call_state),
             _common.encode(method), request_serializer, response_deserializer)
 
+    def _close(self):
+        self._channel.close(cygrpc.StatusCode.cancelled, 'Channel closed!')
+        _moot(self._connectivity_state)
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self._close()
+        return False
+
+    def close(self):
+        self._close()
+
     def __del__(self):
+        # TODO(https://github.com/grpc/grpc/issues/12531): Several releases
+        # after 1.12 (1.16 or thereabouts?) add a "self._channel.close" call
+        # here (or more likely, call self._close() here). We don't do this today
+        # because many valid use cases today allow the channel to be deleted
+        # immediately after stubs are created. After a sufficient period of time
+        # has passed for all users to be trusted to hang out to their channels
+        # for as long as they are in use and to close them after using them,
+        # then deletion of this grpc._channel.Channel instance can be made to
+        # effect closure of the underlying cygrpc.Channel instance.
         _moot(self._connectivity_state)
diff --git a/src/python/grpcio/grpc/_common.py b/src/python/grpcio/grpc/_common.py
index bbb69ad..862987a 100644
--- a/src/python/grpcio/grpc/_common.py
+++ b/src/python/grpcio/grpc/_common.py
@@ -14,8 +14,6 @@
 """Shared implementation."""
 
 import logging
-import threading
-import time
 
 import six
 
@@ -101,35 +99,3 @@
 
 def fully_qualified_method(group, method):
     return '/{}/{}'.format(group, method)
-
-
-class CleanupThread(threading.Thread):
-    """A threading.Thread subclass supporting custom behavior on join().
-
-    On Python Interpreter exit, Python will attempt to join outstanding threads
-    prior to garbage collection.  We may need to do additional cleanup, and
-    we accomplish this by overriding the join() method.
-    """
-
-    def __init__(self, behavior, *args, **kwargs):
-        """Constructor.
-
-        Args:
-            behavior (function): Function called on join() with a single
-                argument, timeout, indicating the maximum duration of
-                `behavior`, or None indicating `behavior` has no deadline.
-                `behavior` must be idempotent.
-            args: Positional arguments passed to threading.Thread constructor.
-            kwargs: Keyword arguments passed to threading.Thread constructor.
-        """
-        super(CleanupThread, self).__init__(*args, **kwargs)
-        self._behavior = behavior
-
-    def join(self, timeout=None):
-        start_time = time.time()
-        self._behavior(timeout)
-        end_time = time.time()
-        if timeout is not None:
-            timeout -= end_time - start_time
-            timeout = max(timeout, 0)
-        super(CleanupThread, self).join(timeout)
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/channel.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/channel.pxd.pxi
index 1ba76b7..eefc685 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/channel.pxd.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/channel.pxd.pxi
@@ -13,9 +13,59 @@
 # limitations under the License.
 
 
+cdef _check_call_error_no_metadata(c_call_error)
+
+
+cdef _check_and_raise_call_error_no_metadata(c_call_error)
+
+
+cdef _check_call_error(c_call_error, metadata)
+
+
+cdef class _CallState:
+
+  cdef grpc_call *c_call
+  cdef set due
+
+
+cdef class _ChannelState:
+
+  cdef object condition
+  cdef grpc_channel *c_channel
+  # A boolean field indicating that the channel is open (if True) or is being
+  # closed (i.e. a call to close is currently executing) or is closed (if
+  # False).
+  # TODO(https://github.com/grpc/grpc/issues/3064): Eliminate "is being closed"
+  # a state in which condition may be acquired by any thread, eliminate this
+  # field and just use the NULLness of c_channel as an indication that the
+  # channel is closed.
+  cdef object open
+
+  # A dict from _BatchOperationTag to _CallState
+  cdef dict integrated_call_states
+  cdef grpc_completion_queue *c_call_completion_queue
+
+  # A set of _CallState
+  cdef set segregated_call_states
+
+  cdef set connectivity_due
+  cdef grpc_completion_queue *c_connectivity_completion_queue
+
+
+cdef class IntegratedCall:
+
+  cdef _ChannelState _channel_state
+  cdef _CallState _call_state
+
+
+cdef class SegregatedCall:
+
+  cdef _ChannelState _channel_state
+  cdef _CallState _call_state
+  cdef grpc_completion_queue *_c_completion_queue
+
+
 cdef class Channel:
 
   cdef grpc_arg_pointer_vtable _vtable
-  cdef grpc_channel *c_channel
-  cdef list references
-  cdef readonly _ArgumentsProcessor _arguments_processor
+  cdef _ChannelState _state
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/channel.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/channel.pyx.pxi
index a396649..72e74e8 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/channel.pyx.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/channel.pyx.pxi
@@ -14,82 +14,439 @@
 
 cimport cpython
 
+import threading
+
+_INTERNAL_CALL_ERROR_MESSAGE_FORMAT = (
+    'Internal gRPC call error %d. ' +
+    'Please report to https://github.com/grpc/grpc/issues')
+
+
+cdef str _call_error_metadata(metadata):
+  return 'metadata was invalid: %s' % metadata
+
+
+cdef str _call_error_no_metadata(c_call_error):
+  return _INTERNAL_CALL_ERROR_MESSAGE_FORMAT % c_call_error
+
+
+cdef str _call_error(c_call_error, metadata):
+  if c_call_error == GRPC_CALL_ERROR_INVALID_METADATA:
+    return _call_error_metadata(metadata)
+  else:
+    return _call_error_no_metadata(c_call_error)
+
+
+cdef _check_call_error_no_metadata(c_call_error):
+  if c_call_error != GRPC_CALL_OK:
+    return _INTERNAL_CALL_ERROR_MESSAGE_FORMAT % c_call_error
+  else:
+    return None
+
+
+cdef _check_and_raise_call_error_no_metadata(c_call_error):
+  error = _check_call_error_no_metadata(c_call_error)
+  if error is not None:
+    raise ValueError(error)
+
+
+cdef _check_call_error(c_call_error, metadata):
+  if c_call_error == GRPC_CALL_ERROR_INVALID_METADATA:
+    return _call_error_metadata(metadata)
+  else:
+    return _check_call_error_no_metadata(c_call_error)
+
+
+cdef void _raise_call_error_no_metadata(c_call_error) except *:
+  raise ValueError(_call_error_no_metadata(c_call_error))
+
+
+cdef void _raise_call_error(c_call_error, metadata) except *:
+  raise ValueError(_call_error(c_call_error, metadata))
+
+
+cdef _destroy_c_completion_queue(grpc_completion_queue *c_completion_queue):
+  grpc_completion_queue_shutdown(c_completion_queue)
+  grpc_completion_queue_destroy(c_completion_queue)
+
+
+cdef class _CallState:
+
+  def __cinit__(self):
+    self.due = set()
+
+
+cdef class _ChannelState:
+
+  def __cinit__(self):
+    self.condition = threading.Condition()
+    self.open = True
+    self.integrated_call_states = {}
+    self.segregated_call_states = set()
+    self.connectivity_due = set()
+
+
+cdef tuple _operate(grpc_call *c_call, object operations, object user_tag):
+  cdef grpc_call_error c_call_error
+  cdef _BatchOperationTag tag = _BatchOperationTag(user_tag, operations, None)
+  tag.prepare()
+  cpython.Py_INCREF(tag)
+  with nogil:
+    c_call_error = grpc_call_start_batch(
+        c_call, tag.c_ops, tag.c_nops, <cpython.PyObject *>tag, NULL)
+  return c_call_error, tag
+
+
+cdef object _operate_from_integrated_call(
+    _ChannelState channel_state, _CallState call_state, object operations,
+    object user_tag):
+  cdef grpc_call_error c_call_error
+  cdef _BatchOperationTag tag
+  with channel_state.condition:
+    if call_state.due:
+      c_call_error, tag = _operate(call_state.c_call, operations, user_tag)
+      if c_call_error == GRPC_CALL_OK:
+        call_state.due.add(tag)
+        channel_state.integrated_call_states[tag] = call_state
+        return True
+      else:
+        _raise_call_error_no_metadata(c_call_error)
+    else:
+      return False
+
+
+cdef object _operate_from_segregated_call(
+    _ChannelState channel_state, _CallState call_state, object operations,
+    object user_tag):
+  cdef grpc_call_error c_call_error
+  cdef _BatchOperationTag tag
+  with channel_state.condition:
+    if call_state.due:
+      c_call_error, tag = _operate(call_state.c_call, operations, user_tag)
+      if c_call_error == GRPC_CALL_OK:
+        call_state.due.add(tag)
+        return True
+      else:
+        _raise_call_error_no_metadata(c_call_error)
+    else:
+      return False
+
+
+cdef _cancel(
+    _ChannelState channel_state, _CallState call_state, grpc_status_code code,
+    str details):
+  cdef grpc_call_error c_call_error
+  with channel_state.condition:
+    if call_state.due:
+      c_call_error = grpc_call_cancel_with_status(
+          call_state.c_call, code, _encode(details), NULL)
+      _check_and_raise_call_error_no_metadata(c_call_error)
+
+
+cdef BatchOperationEvent _next_call_event(
+    _ChannelState channel_state, grpc_completion_queue *c_completion_queue,
+    on_success):
+  tag, event = _latent_event(c_completion_queue, None)
+  with channel_state.condition:
+    on_success(tag)
+    channel_state.condition.notify_all()
+  return event
+
+
+# TODO(https://github.com/grpc/grpc/issues/14569): This could be a lot simpler.
+cdef void _call(
+    _ChannelState channel_state, _CallState call_state,
+    grpc_completion_queue *c_completion_queue, on_success, int flags, method,
+    host, object deadline, CallCredentials credentials,
+    object operationses_and_user_tags, object metadata) except *:
+  """Invokes an RPC.
+
+  Args:
+    channel_state: A _ChannelState with its "open" attribute set to True. RPCs
+      may not be invoked on a closed channel.
+    call_state: An empty _CallState to be altered (specifically assigned a
+      c_call and having its due set populated) if the RPC invocation is
+      successful.
+    c_completion_queue: A grpc_completion_queue to be used for the call's
+      operations.
+    on_success: A behavior to be called if attempting to start operations for
+      the call succeeds. If called the behavior will be called while holding the
+      channel_state condition and passed the tags associated with operations
+      that were successfully started for the call.
+    flags: Flags to be passed to gRPC Core as part of call creation.
+    method: The fully-qualified name of the RPC method being invoked.
+    host: A "host" string to be passed to gRPC Core as part of call creation.
+    deadline: A float for the deadline of the RPC, or None if the RPC is to have
+      no deadline.
+    credentials: A _CallCredentials for the RPC or None.
+    operationses_and_user_tags: A sequence of length-two sequences the first
+      element of which is a sequence of Operations and the second element of
+      which is an object to be used as a tag. A SendInitialMetadataOperation
+      must be present in the first element of this value.
+    metadata: The metadata for this call.
+  """
+  cdef grpc_slice method_slice
+  cdef grpc_slice host_slice
+  cdef grpc_slice *host_slice_ptr
+  cdef grpc_call_credentials *c_call_credentials
+  cdef grpc_call_error c_call_error
+  cdef tuple error_and_wrapper_tag
+  cdef _BatchOperationTag wrapper_tag
+  with channel_state.condition:
+    if channel_state.open:
+      method_slice = _slice_from_bytes(method)
+      if host is None:
+        host_slice_ptr = NULL
+      else:
+        host_slice = _slice_from_bytes(host)
+        host_slice_ptr = &host_slice
+      call_state.c_call = grpc_channel_create_call(
+          channel_state.c_channel, NULL, flags,
+          c_completion_queue, method_slice, host_slice_ptr,
+          _timespec_from_time(deadline), NULL)
+      grpc_slice_unref(method_slice)
+      if host_slice_ptr:
+        grpc_slice_unref(host_slice)
+      if credentials is not None:
+        c_call_credentials = credentials.c()
+        c_call_error = grpc_call_set_credentials(
+            call_state.c_call, c_call_credentials)
+        grpc_call_credentials_release(c_call_credentials)
+        if c_call_error != GRPC_CALL_OK:
+          grpc_call_unref(call_state.c_call)
+          call_state.c_call = NULL
+          _raise_call_error_no_metadata(c_call_error)
+      started_tags = set()
+      for operations, user_tag in operationses_and_user_tags:
+        c_call_error, tag = _operate(call_state.c_call, operations, user_tag)
+        if c_call_error == GRPC_CALL_OK:
+          started_tags.add(tag)
+        else:
+          grpc_call_cancel(call_state.c_call, NULL)
+          grpc_call_unref(call_state.c_call)
+          call_state.c_call = NULL
+          _raise_call_error(c_call_error, metadata)
+      else:
+        call_state.due.update(started_tags)
+        on_success(started_tags)
+    else:
+      raise ValueError('Cannot invoke RPC on closed channel!')
+
+cdef void _process_integrated_call_tag(
+    _ChannelState state, _BatchOperationTag tag) except *:
+  cdef _CallState call_state = state.integrated_call_states.pop(tag)
+  call_state.due.remove(tag)
+  if not call_state.due:
+    grpc_call_unref(call_state.c_call)
+    call_state.c_call = NULL
+
+
+cdef class IntegratedCall:
+
+  def __cinit__(self, _ChannelState channel_state, _CallState call_state):
+    self._channel_state = channel_state
+    self._call_state = call_state
+
+  def operate(self, operations, tag):
+    return _operate_from_integrated_call(
+        self._channel_state, self._call_state, operations, tag)
+
+  def cancel(self, code, details):
+    _cancel(self._channel_state, self._call_state, code, details)
+
+
+cdef IntegratedCall _integrated_call(
+    _ChannelState state, int flags, method, host, object deadline,
+    object metadata, CallCredentials credentials, operationses_and_user_tags):
+  call_state = _CallState()
+
+  def on_success(started_tags):
+    for started_tag in started_tags:
+      state.integrated_call_states[started_tag] = call_state
+
+  _call(
+      state, call_state, state.c_call_completion_queue, on_success, flags,
+      method, host, deadline, credentials, operationses_and_user_tags, metadata)
+
+  return IntegratedCall(state, call_state)
+
+
+cdef object _process_segregated_call_tag(
+    _ChannelState state, _CallState call_state,
+    grpc_completion_queue *c_completion_queue, _BatchOperationTag tag):
+  call_state.due.remove(tag)
+  if not call_state.due:
+    grpc_call_unref(call_state.c_call)
+    call_state.c_call = NULL
+    state.segregated_call_states.remove(call_state)
+    _destroy_c_completion_queue(c_completion_queue)
+    return True
+  else:
+    return False
+
+
+cdef class SegregatedCall:
+
+  def __cinit__(self, _ChannelState channel_state, _CallState call_state):
+    self._channel_state = channel_state
+    self._call_state = call_state
+
+  def operate(self, operations, tag):
+    return _operate_from_segregated_call(
+        self._channel_state, self._call_state, operations, tag)
+
+  def cancel(self, code, details):
+    _cancel(self._channel_state, self._call_state, code, details)
+
+  def next_event(self):
+    def on_success(tag):
+      _process_segregated_call_tag(
+          self._channel_state, self._call_state, self._c_completion_queue, tag)
+    return _next_call_event(
+        self._channel_state, self._c_completion_queue, on_success)
+
+
+cdef SegregatedCall _segregated_call(
+    _ChannelState state, int flags, method, host, object deadline,
+    object metadata, CallCredentials credentials, operationses_and_user_tags):
+  cdef _CallState call_state = _CallState()
+  cdef grpc_completion_queue *c_completion_queue = (
+      grpc_completion_queue_create_for_next(NULL))
+  cdef SegregatedCall segregated_call
+
+  def on_success(started_tags):
+    state.segregated_call_states.add(call_state)
+
+  try:
+    _call(
+        state, call_state, c_completion_queue, on_success, flags, method, host,
+        deadline, credentials, operationses_and_user_tags, metadata)
+  except:
+    _destroy_c_completion_queue(c_completion_queue)
+    raise
+
+  segregated_call = SegregatedCall(state, call_state)
+  segregated_call._c_completion_queue = c_completion_queue
+  return segregated_call
+
+
+cdef object _watch_connectivity_state(
+    _ChannelState state, grpc_connectivity_state last_observed_state,
+    object deadline):
+  cdef _ConnectivityTag tag = _ConnectivityTag(object())
+  with state.condition:
+    if state.open:
+      cpython.Py_INCREF(tag)
+      grpc_channel_watch_connectivity_state(
+          state.c_channel, last_observed_state, _timespec_from_time(deadline),
+          state.c_connectivity_completion_queue, <cpython.PyObject *>tag)
+      state.connectivity_due.add(tag)
+    else:
+      raise ValueError('Cannot invoke RPC on closed channel!')
+  completed_tag, event = _latent_event(
+      state.c_connectivity_completion_queue, None)
+  with state.condition:
+    state.connectivity_due.remove(completed_tag)
+    state.condition.notify_all()
+  return event
+
+
+cdef _close(_ChannelState state, grpc_status_code code, object details):
+  cdef _CallState call_state
+  encoded_details = _encode(details)
+  with state.condition:
+    if state.open:
+      state.open = False
+      for call_state in set(state.integrated_call_states.values()):
+        grpc_call_cancel_with_status(
+            call_state.c_call, code, encoded_details, NULL)
+      for call_state in state.segregated_call_states:
+        grpc_call_cancel_with_status(
+            call_state.c_call, code, encoded_details, NULL)
+      # TODO(https://github.com/grpc/grpc/issues/3064): Cancel connectivity
+      # watching.
+
+      while state.integrated_call_states:
+        state.condition.wait()
+      while state.segregated_call_states:
+        state.condition.wait()
+      while state.connectivity_due:
+        state.condition.wait()
+
+      _destroy_c_completion_queue(state.c_call_completion_queue)
+      _destroy_c_completion_queue(state.c_connectivity_completion_queue)
+      grpc_channel_destroy(state.c_channel)
+      state.c_channel = NULL
+      grpc_shutdown()
+      state.condition.notify_all()
+    else:
+      # Another call to close already completed in the past or is currently
+      # being executed in another thread.
+      while state.c_channel != NULL:
+        state.condition.wait()
+
 
 cdef class Channel:
 
-  def __cinit__(self, bytes target, object arguments,
-                ChannelCredentials channel_credentials=None):
+  def __cinit__(
+      self, bytes target, object arguments,
+      ChannelCredentials channel_credentials):
     grpc_init()
+    self._state = _ChannelState()
     self._vtable.copy = &_copy_pointer
     self._vtable.destroy = &_destroy_pointer
     self._vtable.cmp = &_compare_pointer
     cdef _ArgumentsProcessor arguments_processor = _ArgumentsProcessor(
         arguments)
     cdef grpc_channel_args *c_arguments = arguments_processor.c(&self._vtable)
-    self.references = []
-    c_target = target
     if channel_credentials is None:
-      self.c_channel = grpc_insecure_channel_create(c_target, c_arguments, NULL)
+      self._state.c_channel = grpc_insecure_channel_create(
+          <char *>target, c_arguments, NULL)
     else:
       c_channel_credentials = channel_credentials.c()
-      self.c_channel = grpc_secure_channel_create(
-          c_channel_credentials, c_target, c_arguments, NULL)
+      self._state.c_channel = grpc_secure_channel_create(
+          c_channel_credentials, <char *>target, c_arguments, NULL)
       grpc_channel_credentials_release(c_channel_credentials)
-    arguments_processor.un_c()
-    self.references.append(target)
-    self.references.append(arguments)
-
-  def create_call(self, Call parent, int flags,
-                  CompletionQueue queue not None,
-                  method, host, object deadline):
-    if queue.is_shutting_down:
-      raise ValueError("queue must not be shutting down or shutdown")
-    cdef grpc_slice method_slice = _slice_from_bytes(method)
-    cdef grpc_slice host_slice
-    cdef grpc_slice *host_slice_ptr = NULL
-    if host is not None:
-      host_slice = _slice_from_bytes(host)
-      host_slice_ptr = &host_slice
-    cdef Call operation_call = Call()
-    operation_call.references = [self, queue]
-    cdef grpc_call *parent_call = NULL
-    if parent is not None:
-      parent_call = parent.c_call
-    operation_call.c_call = grpc_channel_create_call(
-        self.c_channel, parent_call, flags,
-        queue.c_completion_queue, method_slice, host_slice_ptr,
-        _timespec_from_time(deadline), NULL)
-    grpc_slice_unref(method_slice)
-    if host_slice_ptr:
-      grpc_slice_unref(host_slice)
-    return operation_call
-
-  def check_connectivity_state(self, bint try_to_connect):
-    cdef grpc_connectivity_state result
-    with nogil:
-      result = grpc_channel_check_connectivity_state(self.c_channel,
-                                                     try_to_connect)
-    return result
-
-  def watch_connectivity_state(
-      self, grpc_connectivity_state last_observed_state,
-      object deadline, CompletionQueue queue not None, tag):
-    cdef _ConnectivityTag connectivity_tag = _ConnectivityTag(tag)
-    cpython.Py_INCREF(connectivity_tag)
-    grpc_channel_watch_connectivity_state(
-        self.c_channel, last_observed_state, _timespec_from_time(deadline),
-        queue.c_completion_queue, <cpython.PyObject *>connectivity_tag)
+    self._state.c_call_completion_queue = (
+        grpc_completion_queue_create_for_next(NULL))
+    self._state.c_connectivity_completion_queue = (
+        grpc_completion_queue_create_for_next(NULL))
 
   def target(self):
-    cdef char *target = NULL
-    with nogil:
-      target = grpc_channel_get_target(self.c_channel)
-    result = <bytes>target
-    with nogil:
-      gpr_free(target)
-    return result
+    cdef char *c_target
+    with self._state.condition:
+      c_target = grpc_channel_get_target(self._state.c_channel)
+      target = <bytes>c_target
+      gpr_free(c_target)
+      return target
 
-  def __dealloc__(self):
-    if self.c_channel != NULL:
-      grpc_channel_destroy(self.c_channel)
-    grpc_shutdown()
+  def integrated_call(
+      self, int flags, method, host, object deadline, object metadata,
+      CallCredentials credentials, operationses_and_tags):
+    return _integrated_call(
+        self._state, flags, method, host, deadline, metadata, credentials,
+        operationses_and_tags)
+
+  def next_call_event(self):
+    def on_success(tag):
+      _process_integrated_call_tag(self._state, tag)
+    return _next_call_event(
+        self._state, self._state.c_call_completion_queue, on_success)
+
+  def segregated_call(
+      self, int flags, method, host, object deadline, object metadata,
+      CallCredentials credentials, operationses_and_tags):
+    return _segregated_call(
+        self._state, flags, method, host, deadline, metadata, credentials,
+        operationses_and_tags)
+
+  def check_connectivity_state(self, bint try_to_connect):
+    with self._state.condition:
+      return grpc_channel_check_connectivity_state(
+          self._state.c_channel, try_to_connect)
+
+  def watch_connectivity_state(
+      self, grpc_connectivity_state last_observed_state, object deadline):
+    return _watch_connectivity_state(self._state, last_observed_state, deadline)
+
+  def close(self, code, details):
+    _close(self._state, code, details)
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd.pxi
index 5ea0287..9f06ce0 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd.pxi
@@ -13,10 +13,16 @@
 # limitations under the License.
 
 
+cdef grpc_event _next(grpc_completion_queue *c_completion_queue, deadline)
+
+
+cdef _interpret_event(grpc_event c_event)
+
+
 cdef class CompletionQueue:
 
   cdef grpc_completion_queue *c_completion_queue
   cdef bint is_shutting_down
   cdef bint is_shutdown
 
-  cdef _interpret_event(self, grpc_event event)
+  cdef _interpret_event(self, grpc_event c_event)
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx.pxi
index 40496d1..a2d7655 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx.pxi
@@ -20,6 +20,53 @@
 cdef int _INTERRUPT_CHECK_PERIOD_MS = 200
 
 
+cdef grpc_event _next(grpc_completion_queue *c_completion_queue, deadline):
+  cdef gpr_timespec c_increment
+  cdef gpr_timespec c_timeout
+  cdef gpr_timespec c_deadline
+  c_increment = gpr_time_from_millis(_INTERRUPT_CHECK_PERIOD_MS, GPR_TIMESPAN)
+  if deadline is None:
+    c_deadline = gpr_inf_future(GPR_CLOCK_REALTIME)
+  else:
+    c_deadline = _timespec_from_time(deadline)
+
+  with nogil:
+    while True:
+      c_timeout = gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), c_increment)
+      if gpr_time_cmp(c_timeout, c_deadline) > 0:
+        c_timeout = c_deadline
+      c_event = grpc_completion_queue_next(c_completion_queue, c_timeout, NULL)
+      if (c_event.type != GRPC_QUEUE_TIMEOUT or
+          gpr_time_cmp(c_timeout, c_deadline) == 0):
+        break
+
+      # Handle any signals
+      with gil:
+        cpython.PyErr_CheckSignals()
+  return c_event
+
+
+cdef _interpret_event(grpc_event c_event):
+  cdef _Tag tag
+  if c_event.type == GRPC_QUEUE_TIMEOUT:
+    # NOTE(nathaniel): For now we coopt ConnectivityEvent here.
+    return None, ConnectivityEvent(GRPC_QUEUE_TIMEOUT, False, None)
+  elif c_event.type == GRPC_QUEUE_SHUTDOWN:
+    # NOTE(nathaniel): For now we coopt ConnectivityEvent here.
+    return None, ConnectivityEvent(GRPC_QUEUE_SHUTDOWN, False, None)
+  else:
+    tag = <_Tag>c_event.tag
+    # We receive event tags only after they've been inc-ref'd elsewhere in
+    # the code.
+    cpython.Py_DECREF(tag)
+    return tag, tag.event(c_event)
+
+
+cdef _latent_event(grpc_completion_queue *c_completion_queue, object deadline):
+  cdef grpc_event c_event = _next(c_completion_queue, deadline)
+  return _interpret_event(c_event)
+
+
 cdef class CompletionQueue:
 
   def __cinit__(self, shutdown_cq=False):
@@ -36,48 +83,16 @@
     self.is_shutting_down = False
     self.is_shutdown = False
 
-  cdef _interpret_event(self, grpc_event event):
-    cdef _Tag tag = None
-    if event.type == GRPC_QUEUE_TIMEOUT:
-      # NOTE(nathaniel): For now we coopt ConnectivityEvent here.
-      return ConnectivityEvent(GRPC_QUEUE_TIMEOUT, False, None)
-    elif event.type == GRPC_QUEUE_SHUTDOWN:
+  cdef _interpret_event(self, grpc_event c_event):
+    unused_tag, event = _interpret_event(c_event)
+    if event.completion_type == GRPC_QUEUE_SHUTDOWN:
       self.is_shutdown = True
-      # NOTE(nathaniel): For now we coopt ConnectivityEvent here.
-      return ConnectivityEvent(GRPC_QUEUE_TIMEOUT, True, None)
-    else:
-      tag = <_Tag>event.tag
-      # We receive event tags only after they've been inc-ref'd elsewhere in
-      # the code.
-      cpython.Py_DECREF(tag)
-      return tag.event(event)
+    return event
 
+  # We name this 'poll' to avoid problems with CPython's expectations for
+  # 'special' methods (like next and __next__).
   def poll(self, deadline=None):
-    # We name this 'poll' to avoid problems with CPython's expectations for
-    # 'special' methods (like next and __next__).
-    cdef gpr_timespec c_increment
-    cdef gpr_timespec c_timeout
-    cdef gpr_timespec c_deadline
-    if deadline is None:
-      c_deadline = gpr_inf_future(GPR_CLOCK_REALTIME)
-    else:
-      c_deadline = _timespec_from_time(deadline)
-    with nogil:
-      c_increment = gpr_time_from_millis(_INTERRUPT_CHECK_PERIOD_MS, GPR_TIMESPAN)
-
-      while True:
-        c_timeout = gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), c_increment)
-        if gpr_time_cmp(c_timeout, c_deadline) > 0:
-          c_timeout = c_deadline
-        event = grpc_completion_queue_next(
-          self.c_completion_queue, c_timeout, NULL)
-        if event.type != GRPC_QUEUE_TIMEOUT or gpr_time_cmp(c_timeout, c_deadline) == 0:
-          break;
-
-        # Handle any signals
-        with gil:
-          cpython.PyErr_CheckSignals()
-    return self._interpret_event(event)
+    return self._interpret_event(_next(self.c_completion_queue, deadline))
 
   def shutdown(self):
     with nogil:
diff --git a/src/python/grpcio/grpc/_grpcio_metadata.py b/src/python/grpcio/grpc/_grpcio_metadata.py
index cb5da72..ad53f60 100644
--- a/src/python/grpcio/grpc/_grpcio_metadata.py
+++ b/src/python/grpcio/grpc/_grpcio_metadata.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio/grpc/_grpcio_metadata.py.template`!!!
 
-__version__ = """1.12.0.dev0"""
+__version__ = """1.13.0.dev0"""
diff --git a/src/python/grpcio/grpc/_interceptor.py b/src/python/grpcio/grpc/_interceptor.py
index d029472..f465e35 100644
--- a/src/python/grpcio/grpc/_interceptor.py
+++ b/src/python/grpcio/grpc/_interceptor.py
@@ -334,6 +334,19 @@
         else:
             return thunk(method)
 
+    def _close(self):
+        self._channel.close()
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self._close()
+        return False
+
+    def close(self):
+        self._channel.close()
+
 
 def intercept_channel(channel, *interceptors):
     for interceptor in reversed(list(interceptors)):
diff --git a/src/python/grpcio/grpc/_server.py b/src/python/grpcio/grpc/_server.py
index c988e0c..d849cad 100644
--- a/src/python/grpcio/grpc/_server.py
+++ b/src/python/grpcio/grpc/_server.py
@@ -780,14 +780,8 @@
         state.stage = _ServerStage.STARTED
         _request_call(state)
 
-        def cleanup_server(timeout):
-            if timeout is None:
-                _stop(state, _UNEXPECTED_EXIT_SERVER_GRACE).wait()
-            else:
-                _stop(state, timeout).wait()
-
-        thread = _common.CleanupThread(
-            cleanup_server, target=_serve, args=(state,))
+        thread = threading.Thread(target=_serve, args=(state,))
+        thread.daemon = True
         thread.start()
 
 
diff --git a/src/python/grpcio/grpc/beta/_server_adaptations.py b/src/python/grpcio/grpc/beta/_server_adaptations.py
index 3c04fd7..ccafec8 100644
--- a/src/python/grpcio/grpc/beta/_server_adaptations.py
+++ b/src/python/grpcio/grpc/beta/_server_adaptations.py
@@ -168,11 +168,8 @@
                 return
         request_consumer.terminate()
 
-    def stop_request_pipe(timeout):  # pylint: disable=unused-argument
-        thread_joined.set()
-
-    request_pipe_thread = _common.CleanupThread(
-        stop_request_pipe, target=pipe_requests)
+    request_pipe_thread = threading.Thread(target=pipe_requests)
+    request_pipe_thread.daemon = True
     request_pipe_thread.start()
 
 
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index b1f9d20..699d504 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -296,7 +296,6 @@
     'third_party/nanopb/pb_decode.c',
     'third_party/nanopb/pb_encode.c',
     'src/core/tsi/transport_security.cc',
-    'src/core/tsi/transport_security_adapter.cc',
     'src/core/ext/transport/chttp2/client/insecure/channel_create.cc',
     'src/core/ext/transport/chttp2/client/insecure/channel_create_posix.cc',
     'src/core/ext/transport/chttp2/client/authority.cc',
@@ -344,7 +343,6 @@
     'src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/load_balancer.pb.c',
     'src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc',
     'src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc',
-    'src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc',
     'src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc',
     'src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc',
     'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc',
diff --git a/src/python/grpcio/grpc_version.py b/src/python/grpcio/grpc_version.py
index de5a780..57dc26d 100644
--- a/src/python/grpcio/grpc_version.py
+++ b/src/python/grpcio/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio/grpc_version.py.template`!!!
 
-VERSION = '1.12.0.dev0'
+VERSION = '1.13.0.dev0'
diff --git a/src/python/grpcio_health_checking/grpc_version.py b/src/python/grpcio_health_checking/grpc_version.py
index afcd316..ba0d4a3 100644
--- a/src/python/grpcio_health_checking/grpc_version.py
+++ b/src/python/grpcio_health_checking/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_health_checking/grpc_version.py.template`!!!
 
-VERSION = '1.12.0.dev0'
+VERSION = '1.13.0.dev0'
diff --git a/src/python/grpcio_reflection/grpc_version.py b/src/python/grpcio_reflection/grpc_version.py
index 824b732..ea2878d 100644
--- a/src/python/grpcio_reflection/grpc_version.py
+++ b/src/python/grpcio_reflection/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_reflection/grpc_version.py.template`!!!
 
-VERSION = '1.12.0.dev0'
+VERSION = '1.13.0.dev0'
diff --git a/src/python/grpcio_testing/grpc_testing/_channel/_channel.py b/src/python/grpcio_testing/grpc_testing/_channel/_channel.py
index b015b8d..0c1941e 100644
--- a/src/python/grpcio_testing/grpc_testing/_channel/_channel.py
+++ b/src/python/grpcio_testing/grpc_testing/_channel/_channel.py
@@ -56,6 +56,21 @@
                       response_deserializer=None):
         return _multi_callable.StreamStream(method, self._state)
 
+    def _close(self):
+        # TODO(https://github.com/grpc/grpc/issues/12531): Decide what
+        # action to take here, if any?
+        pass
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self._close()
+        return False
+
+    def close(self):
+        self._close()
+
     def take_unary_unary(self, method_descriptor):
         return _channel_rpc.unary_unary(self._state, method_descriptor)
 
diff --git a/src/python/grpcio_testing/grpc_version.py b/src/python/grpcio_testing/grpc_version.py
index 5b1f4c4..02f19f2 100644
--- a/src/python/grpcio_testing/grpc_version.py
+++ b/src/python/grpcio_testing/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_testing/grpc_version.py.template`!!!
 
-VERSION = '1.12.0.dev0'
+VERSION = '1.13.0.dev0'
diff --git a/src/python/grpcio_tests/grpc_version.py b/src/python/grpcio_tests/grpc_version.py
index 382f950..9d2e416 100644
--- a/src/python/grpcio_tests/grpc_version.py
+++ b/src/python/grpcio_tests/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_tests/grpc_version.py.template`!!!
 
-VERSION = '1.12.0.dev0'
+VERSION = '1.13.0.dev0'
diff --git a/src/python/grpcio_tests/tests/tests.json b/src/python/grpcio_tests/tests/tests.json
index d38ee51..0d94426 100644
--- a/src/python/grpcio_tests/tests/tests.json
+++ b/src/python/grpcio_tests/tests/tests.json
@@ -25,6 +25,7 @@
   "unit._auth_test.AccessTokenAuthMetadataPluginTest",
   "unit._auth_test.GoogleCallCredentialsTest",
   "unit._channel_args_test.ChannelArgsTest",
+  "unit._channel_close_test.ChannelCloseTest",
   "unit._channel_connectivity_test.ChannelConnectivityTest",
   "unit._channel_ready_future_test.ChannelReadyFutureTest",
   "unit._compression_test.CompressionTest",
@@ -52,7 +53,6 @@
   "unit._server_ssl_cert_config_test.ServerSSLCertReloadTestCertConfigReuse",
   "unit._server_ssl_cert_config_test.ServerSSLCertReloadTestWithClientAuth",
   "unit._server_ssl_cert_config_test.ServerSSLCertReloadTestWithoutClientAuth",
-  "unit._thread_cleanup_test.CleanupThreadTest",
   "unit.beta._beta_features_test.BetaFeaturesTest",
   "unit.beta._beta_features_test.ContextManagementAndLifecycleTest",
   "unit.beta._connectivity_channel_test.ConnectivityStatesTest",
diff --git a/src/python/grpcio_tests/tests/unit/_channel_close_test.py b/src/python/grpcio_tests/tests/unit/_channel_close_test.py
new file mode 100644
index 0000000..af3a9ee
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/_channel_close_test.py
@@ -0,0 +1,185 @@
+# Copyright 2018 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests server and client side compression."""
+
+import threading
+import time
+import unittest
+
+import grpc
+
+from tests.unit import test_common
+from tests.unit.framework.common import test_constants
+
+_BEAT = 0.5
+_SOME_TIME = 5
+_MORE_TIME = 10
+
+
+class _MethodHandler(grpc.RpcMethodHandler):
+
+    request_streaming = True
+    response_streaming = True
+    request_deserializer = None
+    response_serializer = None
+
+    def stream_stream(self, request_iterator, servicer_context):
+        for request in request_iterator:
+            yield request * 2
+
+
+_METHOD_HANDLER = _MethodHandler()
+
+
+class _GenericHandler(grpc.GenericRpcHandler):
+
+    def service(self, handler_call_details):
+        return _METHOD_HANDLER
+
+
+_GENERIC_HANDLER = _GenericHandler()
+
+
+class _Pipe(object):
+
+    def __init__(self, values):
+        self._condition = threading.Condition()
+        self._values = list(values)
+        self._open = True
+
+    def __iter__(self):
+        return self
+
+    def _next(self):
+        with self._condition:
+            while not self._values and self._open:
+                self._condition.wait()
+            if self._values:
+                return self._values.pop(0)
+            else:
+                raise StopIteration()
+
+    def next(self):
+        return self._next()
+
+    def __next__(self):
+        return self._next()
+
+    def add(self, value):
+        with self._condition:
+            self._values.append(value)
+            self._condition.notify()
+
+    def close(self):
+        with self._condition:
+            self._open = False
+            self._condition.notify()
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type, value, traceback):
+        self.close()
+
+
+class ChannelCloseTest(unittest.TestCase):
+
+    def setUp(self):
+        self._server = test_common.test_server(
+            max_workers=test_constants.THREAD_CONCURRENCY)
+        self._server.add_generic_rpc_handlers((_GENERIC_HANDLER,))
+        self._port = self._server.add_insecure_port('[::]:0')
+        self._server.start()
+
+    def tearDown(self):
+        self._server.stop(None)
+
+    def test_close_immediately_after_call_invocation(self):
+        channel = grpc.insecure_channel('localhost:{}'.format(self._port))
+        multi_callable = channel.stream_stream('Meffod')
+        request_iterator = _Pipe(())
+        response_iterator = multi_callable(request_iterator)
+        channel.close()
+        request_iterator.close()
+
+        self.assertIs(response_iterator.code(), grpc.StatusCode.CANCELLED)
+
+    def test_close_while_call_active(self):
+        channel = grpc.insecure_channel('localhost:{}'.format(self._port))
+        multi_callable = channel.stream_stream('Meffod')
+        request_iterator = _Pipe((b'abc',))
+        response_iterator = multi_callable(request_iterator)
+        next(response_iterator)
+        channel.close()
+        request_iterator.close()
+
+        self.assertIs(response_iterator.code(), grpc.StatusCode.CANCELLED)
+
+    def test_context_manager_close_while_call_active(self):
+        with grpc.insecure_channel('localhost:{}'.format(
+                self._port)) as channel:  # pylint: disable=bad-continuation
+            multi_callable = channel.stream_stream('Meffod')
+            request_iterator = _Pipe((b'abc',))
+            response_iterator = multi_callable(request_iterator)
+            next(response_iterator)
+        request_iterator.close()
+
+        self.assertIs(response_iterator.code(), grpc.StatusCode.CANCELLED)
+
+    def test_context_manager_close_while_many_calls_active(self):
+        with grpc.insecure_channel('localhost:{}'.format(
+                self._port)) as channel:  # pylint: disable=bad-continuation
+            multi_callable = channel.stream_stream('Meffod')
+            request_iterators = tuple(
+                _Pipe((b'abc',))
+                for _ in range(test_constants.THREAD_CONCURRENCY))
+            response_iterators = []
+            for request_iterator in request_iterators:
+                response_iterator = multi_callable(request_iterator)
+                next(response_iterator)
+                response_iterators.append(response_iterator)
+        for request_iterator in request_iterators:
+            request_iterator.close()
+
+        for response_iterator in response_iterators:
+            self.assertIs(response_iterator.code(), grpc.StatusCode.CANCELLED)
+
+    def test_many_concurrent_closes(self):
+        channel = grpc.insecure_channel('localhost:{}'.format(self._port))
+        multi_callable = channel.stream_stream('Meffod')
+        request_iterator = _Pipe((b'abc',))
+        response_iterator = multi_callable(request_iterator)
+        next(response_iterator)
+        start = time.time()
+        end = start + _MORE_TIME
+
+        def sleep_some_time_then_close():
+            time.sleep(_SOME_TIME)
+            channel.close()
+
+        for _ in range(test_constants.THREAD_CONCURRENCY):
+            close_thread = threading.Thread(target=sleep_some_time_then_close)
+            close_thread.start()
+        while True:
+            request_iterator.add(b'def')
+            time.sleep(_BEAT)
+            if end < time.time():
+                break
+        request_iterator.close()
+
+        self.assertIs(response_iterator.code(), grpc.StatusCode.CANCELLED)
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)
diff --git a/src/python/grpcio_tests/tests/unit/_cython/_cancel_many_calls_test.py b/src/python/grpcio_tests/tests/unit/_cython/_cancel_many_calls_test.py
index 4f8868d..578a3d7 100644
--- a/src/python/grpcio_tests/tests/unit/_cython/_cancel_many_calls_test.py
+++ b/src/python/grpcio_tests/tests/unit/_cython/_cancel_many_calls_test.py
@@ -19,6 +19,7 @@
 from grpc._cython import cygrpc
 from grpc.framework.foundation import logging_pool
 from tests.unit.framework.common import test_constants
+from tests.unit._cython import test_utilities
 
 _EMPTY_FLAGS = 0
 _EMPTY_METADATA = ()
@@ -30,6 +31,8 @@
 _SERVER_COMPLETE_CALL_TAG = 'server_complete_call'
 
 _SUCCESS_CALL_FRACTION = 1.0 / 8.0
+_SUCCESSFUL_CALLS = int(test_constants.RPC_CONCURRENCY * _SUCCESS_CALL_FRACTION)
+_UNSUCCESSFUL_CALLS = test_constants.RPC_CONCURRENCY - _SUCCESSFUL_CALLS
 
 
 class _State(object):
@@ -150,7 +153,8 @@
         server.register_completion_queue(server_completion_queue)
         port = server.add_http2_port(b'[::]:0')
         server.start()
-        channel = cygrpc.Channel('localhost:{}'.format(port).encode(), None)
+        channel = cygrpc.Channel('localhost:{}'.format(port).encode(), None,
+                                 None)
 
         state = _State()
 
@@ -165,31 +169,33 @@
 
         client_condition = threading.Condition()
         client_due = set()
-        client_completion_queue = cygrpc.CompletionQueue()
-        client_driver = _QueueDriver(client_condition, client_completion_queue,
-                                     client_due)
-        client_driver.start()
 
         with client_condition:
             client_calls = []
             for index in range(test_constants.RPC_CONCURRENCY):
-                client_call = channel.create_call(None, _EMPTY_FLAGS,
-                                                  client_completion_queue,
-                                                  b'/twinkies', None, None)
-                operations = (
-                    cygrpc.SendInitialMetadataOperation(_EMPTY_METADATA,
-                                                        _EMPTY_FLAGS),
-                    cygrpc.SendMessageOperation(b'\x45\x56', _EMPTY_FLAGS),
-                    cygrpc.SendCloseFromClientOperation(_EMPTY_FLAGS),
-                    cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),
-                    cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),
-                    cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
-                )
                 tag = 'client_complete_call_{0:04d}_tag'.format(index)
-                client_call.start_client_batch(operations, tag)
+                client_call = channel.integrated_call(
+                    _EMPTY_FLAGS, b'/twinkies', None, None, _EMPTY_METADATA,
+                    None, ((
+                        (
+                            cygrpc.SendInitialMetadataOperation(
+                                _EMPTY_METADATA, _EMPTY_FLAGS),
+                            cygrpc.SendMessageOperation(b'\x45\x56',
+                                                        _EMPTY_FLAGS),
+                            cygrpc.SendCloseFromClientOperation(_EMPTY_FLAGS),
+                            cygrpc.ReceiveInitialMetadataOperation(
+                                _EMPTY_FLAGS),
+                            cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),
+                            cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
+                        ),
+                        tag,
+                    ),))
                 client_due.add(tag)
                 client_calls.append(client_call)
 
+        client_events_future = test_utilities.SimpleFuture(
+            lambda: tuple(channel.next_call_event() for _ in range(_SUCCESSFUL_CALLS)))
+
         with state.condition:
             while True:
                 if state.parked_handlers < test_constants.THREAD_CONCURRENCY:
@@ -201,12 +207,14 @@
                     state.condition.notify_all()
                     break
 
-        client_driver.events(
-            test_constants.RPC_CONCURRENCY * _SUCCESS_CALL_FRACTION)
+        client_events_future.result()
         with client_condition:
             for client_call in client_calls:
-                client_call.cancel()
+                client_call.cancel(cygrpc.StatusCode.cancelled, 'Cancelled!')
+        for _ in range(_UNSUCCESSFUL_CALLS):
+            channel.next_call_event()
 
+        channel.close(cygrpc.StatusCode.unknown, 'Cancelled on channel close!')
         with state.condition:
             server.shutdown(server_completion_queue, _SERVER_SHUTDOWN_TAG)
 
diff --git a/src/python/grpcio_tests/tests/unit/_cython/_channel_test.py b/src/python/grpcio_tests/tests/unit/_cython/_channel_test.py
index 7305d0f..d952860 100644
--- a/src/python/grpcio_tests/tests/unit/_cython/_channel_test.py
+++ b/src/python/grpcio_tests/tests/unit/_cython/_channel_test.py
@@ -21,25 +21,20 @@
 from tests.unit.framework.common import test_constants
 
 
-def _channel_and_completion_queue():
-    channel = cygrpc.Channel(b'localhost:54321', ())
-    completion_queue = cygrpc.CompletionQueue()
-    return channel, completion_queue
+def _channel():
+    return cygrpc.Channel(b'localhost:54321', (), None)
 
 
-def _connectivity_loop(channel, completion_queue):
+def _connectivity_loop(channel):
     for _ in range(100):
         connectivity = channel.check_connectivity_state(True)
-        channel.watch_connectivity_state(connectivity,
-                                         time.time() + 0.2, completion_queue,
-                                         None)
-        completion_queue.poll()
+        channel.watch_connectivity_state(connectivity, time.time() + 0.2)
 
 
 def _create_loop_destroy():
-    channel, completion_queue = _channel_and_completion_queue()
-    _connectivity_loop(channel, completion_queue)
-    completion_queue.shutdown()
+    channel = _channel()
+    _connectivity_loop(channel)
+    channel.close(cygrpc.StatusCode.ok, 'Channel close!')
 
 
 def _in_parallel(behavior, arguments):
@@ -55,12 +50,9 @@
 class ChannelTest(unittest.TestCase):
 
     def test_single_channel_lonely_connectivity(self):
-        channel, completion_queue = _channel_and_completion_queue()
-        _in_parallel(_connectivity_loop, (
-            channel,
-            completion_queue,
-        ))
-        completion_queue.shutdown()
+        channel = _channel()
+        _connectivity_loop(channel)
+        channel.close(cygrpc.StatusCode.ok, 'Channel close!')
 
     def test_multiple_channels_lonely_connectivity(self):
         _in_parallel(_create_loop_destroy, ())
diff --git a/src/python/grpcio_tests/tests/unit/_cython/_common.py b/src/python/grpcio_tests/tests/unit/_cython/_common.py
index 7fd3d19..d8210f3 100644
--- a/src/python/grpcio_tests/tests/unit/_cython/_common.py
+++ b/src/python/grpcio_tests/tests/unit/_cython/_common.py
@@ -100,7 +100,8 @@
         self.server.register_completion_queue(self.server_completion_queue)
         port = self.server.add_http2_port(b'[::]:0')
         self.server.start()
-        self.channel = cygrpc.Channel('localhost:{}'.format(port).encode(), [])
+        self.channel = cygrpc.Channel('localhost:{}'.format(port).encode(), [],
+                                      None)
 
         self._server_shutdown_tag = 'server_shutdown_tag'
         self.server_condition = threading.Condition()
diff --git a/src/python/grpcio_tests/tests/unit/_cython/_no_messages_server_completion_queue_per_call_test.py b/src/python/grpcio_tests/tests/unit/_cython/_no_messages_server_completion_queue_per_call_test.py
index 7caa98f..8a72178 100644
--- a/src/python/grpcio_tests/tests/unit/_cython/_no_messages_server_completion_queue_per_call_test.py
+++ b/src/python/grpcio_tests/tests/unit/_cython/_no_messages_server_completion_queue_per_call_test.py
@@ -19,6 +19,7 @@
 from grpc._cython import cygrpc
 
 from tests.unit._cython import _common
+from tests.unit._cython import test_utilities
 
 
 class Test(_common.RpcTest, unittest.TestCase):
@@ -41,31 +42,27 @@
                 server_request_call_tag,
             })
 
-        client_call = self.channel.create_call(None, _common.EMPTY_FLAGS,
-                                               self.client_completion_queue,
-                                               b'/twinkies', None, None)
         client_receive_initial_metadata_tag = 'client_receive_initial_metadata_tag'
         client_complete_rpc_tag = 'client_complete_rpc_tag'
-        with self.client_condition:
-            client_receive_initial_metadata_start_batch_result = (
-                client_call.start_client_batch([
-                    cygrpc.ReceiveInitialMetadataOperation(_common.EMPTY_FLAGS),
-                ], client_receive_initial_metadata_tag))
-            self.assertEqual(cygrpc.CallError.ok,
-                             client_receive_initial_metadata_start_batch_result)
-            client_complete_rpc_start_batch_result = client_call.start_client_batch(
+        client_call = self.channel.integrated_call(
+            _common.EMPTY_FLAGS, b'/twinkies', None, None,
+            _common.INVOCATION_METADATA, None, [(
                 [
-                    cygrpc.SendInitialMetadataOperation(
-                        _common.INVOCATION_METADATA, _common.EMPTY_FLAGS),
-                    cygrpc.SendCloseFromClientOperation(_common.EMPTY_FLAGS),
-                    cygrpc.ReceiveStatusOnClientOperation(_common.EMPTY_FLAGS),
-                ], client_complete_rpc_tag)
-            self.assertEqual(cygrpc.CallError.ok,
-                             client_complete_rpc_start_batch_result)
-            self.client_driver.add_due({
+                    cygrpc.ReceiveInitialMetadataOperation(_common.EMPTY_FLAGS),
+                ],
                 client_receive_initial_metadata_tag,
-                client_complete_rpc_tag,
-            })
+            )])
+        client_call.operate([
+            cygrpc.SendInitialMetadataOperation(_common.INVOCATION_METADATA,
+                                                _common.EMPTY_FLAGS),
+            cygrpc.SendCloseFromClientOperation(_common.EMPTY_FLAGS),
+            cygrpc.ReceiveStatusOnClientOperation(_common.EMPTY_FLAGS),
+        ], client_complete_rpc_tag)
+
+        client_events_future = test_utilities.SimpleFuture(
+            lambda: [
+                self.channel.next_call_event(),
+                self.channel.next_call_event(),])
 
         server_request_call_event = self.server_driver.event_with_tag(
             server_request_call_tag)
@@ -96,20 +93,23 @@
         server_complete_rpc_event = server_call_driver.event_with_tag(
             server_complete_rpc_tag)
 
-        client_receive_initial_metadata_event = self.client_driver.event_with_tag(
-            client_receive_initial_metadata_tag)
-        client_complete_rpc_event = self.client_driver.event_with_tag(
-            client_complete_rpc_tag)
+        client_events = client_events_future.result()
+        if client_events[0].tag is client_receive_initial_metadata_tag:
+            client_receive_initial_metadata_event = client_events[0]
+            client_complete_rpc_event = client_events[1]
+        else:
+            client_complete_rpc_event = client_events[0]
+            client_receive_initial_metadata_event = client_events[1]
 
         return (
             _common.OperationResult(server_request_call_start_batch_result,
                                     server_request_call_event.completion_type,
                                     server_request_call_event.success),
             _common.OperationResult(
-                client_receive_initial_metadata_start_batch_result,
+                cygrpc.CallError.ok,
                 client_receive_initial_metadata_event.completion_type,
                 client_receive_initial_metadata_event.success),
-            _common.OperationResult(client_complete_rpc_start_batch_result,
+            _common.OperationResult(cygrpc.CallError.ok,
                                     client_complete_rpc_event.completion_type,
                                     client_complete_rpc_event.success),
             _common.OperationResult(
diff --git a/src/python/grpcio_tests/tests/unit/_cython/_no_messages_single_server_completion_queue_test.py b/src/python/grpcio_tests/tests/unit/_cython/_no_messages_single_server_completion_queue_test.py
index 8582a39..47f39eb 100644
--- a/src/python/grpcio_tests/tests/unit/_cython/_no_messages_single_server_completion_queue_test.py
+++ b/src/python/grpcio_tests/tests/unit/_cython/_no_messages_single_server_completion_queue_test.py
@@ -19,6 +19,7 @@
 from grpc._cython import cygrpc
 
 from tests.unit._cython import _common
+from tests.unit._cython import test_utilities
 
 
 class Test(_common.RpcTest, unittest.TestCase):
@@ -36,28 +37,31 @@
                 server_request_call_tag,
             })
 
-        client_call = self.channel.create_call(None, _common.EMPTY_FLAGS,
-                                               self.client_completion_queue,
-                                               b'/twinkies', None, None)
         client_receive_initial_metadata_tag = 'client_receive_initial_metadata_tag'
         client_complete_rpc_tag = 'client_complete_rpc_tag'
-        with self.client_condition:
-            client_receive_initial_metadata_start_batch_result = (
-                client_call.start_client_batch([
-                    cygrpc.ReceiveInitialMetadataOperation(_common.EMPTY_FLAGS),
-                ], client_receive_initial_metadata_tag))
-            client_complete_rpc_start_batch_result = client_call.start_client_batch(
-                [
-                    cygrpc.SendInitialMetadataOperation(
-                        _common.INVOCATION_METADATA, _common.EMPTY_FLAGS),
-                    cygrpc.SendCloseFromClientOperation(_common.EMPTY_FLAGS),
-                    cygrpc.ReceiveStatusOnClientOperation(_common.EMPTY_FLAGS),
-                ], client_complete_rpc_tag)
-            self.client_driver.add_due({
-                client_receive_initial_metadata_tag,
-                client_complete_rpc_tag,
-            })
+        client_call = self.channel.integrated_call(
+            _common.EMPTY_FLAGS, b'/twinkies', None, None,
+            _common.INVOCATION_METADATA, None, [
+                (
+                    [
+                        cygrpc.SendInitialMetadataOperation(
+                            _common.INVOCATION_METADATA, _common.EMPTY_FLAGS),
+                        cygrpc.SendCloseFromClientOperation(
+                            _common.EMPTY_FLAGS),
+                        cygrpc.ReceiveStatusOnClientOperation(
+                            _common.EMPTY_FLAGS),
+                    ],
+                    client_complete_rpc_tag,
+                ),
+            ])
+        client_call.operate([
+            cygrpc.ReceiveInitialMetadataOperation(_common.EMPTY_FLAGS),
+        ], client_receive_initial_metadata_tag)
 
+        client_events_future = test_utilities.SimpleFuture(
+            lambda: [
+                self.channel.next_call_event(),
+                self.channel.next_call_event(),])
         server_request_call_event = self.server_driver.event_with_tag(
             server_request_call_tag)
 
@@ -87,20 +91,19 @@
         server_complete_rpc_event = self.server_driver.event_with_tag(
             server_complete_rpc_tag)
 
-        client_receive_initial_metadata_event = self.client_driver.event_with_tag(
-            client_receive_initial_metadata_tag)
-        client_complete_rpc_event = self.client_driver.event_with_tag(
-            client_complete_rpc_tag)
+        client_events = client_events_future.result()
+        client_receive_initial_metadata_event = client_events[0]
+        client_complete_rpc_event = client_events[1]
 
         return (
             _common.OperationResult(server_request_call_start_batch_result,
                                     server_request_call_event.completion_type,
                                     server_request_call_event.success),
             _common.OperationResult(
-                client_receive_initial_metadata_start_batch_result,
+                cygrpc.CallError.ok,
                 client_receive_initial_metadata_event.completion_type,
                 client_receive_initial_metadata_event.success),
-            _common.OperationResult(client_complete_rpc_start_batch_result,
+            _common.OperationResult(cygrpc.CallError.ok,
                                     client_complete_rpc_event.completion_type,
                                     client_complete_rpc_event.success),
             _common.OperationResult(
diff --git a/src/python/grpcio_tests/tests/unit/_cython/_read_some_but_not_all_responses_test.py b/src/python/grpcio_tests/tests/unit/_cython/_read_some_but_not_all_responses_test.py
index bc63b54..8a903bf 100644
--- a/src/python/grpcio_tests/tests/unit/_cython/_read_some_but_not_all_responses_test.py
+++ b/src/python/grpcio_tests/tests/unit/_cython/_read_some_but_not_all_responses_test.py
@@ -17,6 +17,7 @@
 import unittest
 
 from grpc._cython import cygrpc
+from tests.unit._cython import test_utilities
 
 _EMPTY_FLAGS = 0
 _EMPTY_METADATA = ()
@@ -118,7 +119,8 @@
         server.register_completion_queue(server_completion_queue)
         port = server.add_http2_port(b'[::]:0')
         server.start()
-        channel = cygrpc.Channel('localhost:{}'.format(port).encode(), set())
+        channel = cygrpc.Channel('localhost:{}'.format(port).encode(), set(),
+                                 None)
 
         server_shutdown_tag = 'server_shutdown_tag'
         server_driver = _ServerDriver(server_completion_queue,
@@ -127,10 +129,6 @@
 
         client_condition = threading.Condition()
         client_due = set()
-        client_completion_queue = cygrpc.CompletionQueue()
-        client_driver = _QueueDriver(client_condition, client_completion_queue,
-                                     client_due)
-        client_driver.start()
 
         server_call_condition = threading.Condition()
         server_send_initial_metadata_tag = 'server_send_initial_metadata_tag'
@@ -154,25 +152,28 @@
                                                   server_completion_queue,
                                                   server_rpc_tag)
 
-        client_call = channel.create_call(None, _EMPTY_FLAGS,
-                                          client_completion_queue, b'/twinkies',
-                                          None, None)
         client_receive_initial_metadata_tag = 'client_receive_initial_metadata_tag'
         client_complete_rpc_tag = 'client_complete_rpc_tag'
-        with client_condition:
-            client_receive_initial_metadata_start_batch_result = (
-                client_call.start_client_batch([
-                    cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),
-                ], client_receive_initial_metadata_tag))
-            client_due.add(client_receive_initial_metadata_tag)
-            client_complete_rpc_start_batch_result = (
-                client_call.start_client_batch([
-                    cygrpc.SendInitialMetadataOperation(_EMPTY_METADATA,
-                                                        _EMPTY_FLAGS),
-                    cygrpc.SendCloseFromClientOperation(_EMPTY_FLAGS),
-                    cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
-                ], client_complete_rpc_tag))
-            client_due.add(client_complete_rpc_tag)
+        client_call = channel.segregated_call(
+            _EMPTY_FLAGS, b'/twinkies', None, None, _EMPTY_METADATA, None, (
+                (
+                    [
+                        cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),
+                    ],
+                    client_receive_initial_metadata_tag,
+                ),
+                (
+                    [
+                        cygrpc.SendInitialMetadataOperation(
+                            _EMPTY_METADATA, _EMPTY_FLAGS),
+                        cygrpc.SendCloseFromClientOperation(_EMPTY_FLAGS),
+                        cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
+                    ],
+                    client_complete_rpc_tag,
+                ),
+            ))
+        client_receive_initial_metadata_event_future = test_utilities.SimpleFuture(
+            client_call.next_event)
 
         server_rpc_event = server_driver.first_event()
 
@@ -208,19 +209,20 @@
             server_complete_rpc_tag)
         server_call_driver.events()
 
-        with client_condition:
-            client_receive_first_message_tag = 'client_receive_first_message_tag'
-            client_receive_first_message_start_batch_result = (
-                client_call.start_client_batch([
-                    cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),
-                ], client_receive_first_message_tag))
-            client_due.add(client_receive_first_message_tag)
-        client_receive_first_message_event = client_driver.event_with_tag(
-            client_receive_first_message_tag)
+        client_recieve_initial_metadata_event = client_receive_initial_metadata_event_future.result(
+        )
 
-        client_call_cancel_result = client_call.cancel()
-        client_driver.events()
+        client_receive_first_message_tag = 'client_receive_first_message_tag'
+        client_call.operate([
+            cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),
+        ], client_receive_first_message_tag)
+        client_receive_first_message_event = client_call.next_event()
 
+        client_call_cancel_result = client_call.cancel(
+            cygrpc.StatusCode.cancelled, 'Cancelled during test!')
+        client_complete_rpc_event = client_call.next_event()
+
+        channel.close(cygrpc.StatusCode.unknown, 'Channel closed!')
         server.shutdown(server_completion_queue, server_shutdown_tag)
         server.cancel_all_calls()
         server_driver.events()
@@ -228,11 +230,6 @@
         self.assertEqual(cygrpc.CallError.ok, request_call_result)
         self.assertEqual(cygrpc.CallError.ok,
                          server_send_initial_metadata_start_batch_result)
-        self.assertEqual(cygrpc.CallError.ok,
-                         client_receive_initial_metadata_start_batch_result)
-        self.assertEqual(cygrpc.CallError.ok,
-                         client_complete_rpc_start_batch_result)
-        self.assertEqual(cygrpc.CallError.ok, client_call_cancel_result)
         self.assertIs(server_rpc_tag, server_rpc_event.tag)
         self.assertEqual(cygrpc.CompletionType.operation_complete,
                          server_rpc_event.completion_type)
diff --git a/src/python/grpcio_tests/tests/unit/_cython/cygrpc_test.py b/src/python/grpcio_tests/tests/unit/_cython/cygrpc_test.py
index 23f5ef6..724a690 100644
--- a/src/python/grpcio_tests/tests/unit/_cython/cygrpc_test.py
+++ b/src/python/grpcio_tests/tests/unit/_cython/cygrpc_test.py
@@ -51,8 +51,8 @@
         del server
 
     def testChannelUpDown(self):
-        channel = cygrpc.Channel(b'[::]:0', None)
-        del channel
+        channel = cygrpc.Channel(b'[::]:0', None, None)
+        channel.close(cygrpc.StatusCode.cancelled, 'Test method anyway!')
 
     def test_metadata_plugin_call_credentials_up_down(self):
         cygrpc.MetadataPluginCallCredentials(_metadata_plugin,
@@ -121,7 +121,7 @@
                                                  client_credentials)
         else:
             self.client_channel = cygrpc.Channel('localhost:{}'.format(
-                self.port).encode(), set())
+                self.port).encode(), set(), None)
         if host_override:
             self.host_argument = None  # default host
             self.expected_host = host_override
@@ -131,17 +131,20 @@
             self.expected_host = self.host_argument
 
     def tearDownMixin(self):
+        self.client_channel.close(cygrpc.StatusCode.ok, 'test being torn down!')
+        del self.client_channel
         del self.server
         del self.client_completion_queue
         del self.server_completion_queue
 
-    def _perform_operations(self, operations, call, queue, deadline,
-                            description):
-        """Perform the list of operations with given call, queue, and deadline.
+    def _perform_queue_operations(self, operations, call, queue, deadline,
+                                  description):
+        """Perform the operations with given call, queue, and deadline.
 
-    Invocation errors are reported with as an exception with `description` in
-    the message. Performs the operations asynchronously, returning a future.
-    """
+        Invocation errors are reported with as an exception with `description`
+        in the message. Performs the operations asynchronously, returning a
+        future.
+        """
 
         def performer():
             tag = object()
@@ -185,9 +188,6 @@
         self.assertEqual(cygrpc.CallError.ok, request_call_result)
 
         client_call_tag = object()
-        client_call = self.client_channel.create_call(
-            None, 0, self.client_completion_queue, METHOD, self.host_argument,
-            DEADLINE)
         client_initial_metadata = (
             (
                 CLIENT_METADATA_ASCII_KEY,
@@ -198,18 +198,24 @@
                 CLIENT_METADATA_BIN_VALUE,
             ),
         )
-        client_start_batch_result = client_call.start_client_batch([
-            cygrpc.SendInitialMetadataOperation(client_initial_metadata,
-                                                _EMPTY_FLAGS),
-            cygrpc.SendMessageOperation(REQUEST, _EMPTY_FLAGS),
-            cygrpc.SendCloseFromClientOperation(_EMPTY_FLAGS),
-            cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),
-            cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),
-            cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
-        ], client_call_tag)
-        self.assertEqual(cygrpc.CallError.ok, client_start_batch_result)
-        client_event_future = test_utilities.CompletionQueuePollFuture(
-            self.client_completion_queue, DEADLINE)
+        client_call = self.client_channel.integrated_call(
+            0, METHOD, self.host_argument, DEADLINE, client_initial_metadata,
+            None, [
+                (
+                    [
+                        cygrpc.SendInitialMetadataOperation(
+                            client_initial_metadata, _EMPTY_FLAGS),
+                        cygrpc.SendMessageOperation(REQUEST, _EMPTY_FLAGS),
+                        cygrpc.SendCloseFromClientOperation(_EMPTY_FLAGS),
+                        cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),
+                        cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),
+                        cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
+                    ],
+                    client_call_tag,
+                ),
+            ])
+        client_event_future = test_utilities.SimpleFuture(
+            self.client_channel.next_call_event)
 
         request_event = self.server_completion_queue.poll(deadline=DEADLINE)
         self.assertEqual(cygrpc.CompletionType.operation_complete,
@@ -304,66 +310,76 @@
         del client_call
         del server_call
 
-    def test6522(self):
+    def test_6522(self):
         DEADLINE = time.time() + 5
         DEADLINE_TOLERANCE = 0.25
         METHOD = b'twinkies'
 
         empty_metadata = ()
 
+        # Prologue
         server_request_tag = object()
         self.server.request_call(self.server_completion_queue,
                                  self.server_completion_queue,
                                  server_request_tag)
-        client_call = self.client_channel.create_call(
-            None, 0, self.client_completion_queue, METHOD, self.host_argument,
-            DEADLINE)
+        client_call = self.client_channel.segregated_call(
+            0, METHOD, self.host_argument, DEADLINE, None, None, ([(
+                [
+                    cygrpc.SendInitialMetadataOperation(empty_metadata,
+                                                        _EMPTY_FLAGS),
+                    cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),
+                ],
+                object(),
+            ), (
+                [
+                    cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
+                ],
+                object(),
+            )]))
 
-        # Prologue
-        def perform_client_operations(operations, description):
-            return self._perform_operations(operations, client_call,
-                                            self.client_completion_queue,
-                                            DEADLINE, description)
-
-        client_event_future = perform_client_operations([
-            cygrpc.SendInitialMetadataOperation(empty_metadata, _EMPTY_FLAGS),
-            cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),
-        ], "Client prologue")
+        client_initial_metadata_event_future = test_utilities.SimpleFuture(
+            client_call.next_event)
 
         request_event = self.server_completion_queue.poll(deadline=DEADLINE)
         server_call = request_event.call
 
         def perform_server_operations(operations, description):
-            return self._perform_operations(operations, server_call,
-                                            self.server_completion_queue,
-                                            DEADLINE, description)
+            return self._perform_queue_operations(operations, server_call,
+                                                  self.server_completion_queue,
+                                                  DEADLINE, description)
 
         server_event_future = perform_server_operations([
             cygrpc.SendInitialMetadataOperation(empty_metadata, _EMPTY_FLAGS),
         ], "Server prologue")
 
-        client_event_future.result()  # force completion
+        client_initial_metadata_event_future.result()  # force completion
         server_event_future.result()
 
         # Messaging
         for _ in range(10):
-            client_event_future = perform_client_operations([
+            client_call.operate([
                 cygrpc.SendMessageOperation(b'', _EMPTY_FLAGS),
                 cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),
             ], "Client message")
+            client_message_event_future = test_utilities.SimpleFuture(
+                client_call.next_event)
             server_event_future = perform_server_operations([
                 cygrpc.SendMessageOperation(b'', _EMPTY_FLAGS),
                 cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),
             ], "Server receive")
 
-            client_event_future.result()  # force completion
+            client_message_event_future.result()  # force completion
             server_event_future.result()
 
         # Epilogue
-        client_event_future = perform_client_operations([
+        client_call.operate([
             cygrpc.SendCloseFromClientOperation(_EMPTY_FLAGS),
-            cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS)
         ], "Client epilogue")
+        # One for ReceiveStatusOnClient, one for SendCloseFromClient.
+        client_events_future = test_utilities.SimpleFuture(
+            lambda: {
+                client_call.next_event(),
+                client_call.next_event(),})
 
         server_event_future = perform_server_operations([
             cygrpc.ReceiveCloseOnServerOperation(_EMPTY_FLAGS),
@@ -371,7 +387,7 @@
                 empty_metadata, cygrpc.StatusCode.ok, b'', _EMPTY_FLAGS)
         ], "Server epilogue")
 
-        client_event_future.result()  # force completion
+        client_events_future.result()  # force completion
         server_event_future.result()
 
 
diff --git a/src/python/grpcio_tests/tests/unit/_invalid_metadata_test.py b/src/python/grpcio_tests/tests/unit/_invalid_metadata_test.py
index 4edf0fc..f153089 100644
--- a/src/python/grpcio_tests/tests/unit/_invalid_metadata_test.py
+++ b/src/python/grpcio_tests/tests/unit/_invalid_metadata_test.py
@@ -81,29 +81,16 @@
         request = b'\x07\x08'
         metadata = (('InVaLiD', 'UnaryRequestFutureUnaryResponse'),)
         expected_error_details = "metadata was invalid: %s" % metadata
-        response_future = self._unary_unary.future(request, metadata=metadata)
-        with self.assertRaises(grpc.RpcError) as exception_context:
-            response_future.result()
-        self.assertEqual(exception_context.exception.details(),
-                         expected_error_details)
-        self.assertEqual(exception_context.exception.code(),
-                         grpc.StatusCode.INTERNAL)
-        self.assertEqual(response_future.details(), expected_error_details)
-        self.assertEqual(response_future.code(), grpc.StatusCode.INTERNAL)
+        with self.assertRaises(ValueError) as exception_context:
+            self._unary_unary.future(request, metadata=metadata)
 
     def testUnaryRequestStreamResponse(self):
         request = b'\x37\x58'
         metadata = (('InVaLiD', 'UnaryRequestStreamResponse'),)
         expected_error_details = "metadata was invalid: %s" % metadata
-        response_iterator = self._unary_stream(request, metadata=metadata)
-        with self.assertRaises(grpc.RpcError) as exception_context:
-            next(response_iterator)
-        self.assertEqual(exception_context.exception.details(),
-                         expected_error_details)
-        self.assertEqual(exception_context.exception.code(),
-                         grpc.StatusCode.INTERNAL)
-        self.assertEqual(response_iterator.details(), expected_error_details)
-        self.assertEqual(response_iterator.code(), grpc.StatusCode.INTERNAL)
+        with self.assertRaises(ValueError) as exception_context:
+            self._unary_stream(request, metadata=metadata)
+        self.assertIn(expected_error_details, str(exception_context.exception))
 
     def testStreamRequestBlockingUnaryResponse(self):
         request_iterator = (
@@ -129,32 +116,18 @@
             b'\x07\x08' for _ in range(test_constants.STREAM_LENGTH))
         metadata = (('InVaLiD', 'StreamRequestFutureUnaryResponse'),)
         expected_error_details = "metadata was invalid: %s" % metadata
-        response_future = self._stream_unary.future(
-            request_iterator, metadata=metadata)
-        with self.assertRaises(grpc.RpcError) as exception_context:
-            response_future.result()
-        self.assertEqual(exception_context.exception.details(),
-                         expected_error_details)
-        self.assertEqual(exception_context.exception.code(),
-                         grpc.StatusCode.INTERNAL)
-        self.assertEqual(response_future.details(), expected_error_details)
-        self.assertEqual(response_future.code(), grpc.StatusCode.INTERNAL)
+        with self.assertRaises(ValueError) as exception_context:
+            self._stream_unary.future(request_iterator, metadata=metadata)
+        self.assertIn(expected_error_details, str(exception_context.exception))
 
     def testStreamRequestStreamResponse(self):
         request_iterator = (
             b'\x07\x08' for _ in range(test_constants.STREAM_LENGTH))
         metadata = (('InVaLiD', 'StreamRequestStreamResponse'),)
         expected_error_details = "metadata was invalid: %s" % metadata
-        response_iterator = self._stream_stream(
-            request_iterator, metadata=metadata)
-        with self.assertRaises(grpc.RpcError) as exception_context:
-            next(response_iterator)
-        self.assertEqual(exception_context.exception.details(),
-                         expected_error_details)
-        self.assertEqual(exception_context.exception.code(),
-                         grpc.StatusCode.INTERNAL)
-        self.assertEqual(response_iterator.details(), expected_error_details)
-        self.assertEqual(response_iterator.code(), grpc.StatusCode.INTERNAL)
+        with self.assertRaises(ValueError) as exception_context:
+            self._stream_stream(request_iterator, metadata=metadata)
+        self.assertIn(expected_error_details, str(exception_context.exception))
 
 
 if __name__ == '__main__':
diff --git a/src/python/grpcio_tests/tests/unit/_thread_cleanup_test.py b/src/python/grpcio_tests/tests/unit/_thread_cleanup_test.py
deleted file mode 100644
index 18f5af0..0000000
--- a/src/python/grpcio_tests/tests/unit/_thread_cleanup_test.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# Copyright 2016 gRPC authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Tests for CleanupThread."""
-
-import threading
-import time
-import unittest
-
-from grpc import _common
-
-_SHORT_TIME = 0.5
-_LONG_TIME = 5.0
-_EPSILON = 0.5
-
-
-def cleanup(timeout):
-    if timeout is not None:
-        time.sleep(timeout)
-    else:
-        time.sleep(_LONG_TIME)
-
-
-def slow_cleanup(timeout):
-    # Don't respect timeout
-    time.sleep(_LONG_TIME)
-
-
-class CleanupThreadTest(unittest.TestCase):
-
-    def testTargetInvocation(self):
-        event = threading.Event()
-
-        def target(arg1, arg2, arg3=None):
-            self.assertEqual('arg1', arg1)
-            self.assertEqual('arg2', arg2)
-            self.assertEqual('arg3', arg3)
-            event.set()
-
-        cleanup_thread = _common.CleanupThread(
-            behavior=lambda x: None,
-            target=target,
-            name='test-name',
-            args=('arg1', 'arg2'),
-            kwargs={
-                'arg3': 'arg3'
-            })
-        cleanup_thread.start()
-        cleanup_thread.join()
-        self.assertEqual(cleanup_thread.name, 'test-name')
-        self.assertTrue(event.is_set())
-
-    def testJoinNoTimeout(self):
-        cleanup_thread = _common.CleanupThread(behavior=cleanup)
-        cleanup_thread.start()
-        start_time = time.time()
-        cleanup_thread.join()
-        end_time = time.time()
-        self.assertAlmostEqual(
-            _LONG_TIME, end_time - start_time, delta=_EPSILON)
-
-    def testJoinTimeout(self):
-        cleanup_thread = _common.CleanupThread(behavior=cleanup)
-        cleanup_thread.start()
-        start_time = time.time()
-        cleanup_thread.join(_SHORT_TIME)
-        end_time = time.time()
-        self.assertAlmostEqual(
-            _SHORT_TIME, end_time - start_time, delta=_EPSILON)
-
-    def testJoinTimeoutSlowBehavior(self):
-        cleanup_thread = _common.CleanupThread(behavior=slow_cleanup)
-        cleanup_thread.start()
-        start_time = time.time()
-        cleanup_thread.join(_SHORT_TIME)
-        end_time = time.time()
-        self.assertAlmostEqual(
-            _LONG_TIME, end_time - start_time, delta=_EPSILON)
-
-    def testJoinTimeoutSlowTarget(self):
-        event = threading.Event()
-
-        def target():
-            event.wait(_LONG_TIME)
-
-        cleanup_thread = _common.CleanupThread(behavior=cleanup, target=target)
-        cleanup_thread.start()
-        start_time = time.time()
-        cleanup_thread.join(_SHORT_TIME)
-        end_time = time.time()
-        self.assertAlmostEqual(
-            _SHORT_TIME, end_time - start_time, delta=_EPSILON)
-        event.set()
-
-    def testJoinZeroTimeout(self):
-        cleanup_thread = _common.CleanupThread(behavior=cleanup)
-        cleanup_thread.start()
-        start_time = time.time()
-        cleanup_thread.join(0)
-        end_time = time.time()
-        self.assertAlmostEqual(0, end_time - start_time, delta=_EPSILON)
-
-
-if __name__ == '__main__':
-    unittest.main(verbosity=2)
diff --git a/src/ruby/lib/grpc/version.rb b/src/ruby/lib/grpc/version.rb
index 2cb7c4b..15f3751 100644
--- a/src/ruby/lib/grpc/version.rb
+++ b/src/ruby/lib/grpc/version.rb
@@ -14,5 +14,5 @@
 
 # GRPC contains the General RPC module.
 module GRPC
-  VERSION = '1.12.0.dev'
+  VERSION = '1.13.0.dev'
 end
diff --git a/src/ruby/pb/grpc/health/v1/health_services_pb.rb b/src/ruby/pb/grpc/health/v1/health_services_pb.rb
index 520f8e7..169e160 100644
--- a/src/ruby/pb/grpc/health/v1/health_services_pb.rb
+++ b/src/ruby/pb/grpc/health/v1/health_services_pb.rb
@@ -1,7 +1,7 @@
 # Generated by the protocol buffer compiler.  DO NOT EDIT!
 # Source: grpc/health/v1/health.proto for package 'grpc.health.v1'
 # Original file comments:
-# Copyright 2015 gRPC authors.
+# Copyright 2015 The gRPC Authors
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -15,6 +15,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+# The canonical version of this proto can be found at
+# https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto
+#
 
 require 'grpc'
 require 'grpc/health/v1/health_pb'
diff --git a/src/ruby/tools/version.rb b/src/ruby/tools/version.rb
index 822f70e..09d5c82 100644
--- a/src/ruby/tools/version.rb
+++ b/src/ruby/tools/version.rb
@@ -14,6 +14,6 @@
 
 module GRPC
   module Tools
-    VERSION = '1.12.0.dev'
+    VERSION = '1.13.0.dev'
   end
 end
diff --git a/templates/CMakeLists.txt.template b/templates/CMakeLists.txt.template
index 8a1581a..06adb33 100644
--- a/templates/CMakeLists.txt.template
+++ b/templates/CMakeLists.txt.template
@@ -82,6 +82,7 @@
   set(gRPC_INSTALL_LIBDIR "lib" CACHE STRING "Installation directory for libraries")
   set(gRPC_INSTALL_INCLUDEDIR "include" CACHE STRING "Installation directory for headers")
   set(gRPC_INSTALL_CMAKEDIR "lib/cmake/<%text>${PACKAGE_NAME}</%text>" CACHE STRING "Installation directory for cmake config files")
+  set(gRPC_INSTALL_SHAREDIR "share/grpc" CACHE STRING "Installation directory for root certificates")
 
   # Options
   option(gRPC_BUILD_TESTS "Build tests" OFF)
@@ -507,3 +508,6 @@
       DESTINATION <%text>${gRPC_INSTALL_CMAKEDIR}</%text>
     )
   endforeach()
+  
+  install(FILES <%text>${CMAKE_CURRENT_SOURCE_DIR}/etc/roots.pem</%text>
+    DESTINATION <%text>${gRPC_INSTALL_SHAREDIR}</%text>)
diff --git a/templates/test/cpp/naming/resolver_component_tests_defs.include b/templates/test/cpp/naming/resolver_component_tests_defs.include
index c29b466..2690af4 100644
--- a/templates/test/cpp/naming/resolver_component_tests_defs.include
+++ b/templates/test/cpp/naming/resolver_component_tests_defs.include
@@ -1,4 +1,4 @@
-<%def name="resolver_component_tests(tests)">#!/bin/bash
+<%def name="resolver_component_tests(tests)">#!/usr/bin/env python
 # Copyright 2015 gRPC authors.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,84 +15,129 @@
 
 # This file is auto-generated
 
-set -ex
+import argparse
+import sys
+import subprocess
+import tempfile
+import os
+import time
+import signal
 
-# all command args required in this set order
-FLAGS_test_bin_path=$(echo "$1" | grep '\--test_bin_path=' | sed 's/^--test_bin_path=//')
-FLAGS_dns_server_bin_path=$(echo "$2" | grep '\--dns_server_bin_path=' | sed 's/^--dns_server_bin_path=//')
-FLAGS_records_config_path=$(echo "$3" | grep '\--records_config_path=' | sed 's/^--records_config_path=//')
-FLAGS_dns_server_port=$(echo "$4" | grep '\--dns_server_port=' | sed 's/^--dns_server_port=//')
-FLAGS_dns_resolver_bin_path=$(echo "$5" | grep '\--dns_resolver_bin_path=' | sed 's/^--dns_resolver_bin_path=//')
-FLAGS_tcp_connect_bin_path=$(echo "$6" | grep '\--tcp_connect_bin_path=' | sed 's/^--tcp_connect_bin_path=//')
 
-for cmd_arg in "$FLAGS_test_bin_path" "$FLAGS_dns_server_bin_path" "$FLAGS_records_config_path" "$FLAGS_dns_server_port" "$FLAGS_dns_resolver_bin_path" "$FLAGS_tcp_connect_bin_path"; do
-  if [[ "$cmd_arg" == "" ]]; then
-    echo "Missing a CMD arg" && exit 1
-  fi
-done
+argp = argparse.ArgumentParser(description='Run c-ares resolver tests')
+argp.add_argument('--test_bin_path', default=None, type=str,
+                  help='Path to gtest test binary to invoke.')
+argp.add_argument('--dns_server_bin_path', default=None, type=str,
+                  help='Path to local DNS server python script.')
+argp.add_argument('--records_config_path', default=None, type=str,
+                  help=('Path to DNS records yaml file that '
+                        'specifies records for the DNS sever. '))
+argp.add_argument('--dns_server_port', default=None, type=int,
+                  help=('Port that local DNS server is listening on.'))
+argp.add_argument('--dns_resolver_bin_path', default=None, type=str,
+                  help=('Path to the DNS health check utility.'))
+argp.add_argument('--tcp_connect_bin_path', default=None, type=str,
+                  help=('Path to the TCP health check utility.'))
+args = argp.parse_args()
 
-if [[ "$GRPC_DNS_RESOLVER" != "" && "$GRPC_DNS_RESOLVER" != ares ]]; then
-  echo "This test only works under GRPC_DNS_RESOLVER=ares. Have GRPC_DNS_RESOLVER=$GRPC_DNS_RESOLVER" && exit 1
-fi
-export GRPC_DNS_RESOLVER=ares
+def test_runner_log(msg):
+  sys.stderr.write('\n%s: %s\n' % (__file__, msg))
 
-DNS_SERVER_LOG="$(mktemp)"
-"$FLAGS_dns_server_bin_path" --records_config_path="$FLAGS_records_config_path" --port="$FLAGS_dns_server_port" > "$DNS_SERVER_LOG" 2>&1 &
-DNS_SERVER_PID=$!
-echo "Local DNS server started. PID: $DNS_SERVER_PID"
+cur_resolver = os.environ.get('GRPC_DNS_RESOLVER')
+if cur_resolver and cur_resolver != 'ares':
+  test_runner_log(('WARNING: cur resolver set to %s. This set of tests '
+      'needs to use GRPC_DNS_RESOLVER=ares.'))
+  test_runner_log('Exit 1 without running tests.')
+  sys.exit(1)
+os.environ.update({'GRPC_DNS_RESOLVER': 'ares'})
 
-# Health check local DNS server TCP and UDP ports
-for ((i=0;i<30;i++));
-do
-  echo "Retry health-check local DNS server by attempting a DNS query and TCP handshake"
-  RETRY=0
-  $FLAGS_dns_resolver_bin_path -s 127.0.0.1 -p "$FLAGS_dns_server_port" -n health-check-local-dns-server-is-alive.resolver-tests.grpctestingexp. -t 1 | grep '123.123.123.123' || RETRY=1
-  $FLAGS_tcp_connect_bin_path -s 127.0.0.1 -p "$FLAGS_dns_server_port" -t 1 || RETRY=1
-  if [[ "$RETRY" == 0 ]]; then
-    break
-  fi;
-  sleep 0.1
-done
+def wait_until_dns_server_is_up(args,
+                                dns_server_subprocess,
+                                dns_server_subprocess_output):
+  for i in range(0, 30):
+    test_runner_log('Health check: attempt to connect to DNS server over TCP.')
+    tcp_connect_subprocess = subprocess.Popen([
+        args.tcp_connect_bin_path,
+        '--server_host', '127.0.0.1',
+        '--server_port', str(args.dns_server_port),
+        '--timeout', str(1)])
+    tcp_connect_subprocess.communicate()
+    if tcp_connect_subprocess.returncode == 0:
+      test_runner_log(('Health check: attempt to make an A-record '
+                       'query to DNS server.'))
+      dns_resolver_subprocess = subprocess.Popen([
+          args.dns_resolver_bin_path,
+          '--qname', 'health-check-local-dns-server-is-alive.resolver-tests.grpctestingexp',
+          '--server_host', '127.0.0.1',
+          '--server_port', str(args.dns_server_port)],
+          stdout=subprocess.PIPE)
+      dns_resolver_stdout, _ = dns_resolver_subprocess.communicate()
+      if dns_resolver_subprocess.returncode == 0:
+        if '123.123.123.123' in dns_resolver_stdout:
+          test_runner_log(('DNS server is up! '
+                           'Successfully reached it over UDP and TCP.'))
+        return
+    time.sleep(0.1)
+  dns_server_subprocess.kill()
+  dns_server_subprocess.wait()
+  test_runner_log(('Failed to reach DNS server over TCP and/or UDP. '
+                   'Exitting without running tests.'))
+  test_runner_log('======= DNS server stdout '
+                  '(merged stdout and stderr) =============')
+  with open(dns_server_subprocess_output, 'r') as l:
+    test_runner_log(l.read())
+  test_runner_log('======= end DNS server output=========')
+  sys.exit(1)
 
-if [[ $RETRY == 1 ]]; then
-  echo "FAILED TO START LOCAL DNS SERVER"
-  kill -SIGTERM "$DNS_SERVER_PID" || true
-  wait
-  echo "========== DNS server log (merged stdout and stderr) ========="
-  cat "$DNS_SERVER_LOG"
-  echo "========== end DNS server log ================================"
-  exit 1
-fi
+dns_server_subprocess_output = tempfile.mktemp()
+with open(dns_server_subprocess_output, 'w') as l:
+  dns_server_subprocess = subprocess.Popen([
+      args.dns_server_bin_path,
+      '--port', str(args.dns_server_port),
+      '--records_config_path', args.records_config_path],
+      stdin=subprocess.PIPE,
+      stdout=l,
+      stderr=l)
 
-function terminate_all {
-  echo "Received signal. Terminating $! and $DNS_SERVER_PID"
-  kill -SIGTERM "$!" || true
-  kill -SIGTERM "$DNS_SERVER_PID" || true
-  wait
-  exit 1
-}
+def _quit_on_signal(signum, _frame):
+  test_runner_log('Received signal: %d' % signum)
+  dns_server_subprocess.kill()
+  dns_server_subprocess.wait()
+  sys.exit(1)
 
-trap terminate_all SIGTERM SIGINT
-
-EXIT_CODE=0
-# TODO: this test should check for GCE residency and skip tests using _grpclb._tcp.* SRV records once GCE residency checks are made
-# in the resolver.
+signal.signal(signal.SIGINT, _quit_on_signal)
+signal.signal(signal.SIGTERM, _quit_on_signal)
+wait_until_dns_server_is_up(args,
+                            dns_server_subprocess,
+                            dns_server_subprocess_output)
+num_test_failures = 0
 
 % for test in tests:
-$FLAGS_test_bin_path \\
+test_runner_log('Run test with target: %s' % '${test['target_name']}')\
 
-  --target_name='${test['target_name']}' \\
+current_test_subprocess = subprocess.Popen([\
 
-  --expected_addrs='${test['expected_addrs']}' \\
+  args.test_bin_path,\
 
-  --expected_chosen_service_config='${test['expected_chosen_service_config']}' \\
+  '--target_name', '${test['target_name']}',\
 
-  --expected_lb_policy='${test['expected_lb_policy']}' \\
+  '--expected_addrs', '${test['expected_addrs']}',\
 
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
+  '--expected_chosen_service_config', '${test['expected_chosen_service_config']}',\
+
+  '--expected_lb_policy', '${test['expected_lb_policy']}',\
+
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])\
+
+current_test_subprocess.communicate()\
+
+if current_test_subprocess.returncode != 0:\
+
+  num_test_failures += 1
 
 % endfor
-kill -SIGTERM "$DNS_SERVER_PID" || true
-wait
-exit $EXIT_CODE</%def>
+test_runner_log('now kill DNS server')
+dns_server_subprocess.kill()
+dns_server_subprocess.wait()
+test_runner_log('%d tests failed.' % num_test_failures)
+sys.exit(num_test_failures)</%def>
diff --git a/templates/test/cpp/naming/resolver_component_tests_runner.sh.template b/templates/test/cpp/naming/resolver_component_tests_runner.py.template
similarity index 100%
rename from templates/test/cpp/naming/resolver_component_tests_runner.sh.template
rename to templates/test/cpp/naming/resolver_component_tests_runner.py.template
diff --git a/templates/tools/dockerfile/interoptest/grpc_interop_python/Dockerfile.template b/templates/tools/dockerfile/interoptest/grpc_interop_python/Dockerfile.template
index f5a53f0..bf28796 100644
--- a/templates/tools/dockerfile/interoptest/grpc_interop_python/Dockerfile.template
+++ b/templates/tools/dockerfile/interoptest/grpc_interop_python/Dockerfile.template
@@ -18,6 +18,10 @@
   
   <%include file="../../apt_get_basic.include"/>
   <%include file="../../python_deps.include"/>
+  # Install pip and virtualenv for Python 3.4
+  RUN curl https://bootstrap.pypa.io/get-pip.py | python3.4
+  RUN python3.4 -m pip install virtualenv
+
   <%include file="../../run_tests_addons.include"/>
   # Define the default command.
   CMD ["bash"]
diff --git a/templates/tools/dockerfile/test/multilang_jessie_x64/Dockerfile.template b/templates/tools/dockerfile/test/multilang_jessie_x64/Dockerfile.template
index 0d47aa9..672e9fb 100644
--- a/templates/tools/dockerfile/test/multilang_jessie_x64/Dockerfile.template
+++ b/templates/tools/dockerfile/test/multilang_jessie_x64/Dockerfile.template
@@ -25,6 +25,10 @@
   <%include file="../../php_deps.include"/>
   <%include file="../../ruby_deps.include"/>
   <%include file="../../python_deps.include"/>
+  # Install pip and virtualenv for Python 3.4
+  RUN curl https://bootstrap.pypa.io/get-pip.py | python3.4
+  RUN python3.4 -m pip install virtualenv
+  
   # Install coverage for Python test coverage reporting
   RUN pip install coverage
   ENV PATH ~/.local/bin:$PATH
diff --git a/templates/tools/dockerfile/test/python_jessie_x64/Dockerfile.template b/templates/tools/dockerfile/test/python_jessie_x64/Dockerfile.template
index dba6a22..e73b839 100644
--- a/templates/tools/dockerfile/test/python_jessie_x64/Dockerfile.template
+++ b/templates/tools/dockerfile/test/python_jessie_x64/Dockerfile.template
@@ -19,6 +19,10 @@
   <%include file="../../apt_get_basic.include"/>
   <%include file="../../gcp_api_libraries.include"/>
   <%include file="../../python_deps.include"/>
+  # Install pip and virtualenv for Python 3.4
+  RUN curl https://bootstrap.pypa.io/get-pip.py | python3.4
+  RUN python3.4 -m pip install virtualenv
+
   <%include file="../../run_tests_addons.include"/>
   # Define the default command.
   CMD ["bash"]
diff --git a/templates/tools/dockerfile/test/python_pyenv_x64/Dockerfile.template b/templates/tools/dockerfile/test/python_pyenv_x64/Dockerfile.template
index 0df19f6..ba65c06 100644
--- a/templates/tools/dockerfile/test/python_pyenv_x64/Dockerfile.template
+++ b/templates/tools/dockerfile/test/python_pyenv_x64/Dockerfile.template
@@ -20,6 +20,10 @@
   <%include file="../../gcp_api_libraries.include"/>
   <%include file="../../python_deps.include"/>
   <%include file="../../apt_get_pyenv.include"/>
+  # Install pip and virtualenv for Python 3.4
+  RUN curl https://bootstrap.pypa.io/get-pip.py | python3.4
+  RUN python3.4 -m pip install virtualenv
+
   <%include file="../../run_tests_addons.include"/>
   # Define the default command.
   CMD ["bash"]
diff --git a/test/core/client_channel/resolvers/dns_resolver_cooldown_test.cc b/test/core/client_channel/resolvers/dns_resolver_cooldown_test.cc
index 01c61a9..37e1d71 100644
--- a/test/core/client_channel/resolvers/dns_resolver_cooldown_test.cc
+++ b/test/core/client_channel/resolvers/dns_resolver_cooldown_test.cc
@@ -145,7 +145,6 @@
   grpc_core::OrphanablePtr<grpc_core::Resolver> resolver;
   grpc_channel_args* result = nullptr;
   grpc_millis delay_before_second_resolution = 0;
-  bool using_cares = false;
 };
 
 // Counter for the number of times a resolution notification callback has been
@@ -155,81 +154,100 @@
 // Set to true by the last callback in the resolution chain.
 bool g_all_callbacks_invoked;
 
-void on_third_resolution(void* arg, grpc_error* error) {
+void on_fourth_resolution(void* arg, grpc_error* error) {
   OnResolutionCallbackArg* cb_arg = static_cast<OnResolutionCallbackArg*>(arg);
+  grpc_channel_args_destroy(cb_arg->result);
   GPR_ASSERT(error == GRPC_ERROR_NONE);
   ++g_on_resolution_invocations_count;
-  grpc_channel_args_destroy(cb_arg->result);
   gpr_log(GPR_INFO,
-          "3rd: g_on_resolution_invocations_count: %d, g_resolution_count: %d",
+          "4th: g_on_resolution_invocations_count: %d, g_resolution_count: %d",
           g_on_resolution_invocations_count, g_resolution_count);
   // In this case we expect to have incurred in another system-level resolution
-  // because on_second_resolution slept for longer than the min resolution
+  // because on_third_resolution slept for longer than the min resolution
   // period.
-  GPR_ASSERT(g_on_resolution_invocations_count == 3);
-  GPR_ASSERT(g_resolution_count == 2);
+  GPR_ASSERT(g_on_resolution_invocations_count == 4);
+  GPR_ASSERT(g_resolution_count == 3);
   cb_arg->resolver.reset();
-  if (cb_arg->using_cares) {
-    gpr_atm_rel_store(&g_iomgr_args.done_atm, 1);
-    gpr_mu_lock(g_iomgr_args.mu);
-    GRPC_LOG_IF_ERROR("pollset_kick",
-                      grpc_pollset_kick(g_iomgr_args.pollset, nullptr));
-    gpr_mu_unlock(g_iomgr_args.mu);
-  }
+  gpr_atm_rel_store(&g_iomgr_args.done_atm, 1);
+  gpr_mu_lock(g_iomgr_args.mu);
+  GRPC_LOG_IF_ERROR("pollset_kick",
+                    grpc_pollset_kick(g_iomgr_args.pollset, nullptr));
+  gpr_mu_unlock(g_iomgr_args.mu);
   grpc_core::Delete(cb_arg);
   g_all_callbacks_invoked = true;
 }
 
+void on_third_resolution(void* arg, grpc_error* error) {
+  OnResolutionCallbackArg* cb_arg = static_cast<OnResolutionCallbackArg*>(arg);
+  grpc_channel_args_destroy(cb_arg->result);
+  GPR_ASSERT(error == GRPC_ERROR_NONE);
+  ++g_on_resolution_invocations_count;
+  gpr_log(GPR_INFO,
+          "3rd: g_on_resolution_invocations_count: %d, g_resolution_count: %d",
+          g_on_resolution_invocations_count, g_resolution_count);
+  // The timer set because of the previous re-resolution request fires, so a new
+  // system-level resolution happened.
+  GPR_ASSERT(g_on_resolution_invocations_count == 3);
+  GPR_ASSERT(g_resolution_count == 2);
+  grpc_core::ExecCtx::Get()->TestOnlySetNow(
+      cb_arg->delay_before_second_resolution * 2);
+  cb_arg->resolver->NextLocked(
+      &cb_arg->result,
+      GRPC_CLOSURE_CREATE(on_fourth_resolution, arg,
+                          grpc_combiner_scheduler(g_combiner)));
+  cb_arg->resolver->RequestReresolutionLocked();
+  gpr_mu_lock(g_iomgr_args.mu);
+  GRPC_LOG_IF_ERROR("pollset_kick",
+                    grpc_pollset_kick(g_iomgr_args.pollset, nullptr));
+  gpr_mu_unlock(g_iomgr_args.mu);
+}
+
 void on_second_resolution(void* arg, grpc_error* error) {
   OnResolutionCallbackArg* cb_arg = static_cast<OnResolutionCallbackArg*>(arg);
-  ++g_on_resolution_invocations_count;
   grpc_channel_args_destroy(cb_arg->result);
-
+  GPR_ASSERT(error == GRPC_ERROR_NONE);
+  ++g_on_resolution_invocations_count;
   gpr_log(GPR_INFO,
           "2nd: g_on_resolution_invocations_count: %d, g_resolution_count: %d",
           g_on_resolution_invocations_count, g_resolution_count);
   // The resolution request for which this function is the callback happened
   // before the min resolution period. Therefore, no new system-level
-  // resolutions happened, as indicated by g_resolution_count.
+  // resolutions happened, as indicated by g_resolution_count. But a resolution
+  // timer was set to fire when the cooldown finishes.
   GPR_ASSERT(g_on_resolution_invocations_count == 2);
   GPR_ASSERT(g_resolution_count == 1);
-  grpc_core::ExecCtx::Get()->TestOnlySetNow(
-      cb_arg->delay_before_second_resolution * 2);
+  // Register a new callback to capture the timer firing.
   cb_arg->resolver->NextLocked(
       &cb_arg->result,
       GRPC_CLOSURE_CREATE(on_third_resolution, arg,
                           grpc_combiner_scheduler(g_combiner)));
-  cb_arg->resolver->RequestReresolutionLocked();
-  if (cb_arg->using_cares) {
-    gpr_mu_lock(g_iomgr_args.mu);
-    GRPC_LOG_IF_ERROR("pollset_kick",
-                      grpc_pollset_kick(g_iomgr_args.pollset, nullptr));
-    gpr_mu_unlock(g_iomgr_args.mu);
-  }
+  gpr_mu_lock(g_iomgr_args.mu);
+  GRPC_LOG_IF_ERROR("pollset_kick",
+                    grpc_pollset_kick(g_iomgr_args.pollset, nullptr));
+  gpr_mu_unlock(g_iomgr_args.mu);
 }
 
 void on_first_resolution(void* arg, grpc_error* error) {
   OnResolutionCallbackArg* cb_arg = static_cast<OnResolutionCallbackArg*>(arg);
-  ++g_on_resolution_invocations_count;
   grpc_channel_args_destroy(cb_arg->result);
+  GPR_ASSERT(error == GRPC_ERROR_NONE);
+  ++g_on_resolution_invocations_count;
+  gpr_log(GPR_INFO,
+          "1st: g_on_resolution_invocations_count: %d, g_resolution_count: %d",
+          g_on_resolution_invocations_count, g_resolution_count);
+  // There's one initial system-level resolution and one invocation of a
+  // notification callback (the current function).
+  GPR_ASSERT(g_on_resolution_invocations_count == 1);
+  GPR_ASSERT(g_resolution_count == 1);
   cb_arg->resolver->NextLocked(
       &cb_arg->result,
       GRPC_CLOSURE_CREATE(on_second_resolution, arg,
                           grpc_combiner_scheduler(g_combiner)));
   cb_arg->resolver->RequestReresolutionLocked();
-  gpr_log(GPR_INFO,
-          "1st: g_on_resolution_invocations_count: %d, g_resolution_count: %d",
-          g_on_resolution_invocations_count, g_resolution_count);
-  // Theres one initial system-level resolution and one invocation of a
-  // notification callback (the current function).
-  GPR_ASSERT(g_on_resolution_invocations_count == 1);
-  GPR_ASSERT(g_resolution_count == 1);
-  if (cb_arg->using_cares) {
-    gpr_mu_lock(g_iomgr_args.mu);
-    GRPC_LOG_IF_ERROR("pollset_kick",
-                      grpc_pollset_kick(g_iomgr_args.pollset, nullptr));
-    gpr_mu_unlock(g_iomgr_args.mu);
-  }
+  gpr_mu_lock(g_iomgr_args.mu);
+  GRPC_LOG_IF_ERROR("pollset_kick",
+                    grpc_pollset_kick(g_iomgr_args.pollset, nullptr));
+  gpr_mu_unlock(g_iomgr_args.mu);
 }
 
 static void start_test_under_combiner(void* arg, grpc_error* error) {
@@ -269,22 +287,19 @@
   grpc_uri_destroy(uri);
 }
 
-static void test_cooldown(bool using_cares) {
+static void test_cooldown() {
   grpc_core::ExecCtx exec_ctx;
-  if (using_cares) iomgr_args_init(&g_iomgr_args);
+  iomgr_args_init(&g_iomgr_args);
   OnResolutionCallbackArg* res_cb_arg =
       grpc_core::New<OnResolutionCallbackArg>();
   res_cb_arg->uri_str = "dns:127.0.0.1";
-  res_cb_arg->using_cares = using_cares;
 
   GRPC_CLOSURE_SCHED(GRPC_CLOSURE_CREATE(start_test_under_combiner, res_cb_arg,
                                          grpc_combiner_scheduler(g_combiner)),
                      GRPC_ERROR_NONE);
-  if (using_cares) {
-    grpc_core::ExecCtx::Get()->Flush();
-    poll_pollset_until_request_done(&g_iomgr_args);
-    iomgr_args_finish(&g_iomgr_args);
-  }
+  grpc_core::ExecCtx::Get()->Flush();
+  poll_pollset_until_request_done(&g_iomgr_args);
+  iomgr_args_finish(&g_iomgr_args);
 }
 
 int main(int argc, char** argv) {
@@ -293,16 +308,12 @@
 
   g_combiner = grpc_combiner_create();
 
-  bool using_cares = false;
-#if GRPC_ARES == 1
-  using_cares = true;
-#endif
   g_default_dns_lookup_ares = grpc_dns_lookup_ares;
   grpc_dns_lookup_ares = test_dns_lookup_ares;
   default_resolve_address = grpc_resolve_address_impl;
   grpc_set_resolver_impl(&test_resolver);
 
-  test_cooldown(using_cares);
+  test_cooldown();
 
   {
     grpc_core::ExecCtx exec_ctx;
diff --git a/test/core/end2end/fuzzers/api_fuzzer.cc b/test/core/end2end/fuzzers/api_fuzzer.cc
index 9ace7d0..36f257d 100644
--- a/test/core/end2end/fuzzers/api_fuzzer.cc
+++ b/test/core/end2end/fuzzers/api_fuzzer.cc
@@ -38,8 +38,14 @@
 #include "src/core/lib/surface/server.h"
 #include "src/core/lib/transport/metadata.h"
 #include "test/core/end2end/data/ssl_test_data.h"
+#include "test/core/util/fuzzer_util.h"
 #include "test/core/util/passthru_endpoint.h"
 
+using grpc_core::testing::grpc_fuzzer_get_next_byte;
+using grpc_core::testing::grpc_fuzzer_get_next_string;
+using grpc_core::testing::grpc_fuzzer_get_next_uint32;
+using grpc_core::testing::input_stream;
+
 ////////////////////////////////////////////////////////////////////////////////
 // logging
 
@@ -65,58 +71,20 @@
   return ts;
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// input_stream: allows easy access to input bytes, and allows reading a little
-//               past the end (avoiding needing to check everywhere)
-
-typedef struct {
-  const uint8_t* cur;
-  const uint8_t* end;
-} input_stream;
-
-static uint8_t next_byte(input_stream* inp) {
-  if (inp->cur == inp->end) {
-    return 0;
-  }
-  return *inp->cur++;
-}
-
 static void end(input_stream* inp) { inp->cur = inp->end; }
 
-static char* read_string(input_stream* inp, bool* special) {
-  char* str = nullptr;
-  size_t cap = 0;
-  size_t sz = 0;
-  char c;
-  do {
-    if (cap == sz) {
-      cap = GPR_MAX(3 * cap / 2, cap + 8);
-      str = static_cast<char*>(gpr_realloc(str, cap));
-    }
-    c = static_cast<char>(next_byte(inp));
-    str[sz++] = c;
-  } while (c != 0 && c != 1);
-  if (special != nullptr) {
-    *special = (c == 1);
-  }
-  if (c == 1) {
-    str[sz - 1] = 0;
-  }
-  return str;
-}
-
 static void read_buffer(input_stream* inp, char** buffer, size_t* length,
                         bool* special) {
-  *length = next_byte(inp);
+  *length = grpc_fuzzer_get_next_byte(inp);
   if (*length == 255) {
     if (special != nullptr) *special = true;
-    *length = next_byte(inp);
+    *length = grpc_fuzzer_get_next_byte(inp);
   } else {
     if (special != nullptr) *special = false;
   }
   *buffer = static_cast<char*>(gpr_malloc(*length));
   for (size_t i = 0; i < *length; i++) {
-    (*buffer)[i] = static_cast<char>(next_byte(inp));
+    (*buffer)[i] = static_cast<char>(grpc_fuzzer_get_next_byte(inp));
   }
 }
 
@@ -128,7 +96,7 @@
 
 static grpc_slice read_string_like_slice(input_stream* inp) {
   bool special;
-  char* s = read_string(inp, &special);
+  char* s = grpc_fuzzer_get_next_string(inp, &special);
   grpc_slice r = maybe_intern(grpc_slice_from_copied_string(s), special);
   gpr_free(s);
   return r;
@@ -146,39 +114,15 @@
 }
 
 static uint32_t read_uint22(input_stream* inp) {
-  uint8_t b = next_byte(inp);
+  uint8_t b = grpc_fuzzer_get_next_byte(inp);
   uint32_t x = b & 0x7f;
   if (b & 0x80) {
     x <<= 7;
-    b = next_byte(inp);
+    b = grpc_fuzzer_get_next_byte(inp);
     x |= b & 0x7f;
     if (b & 0x80) {
       x <<= 8;
-      x |= next_byte(inp);
-    }
-  }
-  return x;
-}
-
-static uint32_t read_uint32(input_stream* inp) {
-  uint8_t b = next_byte(inp);
-  uint32_t x = b & 0x7f;
-  if (b & 0x80) {
-    x <<= 7;
-    b = next_byte(inp);
-    x |= b & 0x7f;
-    if (b & 0x80) {
-      x <<= 7;
-      b = next_byte(inp);
-      x |= b & 0x7f;
-      if (b & 0x80) {
-        x <<= 7;
-        b = next_byte(inp);
-        x |= b & 0x7f;
-        if (b & 0x80) {
-          x = (x << 4) | (next_byte(inp) & 0x0f);
-        }
-      }
+      x |= grpc_fuzzer_get_next_byte(inp);
     }
   }
   return x;
@@ -193,22 +137,22 @@
 }
 
 static int read_int(input_stream* inp) {
-  return static_cast<int>(read_uint32(inp));
+  return static_cast<int>(grpc_fuzzer_get_next_uint32(inp));
 }
 
 static grpc_channel_args* read_args(input_stream* inp) {
-  size_t n = next_byte(inp);
+  size_t n = grpc_fuzzer_get_next_byte(inp);
   grpc_arg* args = static_cast<grpc_arg*>(gpr_malloc(sizeof(*args) * n));
   for (size_t i = 0; i < n; i++) {
-    switch (next_byte(inp)) {
+    switch (grpc_fuzzer_get_next_byte(inp)) {
       case 1:
         args[i].type = GRPC_ARG_STRING;
-        args[i].key = read_string(inp, nullptr);
-        args[i].value.string = read_string(inp, nullptr);
+        args[i].key = grpc_fuzzer_get_next_string(inp, nullptr);
+        args[i].value.string = grpc_fuzzer_get_next_string(inp, nullptr);
         break;
       case 2:
         args[i].type = GRPC_ARG_INTEGER;
-        args[i].key = read_string(inp, nullptr);
+        args[i].key = grpc_fuzzer_get_next_string(inp, nullptr);
         args[i].value.integer = read_int(inp);
         break;
       case 3:
@@ -249,10 +193,11 @@
 static const char* read_cred_artifact(cred_artifact_ctx* ctx, input_stream* inp,
                                       const char** builtins,
                                       size_t num_builtins) {
-  uint8_t b = next_byte(inp);
+  uint8_t b = grpc_fuzzer_get_next_byte(inp);
   if (b == 0) return nullptr;
   if (b == 1)
-    return ctx->release[ctx->num_release++] = read_string(inp, nullptr);
+    return ctx->release[ctx->num_release++] =
+               grpc_fuzzer_get_next_string(inp, nullptr);
   if (b >= num_builtins + 1) {
     end(inp);
     return nullptr;
@@ -288,7 +233,7 @@
     end(inp);
     return nullptr;
   }
-  switch (next_byte(inp)) {
+  switch (grpc_fuzzer_get_next_byte(inp)) {
     default:
       end(inp);
       return nullptr;
@@ -339,7 +284,7 @@
 }
 
 static grpc_channel_credentials* read_channel_creds(input_stream* inp) {
-  switch (next_byte(inp)) {
+  switch (grpc_fuzzer_get_next_byte(inp)) {
     case 0:
       return read_ssl_channel_creds(inp);
       break;
@@ -673,7 +618,7 @@
 
 static void read_metadata(input_stream* inp, size_t* count,
                           grpc_metadata** metadata, call_state* cs) {
-  *count = next_byte(inp);
+  *count = grpc_fuzzer_get_next_byte(inp);
   if (*count) {
     *metadata =
         static_cast<grpc_metadata*>(gpr_malloc(*count * sizeof(**metadata)));
@@ -681,7 +626,7 @@
     for (size_t i = 0; i < *count; i++) {
       (*metadata)[i].key = read_string_like_slice(inp);
       (*metadata)[i].value = read_buffer_like_slice(inp);
-      (*metadata)[i].flags = read_uint32(inp);
+      (*metadata)[i].flags = grpc_fuzzer_get_next_uint32(inp);
       add_slice_to_unref(cs, (*metadata)[i].key);
       add_slice_to_unref(cs, (*metadata)[i].value);
     }
@@ -811,7 +756,7 @@
 
     grpc_timer_manager_tick();
 
-    switch (next_byte(&inp)) {
+    switch (grpc_fuzzer_get_next_byte(&inp)) {
       // terminate on bad bytes
       default:
         end(&inp);
@@ -838,13 +783,14 @@
       // increment global time
       case 1: {
         g_now = gpr_time_add(
-            g_now, gpr_time_from_micros(read_uint32(&inp), GPR_TIMESPAN));
+            g_now, gpr_time_from_micros(grpc_fuzzer_get_next_uint32(&inp),
+                                        GPR_TIMESPAN));
         break;
       }
       // create an insecure channel
       case 2: {
         if (g_channel == nullptr) {
-          char* target = read_string(&inp, nullptr);
+          char* target = grpc_fuzzer_get_next_string(&inp, nullptr);
           char* target_uri;
           gpr_asprintf(&target_uri, "dns:%s", target);
           grpc_channel_args* args = read_args(&inp);
@@ -927,7 +873,7 @@
       // check connectivity
       case 8: {
         if (g_channel != nullptr) {
-          uint8_t try_to_connect = next_byte(&inp);
+          uint8_t try_to_connect = grpc_fuzzer_get_next_byte(&inp);
           if (try_to_connect == 0 || try_to_connect == 1) {
             grpc_channel_check_connectivity_state(g_channel, try_to_connect);
           } else {
@@ -946,7 +892,8 @@
           if (st != GRPC_CHANNEL_SHUTDOWN) {
             gpr_timespec deadline = gpr_time_add(
                 gpr_now(GPR_CLOCK_REALTIME),
-                gpr_time_from_micros(read_uint32(&inp), GPR_TIMESPAN));
+                gpr_time_from_micros(grpc_fuzzer_get_next_uint32(&inp),
+                                     GPR_TIMESPAN));
             grpc_channel_watch_connectivity_state(
                 g_channel, st, deadline, cq,
                 create_validator(validate_connectivity_watch,
@@ -971,7 +918,7 @@
           }
           parent_call = g_active_call->call;
         }
-        uint32_t propagation_mask = read_uint32(&inp);
+        uint32_t propagation_mask = grpc_fuzzer_get_next_uint32(&inp);
         grpc_slice method = read_string_like_slice(&inp);
         if (GRPC_SLICE_LENGTH(method) == 0) {
           ok = false;
@@ -979,7 +926,8 @@
         grpc_slice host = read_string_like_slice(&inp);
         gpr_timespec deadline =
             gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),
-                         gpr_time_from_micros(read_uint32(&inp), GPR_TIMESPAN));
+                         gpr_time_from_micros(grpc_fuzzer_get_next_uint32(&inp),
+                                              GPR_TIMESPAN));
 
         if (ok) {
           call_state* cs = new_call(g_active_call, CLIENT);
@@ -1005,7 +953,7 @@
           end(&inp);
           break;
         }
-        size_t num_ops = next_byte(&inp);
+        size_t num_ops = grpc_fuzzer_get_next_byte(&inp);
         if (num_ops > 6) {
           end(&inp);
           break;
@@ -1019,7 +967,7 @@
         uint8_t has_ops = 0;
         for (i = 0; i < num_ops; i++) {
           op = &ops[i];
-          switch (next_byte(&inp)) {
+          switch (grpc_fuzzer_get_next_byte(&inp)) {
             default:
               /* invalid value */
               op->op = (grpc_op_type)-1;
@@ -1060,7 +1008,8 @@
                   &op->data.send_status_from_server.trailing_metadata,
                   g_active_call);
               op->data.send_status_from_server.status =
-                  static_cast<grpc_status_code>(next_byte(&inp));
+                  static_cast<grpc_status_code>(
+                      grpc_fuzzer_get_next_byte(&inp));
               op->data.send_status_from_server.status_details =
                   add_slice_to_unref(g_active_call,
                                      read_buffer_like_slice(&inp));
@@ -1097,7 +1046,7 @@
               break;
           }
           op->reserved = nullptr;
-          op->flags = read_uint32(&inp);
+          op->flags = grpc_fuzzer_get_next_uint32(&inp);
         }
         if (ok) {
           validator* v = make_finished_batch_validator(g_active_call, has_ops);
@@ -1160,14 +1109,14 @@
       }
       // enable a tracer
       case 17: {
-        char* tracer = read_string(&inp, nullptr);
+        char* tracer = grpc_fuzzer_get_next_string(&inp, nullptr);
         grpc_tracer_set_enabled(tracer, 1);
         gpr_free(tracer);
         break;
       }
       // disable a tracer
       case 18: {
-        char* tracer = read_string(&inp, nullptr);
+        char* tracer = grpc_fuzzer_get_next_string(&inp, nullptr);
         grpc_tracer_set_enabled(tracer, 0);
         gpr_free(tracer);
         break;
@@ -1209,7 +1158,7 @@
       // create a secure channel
       case 22: {
         if (g_channel == nullptr) {
-          char* target = read_string(&inp, nullptr);
+          char* target = grpc_fuzzer_get_next_string(&inp, nullptr);
           char* target_uri;
           gpr_asprintf(&target_uri, "dns:%s", target);
           grpc_channel_args* args = read_args(&inp);
diff --git a/test/core/end2end/tests/bad_ping.cc b/test/core/end2end/tests/bad_ping.cc
index 22481d1..98d893f 100644
--- a/test/core/end2end/tests/bad_ping.cc
+++ b/test/core/end2end/tests/bad_ping.cc
@@ -355,6 +355,11 @@
 
   grpc_server_shutdown_and_notify(f.server, f.cq, tag(0xdead));
   CQ_EXPECT_COMPLETION(cqv, tag(0xdead), 1);
+
+  // Also expect the previously blocked pings to complete with an error
+  CQ_EXPECT_COMPLETION(cqv, tag(200 + MAX_PING_STRIKES + 1), 0);
+  CQ_EXPECT_COMPLETION(cqv, tag(200 + MAX_PING_STRIKES + 2), 0);
+
   cq_verify(cqv);
 
   grpc_call_unref(s);
diff --git a/test/core/security/BUILD b/test/core/security/BUILD
index 9db73b9..70bcc8c 100644
--- a/test/core/security/BUILD
+++ b/test/core/security/BUILD
@@ -21,6 +21,18 @@
 load("//test/core/util:grpc_fuzzer.bzl", "grpc_fuzzer")
 
 grpc_fuzzer(
+    name = "alts_credentials_fuzzer",
+    srcs = ["alts_credentials_fuzzer.cc"],
+    language = "C++",
+    corpus = "corpus/alts_credentials_corpus",
+    deps = [
+        "//:gpr",
+        "//:grpc",
+        "//test/core/util:grpc_test_util",
+    ],
+)
+
+grpc_fuzzer(
     name = "ssl_server_fuzzer",
     srcs = ["ssl_server_fuzzer.cc"],
     language = "C++",
diff --git a/test/core/security/alts_credentials_fuzzer.cc b/test/core/security/alts_credentials_fuzzer.cc
new file mode 100644
index 0000000..bf18f0a
--- /dev/null
+++ b/test/core/security/alts_credentials_fuzzer.cc
@@ -0,0 +1,120 @@
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <string.h>
+
+#include <grpc/grpc.h>
+#include <grpc/grpc_security.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
+#include "test/core/util/fuzzer_util.h"
+#include "test/core/util/memory_counters.h"
+
+#include "src/core/lib/gpr/env.h"
+#include "src/core/lib/security/credentials/alts/alts_credentials.h"
+#include "src/core/lib/security/credentials/alts/check_gcp_environment.h"
+#include "src/core/lib/security/credentials/alts/grpc_alts_credentials_options.h"
+
+using grpc_core::testing::grpc_fuzzer_get_next_byte;
+using grpc_core::testing::grpc_fuzzer_get_next_string;
+using grpc_core::testing::input_stream;
+
+// Logging
+bool squelch = true;
+bool leak_check = true;
+
+static void dont_log(gpr_log_func_args* args) {}
+
+// Add a random number of target service accounts to client options.
+static void read_target_service_accounts(
+    input_stream* inp, grpc_alts_credentials_options* options) {
+  size_t n = grpc_fuzzer_get_next_byte(inp);
+  for (size_t i = 0; i < n; i++) {
+    char* service_account = grpc_fuzzer_get_next_string(inp, nullptr);
+    if (service_account != nullptr) {
+      grpc_alts_credentials_client_options_add_target_service_account(
+          options, service_account);
+      gpr_free(service_account);
+    }
+  }
+  // Added to improve code coverage.
+  grpc_alts_credentials_client_options_add_target_service_account(options,
+                                                                  nullptr);
+  grpc_alts_credentials_client_options_add_target_service_account(
+      nullptr, "this is service account");
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  char* grpc_trace_fuzzer = gpr_getenv("GRPC_TRACE_FUZZER");
+  if (squelch && grpc_trace_fuzzer == nullptr) {
+    gpr_set_log_function(dont_log);
+  }
+  gpr_free(grpc_trace_fuzzer);
+  struct grpc_memory_counters counters;
+  if (leak_check) {
+    grpc_memory_counters_init();
+  }
+  input_stream inp = {data, data + size};
+  grpc_init();
+  bool is_on_gcp = grpc_alts_is_running_on_gcp();
+  while (inp.cur != inp.end) {
+    bool enable_untrusted_alts = grpc_fuzzer_get_next_byte(&inp) & 0x01;
+    char* handshaker_service_url =
+        grpc_fuzzer_get_next_byte(&inp) & 0x01
+            ? grpc_fuzzer_get_next_string(&inp, nullptr)
+            : nullptr;
+    if (grpc_fuzzer_get_next_byte(&inp) & 0x01) {
+      // Test ALTS channel credentials.
+      grpc_alts_credentials_options* options =
+          grpc_alts_credentials_client_options_create();
+      read_target_service_accounts(&inp, options);
+      grpc_channel_credentials* cred = grpc_alts_credentials_create_customized(
+          options, handshaker_service_url, enable_untrusted_alts);
+      if (!enable_untrusted_alts && !is_on_gcp) {
+        GPR_ASSERT(cred == nullptr);
+      } else {
+        GPR_ASSERT(cred != nullptr);
+      }
+      grpc_channel_credentials_release(cred);
+      grpc_alts_credentials_options_destroy(options);
+    } else {
+      // Test ALTS server credentials.
+      grpc_alts_credentials_options* options =
+          grpc_alts_credentials_server_options_create();
+      grpc_server_credentials* cred =
+          grpc_alts_server_credentials_create_customized(
+              options, handshaker_service_url, enable_untrusted_alts);
+      if (!enable_untrusted_alts && !is_on_gcp) {
+        GPR_ASSERT(cred == nullptr);
+      } else {
+        GPR_ASSERT(cred != nullptr);
+      }
+      grpc_server_credentials_release(cred);
+      grpc_alts_credentials_options_destroy(options);
+    }
+    gpr_free(handshaker_service_url);
+  }
+  grpc_shutdown();
+  if (leak_check) {
+    counters = grpc_memory_counters_snapshot();
+    grpc_memory_counters_destroy();
+    GPR_ASSERT(counters.total_size_relative == 0);
+  }
+  return 0;
+}
diff --git a/test/core/security/corpus/alts_credentials_corpus/0149b46b88d583e05be0fb1423d10f2a14d36c48 b/test/core/security/corpus/alts_credentials_corpus/0149b46b88d583e05be0fb1423d10f2a14d36c48
new file mode 100644
index 0000000..c062c7f
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/0149b46b88d583e05be0fb1423d10f2a14d36c48
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/047fc351e73f760d329d5a8845944720be9ce773 b/test/core/security/corpus/alts_credentials_corpus/047fc351e73f760d329d5a8845944720be9ce773
new file mode 100644
index 0000000..d943cfe
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/047fc351e73f760d329d5a8845944720be9ce773
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/04ef96c66d8222d1a2c07e6b2a6548e6a527042b b/test/core/security/corpus/alts_credentials_corpus/04ef96c66d8222d1a2c07e6b2a6548e6a527042b
new file mode 100644
index 0000000..e7346f3
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/04ef96c66d8222d1a2c07e6b2a6548e6a527042b
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/05a7e16c1d7f92111f43e9c777421879920e79a4 b/test/core/security/corpus/alts_credentials_corpus/05a7e16c1d7f92111f43e9c777421879920e79a4
new file mode 100644
index 0000000..43b8b47
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/05a7e16c1d7f92111f43e9c777421879920e79a4
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/063eb46f202fdfe7935c30ca38d7eb81c82db419 b/test/core/security/corpus/alts_credentials_corpus/063eb46f202fdfe7935c30ca38d7eb81c82db419
new file mode 100644
index 0000000..5b30f12
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/063eb46f202fdfe7935c30ca38d7eb81c82db419
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/064773597c295fa871c184fc12d17b6de8aab31b b/test/core/security/corpus/alts_credentials_corpus/064773597c295fa871c184fc12d17b6de8aab31b
new file mode 100644
index 0000000..758709b
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/064773597c295fa871c184fc12d17b6de8aab31b
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/087449740758b114d16790067707934479946bd6 b/test/core/security/corpus/alts_credentials_corpus/087449740758b114d16790067707934479946bd6
new file mode 100644
index 0000000..099636f
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/087449740758b114d16790067707934479946bd6
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/0a5d068feb57a2782c6eba57b637abe8668ac82f b/test/core/security/corpus/alts_credentials_corpus/0a5d068feb57a2782c6eba57b637abe8668ac82f
new file mode 100644
index 0000000..fd7d7c8
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/0a5d068feb57a2782c6eba57b637abe8668ac82f
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/0b81e6d89bf7df80e87e5ee7c49f7cc1431f77e8 b/test/core/security/corpus/alts_credentials_corpus/0b81e6d89bf7df80e87e5ee7c49f7cc1431f77e8
new file mode 100644
index 0000000..a40664b
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/0b81e6d89bf7df80e87e5ee7c49f7cc1431f77e8
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/11409339cec708a5e353893101bfe76364337d5c b/test/core/security/corpus/alts_credentials_corpus/11409339cec708a5e353893101bfe76364337d5c
new file mode 100644
index 0000000..c8b350d
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/11409339cec708a5e353893101bfe76364337d5c
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/147696a264cd6f197adb7c68aff834c30b1b77f8 b/test/core/security/corpus/alts_credentials_corpus/147696a264cd6f197adb7c68aff834c30b1b77f8
new file mode 100644
index 0000000..5f68d8a
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/147696a264cd6f197adb7c68aff834c30b1b77f8
@@ -0,0 +1 @@
+ø+ú
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/160e5cac38c5c9e919ed6e4fbafee76907d63044 b/test/core/security/corpus/alts_credentials_corpus/160e5cac38c5c9e919ed6e4fbafee76907d63044
new file mode 100644
index 0000000..c11ad66
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/160e5cac38c5c9e919ed6e4fbafee76907d63044
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/173d02167db431040b0540d98f6fc5e8b456587d b/test/core/security/corpus/alts_credentials_corpus/173d02167db431040b0540d98f6fc5e8b456587d
new file mode 100644
index 0000000..662cc9b
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/173d02167db431040b0540d98f6fc5e8b456587d
@@ -0,0 +1 @@
+òÒÒ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/18a3fe239806b3c7d1af24bcd2bd23aeeb072d5c b/test/core/security/corpus/alts_credentials_corpus/18a3fe239806b3c7d1af24bcd2bd23aeeb072d5c
new file mode 100644
index 0000000..9239e10
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/18a3fe239806b3c7d1af24bcd2bd23aeeb072d5c
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/195abd83b2e9d32b1b5b854fe33da44b6db40880 b/test/core/security/corpus/alts_credentials_corpus/195abd83b2e9d32b1b5b854fe33da44b6db40880
new file mode 100644
index 0000000..db7815e
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/195abd83b2e9d32b1b5b854fe33da44b6db40880
@@ -0,0 +1 @@
+apÿì~!õìA~;ì
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/19af2509c7d84334b9ec64de4767a07d5294fd72 b/test/core/security/corpus/alts_credentials_corpus/19af2509c7d84334b9ec64de4767a07d5294fd72
new file mode 100644
index 0000000..b87c814
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/19af2509c7d84334b9ec64de4767a07d5294fd72
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/1b9864b948fcf08b062fd4401ef55b214c259535 b/test/core/security/corpus/alts_credentials_corpus/1b9864b948fcf08b062fd4401ef55b214c259535
new file mode 100644
index 0000000..8cb97c5
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/1b9864b948fcf08b062fd4401ef55b214c259535
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/1edddfa67de854d7faaba41418fda845e9c6a89d b/test/core/security/corpus/alts_credentials_corpus/1edddfa67de854d7faaba41418fda845e9c6a89d
new file mode 100644
index 0000000..035f07c
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/1edddfa67de854d7faaba41418fda845e9c6a89d
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/20031bb00e6608e1b570aa96e6afb9de06d42167 b/test/core/security/corpus/alts_credentials_corpus/20031bb00e6608e1b570aa96e6afb9de06d42167
new file mode 100644
index 0000000..27753df
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/20031bb00e6608e1b570aa96e6afb9de06d42167
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/22b4c7ce7db99b0df63c9eae9265de484b695922 b/test/core/security/corpus/alts_credentials_corpus/22b4c7ce7db99b0df63c9eae9265de484b695922
new file mode 100644
index 0000000..ab8f18d
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/22b4c7ce7db99b0df63c9eae9265de484b695922
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/27416437ad287bd3cc1c5efdecebc39f20df73c1 b/test/core/security/corpus/alts_credentials_corpus/27416437ad287bd3cc1c5efdecebc39f20df73c1
new file mode 100644
index 0000000..c400e3e
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/27416437ad287bd3cc1c5efdecebc39f20df73c1
@@ -0,0 +1 @@
+Ä=ļyyyyy‡†yyyyz
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/27e8cd785c2b9346f68dba75761b52fbabaf2b72 b/test/core/security/corpus/alts_credentials_corpus/27e8cd785c2b9346f68dba75761b52fbabaf2b72
new file mode 100644
index 0000000..746dee9
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/27e8cd785c2b9346f68dba75761b52fbabaf2b72
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/28236f860d3d8e5ea11176746cb4c1c5c4f1f6c0 b/test/core/security/corpus/alts_credentials_corpus/28236f860d3d8e5ea11176746cb4c1c5c4f1f6c0
new file mode 100644
index 0000000..982c97b
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/28236f860d3d8e5ea11176746cb4c1c5c4f1f6c0
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/29e15b492c5a409938092a30c003c5c34df7e283 b/test/core/security/corpus/alts_credentials_corpus/29e15b492c5a409938092a30c003c5c34df7e283
new file mode 100644
index 0000000..c3e5b25
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/29e15b492c5a409938092a30c003c5c34df7e283
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/2a47864d77749aa042b772895dbdf46f608ccc6d b/test/core/security/corpus/alts_credentials_corpus/2a47864d77749aa042b772895dbdf46f608ccc6d
new file mode 100644
index 0000000..141d94d
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/2a47864d77749aa042b772895dbdf46f608ccc6d
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/2cca5cb1b135c35f6e5e1ec4c37deb9e12d37dc0 b/test/core/security/corpus/alts_credentials_corpus/2cca5cb1b135c35f6e5e1ec4c37deb9e12d37dc0
new file mode 100644
index 0000000..a956708
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/2cca5cb1b135c35f6e5e1ec4c37deb9e12d37dc0
@@ -0,0 +1,2 @@

+ïï
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/2df27b6c42dbaee382a29a87338d64ee87354acb b/test/core/security/corpus/alts_credentials_corpus/2df27b6c42dbaee382a29a87338d64ee87354acb
new file mode 100644
index 0000000..a35f0e8
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/2df27b6c42dbaee382a29a87338d64ee87354acb
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/2e9ddd1339d8e599cef658a08965985c4f45e428 b/test/core/security/corpus/alts_credentials_corpus/2e9ddd1339d8e599cef658a08965985c4f45e428
new file mode 100644
index 0000000..17c98e7
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/2e9ddd1339d8e599cef658a08965985c4f45e428
@@ -0,0 +1,2 @@

+ïe
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/31a2d12a84a7a56ace831a9668d6ab4847390679 b/test/core/security/corpus/alts_credentials_corpus/31a2d12a84a7a56ace831a9668d6ab4847390679
new file mode 100644
index 0000000..745dc42
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/31a2d12a84a7a56ace831a9668d6ab4847390679
@@ -0,0 +1,2 @@

+ïé
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/33cb9ec0ce3538ed6079b5fcb127649a5d05955b b/test/core/security/corpus/alts_credentials_corpus/33cb9ec0ce3538ed6079b5fcb127649a5d05955b
new file mode 100644
index 0000000..2931764
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/33cb9ec0ce3538ed6079b5fcb127649a5d05955b
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/348d9ae6eebb2e1644addf7f07231d108cf6f3b8 b/test/core/security/corpus/alts_credentials_corpus/348d9ae6eebb2e1644addf7f07231d108cf6f3b8
new file mode 100644
index 0000000..02326f0
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/348d9ae6eebb2e1644addf7f07231d108cf6f3b8
@@ -0,0 +1 @@
+òÒä
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/359f76f3c802292e92b0640de2bfe051e780a3b6 b/test/core/security/corpus/alts_credentials_corpus/359f76f3c802292e92b0640de2bfe051e780a3b6
new file mode 100644
index 0000000..f31c54b
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/359f76f3c802292e92b0640de2bfe051e780a3b6
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/35a479988e965a6e3e75138b64b0bd1f45073e2f b/test/core/security/corpus/alts_credentials_corpus/35a479988e965a6e3e75138b64b0bd1f45073e2f
new file mode 100644
index 0000000..4e44bcc
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/35a479988e965a6e3e75138b64b0bd1f45073e2f
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/362b00d713686ff70cb0199f3d7d0058e5a1a27a b/test/core/security/corpus/alts_credentials_corpus/362b00d713686ff70cb0199f3d7d0058e5a1a27a
new file mode 100644
index 0000000..3e22fca
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/362b00d713686ff70cb0199f3d7d0058e5a1a27a
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/3849c1625071791ceae709b9c6c705b28d099d67 b/test/core/security/corpus/alts_credentials_corpus/3849c1625071791ceae709b9c6c705b28d099d67
new file mode 100644
index 0000000..5bc905d
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/3849c1625071791ceae709b9c6c705b28d099d67
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/39ef03c66ee2d4bcfb6c8da50486dcd40f02fb12 b/test/core/security/corpus/alts_credentials_corpus/39ef03c66ee2d4bcfb6c8da50486dcd40f02fb12
new file mode 100644
index 0000000..467004c
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/39ef03c66ee2d4bcfb6c8da50486dcd40f02fb12
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/3a3ca061863499ebc171a4f910fa1b49523baad4 b/test/core/security/corpus/alts_credentials_corpus/3a3ca061863499ebc171a4f910fa1b49523baad4
new file mode 100644
index 0000000..97f09bf
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/3a3ca061863499ebc171a4f910fa1b49523baad4
@@ -0,0 +1 @@
+úÒû9
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/3a890f3fd01b048ac9db65a9a9b4f4443268b91a b/test/core/security/corpus/alts_credentials_corpus/3a890f3fd01b048ac9db65a9a9b4f4443268b91a
new file mode 100644
index 0000000..97b05e8
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/3a890f3fd01b048ac9db65a9a9b4f4443268b91a
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/3b9554038a425bd1fae057ba41f9366bb467e946 b/test/core/security/corpus/alts_credentials_corpus/3b9554038a425bd1fae057ba41f9366bb467e946
new file mode 100644
index 0000000..d0ef634
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/3b9554038a425bd1fae057ba41f9366bb467e946
@@ -0,0 +1 @@
+„ÜðððððððððððððððððððððððððððÔððððòððððððÔÜÜÜ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/3ce0ae4aa226f205a3a4e66bbb253419d9d754bf b/test/core/security/corpus/alts_credentials_corpus/3ce0ae4aa226f205a3a4e66bbb253419d9d754bf
new file mode 100644
index 0000000..08e6df0
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/3ce0ae4aa226f205a3a4e66bbb253419d9d754bf
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/3dccc5523986c37e27684659bba8a1037e7a92e8 b/test/core/security/corpus/alts_credentials_corpus/3dccc5523986c37e27684659bba8a1037e7a92e8
new file mode 100644
index 0000000..09b735e
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/3dccc5523986c37e27684659bba8a1037e7a92e8
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/3e0908c15b1cede4541d25f388b1345e8641e221 b/test/core/security/corpus/alts_credentials_corpus/3e0908c15b1cede4541d25f388b1345e8641e221
new file mode 100644
index 0000000..eabd458
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/3e0908c15b1cede4541d25f388b1345e8641e221
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/3fcb181ff6a8c8e2ba38ed34cf78f7482eb55cb7 b/test/core/security/corpus/alts_credentials_corpus/3fcb181ff6a8c8e2ba38ed34cf78f7482eb55cb7
new file mode 100644
index 0000000..bbef93b
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/3fcb181ff6a8c8e2ba38ed34cf78f7482eb55cb7
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/41c9b5f720eb8f8fa04c840375a881781a849b43 b/test/core/security/corpus/alts_credentials_corpus/41c9b5f720eb8f8fa04c840375a881781a849b43
new file mode 100644
index 0000000..d611c73
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/41c9b5f720eb8f8fa04c840375a881781a849b43
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/4257a018f08f13a3a9adc848ef808e1be50bc4cf b/test/core/security/corpus/alts_credentials_corpus/4257a018f08f13a3a9adc848ef808e1be50bc4cf
new file mode 100644
index 0000000..607c971
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/4257a018f08f13a3a9adc848ef808e1be50bc4cf
@@ -0,0 +1,2 @@
+òÓ99999999999999999999999999999999999¬¬¬¬¬¬¬¬¬ÿÿÿÿÿÿÿÿÿÿÿÿÿ/////ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
+ÿÿÿÿÿÿÿÿÿÿ²²²²²²²ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿNSt6locale5
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/42dfc5c4d13261b7259e65cd692df9c9d607194e b/test/core/security/corpus/alts_credentials_corpus/42dfc5c4d13261b7259e65cd692df9c9d607194e
new file mode 100644
index 0000000..48ffa85
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/42dfc5c4d13261b7259e65cd692df9c9d607194e
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/43144664aedb585d45d42aa5249ddbfe81afe470 b/test/core/security/corpus/alts_credentials_corpus/43144664aedb585d45d42aa5249ddbfe81afe470
new file mode 100644
index 0000000..fee2672
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/43144664aedb585d45d42aa5249ddbfe81afe470
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/43e5ad495a47593b17dbcbd3e70c2e25a417bb6e b/test/core/security/corpus/alts_credentials_corpus/43e5ad495a47593b17dbcbd3e70c2e25a417bb6e
new file mode 100644
index 0000000..32ffc96
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/43e5ad495a47593b17dbcbd3e70c2e25a417bb6e
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/446614e45b7bef49118b17e031c48faf167ebe3e b/test/core/security/corpus/alts_credentials_corpus/446614e45b7bef49118b17e031c48faf167ebe3e
new file mode 100644
index 0000000..849aadf
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/446614e45b7bef49118b17e031c48faf167ebe3e
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/46492477fa84ca88e85df914801af0b09b0939f6 b/test/core/security/corpus/alts_credentials_corpus/46492477fa84ca88e85df914801af0b09b0939f6
new file mode 100644
index 0000000..98885bc
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/46492477fa84ca88e85df914801af0b09b0939f6
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/47157f83b166b57e0052c98a65c6db864fa6cb9b b/test/core/security/corpus/alts_credentials_corpus/47157f83b166b57e0052c98a65c6db864fa6cb9b
new file mode 100644
index 0000000..fad1e0f
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/47157f83b166b57e0052c98a65c6db864fa6cb9b
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/473fc9b6d768a925527d3ad805ca363d490dc741 b/test/core/security/corpus/alts_credentials_corpus/473fc9b6d768a925527d3ad805ca363d490dc741
new file mode 100644
index 0000000..1775a17
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/473fc9b6d768a925527d3ad805ca363d490dc741
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/483c0b4015100eee00f6b23d1100d8c4953dd3b1 b/test/core/security/corpus/alts_credentials_corpus/483c0b4015100eee00f6b23d1100d8c4953dd3b1
new file mode 100644
index 0000000..2d52687
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/483c0b4015100eee00f6b23d1100d8c4953dd3b1
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/48be2dc4cdc5462407b319caa855d976cda88153 b/test/core/security/corpus/alts_credentials_corpus/48be2dc4cdc5462407b319caa855d976cda88153
new file mode 100644
index 0000000..c1c2cd9
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/48be2dc4cdc5462407b319caa855d976cda88153
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/4e84eb54a0e438052b0c2e83653135042d9eb59a b/test/core/security/corpus/alts_credentials_corpus/4e84eb54a0e438052b0c2e83653135042d9eb59a
new file mode 100644
index 0000000..66b525f
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/4e84eb54a0e438052b0c2e83653135042d9eb59a
@@ -0,0 +1 @@
+òÒ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/50839d5c8bf33f0970986dcc4b73b024f11a95b7 b/test/core/security/corpus/alts_credentials_corpus/50839d5c8bf33f0970986dcc4b73b024f11a95b7
new file mode 100644
index 0000000..9673f73
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/50839d5c8bf33f0970986dcc4b73b024f11a95b7
@@ -0,0 +1 @@
+òÿt_Ó	ÿÿÿÿÿæææ&ææææææææææææææææææææææÿÿÿÿÿÿÿÿÿÿûÿÿÿÿÿÿÿÜ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/523d964986d8ad966ae07e540a608681098813f9 b/test/core/security/corpus/alts_credentials_corpus/523d964986d8ad966ae07e540a608681098813f9
new file mode 100644
index 0000000..06e05df
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/523d964986d8ad966ae07e540a608681098813f9
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/5410b8190c95dacd36d6e6ec75b7538a630e08de b/test/core/security/corpus/alts_credentials_corpus/5410b8190c95dacd36d6e6ec75b7538a630e08de
new file mode 100644
index 0000000..4579c6f
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/5410b8190c95dacd36d6e6ec75b7538a630e08de
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/549b2891ac79f504a7c9ea00f6d7527c34ce04e6 b/test/core/security/corpus/alts_credentials_corpus/549b2891ac79f504a7c9ea00f6d7527c34ce04e6
new file mode 100644
index 0000000..6f1a9ed
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/549b2891ac79f504a7c9ea00f6d7527c34ce04e6
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/55321649e7b7f1b5664ae20724e683c930643fc4 b/test/core/security/corpus/alts_credentials_corpus/55321649e7b7f1b5664ae20724e683c930643fc4
new file mode 100644
index 0000000..e38fd28
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/55321649e7b7f1b5664ae20724e683c930643fc4
@@ -0,0 +1 @@
+ò)Ò
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/55cc52f25865baee3e6e52c3110a9723caa2b3cb b/test/core/security/corpus/alts_credentials_corpus/55cc52f25865baee3e6e52c3110a9723caa2b3cb
new file mode 100644
index 0000000..aae2fe5
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/55cc52f25865baee3e6e52c3110a9723caa2b3cb
@@ -0,0 +1 @@
+òÒÎ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/56c22410e3295ad03aa31552ab888f581756cc17 b/test/core/security/corpus/alts_credentials_corpus/56c22410e3295ad03aa31552ab888f581756cc17
new file mode 100644
index 0000000..e5d7cf9
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/56c22410e3295ad03aa31552ab888f581756cc17
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/5724a705b62a7548ba2df1abe4ef0c970c4e1bd2 b/test/core/security/corpus/alts_credentials_corpus/5724a705b62a7548ba2df1abe4ef0c970c4e1bd2
new file mode 100644
index 0000000..b0875c9
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/5724a705b62a7548ba2df1abe4ef0c970c4e1bd2
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/576a148c107d56861d1611641a6f7c7921061c5c b/test/core/security/corpus/alts_credentials_corpus/576a148c107d56861d1611641a6f7c7921061c5c
new file mode 100644
index 0000000..6d91b58
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/576a148c107d56861d1611641a6f7c7921061c5c
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/5a6b8263e8939f851cf5b1e347a33d97253b7b3d b/test/core/security/corpus/alts_credentials_corpus/5a6b8263e8939f851cf5b1e347a33d97253b7b3d
new file mode 100644
index 0000000..add56ca
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/5a6b8263e8939f851cf5b1e347a33d97253b7b3d
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/5ba93c9db0cff93f52b521d7420e43f6eda2784f b/test/core/security/corpus/alts_credentials_corpus/5ba93c9db0cff93f52b521d7420e43f6eda2784f
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/5ba93c9db0cff93f52b521d7420e43f6eda2784f
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/5bd02a339fd7705449388580c75bfcc597aba954 b/test/core/security/corpus/alts_credentials_corpus/5bd02a339fd7705449388580c75bfcc597aba954
new file mode 100644
index 0000000..f450932
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/5bd02a339fd7705449388580c75bfcc597aba954
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/5bd6fb6fc4163bf3a9db6ddaf509dce8df8a5000 b/test/core/security/corpus/alts_credentials_corpus/5bd6fb6fc4163bf3a9db6ddaf509dce8df8a5000
new file mode 100644
index 0000000..aea3da6
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/5bd6fb6fc4163bf3a9db6ddaf509dce8df8a5000
@@ -0,0 +1 @@
+)applea.ÿÿÿÿ„ÜÜ.ÿßÿÿÿÿÿÜÿÿÿÜÿÿ„ÜÜ.
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/5d06fc38005503af3d084721c60e574fb9d2f370 b/test/core/security/corpus/alts_credentials_corpus/5d06fc38005503af3d084721c60e574fb9d2f370
new file mode 100644
index 0000000..42eb673
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/5d06fc38005503af3d084721c60e574fb9d2f370
@@ -0,0 +1 @@
+ÓÓ×
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/5ddc10489ff3269bdaa3051b70fb7af455ee1104 b/test/core/security/corpus/alts_credentials_corpus/5ddc10489ff3269bdaa3051b70fb7af455ee1104
new file mode 100644
index 0000000..79db88a
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/5ddc10489ff3269bdaa3051b70fb7af455ee1104
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/5ea9d515f0d10b04f1356b9463139bfe121a6e4a b/test/core/security/corpus/alts_credentials_corpus/5ea9d515f0d10b04f1356b9463139bfe121a6e4a
new file mode 100644
index 0000000..7efb47e
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/5ea9d515f0d10b04f1356b9463139bfe121a6e4a
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/61c449793347cf2e1ed0c38d54d23c63dfaabeb8 b/test/core/security/corpus/alts_credentials_corpus/61c449793347cf2e1ed0c38d54d23c63dfaabeb8
new file mode 100644
index 0000000..96031be
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/61c449793347cf2e1ed0c38d54d23c63dfaabeb8
@@ -0,0 +1 @@
+òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÝÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/6287389c373e9788dcc04f9747b4be1fd1ef3028 b/test/core/security/corpus/alts_credentials_corpus/6287389c373e9788dcc04f9747b4be1fd1ef3028
new file mode 100644
index 0000000..799bd0e
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/6287389c373e9788dcc04f9747b4be1fd1ef3028
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/64d4de4d5aafab7ec388a7fe83066c1a4d1d9d68 b/test/core/security/corpus/alts_credentials_corpus/64d4de4d5aafab7ec388a7fe83066c1a4d1d9d68
new file mode 100644
index 0000000..2e91965
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/64d4de4d5aafab7ec388a7fe83066c1a4d1d9d68
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/651c37806d2ac579dcfc97643c3c1ea74dbb8774 b/test/core/security/corpus/alts_credentials_corpus/651c37806d2ac579dcfc97643c3c1ea74dbb8774
new file mode 100644
index 0000000..618e514
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/651c37806d2ac579dcfc97643c3c1ea74dbb8774
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/6551d02d20573cfa2944ec1f12b0c01f264a1326 b/test/core/security/corpus/alts_credentials_corpus/6551d02d20573cfa2944ec1f12b0c01f264a1326
new file mode 100644
index 0000000..98056c0
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/6551d02d20573cfa2944ec1f12b0c01f264a1326
@@ -0,0 +1 @@
+‚s
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/65f029414ee10e45ff4b9f305f7b472364cea538 b/test/core/security/corpus/alts_credentials_corpus/65f029414ee10e45ff4b9f305f7b472364cea538
new file mode 100644
index 0000000..1c3a1a0
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/65f029414ee10e45ff4b9f305f7b472364cea538
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/68b75a17fe2db060df3e61a597650ba99079abbf b/test/core/security/corpus/alts_credentials_corpus/68b75a17fe2db060df3e61a597650ba99079abbf
new file mode 100644
index 0000000..327dd39
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/68b75a17fe2db060df3e61a597650ba99079abbf
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/69e80594dbc5c4c648e39883a650b1760f20ab63 b/test/core/security/corpus/alts_credentials_corpus/69e80594dbc5c4c648e39883a650b1760f20ab63
new file mode 100644
index 0000000..740b987
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/69e80594dbc5c4c648e39883a650b1760f20ab63
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/6cb47d0e640b4c41e32f13c0d64ee46eae1b80b5 b/test/core/security/corpus/alts_credentials_corpus/6cb47d0e640b4c41e32f13c0d64ee46eae1b80b5
new file mode 100644
index 0000000..b22a010
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/6cb47d0e640b4c41e32f13c0d64ee46eae1b80b5
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/6da5fe063432cb9094c7c083efdbbe5ba4246d18 b/test/core/security/corpus/alts_credentials_corpus/6da5fe063432cb9094c7c083efdbbe5ba4246d18
new file mode 100644
index 0000000..993620e
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/6da5fe063432cb9094c7c083efdbbe5ba4246d18
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/6dd140da774d85f272fb587dc1b2a85d881a7c21 b/test/core/security/corpus/alts_credentials_corpus/6dd140da774d85f272fb587dc1b2a85d881a7c21
new file mode 100644
index 0000000..d838739
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/6dd140da774d85f272fb587dc1b2a85d881a7c21
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/6ddab273597d73be49e2307d68e00fa18bba4765 b/test/core/security/corpus/alts_credentials_corpus/6ddab273597d73be49e2307d68e00fa18bba4765
new file mode 100644
index 0000000..3e74467
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/6ddab273597d73be49e2307d68e00fa18bba4765
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/6eaf85d84fbf47ea0619d0dba8d366f4e3ff0be6 b/test/core/security/corpus/alts_credentials_corpus/6eaf85d84fbf47ea0619d0dba8d366f4e3ff0be6
new file mode 100644
index 0000000..42cddf7
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/6eaf85d84fbf47ea0619d0dba8d366f4e3ff0be6
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/6f751cc09af8113f6ecd491b1830bd8454c4738d b/test/core/security/corpus/alts_credentials_corpus/6f751cc09af8113f6ecd491b1830bd8454c4738d
new file mode 100644
index 0000000..a51a3d0
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/6f751cc09af8113f6ecd491b1830bd8454c4738d
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/70d9eb29a70d483d07e2faca6b00098af78d1fff b/test/core/security/corpus/alts_credentials_corpus/70d9eb29a70d483d07e2faca6b00098af78d1fff
new file mode 100644
index 0000000..e9fb9b9
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/70d9eb29a70d483d07e2faca6b00098af78d1fff
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/7192effa1058382b379fb7b87f1acad5ac554d05 b/test/core/security/corpus/alts_credentials_corpus/7192effa1058382b379fb7b87f1acad5ac554d05
new file mode 100644
index 0000000..23a3819
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/7192effa1058382b379fb7b87f1acad5ac554d05
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/730e85d6a62e70cb6721009b903782ade4ff73a2 b/test/core/security/corpus/alts_credentials_corpus/730e85d6a62e70cb6721009b903782ade4ff73a2
new file mode 100644
index 0000000..9c1e71c
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/730e85d6a62e70cb6721009b903782ade4ff73a2
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/74002471a854059cb29de7cad8f9fb7adc3c5ec2 b/test/core/security/corpus/alts_credentials_corpus/74002471a854059cb29de7cad8f9fb7adc3c5ec2
new file mode 100644
index 0000000..bb83231
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/74002471a854059cb29de7cad8f9fb7adc3c5ec2
@@ -0,0 +1,2 @@
+
+¡Š
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/747f2330cd1fc4a06d54b376a9a6528d0364f0ac b/test/core/security/corpus/alts_credentials_corpus/747f2330cd1fc4a06d54b376a9a6528d0364f0ac
new file mode 100644
index 0000000..332ac4b
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/747f2330cd1fc4a06d54b376a9a6528d0364f0ac
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/749d5d7a9e0b1545b297117e834462af32b3e230 b/test/core/security/corpus/alts_credentials_corpus/749d5d7a9e0b1545b297117e834462af32b3e230
new file mode 100644
index 0000000..a00db59
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/749d5d7a9e0b1545b297117e834462af32b3e230
@@ -0,0 +1 @@
+aèÿÿ+
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/77de0b1de120ac702ca45868b1008a48626daf12 b/test/core/security/corpus/alts_credentials_corpus/77de0b1de120ac702ca45868b1008a48626daf12
new file mode 100644
index 0000000..352762c
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/77de0b1de120ac702ca45868b1008a48626daf12
@@ -0,0 +1 @@
+òÒ^
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/792c67398bce19a4eeda32653c994436e79456e5 b/test/core/security/corpus/alts_credentials_corpus/792c67398bce19a4eeda32653c994436e79456e5
new file mode 100644
index 0000000..0834287
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/792c67398bce19a4eeda32653c994436e79456e5
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/7a3022b248c8960289e4c80c7cc8df409499e5da b/test/core/security/corpus/alts_credentials_corpus/7a3022b248c8960289e4c80c7cc8df409499e5da
new file mode 100644
index 0000000..f9ef6a4
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/7a3022b248c8960289e4c80c7cc8df409499e5da
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/7a9372081294a6fbd3fecdd91b99589c98d4948e b/test/core/security/corpus/alts_credentials_corpus/7a9372081294a6fbd3fecdd91b99589c98d4948e
new file mode 100644
index 0000000..7100f32
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/7a9372081294a6fbd3fecdd91b99589c98d4948e
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/7bbe4ba828947550f4ad089d5989cb695ecbdb1b b/test/core/security/corpus/alts_credentials_corpus/7bbe4ba828947550f4ad089d5989cb695ecbdb1b
new file mode 100644
index 0000000..44e3963
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/7bbe4ba828947550f4ad089d5989cb695ecbdb1b
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/7f1ad514a96f0c3d5ca5d6f7880b929a65eeae96 b/test/core/security/corpus/alts_credentials_corpus/7f1ad514a96f0c3d5ca5d6f7880b929a65eeae96
new file mode 100644
index 0000000..700c079
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/7f1ad514a96f0c3d5ca5d6f7880b929a65eeae96
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/7f2b075f0b6707c38db851747e2578343eeab286 b/test/core/security/corpus/alts_credentials_corpus/7f2b075f0b6707c38db851747e2578343eeab286
new file mode 100644
index 0000000..1f7b5d7
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/7f2b075f0b6707c38db851747e2578343eeab286
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/81ebc64bfde3fad37af5a58ef7f1c5c3c54c4b5d b/test/core/security/corpus/alts_credentials_corpus/81ebc64bfde3fad37af5a58ef7f1c5c3c54c4b5d
new file mode 100644
index 0000000..d89f30f
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/81ebc64bfde3fad37af5a58ef7f1c5c3c54c4b5d
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/82fae081afaea13831404024d39658344d56e1c6 b/test/core/security/corpus/alts_credentials_corpus/82fae081afaea13831404024d39658344d56e1c6
new file mode 100644
index 0000000..bc3cfc2
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/82fae081afaea13831404024d39658344d56e1c6
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/83ba41cea1adab707f7f213af5e2ed734bdddc25 b/test/core/security/corpus/alts_credentials_corpus/83ba41cea1adab707f7f213af5e2ed734bdddc25
new file mode 100644
index 0000000..d465fb4
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/83ba41cea1adab707f7f213af5e2ed734bdddc25
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/841a3f66c94e5acd836a44cd5a8514d4ad45d83e b/test/core/security/corpus/alts_credentials_corpus/841a3f66c94e5acd836a44cd5a8514d4ad45d83e
new file mode 100644
index 0000000..cbb7bdc
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/841a3f66c94e5acd836a44cd5a8514d4ad45d83e
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/841ef94ee0f1b0b45983d95b75aba25421d73f2c b/test/core/security/corpus/alts_credentials_corpus/841ef94ee0f1b0b45983d95b75aba25421d73f2c
new file mode 100644
index 0000000..ba85d1d
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/841ef94ee0f1b0b45983d95b75aba25421d73f2c
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/843b0aad4a9707c5dcc92d12d876b78675cfcb65 b/test/core/security/corpus/alts_credentials_corpus/843b0aad4a9707c5dcc92d12d876b78675cfcb65
new file mode 100644
index 0000000..02d61c5
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/843b0aad4a9707c5dcc92d12d876b78675cfcb65
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/8483e3d92eda8df504b1d1d0d012f4bcd778cd33 b/test/core/security/corpus/alts_credentials_corpus/8483e3d92eda8df504b1d1d0d012f4bcd778cd33
new file mode 100644
index 0000000..3795466
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/8483e3d92eda8df504b1d1d0d012f4bcd778cd33
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/876830fdff4e59038fa2173b700faef5bffe61de b/test/core/security/corpus/alts_credentials_corpus/876830fdff4e59038fa2173b700faef5bffe61de
new file mode 100644
index 0000000..26048bd
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/876830fdff4e59038fa2173b700faef5bffe61de
@@ -0,0 +1 @@
+òÒA
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/87ca3342fdce0c1f678a3f1b62428032ef51442d b/test/core/security/corpus/alts_credentials_corpus/87ca3342fdce0c1f678a3f1b62428032ef51442d
new file mode 100644
index 0000000..ea35b45
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/87ca3342fdce0c1f678a3f1b62428032ef51442d
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/87d044027cdb7d35fadb56532f497764246946a6 b/test/core/security/corpus/alts_credentials_corpus/87d044027cdb7d35fadb56532f497764246946a6
new file mode 100644
index 0000000..1e474c9
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/87d044027cdb7d35fadb56532f497764246946a6
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/88ce75ba18bdb7e93a81197d850f4e792f6a8155 b/test/core/security/corpus/alts_credentials_corpus/88ce75ba18bdb7e93a81197d850f4e792f6a8155
new file mode 100644
index 0000000..70c9ab1
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/88ce75ba18bdb7e93a81197d850f4e792f6a8155
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/89dc55e8e20e811e78c952c8bd2c16f55fe72f57 b/test/core/security/corpus/alts_credentials_corpus/89dc55e8e20e811e78c952c8bd2c16f55fe72f57
new file mode 100644
index 0000000..00961fd
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/89dc55e8e20e811e78c952c8bd2c16f55fe72f57
@@ -0,0 +1 @@
+òÒŽ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/8a215a58908f44bdced595ceb01a81977f1d72f0 b/test/core/security/corpus/alts_credentials_corpus/8a215a58908f44bdced595ceb01a81977f1d72f0
new file mode 100644
index 0000000..296f8b7
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/8a215a58908f44bdced595ceb01a81977f1d72f0
@@ -0,0 +1 @@
+Ä=ÒÄ=)†††††††ÄÄÄÄÄÄ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/8ac7459e918304ca40b1cf29a3ac0f555eada678 b/test/core/security/corpus/alts_credentials_corpus/8ac7459e918304ca40b1cf29a3ac0f555eada678
new file mode 100644
index 0000000..629a6f9
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/8ac7459e918304ca40b1cf29a3ac0f555eada678
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/8b93e50a911f3ea0e0b0377ba4636574f2ee9a5e b/test/core/security/corpus/alts_credentials_corpus/8b93e50a911f3ea0e0b0377ba4636574f2ee9a5e
new file mode 100644
index 0000000..280aecf
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/8b93e50a911f3ea0e0b0377ba4636574f2ee9a5e
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/8c9ec0ffd803505772693833d56e7a02110645b3 b/test/core/security/corpus/alts_credentials_corpus/8c9ec0ffd803505772693833d56e7a02110645b3
new file mode 100644
index 0000000..3bd046b
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/8c9ec0ffd803505772693833d56e7a02110645b3
@@ -0,0 +1 @@
+K]//(
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/8e4b361a530dc6825afcfb4106bd482c3fd010fa b/test/core/security/corpus/alts_credentials_corpus/8e4b361a530dc6825afcfb4106bd482c3fd010fa
new file mode 100644
index 0000000..3733df9
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/8e4b361a530dc6825afcfb4106bd482c3fd010fa
@@ -0,0 +1 @@
+òÒÝ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/8f6690d97bcda890f2a5b2930a2b7a4d7b56c6e7 b/test/core/security/corpus/alts_credentials_corpus/8f6690d97bcda890f2a5b2930a2b7a4d7b56c6e7
new file mode 100644
index 0000000..7885e1e
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/8f6690d97bcda890f2a5b2930a2b7a4d7b56c6e7
@@ -0,0 +1 @@
+òÒÝ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/917636de2c14dce2580d4308249a94d61d62c305 b/test/core/security/corpus/alts_credentials_corpus/917636de2c14dce2580d4308249a94d61d62c305
new file mode 100644
index 0000000..cccb20d
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/917636de2c14dce2580d4308249a94d61d62c305
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/91f11008defda918951bda868cc68c6373fb0e6a b/test/core/security/corpus/alts_credentials_corpus/91f11008defda918951bda868cc68c6373fb0e6a
new file mode 100644
index 0000000..bf10853
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/91f11008defda918951bda868cc68c6373fb0e6a
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/92e01a34047b660a798086d55a3d8d7100a01939 b/test/core/security/corpus/alts_credentials_corpus/92e01a34047b660a798086d55a3d8d7100a01939
new file mode 100644
index 0000000..e86bc1d
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/92e01a34047b660a798086d55a3d8d7100a01939
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/963fafadb4de09dee0e6a852bd61b1740039a465 b/test/core/security/corpus/alts_credentials_corpus/963fafadb4de09dee0e6a852bd61b1740039a465
new file mode 100644
index 0000000..8933b18
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/963fafadb4de09dee0e6a852bd61b1740039a465
@@ -0,0 +1 @@
+ê
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/97bf33ec97b93fcc2449431915911a55b906e3b6 b/test/core/security/corpus/alts_credentials_corpus/97bf33ec97b93fcc2449431915911a55b906e3b6
new file mode 100644
index 0000000..8101e37
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/97bf33ec97b93fcc2449431915911a55b906e3b6
@@ -0,0 +1 @@
+òÓÓÒ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/99e31e12b02b02479d10b2c08426906bd93a0840 b/test/core/security/corpus/alts_credentials_corpus/99e31e12b02b02479d10b2c08426906bd93a0840
new file mode 100644
index 0000000..9fe2a6a
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/99e31e12b02b02479d10b2c08426906bd93a0840
@@ -0,0 +1 @@
+òÓÓÒ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/9a75ce693e7259d4d3bb9203dfc0a65f8bbaa466 b/test/core/security/corpus/alts_credentials_corpus/9a75ce693e7259d4d3bb9203dfc0a65f8bbaa466
new file mode 100644
index 0000000..5366cdd
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/9a75ce693e7259d4d3bb9203dfc0a65f8bbaa466
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/9ac0d956f9743e026baad7319ba2a75d9f1a534f b/test/core/security/corpus/alts_credentials_corpus/9ac0d956f9743e026baad7319ba2a75d9f1a534f
new file mode 100644
index 0000000..adb0583
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/9ac0d956f9743e026baad7319ba2a75d9f1a534f
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/9ae56d4451dd3e1b66ddc250d84dbf6d8cae0dbd b/test/core/security/corpus/alts_credentials_corpus/9ae56d4451dd3e1b66ddc250d84dbf6d8cae0dbd
new file mode 100644
index 0000000..121d0cb
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/9ae56d4451dd3e1b66ddc250d84dbf6d8cae0dbd
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/9b9a3a1e4023c9b172060249752a482a3437ef2a b/test/core/security/corpus/alts_credentials_corpus/9b9a3a1e4023c9b172060249752a482a3437ef2a
new file mode 100644
index 0000000..0b19f19
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/9b9a3a1e4023c9b172060249752a482a3437ef2a
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/9c81164e10bf612c352dca3ecabf57743b451d42 b/test/core/security/corpus/alts_credentials_corpus/9c81164e10bf612c352dca3ecabf57743b451d42
new file mode 100644
index 0000000..1cae116
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/9c81164e10bf612c352dca3ecabf57743b451d42
@@ -0,0 +1,2 @@
+
+z
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/9d8b420b5d32deb0140ab91eeebba58ca6163722 b/test/core/security/corpus/alts_credentials_corpus/9d8b420b5d32deb0140ab91eeebba58ca6163722
new file mode 100644
index 0000000..6d0f809
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/9d8b420b5d32deb0140ab91eeebba58ca6163722
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/9de687bf1e2cfac54c3b2e2eb85b53014a460ff7 b/test/core/security/corpus/alts_credentials_corpus/9de687bf1e2cfac54c3b2e2eb85b53014a460ff7
new file mode 100644
index 0000000..789f59c
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/9de687bf1e2cfac54c3b2e2eb85b53014a460ff7
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/9f3cda19a186bd11bfac361b464f92daa129a33b b/test/core/security/corpus/alts_credentials_corpus/9f3cda19a186bd11bfac361b464f92daa129a33b
new file mode 100644
index 0000000..61347c3
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/9f3cda19a186bd11bfac361b464f92daa129a33b
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/a14fc6a608121f8abf0fe25cf466720f00f25653 b/test/core/security/corpus/alts_credentials_corpus/a14fc6a608121f8abf0fe25cf466720f00f25653
new file mode 100644
index 0000000..58af762
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/a14fc6a608121f8abf0fe25cf466720f00f25653
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/a39906074669a6b76a35b0adf2bf36ad751f3b35 b/test/core/security/corpus/alts_credentials_corpus/a39906074669a6b76a35b0adf2bf36ad751f3b35
new file mode 100644
index 0000000..e10cdb9
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/a39906074669a6b76a35b0adf2bf36ad751f3b35
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/a454ca483b4a66b83826d061be2859dd79ff0d6c b/test/core/security/corpus/alts_credentials_corpus/a454ca483b4a66b83826d061be2859dd79ff0d6c
new file mode 100644
index 0000000..38424fc
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/a454ca483b4a66b83826d061be2859dd79ff0d6c
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/a52df5607370ff0f56d821000f3d5e386a01d489 b/test/core/security/corpus/alts_credentials_corpus/a52df5607370ff0f56d821000f3d5e386a01d489
new file mode 100644
index 0000000..f16da07
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/a52df5607370ff0f56d821000f3d5e386a01d489
@@ -0,0 +1 @@
+úÒû;
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/a56eaf47f7c7263e53efdc55ec39063dbb4ae71c b/test/core/security/corpus/alts_credentials_corpus/a56eaf47f7c7263e53efdc55ec39063dbb4ae71c
new file mode 100644
index 0000000..3664ce0
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/a56eaf47f7c7263e53efdc55ec39063dbb4ae71c
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/a79249fb8f7d53f0a280359d2d9df31594adbdfc b/test/core/security/corpus/alts_credentials_corpus/a79249fb8f7d53f0a280359d2d9df31594adbdfc
new file mode 100644
index 0000000..d7f1f45
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/a79249fb8f7d53f0a280359d2d9df31594adbdfc
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/aa98a46f25004f7436aadb36ff8b7f07ed7bfce1 b/test/core/security/corpus/alts_credentials_corpus/aa98a46f25004f7436aadb36ff8b7f07ed7bfce1
new file mode 100644
index 0000000..f1db8d9
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/aa98a46f25004f7436aadb36ff8b7f07ed7bfce1
@@ -0,0 +1 @@
+ap“ž’/òÒ+
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/adc83b19e793491b1c6ea0fd8b46cd9f32e592fc b/test/core/security/corpus/alts_credentials_corpus/adc83b19e793491b1c6ea0fd8b46cd9f32e592fc
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/adc83b19e793491b1c6ea0fd8b46cd9f32e592fc
@@ -0,0 +1 @@
+
diff --git a/test/core/security/corpus/alts_credentials_corpus/afd8e19f7bfd6c963f1856be59b75627864821dc b/test/core/security/corpus/alts_credentials_corpus/afd8e19f7bfd6c963f1856be59b75627864821dc
new file mode 100644
index 0000000..385405e
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/afd8e19f7bfd6c963f1856be59b75627864821dc
@@ -0,0 +1,2 @@
+¯+ú
+䍴õ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/b3966239b8568442baecbeb0f8a1aa29dcdfd7ed b/test/core/security/corpus/alts_credentials_corpus/b3966239b8568442baecbeb0f8a1aa29dcdfd7ed
new file mode 100644
index 0000000..3d9fb86
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/b3966239b8568442baecbeb0f8a1aa29dcdfd7ed
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/b430d41ef65493b3e917182c23ce90df983e01ab b/test/core/security/corpus/alts_credentials_corpus/b430d41ef65493b3e917182c23ce90df983e01ab
new file mode 100644
index 0000000..cbb8ceb
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/b430d41ef65493b3e917182c23ce90df983e01ab
@@ -0,0 +1,2 @@
+ÿ
+
diff --git a/test/core/security/corpus/alts_credentials_corpus/b44e715e0cfe05f0c92a9e000ac3c36aae17df9d b/test/core/security/corpus/alts_credentials_corpus/b44e715e0cfe05f0c92a9e000ac3c36aae17df9d
new file mode 100644
index 0000000..f0b1381
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/b44e715e0cfe05f0c92a9e000ac3c36aae17df9d
@@ -0,0 +1 @@
+]]
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/b4cf4ef7b3f64eff76cf99091fddc04411774708 b/test/core/security/corpus/alts_credentials_corpus/b4cf4ef7b3f64eff76cf99091fddc04411774708
new file mode 100644
index 0000000..65d4adb
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/b4cf4ef7b3f64eff76cf99091fddc04411774708
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/b53d84468ea93620a9824ca65acf1179f431e763 b/test/core/security/corpus/alts_credentials_corpus/b53d84468ea93620a9824ca65acf1179f431e763
new file mode 100644
index 0000000..5844d31
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/b53d84468ea93620a9824ca65acf1179f431e763
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/b6ac4831cc5baabee9c8ab9af9ca3923f91097a0 b/test/core/security/corpus/alts_credentials_corpus/b6ac4831cc5baabee9c8ab9af9ca3923f91097a0
new file mode 100644
index 0000000..3d54a26
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/b6ac4831cc5baabee9c8ab9af9ca3923f91097a0
@@ -0,0 +1,2 @@
+îÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿAï
+ïé
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/b7f4a484866a8050dbc63bc905c9803c6964eda5 b/test/core/security/corpus/alts_credentials_corpus/b7f4a484866a8050dbc63bc905c9803c6964eda5
new file mode 100644
index 0000000..e17c966
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/b7f4a484866a8050dbc63bc905c9803c6964eda5
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/b8f21e59f90431c982d5ec3fb54ae4605f102252 b/test/core/security/corpus/alts_credentials_corpus/b8f21e59f90431c982d5ec3fb54ae4605f102252
new file mode 100644
index 0000000..79e3486
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/b8f21e59f90431c982d5ec3fb54ae4605f102252
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/bad10b6581cdead8e7cb96e4f544dcf0ea650fbc b/test/core/security/corpus/alts_credentials_corpus/bad10b6581cdead8e7cb96e4f544dcf0ea650fbc
new file mode 100644
index 0000000..9deb760
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/bad10b6581cdead8e7cb96e4f544dcf0ea650fbc
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/bb01bed86b43257be9f527388e1183f52438c473 b/test/core/security/corpus/alts_credentials_corpus/bb01bed86b43257be9f527388e1183f52438c473
new file mode 100644
index 0000000..ad47d46
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/bb01bed86b43257be9f527388e1183f52438c473
@@ -0,0 +1 @@
+òÒûþ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/bb7497b00f0d999ef39dbf81c6bd0441e32723b6 b/test/core/security/corpus/alts_credentials_corpus/bb7497b00f0d999ef39dbf81c6bd0441e32723b6
new file mode 100644
index 0000000..3b2f108
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/bb7497b00f0d999ef39dbf81c6bd0441e32723b6
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/bf01b72e635deda1b4a8468f1cc36f01a54e1338 b/test/core/security/corpus/alts_credentials_corpus/bf01b72e635deda1b4a8468f1cc36f01a54e1338
new file mode 100644
index 0000000..7fa6cf8
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/bf01b72e635deda1b4a8468f1cc36f01a54e1338
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/bf8b4530d8d246dd74ac53a13471bba17941dff7 b/test/core/security/corpus/alts_credentials_corpus/bf8b4530d8d246dd74ac53a13471bba17941dff7
new file mode 100644
index 0000000..6b2aaa7
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/bf8b4530d8d246dd74ac53a13471bba17941dff7
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/c08bc84ab6a512b901bb730beb05c8394e4f1c5d b/test/core/security/corpus/alts_credentials_corpus/c08bc84ab6a512b901bb730beb05c8394e4f1c5d
new file mode 100644
index 0000000..8d75368
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/c08bc84ab6a512b901bb730beb05c8394e4f1c5d
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/c244b635d94e6f5d6b344887434be3e001a04b41 b/test/core/security/corpus/alts_credentials_corpus/c244b635d94e6f5d6b344887434be3e001a04b41
new file mode 100644
index 0000000..92e73a2
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/c244b635d94e6f5d6b344887434be3e001a04b41
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/c281efe9620da999a637ff6e9b3279ec613fb992 b/test/core/security/corpus/alts_credentials_corpus/c281efe9620da999a637ff6e9b3279ec613fb992
new file mode 100644
index 0000000..c17a77e
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/c281efe9620da999a637ff6e9b3279ec613fb992
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/c30a212824ee71e215f475f453de17c65a200101 b/test/core/security/corpus/alts_credentials_corpus/c30a212824ee71e215f475f453de17c65a200101
new file mode 100644
index 0000000..86ac99e
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/c30a212824ee71e215f475f453de17c65a200101
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/c449427f35b7ecdf5641073629f7723df52c4cb0 b/test/core/security/corpus/alts_credentials_corpus/c449427f35b7ecdf5641073629f7723df52c4cb0
new file mode 100644
index 0000000..87d1a32
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/c449427f35b7ecdf5641073629f7723df52c4cb0
@@ -0,0 +1 @@
+ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚ՏÒÒÒÒÒÒÒÒÒÒ‘-
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/c60240cd3b02eb71e2bf5ebd59afa3a5dc9b5e4d b/test/core/security/corpus/alts_credentials_corpus/c60240cd3b02eb71e2bf5ebd59afa3a5dc9b5e4d
new file mode 100644
index 0000000..f8804b8
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/c60240cd3b02eb71e2bf5ebd59afa3a5dc9b5e4d
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/c60cdf9c3fb9060838f445b3bc3852b6f81e1e4c b/test/core/security/corpus/alts_credentials_corpus/c60cdf9c3fb9060838f445b3bc3852b6f81e1e4c
new file mode 100644
index 0000000..327246f
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/c60cdf9c3fb9060838f445b3bc3852b6f81e1e4c
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/c72d0501bacadb45242c553ba292591302f12a6a b/test/core/security/corpus/alts_credentials_corpus/c72d0501bacadb45242c553ba292591302f12a6a
new file mode 100644
index 0000000..ca02baf
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/c72d0501bacadb45242c553ba292591302f12a6a
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/c739e7b5ad999edbdeffdab672dbc30deb3959a0 b/test/core/security/corpus/alts_credentials_corpus/c739e7b5ad999edbdeffdab672dbc30deb3959a0
new file mode 100644
index 0000000..d7f2d22
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/c739e7b5ad999edbdeffdab672dbc30deb3959a0
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/c7d73b12a7108d82f8dac6d8a6a34f838601aca6 b/test/core/security/corpus/alts_credentials_corpus/c7d73b12a7108d82f8dac6d8a6a34f838601aca6
new file mode 100644
index 0000000..b38437a
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/c7d73b12a7108d82f8dac6d8a6a34f838601aca6
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/ca781e1add632433293e847ae9e71649c217ee5f b/test/core/security/corpus/alts_credentials_corpus/ca781e1add632433293e847ae9e71649c217ee5f
new file mode 100644
index 0000000..5c79dd1
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/ca781e1add632433293e847ae9e71649c217ee5f
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/cc48e916f40e8d69c2d07cfda42c7d3b7fe3447a b/test/core/security/corpus/alts_credentials_corpus/cc48e916f40e8d69c2d07cfda42c7d3b7fe3447a
new file mode 100644
index 0000000..11ccb07
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/cc48e916f40e8d69c2d07cfda42c7d3b7fe3447a
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/cca1aff4c08ee4ccbcb6f80e1cd1480a0a093cfd b/test/core/security/corpus/alts_credentials_corpus/cca1aff4c08ee4ccbcb6f80e1cd1480a0a093cfd
new file mode 100644
index 0000000..b7bebbe
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/cca1aff4c08ee4ccbcb6f80e1cd1480a0a093cfd
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/cf6ae8bf1d08d25e235b7bee0839984bbc04edf6 b/test/core/security/corpus/alts_credentials_corpus/cf6ae8bf1d08d25e235b7bee0839984bbc04edf6
new file mode 100644
index 0000000..32eb656
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/cf6ae8bf1d08d25e235b7bee0839984bbc04edf6
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/cfc52fa086292c699efd7bf41d2fae3deb449536 b/test/core/security/corpus/alts_credentials_corpus/cfc52fa086292c699efd7bf41d2fae3deb449536
new file mode 100644
index 0000000..285e35d
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/cfc52fa086292c699efd7bf41d2fae3deb449536
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/cfe13ef3c6c713a059f231f0001ecec97e2a932d b/test/core/security/corpus/alts_credentials_corpus/cfe13ef3c6c713a059f231f0001ecec97e2a932d
new file mode 100644
index 0000000..1059f60
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/cfe13ef3c6c713a059f231f0001ecec97e2a932d
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/d14026ac6421bca7161024f4e735cb80a1068d01 b/test/core/security/corpus/alts_credentials_corpus/d14026ac6421bca7161024f4e735cb80a1068d01
new file mode 100644
index 0000000..68b7682
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/d14026ac6421bca7161024f4e735cb80a1068d01
@@ -0,0 +1,2 @@
+
+ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÿ+´úä¯õ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/d2fb6e8f7867fc1e2ebe723da2b5246dc9cc6b14 b/test/core/security/corpus/alts_credentials_corpus/d2fb6e8f7867fc1e2ebe723da2b5246dc9cc6b14
new file mode 100644
index 0000000..9854260
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/d2fb6e8f7867fc1e2ebe723da2b5246dc9cc6b14
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/d4db7d51bdaa4781cf12c3b59914bad414d2a41e b/test/core/security/corpus/alts_credentials_corpus/d4db7d51bdaa4781cf12c3b59914bad414d2a41e
new file mode 100644
index 0000000..0979d45
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/d4db7d51bdaa4781cf12c3b59914bad414d2a41e
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/d533da0e7f8c1e39bb025b4d7a89613142a6f54e b/test/core/security/corpus/alts_credentials_corpus/d533da0e7f8c1e39bb025b4d7a89613142a6f54e
new file mode 100644
index 0000000..89ef0d2
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/d533da0e7f8c1e39bb025b4d7a89613142a6f54e
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/d5cf489d01a1b847a7aac5dddabff23fdc218e1e b/test/core/security/corpus/alts_credentials_corpus/d5cf489d01a1b847a7aac5dddabff23fdc218e1e
new file mode 100644
index 0000000..c544b27
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/d5cf489d01a1b847a7aac5dddabff23fdc218e1e
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/d686f8561a249c7c15c78f76a5fceb884286e070 b/test/core/security/corpus/alts_credentials_corpus/d686f8561a249c7c15c78f76a5fceb884286e070
new file mode 100644
index 0000000..b96c459
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/d686f8561a249c7c15c78f76a5fceb884286e070
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/d92424daad9d96a40e5ab177e3824c36ef51dc0f b/test/core/security/corpus/alts_credentials_corpus/d92424daad9d96a40e5ab177e3824c36ef51dc0f
new file mode 100644
index 0000000..509eb39
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/d92424daad9d96a40e5ab177e3824c36ef51dc0f
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/db242a11ed88b2b26af46770dd1927d9f35301fb b/test/core/security/corpus/alts_credentials_corpus/db242a11ed88b2b26af46770dd1927d9f35301fb
new file mode 100644
index 0000000..bd5d046
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/db242a11ed88b2b26af46770dd1927d9f35301fb
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/db32eb04db13d58f65f46d262608bd088987c063 b/test/core/security/corpus/alts_credentials_corpus/db32eb04db13d58f65f46d262608bd088987c063
new file mode 100644
index 0000000..5c526d8
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/db32eb04db13d58f65f46d262608bd088987c063
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/db39a953317951759e40734de6607a0b4457728e b/test/core/security/corpus/alts_credentials_corpus/db39a953317951759e40734de6607a0b4457728e
new file mode 100644
index 0000000..d794576
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/db39a953317951759e40734de6607a0b4457728e
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/dc5e8f3102456bed90d17303bc4cff1a7e076d5d b/test/core/security/corpus/alts_credentials_corpus/dc5e8f3102456bed90d17303bc4cff1a7e076d5d
new file mode 100644
index 0000000..53e1bbc
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/dc5e8f3102456bed90d17303bc4cff1a7e076d5d
@@ -0,0 +1 @@
+‚
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/dd9542bbed8e5dc58da2789edbfb9c38d578d3b4 b/test/core/security/corpus/alts_credentials_corpus/dd9542bbed8e5dc58da2789edbfb9c38d578d3b4
new file mode 100644
index 0000000..95fd950
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/dd9542bbed8e5dc58da2789edbfb9c38d578d3b4
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/de2ebb1ed324385de500a1a3308846239857c3c7 b/test/core/security/corpus/alts_credentials_corpus/de2ebb1ed324385de500a1a3308846239857c3c7
new file mode 100644
index 0000000..d0cee5f
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/de2ebb1ed324385de500a1a3308846239857c3c7
@@ -0,0 +1 @@
+ø+ø
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/de8ba9158254c1cd84b53df1e4cdf1757b1392f1 b/test/core/security/corpus/alts_credentials_corpus/de8ba9158254c1cd84b53df1e4cdf1757b1392f1
new file mode 100644
index 0000000..1cbbe5b
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/de8ba9158254c1cd84b53df1e4cdf1757b1392f1
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/e1dd260746f50024822a8b729b89545d26decfb8 b/test/core/security/corpus/alts_credentials_corpus/e1dd260746f50024822a8b729b89545d26decfb8
new file mode 100644
index 0000000..c44854e
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/e1dd260746f50024822a8b729b89545d26decfb8
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/e29add81b20dc570fdc885782689f6dccb1c5fad b/test/core/security/corpus/alts_credentials_corpus/e29add81b20dc570fdc885782689f6dccb1c5fad
new file mode 100644
index 0000000..5c50fe7
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/e29add81b20dc570fdc885782689f6dccb1c5fad
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/e2e99af62843cd3b29d50daeb118e58830784da9 b/test/core/security/corpus/alts_credentials_corpus/e2e99af62843cd3b29d50daeb118e58830784da9
new file mode 100644
index 0000000..04da131
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/e2e99af62843cd3b29d50daeb118e58830784da9
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/e46611c5daf99662e1576147c1623409752a1f39 b/test/core/security/corpus/alts_credentials_corpus/e46611c5daf99662e1576147c1623409752a1f39
new file mode 100644
index 0000000..0334d31
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/e46611c5daf99662e1576147c1623409752a1f39
@@ -0,0 +1 @@
+ä##
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/e5a1ba11af830e9d2db201c5164f75747a85fe9b b/test/core/security/corpus/alts_credentials_corpus/e5a1ba11af830e9d2db201c5164f75747a85fe9b
new file mode 100644
index 0000000..8e8b1e2
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/e5a1ba11af830e9d2db201c5164f75747a85fe9b
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/e6026ee0badf216b326443a5f708446b2f2e579f b/test/core/security/corpus/alts_credentials_corpus/e6026ee0badf216b326443a5f708446b2f2e579f
new file mode 100644
index 0000000..a99014f
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/e6026ee0badf216b326443a5f708446b2f2e579f
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/e6c7d2c0038fa1f03fc6590a726abc98f4c641f3 b/test/core/security/corpus/alts_credentials_corpus/e6c7d2c0038fa1f03fc6590a726abc98f4c641f3
new file mode 100644
index 0000000..8f5a9f5
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/e6c7d2c0038fa1f03fc6590a726abc98f4c641f3
@@ -0,0 +1 @@
+òi
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/eafdef6a630bed71bd0e4f3d4a16b5fa0c920651 b/test/core/security/corpus/alts_credentials_corpus/eafdef6a630bed71bd0e4f3d4a16b5fa0c920651
new file mode 100644
index 0000000..6fbe750
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/eafdef6a630bed71bd0e4f3d4a16b5fa0c920651
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/ece985b9b82e27281514d460709d7edf8203ded7 b/test/core/security/corpus/alts_credentials_corpus/ece985b9b82e27281514d460709d7edf8203ded7
new file mode 100644
index 0000000..c124302
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/ece985b9b82e27281514d460709d7edf8203ded7
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/edb8f4259f756c2c4bc731f05beaa36f992cf079 b/test/core/security/corpus/alts_credentials_corpus/edb8f4259f756c2c4bc731f05beaa36f992cf079
new file mode 100644
index 0000000..1cc08a4
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/edb8f4259f756c2c4bc731f05beaa36f992cf079
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/edce7778c2e1adb81dda3d057a6536759a7cb293 b/test/core/security/corpus/alts_credentials_corpus/edce7778c2e1adb81dda3d057a6536759a7cb293
new file mode 100644
index 0000000..39b52cf
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/edce7778c2e1adb81dda3d057a6536759a7cb293
@@ -0,0 +1 @@
++
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/ee4040c0dd406dd616c49ed2c37b40478dabfe0f b/test/core/security/corpus/alts_credentials_corpus/ee4040c0dd406dd616c49ed2c37b40478dabfe0f
new file mode 100644
index 0000000..48325e3
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/ee4040c0dd406dd616c49ed2c37b40478dabfe0f
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/ee69f2b380663d051a70f30fcfce9f79f5341e5a b/test/core/security/corpus/alts_credentials_corpus/ee69f2b380663d051a70f30fcfce9f79f5341e5a
new file mode 100644
index 0000000..7e0ad2a
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/ee69f2b380663d051a70f30fcfce9f79f5341e5a
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/efc6743e47274058771bb6eda1fefa017bde4a95 b/test/core/security/corpus/alts_credentials_corpus/efc6743e47274058771bb6eda1fefa017bde4a95
new file mode 100644
index 0000000..4418e8a
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/efc6743e47274058771bb6eda1fefa017bde4a95
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/f0038e54162000694d882b1acb80930c807b41d2 b/test/core/security/corpus/alts_credentials_corpus/f0038e54162000694d882b1acb80930c807b41d2
new file mode 100644
index 0000000..292a5e4
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/f0038e54162000694d882b1acb80930c807b41d2
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/f1deb9e388c877337dabe92f31b01e2a019a10f4 b/test/core/security/corpus/alts_credentials_corpus/f1deb9e388c877337dabe92f31b01e2a019a10f4
new file mode 100644
index 0000000..46464ee
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/f1deb9e388c877337dabe92f31b01e2a019a10f4
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/f3a09373e4d3c7310d372089e6deb15d6b22c198 b/test/core/security/corpus/alts_credentials_corpus/f3a09373e4d3c7310d372089e6deb15d6b22c198
new file mode 100644
index 0000000..a974996
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/f3a09373e4d3c7310d372089e6deb15d6b22c198
@@ -0,0 +1 @@
+üÒûþ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/f3db7ef6495fa1ac5bb4db293fb38dd59122bb7c b/test/core/security/corpus/alts_credentials_corpus/f3db7ef6495fa1ac5bb4db293fb38dd59122bb7c
new file mode 100644
index 0000000..8afc32b
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/f3db7ef6495fa1ac5bb4db293fb38dd59122bb7c
@@ -0,0 +1,2 @@
+[ÿÿ[ÿÿÿ	
+¡Š
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/f434bb4ceecc573e085d4c3ef453ef01e93d9c89 b/test/core/security/corpus/alts_credentials_corpus/f434bb4ceecc573e085d4c3ef453ef01e93d9c89
new file mode 100644
index 0000000..2b66a00
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/f434bb4ceecc573e085d4c3ef453ef01e93d9c89
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/f55bceaad42ddf9d2b37fdfca68255d29a696109 b/test/core/security/corpus/alts_credentials_corpus/f55bceaad42ddf9d2b37fdfca68255d29a696109
new file mode 100644
index 0000000..a8eef8f
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/f55bceaad42ddf9d2b37fdfca68255d29a696109
@@ -0,0 +1 @@
+òÒ
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/f62ca5321428a5d23f3c804fb51eb4e65bc58716 b/test/core/security/corpus/alts_credentials_corpus/f62ca5321428a5d23f3c804fb51eb4e65bc58716
new file mode 100644
index 0000000..0d80f7b
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/f62ca5321428a5d23f3c804fb51eb4e65bc58716
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/f7c6a558b8d0af64db2b139371a7af7068b01b92 b/test/core/security/corpus/alts_credentials_corpus/f7c6a558b8d0af64db2b139371a7af7068b01b92
new file mode 100644
index 0000000..d46ea45
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/f7c6a558b8d0af64db2b139371a7af7068b01b92
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/faa1781e1444bba5b8c677bc5e2a38d023a1ec65 b/test/core/security/corpus/alts_credentials_corpus/faa1781e1444bba5b8c677bc5e2a38d023a1ec65
new file mode 100644
index 0000000..013d565
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/faa1781e1444bba5b8c677bc5e2a38d023a1ec65
@@ -0,0 +1 @@

\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/fceba33ada1dda05fccedfefd331c9a201f1a2e5 b/test/core/security/corpus/alts_credentials_corpus/fceba33ada1dda05fccedfefd331c9a201f1a2e5
new file mode 100644
index 0000000..f3db26d
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/fceba33ada1dda05fccedfefd331c9a201f1a2e5
@@ -0,0 +1 @@
+¿
\ No newline at end of file
diff --git a/test/core/security/corpus/alts_credentials_corpus/fd668bef6fdaf7f3ffd58d8c60ce550476652e60 b/test/core/security/corpus/alts_credentials_corpus/fd668bef6fdaf7f3ffd58d8c60ce550476652e60
new file mode 100644
index 0000000..dcefd99
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/fd668bef6fdaf7f3ffd58d8c60ce550476652e60
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/fdf06b928e37e7c4ae59a568b5723ad98bbed6e5 b/test/core/security/corpus/alts_credentials_corpus/fdf06b928e37e7c4ae59a568b5723ad98bbed6e5
new file mode 100644
index 0000000..fe28d69
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/fdf06b928e37e7c4ae59a568b5723ad98bbed6e5
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/fe2fc5d499aeb2762387ef2e3ce939280813dec0 b/test/core/security/corpus/alts_credentials_corpus/fe2fc5d499aeb2762387ef2e3ce939280813dec0
new file mode 100644
index 0000000..cb035e7
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/fe2fc5d499aeb2762387ef2e3ce939280813dec0
Binary files differ
diff --git a/test/core/security/corpus/alts_credentials_corpus/ff548d368b090409a138e5cc4afc7f43b4a3fbbd b/test/core/security/corpus/alts_credentials_corpus/ff548d368b090409a138e5cc4afc7f43b4a3fbbd
new file mode 100644
index 0000000..caadbc0
--- /dev/null
+++ b/test/core/security/corpus/alts_credentials_corpus/ff548d368b090409a138e5cc4afc7f43b4a3fbbd
Binary files differ
diff --git a/test/core/security/credentials_test.cc b/test/core/security/credentials_test.cc
index ce92e21..2b90939 100644
--- a/test/core/security/credentials_test.cc
+++ b/test/core/security/credentials_test.cc
@@ -867,6 +867,7 @@
 static void test_google_default_creds_auth_key(void) {
   grpc_core::ExecCtx exec_ctx;
   grpc_service_account_jwt_access_credentials* jwt;
+  grpc_google_default_channel_credentials* default_creds;
   grpc_composite_channel_credentials* creds;
   char* json_key = test_json_key_str();
   grpc_flush_cached_google_default_credentials();
@@ -875,7 +876,9 @@
   gpr_free(json_key);
   creds = reinterpret_cast<grpc_composite_channel_credentials*>(
       grpc_google_default_credentials_create());
-  GPR_ASSERT(creds != nullptr);
+  default_creds = reinterpret_cast<grpc_google_default_channel_credentials*>(
+      creds->inner_creds);
+  GPR_ASSERT(default_creds->ssl_creds != nullptr);
   jwt = reinterpret_cast<grpc_service_account_jwt_access_credentials*>(
       creds->call_creds);
   GPR_ASSERT(
@@ -889,13 +892,16 @@
 static void test_google_default_creds_refresh_token(void) {
   grpc_core::ExecCtx exec_ctx;
   grpc_google_refresh_token_credentials* refresh;
+  grpc_google_default_channel_credentials* default_creds;
   grpc_composite_channel_credentials* creds;
   grpc_flush_cached_google_default_credentials();
   set_google_default_creds_env_var_with_file_contents(
       "refresh_token_google_default_creds", test_refresh_token_str);
   creds = reinterpret_cast<grpc_composite_channel_credentials*>(
       grpc_google_default_credentials_create());
-  GPR_ASSERT(creds != nullptr);
+  default_creds = reinterpret_cast<grpc_google_default_channel_credentials*>(
+      creds->inner_creds);
+  GPR_ASSERT(default_creds->ssl_creds != nullptr);
   refresh = reinterpret_cast<grpc_google_refresh_token_credentials*>(
       creds->call_creds);
   GPR_ASSERT(strcmp(refresh->refresh_token.client_id,
diff --git a/test/core/security/security_connector_test.cc b/test/core/security/security_connector_test.cc
index f03f4cc..e4c3ace 100644
--- a/test/core/security/security_connector_test.cc
+++ b/test/core/security/security_connector_test.cc
@@ -87,15 +87,15 @@
   GPR_ASSERT(tsi_construct_string_peer_property_from_cstring(
                  TSI_CERTIFICATE_TYPE_PEER_PROPERTY, TSI_X509_CERTIFICATE_TYPE,
                  &peer.properties[0]) == TSI_OK);
-  ctx = tsi_ssl_peer_to_auth_context(&peer);
+  ctx = grpc_ssl_peer_to_auth_context(&peer);
   GPR_ASSERT(ctx != nullptr);
   GPR_ASSERT(!grpc_auth_context_peer_is_authenticated(ctx));
   GPR_ASSERT(check_transport_security_type(ctx));
 
-  rpeer = tsi_shallow_peer_from_ssl_auth_context(ctx);
+  rpeer = grpc_shallow_peer_from_ssl_auth_context(ctx);
   GPR_ASSERT(check_ssl_peer_equivalence(&peer, &rpeer));
 
-  tsi_shallow_peer_destruct(&rpeer);
+  grpc_shallow_peer_destruct(&rpeer);
   tsi_peer_destruct(&peer);
   GRPC_AUTH_CONTEXT_UNREF(ctx, "test");
 }
@@ -187,7 +187,7 @@
   GPR_ASSERT(tsi_construct_string_peer_property_from_cstring(
                  TSI_X509_PEM_CERT_PROPERTY, expected_pem_cert,
                  &peer.properties[2]) == TSI_OK);
-  ctx = tsi_ssl_peer_to_auth_context(&peer);
+  ctx = grpc_ssl_peer_to_auth_context(&peer);
   GPR_ASSERT(ctx != nullptr);
   GPR_ASSERT(grpc_auth_context_peer_is_authenticated(ctx));
   GPR_ASSERT(check_identity(ctx, GRPC_X509_CN_PROPERTY_NAME, &expected_cn, 1));
@@ -195,10 +195,10 @@
   GPR_ASSERT(check_x509_cn(ctx, expected_cn));
   GPR_ASSERT(check_x509_pem_cert(ctx, expected_pem_cert));
 
-  rpeer = tsi_shallow_peer_from_ssl_auth_context(ctx);
+  rpeer = grpc_shallow_peer_from_ssl_auth_context(ctx);
   GPR_ASSERT(check_ssl_peer_equivalence(&peer, &rpeer));
 
-  tsi_shallow_peer_destruct(&rpeer);
+  grpc_shallow_peer_destruct(&rpeer);
   tsi_peer_destruct(&peer);
   GRPC_AUTH_CONTEXT_UNREF(ctx, "test");
 }
@@ -223,7 +223,7 @@
   GPR_ASSERT(tsi_construct_string_peer_property_from_cstring(
                  TSI_X509_PEM_CERT_PROPERTY, expected_pem_cert,
                  &peer.properties[3]) == TSI_OK);
-  ctx = tsi_ssl_peer_to_auth_context(&peer);
+  ctx = grpc_ssl_peer_to_auth_context(&peer);
   GPR_ASSERT(ctx != nullptr);
   GPR_ASSERT(grpc_auth_context_peer_is_authenticated(ctx));
   GPR_ASSERT(
@@ -232,10 +232,10 @@
   GPR_ASSERT(check_x509_cn(ctx, expected_cn));
   GPR_ASSERT(check_x509_pem_cert(ctx, expected_pem_cert));
 
-  rpeer = tsi_shallow_peer_from_ssl_auth_context(ctx);
+  rpeer = grpc_shallow_peer_from_ssl_auth_context(ctx);
   GPR_ASSERT(check_ssl_peer_equivalence(&peer, &rpeer));
 
-  tsi_shallow_peer_destruct(&rpeer);
+  grpc_shallow_peer_destruct(&rpeer);
   tsi_peer_destruct(&peer);
   GRPC_AUTH_CONTEXT_UNREF(ctx, "test");
 }
@@ -264,7 +264,7 @@
                    TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY,
                    expected_sans[i], &peer.properties[3 + i]) == TSI_OK);
   }
-  ctx = tsi_ssl_peer_to_auth_context(&peer);
+  ctx = grpc_ssl_peer_to_auth_context(&peer);
   GPR_ASSERT(ctx != nullptr);
   GPR_ASSERT(grpc_auth_context_peer_is_authenticated(ctx));
   GPR_ASSERT(check_identity(ctx, GRPC_X509_SAN_PROPERTY_NAME, expected_sans,
@@ -273,10 +273,10 @@
   GPR_ASSERT(check_x509_cn(ctx, expected_cn));
   GPR_ASSERT(check_x509_pem_cert(ctx, expected_pem_cert));
 
-  rpeer = tsi_shallow_peer_from_ssl_auth_context(ctx);
+  rpeer = grpc_shallow_peer_from_ssl_auth_context(ctx);
   GPR_ASSERT(check_ssl_peer_equivalence(&peer, &rpeer));
 
-  tsi_shallow_peer_destruct(&rpeer);
+  grpc_shallow_peer_destruct(&rpeer);
   tsi_peer_destruct(&peer);
   GRPC_AUTH_CONTEXT_UNREF(ctx, "test");
 }
@@ -310,7 +310,7 @@
                    TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY,
                    expected_sans[i], &peer.properties[5 + i]) == TSI_OK);
   }
-  ctx = tsi_ssl_peer_to_auth_context(&peer);
+  ctx = grpc_ssl_peer_to_auth_context(&peer);
   GPR_ASSERT(ctx != nullptr);
   GPR_ASSERT(grpc_auth_context_peer_is_authenticated(ctx));
   GPR_ASSERT(check_identity(ctx, GRPC_X509_SAN_PROPERTY_NAME, expected_sans,
@@ -319,10 +319,10 @@
   GPR_ASSERT(check_x509_cn(ctx, expected_cn));
   GPR_ASSERT(check_x509_pem_cert(ctx, expected_pem_cert));
 
-  rpeer = tsi_shallow_peer_from_ssl_auth_context(ctx);
+  rpeer = grpc_shallow_peer_from_ssl_auth_context(ctx);
   GPR_ASSERT(check_ssl_peer_equivalence(&peer, &rpeer));
 
-  tsi_shallow_peer_destruct(&rpeer);
+  grpc_shallow_peer_destruct(&rpeer);
   tsi_peer_destruct(&peer);
   GRPC_AUTH_CONTEXT_UNREF(ctx, "test");
 }
@@ -340,6 +340,26 @@
   return GRPC_SSL_ROOTS_OVERRIDE_FAIL_PERMANENTLY;
 }
 
+static void test_ipv6_address_san(void) {
+  const char* addresses[] = {
+      "2001:db8::1",     "fe80::abcd:ef65:4321%em0", "fd11:feed:beef:0:cafe::4",
+      "128.10.0.1:8888", "[2001:db8::1]:8080",       "[2001:db8::1%em1]:8080",
+  };
+  const char* san_ips[] = {
+      "2001:db8::1", "fe80::abcd:ef65:4321", "fd11:feed:beef:0:cafe::4",
+      "128.10.0.1",  "2001:db8::1",          "2001:db8::1",
+  };
+  tsi_peer peer;
+  GPR_ASSERT(tsi_construct_peer(1, &peer) == TSI_OK);
+  for (size_t i = 0; i < GPR_ARRAY_SIZE(addresses); i++) {
+    GPR_ASSERT(tsi_construct_string_peer_property_from_cstring(
+                   TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY, san_ips[i],
+                   &peer.properties[0]) == TSI_OK);
+    GPR_ASSERT(grpc_ssl_host_matches_name(&peer, addresses[i]));
+    tsi_peer_property_destruct(&peer.properties[0]);
+  }
+  tsi_peer_destruct(&peer);
+}
 namespace grpc_core {
 namespace {
 
@@ -416,6 +436,7 @@
   test_cn_and_one_san_ssl_peer_to_auth_context();
   test_cn_and_multiple_sans_ssl_peer_to_auth_context();
   test_cn_and_multiple_sans_and_others_ssl_peer_to_auth_context();
+  test_ipv6_address_san();
   test_default_ssl_roots();
 
   grpc_shutdown();
diff --git a/test/core/tsi/alts/handshaker/alts_handshaker_client_test.cc b/test/core/tsi/alts/handshaker/alts_handshaker_client_test.cc
index b9dd52a..c8d88aa 100644
--- a/test/core/tsi/alts/handshaker/alts_handshaker_client_test.cc
+++ b/test/core/tsi/alts/handshaker/alts_handshaker_client_test.cc
@@ -326,6 +326,9 @@
   GPR_ASSERT(alts_handshaker_client_next(nullptr, event, &config->out_frame) ==
              TSI_INVALID_ARGUMENT);
 
+  /* Check shutdown. */
+  alts_handshaker_client_shutdown(nullptr);
+
   /* Cleanup. */
   alts_tsi_event_destroy(event);
   destroy_config(config);
diff --git a/test/core/tsi/alts/handshaker/alts_tsi_handshaker_test.cc b/test/core/tsi/alts/handshaker/alts_tsi_handshaker_test.cc
index 95724f8..85a5811 100644
--- a/test/core/tsi/alts/handshaker/alts_tsi_handshaker_test.cc
+++ b/test/core/tsi/alts/handshaker/alts_tsi_handshaker_test.cc
@@ -330,6 +330,8 @@
   return TSI_OK;
 }
 
+static void mock_shutdown(alts_handshaker_client* self) {}
+
 static tsi_result mock_server_start(alts_handshaker_client* self,
                                     alts_tsi_event* event,
                                     grpc_slice* bytes_received) {
@@ -400,7 +402,8 @@
 static void mock_destruct(alts_handshaker_client* client) {}
 
 static const alts_handshaker_client_vtable vtable = {
-    mock_client_start, mock_server_start, mock_next, mock_destruct};
+    mock_client_start, mock_server_start, mock_next, mock_shutdown,
+    mock_destruct};
 
 static alts_handshaker_client* alts_mock_handshaker_client_create(
     bool used_for_success_test) {
@@ -442,6 +445,16 @@
   tsi_handshaker_destroy(handshaker);
 }
 
+static void check_handshaker_shutdown_invalid_input() {
+  /* Initialization. */
+  tsi_handshaker* handshaker = create_test_handshaker(
+      false /* used_for_success_test */, true /* is_client */);
+  /* Check nullptr handshaker. */
+  tsi_handshaker_shutdown(nullptr);
+  /* Cleanup. */
+  tsi_handshaker_destroy(handshaker);
+}
+
 static void check_handshaker_next_success() {
   /**
    * Create handshakers for which internal mock client is going to do
@@ -480,6 +493,33 @@
   tsi_handshaker_destroy(client_handshaker);
 }
 
+static void check_handshaker_next_with_shutdown() {
+  /* Initialization. */
+  tsi_handshaker* handshaker = create_test_handshaker(
+      true /* used_for_success_test */, true /* is_client*/);
+  /* next(success) -- shutdown(success) -- next (fail) */
+  GPR_ASSERT(tsi_handshaker_next(handshaker, nullptr, 0, nullptr, nullptr,
+                                 nullptr, on_client_start_success_cb,
+                                 nullptr) == TSI_ASYNC);
+  wait(&tsi_to_caller_notification);
+  tsi_handshaker_shutdown(handshaker);
+  GPR_ASSERT(tsi_handshaker_next(
+                 handshaker,
+                 (const unsigned char*)ALTS_TSI_HANDSHAKER_TEST_RECV_BYTES,
+                 strlen(ALTS_TSI_HANDSHAKER_TEST_RECV_BYTES), nullptr, nullptr,
+                 nullptr, on_client_next_success_cb,
+                 nullptr) == TSI_HANDSHAKE_SHUTDOWN);
+  /* Cleanup. */
+  tsi_handshaker_destroy(handshaker);
+}
+
+static void check_handle_response_with_shutdown(void* unused) {
+  /* Client start. */
+  wait(&caller_to_tsi_notification);
+  alts_tsi_event_dispatch_to_handshaker(client_start_event, true /* is_ok */);
+  alts_tsi_event_destroy(client_start_event);
+}
+
 static void check_handshaker_next_failure() {
   /**
    * Create handshakers for which internal mock client is always going to fail.
@@ -647,6 +687,49 @@
   tsi_handshaker_destroy(handshaker);
 }
 
+static void on_shutdown_resp_cb(tsi_result status, void* user_data,
+                                const unsigned char* bytes_to_send,
+                                size_t bytes_to_send_size,
+                                tsi_handshaker_result* result) {
+  GPR_ASSERT(status == TSI_HANDSHAKE_SHUTDOWN);
+  GPR_ASSERT(user_data == nullptr);
+  GPR_ASSERT(bytes_to_send == nullptr);
+  GPR_ASSERT(bytes_to_send_size == 0);
+  GPR_ASSERT(result == nullptr);
+}
+
+static void check_handle_response_after_shutdown() {
+  tsi_handshaker* handshaker = create_test_handshaker(
+      true /* used_for_success_test */, true /* is_client */);
+  alts_tsi_handshaker* alts_handshaker =
+      reinterpret_cast<alts_tsi_handshaker*>(handshaker);
+  /* Tests. */
+  tsi_handshaker_shutdown(handshaker);
+  grpc_byte_buffer* recv_buffer = generate_handshaker_response(CLIENT_START);
+  alts_tsi_handshaker_handle_response(alts_handshaker, recv_buffer,
+                                      GRPC_STATUS_OK, nullptr,
+                                      on_shutdown_resp_cb, nullptr, true);
+  grpc_byte_buffer_destroy(recv_buffer);
+  /* Cleanup. */
+  tsi_handshaker_destroy(handshaker);
+}
+
+void check_handshaker_next_fails_after_shutdown() {
+  /* Initialization. */
+  notification_init(&caller_to_tsi_notification);
+  notification_init(&tsi_to_caller_notification);
+  client_start_event = nullptr;
+  /* Tests. */
+  grpc_core::Thread thd("alts_tsi_handshaker_test",
+                        &check_handle_response_with_shutdown, nullptr);
+  thd.Start();
+  check_handshaker_next_with_shutdown();
+  thd.Join();
+  /* Cleanup. */
+  notification_destroy(&caller_to_tsi_notification);
+  notification_destroy(&tsi_to_caller_notification);
+}
+
 void check_handshaker_success() {
   /* Initialization. */
   notification_init(&caller_to_tsi_notification);
@@ -672,10 +755,13 @@
   /* Tests. */
   check_handshaker_success();
   check_handshaker_next_invalid_input();
+  check_handshaker_shutdown_invalid_input();
+  check_handshaker_next_fails_after_shutdown();
   check_handshaker_next_failure();
   check_handle_response_invalid_input();
   check_handle_response_invalid_resp();
   check_handle_response_failure();
+  check_handle_response_after_shutdown();
   /* Cleanup. */
   grpc_shutdown();
   return 0;
diff --git a/test/core/tsi/ssl_transport_security_test.cc b/test/core/tsi/ssl_transport_security_test.cc
index cf1ac82..b477904 100644
--- a/test/core/tsi/ssl_transport_security_test.cc
+++ b/test/core/tsi/ssl_transport_security_test.cc
@@ -24,7 +24,6 @@
 #include "src/core/lib/security/security_connector/security_connector.h"
 #include "src/core/tsi/ssl_transport_security.h"
 #include "src/core/tsi/transport_security.h"
-#include "src/core/tsi/transport_security_adapter.h"
 #include "src/core/tsi/transport_security_interface.h"
 #include "test/core/tsi/transport_security_test_lib.h"
 #include "test/core/util/test_config.h"
@@ -164,19 +163,13 @@
                  &server_options, &ssl_fixture->server_handshaker_factory) ==
              TSI_OK);
   /* Create server and client handshakers. */
-  tsi_handshaker* client_handshaker = nullptr;
   GPR_ASSERT(tsi_ssl_client_handshaker_factory_create_handshaker(
                  ssl_fixture->client_handshaker_factory,
                  ssl_fixture->server_name_indication,
-                 &client_handshaker) == TSI_OK);
-  ssl_fixture->base.client_handshaker =
-      tsi_create_adapter_handshaker(client_handshaker);
-  tsi_handshaker* server_handshaker = nullptr;
+                 &ssl_fixture->base.client_handshaker) == TSI_OK);
   GPR_ASSERT(tsi_ssl_server_handshaker_factory_create_handshaker(
-                 ssl_fixture->server_handshaker_factory, &server_handshaker) ==
-             TSI_OK);
-  ssl_fixture->base.server_handshaker =
-      tsi_create_adapter_handshaker(server_handshaker);
+                 ssl_fixture->server_handshaker_factory,
+                 &ssl_fixture->base.server_handshaker) == TSI_OK);
 }
 
 static void check_alpn(ssl_tsi_test_fixture* ssl_fixture,
diff --git a/test/core/util/BUILD b/test/core/util/BUILD
index 886cfdd..f52570c 100644
--- a/test/core/util/BUILD
+++ b/test/core/util/BUILD
@@ -52,6 +52,7 @@
     name = "grpc_test_util_base",
     srcs = [
         "cmdline.cc",
+        "fuzzer_util.cc",
         "grpc_profiler.cc",
         "histogram.cc",
         "mock_endpoint.cc",
@@ -70,6 +71,7 @@
     ],
     hdrs = [
         "cmdline.h",
+        "fuzzer_util.h",
         "grpc_profiler.h",
         "histogram.h",
         "mock_endpoint.h",
diff --git a/test/core/util/fuzzer_util.cc b/test/core/util/fuzzer_util.cc
new file mode 100644
index 0000000..29c9b88
--- /dev/null
+++ b/test/core/util/fuzzer_util.cc
@@ -0,0 +1,82 @@
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "test/core/util/fuzzer_util.h"
+
+#include <grpc/support/alloc.h>
+
+#include "src/core/lib/gpr/useful.h"
+
+namespace grpc_core {
+namespace testing {
+
+uint8_t grpc_fuzzer_get_next_byte(input_stream* inp) {
+  if (inp->cur == inp->end) {
+    return 0;
+  }
+  return *inp->cur++;
+}
+
+char* grpc_fuzzer_get_next_string(input_stream* inp, bool* special) {
+  char* str = nullptr;
+  size_t cap = 0;
+  size_t sz = 0;
+  char c;
+  do {
+    if (cap == sz) {
+      cap = GPR_MAX(3 * cap / 2, cap + 8);
+      str = static_cast<char*>(gpr_realloc(str, cap));
+    }
+    c = static_cast<char>(grpc_fuzzer_get_next_byte(inp));
+    str[sz++] = c;
+  } while (c != 0 && c != 1);
+  if (special != nullptr) {
+    *special = (c == 1);
+  }
+  if (c == 1) {
+    str[sz - 1] = 0;
+  }
+  return str;
+}
+
+uint32_t grpc_fuzzer_get_next_uint32(input_stream* inp) {
+  uint8_t b = grpc_fuzzer_get_next_byte(inp);
+  uint32_t x = b & 0x7f;
+  if (b & 0x80) {
+    x <<= 7;
+    b = grpc_fuzzer_get_next_byte(inp);
+    x |= b & 0x7f;
+    if (b & 0x80) {
+      x <<= 7;
+      b = grpc_fuzzer_get_next_byte(inp);
+      x |= b & 0x7f;
+      if (b & 0x80) {
+        x <<= 7;
+        b = grpc_fuzzer_get_next_byte(inp);
+        x |= b & 0x7f;
+        if (b & 0x80) {
+          x = (x << 4) | (grpc_fuzzer_get_next_byte(inp) & 0x0f);
+        }
+      }
+    }
+  }
+  return x;
+}
+
+}  // namespace testing
+}  // namespace grpc_core
diff --git a/test/core/util/fuzzer_util.h b/test/core/util/fuzzer_util.h
new file mode 100644
index 0000000..0e93839
--- /dev/null
+++ b/test/core/util/fuzzer_util.h
@@ -0,0 +1,49 @@
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_TEST_CORE_UTIL_FUZZER_UTIL_H
+#define GRPC_TEST_CORE_UTIL_FUZZER_UTIL_H
+
+#include <stdint.h>
+
+namespace grpc_core {
+
+namespace testing {
+
+// Main struct for input_stream. It allows easy access to input
+// bytes, and allows reading a little past the end(avoiding
+// needing to check everywhere).
+typedef struct {
+  const uint8_t* cur;
+  const uint8_t* end;
+} input_stream;
+
+// get a byte from an input stream.
+uint8_t grpc_fuzzer_get_next_byte(input_stream* inp);
+
+// get a string and boolean values (if special is not null) from an input
+// stream.
+char* grpc_fuzzer_get_next_string(input_stream* inp, bool* special);
+
+// get a uint32 value from an input stream.
+uint32_t grpc_fuzzer_get_next_uint32(input_stream* inp);
+
+}  // namespace testing
+}  // namespace grpc_core
+
+#endif /* GRPC_TEST_CORE_UTIL_FUZZER_UTIL_H */
diff --git a/test/core/util/port_isolated_runtime_environment.cc b/test/core/util/port_isolated_runtime_environment.cc
index 5f0585e..ff8342f 100644
--- a/test/core/util/port_isolated_runtime_environment.cc
+++ b/test/core/util/port_isolated_runtime_environment.cc
@@ -19,19 +19,28 @@
 /* When running tests on remote machines, the framework takes a round-robin pick
  * of a port within certain range. There is no need to recycle ports.
  */
+#include <grpc/support/time.h>
+#include <stdlib.h>
 #include "src/core/lib/iomgr/port.h"
 #include "test/core/util/test_config.h"
 #if defined(GRPC_PORT_ISOLATED_RUNTIME)
 
 #include "test/core/util/port.h"
 
-#define LOWER_PORT 49152
-static int s_allocated_port = LOWER_PORT;
+#define MIN_PORT 49152
+#define MAX_PORT 65536
+
+int get_random_starting_port() {
+  srand(gpr_now(GPR_CLOCK_REALTIME).tv_nsec);
+  return rand() % (MAX_PORT - MIN_PORT + 1) + MIN_PORT;
+}
+
+static int s_allocated_port = get_random_starting_port();
 
 int grpc_pick_unused_port_or_die(void) {
   int allocated_port = s_allocated_port++;
-  if (s_allocated_port == 65536) {
-    s_allocated_port = LOWER_PORT;
+  if (s_allocated_port == MAX_PORT) {
+    s_allocated_port = MIN_PORT;
   }
 
   return allocated_port;
diff --git a/test/cpp/interop/interop_server.cc b/test/cpp/interop/interop_server.cc
index 6526e05..f55d624 100644
--- a/test/cpp/interop/interop_server.cc
+++ b/test/cpp/interop/interop_server.cc
@@ -318,12 +318,27 @@
 
 void grpc::testing::interop::RunServer(
     std::shared_ptr<ServerCredentials> creds) {
-  RunServer(creds, FLAGS_port, nullptr);
+  RunServer(creds, FLAGS_port, nullptr, nullptr);
+}
+
+void grpc::testing::interop::RunServer(
+    std::shared_ptr<ServerCredentials> creds,
+    std::unique_ptr<std::vector<std::unique_ptr<ServerBuilderOption>>>
+        server_options) {
+  RunServer(creds, FLAGS_port, nullptr, std::move(server_options));
 }
 
 void grpc::testing::interop::RunServer(
     std::shared_ptr<ServerCredentials> creds, const int port,
     ServerStartedCondition* server_started_condition) {
+  RunServer(creds, port, server_started_condition, nullptr);
+}
+
+void grpc::testing::interop::RunServer(
+    std::shared_ptr<ServerCredentials> creds, const int port,
+    ServerStartedCondition* server_started_condition,
+    std::unique_ptr<std::vector<std::unique_ptr<ServerBuilderOption>>>
+        server_options) {
   GPR_ASSERT(port != 0);
   std::ostringstream server_address;
   server_address << "0.0.0.0:" << port;
@@ -335,6 +350,11 @@
   ServerBuilder builder;
   builder.RegisterService(&service);
   builder.AddListeningPort(server_address.str(), creds);
+  if (server_options != nullptr) {
+    for (size_t i = 0; i < server_options->size(); i++) {
+      builder.SetOption(std::move((*server_options)[i]));
+    }
+  }
   if (FLAGS_max_send_message_size >= 0) {
     builder.SetMaxSendMessageSize(FLAGS_max_send_message_size);
   }
diff --git a/test/cpp/interop/server_helper.h b/test/cpp/interop/server_helper.h
index 3004e7f..265874d 100644
--- a/test/cpp/interop/server_helper.h
+++ b/test/cpp/interop/server_helper.h
@@ -26,6 +26,8 @@
 #include <grpc/impl/codegen/atm.h>
 
 #include <grpcpp/security/server_credentials.h>
+#include <grpcpp/server.h>
+#include <grpcpp/server_builder.h>
 #include <grpcpp/server_context.h>
 
 namespace grpc {
@@ -72,6 +74,28 @@
 void RunServer(std::shared_ptr<ServerCredentials> creds, int port,
                ServerStartedCondition* server_started_condition);
 
+/// Run gRPC interop server.
+///
+/// \param creds The credentials associated with the server.
+/// \param server_options List of options to set when building the server.
+void RunServer(
+    std::shared_ptr<ServerCredentials> creds,
+    std::unique_ptr<std::vector<std::unique_ptr<ServerBuilderOption>>>
+        server_options);
+
+/// Run gRPC interop server.
+///
+/// \param creds The credentials associated with the server.
+/// \param port Port to use for the server.
+/// \param server_options List of options to set when building the server.
+/// \param server_started_condition (optional) Struct holding mutex, condition
+//     variable, and condition used to notify when the server has started.
+void RunServer(
+    std::shared_ptr<ServerCredentials> creds, const int port,
+    ServerStartedCondition* server_started_condition,
+    std::unique_ptr<std::vector<std::unique_ptr<grpc::ServerBuilderOption>>>
+        server_options);
+
 }  // namespace interop
 }  // namespace testing
 }  // namespace grpc
diff --git a/test/cpp/microbenchmarks/helpers.cc b/test/cpp/microbenchmarks/helpers.cc
index e4a31f5..e4ba37e 100644
--- a/test/cpp/microbenchmarks/helpers.cc
+++ b/test/cpp/microbenchmarks/helpers.cc
@@ -48,16 +48,10 @@
             static_cast<double>(state.iterations()));
   }
   for (int i = 0; i < GRPC_STATS_HISTOGRAM_COUNT; i++) {
-    std::ostringstream median_ss;
-    median_ss << grpc_stats_histogram_name[i] << "-median";
-    state.counters[median_ss.str()] =
-        benchmark::Counter(grpc_stats_histo_percentile(
-            &stats, static_cast<grpc_stats_histograms>(i), 50.0));
-    std::ostringstream tail_ss;
-    tail_ss << grpc_stats_histogram_name[i] << "-99p";
-    state.counters[tail_ss.str()] =
-        benchmark::Counter(grpc_stats_histo_percentile(
-            &stats, static_cast<grpc_stats_histograms>(i), 99.0));
+    out << " " << grpc_stats_histogram_name[i] << "-median:"
+        << grpc_stats_histo_percentile(&stats, (grpc_stats_histograms)i, 50.0)
+        << " " << grpc_stats_histogram_name[i] << "-99p:"
+        << grpc_stats_histo_percentile(&stats, (grpc_stats_histograms)i, 99.0);
   }
 #ifdef GPR_LOW_LEVEL_COUNTERS
   grpc_memory_counters counters_at_end = grpc_memory_counters_snapshot();
diff --git a/test/cpp/naming/BUILD b/test/cpp/naming/BUILD
index 54b53d2..fa0b216 100644
--- a/test/cpp/naming/BUILD
+++ b/test/cpp/naming/BUILD
@@ -22,16 +22,17 @@
 
 licenses(["notice"])  # Apache v2
 
-load("//bazel:grpc_build_system.bzl", "grpc_sh_binary", "grpc_py_binary")
+load("//bazel:grpc_build_system.bzl", "grpc_py_binary")
 
 load(":generate_resolver_component_tests.bzl", "generate_resolver_component_tests")
 
 # Meant to be invoked only through the top-level shell script driver.
-grpc_sh_binary(
+grpc_py_binary(
     name = "resolver_component_tests_runner",
     srcs = [
-        "resolver_component_tests_runner.sh",
+        "resolver_component_tests_runner.py",
     ],
+    testonly = True,
 )
 
 generate_resolver_component_tests()
diff --git a/test/cpp/naming/address_sorting_test.cc b/test/cpp/naming/address_sorting_test.cc
index a423733..a92e9e3 100644
--- a/test/cpp/naming/address_sorting_test.cc
+++ b/test/cpp/naming/address_sorting_test.cc
@@ -298,6 +298,29 @@
                                 });
 }
 
+/* Flip the input on the test above to reorder the sort function's
+ * comparator's inputs. */
+TEST(AddressSortingTest, TestUsesLabelFromDefaultTableInputFlipped) {
+  bool ipv4_supported = true;
+  bool ipv6_supported = true;
+  OverrideAddressSortingSourceAddrFactory(
+      ipv4_supported, ipv6_supported,
+      {
+          {"[2002::5001]:443", {"[2001::5002]:0", AF_INET6}},
+          {"[2001::5001]:443",
+           {"[2001::5002]:0", AF_INET6}},  // matching labels
+      });
+  grpc_lb_addresses* lb_addrs = BuildLbAddrInputs({
+      {"[2001::5001]:443", AF_INET6},
+      {"[2002::5001]:443", AF_INET6},
+  });
+  grpc_cares_wrapper_test_only_address_sorting_sort(lb_addrs);
+  VerifyLbAddrOutputs(lb_addrs, {
+                                    "[2001::5001]:443",
+                                    "[2002::5001]:443",
+                                });
+}
+
 /* Tests for rule 6 */
 
 TEST(AddressSortingTest,
diff --git a/test/cpp/naming/generate_resolver_component_tests.bzl b/test/cpp/naming/generate_resolver_component_tests.bzl
index ebc1679..5e9aa63 100755
--- a/test/cpp/naming/generate_resolver_component_tests.bzl
+++ b/test/cpp/naming/generate_resolver_component_tests.bzl
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("//bazel:grpc_build_system.bzl", "grpc_sh_binary", "grpc_cc_test", "grpc_cc_binary")
+load("//bazel:grpc_build_system.bzl", "grpc_cc_test", "grpc_cc_binary")
 
 def generate_resolver_component_tests():
   for unsecure_build_config_suffix in ['_unsecure', '']:
diff --git a/test/cpp/naming/resolver_component_tests_runner.py b/test/cpp/naming/resolver_component_tests_runner.py
new file mode 100755
index 0000000..69386eb
--- /dev/null
+++ b/test/cpp/naming/resolver_component_tests_runner.py
@@ -0,0 +1,275 @@
+#!/usr/bin/env python
+# Copyright 2015 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This file is auto-generated
+
+import argparse
+import sys
+import subprocess
+import tempfile
+import os
+import time
+import signal
+
+
+argp = argparse.ArgumentParser(description='Run c-ares resolver tests')
+argp.add_argument('--test_bin_path', default=None, type=str,
+                  help='Path to gtest test binary to invoke.')
+argp.add_argument('--dns_server_bin_path', default=None, type=str,
+                  help='Path to local DNS server python script.')
+argp.add_argument('--records_config_path', default=None, type=str,
+                  help=('Path to DNS records yaml file that '
+                        'specifies records for the DNS sever. '))
+argp.add_argument('--dns_server_port', default=None, type=int,
+                  help=('Port that local DNS server is listening on.'))
+argp.add_argument('--dns_resolver_bin_path', default=None, type=str,
+                  help=('Path to the DNS health check utility.'))
+argp.add_argument('--tcp_connect_bin_path', default=None, type=str,
+                  help=('Path to the TCP health check utility.'))
+args = argp.parse_args()
+
+def test_runner_log(msg):
+  sys.stderr.write('\n%s: %s\n' % (__file__, msg))
+
+cur_resolver = os.environ.get('GRPC_DNS_RESOLVER')
+if cur_resolver and cur_resolver != 'ares':
+  test_runner_log(('WARNING: cur resolver set to %s. This set of tests '
+      'needs to use GRPC_DNS_RESOLVER=ares.'))
+  test_runner_log('Exit 1 without running tests.')
+  sys.exit(1)
+os.environ.update({'GRPC_DNS_RESOLVER': 'ares'})
+
+def wait_until_dns_server_is_up(args,
+                                dns_server_subprocess,
+                                dns_server_subprocess_output):
+  for i in range(0, 30):
+    test_runner_log('Health check: attempt to connect to DNS server over TCP.')
+    tcp_connect_subprocess = subprocess.Popen([
+        args.tcp_connect_bin_path,
+        '--server_host', '127.0.0.1',
+        '--server_port', str(args.dns_server_port),
+        '--timeout', str(1)])
+    tcp_connect_subprocess.communicate()
+    if tcp_connect_subprocess.returncode == 0:
+      test_runner_log(('Health check: attempt to make an A-record '
+                       'query to DNS server.'))
+      dns_resolver_subprocess = subprocess.Popen([
+          args.dns_resolver_bin_path,
+          '--qname', 'health-check-local-dns-server-is-alive.resolver-tests.grpctestingexp',
+          '--server_host', '127.0.0.1',
+          '--server_port', str(args.dns_server_port)],
+          stdout=subprocess.PIPE)
+      dns_resolver_stdout, _ = dns_resolver_subprocess.communicate()
+      if dns_resolver_subprocess.returncode == 0:
+        if '123.123.123.123' in dns_resolver_stdout:
+          test_runner_log(('DNS server is up! '
+                           'Successfully reached it over UDP and TCP.'))
+        return
+    time.sleep(0.1)
+  dns_server_subprocess.kill()
+  dns_server_subprocess.wait()
+  test_runner_log(('Failed to reach DNS server over TCP and/or UDP. '
+                   'Exitting without running tests.'))
+  test_runner_log('======= DNS server stdout '
+                  '(merged stdout and stderr) =============')
+  with open(dns_server_subprocess_output, 'r') as l:
+    test_runner_log(l.read())
+  test_runner_log('======= end DNS server output=========')
+  sys.exit(1)
+
+dns_server_subprocess_output = tempfile.mktemp()
+with open(dns_server_subprocess_output, 'w') as l:
+  dns_server_subprocess = subprocess.Popen([
+      args.dns_server_bin_path,
+      '--port', str(args.dns_server_port),
+      '--records_config_path', args.records_config_path],
+      stdin=subprocess.PIPE,
+      stdout=l,
+      stderr=l)
+
+def _quit_on_signal(signum, _frame):
+  test_runner_log('Received signal: %d' % signum)
+  dns_server_subprocess.kill()
+  dns_server_subprocess.wait()
+  sys.exit(1)
+
+signal.signal(signal.SIGINT, _quit_on_signal)
+signal.signal(signal.SIGTERM, _quit_on_signal)
+wait_until_dns_server_is_up(args,
+                            dns_server_subprocess,
+                            dns_server_subprocess_output)
+num_test_failures = 0
+
+test_runner_log('Run test with target: %s' % 'srv-ipv4-single-target.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'srv-ipv4-single-target.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:1234,True',
+  '--expected_chosen_service_config', '',
+  '--expected_lb_policy', '',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'srv-ipv4-multi-target.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'srv-ipv4-multi-target.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.5:1234,True;1.2.3.6:1234,True;1.2.3.7:1234,True',
+  '--expected_chosen_service_config', '',
+  '--expected_lb_policy', '',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'srv-ipv6-single-target.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'srv-ipv6-single-target.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '[2607:f8b0:400a:801::1001]:1234,True',
+  '--expected_chosen_service_config', '',
+  '--expected_lb_policy', '',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'srv-ipv6-multi-target.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'srv-ipv6-multi-target.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '[2607:f8b0:400a:801::1002]:1234,True;[2607:f8b0:400a:801::1003]:1234,True;[2607:f8b0:400a:801::1004]:1234,True',
+  '--expected_chosen_service_config', '',
+  '--expected_lb_policy', '',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'srv-ipv4-simple-service-config.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'srv-ipv4-simple-service-config.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:1234,True',
+  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]}]}',
+  '--expected_lb_policy', 'round_robin',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'ipv4-no-srv-simple-service-config.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'ipv4-no-srv-simple-service-config.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:443,False',
+  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"NoSrvSimpleService","waitForReady":true}]}]}',
+  '--expected_lb_policy', 'round_robin',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'ipv4-no-config-for-cpp.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'ipv4-no-config-for-cpp.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:443,False',
+  '--expected_chosen_service_config', '',
+  '--expected_lb_policy', '',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'ipv4-cpp-config-has-zero-percentage.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'ipv4-cpp-config-has-zero-percentage.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:443,False',
+  '--expected_chosen_service_config', '',
+  '--expected_lb_policy', '',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'ipv4-second-language-is-cpp.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'ipv4-second-language-is-cpp.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:443,False',
+  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService","waitForReady":true}]}]}',
+  '--expected_lb_policy', 'round_robin',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'ipv4-config-with-percentages.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'ipv4-config-with-percentages.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:443,False',
+  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"AlwaysPickedService","waitForReady":true}]}]}',
+  '--expected_lb_policy', 'round_robin',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'srv-ipv4-target-has-backend-and-balancer.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'srv-ipv4-target-has-backend-and-balancer.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:1234,True;1.2.3.4:443,False',
+  '--expected_chosen_service_config', '',
+  '--expected_lb_policy', '',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'srv-ipv6-target-has-backend-and-balancer.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'srv-ipv6-target-has-backend-and-balancer.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '[2607:f8b0:400a:801::1002]:1234,True;[2607:f8b0:400a:801::1002]:443,False',
+  '--expected_chosen_service_config', '',
+  '--expected_lb_policy', '',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'ipv4-config-causing-fallback-to-tcp.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'ipv4-config-causing-fallback-to-tcp.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:443,False',
+  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooThree","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFour","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFive","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSix","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSeven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEight","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooNine","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTen","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEleven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]}]}',
+  '--expected_lb_policy', '',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('now kill DNS server')
+dns_server_subprocess.kill()
+dns_server_subprocess.wait()
+test_runner_log('%d tests failed.' % num_test_failures)
+sys.exit(num_test_failures)
diff --git a/test/cpp/naming/resolver_component_tests_runner.sh b/test/cpp/naming/resolver_component_tests_runner.sh
deleted file mode 100755
index 3f8765f..0000000
--- a/test/cpp/naming/resolver_component_tests_runner.sh
+++ /dev/null
@@ -1,187 +0,0 @@
-#!/bin/bash
-# Copyright 2015 gRPC authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# This file is auto-generated
-
-set -ex
-
-# all command args required in this set order
-FLAGS_test_bin_path=$(echo "$1" | grep '\--test_bin_path=' | sed 's/^--test_bin_path=//')
-FLAGS_dns_server_bin_path=$(echo "$2" | grep '\--dns_server_bin_path=' | sed 's/^--dns_server_bin_path=//')
-FLAGS_records_config_path=$(echo "$3" | grep '\--records_config_path=' | sed 's/^--records_config_path=//')
-FLAGS_dns_server_port=$(echo "$4" | grep '\--dns_server_port=' | sed 's/^--dns_server_port=//')
-FLAGS_dns_resolver_bin_path=$(echo "$5" | grep '\--dns_resolver_bin_path=' | sed 's/^--dns_resolver_bin_path=//')
-FLAGS_tcp_connect_bin_path=$(echo "$6" | grep '\--tcp_connect_bin_path=' | sed 's/^--tcp_connect_bin_path=//')
-
-for cmd_arg in "$FLAGS_test_bin_path" "$FLAGS_dns_server_bin_path" "$FLAGS_records_config_path" "$FLAGS_dns_server_port" "$FLAGS_dns_resolver_bin_path" "$FLAGS_tcp_connect_bin_path"; do
-  if [[ "$cmd_arg" == "" ]]; then
-    echo "Missing a CMD arg" && exit 1
-  fi
-done
-
-if [[ "$GRPC_DNS_RESOLVER" != "" && "$GRPC_DNS_RESOLVER" != ares ]]; then
-  echo "This test only works under GRPC_DNS_RESOLVER=ares. Have GRPC_DNS_RESOLVER=$GRPC_DNS_RESOLVER" && exit 1
-fi
-export GRPC_DNS_RESOLVER=ares
-
-DNS_SERVER_LOG="$(mktemp)"
-"$FLAGS_dns_server_bin_path" --records_config_path="$FLAGS_records_config_path" --port="$FLAGS_dns_server_port" > "$DNS_SERVER_LOG" 2>&1 &
-DNS_SERVER_PID=$!
-echo "Local DNS server started. PID: $DNS_SERVER_PID"
-
-# Health check local DNS server TCP and UDP ports
-for ((i=0;i<30;i++));
-do
-  echo "Retry health-check local DNS server by attempting a DNS query and TCP handshake"
-  RETRY=0
-  $FLAGS_dns_resolver_bin_path -s 127.0.0.1 -p "$FLAGS_dns_server_port" -n health-check-local-dns-server-is-alive.resolver-tests.grpctestingexp. -t 1 | grep '123.123.123.123' || RETRY=1
-  $FLAGS_tcp_connect_bin_path -s 127.0.0.1 -p "$FLAGS_dns_server_port" -t 1 || RETRY=1
-  if [[ "$RETRY" == 0 ]]; then
-    break
-  fi;
-  sleep 0.1
-done
-
-if [[ $RETRY == 1 ]]; then
-  echo "FAILED TO START LOCAL DNS SERVER"
-  kill -SIGTERM "$DNS_SERVER_PID" || true
-  wait
-  echo "========== DNS server log (merged stdout and stderr) ========="
-  cat "$DNS_SERVER_LOG"
-  echo "========== end DNS server log ================================"
-  exit 1
-fi
-
-function terminate_all {
-  echo "Received signal. Terminating $! and $DNS_SERVER_PID"
-  kill -SIGTERM "$!" || true
-  kill -SIGTERM "$DNS_SERVER_PID" || true
-  wait
-  exit 1
-}
-
-trap terminate_all SIGTERM SIGINT
-
-EXIT_CODE=0
-# TODO: this test should check for GCE residency and skip tests using _grpclb._tcp.* SRV records once GCE residency checks are made
-# in the resolver.
-
-$FLAGS_test_bin_path \
-  --target_name='srv-ipv4-single-target.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='1.2.3.4:1234,True' \
-  --expected_chosen_service_config='' \
-  --expected_lb_policy='' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-$FLAGS_test_bin_path \
-  --target_name='srv-ipv4-multi-target.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='1.2.3.5:1234,True;1.2.3.6:1234,True;1.2.3.7:1234,True' \
-  --expected_chosen_service_config='' \
-  --expected_lb_policy='' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-$FLAGS_test_bin_path \
-  --target_name='srv-ipv6-single-target.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='[2607:f8b0:400a:801::1001]:1234,True' \
-  --expected_chosen_service_config='' \
-  --expected_lb_policy='' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-$FLAGS_test_bin_path \
-  --target_name='srv-ipv6-multi-target.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='[2607:f8b0:400a:801::1002]:1234,True;[2607:f8b0:400a:801::1003]:1234,True;[2607:f8b0:400a:801::1004]:1234,True' \
-  --expected_chosen_service_config='' \
-  --expected_lb_policy='' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-$FLAGS_test_bin_path \
-  --target_name='srv-ipv4-simple-service-config.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='1.2.3.4:1234,True' \
-  --expected_chosen_service_config='{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]}]}' \
-  --expected_lb_policy='round_robin' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-$FLAGS_test_bin_path \
-  --target_name='ipv4-no-srv-simple-service-config.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='1.2.3.4:443,False' \
-  --expected_chosen_service_config='{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"NoSrvSimpleService","waitForReady":true}]}]}' \
-  --expected_lb_policy='round_robin' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-$FLAGS_test_bin_path \
-  --target_name='ipv4-no-config-for-cpp.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='1.2.3.4:443,False' \
-  --expected_chosen_service_config='' \
-  --expected_lb_policy='' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-$FLAGS_test_bin_path \
-  --target_name='ipv4-cpp-config-has-zero-percentage.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='1.2.3.4:443,False' \
-  --expected_chosen_service_config='' \
-  --expected_lb_policy='' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-$FLAGS_test_bin_path \
-  --target_name='ipv4-second-language-is-cpp.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='1.2.3.4:443,False' \
-  --expected_chosen_service_config='{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService","waitForReady":true}]}]}' \
-  --expected_lb_policy='round_robin' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-$FLAGS_test_bin_path \
-  --target_name='ipv4-config-with-percentages.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='1.2.3.4:443,False' \
-  --expected_chosen_service_config='{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"AlwaysPickedService","waitForReady":true}]}]}' \
-  --expected_lb_policy='round_robin' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-$FLAGS_test_bin_path \
-  --target_name='srv-ipv4-target-has-backend-and-balancer.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='1.2.3.4:1234,True;1.2.3.4:443,False' \
-  --expected_chosen_service_config='' \
-  --expected_lb_policy='' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-$FLAGS_test_bin_path \
-  --target_name='srv-ipv6-target-has-backend-and-balancer.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='[2607:f8b0:400a:801::1002]:1234,True;[2607:f8b0:400a:801::1002]:443,False' \
-  --expected_chosen_service_config='' \
-  --expected_lb_policy='' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-$FLAGS_test_bin_path \
-  --target_name='ipv4-config-causing-fallback-to-tcp.resolver-tests-version-4.grpctestingexp.' \
-  --expected_addrs='1.2.3.4:443,False' \
-  --expected_chosen_service_config='{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooThree","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFour","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFive","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSix","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSeven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEight","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooNine","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTen","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEleven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]}]}' \
-  --expected_lb_policy='' \
-  --local_dns_server_address="127.0.0.1:$FLAGS_dns_server_port" &
-wait "$!" || EXIT_CODE=1
-
-kill -SIGTERM "$DNS_SERVER_PID" || true
-wait
-exit $EXIT_CODE
diff --git a/test/cpp/naming/resolver_component_tests_runner_invoker.cc b/test/cpp/naming/resolver_component_tests_runner_invoker.cc
index 6d0708e..45c1029 100644
--- a/test/cpp/naming/resolver_component_tests_runner_invoker.cc
+++ b/test/cpp/naming/resolver_component_tests_runner_invoker.cc
@@ -184,7 +184,7 @@
     std::string const bin_dir = my_bin.substr(0, my_bin.rfind('/'));
     // Invoke the .sh and .py scripts directly where they are in source code.
     grpc::testing::InvokeResolverComponentTestsRunner(
-        "test/cpp/naming/resolver_component_tests_runner.sh",
+        "test/cpp/naming/resolver_component_tests_runner.py",
         bin_dir + "/" + FLAGS_test_bin_name,
         "test/cpp/naming/utils/dns_server.py",
         "test/cpp/naming/resolver_test_record_groups.yaml",
diff --git a/test/cpp/naming/utils/dns_resolver.py b/test/cpp/naming/utils/dns_resolver.py
index 6b27244..74f4ca2 100755
--- a/test/cpp/naming/utils/dns_resolver.py
+++ b/test/cpp/naming/utils/dns_resolver.py
@@ -16,9 +16,12 @@
 """Makes DNS queries for A records to specified servers"""
 
 import argparse
-import signal
+import threading
+import time
 import twisted.internet.task as task
 import twisted.names.client as client
+import twisted.internet.reactor as reactor
+
 
 def main():
   argp = argparse.ArgumentParser(description='Make DNS queries for A records')
@@ -31,7 +34,6 @@
   argp.add_argument('-t', '--timeout', default=1, type=int,
                     help=('Force process exit after this number of seconds.'))
   args = argp.parse_args()
-  signal.alarm(args.timeout)
   def OnResolverResultAvailable(result):
     answers, authority, additional = result
     for a in answers:
diff --git a/test/cpp/naming/utils/dns_server.py b/test/cpp/naming/utils/dns_server.py
index 9f42f65..1e8e2e3 100755
--- a/test/cpp/naming/utils/dns_server.py
+++ b/test/cpp/naming/utils/dns_server.py
@@ -20,6 +20,8 @@
 import yaml
 import signal
 import os
+import threading
+import time
 
 import twisted
 import twisted.internet
@@ -33,6 +35,7 @@
 import twisted.names.server
 from twisted.names import client, server, common, authority, dns
 import argparse
+import platform
 
 _SERVER_HEALTH_CHECK_RECORD_NAME = 'health-check-local-dns-server-is-alive.resolver-tests.grpctestingexp' # missing end '.' for twisted syntax
 _SERVER_HEALTH_CHECK_RECORD_DATA = '123.123.123.123'
@@ -115,6 +118,18 @@
   sys.stdout.flush()
   sys.exit(0)
 
+def flush_stdout_loop():
+  num_timeouts_so_far = 0
+  sleep_time = 1
+  # Prevent zombies. Tests that use this server are short-lived.
+  max_timeouts = 60 * 2
+  while num_timeouts_so_far < max_timeouts:
+    sys.stdout.flush()
+    time.sleep(sleep_time)
+    num_timeouts_so_far += 1
+  print('Process timeout reached, or cancelled. Exitting 0.')
+  os.kill(os.getpid(), signal.SIGTERM)
+
 def main():
   argp = argparse.ArgumentParser(description='Local DNS Server for resolver tests')
   argp.add_argument('-p', '--port', default=None, type=int,
@@ -123,11 +138,11 @@
                     help=('Directory of resolver_test_record_groups.yaml file. '
                           'Defauls to path needed when the test is invoked as part of run_tests.py.'))
   args = argp.parse_args()
-  signal.signal(signal.SIGALRM, _quit_on_signal)
   signal.signal(signal.SIGTERM, _quit_on_signal)
   signal.signal(signal.SIGINT, _quit_on_signal)
-  # Prevent zombies. Tests that use this server are short-lived.
-  signal.alarm(2 * 60)
+  output_flush_thread = threading.Thread(target=flush_stdout_loop)
+  output_flush_thread.setDaemon(True)
+  output_flush_thread.start()
   start_local_dns_server(args)
 
 if __name__ == '__main__':
diff --git a/test/cpp/naming/utils/tcp_connect.py b/test/cpp/naming/utils/tcp_connect.py
index bf7455e..5773c7c 100755
--- a/test/cpp/naming/utils/tcp_connect.py
+++ b/test/cpp/naming/utils/tcp_connect.py
@@ -16,8 +16,11 @@
 """Opens a TCP connection to a specified server and then exits."""
 
 import argparse
-import signal
 import socket
+import threading
+import time
+import sys
+
 
 def main():
   argp = argparse.ArgumentParser(description='Open a TCP handshake to a server')
@@ -28,7 +31,6 @@
   argp.add_argument('-t', '--timeout', default=1, type=int,
                     help='Force process exit after this number of seconds.')
   args = argp.parse_args()
-  signal.alarm(args.timeout)
   socket.create_connection([args.server_host, args.server_port])
 
 if __name__ == '__main__':
diff --git a/test/cpp/qps/gen_build_yaml.py b/test/cpp/qps/gen_build_yaml.py
index 1ef8f65..776283c 100755
--- a/test/cpp/qps/gen_build_yaml.py
+++ b/test/cpp/qps/gen_build_yaml.py
@@ -63,6 +63,11 @@
   return (scenario_json['num_clients'] * client +
           scenario_json['num_servers'] * server)
 
+def maybe_exclude_gcov(scenario_json):
+  if scenario_json['client_config']['client_channels'] > 100:
+    return ['gcov']
+  return []
+
 print yaml.dump({
   'tests': [
     {
@@ -76,7 +81,7 @@
       'boringssl': True,
       'defaults': 'boringssl',
       'cpu_cost': guess_cpu(scenario_json, False),
-      'exclude_configs': ['tsan', 'asan'],
+      'exclude_configs': ['tsan', 'asan'] + maybe_exclude_gcov(scenario_json),
       'timeout_seconds': 2*60,
       'excluded_poll_engines': scenario_json.get('EXCLUDED_POLL_ENGINES', []),
       'auto_timeout_scaling': False
diff --git a/test/cpp/server/BUILD b/test/cpp/server/BUILD
index 7538845..3f89d6e 100644
--- a/test/cpp/server/BUILD
+++ b/test/cpp/server/BUILD
@@ -32,6 +32,19 @@
 )
 
 grpc_cc_test(
+    name = "server_builder_with_socket_mutator_test",
+    srcs = ["server_builder_with_socket_mutator_test.cc"],
+    deps = [
+        "//:grpc++_unsecure",
+        "//src/proto/grpc/testing:echo_proto",
+        "//test/core/util:grpc_test_util_unsecure",
+    ],
+    external_deps = [
+        "gtest",
+    ],
+)
+
+grpc_cc_test(
     name = "server_request_call_test",
     srcs = ["server_request_call_test.cc"],
     deps = [
diff --git a/test/cpp/server/load_reporter/BUILD b/test/cpp/server/load_reporter/BUILD
new file mode 100644
index 0000000..5cb3a00
--- /dev/null
+++ b/test/cpp/server/load_reporter/BUILD
@@ -0,0 +1,31 @@
+# Copyright 2017 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+licenses(["notice"])  # Apache v2
+
+load("//bazel:grpc_build_system.bzl", "grpc_cc_test", "grpc_cc_library", "grpc_cc_binary", "grpc_package")
+
+grpc_package(name = "test/cpp/server/load_reporter")
+
+grpc_cc_test(
+    name = "lb_load_data_store_test",
+    srcs = ["load_data_store_test.cc"],
+    external_deps = [
+        "gtest",
+    ],
+    deps = [
+        "//:lb_load_data_store",
+        "//test/core/util:grpc_test_util",
+    ],
+)
diff --git a/test/cpp/server/load_reporter/load_data_store_test.cc b/test/cpp/server/load_reporter/load_data_store_test.cc
new file mode 100644
index 0000000..8280dee
--- /dev/null
+++ b/test/cpp/server/load_reporter/load_data_store_test.cc
@@ -0,0 +1,481 @@
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <grpc/impl/codegen/port_platform.h>
+
+#include <set>
+#include <vector>
+
+#include <grpc/grpc.h>
+#include <gtest/gtest.h>
+
+#include "src/cpp/server/load_reporter/load_data_store.h"
+#include "test/core/util/port.h"
+#include "test/core/util/test_config.h"
+
+namespace grpc {
+namespace testing {
+namespace {
+
+using ::grpc::load_reporter::CallMetricValue;
+using ::grpc::load_reporter::LoadDataStore;
+using ::grpc::load_reporter::LoadRecordKey;
+using ::grpc::load_reporter::LoadRecordValue;
+using ::grpc::load_reporter::PerBalancerStore;
+using ::grpc::load_reporter::kInvalidLbId;
+
+class LoadDataStoreTest : public ::testing::Test {
+ public:
+  LoadDataStoreTest()
+      : kKey1(kLbId1, kLbTag1, kUser1, kClientIp1),
+        kKey2(kLbId2, kLbTag2, kUser2, kClientIp2) {}
+
+  // Check whether per_balancer_stores contains a store which was originally
+  // created for <hostname, lb_id, and load_key>.
+  bool PerBalancerStoresContains(
+      const LoadDataStore& load_data_store,
+      const std::set<PerBalancerStore*>* per_balancer_stores,
+      const grpc::string hostname, const grpc::string lb_id,
+      const grpc::string load_key) {
+    auto original_per_balancer_store =
+        load_data_store.FindPerBalancerStore(hostname, lb_id);
+    EXPECT_NE(original_per_balancer_store, nullptr);
+    EXPECT_EQ(original_per_balancer_store->lb_id(), lb_id);
+    EXPECT_EQ(original_per_balancer_store->load_key(), load_key);
+    for (auto per_balancer_store : *per_balancer_stores) {
+      if (per_balancer_store == original_per_balancer_store) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  grpc::string FormatLbId(size_t index) {
+    return "kLbId" + std::to_string(index);
+  }
+
+  const grpc::string kHostname1 = "kHostname1";
+  const grpc::string kHostname2 = "kHostname2";
+  const grpc::string kLbId1 = "kLbId1";
+  const grpc::string kLbId2 = "kLbId2";
+  const grpc::string kLbId3 = "kLbId3";
+  const grpc::string kLbId4 = "kLbId4";
+  const grpc::string kLoadKey1 = "kLoadKey1";
+  const grpc::string kLoadKey2 = "kLoadKey2";
+  const grpc::string kLbTag1 = "kLbTag1";
+  const grpc::string kLbTag2 = "kLbTag2";
+  const grpc::string kUser1 = "kUser1";
+  const grpc::string kUser2 = "kUser2";
+  const grpc::string kClientIp1 = "00";
+  const grpc::string kClientIp2 = "02";
+  const grpc::string kMetric1 = "kMetric1";
+  const grpc::string kMetric2 = "kMetric2";
+  const LoadRecordKey kKey1;
+  const LoadRecordKey kKey2;
+};
+
+using PerBalancerStoreTest = LoadDataStoreTest;
+
+TEST_F(LoadDataStoreTest, AssignToSelf) {
+  LoadDataStore load_data_store;
+  load_data_store.ReportStreamCreated(kHostname1, kLbId1, kLoadKey1);
+  auto assigned_stores = load_data_store.GetAssignedStores(kHostname1, kLbId1);
+  EXPECT_TRUE(PerBalancerStoresContains(load_data_store, assigned_stores,
+                                        kHostname1, kLbId1, kLoadKey1));
+}
+
+TEST_F(LoadDataStoreTest, ReassignOrphanStores) {
+  LoadDataStore load_data_store;
+  load_data_store.ReportStreamCreated(kHostname1, kLbId1, kLoadKey1);
+  load_data_store.ReportStreamCreated(kHostname1, kLbId2, kLoadKey1);
+  load_data_store.ReportStreamCreated(kHostname1, kLbId3, kLoadKey2);
+  load_data_store.ReportStreamCreated(kHostname2, kLbId4, kLoadKey1);
+  // 1. Close the second stream.
+  load_data_store.ReportStreamClosed(kHostname1, kLbId2);
+  auto assigned_to_lb_id_1 =
+      load_data_store.GetAssignedStores(kHostname1, kLbId1);
+  // The orphaned store is re-assigned to kLbId1 with the same load key.
+  EXPECT_TRUE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_1,
+                                        kHostname1, kLbId1, kLoadKey1));
+  EXPECT_TRUE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_1,
+                                        kHostname1, kLbId2, kLoadKey1));
+  // 2. Close the first stream.
+  load_data_store.ReportStreamClosed(kHostname1, kLbId1);
+  auto assigned_to_lb_id_3 =
+      load_data_store.GetAssignedStores(kHostname1, kLbId3);
+  // The orphaned stores are re-assigned to kLbId3 with the same host,
+  // because there isn't any LB with the same load key.
+  EXPECT_TRUE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_3,
+                                        kHostname1, kLbId1, kLoadKey1));
+  EXPECT_TRUE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_3,
+                                        kHostname1, kLbId2, kLoadKey1));
+  EXPECT_TRUE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_3,
+                                        kHostname1, kLbId3, kLoadKey2));
+  // 3. Close the third stream.
+  load_data_store.ReportStreamClosed(kHostname1, kLbId3);
+  auto assigned_to_lb_id_4 =
+      load_data_store.GetAssignedStores(kHostname2, kLbId4);
+  // There is no active LB for the first host now. kLbId4 is active but
+  // it's for the second host, so it wll NOT adopt the orphaned stores.
+  EXPECT_FALSE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_4,
+                                         kHostname1, kLbId1, kLoadKey1));
+  EXPECT_FALSE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_4,
+                                         kHostname1, kLbId2, kLoadKey1));
+  EXPECT_FALSE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_4,
+                                         kHostname1, kLbId3, kLoadKey2));
+  EXPECT_TRUE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_4,
+                                        kHostname2, kLbId4, kLoadKey1));
+}
+
+TEST_F(LoadDataStoreTest, OrphanAssignmentIsSticky) {
+  LoadDataStore load_data_store;
+  std::set<grpc::string> active_lb_ids;
+  size_t num_lb_ids = 1000;
+  for (size_t i = 0; i < num_lb_ids; ++i) {
+    load_data_store.ReportStreamCreated(kHostname1, FormatLbId(i), kLoadKey1);
+    active_lb_ids.insert(FormatLbId(i));
+  }
+  grpc::string orphaned_lb_id = FormatLbId(std::rand() % num_lb_ids);
+  load_data_store.ReportStreamClosed(kHostname1, orphaned_lb_id);
+  active_lb_ids.erase(orphaned_lb_id);
+  // Find which LB is assigned the orphaned store.
+  grpc::string assigned_lb_id = "";
+  for (auto lb_id : active_lb_ids) {
+    if (PerBalancerStoresContains(
+            load_data_store,
+            load_data_store.GetAssignedStores(kHostname1, lb_id), kHostname1,
+            orphaned_lb_id, kLoadKey1)) {
+      assigned_lb_id = lb_id;
+      break;
+    }
+  }
+  EXPECT_STRNE(assigned_lb_id.c_str(), "");
+  // Close 10 more stream, skipping the assigned_lb_id. The assignment of
+  // orphaned_lb_id shouldn't change.
+  for (size_t _ = 0; _ < 10; ++_) {
+    grpc::string lb_id_to_close = "";
+    for (auto lb_id : active_lb_ids) {
+      if (lb_id != assigned_lb_id) {
+        lb_id_to_close = lb_id;
+        break;
+      }
+    }
+    EXPECT_STRNE(lb_id_to_close.c_str(), "");
+    load_data_store.ReportStreamClosed(kHostname1, lb_id_to_close);
+    active_lb_ids.erase(lb_id_to_close);
+    EXPECT_TRUE(PerBalancerStoresContains(
+        load_data_store,
+        load_data_store.GetAssignedStores(kHostname1, assigned_lb_id),
+        kHostname1, orphaned_lb_id, kLoadKey1));
+  }
+  // Close the assigned_lb_id, orphaned_lb_id will be re-assigned again.
+  load_data_store.ReportStreamClosed(kHostname1, assigned_lb_id);
+  active_lb_ids.erase(assigned_lb_id);
+  size_t orphaned_lb_id_occurences = 0;
+  for (auto lb_id : active_lb_ids) {
+    if (PerBalancerStoresContains(
+            load_data_store,
+            load_data_store.GetAssignedStores(kHostname1, lb_id), kHostname1,
+            orphaned_lb_id, kLoadKey1)) {
+      orphaned_lb_id_occurences++;
+    }
+  }
+  EXPECT_EQ(orphaned_lb_id_occurences, 1U);
+}
+
+TEST_F(LoadDataStoreTest, HostTemporarilyLoseAllStreams) {
+  LoadDataStore load_data_store;
+  load_data_store.ReportStreamCreated(kHostname1, kLbId1, kLoadKey1);
+  load_data_store.ReportStreamCreated(kHostname2, kLbId2, kLoadKey1);
+  auto store_lb_id_1 = load_data_store.FindPerBalancerStore(kHostname1, kLbId1);
+  auto store_invalid_lb_id_1 =
+      load_data_store.FindPerBalancerStore(kHostname1, kInvalidLbId);
+  EXPECT_FALSE(store_lb_id_1->IsSuspended());
+  EXPECT_FALSE(store_invalid_lb_id_1->IsSuspended());
+  // Disconnect all the streams of the first host.
+  load_data_store.ReportStreamClosed(kHostname1, kLbId1);
+  // All the streams of that host are suspended.
+  EXPECT_TRUE(store_lb_id_1->IsSuspended());
+  EXPECT_TRUE(store_invalid_lb_id_1->IsSuspended());
+  // Detailed load data won't be kept when the PerBalancerStore is suspended.
+  store_lb_id_1->MergeRow(kKey1, LoadRecordValue());
+  store_invalid_lb_id_1->MergeRow(kKey1, LoadRecordValue());
+  EXPECT_EQ(store_lb_id_1->load_record_map().size(), 0U);
+  EXPECT_EQ(store_invalid_lb_id_1->load_record_map().size(), 0U);
+  // The stores for different hosts won't mix, even if the load key is the same.
+  auto assigned_to_lb_id_2 =
+      load_data_store.GetAssignedStores(kHostname2, kLbId2);
+  EXPECT_EQ(assigned_to_lb_id_2->size(), 2U);
+  EXPECT_TRUE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_2,
+                                        kHostname2, kLbId2, kLoadKey1));
+  EXPECT_TRUE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_2,
+                                        kHostname2, kInvalidLbId, ""));
+  // A new stream is created for the first host.
+  load_data_store.ReportStreamCreated(kHostname1, kLbId3, kLoadKey2);
+  // The stores for the first host are resumed.
+  EXPECT_FALSE(store_lb_id_1->IsSuspended());
+  EXPECT_FALSE(store_invalid_lb_id_1->IsSuspended());
+  store_lb_id_1->MergeRow(kKey1, LoadRecordValue());
+  store_invalid_lb_id_1->MergeRow(kKey1, LoadRecordValue());
+  EXPECT_EQ(store_lb_id_1->load_record_map().size(), 1U);
+  EXPECT_EQ(store_invalid_lb_id_1->load_record_map().size(), 1U);
+  // The resumed stores are assigned to the new LB.
+  auto assigned_to_lb_id_3 =
+      load_data_store.GetAssignedStores(kHostname1, kLbId3);
+  EXPECT_EQ(assigned_to_lb_id_3->size(), 3U);
+  EXPECT_TRUE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_3,
+                                        kHostname1, kLbId1, kLoadKey1));
+  EXPECT_TRUE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_3,
+                                        kHostname1, kInvalidLbId, ""));
+  EXPECT_TRUE(PerBalancerStoresContains(load_data_store, assigned_to_lb_id_3,
+                                        kHostname1, kLbId3, kLoadKey2));
+}
+
+TEST_F(LoadDataStoreTest, OneStorePerLbId) {
+  LoadDataStore load_data_store;
+  EXPECT_EQ(load_data_store.FindPerBalancerStore(kHostname1, kLbId1), nullptr);
+  EXPECT_EQ(load_data_store.FindPerBalancerStore(kHostname1, kInvalidLbId),
+            nullptr);
+  EXPECT_EQ(load_data_store.FindPerBalancerStore(kHostname2, kLbId2), nullptr);
+  EXPECT_EQ(load_data_store.FindPerBalancerStore(kHostname2, kLbId3), nullptr);
+  // Create The first stream.
+  load_data_store.ReportStreamCreated(kHostname1, kLbId1, kLoadKey1);
+  auto store_lb_id_1 = load_data_store.FindPerBalancerStore(kHostname1, kLbId1);
+  auto store_invalid_lb_id_1 =
+      load_data_store.FindPerBalancerStore(kHostname1, kInvalidLbId);
+  // Two stores will be created: one is for the stream; the other one is for
+  // kInvalidLbId.
+  EXPECT_NE(store_lb_id_1, nullptr);
+  EXPECT_NE(store_invalid_lb_id_1, nullptr);
+  EXPECT_NE(store_lb_id_1, store_invalid_lb_id_1);
+  EXPECT_EQ(load_data_store.FindPerBalancerStore(kHostname2, kLbId2), nullptr);
+  EXPECT_EQ(load_data_store.FindPerBalancerStore(kHostname2, kLbId3), nullptr);
+  // Create the second stream.
+  load_data_store.ReportStreamCreated(kHostname2, kLbId3, kLoadKey1);
+  auto store_lb_id_3 = load_data_store.FindPerBalancerStore(kHostname2, kLbId3);
+  auto store_invalid_lb_id_2 =
+      load_data_store.FindPerBalancerStore(kHostname2, kInvalidLbId);
+  EXPECT_NE(store_lb_id_3, nullptr);
+  EXPECT_NE(store_invalid_lb_id_2, nullptr);
+  EXPECT_NE(store_lb_id_3, store_invalid_lb_id_2);
+  // The PerBalancerStores created for different hosts are independent.
+  EXPECT_NE(store_lb_id_3, store_invalid_lb_id_1);
+  EXPECT_NE(store_invalid_lb_id_2, store_invalid_lb_id_1);
+  EXPECT_EQ(load_data_store.FindPerBalancerStore(kHostname2, kLbId2), nullptr);
+}
+
+TEST_F(LoadDataStoreTest, ExactlyOnceAssignment) {
+  LoadDataStore load_data_store;
+  size_t num_create = 100;
+  size_t num_close = 50;
+  for (size_t i = 0; i < num_create; ++i) {
+    load_data_store.ReportStreamCreated(kHostname1, FormatLbId(i), kLoadKey1);
+  }
+  for (size_t i = 0; i < num_close; ++i) {
+    load_data_store.ReportStreamClosed(kHostname1, FormatLbId(i));
+  }
+  std::set<grpc::string> reported_lb_ids;
+  for (size_t i = num_close; i < num_create; ++i) {
+    for (auto assigned_store :
+         *load_data_store.GetAssignedStores(kHostname1, FormatLbId(i))) {
+      EXPECT_TRUE(reported_lb_ids.insert(assigned_store->lb_id()).second);
+    }
+  }
+  // Add one for kInvalidLbId.
+  EXPECT_EQ(reported_lb_ids.size(), (num_create + 1));
+  EXPECT_NE(reported_lb_ids.find(kInvalidLbId), reported_lb_ids.end());
+}
+
+TEST_F(LoadDataStoreTest, UnknownBalancerIdTracking) {
+  LoadDataStore load_data_store;
+  load_data_store.ReportStreamCreated(kHostname1, kLbId1, kLoadKey1);
+  // Merge data for a known LB ID.
+  LoadRecordValue v1(192);
+  load_data_store.MergeRow(kHostname1, kKey1, v1);
+  // Merge data for unknown LB ID.
+  LoadRecordValue v2(23);
+  EXPECT_FALSE(load_data_store.IsTrackedUnknownBalancerId(kLbId2));
+  load_data_store.MergeRow(
+      kHostname1, LoadRecordKey(kLbId2, kLbTag1, kUser1, kClientIp1), v2);
+  EXPECT_TRUE(load_data_store.IsTrackedUnknownBalancerId(kLbId2));
+  LoadRecordValue v3(952);
+  load_data_store.MergeRow(
+      kHostname2, LoadRecordKey(kLbId3, kLbTag1, kUser1, kClientIp1), v3);
+  EXPECT_TRUE(load_data_store.IsTrackedUnknownBalancerId(kLbId3));
+  // The data kept for a known LB ID is correct.
+  auto store_lb_id_1 = load_data_store.FindPerBalancerStore(kHostname1, kLbId1);
+  EXPECT_EQ(store_lb_id_1->load_record_map().size(), 1U);
+  EXPECT_EQ(store_lb_id_1->load_record_map().find(kKey1)->second.start_count(),
+            v1.start_count());
+  EXPECT_EQ(store_lb_id_1->GetNumCallsInProgressForReport(), v1.start_count());
+  // No PerBalancerStore created for Unknown LB ID.
+  EXPECT_EQ(load_data_store.FindPerBalancerStore(kHostname1, kLbId2), nullptr);
+  EXPECT_EQ(load_data_store.FindPerBalancerStore(kHostname2, kLbId3), nullptr);
+  // End all the started RPCs for kLbId1.
+  LoadRecordValue v4(0, v1.start_count());
+  load_data_store.MergeRow(kHostname1, kKey1, v4);
+  EXPECT_EQ(store_lb_id_1->load_record_map().size(), 1U);
+  EXPECT_EQ(store_lb_id_1->load_record_map().find(kKey1)->second.start_count(),
+            v1.start_count());
+  EXPECT_EQ(store_lb_id_1->load_record_map().find(kKey1)->second.ok_count(),
+            v4.ok_count());
+  EXPECT_EQ(store_lb_id_1->GetNumCallsInProgressForReport(), 0U);
+  EXPECT_FALSE(load_data_store.IsTrackedUnknownBalancerId(kLbId1));
+  // End all the started RPCs for kLbId2.
+  LoadRecordValue v5(0, v2.start_count());
+  load_data_store.MergeRow(
+      kHostname1, LoadRecordKey(kLbId2, kLbTag1, kUser1, kClientIp1), v5);
+  EXPECT_FALSE(load_data_store.IsTrackedUnknownBalancerId(kLbId2));
+  // End some of the started RPCs for kLbId3.
+  LoadRecordValue v6(0, v3.start_count() / 2);
+  load_data_store.MergeRow(
+      kHostname2, LoadRecordKey(kLbId3, kLbTag1, kUser1, kClientIp1), v6);
+  EXPECT_TRUE(load_data_store.IsTrackedUnknownBalancerId(kLbId3));
+}
+
+TEST_F(PerBalancerStoreTest, Suspend) {
+  PerBalancerStore per_balancer_store(kLbId1, kLoadKey1);
+  EXPECT_FALSE(per_balancer_store.IsSuspended());
+  // Suspend the store.
+  per_balancer_store.Suspend();
+  EXPECT_TRUE(per_balancer_store.IsSuspended());
+  EXPECT_EQ(0U, per_balancer_store.load_record_map().size());
+  // Data merged when the store is suspended won't be kept.
+  LoadRecordValue v1(139, 19);
+  per_balancer_store.MergeRow(kKey1, v1);
+  EXPECT_EQ(0U, per_balancer_store.load_record_map().size());
+  // Resume the store.
+  per_balancer_store.Resume();
+  EXPECT_FALSE(per_balancer_store.IsSuspended());
+  EXPECT_EQ(0U, per_balancer_store.load_record_map().size());
+  // Data merged after the store is resumed will be kept.
+  LoadRecordValue v2(23, 0, 51);
+  per_balancer_store.MergeRow(kKey1, v2);
+  EXPECT_EQ(1U, per_balancer_store.load_record_map().size());
+  // Suspend the store.
+  per_balancer_store.Suspend();
+  EXPECT_TRUE(per_balancer_store.IsSuspended());
+  EXPECT_EQ(0U, per_balancer_store.load_record_map().size());
+  // Data merged when the store is suspended won't be kept.
+  LoadRecordValue v3(62, 11);
+  per_balancer_store.MergeRow(kKey1, v3);
+  EXPECT_EQ(0U, per_balancer_store.load_record_map().size());
+  // Resume the store.
+  per_balancer_store.Resume();
+  EXPECT_FALSE(per_balancer_store.IsSuspended());
+  EXPECT_EQ(0U, per_balancer_store.load_record_map().size());
+  // Data merged after the store is resumed will be kept.
+  LoadRecordValue v4(225, 98);
+  per_balancer_store.MergeRow(kKey1, v4);
+  EXPECT_EQ(1U, per_balancer_store.load_record_map().size());
+  // In-progress count is always kept.
+  EXPECT_EQ(per_balancer_store.GetNumCallsInProgressForReport(),
+            v1.start_count() - v1.ok_count() + v2.start_count() -
+                v2.error_count() + v3.start_count() - v3.ok_count() +
+                v4.start_count() - v4.ok_count());
+}
+
+TEST_F(PerBalancerStoreTest, DataAggregation) {
+  PerBalancerStore per_balancer_store(kLbId1, kLoadKey1);
+  // Construct some Values.
+  LoadRecordValue v1(992, 34, 13, 234.0, 164.0, 173467.38);
+  v1.InsertCallMetric(kMetric1, CallMetricValue(3, 2773.2));
+  LoadRecordValue v2(4842, 213, 9, 393.0, 974.0, 1345.2398);
+  v2.InsertCallMetric(kMetric1, CallMetricValue(7, 25.234));
+  v2.InsertCallMetric(kMetric2, CallMetricValue(2, 387.08));
+  // v3 doesn't change the number of in-progress RPCs.
+  LoadRecordValue v3(293, 55, 293 - 55, 28764, 5284, 5772);
+  v3.InsertCallMetric(kMetric1, CallMetricValue(61, 3465.0));
+  v3.InsertCallMetric(kMetric2, CallMetricValue(13, 672.0));
+  // The initial state of the store.
+  uint64_t num_calls_in_progress = 0;
+  EXPECT_FALSE(per_balancer_store.IsNumCallsInProgressChangedSinceLastReport());
+  EXPECT_EQ(per_balancer_store.GetNumCallsInProgressForReport(),
+            num_calls_in_progress);
+  // Merge v1 and get report of the number of in-progress calls.
+  per_balancer_store.MergeRow(kKey1, v1);
+  EXPECT_TRUE(per_balancer_store.IsNumCallsInProgressChangedSinceLastReport());
+  EXPECT_EQ(per_balancer_store.GetNumCallsInProgressForReport(),
+            num_calls_in_progress +=
+            (v1.start_count() - v1.ok_count() - v1.error_count()));
+  EXPECT_FALSE(per_balancer_store.IsNumCallsInProgressChangedSinceLastReport());
+  // Merge v2 and get report of the number of in-progress calls.
+  per_balancer_store.MergeRow(kKey2, v2);
+  EXPECT_TRUE(per_balancer_store.IsNumCallsInProgressChangedSinceLastReport());
+  EXPECT_EQ(per_balancer_store.GetNumCallsInProgressForReport(),
+            num_calls_in_progress +=
+            (v2.start_count() - v2.ok_count() - v2.error_count()));
+  EXPECT_FALSE(per_balancer_store.IsNumCallsInProgressChangedSinceLastReport());
+  // Merge v3 and get report of the number of in-progress calls.
+  per_balancer_store.MergeRow(kKey1, v3);
+  EXPECT_FALSE(per_balancer_store.IsNumCallsInProgressChangedSinceLastReport());
+  EXPECT_EQ(per_balancer_store.GetNumCallsInProgressForReport(),
+            num_calls_in_progress);
+  // LoadRecordValue for kKey1 is aggregated correctly.
+  LoadRecordValue value_for_key1 =
+      per_balancer_store.load_record_map().find(kKey1)->second;
+  EXPECT_EQ(value_for_key1.start_count(), v1.start_count() + v3.start_count());
+  EXPECT_EQ(value_for_key1.ok_count(), v1.ok_count() + v3.ok_count());
+  EXPECT_EQ(value_for_key1.error_count(), v1.error_count() + v3.error_count());
+  EXPECT_EQ(value_for_key1.bytes_sent(), v1.bytes_sent() + v3.bytes_sent());
+  EXPECT_EQ(value_for_key1.bytes_recv(), v1.bytes_recv() + v3.bytes_recv());
+  EXPECT_EQ(value_for_key1.latency_ms(), v1.latency_ms() + v3.latency_ms());
+  EXPECT_EQ(value_for_key1.call_metrics().size(), 2U);
+  EXPECT_EQ(value_for_key1.call_metrics().find(kMetric1)->second.num_calls(),
+            v1.call_metrics().find(kMetric1)->second.num_calls() +
+                v3.call_metrics().find(kMetric1)->second.num_calls());
+  EXPECT_EQ(
+      value_for_key1.call_metrics().find(kMetric1)->second.total_metric_value(),
+      v1.call_metrics().find(kMetric1)->second.total_metric_value() +
+          v3.call_metrics().find(kMetric1)->second.total_metric_value());
+  EXPECT_EQ(value_for_key1.call_metrics().find(kMetric2)->second.num_calls(),
+            v3.call_metrics().find(kMetric2)->second.num_calls());
+  EXPECT_EQ(
+      value_for_key1.call_metrics().find(kMetric2)->second.total_metric_value(),
+      v3.call_metrics().find(kMetric2)->second.total_metric_value());
+  // LoadRecordValue for kKey2 is aggregated (trivially) correctly.
+  LoadRecordValue value_for_key2 =
+      per_balancer_store.load_record_map().find(kKey2)->second;
+  EXPECT_EQ(value_for_key2.start_count(), v2.start_count());
+  EXPECT_EQ(value_for_key2.ok_count(), v2.ok_count());
+  EXPECT_EQ(value_for_key2.error_count(), v2.error_count());
+  EXPECT_EQ(value_for_key2.bytes_sent(), v2.bytes_sent());
+  EXPECT_EQ(value_for_key2.bytes_recv(), v2.bytes_recv());
+  EXPECT_EQ(value_for_key2.latency_ms(), v2.latency_ms());
+  EXPECT_EQ(value_for_key2.call_metrics().size(), 2U);
+  EXPECT_EQ(value_for_key2.call_metrics().find(kMetric1)->second.num_calls(),
+            v2.call_metrics().find(kMetric1)->second.num_calls());
+  EXPECT_EQ(
+      value_for_key2.call_metrics().find(kMetric1)->second.total_metric_value(),
+      v2.call_metrics().find(kMetric1)->second.total_metric_value());
+  EXPECT_EQ(value_for_key2.call_metrics().find(kMetric2)->second.num_calls(),
+            v2.call_metrics().find(kMetric2)->second.num_calls());
+  EXPECT_EQ(
+      value_for_key2.call_metrics().find(kMetric2)->second.total_metric_value(),
+      v2.call_metrics().find(kMetric2)->second.total_metric_value());
+}
+
+}  // namespace
+}  // namespace testing
+}  // namespace grpc
+
+int main(int argc, char** argv) {
+  grpc_test_init(argc, argv);
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/test/cpp/server/server_builder_with_socket_mutator_test.cc b/test/cpp/server/server_builder_with_socket_mutator_test.cc
new file mode 100644
index 0000000..5c7dd69
--- /dev/null
+++ b/test/cpp/server/server_builder_with_socket_mutator_test.cc
@@ -0,0 +1,116 @@
+/*
+ *
+ * Copyright 2017 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <grpcpp/impl/codegen/config.h>
+#include <gtest/gtest.h>
+
+#include <grpcpp/server.h>
+#include <grpcpp/server_builder.h>
+
+#include <grpc/grpc.h>
+#include <memory>
+
+#include "src/core/lib/iomgr/socket_mutator.h"
+#include "src/proto/grpc/testing/echo.grpc.pb.h"
+#include "test/core/util/port.h"
+
+/* This test does a sanity check that grpc_socket_mutator's
+ * are used by servers. It's meant to protect code and end-to-end
+ * tests that rely on this functionality but which live outside
+ * of the grpc github repo. */
+
+namespace grpc {
+namespace {
+
+bool mock_socket_mutator_mutate_fd(int, grpc_socket_mutator*);
+int mock_socket_mutator_compare(grpc_socket_mutator*, grpc_socket_mutator*);
+void mock_socket_mutator_destroy(grpc_socket_mutator*);
+
+const grpc_socket_mutator_vtable mock_socket_mutator_vtable = {
+    mock_socket_mutator_mutate_fd,
+    mock_socket_mutator_compare,
+    mock_socket_mutator_destroy,
+};
+
+class MockSocketMutator : public grpc_socket_mutator {
+ public:
+  MockSocketMutator() : mutate_fd_call_count_(0) {
+    grpc_socket_mutator_init(this, &mock_socket_mutator_vtable);
+  }
+  int mutate_fd_call_count_;
+};
+
+bool mock_socket_mutator_mutate_fd(int fd, grpc_socket_mutator* m) {
+  MockSocketMutator* s = reinterpret_cast<MockSocketMutator*>(m);
+  s->mutate_fd_call_count_++;
+  return true;
+}
+
+int mock_socket_mutator_compare(grpc_socket_mutator* a,
+                                grpc_socket_mutator* b) {
+  return (uintptr_t)a - (uintptr_t)b;
+}
+
+void mock_socket_mutator_destroy(grpc_socket_mutator* m) {
+  MockSocketMutator* s = reinterpret_cast<MockSocketMutator*>(m);
+  delete s;
+}
+
+class MockSocketMutatorServerBuilderOption : public grpc::ServerBuilderOption {
+ public:
+  MockSocketMutatorServerBuilderOption(MockSocketMutator* mock_socket_mutator)
+      : mock_socket_mutator_(mock_socket_mutator) {}
+
+  void UpdateArguments(ChannelArguments* args) override {
+    args->SetSocketMutator(mock_socket_mutator_);
+  }
+
+  void UpdatePlugins(
+      std::vector<std::unique_ptr<ServerBuilderPlugin>>*) override{};
+
+  MockSocketMutator* mock_socket_mutator_;
+};
+
+TEST(ServerBuilderWithSocketMutatorTest, CreateServerWithSocketMutator) {
+  auto address = "localhost:" + std::to_string(grpc_pick_unused_port_or_die());
+  auto mock_socket_mutator = new MockSocketMutator();
+  std::unique_ptr<grpc::ServerBuilderOption> mock_socket_mutator_builder_option(
+      new MockSocketMutatorServerBuilderOption(mock_socket_mutator));
+  testing::EchoTestService::Service echo_service;
+  EXPECT_EQ(mock_socket_mutator->mutate_fd_call_count_, 0);
+  ServerBuilder builder;
+  builder.RegisterService(&echo_service);
+  builder.AddListeningPort(address, InsecureServerCredentials());
+  builder.SetOption(std::move(mock_socket_mutator_builder_option));
+  std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
+  EXPECT_NE(server, nullptr);
+  // Only assert that the socket mutator was used.
+  EXPECT_GE(mock_socket_mutator->mutate_fd_call_count_, 1);
+  server->Shutdown();
+}
+
+}  // namespace
+}  // namespace grpc
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  grpc_init();
+  int ret = RUN_ALL_TESTS();
+  grpc_shutdown();
+  return ret;
+}
diff --git a/test/distrib/python/test_packages.sh b/test/distrib/python/test_packages.sh
index e16eddd..6bf49d4 100755
--- a/test/distrib/python/test_packages.sh
+++ b/test/distrib/python/test_packages.sh
@@ -22,12 +22,14 @@
 if [[ "$1" == "binary" ]]
 then
   echo "Testing Python binary distribution"
-  ARCHIVES=("$EXTERNAL_GIT_ROOT"/input_artifacts/grpcio-*.whl)
-  TOOLS_ARCHIVES=("$EXTERNAL_GIT_ROOT"/input_artifacts/grpcio_tools-*.whl)
+  ARCHIVES=("$EXTERNAL_GIT_ROOT"/input_artifacts/grpcio-[0-9]*.whl)
+  TOOLS_ARCHIVES=("$EXTERNAL_GIT_ROOT"/input_artifacts/grpcio_tools-[0-9]*.whl)
 else
   echo "Testing Python source distribution"
-  ARCHIVES=("$EXTERNAL_GIT_ROOT"/input_artifacts/grpcio-*.tar.gz)
-  TOOLS_ARCHIVES=("$EXTERNAL_GIT_ROOT"/input_artifacts/grpcio_tools-*.tar.gz)
+  ARCHIVES=("$EXTERNAL_GIT_ROOT"/input_artifacts/grpcio-[0-9]*.tar.gz)
+  TOOLS_ARCHIVES=("$EXTERNAL_GIT_ROOT"/input_artifacts/grpcio-tools-[0-9]*.tar.gz)
+  HEALTH_ARCHIVES=("$EXTERNAL_GIT_ROOT"/input_artifacts/grpcio-health-checking-[0-9]*.tar.gz)
+  REFLECTION_ARCHIVES=("$EXTERNAL_GIT_ROOT"/input_artifacts/grpcio-reflection-[0-9]*.tar.gz)
 fi
 
 VIRTUAL_ENV=$(mktemp -d)
@@ -52,6 +54,14 @@
 at_least_one_installs "${ARCHIVES[@]}"
 at_least_one_installs "${TOOLS_ARCHIVES[@]}"
 
+if [[ "$1" == "source" ]]
+then
+  echo "Testing Python health and reflection packages"
+  at_least_one_installs "${HEALTH_ARCHIVES[@]}"
+  at_least_one_installs "${REFLECTION_ARCHIVES[@]}"
+fi
+
+
 #
 # Test our distributions
 #
diff --git a/third_party/address_sorting/address_sorting.c b/third_party/address_sorting/address_sorting.c
index d62aca3..e4f3b53 100644
--- a/third_party/address_sorting/address_sorting.c
+++ b/third_party/address_sorting/address_sorting.c
@@ -225,15 +225,15 @@
 static int compare_source_dest_scope_matches(
     const address_sorting_sortable* first,
     const address_sorting_sortable* second) {
-  int first_src_dst_scope_matches = 0;
+  bool first_src_dst_scope_matches = false;
   if (sockaddr_get_scope(&first->dest_addr) ==
       sockaddr_get_scope(&first->source_addr)) {
-    first_src_dst_scope_matches = 1;
+    first_src_dst_scope_matches = true;
   }
-  int second_src_dst_scope_matches = 0;
+  bool second_src_dst_scope_matches = false;
   if (sockaddr_get_scope(&second->dest_addr) ==
       sockaddr_get_scope(&second->source_addr)) {
-    second_src_dst_scope_matches = 1;
+    second_src_dst_scope_matches = true;
   }
   if (first_src_dst_scope_matches != second_src_dst_scope_matches) {
     return first_src_dst_scope_matches ? -1 : 1;
@@ -244,18 +244,18 @@
 static int compare_source_dest_labels_match(
     const address_sorting_sortable* first,
     const address_sorting_sortable* second) {
-  int first_label_matches = 0;
+  bool first_label_matches = false;
   if (get_label_value(&first->dest_addr) ==
       get_label_value(&first->source_addr)) {
-    first_label_matches = 1;
+    first_label_matches = true;
   }
-  int second_label_matches = 0;
+  bool second_label_matches = false;
   if (get_label_value(&second->dest_addr) ==
       get_label_value(&second->source_addr)) {
-    second_label_matches = 1;
+    second_label_matches = true;
   }
   if (first_label_matches != second_label_matches) {
-    return first_label_matches ? 1 : 1;
+    return first_label_matches ? -1 : 1;
   }
   return 0;
 }
diff --git a/tools/codegen/core/gen_stats_data.py b/tools/codegen/core/gen_stats_data.py
index 5c9d9e5..3ebd00d 100755
--- a/tools/codegen/core/gen_stats_data.py
+++ b/tools/codegen/core/gen_stats_data.py
@@ -230,13 +230,11 @@
     print >> H, "#ifndef GRPC_CORE_LIB_DEBUG_STATS_DATA_H"
     print >> H, "#define GRPC_CORE_LIB_DEBUG_STATS_DATA_H"
     print >> H
+    print >> H, "#include <grpc/support/port_platform.h>"
+    print >> H
     print >> H, "#include <inttypes.h>"
     print >> H, "#include \"src/core/lib/iomgr/exec_ctx.h\""
     print >> H
-    print >> H, "#ifdef __cplusplus"
-    print >> H, "extern \"C\" {"
-    print >> H, "#endif"
-    print >> H
 
     for typename, instances in sorted(inst_map.items()):
         print >> H, "typedef enum {"
@@ -290,10 +288,6 @@
         inst_map['Histogram'])
 
     print >> H
-    print >> H, "#ifdef __cplusplus"
-    print >> H, "}"
-    print >> H, "#endif"
-    print >> H
     print >> H, "#endif /* GRPC_CORE_LIB_DEBUG_STATS_DATA_H */"
 
 with open('src/core/lib/debug/stats_data.cc', 'w') as C:
@@ -316,10 +310,13 @@
         [C],
         ["Automatically generated by tools/codegen/core/gen_stats_data.py"])
 
-    print >> C, "#include \"src/core/lib/debug/stats_data.h\""
+    print >> C, "#include <grpc/support/port_platform.h>"
+    print >> C
     print >> C, "#include \"src/core/lib/debug/stats.h\""
+    print >> C, "#include \"src/core/lib/debug/stats_data.h\""
+    print >> C, "#include \"src/core/lib/gpr/useful.h\""
     print >> C, "#include \"src/core/lib/iomgr/exec_ctx.h\""
-    print >> C, "#include <grpc/support/useful.h>"
+    print >> C
 
     histo_code = []
     for histogram in inst_map['Histogram']:
diff --git a/tools/distrib/check_copyright.py b/tools/distrib/check_copyright.py
index 66acc73..09eecf4 100755
--- a/tools/distrib/check_copyright.py
+++ b/tools/distrib/check_copyright.py
@@ -98,7 +98,7 @@
     'src/android/test/interop/gradlew.bat',
 ))
 
-RE_YEAR = r'Copyright (?P<first_year>[0-9]+\-)?(?P<last_year>[0-9]+) gRPC authors.'
+RE_YEAR = r'Copyright (?P<first_year>[0-9]+\-)?(?P<last_year>[0-9]+) ([Tt]he )?gRPC [Aa]uthors(\.|)'
 RE_LICENSE = dict(
     (k, r'\n'.join(LICENSE_PREFIX[k] +
                    (RE_YEAR if re.search(RE_YEAR, line) else re.escape(line))
diff --git a/tools/distrib/python/grpcio_tools/grpc_version.py b/tools/distrib/python/grpcio_tools/grpc_version.py
index e71f836..f0367e2 100644
--- a/tools/distrib/python/grpcio_tools/grpc_version.py
+++ b/tools/distrib/python/grpcio_tools/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/tools/distrib/python/grpcio_tools/grpc_version.py.template`!!!
 
-VERSION = '1.12.0.dev0'
+VERSION = '1.13.0.dev0'
diff --git a/tools/distrib/yapf_code.sh b/tools/distrib/yapf_code.sh
index dbc842f..27c5e31 100755
--- a/tools/distrib/yapf_code.sh
+++ b/tools/distrib/yapf_code.sh
@@ -30,11 +30,11 @@
 
 VIRTUALENV=yapf_virtual_environment
 
-virtualenv $VIRTUALENV
-PYTHON=$(realpath "${VIRTUALENV}/bin/python")
-$PYTHON -m pip install --upgrade pip==10.0.1
-$PYTHON -m pip install --upgrade futures
-$PYTHON -m pip install yapf==0.20.0
+python -m virtualenv $VIRTUALENV
+PYTHON=${VIRTUALENV}/bin/python
+"$PYTHON" -m pip install --upgrade pip==10.0.1
+"$PYTHON" -m pip install --upgrade futures
+"$PYTHON" -m pip install yapf==0.20.0
 
 yapf() {
     local exclusion exclusion_args=()
diff --git a/tools/dockerfile/distribtest/python_dev_arch_x64/Dockerfile b/tools/dockerfile/distribtest/python_dev_arch_x64/Dockerfile
new file mode 100644
index 0000000..7f09fd6
--- /dev/null
+++ b/tools/dockerfile/distribtest/python_dev_arch_x64/Dockerfile
@@ -0,0 +1,22 @@
+# Copyright 2015 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FROM base/archlinux
+
+RUN pacman --noconfirm -Syy
+RUN pacman --noconfirm -S openssl
+RUN pacman --noconfirm -S python2
+RUN pacman --noconfirm -S python2-pip
+RUN pip2 install virtualenv
+RUN pacman --noconfirm -S base-devel
diff --git a/tools/dockerfile/distribtest/python_dev_centos7_x64/Dockerfile b/tools/dockerfile/distribtest/python_dev_centos7_x64/Dockerfile
new file mode 100644
index 0000000..954146c
--- /dev/null
+++ b/tools/dockerfile/distribtest/python_dev_centos7_x64/Dockerfile
@@ -0,0 +1,22 @@
+# Copyright 2015 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FROM centos:7
+
+RUN yum install -y python
+RUN yum install -y epel-release
+RUN yum install -y python-pip
+RUN pip install virtualenv
+RUN yum groupinstall -y 'Development Tools'
+RUN yum install -y python-devel
diff --git a/tools/dockerfile/distribtest/python_dev_fedora22_x64/Dockerfile b/tools/dockerfile/distribtest/python_dev_fedora22_x64/Dockerfile
new file mode 100644
index 0000000..d86ad37
--- /dev/null
+++ b/tools/dockerfile/distribtest/python_dev_fedora22_x64/Dockerfile
@@ -0,0 +1,23 @@
+# Copyright 2015 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FROM fedora:22
+
+RUN yum clean all && yum update -y && yum install -y python python-pip
+RUN pip install virtualenv
+
+RUN yum groupinstall -y "Development Tools"
+RUN yum install -y redhat-rpm-config
+RUN yum install -y gcc-c++
+RUN yum install -y python2-devel
diff --git a/tools/dockerfile/distribtest/python_dev_fedora23_x64/Dockerfile b/tools/dockerfile/distribtest/python_dev_fedora23_x64/Dockerfile
new file mode 100644
index 0000000..0dbf5e4
--- /dev/null
+++ b/tools/dockerfile/distribtest/python_dev_fedora23_x64/Dockerfile
@@ -0,0 +1,23 @@
+# Copyright 2015 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FROM fedora:23
+
+RUN yum clean all && yum update -y && yum install -y python python-pip
+RUN pip install virtualenv
+
+RUN yum groupinstall -y "Development Tools"
+RUN yum install -y redhat-rpm-config
+RUN yum install -y gcc-c++
+RUN yum install -y python2-devel
diff --git a/tools/dockerfile/distribtest/python_dev_jessie_x64/Dockerfile b/tools/dockerfile/distribtest/python_dev_jessie_x64/Dockerfile
new file mode 100644
index 0000000..c222898
--- /dev/null
+++ b/tools/dockerfile/distribtest/python_dev_jessie_x64/Dockerfile
@@ -0,0 +1,21 @@
+# Copyright 2015 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FROM debian:jessie
+
+RUN apt-get update && apt-get install -y python python-pip
+RUN pip install virtualenv
+
+RUN apt-get install -y build-essential
+RUN apt-get install -y python-dev
diff --git a/tools/dockerfile/distribtest/python_dev_jessie_x86/Dockerfile b/tools/dockerfile/distribtest/python_dev_jessie_x86/Dockerfile
new file mode 100644
index 0000000..5e0b8ef
--- /dev/null
+++ b/tools/dockerfile/distribtest/python_dev_jessie_x86/Dockerfile
@@ -0,0 +1,27 @@
+# Copyright 2015 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FROM 32bit/debian:jessie
+
+RUN apt-get update && apt-get install -y python python-pip
+
+RUN pip install virtualenv
+
+RUN apt-get install -y build-essential
+RUN apt-get install -y python-dev
+
+# docker is running on a 64-bit machine, so we need to
+# override "uname -m" to report i686 instead of x86_64, otherwise
+# python will choose a wrong binary package to install.
+ENTRYPOINT ["linux32"]
diff --git a/tools/dockerfile/distribtest/python_dev_ubuntu1404_x64/Dockerfile b/tools/dockerfile/distribtest/python_dev_ubuntu1404_x64/Dockerfile
new file mode 100644
index 0000000..6c842ae
--- /dev/null
+++ b/tools/dockerfile/distribtest/python_dev_ubuntu1404_x64/Dockerfile
@@ -0,0 +1,22 @@
+# Copyright 2015 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FROM ubuntu:14.04
+
+RUN apt-get update -y && apt-get install -y python python-pip
+
+RUN apt-get install -y build-essential
+RUN apt-get install -y python-dev
+
+RUN pip install virtualenv
diff --git a/tools/dockerfile/distribtest/python_dev_ubuntu1604_x64/Dockerfile b/tools/dockerfile/distribtest/python_dev_ubuntu1604_x64/Dockerfile
new file mode 100644
index 0000000..1ff1e0a
--- /dev/null
+++ b/tools/dockerfile/distribtest/python_dev_ubuntu1604_x64/Dockerfile
@@ -0,0 +1,22 @@
+# Copyright 2015 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FROM ubuntu:16.04
+
+RUN apt-get update -y && apt-get install -y python python-pip
+
+RUN apt-get install -y build-essential
+RUN apt-get install -y python-dev
+
+RUN pip install virtualenv
diff --git a/tools/dockerfile/grpc_artifact_android_ndk/Dockerfile b/tools/dockerfile/grpc_artifact_android_ndk/Dockerfile
new file mode 100644
index 0000000..77b6acf
--- /dev/null
+++ b/tools/dockerfile/grpc_artifact_android_ndk/Dockerfile
@@ -0,0 +1,68 @@
+# Copyright 2016 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Docker file for building gRPC artifacts.
+
+# Recent enough cmake (>=3.9) needed by Android SDK
+FROM debian:sid
+
+RUN apt-get update && apt-get install -y debian-keyring && apt-key update
+
+# Install Git and basic packages.
+RUN apt-get update && apt-key update && apt-get install -y \
+  autoconf \
+  autotools-dev \
+  build-essential \
+  bzip2 \
+  clang \
+  curl \
+  gcc \
+  gcc-multilib \
+  git \
+  golang \
+  libc6 \
+  libc6-dbg \
+  libc6-dev \
+  libgtest-dev \
+  libtool \
+  make \
+  perl \
+  strace \
+  python-dev \
+  python-setuptools \
+  python-yaml \
+  telnet \
+  unzip \
+  wget \
+  zip && apt-get clean
+
+# Cmake for cross-compilation
+RUN apt-get update && apt-get install -y cmake golang && apt-get clean
+
+##################
+# Android NDK
+
+# Download and install Android NDK
+RUN wget -q https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip -O android_ndk.zip \
+    && unzip -q android_ndk.zip \
+    && rm android_ndk.zip \
+    && mv ./android-ndk-r16b /opt
+ENV ANDROID_NDK_PATH /opt/android-ndk-r16b
+
+RUN apt-get update && apt-get install -y libpthread-stubs0-dev && apt-get clean
+
+RUN mkdir /var/local/jenkins
+
+# Define the default command.
+CMD ["bash"]
diff --git a/tools/dockerfile/interoptest/grpc_interop_node/build_interop.sh b/tools/dockerfile/interoptest/grpc_interop_node/build_interop.sh
index 21fdd0b..c16efc1 100755
--- a/tools/dockerfile/interoptest/grpc_interop_node/build_interop.sh
+++ b/tools/dockerfile/interoptest/grpc_interop_node/build_interop.sh
@@ -17,23 +17,12 @@
 set -e
 
 mkdir -p /var/local/git
-
 git clone /var/local/jenkins/grpc-node /var/local/git/grpc-node
 # clone gRPC submodules, use data from locally cloned submodules where possible
 (cd /var/local/jenkins/grpc-node/ && git submodule foreach 'cd /var/local/git/grpc-node \
 && git submodule update --init --recursive --reference /var/local/jenkins/grpc-node/${name} \
 ${name}')
 
-# Use the pending c-core changes if possible
-if [ -d "/var/local/jenkins/grpc" ]; then
-  cd /var/local/jenkins/grpc
-  CURRENT_COMMIT="$(git rev-parse --verify HEAD)"
-  cd /var/local/git/grpc-node/packages/grpc-native-core/deps/grpc/
-  git fetch --tags --progress https://github.com/grpc/grpc.git +refs/pull/*:refs/remotes/origin/pr/*
-  git checkout $CURRENT_COMMIT
-  git submodule update --init --recursive --reference /var/local/jenkins/grpc
-fi
-
 # copy service account keys if available
 cp -r /var/local/jenkins/service_account $HOME || true
 
diff --git a/tools/dockerfile/interoptest/grpc_interop_python/Dockerfile b/tools/dockerfile/interoptest/grpc_interop_python/Dockerfile
index 97cdf48..94290f3 100644
--- a/tools/dockerfile/interoptest/grpc_interop_python/Dockerfile
+++ b/tools/dockerfile/interoptest/grpc_interop_python/Dockerfile
@@ -64,6 +64,10 @@
 RUN pip install virtualenv
 RUN pip install futures==2.2.0 enum34==1.0.4 protobuf==3.5.2.post1 six==1.10.0 twisted==17.5.0
 
+# Install pip and virtualenv for Python 3.4
+RUN curl https://bootstrap.pypa.io/get-pip.py | python3.4
+RUN python3.4 -m pip install virtualenv
+
 # Prepare ccache
 RUN ln -s /usr/bin/ccache /usr/local/bin/gcc
 RUN ln -s /usr/bin/ccache /usr/local/bin/g++
diff --git a/tools/dockerfile/test/multilang_jessie_x64/Dockerfile b/tools/dockerfile/test/multilang_jessie_x64/Dockerfile
index 962fe97..e22484f 100644
--- a/tools/dockerfile/test/multilang_jessie_x64/Dockerfile
+++ b/tools/dockerfile/test/multilang_jessie_x64/Dockerfile
@@ -144,6 +144,10 @@
 RUN pip install virtualenv
 RUN pip install futures==2.2.0 enum34==1.0.4 protobuf==3.5.2.post1 six==1.10.0 twisted==17.5.0
 
+# Install pip and virtualenv for Python 3.4
+RUN curl https://bootstrap.pypa.io/get-pip.py | python3.4
+RUN python3.4 -m pip install virtualenv
+
 # Install coverage for Python test coverage reporting
 RUN pip install coverage
 ENV PATH ~/.local/bin:$PATH
diff --git a/tools/dockerfile/test/python_jessie_x64/Dockerfile b/tools/dockerfile/test/python_jessie_x64/Dockerfile
index 41b670c..bd9d55e 100644
--- a/tools/dockerfile/test/python_jessie_x64/Dockerfile
+++ b/tools/dockerfile/test/python_jessie_x64/Dockerfile
@@ -68,6 +68,10 @@
 RUN pip install virtualenv
 RUN pip install futures==2.2.0 enum34==1.0.4 protobuf==3.5.2.post1 six==1.10.0 twisted==17.5.0
 
+# Install pip and virtualenv for Python 3.4
+RUN curl https://bootstrap.pypa.io/get-pip.py | python3.4
+RUN python3.4 -m pip install virtualenv
+
 # Prepare ccache
 RUN ln -s /usr/bin/ccache /usr/local/bin/gcc
 RUN ln -s /usr/bin/ccache /usr/local/bin/g++
diff --git a/tools/dockerfile/test/python_pyenv_x64/Dockerfile b/tools/dockerfile/test/python_pyenv_x64/Dockerfile
index 24fe7b2..6d72a1c 100644
--- a/tools/dockerfile/test/python_pyenv_x64/Dockerfile
+++ b/tools/dockerfile/test/python_pyenv_x64/Dockerfile
@@ -91,6 +91,10 @@
 RUN pyenv install pypy-5.3.1
 RUN pyenv local 3.5-dev 3.6-dev pypy-5.3.1
 
+# Install pip and virtualenv for Python 3.4
+RUN curl https://bootstrap.pypa.io/get-pip.py | python3.4
+RUN python3.4 -m pip install virtualenv
+
 # Prepare ccache
 RUN ln -s /usr/bin/ccache /usr/local/bin/gcc
 RUN ln -s /usr/bin/ccache /usr/local/bin/g++
diff --git a/tools/doxygen/Doxyfile.c++ b/tools/doxygen/Doxyfile.c++
index 22f225e..450ff95 100644
--- a/tools/doxygen/Doxyfile.c++
+++ b/tools/doxygen/Doxyfile.c++
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.12.0-dev
+PROJECT_NUMBER         = 1.13.0-dev
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
@@ -777,6 +777,7 @@
 doc/environment_variables.md \
 doc/epoll-polling-engine.md \
 doc/fail_fast.md \
+doc/fork_support.md \
 doc/g_stands_for.md \
 doc/health-checking.md \
 doc/http-grpc-status-mapping.md \
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index 63d238d..7c6b75d 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.12.0-dev
+PROJECT_NUMBER         = 1.13.0-dev
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
@@ -777,6 +777,7 @@
 doc/environment_variables.md \
 doc/epoll-polling-engine.md \
 doc/fail_fast.md \
+doc/fork_support.md \
 doc/g_stands_for.md \
 doc/health-checking.md \
 doc/http-grpc-status-mapping.md \
diff --git a/tools/doxygen/Doxyfile.core b/tools/doxygen/Doxyfile.core
index 04f9d78..c47d36c 100644
--- a/tools/doxygen/Doxyfile.core
+++ b/tools/doxygen/Doxyfile.core
@@ -779,6 +779,7 @@
 doc/environment_variables.md \
 doc/epoll-polling-engine.md \
 doc/fail_fast.md \
+doc/fork_support.md \
 doc/g_stands_for.md \
 doc/health-checking.md \
 doc/http-grpc-status-mapping.md \
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index b5869a6..2d15412 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -779,6 +779,7 @@
 doc/environment_variables.md \
 doc/epoll-polling-engine.md \
 doc/fail_fast.md \
+doc/fork_support.md \
 doc/g_stands_for.md \
 doc/health-checking.md \
 doc/http-grpc-status-mapping.md \
@@ -885,6 +886,7 @@
 src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.cc \
 src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h \
 src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc \
+src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h \
 src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h \
 src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel_secure.cc \
 src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc \
@@ -895,7 +897,6 @@
 src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/load_balancer.pb.h \
 src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc \
 src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc \
-src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc \
 src/core/ext/filters/client_channel/lb_policy/subchannel_list.h \
 src/core/ext/filters/client_channel/lb_policy_factory.cc \
 src/core/ext/filters/client_channel/lb_policy_factory.h \
@@ -1496,8 +1497,6 @@
 src/core/tsi/ssl_types.h \
 src/core/tsi/transport_security.cc \
 src/core/tsi/transport_security.h \
-src/core/tsi/transport_security_adapter.cc \
-src/core/tsi/transport_security_adapter.h \
 src/core/tsi/transport_security_grpc.cc \
 src/core/tsi/transport_security_grpc.h \
 src/core/tsi/transport_security_interface.h \
diff --git a/tools/fuzzer/runners/alts_credentials_fuzzer.sh b/tools/fuzzer/runners/alts_credentials_fuzzer.sh
new file mode 100644
index 0000000..3aaff6b
--- /dev/null
+++ b/tools/fuzzer/runners/alts_credentials_fuzzer.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+# Copyright 2016 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+flags="-max_total_time=$runtime -artifact_prefix=fuzzer_output/ -max_len=2048 -timeout=120"
+
+
+if [ "$jobs" != "1" ]
+then
+  flags="-jobs=$jobs -workers=$jobs $flags"
+fi
+
+if [ "$config" == "asan-trace-cmp" ]
+then
+  flags="-use_traces=1 $flags"
+fi
+
+bins/$config/alts_credentials_fuzzer $flags fuzzer_output test/core/security/corpus/alts_credentials_corpus
diff --git a/tools/gce/create_windows_debug_worker.sh b/tools/gce/create_windows_debug_worker.sh
index 6f903b5..3625df8 100755
--- a/tools/gce/create_windows_debug_worker.sh
+++ b/tools/gce/create_windows_debug_worker.sh
@@ -44,12 +44,13 @@
 echo 'Created scratch disk, waiting for it to become available.'
 sleep 15
 
+# The image version might need updating.
 gcloud compute instances create "$INSTANCE_NAME" \
     --project="$CLOUD_PROJECT" \
     --zone "$ZONE" \
     --machine-type "$MACHINE_TYPE" \
     --image-project google.com:kokoro \
-    --image kokoro-win7build-v9-prod-debug \
+    --image kokoro-win7build-v11-prod-debug \
     --boot-disk-size 500 \
     --boot-disk-type pd-ssd \
     --tags=allow-ssh \
diff --git a/tools/internal_ci/helper_scripts/prepare_build_macos_rc b/tools/internal_ci/helper_scripts/prepare_build_macos_rc
index d84bebd..d2b7769 100644
--- a/tools/internal_ci/helper_scripts/prepare_build_macos_rc
+++ b/tools/internal_ci/helper_scripts/prepare_build_macos_rc
@@ -69,6 +69,11 @@
 # set xcode version for Obj-C tests
 sudo xcode-select -switch /Applications/Xcode_9.2.app/Contents/Developer/
 
+# Disable some unwanted dotnet options
+export NUGET_XMLDOC_MODE=skip
+export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true
+export DOTNET_CLI_TELEMETRY_OPTOUT=true
+
 # TODO(jtattermusch): better debugging of clock skew, remove once not needed
 date
 
diff --git a/tools/internal_ci/helper_scripts/prepare_build_windows.bat b/tools/internal_ci/helper_scripts/prepare_build_windows.bat
index 9fa6860..f987f8a 100644
--- a/tools/internal_ci/helper_scripts/prepare_build_windows.bat
+++ b/tools/internal_ci/helper_scripts/prepare_build_windows.bat
@@ -34,4 +34,9 @@
 @rem Needed for big_query_utils
 python -m pip install google-api-python-client
 
+@rem Disable some unwanted dotnet options
+set NUGET_XMLDOC_MODE=skip
+set DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true
+set DOTNET_CLI_TELEMETRY_OPTOUT=true
+
 git submodule update --init
diff --git a/tools/internal_ci/linux/grpc_asan_on_foundry.sh b/tools/internal_ci/linux/grpc_asan_on_foundry.sh
index 2aebb65..791c56c 100644
--- a/tools/internal_ci/linux/grpc_asan_on_foundry.sh
+++ b/tools/internal_ci/linux/grpc_asan_on_foundry.sh
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+export UPLOAD_TEST_RESULTS=true
 EXTRA_FLAGS="--copt=-gmlt --strip=never --copt=-fsanitize=address --linkopt=-fsanitize=address --test_timeout=3600"
 github/grpc/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh "${EXTRA_FLAGS}"
 
diff --git a/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh b/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh
index 3102992..7881e3a 100755
--- a/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh
+++ b/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh
@@ -22,8 +22,8 @@
 cp ${KOKORO_GFILE_DIR}/GrpcTesting-d0eeee2db331.json ${KOKORO_KEYSTORE_DIR}/4321_grpc-testing-service
 
 temp_dir=$(mktemp -d)
-ln -f "${KOKORO_GFILE_DIR}/bazel-canary" ${temp_dir}/bazel
-chmod 755 "${KOKORO_GFILE_DIR}/bazel-canary"
+ln -f "${KOKORO_GFILE_DIR}/bazel-release-0.12.0" ${temp_dir}/bazel
+chmod 755 "${KOKORO_GFILE_DIR}/bazel-release-0.12.0"
 export PATH="${temp_dir}:${PATH}"
 # This should show ${temp_dir}/bazel
 which bazel
@@ -53,4 +53,16 @@
   --crosstool_top=@com_github_bazelbuild_bazeltoolchains//configs/debian8_clang/0.3.0/bazel_0.10.0:toolchain \
   --define GRPC_PORT_ISOLATED_RUNTIME=1 \
   $1 \
-  -- //test/...
+  -- //test/... || FAILED="true"
+
+if [ "$UPLOAD_TEST_RESULTS" != "" ]
+then
+  # Sleep to let ResultStore finish writing results before querying
+  sleep 60
+  python ./tools/run_tests/python_utils/upload_rbe_results.py
+fi
+
+if [ "$FAILED" != "" ]
+then
+  exit 1
+fi
diff --git a/tools/internal_ci/linux/grpc_bazel_on_foundry_dbg.sh b/tools/internal_ci/linux/grpc_bazel_on_foundry_dbg.sh
index 882e4df..192d9d1 100644
--- a/tools/internal_ci/linux/grpc_bazel_on_foundry_dbg.sh
+++ b/tools/internal_ci/linux/grpc_bazel_on_foundry_dbg.sh
@@ -13,6 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-EXTRA_FLAGS="-c dbg --test_timeout=300,450,1200,3600 --runs_per_test_detects_flakes --runs_per_test=1"
+export UPLOAD_TEST_RESULTS=true
+EXTRA_FLAGS="-c dbg --test_timeout=300,450,1200,3600 --runs_per_test_detects_flakes --runs_per_test=2"
 github/grpc/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh "${EXTRA_FLAGS}"
-
diff --git a/tools/internal_ci/linux/grpc_bazel_on_foundry_opt.sh b/tools/internal_ci/linux/grpc_bazel_on_foundry_opt.sh
index 85f6953..6fb3c77 100644
--- a/tools/internal_ci/linux/grpc_bazel_on_foundry_opt.sh
+++ b/tools/internal_ci/linux/grpc_bazel_on_foundry_opt.sh
@@ -13,5 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-EXTRA_FLAGS="-c opt --test_timeout=300,450,1200,3600 --runs_per_test_detects_flakes --runs_per_test=1"
+export UPLOAD_TEST_RESULTS=true
+EXTRA_FLAGS="-c opt --test_timeout=300,450,1200,3600 --runs_per_test_detects_flakes --runs_per_test=2"
 github/grpc/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh "${EXTRA_FLAGS}"
diff --git a/tools/internal_ci/linux/grpc_msan_on_foundry.sh b/tools/internal_ci/linux/grpc_msan_on_foundry.sh
index 6858d97..5e64479 100644
--- a/tools/internal_ci/linux/grpc_msan_on_foundry.sh
+++ b/tools/internal_ci/linux/grpc_msan_on_foundry.sh
@@ -23,8 +23,8 @@
 cp ${KOKORO_GFILE_DIR}/GrpcTesting-d0eeee2db331.json ${KOKORO_KEYSTORE_DIR}/4321_grpc-testing-service
 
 temp_dir=$(mktemp -d)
-ln -f "${KOKORO_GFILE_DIR}/bazel-canary" ${temp_dir}/bazel
-chmod 755 "${KOKORO_GFILE_DIR}/bazel-canary"
+ln -f "${KOKORO_GFILE_DIR}/bazel-release-0.12.0" ${temp_dir}/bazel
+chmod 755 "${KOKORO_GFILE_DIR}/bazel-release-0.12.0"
 export PATH="${temp_dir}:${PATH}"
 # This should show ${temp_dir}/bazel
 which bazel
@@ -61,4 +61,13 @@
   --action_env=LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH \
   --host_crosstool_top=@com_github_bazelbuild_bazeltoolchains//configs/debian8_clang/0.3.0/bazel_0.10.0:toolchain \
   --crosstool_top=@com_github_bazelbuild_bazeltoolchains//configs/experimental/debian8_clang/0.3.0/bazel_0.10.0/msan:msan_experimental_toolchain \
-  -- //test/...
+  -- //test/... || FAILED="true"
+
+# Sleep to let ResultStore finish writing results before querying
+sleep 60
+python ./tools/run_tests/python_utils/upload_rbe_results.py
+
+if [ "$FAILED" != "" ]
+then
+  exit 1
+fi
diff --git a/tools/internal_ci/linux/grpc_tsan_on_foundry.sh b/tools/internal_ci/linux/grpc_tsan_on_foundry.sh
index aacb1ad..fafa1ce 100644
--- a/tools/internal_ci/linux/grpc_tsan_on_foundry.sh
+++ b/tools/internal_ci/linux/grpc_tsan_on_foundry.sh
@@ -13,6 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+export UPLOAD_TEST_RESULTS=true
 EXTRA_FLAGS="--copt=-gmlt --strip=never --copt=-fsanitize=thread --linkopt=-fsanitize=thread --test_timeout=3600"
 github/grpc/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh "${EXTRA_FLAGS}"
-
diff --git a/tools/internal_ci/linux/grpc_ubsan_on_foundry.sh b/tools/internal_ci/linux/grpc_ubsan_on_foundry.sh
index 0f0c12d..0f71ff2 100644
--- a/tools/internal_ci/linux/grpc_ubsan_on_foundry.sh
+++ b/tools/internal_ci/linux/grpc_ubsan_on_foundry.sh
@@ -23,8 +23,8 @@
 cp ${KOKORO_GFILE_DIR}/GrpcTesting-d0eeee2db331.json ${KOKORO_KEYSTORE_DIR}/4321_grpc-testing-service
 
 temp_dir=$(mktemp -d)
-ln -f "${KOKORO_GFILE_DIR}/bazel-canary" ${temp_dir}/bazel
-chmod 755 "${KOKORO_GFILE_DIR}/bazel-canary"
+ln -f "${KOKORO_GFILE_DIR}/bazel-release-0.12.0" ${temp_dir}/bazel
+chmod 755 "${KOKORO_GFILE_DIR}/bazel-release-0.12.0"
 export PATH="${temp_dir}:${PATH}"
 # This should show ${temp_dir}/bazel
 which bazel
@@ -57,4 +57,13 @@
   --copt=-fsanitize=undefined \
   --linkopt=-fsanitize=undefined \
   --crosstool_top=@com_github_bazelbuild_bazeltoolchains//configs/experimental/debian8_clang/0.3.0/bazel_0.10.0/ubsan:ubsan_experimental_toolchain \
-  -- //test/...
+  -- //test/... || FAILED="true"
+
+# Sleep to let ResultStore finish writing results before querying
+sleep 60
+python ./tools/run_tests/python_utils/upload_rbe_results.py
+
+if [ "$FAILED" != "" ]
+then
+  exit 1
+fi
diff --git a/tools/internal_ci/linux/pull_request/grpc_bazel_on_foundry_dbg.sh b/tools/internal_ci/linux/pull_request/grpc_bazel_on_foundry_dbg.sh
index 3ff569d..8e2aaeb 100644
--- a/tools/internal_ci/linux/pull_request/grpc_bazel_on_foundry_dbg.sh
+++ b/tools/internal_ci/linux/pull_request/grpc_bazel_on_foundry_dbg.sh
@@ -15,4 +15,3 @@
 
 EXTRA_FLAGS="-c dbg --test_timeout=300,450,1200,3600"
 github/grpc/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh "${EXTRA_FLAGS}"
-
diff --git a/tools/profiling/microbenchmarks/bm_diff/bm_constants.py b/tools/profiling/microbenchmarks/bm_diff/bm_constants.py
index 5719e42..c8b6c1e 100644
--- a/tools/profiling/microbenchmarks/bm_diff/bm_constants.py
+++ b/tools/profiling/microbenchmarks/bm_diff/bm_constants.py
@@ -22,11 +22,10 @@
     'bm_metadata', 'bm_fullstack_trickle'
 ]
 
-_INTERESTING = ('cpu_time', 'real_time', 'call_initial_size-median',
-                'locks_per_iteration', 'allocs_per_iteration',
-                'writes_per_iteration', 'atm_cas_per_iteration',
-                'atm_add_per_iteration', 'nows_per_iteration',
-                'cli_transport_stalls_per_iteration',
+_INTERESTING = ('cpu_time', 'real_time', 'locks_per_iteration',
+                'allocs_per_iteration', 'writes_per_iteration',
+                'atm_cas_per_iteration', 'atm_add_per_iteration',
+                'nows_per_iteration', 'cli_transport_stalls_per_iteration',
                 'cli_stream_stalls_per_iteration',
                 'svr_transport_stalls_per_iteration',
                 'svr_stream_stalls_per_iteration',
diff --git a/tools/run_tests/artifacts/artifact_targets.py b/tools/run_tests/artifacts/artifact_targets.py
index daa163e..e4d9e6b 100644
--- a/tools/run_tests/artifacts/artifact_targets.py
+++ b/tools/run_tests/artifacts/artifact_targets.py
@@ -222,7 +222,13 @@
         return []
 
     def build_jobspec(self):
-        if self.platform == 'windows':
+        if self.arch == 'android':
+            return create_docker_jobspec(
+                self.name,
+                'tools/dockerfile/grpc_artifact_android_ndk',
+                'tools/run_tests/artifacts/build_artifact_csharp_android.sh',
+                environ={})
+        elif self.platform == 'windows':
             cmake_arch_option = 'Win32' if self.arch == 'x86' else self.arch
             return create_jobspec(
                 self.name, [
@@ -342,6 +348,7 @@
         for Cls in (CSharpExtArtifact, ProtocArtifact)
         for platform in ('linux', 'macos', 'windows') for arch in ('x86', 'x64')
     ] + [
+        CSharpExtArtifact('linux', 'android'),
         PythonArtifact('linux', 'x86', 'cp27-cp27m'),
         PythonArtifact('linux', 'x86', 'cp27-cp27mu'),
         PythonArtifact('linux', 'x86', 'cp34-cp34m'),
diff --git a/tools/run_tests/artifacts/build_artifact_csharp_android.sh b/tools/run_tests/artifacts/build_artifact_csharp_android.sh
new file mode 100755
index 0000000..ba598e7
--- /dev/null
+++ b/tools/run_tests/artifacts/build_artifact_csharp_android.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+# Copyright 2016 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -ex
+
+cd "$(dirname "$0")/../../.."
+
+src/csharp/experimental/build_native_ext_for_android.sh
+
+mkdir -p "${ARTIFACTS_OUT}"
+cp cmake/build/libgrpc_csharp_ext.so "${ARTIFACTS_OUT}"
diff --git a/tools/run_tests/artifacts/build_artifact_python.sh b/tools/run_tests/artifacts/build_artifact_python.sh
index 9ea0f05..cd794a1 100755
--- a/tools/run_tests/artifacts/build_artifact_python.sh
+++ b/tools/run_tests/artifacts/build_artifact_python.sh
@@ -17,7 +17,6 @@
 
 cd "$(dirname "$0")/../../.."
 
-export GRPC_PYTHON_USE_CUSTOM_BDIST=0
 export GRPC_PYTHON_BUILD_WITH_CYTHON=1
 export PYTHON=${PYTHON:-python}
 export PIP=${PIP:-pip}
diff --git a/tools/run_tests/artifacts/distribtest_targets.py b/tools/run_tests/artifacts/distribtest_targets.py
index 9087841..80adc20 100644
--- a/tools/run_tests/artifacts/distribtest_targets.py
+++ b/tools/run_tests/artifacts/distribtest_targets.py
@@ -146,8 +146,12 @@
 class PythonDistribTest(object):
     """Tests Python package"""
 
-    def __init__(self, platform, arch, docker_suffix):
-        self.name = 'python_%s_%s_%s' % (platform, arch, docker_suffix)
+    def __init__(self, platform, arch, docker_suffix, source=False):
+        self.source = source
+        if source:
+            self.name = 'python_dev_%s_%s_%s' % (platform, arch, docker_suffix)
+        else:
+            self.name = 'python_%s_%s_%s' % (platform, arch, docker_suffix)
         self.platform = platform
         self.arch = arch
         self.docker_suffix = docker_suffix
@@ -160,12 +164,20 @@
         if not self.platform == 'linux':
             raise Exception("Not supported yet.")
 
-        return create_docker_jobspec(
-            self.name,
-            'tools/dockerfile/distribtest/python_%s_%s' % (self.docker_suffix,
-                                                           self.arch),
-            'test/distrib/python/run_binary_distrib_test.sh',
-            copy_rel_path='test/distrib')
+        if self.source:
+            return create_docker_jobspec(
+                self.name,
+                'tools/dockerfile/distribtest/python_dev_%s_%s' %
+                (self.docker_suffix, self.arch),
+                'test/distrib/python/run_source_distrib_test.sh',
+                copy_rel_path='test/distrib')
+        else:
+            return create_docker_jobspec(
+                self.name,
+                'tools/dockerfile/distribtest/python_%s_%s' %
+                (self.docker_suffix, self.arch),
+                'test/distrib/python/run_binary_distrib_test.sh',
+                copy_rel_path='test/distrib')
 
     def __str__(self):
         return self.name
@@ -315,6 +327,14 @@
         PythonDistribTest('linux', 'x64', 'ubuntu1204'),
         PythonDistribTest('linux', 'x64', 'ubuntu1404'),
         PythonDistribTest('linux', 'x64', 'ubuntu1604'),
+        PythonDistribTest('linux', 'x64', 'jessie', source=True),
+        PythonDistribTest('linux', 'x86', 'jessie', source=True),
+        PythonDistribTest('linux', 'x64', 'centos7', source=True),
+        PythonDistribTest('linux', 'x64', 'fedora22', source=True),
+        PythonDistribTest('linux', 'x64', 'fedora23', source=True),
+        PythonDistribTest('linux', 'x64', 'arch', source=True),
+        PythonDistribTest('linux', 'x64', 'ubuntu1404', source=True),
+        PythonDistribTest('linux', 'x64', 'ubuntu1604', source=True),
         RubyDistribTest('linux', 'x64', 'wheezy'),
         RubyDistribTest('linux', 'x64', 'jessie'),
         RubyDistribTest('linux', 'x86', 'jessie'),
diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json
index 41722e0..4f0fc1d 100644
--- a/tools/run_tests/generated/sources_and_headers.json
+++ b/tools/run_tests/generated/sources_and_headers.json
@@ -60,6 +60,23 @@
     "headers": [], 
     "is_filegroup": false, 
     "language": "c", 
+    "name": "alts_credentials_fuzzer", 
+    "src": [
+      "test/core/security/alts_credentials_fuzzer.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "gpr_test_util", 
+      "grpc", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c", 
     "name": "api_fuzzer", 
     "src": [
       "test/core/end2end/fuzzers/api_fuzzer.cc"
@@ -3909,6 +3926,26 @@
       "gpr_test_util", 
       "grpc", 
       "grpc++", 
+      "grpc++_test_util", 
+      "grpc_test_util", 
+      "lb_load_data_store"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "lb_load_data_store_test", 
+    "src": [
+      "test/cpp/server/load_reporter/load_data_store_test.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "gpr_test_util", 
+      "grpc", 
+      "grpc++", 
       "grpc++_test", 
       "grpc_test_util"
     ], 
@@ -4358,6 +4395,32 @@
     "deps": [
       "gpr", 
       "gpr_test_util", 
+      "grpc++_test_util_unsecure", 
+      "grpc++_unsecure", 
+      "grpc_test_util_unsecure", 
+      "grpc_unsecure"
+    ], 
+    "headers": [
+      "src/proto/grpc/testing/echo.grpc.pb.h", 
+      "src/proto/grpc/testing/echo.pb.h", 
+      "src/proto/grpc/testing/echo_messages.grpc.pb.h", 
+      "src/proto/grpc/testing/echo_messages.pb.h", 
+      "src/proto/grpc/testing/echo_messages_mock.grpc.pb.h", 
+      "src/proto/grpc/testing/echo_mock.grpc.pb.h"
+    ], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "server_builder_with_socket_mutator_test", 
+    "src": [
+      "test/cpp/server/server_builder_with_socket_mutator_test.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "gpr_test_util", 
       "grpc", 
       "grpc++", 
       "grpc++_test", 
@@ -6439,6 +6502,24 @@
     "headers": [], 
     "is_filegroup": false, 
     "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "src": [
+      "test/core/security/alts_credentials_fuzzer.cc", 
+      "test/core/util/one_corpus_entry_fuzzer.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "gpr_test_util", 
+      "grpc", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c", 
     "name": "api_fuzzer_one_entry", 
     "src": [
       "test/core/end2end/fuzzers/api_fuzzer.cc", 
@@ -7469,6 +7550,23 @@
   }, 
   {
     "deps": [
+      "grpc++"
+    ], 
+    "headers": [
+      "src/cpp/server/load_reporter/load_data_store.h"
+    ], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "lb_load_data_store", 
+    "src": [
+      "src/cpp/server/load_reporter/load_data_store.cc", 
+      "src/cpp/server/load_reporter/load_data_store.h"
+    ], 
+    "third_party": false, 
+    "type": "lib"
+  }, 
+  {
+    "deps": [
       "grpc", 
       "grpc++", 
       "grpc++_core_stats", 
@@ -9757,6 +9855,7 @@
     ], 
     "headers": [
       "src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h", 
+      "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h", 
@@ -9769,6 +9868,7 @@
       "src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.cc", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc", 
+      "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.cc", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc", 
@@ -9792,6 +9892,7 @@
     ], 
     "headers": [
       "src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h", 
+      "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h", 
@@ -9804,6 +9905,7 @@
       "src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.cc", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc", 
+      "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel_secure.cc", 
       "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc", 
@@ -9863,7 +9965,6 @@
     "language": "c", 
     "name": "grpc_lb_subchannel_list", 
     "src": [
-      "src/core/ext/filters/client_channel/lb_policy/subchannel_list.cc", 
       "src/core/ext/filters/client_channel/lb_policy/subchannel_list.h"
     ], 
     "third_party": false, 
@@ -9990,6 +10091,7 @@
     ], 
     "headers": [
       "include/grpc/grpc_security.h", 
+      "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h", 
       "src/core/lib/security/context/security_context.h", 
       "src/core/lib/security/credentials/alts/alts_credentials.h", 
       "src/core/lib/security/credentials/composite/composite_credentials.h", 
@@ -10017,6 +10119,7 @@
     "name": "grpc_secure", 
     "src": [
       "include/grpc/grpc_security.h", 
+      "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h", 
       "src/core/lib/http/httpcli_security_connector.cc", 
       "src/core/lib/security/context/security_context.cc", 
       "src/core/lib/security/context/security_context.h", 
@@ -10123,6 +10226,7 @@
       "test/core/end2end/fixtures/proxy.h", 
       "test/core/iomgr/endpoint_tests.h", 
       "test/core/util/debugger_macros.h", 
+      "test/core/util/fuzzer_util.h", 
       "test/core/util/grpc_profiler.h", 
       "test/core/util/histogram.h", 
       "test/core/util/memory_counters.h", 
@@ -10152,6 +10256,8 @@
       "test/core/iomgr/endpoint_tests.h", 
       "test/core/util/debugger_macros.cc", 
       "test/core/util/debugger_macros.h", 
+      "test/core/util/fuzzer_util.cc", 
+      "test/core/util/fuzzer_util.h", 
       "test/core/util/grpc_profiler.cc", 
       "test/core/util/grpc_profiler.h", 
       "test/core/util/histogram.cc", 
@@ -10596,7 +10702,6 @@
     ], 
     "headers": [
       "src/core/tsi/transport_security.h", 
-      "src/core/tsi/transport_security_adapter.h", 
       "src/core/tsi/transport_security_interface.h"
     ], 
     "is_filegroup": true, 
@@ -10605,8 +10710,6 @@
     "src": [
       "src/core/tsi/transport_security.cc", 
       "src/core/tsi/transport_security.h", 
-      "src/core/tsi/transport_security_adapter.cc", 
-      "src/core/tsi/transport_security_adapter.h", 
       "src/core/tsi/transport_security_interface.h"
     ], 
     "third_party": false, 
diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json
index 067d9b3..701f44f 100644
--- a/tools/run_tests/generated/tests.json
+++ b/tools/run_tests/generated/tests.json
@@ -4404,6 +4404,30 @@
     "flaky": false, 
     "gtest": true, 
     "language": "c++", 
+    "name": "lb_load_data_store_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "uses_polling": true
+  }, 
+  {
+    "args": [], 
+    "benchmark": false, 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "gtest": true, 
+    "language": "c++", 
     "name": "memory_test", 
     "platforms": [
       "linux", 
@@ -4749,6 +4773,24 @@
     "args": [], 
     "benchmark": false, 
     "ci_platforms": [
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "gtest": true, 
+    "language": "c++", 
+    "name": "server_builder_with_socket_mutator_test", 
+    "platforms": [
+      "posix"
+    ], 
+    "uses_polling": true
+  }, 
+  {
+    "args": [], 
+    "benchmark": false, 
+    "ci_platforms": [
       "linux", 
       "mac", 
       "posix", 
@@ -58002,7 +58044,8 @@
     "defaults": "boringssl", 
     "exclude_configs": [
       "tsan", 
-      "asan"
+      "asan", 
+      "gcov"
     ], 
     "excluded_poll_engines": [], 
     "flaky": false, 
@@ -63820,6 +63863,4836 @@
   }, 
   {
     "args": [
+      "test/core/security/corpus/alts_credentials_corpus/0149b46b88d583e05be0fb1423d10f2a14d36c48"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/047fc351e73f760d329d5a8845944720be9ce773"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/04ef96c66d8222d1a2c07e6b2a6548e6a527042b"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/05a7e16c1d7f92111f43e9c777421879920e79a4"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/063eb46f202fdfe7935c30ca38d7eb81c82db419"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/064773597c295fa871c184fc12d17b6de8aab31b"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/087449740758b114d16790067707934479946bd6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/0a5d068feb57a2782c6eba57b637abe8668ac82f"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/0b81e6d89bf7df80e87e5ee7c49f7cc1431f77e8"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/11409339cec708a5e353893101bfe76364337d5c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/147696a264cd6f197adb7c68aff834c30b1b77f8"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/160e5cac38c5c9e919ed6e4fbafee76907d63044"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/173d02167db431040b0540d98f6fc5e8b456587d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/18a3fe239806b3c7d1af24bcd2bd23aeeb072d5c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/195abd83b2e9d32b1b5b854fe33da44b6db40880"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/19af2509c7d84334b9ec64de4767a07d5294fd72"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/1b9864b948fcf08b062fd4401ef55b214c259535"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/1edddfa67de854d7faaba41418fda845e9c6a89d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/20031bb00e6608e1b570aa96e6afb9de06d42167"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/22b4c7ce7db99b0df63c9eae9265de484b695922"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/27416437ad287bd3cc1c5efdecebc39f20df73c1"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/27e8cd785c2b9346f68dba75761b52fbabaf2b72"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/28236f860d3d8e5ea11176746cb4c1c5c4f1f6c0"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/29e15b492c5a409938092a30c003c5c34df7e283"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/2a47864d77749aa042b772895dbdf46f608ccc6d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/2cca5cb1b135c35f6e5e1ec4c37deb9e12d37dc0"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/2df27b6c42dbaee382a29a87338d64ee87354acb"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/2e9ddd1339d8e599cef658a08965985c4f45e428"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/31a2d12a84a7a56ace831a9668d6ab4847390679"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/33cb9ec0ce3538ed6079b5fcb127649a5d05955b"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/348d9ae6eebb2e1644addf7f07231d108cf6f3b8"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/359f76f3c802292e92b0640de2bfe051e780a3b6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/35a479988e965a6e3e75138b64b0bd1f45073e2f"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/362b00d713686ff70cb0199f3d7d0058e5a1a27a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/3849c1625071791ceae709b9c6c705b28d099d67"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/39ef03c66ee2d4bcfb6c8da50486dcd40f02fb12"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/3a3ca061863499ebc171a4f910fa1b49523baad4"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/3a890f3fd01b048ac9db65a9a9b4f4443268b91a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/3b9554038a425bd1fae057ba41f9366bb467e946"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/3ce0ae4aa226f205a3a4e66bbb253419d9d754bf"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/3dccc5523986c37e27684659bba8a1037e7a92e8"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/3e0908c15b1cede4541d25f388b1345e8641e221"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/3fcb181ff6a8c8e2ba38ed34cf78f7482eb55cb7"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/41c9b5f720eb8f8fa04c840375a881781a849b43"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/4257a018f08f13a3a9adc848ef808e1be50bc4cf"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/42dfc5c4d13261b7259e65cd692df9c9d607194e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/43144664aedb585d45d42aa5249ddbfe81afe470"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/43e5ad495a47593b17dbcbd3e70c2e25a417bb6e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/446614e45b7bef49118b17e031c48faf167ebe3e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/46492477fa84ca88e85df914801af0b09b0939f6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/47157f83b166b57e0052c98a65c6db864fa6cb9b"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/473fc9b6d768a925527d3ad805ca363d490dc741"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/483c0b4015100eee00f6b23d1100d8c4953dd3b1"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/48be2dc4cdc5462407b319caa855d976cda88153"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/4e84eb54a0e438052b0c2e83653135042d9eb59a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/50839d5c8bf33f0970986dcc4b73b024f11a95b7"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/523d964986d8ad966ae07e540a608681098813f9"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/5410b8190c95dacd36d6e6ec75b7538a630e08de"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/549b2891ac79f504a7c9ea00f6d7527c34ce04e6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/55321649e7b7f1b5664ae20724e683c930643fc4"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/55cc52f25865baee3e6e52c3110a9723caa2b3cb"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/56c22410e3295ad03aa31552ab888f581756cc17"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/5724a705b62a7548ba2df1abe4ef0c970c4e1bd2"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/576a148c107d56861d1611641a6f7c7921061c5c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/5a6b8263e8939f851cf5b1e347a33d97253b7b3d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/5ba93c9db0cff93f52b521d7420e43f6eda2784f"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/5bd02a339fd7705449388580c75bfcc597aba954"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/5bd6fb6fc4163bf3a9db6ddaf509dce8df8a5000"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/5d06fc38005503af3d084721c60e574fb9d2f370"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/5ddc10489ff3269bdaa3051b70fb7af455ee1104"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/5ea9d515f0d10b04f1356b9463139bfe121a6e4a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/61c449793347cf2e1ed0c38d54d23c63dfaabeb8"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/6287389c373e9788dcc04f9747b4be1fd1ef3028"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/64d4de4d5aafab7ec388a7fe83066c1a4d1d9d68"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/651c37806d2ac579dcfc97643c3c1ea74dbb8774"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/6551d02d20573cfa2944ec1f12b0c01f264a1326"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/65f029414ee10e45ff4b9f305f7b472364cea538"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/68b75a17fe2db060df3e61a597650ba99079abbf"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/69e80594dbc5c4c648e39883a650b1760f20ab63"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/6cb47d0e640b4c41e32f13c0d64ee46eae1b80b5"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/6da5fe063432cb9094c7c083efdbbe5ba4246d18"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/6dd140da774d85f272fb587dc1b2a85d881a7c21"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/6ddab273597d73be49e2307d68e00fa18bba4765"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/6eaf85d84fbf47ea0619d0dba8d366f4e3ff0be6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/6f751cc09af8113f6ecd491b1830bd8454c4738d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/70d9eb29a70d483d07e2faca6b00098af78d1fff"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/7192effa1058382b379fb7b87f1acad5ac554d05"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/730e85d6a62e70cb6721009b903782ade4ff73a2"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/74002471a854059cb29de7cad8f9fb7adc3c5ec2"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/747f2330cd1fc4a06d54b376a9a6528d0364f0ac"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/749d5d7a9e0b1545b297117e834462af32b3e230"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/77de0b1de120ac702ca45868b1008a48626daf12"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/792c67398bce19a4eeda32653c994436e79456e5"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/7a3022b248c8960289e4c80c7cc8df409499e5da"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/7a9372081294a6fbd3fecdd91b99589c98d4948e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/7bbe4ba828947550f4ad089d5989cb695ecbdb1b"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/7f1ad514a96f0c3d5ca5d6f7880b929a65eeae96"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/7f2b075f0b6707c38db851747e2578343eeab286"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/81ebc64bfde3fad37af5a58ef7f1c5c3c54c4b5d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/82fae081afaea13831404024d39658344d56e1c6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/83ba41cea1adab707f7f213af5e2ed734bdddc25"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/841a3f66c94e5acd836a44cd5a8514d4ad45d83e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/841ef94ee0f1b0b45983d95b75aba25421d73f2c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/843b0aad4a9707c5dcc92d12d876b78675cfcb65"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/8483e3d92eda8df504b1d1d0d012f4bcd778cd33"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/876830fdff4e59038fa2173b700faef5bffe61de"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/87ca3342fdce0c1f678a3f1b62428032ef51442d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/87d044027cdb7d35fadb56532f497764246946a6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/88ce75ba18bdb7e93a81197d850f4e792f6a8155"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/89dc55e8e20e811e78c952c8bd2c16f55fe72f57"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/8a215a58908f44bdced595ceb01a81977f1d72f0"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/8ac7459e918304ca40b1cf29a3ac0f555eada678"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/8b93e50a911f3ea0e0b0377ba4636574f2ee9a5e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/8c9ec0ffd803505772693833d56e7a02110645b3"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/8e4b361a530dc6825afcfb4106bd482c3fd010fa"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/8f6690d97bcda890f2a5b2930a2b7a4d7b56c6e7"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/917636de2c14dce2580d4308249a94d61d62c305"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/91f11008defda918951bda868cc68c6373fb0e6a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/92e01a34047b660a798086d55a3d8d7100a01939"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/963fafadb4de09dee0e6a852bd61b1740039a465"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/97bf33ec97b93fcc2449431915911a55b906e3b6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/99e31e12b02b02479d10b2c08426906bd93a0840"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/9a75ce693e7259d4d3bb9203dfc0a65f8bbaa466"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/9ac0d956f9743e026baad7319ba2a75d9f1a534f"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/9ae56d4451dd3e1b66ddc250d84dbf6d8cae0dbd"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/9b9a3a1e4023c9b172060249752a482a3437ef2a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/9c81164e10bf612c352dca3ecabf57743b451d42"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/9d8b420b5d32deb0140ab91eeebba58ca6163722"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/9de687bf1e2cfac54c3b2e2eb85b53014a460ff7"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/9f3cda19a186bd11bfac361b464f92daa129a33b"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/a14fc6a608121f8abf0fe25cf466720f00f25653"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/a39906074669a6b76a35b0adf2bf36ad751f3b35"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/a454ca483b4a66b83826d061be2859dd79ff0d6c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/a52df5607370ff0f56d821000f3d5e386a01d489"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/a56eaf47f7c7263e53efdc55ec39063dbb4ae71c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/a79249fb8f7d53f0a280359d2d9df31594adbdfc"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/aa98a46f25004f7436aadb36ff8b7f07ed7bfce1"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/adc83b19e793491b1c6ea0fd8b46cd9f32e592fc"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/afd8e19f7bfd6c963f1856be59b75627864821dc"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/b3966239b8568442baecbeb0f8a1aa29dcdfd7ed"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/b430d41ef65493b3e917182c23ce90df983e01ab"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/b44e715e0cfe05f0c92a9e000ac3c36aae17df9d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/b4cf4ef7b3f64eff76cf99091fddc04411774708"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/b53d84468ea93620a9824ca65acf1179f431e763"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/b6ac4831cc5baabee9c8ab9af9ca3923f91097a0"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/b7f4a484866a8050dbc63bc905c9803c6964eda5"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/b8f21e59f90431c982d5ec3fb54ae4605f102252"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/bad10b6581cdead8e7cb96e4f544dcf0ea650fbc"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/bb01bed86b43257be9f527388e1183f52438c473"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/bb7497b00f0d999ef39dbf81c6bd0441e32723b6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/bf01b72e635deda1b4a8468f1cc36f01a54e1338"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/bf8b4530d8d246dd74ac53a13471bba17941dff7"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/c08bc84ab6a512b901bb730beb05c8394e4f1c5d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/c244b635d94e6f5d6b344887434be3e001a04b41"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/c281efe9620da999a637ff6e9b3279ec613fb992"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/c30a212824ee71e215f475f453de17c65a200101"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/c449427f35b7ecdf5641073629f7723df52c4cb0"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/c60240cd3b02eb71e2bf5ebd59afa3a5dc9b5e4d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/c60cdf9c3fb9060838f445b3bc3852b6f81e1e4c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/c72d0501bacadb45242c553ba292591302f12a6a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/c739e7b5ad999edbdeffdab672dbc30deb3959a0"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/c7d73b12a7108d82f8dac6d8a6a34f838601aca6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/ca781e1add632433293e847ae9e71649c217ee5f"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/cc48e916f40e8d69c2d07cfda42c7d3b7fe3447a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/cca1aff4c08ee4ccbcb6f80e1cd1480a0a093cfd"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/cf6ae8bf1d08d25e235b7bee0839984bbc04edf6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/cfc52fa086292c699efd7bf41d2fae3deb449536"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/cfe13ef3c6c713a059f231f0001ecec97e2a932d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/d14026ac6421bca7161024f4e735cb80a1068d01"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/d2fb6e8f7867fc1e2ebe723da2b5246dc9cc6b14"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/d4db7d51bdaa4781cf12c3b59914bad414d2a41e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/d533da0e7f8c1e39bb025b4d7a89613142a6f54e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/d5cf489d01a1b847a7aac5dddabff23fdc218e1e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/d686f8561a249c7c15c78f76a5fceb884286e070"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/d92424daad9d96a40e5ab177e3824c36ef51dc0f"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/db242a11ed88b2b26af46770dd1927d9f35301fb"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/db32eb04db13d58f65f46d262608bd088987c063"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/db39a953317951759e40734de6607a0b4457728e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/dc5e8f3102456bed90d17303bc4cff1a7e076d5d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/dd9542bbed8e5dc58da2789edbfb9c38d578d3b4"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/de2ebb1ed324385de500a1a3308846239857c3c7"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/de8ba9158254c1cd84b53df1e4cdf1757b1392f1"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/e1dd260746f50024822a8b729b89545d26decfb8"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/e29add81b20dc570fdc885782689f6dccb1c5fad"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/e2e99af62843cd3b29d50daeb118e58830784da9"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/e46611c5daf99662e1576147c1623409752a1f39"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/e5a1ba11af830e9d2db201c5164f75747a85fe9b"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/e6026ee0badf216b326443a5f708446b2f2e579f"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/e6c7d2c0038fa1f03fc6590a726abc98f4c641f3"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/eafdef6a630bed71bd0e4f3d4a16b5fa0c920651"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/ece985b9b82e27281514d460709d7edf8203ded7"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/edb8f4259f756c2c4bc731f05beaa36f992cf079"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/edce7778c2e1adb81dda3d057a6536759a7cb293"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/ee4040c0dd406dd616c49ed2c37b40478dabfe0f"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/ee69f2b380663d051a70f30fcfce9f79f5341e5a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/efc6743e47274058771bb6eda1fefa017bde4a95"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/f0038e54162000694d882b1acb80930c807b41d2"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/f1deb9e388c877337dabe92f31b01e2a019a10f4"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/f3a09373e4d3c7310d372089e6deb15d6b22c198"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/f3db7ef6495fa1ac5bb4db293fb38dd59122bb7c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/f434bb4ceecc573e085d4c3ef453ef01e93d9c89"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/f55bceaad42ddf9d2b37fdfca68255d29a696109"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/f62ca5321428a5d23f3c804fb51eb4e65bc58716"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/f7c6a558b8d0af64db2b139371a7af7068b01b92"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/faa1781e1444bba5b8c677bc5e2a38d023a1ec65"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/fceba33ada1dda05fccedfefd331c9a201f1a2e5"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/fd668bef6fdaf7f3ffd58d8c60ce550476652e60"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/fdf06b928e37e7c4ae59a568b5723ad98bbed6e5"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/fe2fc5d499aeb2762387ef2e3ce939280813dec0"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/security/corpus/alts_credentials_corpus/ff548d368b090409a138e5cc4afc7f43b4a3fbbd"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "alts_credentials_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/api_fuzzer_corpus/00.bin"
     ], 
     "ci_platforms": [
diff --git a/tools/run_tests/helper_scripts/build_python.sh b/tools/run_tests/helper_scripts/build_python.sh
index b0a6f0f..cdcb2aa 100755
--- a/tools/run_tests/helper_scripts/build_python.sh
+++ b/tools/run_tests/helper_scripts/build_python.sh
@@ -112,10 +112,6 @@
 export GRPC_PYTHON_BUILD_WITH_CYTHON=1
 export LANG=en_US.UTF-8
 
-# Default python on the host to fall back to when instantiating e.g. the
-# virtualenv.
-HOST_PYTHON=${HOST_PYTHON:-python}
-
 # If ccache is available on Linux, use it.
 if [ "$(is_linux)" ]; then
   # We're not on Darwin (Mac OS X)
@@ -132,14 +128,9 @@
 # Perform build operations #
 ############################
 
-# Instantiate the virtualenv, preferring to do so from the relevant python
-# version. Even if these commands fail (e.g. on Windows due to name conflicts)
-# it's possible that the virtualenv is still usable and we trust the tester to
-# be able to 'figure it out' instead of us e.g. doing potentially expensive and
-# unnecessary error recovery by `rm -rf`ing the virtualenv.
-($PYTHON -m virtualenv "$VENV" ||
- $HOST_PYTHON -m virtualenv -p "$PYTHON" "$VENV" ||
- true)
+# Instantiate the virtualenv from the Python version passed in.
+$PYTHON -m pip install virtualenv
+$PYTHON -m virtualenv "$VENV"
 VENV_PYTHON=$(script_realpath "$VENV/$VENV_RELATIVE_PYTHON")
 
 # See https://github.com/grpc/grpc/issues/14815 for more context. We cannot rely
diff --git a/tools/run_tests/python_utils/upload_rbe_results.py b/tools/run_tests/python_utils/upload_rbe_results.py
new file mode 100644
index 0000000..d302024
--- /dev/null
+++ b/tools/run_tests/python_utils/upload_rbe_results.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python
+# Copyright 2017 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Uploads RBE results to BigQuery"""
+
+import argparse
+import os
+import json
+import sys
+import urllib2
+import uuid
+
+gcp_utils_dir = os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '../../gcp/utils'))
+sys.path.append(gcp_utils_dir)
+import big_query_utils
+
+_DATASET_ID = 'jenkins_test_results'
+_DESCRIPTION = 'Test results from master RBE builds on Kokoro'
+# 90 days in milliseconds
+_EXPIRATION_MS = 90 * 24 * 60 * 60 * 1000
+_PARTITION_TYPE = 'DAY'
+_PROJECT_ID = 'grpc-testing'
+_RESULTS_SCHEMA = [
+    ('job_name', 'STRING', 'Name of Kokoro job'),
+    ('build_id', 'INTEGER', 'Build ID of Kokoro job'),
+    ('build_url', 'STRING', 'URL of Kokoro build'),
+    ('test_target', 'STRING', 'Bazel target path'),
+    ('test_case', 'STRING', 'Name of test case'),
+    ('result', 'STRING', 'Test or build result'),
+    ('timestamp', 'TIMESTAMP', 'Timestamp of test run'),
+]
+_TABLE_ID = 'rbe_test_results'
+
+
+def _get_api_key():
+    """Returns string with API key to access ResultStore.
+	Intended to be used in Kokoro envrionment."""
+    api_key_directory = os.getenv('KOKORO_GFILE_DIR')
+    api_key_file = os.path.join(api_key_directory, 'resultstore_api_key')
+    assert os.path.isfile(api_key_file), 'Must add --api_key arg if not on ' \
+     'Kokoro or Kokoro envrionment is not set up properly.'
+    with open(api_key_file, 'r') as f:
+        return f.read().replace('\n', '')
+
+
+def _get_invocation_id():
+    """Returns String of Bazel invocation ID. Intended to be used in
+	Kokoro envirionment."""
+    bazel_id_directory = os.getenv('KOKORO_ARTIFACTS_DIR')
+    bazel_id_file = os.path.join(bazel_id_directory, 'bazel_invocation_ids')
+    assert os.path.isfile(bazel_id_file), 'bazel_invocation_ids file, written ' \
+     'by bazel_wrapper.py, expected but not found.'
+    with open(bazel_id_file, 'r') as f:
+        return f.read().replace('\n', '')
+
+
+def _upload_results_to_bq(rows):
+    """Upload test results to a BQ table.
+
+  Args:
+      rows: A list of dictionaries containing data for each row to insert
+  """
+    bq = big_query_utils.create_big_query()
+    big_query_utils.create_partitioned_table(
+        bq,
+        _PROJECT_ID,
+        _DATASET_ID,
+        _TABLE_ID,
+        _RESULTS_SCHEMA,
+        _DESCRIPTION,
+        partition_type=_PARTITION_TYPE,
+        expiration_ms=_EXPIRATION_MS)
+
+    max_retries = 3
+    for attempt in range(max_retries):
+        if big_query_utils.insert_rows(bq, _PROJECT_ID, _DATASET_ID, _TABLE_ID,
+                                       rows):
+            break
+        else:
+            if attempt < max_retries - 1:
+                print('Error uploading result to bigquery, will retry.')
+            else:
+                print(
+                    'Error uploading result to bigquery, all attempts failed.')
+                sys.exit(1)
+
+
+def _get_resultstore_data(api_key, invocation_id):
+    """Returns dictionary of test results by querying ResultStore API.
+  Args:
+      api_key: String of ResultStore API key
+      invocation_id: String of ResultStore invocation ID to results from 
+  """
+    all_actions = []
+    page_token = ''
+    # ResultStore's API returns data on a limited number of tests. When we exceed
+    # that limit, the 'nextPageToken' field is included in the request to get
+    # subsequent data, so keep requesting until 'nextPageToken' field is omitted.
+    while True:
+        req = urllib2.Request(
+            url=
+            'https://resultstore.googleapis.com/v2/invocations/%s/targets/-/configuredTargets/-/actions?key=%s&pageToken=%s'
+            % (invocation_id, api_key, page_token),
+            headers={
+                'Content-Type': 'application/json'
+            })
+        results = json.loads(urllib2.urlopen(req).read())
+        all_actions.extend(results['actions'])
+        if 'nextPageToken' not in results:
+            break
+        page_token = results['nextPageToken']
+    return all_actions
+
+
+if __name__ == "__main__":
+    # Arguments are necessary if running in a non-Kokoro envrionment.
+    argp = argparse.ArgumentParser(description='Upload RBE results.')
+    argp.add_argument('--api_key', default='', type=str)
+    argp.add_argument('--invocation_id', default='', type=str)
+    args = argp.parse_args()
+
+    api_key = args.api_key or _get_api_key()
+    invocation_id = args.invocation_id or _get_invocation_id()
+    resultstore_actions = _get_resultstore_data(api_key, invocation_id)
+
+    bq_rows = []
+    for action in resultstore_actions:
+        # Filter out non-test related data, such as build results.
+        if 'testAction' not in action:
+            continue
+        # Some test results contain the fileProcessingErrors field, which indicates
+        # an issue with parsing results individual test cases.
+        if 'fileProcessingErrors' in action:
+            test_cases = [{
+                'testCase': {
+                    'caseName': str(action['id']['actionId']),
+                    'result': str(action['statusAttributes']['status'])
+                }
+            }]
+        else:
+            test_cases = action['testAction']['testSuite']['tests'][0][
+                'testSuite']['tests']
+        for test_case in test_cases:
+            if 'errors' in test_case['testCase']:
+                result = 'FAILED'
+            else:
+                result = 'PASSED'
+            bq_rows.append({
+                'insertId': str(uuid.uuid4()),
+                'json': {
+                    'job_name':
+                    os.getenv('KOKORO_JOB_NAME'),
+                    'build_id':
+                    os.getenv('KOKORO_BUILD_NUMBER'),
+                    'build_url':
+                    'https://sponge.corp.google.com/invocation?id=%s' %
+                    os.getenv('KOKORO_BUILD_ID'),
+                    'test_target':
+                    action['id']['targetId'],
+                    'test_case':
+                    test_case['testCase']['caseName'],
+                    'result':
+                    result,
+                    'timestamp':
+                    action['timing']['startTime'],
+                }
+            })
+    # BigQuery sometimes fails with large uploads, so batch 1,000 rows at a time.
+    for i in range((len(bq_rows) / 1000) + 1):
+        _upload_results_to_bq(bq_rows[i * 1000:(i + 1) * 1000])
diff --git a/tools/run_tests/run_interop_tests.py b/tools/run_tests/run_interop_tests.py
index 8c56665..aa58107 100755
--- a/tools/run_tests/run_interop_tests.py
+++ b/tools/run_tests/run_interop_tests.py
@@ -638,10 +638,10 @@
 ]
 
 #TODO: Add c++ when c++ ALTS interop client is ready.
-_LANGUAGES_FOR_ALTS_TEST_CASES = ['java', 'go']
+_LANGUAGES_FOR_ALTS_TEST_CASES = ['java', 'go', 'c++']
 
 #TODO: Add c++ when c++ ALTS interop server is ready.
-_SERVERS_FOR_ALTS_TEST_CASES = ['java', 'go']
+_SERVERS_FOR_ALTS_TEST_CASES = ['java', 'go', 'c++']
 
 _TRANSPORT_SECURITY_OPTIONS = ['tls', 'alts', 'insecure']