diff --git a/BUILD b/BUILD
index 2b21b19aba498094f993f2a3e8b78129c730204f..e1befa8b042266e6c1265d4838a8b757d45f6118 100644
--- a/BUILD
+++ b/BUILD
@@ -224,6 +224,7 @@ cc_library(
     "src/core/surface/api_trace.h",
     "src/core/surface/byte_buffer_queue.h",
     "src/core/surface/call.h",
+    "src/core/surface/call_test_only.h",
     "src/core/surface/channel.h",
     "src/core/surface/completion_queue.h",
     "src/core/surface/event_string.h",
@@ -512,6 +513,7 @@ cc_library(
     "src/core/surface/api_trace.h",
     "src/core/surface/byte_buffer_queue.h",
     "src/core/surface/call.h",
+    "src/core/surface/call_test_only.h",
     "src/core/surface/channel.h",
     "src/core/surface/completion_queue.h",
     "src/core/surface/event_string.h",
@@ -1302,6 +1304,7 @@ objc_library(
     "src/core/surface/api_trace.h",
     "src/core/surface/byte_buffer_queue.h",
     "src/core/surface/call.h",
+    "src/core/surface/call_test_only.h",
     "src/core/surface/channel.h",
     "src/core/surface/completion_queue.h",
     "src/core/surface/event_string.h",
diff --git a/binding.gyp b/binding.gyp
index 2a5cb1ced496d63722dd730c486c9e7919c9108f..392835c7721cc41b353d30ec512e2cd645741e97 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -1,3 +1,10 @@
+# GRPC Node gyp file
+# This currently builds the Node extension and dependencies
+# This file has been automatically generated from a template file.
+# Please look at the templates directory instead.
+# This file can be regenerated from the template by running
+# tools/buildgen/generate_projects.sh
+
 # Copyright 2015, Google Inc.
 # All rights reserved.
 #
@@ -26,11 +33,234 @@
 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Some of this file is built with the help of
+# https://n8.io/converting-a-c-library-to-gyp/
 {
-  "variables" : {
+  'variables': {
     'config': '<!(echo $CONFIG)'
   },
-  "targets" : [
+  # TODO: Finish windows support
+  'target_defaults': {
+      # Empirically, Node only exports ALPN symbols if its major version is >0.
+      # io.js always reports versions >0 and always exports ALPN symbols.
+      # Therefore, Node's major version will be truthy if and only if it
+      # supports ALPN. The output of "node -v" is v[major].[minor].[patch],
+      # like "v4.1.1" in a recent version. We use grep to extract just the
+      # major version. "4", would be the output for the example.
+    'defines': [
+      'TSI_OPENSSL_ALPN_SUPPORT=<!(node -v | grep -oP "(?<=v)(\d+)(?=\.\d+\.\d+)")'
+    ],
+    'include_dirs': [
+      '.',
+      'include'
+    ]
+  },
+  'targets': [
+    {
+      'target_name': 'gpr',
+      'product_prefix': 'lib',
+      'type': 'static_library',
+      'dependencies': [
+      ],
+      'sources': [
+        'src/core/support/alloc.c',
+        'src/core/support/cmdline.c',
+        'src/core/support/cpu_iphone.c',
+        'src/core/support/cpu_linux.c',
+        'src/core/support/cpu_posix.c',
+        'src/core/support/cpu_windows.c',
+        'src/core/support/env_linux.c',
+        'src/core/support/env_posix.c',
+        'src/core/support/env_win32.c',
+        'src/core/support/file.c',
+        'src/core/support/file_posix.c',
+        'src/core/support/file_win32.c',
+        'src/core/support/histogram.c',
+        'src/core/support/host_port.c',
+        'src/core/support/log.c',
+        'src/core/support/log_android.c',
+        'src/core/support/log_linux.c',
+        'src/core/support/log_posix.c',
+        'src/core/support/log_win32.c',
+        'src/core/support/murmur_hash.c',
+        'src/core/support/slice.c',
+        'src/core/support/slice_buffer.c',
+        'src/core/support/stack_lockfree.c',
+        'src/core/support/string.c',
+        'src/core/support/string_posix.c',
+        'src/core/support/string_win32.c',
+        'src/core/support/subprocess_posix.c',
+        'src/core/support/sync.c',
+        'src/core/support/sync_posix.c',
+        'src/core/support/sync_win32.c',
+        'src/core/support/thd.c',
+        'src/core/support/thd_posix.c',
+        'src/core/support/thd_win32.c',
+        'src/core/support/time.c',
+        'src/core/support/time_posix.c',
+        'src/core/support/time_win32.c',
+        'src/core/support/tls_pthread.c',
+      ],
+    },
+    {
+      'target_name': 'grpc',
+      'product_prefix': 'lib',
+      'type': 'static_library',
+      'dependencies': [
+        'gpr',
+      ],
+      'sources': [
+        'src/core/httpcli/httpcli_security_connector.c',
+        'src/core/security/base64.c',
+        'src/core/security/client_auth_filter.c',
+        'src/core/security/credentials.c',
+        'src/core/security/credentials_metadata.c',
+        'src/core/security/credentials_posix.c',
+        'src/core/security/credentials_win32.c',
+        'src/core/security/google_default_credentials.c',
+        'src/core/security/handshake.c',
+        'src/core/security/json_token.c',
+        'src/core/security/jwt_verifier.c',
+        'src/core/security/secure_endpoint.c',
+        'src/core/security/security_connector.c',
+        'src/core/security/security_context.c',
+        'src/core/security/server_auth_filter.c',
+        'src/core/security/server_secure_chttp2.c',
+        'src/core/surface/init_secure.c',
+        'src/core/surface/secure_channel_create.c',
+        'src/core/tsi/fake_transport_security.c',
+        'src/core/tsi/ssl_transport_security.c',
+        'src/core/tsi/transport_security.c',
+        'src/core/census/grpc_context.c',
+        'src/core/census/grpc_filter.c',
+        'src/core/channel/channel_args.c',
+        'src/core/channel/channel_stack.c',
+        'src/core/channel/client_channel.c',
+        'src/core/channel/compress_filter.c',
+        'src/core/channel/connected_channel.c',
+        'src/core/channel/http_client_filter.c',
+        'src/core/channel/http_server_filter.c',
+        'src/core/channel/noop_filter.c',
+        'src/core/client_config/client_config.c',
+        'src/core/client_config/connector.c',
+        'src/core/client_config/lb_policies/pick_first.c',
+        'src/core/client_config/lb_policies/round_robin.c',
+        'src/core/client_config/lb_policy.c',
+        'src/core/client_config/lb_policy_factory.c',
+        'src/core/client_config/lb_policy_registry.c',
+        'src/core/client_config/resolver.c',
+        'src/core/client_config/resolver_factory.c',
+        'src/core/client_config/resolver_registry.c',
+        'src/core/client_config/resolvers/dns_resolver.c',
+        'src/core/client_config/resolvers/sockaddr_resolver.c',
+        'src/core/client_config/subchannel.c',
+        'src/core/client_config/subchannel_factory.c',
+        'src/core/client_config/subchannel_factory_decorators/add_channel_arg.c',
+        'src/core/client_config/subchannel_factory_decorators/merge_channel_args.c',
+        'src/core/client_config/uri_parser.c',
+        'src/core/compression/algorithm.c',
+        'src/core/compression/message_compress.c',
+        'src/core/debug/trace.c',
+        'src/core/httpcli/format_request.c',
+        'src/core/httpcli/httpcli.c',
+        'src/core/httpcli/parser.c',
+        'src/core/iomgr/alarm.c',
+        'src/core/iomgr/alarm_heap.c',
+        'src/core/iomgr/closure.c',
+        'src/core/iomgr/endpoint.c',
+        'src/core/iomgr/endpoint_pair_posix.c',
+        'src/core/iomgr/endpoint_pair_windows.c',
+        'src/core/iomgr/exec_ctx.c',
+        'src/core/iomgr/fd_posix.c',
+        'src/core/iomgr/iocp_windows.c',
+        'src/core/iomgr/iomgr.c',
+        'src/core/iomgr/iomgr_posix.c',
+        'src/core/iomgr/iomgr_windows.c',
+        'src/core/iomgr/pollset_multipoller_with_epoll.c',
+        'src/core/iomgr/pollset_multipoller_with_poll_posix.c',
+        'src/core/iomgr/pollset_posix.c',
+        'src/core/iomgr/pollset_set_posix.c',
+        'src/core/iomgr/pollset_set_windows.c',
+        'src/core/iomgr/pollset_windows.c',
+        'src/core/iomgr/resolve_address_posix.c',
+        'src/core/iomgr/resolve_address_windows.c',
+        'src/core/iomgr/sockaddr_utils.c',
+        'src/core/iomgr/socket_utils_common_posix.c',
+        'src/core/iomgr/socket_utils_linux.c',
+        'src/core/iomgr/socket_utils_posix.c',
+        'src/core/iomgr/socket_windows.c',
+        'src/core/iomgr/tcp_client_posix.c',
+        'src/core/iomgr/tcp_client_windows.c',
+        'src/core/iomgr/tcp_posix.c',
+        'src/core/iomgr/tcp_server_posix.c',
+        'src/core/iomgr/tcp_server_windows.c',
+        'src/core/iomgr/tcp_windows.c',
+        'src/core/iomgr/time_averaged_stats.c',
+        'src/core/iomgr/udp_server.c',
+        'src/core/iomgr/wakeup_fd_eventfd.c',
+        'src/core/iomgr/wakeup_fd_nospecial.c',
+        'src/core/iomgr/wakeup_fd_pipe.c',
+        'src/core/iomgr/wakeup_fd_posix.c',
+        'src/core/iomgr/workqueue_posix.c',
+        'src/core/iomgr/workqueue_windows.c',
+        'src/core/json/json.c',
+        'src/core/json/json_reader.c',
+        'src/core/json/json_string.c',
+        'src/core/json/json_writer.c',
+        'src/core/profiling/basic_timers.c',
+        'src/core/profiling/stap_timers.c',
+        'src/core/surface/api_trace.c',
+        'src/core/surface/byte_buffer.c',
+        'src/core/surface/byte_buffer_queue.c',
+        'src/core/surface/byte_buffer_reader.c',
+        'src/core/surface/call.c',
+        'src/core/surface/call_details.c',
+        'src/core/surface/call_log_batch.c',
+        'src/core/surface/channel.c',
+        'src/core/surface/channel_connectivity.c',
+        'src/core/surface/channel_create.c',
+        'src/core/surface/completion_queue.c',
+        'src/core/surface/event_string.c',
+        'src/core/surface/init.c',
+        'src/core/surface/lame_client.c',
+        'src/core/surface/metadata_array.c',
+        'src/core/surface/server.c',
+        'src/core/surface/server_chttp2.c',
+        'src/core/surface/server_create.c',
+        'src/core/surface/version.c',
+        'src/core/transport/chttp2/alpn.c',
+        'src/core/transport/chttp2/bin_encoder.c',
+        'src/core/transport/chttp2/frame_data.c',
+        'src/core/transport/chttp2/frame_goaway.c',
+        'src/core/transport/chttp2/frame_ping.c',
+        'src/core/transport/chttp2/frame_rst_stream.c',
+        'src/core/transport/chttp2/frame_settings.c',
+        'src/core/transport/chttp2/frame_window_update.c',
+        'src/core/transport/chttp2/hpack_parser.c',
+        'src/core/transport/chttp2/hpack_table.c',
+        'src/core/transport/chttp2/huffsyms.c',
+        'src/core/transport/chttp2/incoming_metadata.c',
+        'src/core/transport/chttp2/parsing.c',
+        'src/core/transport/chttp2/status_conversion.c',
+        'src/core/transport/chttp2/stream_encoder.c',
+        'src/core/transport/chttp2/stream_lists.c',
+        'src/core/transport/chttp2/stream_map.c',
+        'src/core/transport/chttp2/timeout_encoding.c',
+        'src/core/transport/chttp2/varint.c',
+        'src/core/transport/chttp2/writing.c',
+        'src/core/transport/chttp2_transport.c',
+        'src/core/transport/connectivity_state.c',
+        'src/core/transport/metadata.c',
+        'src/core/transport/stream_op.c',
+        'src/core/transport/transport.c',
+        'src/core/transport/transport_op_string.c',
+        'src/core/census/context.c',
+        'src/core/census/initialize.c',
+        'src/core/census/operation.c',
+        'src/core/census/tracing.c',
+      ],
+    },
     {
       'include_dirs': [
         "<!(node -e \"require('nan')\")"
@@ -88,7 +318,7 @@
         "src/node/ext/timeval.cc"
       ],
       "dependencies": [
-        "grpc.gyp:grpc"
+        "grpc"
       ]
     }
   ]
diff --git a/build.yaml b/build.yaml
index 6879237ce894c13dcfaaa8866fcfa35fc44e3a0d..370c43a7f5370b3f4584773e7d76bdc3b1b0d24c 100644
--- a/build.yaml
+++ b/build.yaml
@@ -184,6 +184,7 @@ filegroups:
   - src/core/surface/api_trace.h
   - src/core/surface/byte_buffer_queue.h
   - src/core/surface/call.h
+  - src/core/surface/call_test_only.h
   - src/core/surface/channel.h
   - src/core/surface/completion_queue.h
   - src/core/surface/event_string.h
diff --git a/examples/php/README.md b/examples/php/README.md
index a4ee8e698b9cc85bf28c2bb4e04c719b75da9da0..8fb060863a3f6077cd99aa3f653752081c80c028 100644
--- a/examples/php/README.md
+++ b/examples/php/README.md
@@ -8,7 +8,7 @@ This requires PHP 5.5 or greater.
 
 INSTALL
 -------
- - On Mac OS X, install [homebrew][]. On Linux, install [linuxbrew][]. Run the following command to install gRPC.
+ - On Mac OS X, install [homebrew][]. Run the following command to install gRPC.
 
    ```sh
    $ curl -fsSL https://goo.gl/getgrpc | bash -s php
@@ -59,7 +59,6 @@ TUTORIAL
 You can find a more detailed tutorial in [gRPC Basics: PHP][]
 
 [homebrew]:http://brew.sh
-[linuxbrew]:https://github.com/Homebrew/linuxbrew#installation
 [gRPC install script]:https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install
 [Node]:https://github.com/grpc/grpc/tree/master/examples/node
 [gRPC Basics: PHP]:http://www.grpc.io/docs/tutorials/basic/php.html
diff --git a/gRPC.podspec b/gRPC.podspec
index 2c035620f6c94461f2faf8b99d577c06a9f06c75..be51fe86e0ce918026ce0ecc22b4754d82ae08f3 100644
--- a/gRPC.podspec
+++ b/gRPC.podspec
@@ -228,6 +228,7 @@ Pod::Spec.new do |s|
                       'src/core/surface/api_trace.h',
                       'src/core/surface/byte_buffer_queue.h',
                       'src/core/surface/call.h',
+                      'src/core/surface/call_test_only.h',
                       'src/core/surface/channel.h',
                       'src/core/surface/completion_queue.h',
                       'src/core/surface/event_string.h',
@@ -521,6 +522,7 @@ Pod::Spec.new do |s|
                               'src/core/surface/api_trace.h',
                               'src/core/surface/byte_buffer_queue.h',
                               'src/core/surface/call.h',
+                              'src/core/surface/call_test_only.h',
                               'src/core/surface/channel.h',
                               'src/core/surface/completion_queue.h',
                               'src/core/surface/event_string.h',
diff --git a/grpc.gyp b/grpc.gyp
deleted file mode 100644
index bb8a7423ef4611f1085321c515729faffafb3b20..0000000000000000000000000000000000000000
--- a/grpc.gyp
+++ /dev/null
@@ -1,296 +0,0 @@
-# GRPC gyp file
-# This currently builds C code.
-# This file has been automatically generated from a template file.
-# Please look at the templates directory instead.
-# This file can be regenerated from the template by running
-# tools/buildgen/generate_projects.sh
-
-# Copyright 2015, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-# Some of this file is built with the help of
-# https://n8.io/converting-a-c-library-to-gyp/
-{
-  # TODO: Finish windows support
-  'target_defaults': {
-    'default_configuration': 'Debug',
-    'configurations': {
-      'Debug': {
-        'defines': [ 'DEBUG', '_DEBUG' ],
-        'msvs_settings': {
-          'VCCLCompilerTool': {
-            'RuntimeLibrary': 1, # static debug
-          },
-        },
-      },
-      'Release': {
-        'defines': [ 'NDEBUG' ],
-        'msvs_settings': {
-          'VCCLCompilerTool': {
-            'RuntimeLibrary': 0, # static release
-          },
-        },
-      }
-    },
-    'msvs_settings': {
-      'VCLinkerTool': {
-        'GenerateDebugInformation': 'true',
-      },
-    },
-    # TODO: Add fallback for Windows, and if pkg-config is not available
-    'defines': [
-      'TSI_OPENSSL_ALPN_SUPPORT=<!(pkg-config --atleast-version=1.0.2 openssl >/dev/null 2>&1 && echo 1 || echo 0)'
-    ],
-    'include_dirs': [
-      '.',
-      'include'
-    ],
-    # TODO: Check for libraries with pkg-config
-    'libraries': [
-      '-lcrypto',
-      '-lssl',
-      '-ldl',
-      '-lpthread',
-      '-lz'
-    ],
-    'direct_dependent_settings': {
-      'include_dirs': [
-        '.',
-        'include'
-      ],
-    }
-  },
-  'targets': [
-    {
-      'target_name': 'gpr',
-      'product_prefix': 'lib',
-      'type': 'static_library',
-      'dependencies': [
-      ],
-      'sources': [
-        'src/core/support/alloc.c',
-        'src/core/support/cmdline.c',
-        'src/core/support/cpu_iphone.c',
-        'src/core/support/cpu_linux.c',
-        'src/core/support/cpu_posix.c',
-        'src/core/support/cpu_windows.c',
-        'src/core/support/env_linux.c',
-        'src/core/support/env_posix.c',
-        'src/core/support/env_win32.c',
-        'src/core/support/file.c',
-        'src/core/support/file_posix.c',
-        'src/core/support/file_win32.c',
-        'src/core/support/histogram.c',
-        'src/core/support/host_port.c',
-        'src/core/support/log.c',
-        'src/core/support/log_android.c',
-        'src/core/support/log_linux.c',
-        'src/core/support/log_posix.c',
-        'src/core/support/log_win32.c',
-        'src/core/support/murmur_hash.c',
-        'src/core/support/slice.c',
-        'src/core/support/slice_buffer.c',
-        'src/core/support/stack_lockfree.c',
-        'src/core/support/string.c',
-        'src/core/support/string_posix.c',
-        'src/core/support/string_win32.c',
-        'src/core/support/subprocess_posix.c',
-        'src/core/support/sync.c',
-        'src/core/support/sync_posix.c',
-        'src/core/support/sync_win32.c',
-        'src/core/support/thd.c',
-        'src/core/support/thd_posix.c',
-        'src/core/support/thd_win32.c',
-        'src/core/support/time.c',
-        'src/core/support/time_posix.c',
-        'src/core/support/time_win32.c',
-        'src/core/support/tls_pthread.c',
-      ],
-    },
-    {
-      'target_name': 'grpc',
-      'product_prefix': 'lib',
-      'type': 'static_library',
-      'dependencies': [
-        'gpr',
-      ],
-      'sources': [
-        'src/core/httpcli/httpcli_security_connector.c',
-        'src/core/security/base64.c',
-        'src/core/security/client_auth_filter.c',
-        'src/core/security/credentials.c',
-        'src/core/security/credentials_metadata.c',
-        'src/core/security/credentials_posix.c',
-        'src/core/security/credentials_win32.c',
-        'src/core/security/google_default_credentials.c',
-        'src/core/security/handshake.c',
-        'src/core/security/json_token.c',
-        'src/core/security/jwt_verifier.c',
-        'src/core/security/secure_endpoint.c',
-        'src/core/security/security_connector.c',
-        'src/core/security/security_context.c',
-        'src/core/security/server_auth_filter.c',
-        'src/core/security/server_secure_chttp2.c',
-        'src/core/surface/init_secure.c',
-        'src/core/surface/secure_channel_create.c',
-        'src/core/tsi/fake_transport_security.c',
-        'src/core/tsi/ssl_transport_security.c',
-        'src/core/tsi/transport_security.c',
-        'src/core/census/grpc_context.c',
-        'src/core/census/grpc_filter.c',
-        'src/core/channel/channel_args.c',
-        'src/core/channel/channel_stack.c',
-        'src/core/channel/client_channel.c',
-        'src/core/channel/compress_filter.c',
-        'src/core/channel/connected_channel.c',
-        'src/core/channel/http_client_filter.c',
-        'src/core/channel/http_server_filter.c',
-        'src/core/channel/noop_filter.c',
-        'src/core/client_config/client_config.c',
-        'src/core/client_config/connector.c',
-        'src/core/client_config/lb_policies/pick_first.c',
-        'src/core/client_config/lb_policies/round_robin.c',
-        'src/core/client_config/lb_policy.c',
-        'src/core/client_config/lb_policy_factory.c',
-        'src/core/client_config/lb_policy_registry.c',
-        'src/core/client_config/resolver.c',
-        'src/core/client_config/resolver_factory.c',
-        'src/core/client_config/resolver_registry.c',
-        'src/core/client_config/resolvers/dns_resolver.c',
-        'src/core/client_config/resolvers/sockaddr_resolver.c',
-        'src/core/client_config/subchannel.c',
-        'src/core/client_config/subchannel_factory.c',
-        'src/core/client_config/subchannel_factory_decorators/add_channel_arg.c',
-        'src/core/client_config/subchannel_factory_decorators/merge_channel_args.c',
-        'src/core/client_config/uri_parser.c',
-        'src/core/compression/algorithm.c',
-        'src/core/compression/message_compress.c',
-        'src/core/debug/trace.c',
-        'src/core/httpcli/format_request.c',
-        'src/core/httpcli/httpcli.c',
-        'src/core/httpcli/parser.c',
-        'src/core/iomgr/alarm.c',
-        'src/core/iomgr/alarm_heap.c',
-        'src/core/iomgr/closure.c',
-        'src/core/iomgr/endpoint.c',
-        'src/core/iomgr/endpoint_pair_posix.c',
-        'src/core/iomgr/endpoint_pair_windows.c',
-        'src/core/iomgr/exec_ctx.c',
-        'src/core/iomgr/executor.c',
-        'src/core/iomgr/fd_posix.c',
-        'src/core/iomgr/iocp_windows.c',
-        'src/core/iomgr/iomgr.c',
-        'src/core/iomgr/iomgr_posix.c',
-        'src/core/iomgr/iomgr_windows.c',
-        'src/core/iomgr/pollset_multipoller_with_epoll.c',
-        'src/core/iomgr/pollset_multipoller_with_poll_posix.c',
-        'src/core/iomgr/pollset_posix.c',
-        'src/core/iomgr/pollset_set_posix.c',
-        'src/core/iomgr/pollset_set_windows.c',
-        'src/core/iomgr/pollset_windows.c',
-        'src/core/iomgr/resolve_address_posix.c',
-        'src/core/iomgr/resolve_address_windows.c',
-        'src/core/iomgr/sockaddr_utils.c',
-        'src/core/iomgr/socket_utils_common_posix.c',
-        'src/core/iomgr/socket_utils_linux.c',
-        'src/core/iomgr/socket_utils_posix.c',
-        'src/core/iomgr/socket_windows.c',
-        'src/core/iomgr/tcp_client_posix.c',
-        'src/core/iomgr/tcp_client_windows.c',
-        'src/core/iomgr/tcp_posix.c',
-        'src/core/iomgr/tcp_server_posix.c',
-        'src/core/iomgr/tcp_server_windows.c',
-        'src/core/iomgr/tcp_windows.c',
-        'src/core/iomgr/time_averaged_stats.c',
-        'src/core/iomgr/udp_server.c',
-        'src/core/iomgr/wakeup_fd_eventfd.c',
-        'src/core/iomgr/wakeup_fd_nospecial.c',
-        'src/core/iomgr/wakeup_fd_pipe.c',
-        'src/core/iomgr/wakeup_fd_posix.c',
-        'src/core/iomgr/workqueue_posix.c',
-        'src/core/iomgr/workqueue_windows.c',
-        'src/core/json/json.c',
-        'src/core/json/json_reader.c',
-        'src/core/json/json_string.c',
-        'src/core/json/json_writer.c',
-        'src/core/profiling/basic_timers.c',
-        'src/core/profiling/stap_timers.c',
-        'src/core/surface/api_trace.c',
-        'src/core/surface/byte_buffer.c',
-        'src/core/surface/byte_buffer_queue.c',
-        'src/core/surface/byte_buffer_reader.c',
-        'src/core/surface/call.c',
-        'src/core/surface/call_details.c',
-        'src/core/surface/call_log_batch.c',
-        'src/core/surface/channel.c',
-        'src/core/surface/channel_connectivity.c',
-        'src/core/surface/channel_create.c',
-        'src/core/surface/completion_queue.c',
-        'src/core/surface/event_string.c',
-        'src/core/surface/init.c',
-        'src/core/surface/lame_client.c',
-        'src/core/surface/metadata_array.c',
-        'src/core/surface/server.c',
-        'src/core/surface/server_chttp2.c',
-        'src/core/surface/server_create.c',
-        'src/core/surface/version.c',
-        'src/core/transport/chttp2/alpn.c',
-        'src/core/transport/chttp2/bin_encoder.c',
-        'src/core/transport/chttp2/frame_data.c',
-        'src/core/transport/chttp2/frame_goaway.c',
-        'src/core/transport/chttp2/frame_ping.c',
-        'src/core/transport/chttp2/frame_rst_stream.c',
-        'src/core/transport/chttp2/frame_settings.c',
-        'src/core/transport/chttp2/frame_window_update.c',
-        'src/core/transport/chttp2/hpack_parser.c',
-        'src/core/transport/chttp2/hpack_table.c',
-        'src/core/transport/chttp2/huffsyms.c',
-        'src/core/transport/chttp2/incoming_metadata.c',
-        'src/core/transport/chttp2/parsing.c',
-        'src/core/transport/chttp2/status_conversion.c',
-        'src/core/transport/chttp2/stream_encoder.c',
-        'src/core/transport/chttp2/stream_lists.c',
-        'src/core/transport/chttp2/stream_map.c',
-        'src/core/transport/chttp2/timeout_encoding.c',
-        'src/core/transport/chttp2/varint.c',
-        'src/core/transport/chttp2/writing.c',
-        'src/core/transport/chttp2_transport.c',
-        'src/core/transport/connectivity_state.c',
-        'src/core/transport/metadata.c',
-        'src/core/transport/stream_op.c',
-        'src/core/transport/transport.c',
-        'src/core/transport/transport_op_string.c',
-        'src/core/census/context.c',
-        'src/core/census/initialize.c',
-        'src/core/census/operation.c',
-        'src/core/census/tracing.c',
-      ],
-    },
-  ]
-}
diff --git a/include/grpc/support/port_platform.h b/include/grpc/support/port_platform.h
index 434dd6de874e1988dabb7c8d576e4ec0e19c49ad..f98038da520ea7249fe002cf0d8e54f081ed196c 100644
--- a/include/grpc/support/port_platform.h
+++ b/include/grpc/support/port_platform.h
@@ -181,6 +181,7 @@
 #ifndef _BSD_SOURCE
 #define _BSD_SOURCE
 #endif
+#define GPR_FORBID_UNREACHABLE_CODE
 #define GPR_MSG_IOVLEN_TYPE int
 #if TARGET_OS_IPHONE
 #define GPR_PLATFORM_STRING "ios"
@@ -336,4 +337,15 @@ typedef uintptr_t gpr_uintptr;
 #endif
 #endif
 
+#ifdef GPR_FORBID_UNREACHABLE_CODE
+#define GPR_UNREACHABLE_CODE(STATEMENT)
+#else
+#define GPR_UNREACHABLE_CODE(STATEMENT)             \
+  do {                                              \
+    gpr_log(GPR_ERROR, "Should never reach here."); \
+    abort();                                        \
+    STATEMENT;                                      \
+  } while (0)
+#endif /* GPR_FORBID_UNREACHABLE_CODE */
+
 #endif /* GRPC_SUPPORT_PORT_PLATFORM_H */
diff --git a/package.json b/package.json
index 0eea3475a34b2b1e46ce7ca3ba3b506a2fc72e58..c624c45107edf5735a289c324d627a9de5f76c1e 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
     "lib": "src/node/src"
   },
   "scripts": {
-    "lint": "node ./node_modules/jshint/bin/jshint src/node/src src/node/test src/node/examples src/node/interop src/node/index.js",
+    "lint": "node ./node_modules/jshint/bin/jshint src/node/src src/node/test src/node/interop src/node/index.js",
     "test": "./node_modules/.bin/mocha src/node/test && npm run-script lint",
     "gen_docs": "./node_modules/.bin/jsdoc -c src/node/jsdoc_conf.json",
     "coverage": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha src/node/test"
@@ -53,7 +53,6 @@
     "src/core",
     "test/proto",
     "include",
-    "grpc.gyp",
     "binding.gyp"
   ],
   "main": "src/node/index.js",
diff --git a/src/core/channel/client_channel.c b/src/core/channel/client_channel.c
index 8e7cb27cfd2bb3e7d6f23a6f9ea91bcadc512dc2..ce8af960541574d20d2044bac68609f3f1ca63f2 100644
--- a/src/core/channel/client_channel.c
+++ b/src/core/channel/client_channel.c
@@ -196,13 +196,12 @@ static int is_empty(void *p, int len) {
   return 1;
 }
 
-static void started_call(grpc_exec_ctx *exec_ctx, void *arg,
-                         int iomgr_success) {
+static void started_call_locked(grpc_exec_ctx *exec_ctx, void *arg,
+                                int iomgr_success) {
   call_data *calld = arg;
   grpc_transport_stream_op op;
   int have_waiting;
 
-  gpr_mu_lock(&calld->mu_state);
   if (calld->state == CALL_CANCELLED && calld->subchannel_call != NULL) {
     memset(&op, 0, sizeof(op));
     op.cancel_with_status = GRPC_STATUS_CANCELLED;
@@ -230,10 +229,18 @@ static void started_call(grpc_exec_ctx *exec_ctx, void *arg,
   }
 }
 
+static void started_call(grpc_exec_ctx *exec_ctx, void *arg,
+                         int iomgr_success) {
+  call_data *calld = arg;
+  gpr_mu_lock(&calld->mu_state);
+  started_call_locked(exec_ctx, arg, iomgr_success);
+}
+
 static void picked_target(grpc_exec_ctx *exec_ctx, void *arg,
                           int iomgr_success) {
   call_data *calld = arg;
   grpc_pollset *pollset;
+  grpc_subchannel_call_create_status call_creation_status;
 
   if (calld->picked_channel == NULL) {
     /* treat this like a cancellation */
@@ -248,11 +255,15 @@ static void picked_target(grpc_exec_ctx *exec_ctx, void *arg,
       GPR_ASSERT(calld->state == CALL_WAITING_FOR_PICK);
       calld->state = CALL_WAITING_FOR_CALL;
       pollset = calld->waiting_op.bind_pollset;
-      gpr_mu_unlock(&calld->mu_state);
       grpc_closure_init(&calld->async_setup_task, started_call, calld);
-      grpc_subchannel_create_call(exec_ctx, calld->picked_channel, pollset,
-                                  &calld->subchannel_call,
-                                  &calld->async_setup_task);
+      call_creation_status = grpc_subchannel_create_call(
+          exec_ctx, calld->picked_channel, pollset, &calld->subchannel_call,
+          &calld->async_setup_task);
+      if (call_creation_status == GRPC_SUBCHANNEL_CALL_CREATE_READY) {
+        started_call_locked(exec_ctx, calld, iomgr_success);
+      } else {
+        gpr_mu_unlock(&calld->mu_state);
+      }
     }
   }
 }
@@ -645,9 +656,7 @@ static void destroy_call_elem(grpc_exec_ctx *exec_ctx,
     case CALL_WAITING_FOR_CONFIG:
     case CALL_WAITING_FOR_CALL:
     case CALL_WAITING_FOR_SEND:
-      gpr_log(GPR_ERROR, "should never reach here");
-      abort();
-      break;
+      GPR_UNREACHABLE_CODE(return );
   }
 }
 
diff --git a/src/core/channel/compress_filter.c b/src/core/channel/compress_filter.c
index 182fbf18bfc869f188f9a46898b80ec9a82d52d0..c32f150715c9d453160892bc60a2559bffadd3c6 100644
--- a/src/core/channel/compress_filter.c
+++ b/src/core/channel/compress_filter.c
@@ -242,7 +242,7 @@ static void process_send_ops(grpc_call_element *elem,
         GPR_ASSERT(calld->remaining_slice_bytes > 0);
         /* Increase input ref count, gpr_slice_buffer_add takes ownership.  */
         gpr_slice_buffer_add(&calld->slices, gpr_slice_ref(sop->data.slice));
-        GPR_ASSERT(GPR_SLICE_LENGTH(sop->data.slice) >=
+        GPR_ASSERT(GPR_SLICE_LENGTH(sop->data.slice) <=
                    calld->remaining_slice_bytes);
         calld->remaining_slice_bytes -=
             (gpr_uint32)GPR_SLICE_LENGTH(sop->data.slice);
diff --git a/src/core/client_config/subchannel.c b/src/core/client_config/subchannel.c
index a2c521a20d9f9c38ff116557c2a85515e654f16e..5e84dec0ca5753f60671d9dc86e3df44a69589f6 100644
--- a/src/core/client_config/subchannel.c
+++ b/src/core/client_config/subchannel.c
@@ -335,18 +335,20 @@ static void start_connect(grpc_exec_ctx *exec_ctx, grpc_subchannel *c) {
 
 static void continue_creating_call(grpc_exec_ctx *exec_ctx, void *arg,
                                    int iomgr_success) {
+  grpc_subchannel_call_create_status call_creation_status;
   waiting_for_connect *w4c = arg;
   grpc_subchannel_del_interested_party(exec_ctx, w4c->subchannel, w4c->pollset);
-  grpc_subchannel_create_call(exec_ctx, w4c->subchannel, w4c->pollset,
-                              w4c->target, w4c->notify);
+  call_creation_status = grpc_subchannel_create_call(
+      exec_ctx, w4c->subchannel, w4c->pollset, w4c->target, w4c->notify);
+  GPR_ASSERT(call_creation_status == GRPC_SUBCHANNEL_CALL_CREATE_READY);
+  w4c->notify->cb(exec_ctx, w4c->notify->cb_arg, iomgr_success);
   GRPC_SUBCHANNEL_UNREF(exec_ctx, w4c->subchannel, "waiting_for_connect");
   gpr_free(w4c);
 }
 
-void grpc_subchannel_create_call(grpc_exec_ctx *exec_ctx, grpc_subchannel *c,
-                                 grpc_pollset *pollset,
-                                 grpc_subchannel_call **target,
-                                 grpc_closure *notify) {
+grpc_subchannel_call_create_status grpc_subchannel_create_call(
+    grpc_exec_ctx *exec_ctx, grpc_subchannel *c, grpc_pollset *pollset,
+    grpc_subchannel_call **target, grpc_closure *notify) {
   connection *con;
   gpr_mu_lock(&c->mu);
   if (c->active != NULL) {
@@ -355,7 +357,7 @@ void grpc_subchannel_create_call(grpc_exec_ctx *exec_ctx, grpc_subchannel *c,
     gpr_mu_unlock(&c->mu);
 
     *target = create_call(exec_ctx, con);
-    notify->cb(exec_ctx, notify->cb_arg, 1);
+    return GRPC_SUBCHANNEL_CALL_CREATE_READY;
   } else {
     waiting_for_connect *w4c = gpr_malloc(sizeof(*w4c));
     w4c->next = c->waiting;
@@ -380,6 +382,7 @@ void grpc_subchannel_create_call(grpc_exec_ctx *exec_ctx, grpc_subchannel *c,
     } else {
       gpr_mu_unlock(&c->mu);
     }
+    return GRPC_SUBCHANNEL_CALL_CREATE_PENDING;
   }
 }
 
diff --git a/src/core/client_config/subchannel.h b/src/core/client_config/subchannel.h
index 86b7fa585134f2206ddafbde6bd041aaf0da9f4b..a26d08f02ee4d81f3fb18e35f4377469043b4454 100644
--- a/src/core/client_config/subchannel.h
+++ b/src/core/client_config/subchannel.h
@@ -75,12 +75,22 @@ void grpc_subchannel_call_unref(grpc_exec_ctx *exec_ctx,
                                 grpc_subchannel_call *call
                                     GRPC_SUBCHANNEL_REF_EXTRA_ARGS);
 
-/** construct a call (possibly asynchronously) */
-void grpc_subchannel_create_call(grpc_exec_ctx *exec_ctx,
-                                 grpc_subchannel *subchannel,
-                                 grpc_pollset *pollset,
-                                 grpc_subchannel_call **target,
-                                 grpc_closure *notify);
+typedef enum {
+  GRPC_SUBCHANNEL_CALL_CREATE_READY,
+  GRPC_SUBCHANNEL_CALL_CREATE_PENDING
+} grpc_subchannel_call_create_status;
+
+/** construct a subchannel call (possibly asynchronously).
+ *
+ * If the returned status is \a GRPC_SUBCHANNEL_CALL_CREATE_READY, the call will
+ * return immediately and \a target will point to a connected \a subchannel_call
+ * instance. Note that \a notify will \em not be invoked in this case.
+ * Otherwise, if the returned status is GRPC_SUBCHANNEL_CALL_CREATE_PENDING, the
+ * subchannel call will be created asynchronously, invoking the \a notify
+ * callback upon completion. */
+grpc_subchannel_call_create_status grpc_subchannel_create_call(
+    grpc_exec_ctx *exec_ctx, grpc_subchannel *subchannel, grpc_pollset *pollset,
+    grpc_subchannel_call **target, grpc_closure *notify);
 
 /** process a transport level op */
 void grpc_subchannel_process_transport_op(grpc_exec_ctx *exec_ctx,
diff --git a/src/core/client_config/uri_parser.c b/src/core/client_config/uri_parser.c
index df9f32d403cb6bf89bd8debdd5ae24aa7c7e476f..cbdfffcf8e88ebf5c32944ef5d80b3ddacd2e0d4 100644
--- a/src/core/client_config/uri_parser.c
+++ b/src/core/client_config/uri_parser.c
@@ -37,6 +37,7 @@
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
+#include <grpc/support/port_platform.h>
 #include <grpc/support/string_util.h>
 
 /** a size_t default value... maps to all 1's */
@@ -120,8 +121,7 @@ static int parse_fragment_or_query(const char *uri_text, size_t *i) {
         } else {
           return 1;
         }
-        gpr_log(GPR_ERROR, "should never reach here");
-        abort();
+        GPR_UNREACHABLE_CODE(return 0);
       default:
         (*i) += advance;
         break;
diff --git a/src/core/compression/algorithm.c b/src/core/compression/algorithm.c
index d55e499f5eac8a07046444763a1515e61b0e8583..fd95a3c8912c354e86c19863e35d96b4447ca0f4 100644
--- a/src/core/compression/algorithm.c
+++ b/src/core/compression/algorithm.c
@@ -101,6 +101,7 @@ grpc_compression_algorithm grpc_compression_algorithm_for_level(
     default:
       /* we shouldn't be making it here */
       abort();
+      return GRPC_COMPRESS_NONE;
   }
 }
 
@@ -116,6 +117,7 @@ grpc_compression_level grpc_compression_level_for_algorithm(
     }
   }
   abort();
+  return GRPC_COMPRESS_LEVEL_NONE;
 }
 
 void grpc_compression_options_init(grpc_compression_options *opts) {
diff --git a/src/core/httpcli/parser.c b/src/core/httpcli/parser.c
index 404906d5ae216054c176dec2f8e318399d88cee6..046770c094421f6e2a122115725a50ba0cfc9aeb 100644
--- a/src/core/httpcli/parser.c
+++ b/src/core/httpcli/parser.c
@@ -139,8 +139,7 @@ static int finish_line(grpc_httpcli_parser *parser) {
       }
       break;
     case GRPC_HTTPCLI_BODY:
-      gpr_log(GPR_ERROR, "should never reach here");
-      abort();
+      GPR_UNREACHABLE_CODE(return 0);
   }
 
   parser->cur_line_length = 0;
@@ -165,8 +164,7 @@ static int addbyte(grpc_httpcli_parser *parser, gpr_uint8 byte) {
       } else {
         return 1;
       }
-      gpr_log(GPR_ERROR, "should never reach here");
-      abort();
+      GPR_UNREACHABLE_CODE(return 0);
     case GRPC_HTTPCLI_BODY:
       if (parser->r.body_length == parser->body_capacity) {
         parser->body_capacity = GPR_MAX(8, parser->body_capacity * 3 / 2);
@@ -177,10 +175,7 @@ static int addbyte(grpc_httpcli_parser *parser, gpr_uint8 byte) {
       parser->r.body_length++;
       return 1;
   }
-  gpr_log(GPR_ERROR, "should never reach here");
-  abort();
-
-  return 0;
+  GPR_UNREACHABLE_CODE(return 0);
 }
 
 void grpc_httpcli_parser_init(grpc_httpcli_parser *parser) {
diff --git a/src/core/iomgr/fd_posix.c b/src/core/iomgr/fd_posix.c
index 231bc988a8746dd774d352ce0e3852c7b7f7b2cf..7ff80e6cf82ebce6d234712e8f5a39f8dad886b9 100644
--- a/src/core/iomgr/fd_posix.c
+++ b/src/core/iomgr/fd_posix.c
@@ -286,7 +286,7 @@ static void set_ready(grpc_exec_ctx *exec_ctx, grpc_fd *fd, grpc_closure **st) {
 
 void grpc_fd_shutdown(grpc_exec_ctx *exec_ctx, grpc_fd *fd) {
   gpr_mu_lock(&fd->mu);
-  GPR_ASSERT(!gpr_atm_no_barrier_load(&fd->shutdown));
+  GPR_ASSERT(!fd->shutdown);
   fd->shutdown = 1;
   set_ready_locked(exec_ctx, fd, &fd->read_closure);
   set_ready_locked(exec_ctx, fd, &fd->write_closure);
@@ -320,7 +320,7 @@ gpr_uint32 grpc_fd_begin_poll(grpc_fd *fd, grpc_pollset *pollset,
   gpr_mu_lock(&fd->mu);
 
   /* if we are shutdown, then don't add to the watcher set */
-  if (gpr_atm_no_barrier_load(&fd->shutdown)) {
+  if (fd->shutdown) {
     watcher->fd = NULL;
     watcher->pollset = NULL;
     watcher->worker = NULL;
diff --git a/src/core/iomgr/tcp_client_posix.c b/src/core/iomgr/tcp_client_posix.c
index aca2691c416e64f29d9ea81db9e223fc82ace368..fe20039264121585feaeb058bba68535597f7429 100644
--- a/src/core/iomgr/tcp_client_posix.c
+++ b/src/core/iomgr/tcp_client_posix.c
@@ -191,7 +191,7 @@ static void on_writable(grpc_exec_ctx *exec_ctx, void *acp, int success) {
     goto finish;
   }
 
-  abort();
+  GPR_UNREACHABLE_CODE(return );
 
 finish:
   if (fd != NULL) {
diff --git a/src/core/iomgr/tcp_server_posix.c b/src/core/iomgr/tcp_server_posix.c
index 13bd67576f55d9adf90d36c64c69efcaed7f3c41..99c76dcbe9aeddfd7d303e994fba5526e9057bea 100644
--- a/src/core/iomgr/tcp_server_posix.c
+++ b/src/core/iomgr/tcp_server_posix.c
@@ -352,7 +352,7 @@ static void on_read(grpc_exec_ctx *exec_ctx, void *arg, int success) {
     gpr_free(addr_str);
   }
 
-  abort();
+  GPR_UNREACHABLE_CODE(return );
 
 error:
   gpr_mu_lock(&sp->server->mu);
diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c
index 398db20e8cf0ad817d30a1f5ffda0fed2076bd9b..5e155d83b9cf9ec6c7d7caf8ba6f02297a649231 100644
--- a/src/core/security/credentials.c
+++ b/src/core/security/credentials.c
@@ -181,6 +181,48 @@ void grpc_server_credentials_set_auth_metadata_processor(
   creds->processor = processor;
 }
 
+static void server_credentials_pointer_arg_destroy(void *p) {
+  grpc_server_credentials_unref(p);
+}
+
+static void *server_credentials_pointer_arg_copy(void *p) {
+  return grpc_server_credentials_ref(p);
+}
+
+grpc_arg grpc_server_credentials_to_arg(grpc_server_credentials *p) {
+  grpc_arg arg;
+  memset(&arg, 0, sizeof(grpc_arg));
+  arg.type = GRPC_ARG_POINTER;
+  arg.key = GRPC_SERVER_CREDENTIALS_ARG;
+  arg.value.pointer.p = p;
+  arg.value.pointer.copy = server_credentials_pointer_arg_copy;
+  arg.value.pointer.destroy = server_credentials_pointer_arg_destroy;
+  return arg;
+}
+
+grpc_server_credentials *grpc_server_credentials_from_arg(
+    const grpc_arg *arg) {
+  if (strcmp(arg->key, GRPC_SERVER_CREDENTIALS_ARG) != 0) return NULL;
+  if (arg->type != GRPC_ARG_POINTER) {
+    gpr_log(GPR_ERROR, "Invalid type %d for arg %s", arg->type,
+            GRPC_SERVER_CREDENTIALS_ARG);
+    return NULL;
+  }
+  return arg->value.pointer.p;
+}
+
+grpc_server_credentials *grpc_find_server_credentials_in_args(
+    const grpc_channel_args *args) {
+  size_t i;
+  if (args == NULL) return NULL;
+  for (i = 0; i < args->num_args; i++) {
+    grpc_server_credentials *p =
+        grpc_server_credentials_from_arg(&args->args[i]);
+    if (p != NULL) return p;
+  }
+  return NULL;
+}
+
 /* -- Ssl credentials. -- */
 
 static void ssl_destruct(grpc_credentials *creds) {
diff --git a/src/core/security/credentials.h b/src/core/security/credentials.h
index b213e052d34c02d8c48ebecc968b57e05cd11a04..01203b08f1faf6dcc84a6553d1d4a87b0cec09c7 100644
--- a/src/core/security/credentials.h
+++ b/src/core/security/credentials.h
@@ -215,7 +215,6 @@ typedef struct {
       grpc_server_credentials *c, grpc_security_connector **sc);
 } grpc_server_credentials_vtable;
 
-/* TODO(jboeuf): Add a refcount. */
 struct grpc_server_credentials {
   const grpc_server_credentials_vtable *vtable;
   const char *type;
@@ -231,6 +230,13 @@ grpc_server_credentials *grpc_server_credentials_ref(
 
 void grpc_server_credentials_unref(grpc_server_credentials *creds);
 
+#define GRPC_SERVER_CREDENTIALS_ARG "grpc.server_credentials"
+
+grpc_arg grpc_server_credentials_to_arg(grpc_server_credentials *c);
+grpc_server_credentials *grpc_server_credentials_from_arg(const grpc_arg *arg);
+grpc_server_credentials *grpc_find_server_credentials_in_args(
+    const grpc_channel_args *args);
+
 /* -- Ssl credentials. -- */
 
 typedef struct {
diff --git a/src/core/security/security_context.c b/src/core/security/security_context.c
index fb905e0b228fa06a5b40ebe0599eacf307225b8c..f544c1d943d313d8b9ff21eaa12f85ee1038e0bd 100644
--- a/src/core/security/security_context.c
+++ b/src/core/security/security_context.c
@@ -305,33 +305,43 @@ void grpc_auth_property_reset(grpc_auth_property *property) {
   memset(property, 0, sizeof(grpc_auth_property));
 }
 
-grpc_arg grpc_auth_metadata_processor_to_arg(grpc_auth_metadata_processor *p) {
+static void auth_context_pointer_arg_destroy(void *p) {
+  GRPC_AUTH_CONTEXT_UNREF(p, "auth_context_pointer_arg");
+}
+
+static void *auth_context_pointer_arg_copy(void *p) {
+  return GRPC_AUTH_CONTEXT_REF(p, "auth_context_pointer_arg");
+}
+
+grpc_arg grpc_auth_context_to_arg(grpc_auth_context *p) {
   grpc_arg arg;
   memset(&arg, 0, sizeof(grpc_arg));
   arg.type = GRPC_ARG_POINTER;
-  arg.key = GRPC_AUTH_METADATA_PROCESSOR_ARG;
+  arg.key = GRPC_AUTH_CONTEXT_ARG;
   arg.value.pointer.p = p;
+  arg.value.pointer.copy = auth_context_pointer_arg_copy;
+  arg.value.pointer.destroy = auth_context_pointer_arg_destroy;
   return arg;
 }
 
-grpc_auth_metadata_processor *grpc_auth_metadata_processor_from_arg(
+grpc_auth_context *grpc_auth_context_from_arg(
     const grpc_arg *arg) {
-  if (strcmp(arg->key, GRPC_AUTH_METADATA_PROCESSOR_ARG) != 0) return NULL;
+  if (strcmp(arg->key, GRPC_AUTH_CONTEXT_ARG) != 0) return NULL;
   if (arg->type != GRPC_ARG_POINTER) {
     gpr_log(GPR_ERROR, "Invalid type %d for arg %s", arg->type,
-            GRPC_AUTH_METADATA_PROCESSOR_ARG);
+            GRPC_AUTH_CONTEXT_ARG);
     return NULL;
   }
   return arg->value.pointer.p;
 }
 
-grpc_auth_metadata_processor *grpc_find_auth_metadata_processor_in_args(
+grpc_auth_context *grpc_find_auth_context_in_args(
     const grpc_channel_args *args) {
   size_t i;
   if (args == NULL) return NULL;
   for (i = 0; i < args->num_args; i++) {
-    grpc_auth_metadata_processor *p =
-        grpc_auth_metadata_processor_from_arg(&args->args[i]);
+    grpc_auth_context *p =
+        grpc_auth_context_from_arg(&args->args[i]);
     if (p != NULL) return p;
   }
   return NULL;
diff --git a/src/core/security/security_context.h b/src/core/security/security_context.h
index a9a03064108a162c08efd4de9f33c0f0f654724e..2bbdc4be973e3f66237663b83ea59e6a10536fa0 100644
--- a/src/core/security/security_context.h
+++ b/src/core/security/security_context.h
@@ -103,13 +103,12 @@ typedef struct {
 grpc_server_security_context *grpc_server_security_context_create(void);
 void grpc_server_security_context_destroy(void *ctx);
 
-/* --- Auth metadata processing. --- */
-#define GRPC_AUTH_METADATA_PROCESSOR_ARG "grpc.auth_metadata_processor"
+/* --- Channel args for auth context --- */
+#define GRPC_AUTH_CONTEXT_ARG "grpc.auth_context"
 
-grpc_arg grpc_auth_metadata_processor_to_arg(grpc_auth_metadata_processor *p);
-grpc_auth_metadata_processor *grpc_auth_metadata_processor_from_arg(
-    const grpc_arg *arg);
-grpc_auth_metadata_processor *grpc_find_auth_metadata_processor_in_args(
+grpc_arg grpc_auth_context_to_arg(grpc_auth_context *c);
+grpc_auth_context *grpc_auth_context_from_arg(const grpc_arg *arg);
+grpc_auth_context *grpc_find_auth_context_in_args(
     const grpc_channel_args *args);
 
 #endif /* GRPC_INTERNAL_CORE_SECURITY_SECURITY_CONTEXT_H */
diff --git a/src/core/security/server_auth_filter.c b/src/core/security/server_auth_filter.c
index 30ca9f57a25e2ae770afeba65c4070badf3ea436..2e18369fe8c0deb2c27c0d01236f279a3a2be034 100644
--- a/src/core/security/server_auth_filter.c
+++ b/src/core/security/server_auth_filter.c
@@ -34,7 +34,7 @@
 #include <string.h>
 
 #include "src/core/security/auth_filters.h"
-#include "src/core/security/security_connector.h"
+#include "src/core/security/credentials.h"
 #include "src/core/security/security_context.h"
 
 #include <grpc/support/alloc.h>
@@ -58,8 +58,8 @@ typedef struct call_data {
 } call_data;
 
 typedef struct channel_data {
-  grpc_security_connector *security_connector;
-  grpc_auth_metadata_processor processor;
+  grpc_auth_context *auth_context;
+  grpc_server_credentials *creds;
   grpc_mdctx *mdctx;
 } channel_data;
 
@@ -160,12 +160,12 @@ static void auth_on_recv(grpc_exec_ctx *exec_ctx, void *user_data,
       grpc_stream_op *op = &ops[i];
       if (op->type != GRPC_OP_METADATA || calld->got_client_metadata) continue;
       calld->got_client_metadata = 1;
-      if (chand->processor.process == NULL) continue;
+      if (chand->creds->processor.process == NULL) continue;
       calld->md_op = op;
       calld->md = metadata_batch_to_md_array(&op->data.metadata);
-      chand->processor.process(chand->processor.state, calld->auth_context,
-                               calld->md.metadata, calld->md.count,
-                               on_md_processing_done, elem);
+      chand->creds->processor.process(
+          chand->creds->processor.state, calld->auth_context,
+          calld->md.metadata, calld->md.count, on_md_processing_done, elem);
       return;
     }
   }
@@ -221,7 +221,7 @@ static void init_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem,
   }
   server_ctx = grpc_server_security_context_create();
   server_ctx->auth_context =
-      grpc_auth_context_create(chand->security_connector->auth_context);
+      grpc_auth_context_create(chand->auth_context);
   server_ctx->auth_context->pollset = initial_op->bind_pollset;
   initial_op->context[GRPC_CONTEXT_SECURITY].value = server_ctx;
   initial_op->context[GRPC_CONTEXT_SECURITY].destroy =
@@ -241,9 +241,8 @@ static void init_channel_elem(grpc_exec_ctx *exec_ctx,
                               grpc_channel_element *elem, grpc_channel *master,
                               const grpc_channel_args *args, grpc_mdctx *mdctx,
                               int is_first, int is_last) {
-  grpc_security_connector *sc = grpc_find_security_connector_in_args(args);
-  grpc_auth_metadata_processor *processor =
-      grpc_find_auth_metadata_processor_in_args(args);
+  grpc_auth_context *auth_context = grpc_find_auth_context_in_args(args);
+  grpc_server_credentials *creds = grpc_find_server_credentials_in_args(args);
   /* grab pointers to our data from the channel element */
   channel_data *chand = elem->channel_data;
 
@@ -252,15 +251,14 @@ static void init_channel_elem(grpc_exec_ctx *exec_ctx,
      path */
   GPR_ASSERT(!is_first);
   GPR_ASSERT(!is_last);
-  GPR_ASSERT(sc != NULL);
-  GPR_ASSERT(processor != NULL);
+  GPR_ASSERT(auth_context != NULL);
+  GPR_ASSERT(creds != NULL);
 
   /* initialize members */
-  GPR_ASSERT(!sc->is_client_side);
-  chand->security_connector =
-      GRPC_SECURITY_CONNECTOR_REF(sc, "server_auth_filter");
+  chand->auth_context =
+      GRPC_AUTH_CONTEXT_REF(auth_context, "server_auth_filter");
+  chand->creds = grpc_server_credentials_ref(creds);
   chand->mdctx = mdctx;
-  chand->processor = *processor;
 }
 
 /* Destructor for channel data */
@@ -268,8 +266,8 @@ static void destroy_channel_elem(grpc_exec_ctx *exec_ctx,
                                  grpc_channel_element *elem) {
   /* grab pointers to our data from the channel element */
   channel_data *chand = elem->channel_data;
-  GRPC_SECURITY_CONNECTOR_UNREF(chand->security_connector,
-                                "server_auth_filter");
+  GRPC_AUTH_CONTEXT_UNREF(chand->auth_context, "server_auth_filter");
+  grpc_server_credentials_unref(chand->creds);
 }
 
 const grpc_channel_filter grpc_server_auth_filter = {
diff --git a/src/core/security/server_secure_chttp2.c b/src/core/security/server_secure_chttp2.c
index 881e44a3fedd012397298dad20ad5872ac07a419..82c639e8301debc2fd8f7a5a31d9bc5ef1dda4e8 100644
--- a/src/core/security/server_secure_chttp2.c
+++ b/src/core/security/server_secure_chttp2.c
@@ -93,9 +93,9 @@ static void setup_transport(grpc_exec_ctx *exec_ctx, void *statep,
   grpc_server_secure_state *state = statep;
   grpc_channel_args *args_copy;
   grpc_arg args_to_add[2];
-  args_to_add[0] = grpc_security_connector_to_arg(state->sc);
+  args_to_add[0] = grpc_server_credentials_to_arg(state->creds);
   args_to_add[1] =
-      grpc_auth_metadata_processor_to_arg(&state->creds->processor);
+      grpc_auth_context_to_arg(state->sc->auth_context);
   args_copy = grpc_channel_args_copy_and_add(
       grpc_server_get_channel_args(state->server), args_to_add,
       GPR_ARRAY_SIZE(args_to_add));
diff --git a/src/core/surface/byte_buffer.c b/src/core/surface/byte_buffer.c
index a930949f2d4690fea58ee987205659cc66c7e1f7..fb39c4531d9acca2d2cd2bd21ec4de2098fb2e5d 100644
--- a/src/core/surface/byte_buffer.c
+++ b/src/core/surface/byte_buffer.c
@@ -75,9 +75,7 @@ grpc_byte_buffer *grpc_byte_buffer_copy(grpc_byte_buffer *bb) {
       return grpc_raw_byte_buffer_create(bb->data.raw.slice_buffer.slices,
                                          bb->data.raw.slice_buffer.count);
   }
-  gpr_log(GPR_INFO, "should never get here");
-  abort();
-  return NULL;
+  GPR_UNREACHABLE_CODE(return NULL);
 }
 
 void grpc_byte_buffer_destroy(grpc_byte_buffer *bb) {
@@ -95,6 +93,5 @@ size_t grpc_byte_buffer_length(grpc_byte_buffer *bb) {
     case GRPC_BB_RAW:
       return bb->data.raw.slice_buffer.length;
   }
-  gpr_log(GPR_ERROR, "should never reach here");
-  abort();
+  GPR_UNREACHABLE_CODE(return 0);
 }
diff --git a/src/core/surface/call.c b/src/core/surface/call.c
index 07c3ff6ae4ec7871e028a0105e35465c01e24a3c..386a4a6b29650eb92ee409ec549dfb129a92d2b3 100644
--- a/src/core/surface/call.c
+++ b/src/core/surface/call.c
@@ -434,8 +434,8 @@ static grpc_cq_completion *allocate_completion(grpc_call *call) {
     gpr_mu_unlock(&call->completion_mu);
     return &call->completions[i];
   }
-  gpr_log(GPR_ERROR, "should never reach here");
-  abort();
+  GPR_UNREACHABLE_CODE(return NULL);
+  return NULL;
 }
 
 static void done_completion(grpc_exec_ctx *exec_ctx, void *call,
@@ -526,9 +526,13 @@ static void set_compression_algorithm(grpc_call *call,
   call->compression_algorithm = algo;
 }
 
-grpc_compression_algorithm grpc_call_get_compression_algorithm(
-    const grpc_call *call) {
-  return call->compression_algorithm;
+grpc_compression_algorithm grpc_call_test_only_get_compression_algorithm(
+    grpc_call *call) {
+  grpc_compression_algorithm algorithm;
+  gpr_mu_lock(&call->mu);
+  algorithm = call->compression_algorithm;
+  gpr_mu_unlock(&call->mu);
+  return algorithm;
 }
 
 static void set_encodings_accepted_by_peer(
@@ -562,12 +566,20 @@ static void set_encodings_accepted_by_peer(
   }
 }
 
-gpr_uint32 grpc_call_get_encodings_accepted_by_peer(grpc_call *call) {
-  return call->encodings_accepted_by_peer;
+gpr_uint32 grpc_call_test_only_get_encodings_accepted_by_peer(grpc_call *call) {
+  gpr_uint32 encodings_accepted_by_peer;
+  gpr_mu_lock(&call->mu);
+  encodings_accepted_by_peer = call->encodings_accepted_by_peer;
+  gpr_mu_unlock(&call->mu);
+  return encodings_accepted_by_peer;
 }
 
-gpr_uint32 grpc_call_get_message_flags(const grpc_call *call) {
-  return call->incoming_message_flags;
+gpr_uint32 grpc_call_test_only_get_message_flags(grpc_call *call) {
+  gpr_uint32 flags;
+  gpr_mu_lock(&call->mu);
+  flags = call->incoming_message_flags;
+  gpr_mu_unlock(&call->mu);
+  return flags;
 }
 
 static void set_status_details(grpc_call *call, status_source source,
diff --git a/src/core/surface/call.h b/src/core/surface/call.h
index f421a81619f7854c0f3024afea7721feadc5876c..9b7c6f9bfbed9e4bc174820fac8759bb5b449c9a 100644
--- a/src/core/surface/call.h
+++ b/src/core/surface/call.h
@@ -169,17 +169,6 @@ void *grpc_call_context_get(grpc_call *call, grpc_context_index elem);
 
 gpr_uint8 grpc_call_is_client(grpc_call *call);
 
-grpc_compression_algorithm grpc_call_get_compression_algorithm(
-    const grpc_call *call);
-
-gpr_uint32 grpc_call_get_message_flags(const grpc_call *call);
-
-/** Returns a bitset for the encodings (compression algorithms) supported by \a
- * call's peer.
- *
- * To be indexed by grpc_compression_algorithm enum values. */
-gpr_uint32 grpc_call_get_encodings_accepted_by_peer(grpc_call *call);
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/core/surface/call_test_only.h b/src/core/surface/call_test_only.h
new file mode 100644
index 0000000000000000000000000000000000000000..df4be3248b2a0baa58f16fdfcea7c10f131f01bb
--- /dev/null
+++ b/src/core/surface/call_test_only.h
@@ -0,0 +1,65 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef GRPC_INTERNAL_CORE_SURFACE_CALL_TEST_ONLY_H
+#define GRPC_INTERNAL_CORE_SURFACE_CALL_TEST_ONLY_H
+
+#include <grpc/grpc.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Return the compression algorithm from \a call.
+ *
+ * \warning This function should \b only be used in test code. */
+grpc_compression_algorithm grpc_call_test_only_get_compression_algorithm(
+    grpc_call *call);
+
+/** Return the message flags from \a call.
+ *
+ * \warning This function should \b only be used in test code. */
+gpr_uint32 grpc_call_test_only_get_message_flags(grpc_call *call);
+
+/** Returns a bitset for the encodings (compression algorithms) supported by \a
+ * call's peer.
+ *
+ * To be indexed by grpc_compression_algorithm enum values. */
+gpr_uint32 grpc_call_test_only_get_encodings_accepted_by_peer(grpc_call *call);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GRPC_INTERNAL_CORE_SURFACE_CALL_TEST_ONLY_H */
diff --git a/src/core/surface/channel_connectivity.c b/src/core/surface/channel_connectivity.c
index f72fb0414261d3e63ca1dbc29dcec66c5666b317..ca3c02c536ab23e16c954848f51955b801bd745a 100644
--- a/src/core/surface/channel_connectivity.c
+++ b/src/core/surface/channel_connectivity.c
@@ -100,9 +100,7 @@ static void finished_completion(grpc_exec_ctx *exec_ctx, void *pw,
   switch (w->phase) {
     case WAITING:
     case CALLED_BACK:
-      gpr_log(GPR_ERROR, "should never reach here");
-      abort();
-      break;
+      GPR_UNREACHABLE_CODE(return );
     case CALLING_BACK:
       w->phase = CALLED_BACK;
       break;
@@ -149,9 +147,7 @@ static void partly_done(grpc_exec_ctx *exec_ctx, state_watcher *w,
       w->phase = CALLING_BACK_AND_FINISHED;
       break;
     case CALLING_BACK_AND_FINISHED:
-      gpr_log(GPR_ERROR, "should never reach here");
-      abort();
-      break;
+      GPR_UNREACHABLE_CODE(return );
     case CALLED_BACK:
       delete = 1;
       break;
diff --git a/src/core/surface/completion_queue.c b/src/core/surface/completion_queue.c
index e818ccba488b86c15360751306988fe2bf3820b7..9e552c2cdf85d6a81cc7bfa93b793e8bcb8580e0 100644
--- a/src/core/surface/completion_queue.c
+++ b/src/core/surface/completion_queue.c
@@ -254,8 +254,7 @@ static void del_plucker(grpc_completion_queue *cc, void *tag,
       return;
     }
   }
-  gpr_log(GPR_ERROR, "should never reach here");
-  abort();
+  GPR_UNREACHABLE_CODE(return );
 }
 
 grpc_event grpc_completion_queue_pluck(grpc_completion_queue *cc, void *tag,
diff --git a/src/core/transport/chttp2/frame_data.c b/src/core/transport/chttp2/frame_data.c
index acfa7c002ebac93f934fbb7ce43dff63d9c7a43e..07179a4571e9b139ba17d93c623a0841d6fa6d8d 100644
--- a/src/core/transport/chttp2/frame_data.c
+++ b/src/core/transport/chttp2/frame_data.c
@@ -168,7 +168,5 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
       }
   }
 
-  gpr_log(GPR_ERROR, "should never reach here");
-  abort();
-  return GRPC_CHTTP2_CONNECTION_ERROR;
+  GPR_UNREACHABLE_CODE(return GRPC_CHTTP2_CONNECTION_ERROR);
 }
diff --git a/src/core/transport/chttp2/frame_goaway.c b/src/core/transport/chttp2/frame_goaway.c
index 2ff1eda89b60d7c219f08db1698f9ecb21a05d02..c5758bcb7147b84623250d25ea47810dd736581b 100644
--- a/src/core/transport/chttp2/frame_goaway.c
+++ b/src/core/transport/chttp2/frame_goaway.c
@@ -152,9 +152,7 @@ grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
       }
       return GRPC_CHTTP2_PARSE_OK;
   }
-  gpr_log(GPR_ERROR, "Should never end up here");
-  abort();
-  return GRPC_CHTTP2_CONNECTION_ERROR;
+  GPR_UNREACHABLE_CODE(return GRPC_CHTTP2_CONNECTION_ERROR);
 }
 
 void grpc_chttp2_goaway_append(gpr_uint32 last_stream_id, gpr_uint32 error_code,
diff --git a/src/core/transport/chttp2/hpack_parser.c b/src/core/transport/chttp2/hpack_parser.c
index 20ea51337517bd0fa65a18a8482981aee6bc11a9..20d8312d547bc72392b6c75667c520ad718e3fea 100644
--- a/src/core/transport/chttp2/hpack_parser.c
+++ b/src/core/transport/chttp2/hpack_parser.c
@@ -1166,9 +1166,7 @@ static int append_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
       append_bytes(str, decoded, 3);
       goto b64_byte0;
   }
-  gpr_log(GPR_ERROR, "should never reach here");
-  abort();
-  return 1;
+  GPR_UNREACHABLE_CODE(return 1);
 }
 
 /* append a null terminator to a string */
@@ -1313,9 +1311,7 @@ static int parse_value_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
       return 0;
   }
   /* Add code to prevent return without value error */
-  gpr_log(GPR_ERROR, "Should never reach beyond switch in parse_value_string");
-  abort();
-  return 0;
+  GPR_UNREACHABLE_CODE(return 0);
 }
 
 static int parse_value_string_with_indexed_key(grpc_chttp2_hpack_parser *p,
diff --git a/src/core/transport/chttp2/parsing.c b/src/core/transport/chttp2/parsing.c
index f7a0a10581b879418c401b26682d74ca7334766a..a0977ccaf6cea48063ec2bf7c632e605c38c38dc 100644
--- a/src/core/transport/chttp2/parsing.c
+++ b/src/core/transport/chttp2/parsing.c
@@ -417,14 +417,10 @@ int grpc_chttp2_perform_read(grpc_exec_ctx *exec_ctx,
         transport_parsing->incoming_frame_size -= (gpr_uint32)(end - cur);
         return 1;
       }
-      gpr_log(GPR_ERROR, "should never reach here");
-      abort();
+      GPR_UNREACHABLE_CODE(return 0);
   }
 
-  gpr_log(GPR_ERROR, "should never reach here");
-  abort();
-
-  return 0;
+  GPR_UNREACHABLE_CODE(return 0);
 }
 
 static int init_frame_parser(grpc_chttp2_transport_parsing *transport_parsing) {
@@ -580,9 +576,7 @@ static int init_data_frame_parser(
     case GRPC_CHTTP2_CONNECTION_ERROR:
       return 0;
   }
-  gpr_log(GPR_ERROR, "should never reach here");
-  abort();
-  return 0;
+  GPR_UNREACHABLE_CODE(return 0);
 }
 
 static void free_timeout(void *p) { gpr_free(p); }
@@ -820,7 +814,5 @@ static int parse_frame_slice(grpc_exec_ctx *exec_ctx,
     case GRPC_CHTTP2_CONNECTION_ERROR:
       return 0;
   }
-  gpr_log(GPR_ERROR, "should never reach here");
-  abort();
-  return 0;
+  GPR_UNREACHABLE_CODE(return 0);
 }
diff --git a/src/core/transport/chttp2/stream_encoder.c b/src/core/transport/chttp2/stream_encoder.c
index 83227e677db07cacf633799bba2fc312034ad435..6c7f7a9ea7f068c4e24ea8c0de96278d1fb23936 100644
--- a/src/core/transport/chttp2/stream_encoder.c
+++ b/src/core/transport/chttp2/stream_encoder.c
@@ -428,7 +428,7 @@ static grpc_mdelem *hpack_enc(grpc_chttp2_hpack_compressor *c,
       emit_lithdr_noidx(c, dynidx(c, indices_key), elem, st);
       return elem;
     }
-    abort();
+    GPR_UNREACHABLE_CODE(return NULL);
   }
 
   indices_key = c->indices_keys[HASH_FRAGMENT_3(key_hash)];
@@ -442,7 +442,7 @@ static grpc_mdelem *hpack_enc(grpc_chttp2_hpack_compressor *c,
       emit_lithdr_noidx(c, dynidx(c, indices_key), elem, st);
       return elem;
     }
-    abort();
+    GPR_UNREACHABLE_CODE(return NULL);
   }
 
   /* no elem, key in the table... fall back to literal emission */
@@ -454,7 +454,7 @@ static grpc_mdelem *hpack_enc(grpc_chttp2_hpack_compressor *c,
     emit_lithdr_noidx_v(c, elem, st);
     return elem;
   }
-  abort();
+  GPR_UNREACHABLE_CODE(return NULL);
 }
 
 #define STRLEN_LIT(x) (sizeof(x) - 1)
diff --git a/src/core/tsi/ssl_transport_security.c b/src/core/tsi/ssl_transport_security.c
index 05789f07d497434ecda33b5baf98c5dc34fb5599..22b57964ccabb295a5c37e00bd3814c74602de26 100644
--- a/src/core/tsi/ssl_transport_security.c
+++ b/src/core/tsi/ssl_transport_security.c
@@ -319,8 +319,9 @@ static tsi_result peer_from_x509(X509 *cert, int include_certificate_type,
   /* TODO(jboeuf): Maybe add more properties. */
   GENERAL_NAMES *subject_alt_names =
       X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0);
-  int subject_alt_name_count =
-      (subject_alt_names != NULL) ? sk_GENERAL_NAME_num(subject_alt_names) : 0;
+  int subject_alt_name_count = (subject_alt_names != NULL)
+                                   ? (int)sk_GENERAL_NAME_num(subject_alt_names)
+                                   : 0;
   size_t property_count;
   tsi_result result;
   GPR_ASSERT(subject_alt_name_count >= 0);
@@ -358,7 +359,7 @@ static void log_ssl_error_stack(void) {
   unsigned long err;
   while ((err = ERR_get_error()) != 0) {
     char details[256];
-    ERR_error_string_n(err, details, sizeof(details));
+    ERR_error_string_n((uint32_t)err, details, sizeof(details));
     gpr_log(GPR_ERROR, "%s", details);
   }
 }
@@ -668,7 +669,7 @@ static tsi_result ssl_protector_protect(tsi_frame_protector *self,
   tsi_result result = TSI_OK;
 
   /* First see if we have some pending data in the SSL BIO. */
-  int pending_in_ssl = BIO_pending(impl->from_ssl);
+  int pending_in_ssl = (int)BIO_pending(impl->from_ssl);
   if (pending_in_ssl > 0) {
     *unprotected_bytes_size = 0;
     GPR_ASSERT(*protected_output_frames_size <= INT_MAX);
@@ -726,7 +727,7 @@ static tsi_result ssl_protector_protect_flush(
     impl->buffer_offset = 0;
   }
 
-  pending = BIO_pending(impl->from_ssl);
+  pending = (int)BIO_pending(impl->from_ssl);
   GPR_ASSERT(pending >= 0);
   *still_pending_size = (size_t)pending;
   if (*still_pending_size == 0) return TSI_OK;
@@ -739,7 +740,7 @@ static tsi_result ssl_protector_protect_flush(
     return TSI_INTERNAL_ERROR;
   }
   *protected_output_frames_size = (size_t)read_from_ssl;
-  pending = BIO_pending(impl->from_ssl);
+  pending = (int)BIO_pending(impl->from_ssl);
   GPR_ASSERT(pending >= 0);
   *still_pending_size = (size_t)pending;
   return TSI_OK;
diff --git a/src/cpp/server/server.cc b/src/cpp/server/server.cc
index a44e1d202503415ca0eb711ab985e09f73697254..e09744b842bf797a76742a653e4c7a247cd7dcb2 100644
--- a/src/cpp/server/server.cc
+++ b/src/cpp/server/server.cc
@@ -153,8 +153,7 @@ class Server::SyncRequest GRPC_FINAL : public CompletionQueueTag {
         GPR_ASSERT((*req)->in_flight_);
         return true;
     }
-    gpr_log(GPR_ERROR, "Should never reach here");
-    abort();
+    GPR_UNREACHABLE_CODE(return false);
   }
 
   void SetupRequest() { cq_ = grpc_completion_queue_create(nullptr); }
diff --git a/src/csharp/Grpc.Auth/AuthInterceptors.cs b/src/csharp/Grpc.Auth/AuthInterceptors.cs
index c8ab4d9af6f8966b6dcc1354ee1da6c02954adfc..fa9256677518d13c205a191827ab4d72ca171ee5 100644
--- a/src/csharp/Grpc.Auth/AuthInterceptors.cs
+++ b/src/csharp/Grpc.Auth/AuthInterceptors.cs
@@ -41,8 +41,8 @@ using Grpc.Core.Utils;
 namespace Grpc.Auth
 {
     /// <summary>
-    /// Factory methods to create authorization interceptors. Interceptors created can be registered with gRPC client classes (autogenerated client stubs that
-    /// inherit from <see cref="Grpc.Core.ClientBase"/>).
+    /// Factory methods to create authorization interceptors.
+    /// <seealso cref="GrpcCredentials"/>
     /// </summary>
     public static class AuthInterceptors
     {
@@ -50,31 +50,29 @@ namespace Grpc.Auth
         private const string Schema = "Bearer";
 
         /// <summary>
-        /// Creates interceptor that will obtain access token from any credential type that implements
+        /// Creates an <see cref="AsyncAuthInterceptor"/> that will obtain access token from any credential type that implements
         /// <c>ITokenAccess</c>. (e.g. <c>GoogleCredential</c>).
         /// </summary>
         /// <param name="credential">The credential to use to obtain access tokens.</param>
-        /// <returns>The header interceptor.</returns>
-        public static HeaderInterceptor FromCredential(ITokenAccess credential)
+        /// <returns>The interceptor.</returns>
+        public static AsyncAuthInterceptor FromCredential(ITokenAccess credential)
         {
-            return new HeaderInterceptor((method, authUri, metadata) =>
+            return new AsyncAuthInterceptor(async (authUri, metadata) =>
             {
-                // TODO(jtattermusch): Rethink synchronous wait to obtain the result.
-                var accessToken = credential.GetAccessTokenForRequestAsync(authUri, CancellationToken.None)
-                        .ConfigureAwait(false).GetAwaiter().GetResult();
+                var accessToken = await credential.GetAccessTokenForRequestAsync(authUri, CancellationToken.None).ConfigureAwait(false);
                 metadata.Add(CreateBearerTokenHeader(accessToken));
             });
         }
 
         /// <summary>
-        /// Creates OAuth2 interceptor that will use given access token as authorization.
+        /// Creates an <see cref="AsyncAuthInterceptor"/> that will use given access token as authorization.
         /// </summary>
         /// <param name="accessToken">OAuth2 access token.</param>
-        /// <returns>The header interceptor.</returns>
-        public static HeaderInterceptor FromAccessToken(string accessToken)
+        /// <returns>The interceptor.</returns>
+        public static AsyncAuthInterceptor FromAccessToken(string accessToken)
         {
             Preconditions.CheckNotNull(accessToken);
-            return new HeaderInterceptor((method, authUri, metadata) =>
+            return new AsyncAuthInterceptor(async (authUri, metadata) =>
             {
                 metadata.Add(CreateBearerTokenHeader(accessToken));
             });
diff --git a/src/csharp/Grpc.Auth/Grpc.Auth.csproj b/src/csharp/Grpc.Auth/Grpc.Auth.csproj
index 4fb087d4a342686c0acc4d75da04ea22240e851a..80ab07d2ae531222ef1b5f93823818c6db2bb892 100644
--- a/src/csharp/Grpc.Auth/Grpc.Auth.csproj
+++ b/src/csharp/Grpc.Auth/Grpc.Auth.csproj
@@ -78,6 +78,7 @@
     <Compile Include="..\Grpc.Core\Version.cs">
       <Link>Version.cs</Link>
     </Compile>
+    <Compile Include="GrpcCredentials.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="AuthInterceptors.cs" />
   </ItemGroup>
diff --git a/src/csharp/Grpc.Auth/GrpcCredentials.cs b/src/csharp/Grpc.Auth/GrpcCredentials.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d8b10804c6e2959c7f77287a5408bcce064e3f0e
--- /dev/null
+++ b/src/csharp/Grpc.Auth/GrpcCredentials.cs
@@ -0,0 +1,93 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Threading;
+
+using Google.Apis.Auth.OAuth2;
+using Grpc.Core;
+using Grpc.Core.Utils;
+
+namespace Grpc.Auth
+{
+    /// <summary>
+    /// Factory methods to create instances of <see cref="ChannelCredentials"/> and <see cref="CallCredentials"/> classes.
+    /// </summary>
+    public static class GrpcCredentials
+    {
+        /// <summary>
+        /// Creates a <see cref="MetadataCredentials"/> instance that will obtain access tokens 
+        /// from any credential that implements <c>ITokenAccess</c>. (e.g. <c>GoogleCredential</c>).
+        /// </summary>
+        /// <param name="credential">The credential to use to obtain access tokens.</param>
+        /// <returns>The <c>MetadataCredentials</c> instance.</returns>
+        public static MetadataCredentials Create(ITokenAccess credential)
+        {
+            return new MetadataCredentials(AuthInterceptors.FromCredential(credential));
+        }
+
+        /// <summary>
+        /// Convenience method to create a <see cref="ChannelCredentials"/> instance from
+        /// <c>ITokenAccess</c> credential and <c>SslCredentials</c> instance.
+        /// </summary>
+        /// <param name="credential">The credential to use to obtain access tokens.</param>
+        /// <param name="sslCredentials">The <c>SslCredentials</c> instance.</param>
+        /// <returns>The channel credentials for access token based auth over a secure channel.</returns>
+        public static ChannelCredentials Create(ITokenAccess credential, SslCredentials sslCredentials)
+        {
+            return ChannelCredentials.Create(sslCredentials, Create(credential));
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="MetadataCredentials"/> that will use given access token to authenticate
+        /// with a gRPC service.
+        /// </summary>
+        /// <param name="accessToken">OAuth2 access token.</param>
+        /// /// <returns>The <c>MetadataCredentials</c> instance.</returns>
+        public static MetadataCredentials FromAccessToken(string accessToken)
+        {
+            return new MetadataCredentials(AuthInterceptors.FromAccessToken(accessToken));
+        }
+
+        /// <summary>
+        /// Converts a <c>ITokenAccess</c> object into a <see cref="MetadataCredentials"/> object supported
+        /// by gRPC.
+        /// </summary>
+        /// <param name="credential"></param>
+        /// <returns></returns>
+        public static MetadataCredentials ToGrpcCredentials(this ITokenAccess credential)
+        {
+            return GrpcCredentials.Create(credential);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/ClientBaseTest.cs b/src/csharp/Grpc.Core.Tests/CallCredentialsTest.cs
similarity index 66%
rename from src/csharp/Grpc.Core.Tests/ClientBaseTest.cs
rename to src/csharp/Grpc.Core.Tests/CallCredentialsTest.cs
index 2dc10ebe971a2c536bda5dc903b93e23f2b9c389..451963229a40c32f711727994bad9fc978c84cd9 100644
--- a/src/csharp/Grpc.Core.Tests/ClientBaseTest.cs
+++ b/src/csharp/Grpc.Core.Tests/CallCredentialsTest.cs
@@ -32,6 +32,10 @@
 #endregion
 
 using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
 using Grpc.Core;
 using Grpc.Core.Internal;
 using Grpc.Core.Utils;
@@ -39,24 +43,23 @@ using NUnit.Framework;
 
 namespace Grpc.Core.Tests
 {
-    public class ClientBaseTest
+    public class CallCredentialsTest
     {
         [Test]
-        public void GetAuthUriBase_Valid()
+        public void CallCredentials_ComposeAtLeastTwo()
         {
-            Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("some.googleapi.com"));
-            Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("dns:///some.googleapi.com/"));
-            Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("dns:///some.googleapi.com:443/"));
-            Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("some.googleapi.com:443/"));
+            Assert.Throws(typeof(ArgumentException), () => CallCredentials.Compose(new FakeCallCredentials()));
         }
 
         [Test]
-        public void GetAuthUriBase_Invalid()
+        public void CallCredentials_ToNativeCredentials()
         {
-            Assert.IsNull(ClientBase.GetAuthUriBase("some.googleapi.com:"));
-            Assert.IsNull(ClientBase.GetAuthUriBase("https://some.googleapi.com/"));
-            Assert.IsNull(ClientBase.GetAuthUriBase("dns://some.googleapi.com:443"));  // just two slashes
-            Assert.IsNull(ClientBase.GetAuthUriBase(""));
+            var composite = CallCredentials.Compose(
+                new MetadataCredentials(async (uri, m) => { await Task.Delay(1); }),
+                new MetadataCredentials(async (uri, m) => { await Task.Delay(2); }));
+            using (var nativeComposite = composite.ToNativeCredentials())
+            {
+            }
         }
     }
 }
diff --git a/src/csharp/Grpc.Core.Tests/ChannelCredentialsTest.cs b/src/csharp/Grpc.Core.Tests/ChannelCredentialsTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..489bf38575686a3367d840abf2e00b4ed2623617
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/ChannelCredentialsTest.cs
@@ -0,0 +1,73 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+    public class ChannelCredentialsTest
+    {
+        [Test]
+        public void InsecureCredentials_IsNonComposable()
+        {
+            Assert.IsFalse(ChannelCredentials.Insecure.IsComposable);
+        }
+
+        [Test]
+        public void ChannelCredentials_CreateComposite()
+        {
+            var composite = ChannelCredentials.Create(new FakeChannelCredentials(true), new FakeCallCredentials());
+            Assert.IsFalse(composite.IsComposable);
+
+            Assert.Throws(typeof(ArgumentNullException), () => ChannelCredentials.Create(null, new FakeCallCredentials()));
+            Assert.Throws(typeof(ArgumentNullException), () => ChannelCredentials.Create(new FakeChannelCredentials(true), null));
+            
+            // forbid composing non-composable
+            Assert.Throws(typeof(ArgumentException), () => ChannelCredentials.Create(new FakeChannelCredentials(false), new FakeCallCredentials()));
+        }
+
+        [Test]
+        public void ChannelCredentials_CreateWrapped()
+        {
+            ChannelCredentials.Create(new FakeCallCredentials());
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/ChannelTest.cs b/src/csharp/Grpc.Core.Tests/ChannelTest.cs
index dfbd92879e7cb969b7f7f9225b5de3bc34d1a8cf..f4ae9abefd803002a91685a86400260b471824bc 100644
--- a/src/csharp/Grpc.Core.Tests/ChannelTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ChannelTest.cs
@@ -44,13 +44,13 @@ namespace Grpc.Core.Tests
         [Test]
         public void Constructor_RejectsInvalidParams()
         {
-            Assert.Throws(typeof(ArgumentNullException), () => new Channel(null, Credentials.Insecure));
+            Assert.Throws(typeof(ArgumentNullException), () => new Channel(null, ChannelCredentials.Insecure));
         }
 
         [Test]
         public void State_IdleAfterCreation()
         {
-            var channel = new Channel("localhost", Credentials.Insecure);
+            var channel = new Channel("localhost", ChannelCredentials.Insecure);
             Assert.AreEqual(ChannelState.Idle, channel.State);
             channel.ShutdownAsync().Wait();
         }
@@ -58,7 +58,7 @@ namespace Grpc.Core.Tests
         [Test]
         public void WaitForStateChangedAsync_InvalidArgument()
         {
-            var channel = new Channel("localhost", Credentials.Insecure);
+            var channel = new Channel("localhost", ChannelCredentials.Insecure);
             Assert.Throws(typeof(ArgumentException), () => channel.WaitForStateChangedAsync(ChannelState.FatalFailure));
             channel.ShutdownAsync().Wait();
         }
@@ -66,7 +66,7 @@ namespace Grpc.Core.Tests
         [Test]
         public void ResolvedTarget()
         {
-            var channel = new Channel("127.0.0.1", Credentials.Insecure);
+            var channel = new Channel("127.0.0.1", ChannelCredentials.Insecure);
             Assert.IsTrue(channel.ResolvedTarget.Contains("127.0.0.1"));
             channel.ShutdownAsync().Wait();
         }
@@ -74,7 +74,7 @@ namespace Grpc.Core.Tests
         [Test]
         public void Shutdown_AllowedOnlyOnce()
         {
-            var channel = new Channel("localhost", Credentials.Insecure);
+            var channel = new Channel("localhost", ChannelCredentials.Insecure);
             channel.ShutdownAsync().Wait();
             Assert.Throws(typeof(InvalidOperationException), () => channel.ShutdownAsync().GetAwaiter().GetResult());
         }
diff --git a/src/csharp/Grpc.IntegrationTesting/proto/empty.proto b/src/csharp/Grpc.Core.Tests/FakeCredentials.cs
similarity index 62%
rename from src/csharp/Grpc.IntegrationTesting/proto/empty.proto
rename to src/csharp/Grpc.Core.Tests/FakeCredentials.cs
index 6d0eb937d674ca3ee9aaf9c2ed8313250cd76647..87d55cd276ae5601b84a1dd3b7db97dd4aac5fe7 100644
--- a/src/csharp/Grpc.IntegrationTesting/proto/empty.proto
+++ b/src/csharp/Grpc.Core.Tests/FakeCredentials.cs
@@ -1,3 +1,4 @@
+#region Copyright notice and license
 
 // Copyright 2015, Google Inc.
 // All rights reserved.
@@ -28,16 +29,45 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-syntax = "proto3";
+#endregion
 
-package grpc.testing;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
 
-// An empty message that you can re-use to avoid defining duplicated empty
-// messages in your project. A typical example is to use it as argument or the
-// return value of a service API. For instance:
-//
-//   service Foo {
-//     rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { };
-//   };
-//
-message Empty {}
+namespace Grpc.Core.Tests
+{
+    internal class FakeChannelCredentials : ChannelCredentials
+    {
+        readonly bool composable;
+
+        public FakeChannelCredentials(bool composable)
+        {
+            this.composable = composable;
+        }
+
+        internal override bool IsComposable
+        {
+            get { return composable; }
+        }
+
+        internal override CredentialsSafeHandle ToNativeCredentials()
+        {
+            return null;
+        }
+    }
+
+    internal class FakeCallCredentials : CallCredentials
+    {
+        internal override CredentialsSafeHandle ToNativeCredentials()
+        {
+            return null;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
index f730936062daa1efeec6c00c425a27cbcde9a25d..91d072ababecdd6360923320f302fd7a086b74a2 100644
--- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
+++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
@@ -63,8 +63,10 @@
     <Compile Include="..\Grpc.Core\Version.cs">
       <Link>Version.cs</Link>
     </Compile>
-    <Compile Include="ClientBaseTest.cs" />
+    <Compile Include="CallCredentialsTest.cs" />
+    <Compile Include="FakeCredentials.cs" />
     <Compile Include="MarshallingErrorsTest.cs" />
+    <Compile Include="ChannelCredentialsTest.cs" />
     <Compile Include="ShutdownTest.cs" />
     <Compile Include="Internal\AsyncCallTest.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
diff --git a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
index 685c5f7d6cb7a314cbfdee015e07ed79cd1a5efd..246072bff1305cc605e671fa3c766eb5d235acb7 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
@@ -49,7 +49,7 @@ namespace Grpc.Core.Internal.Tests
         [SetUp]
         public void Init()
         {
-            channel = new Channel("localhost", Credentials.Insecure);
+            channel = new Channel("localhost", ChannelCredentials.Insecure);
 
             fakeCall = new FakeNativeCall();
 
diff --git a/src/csharp/Grpc.Core.Tests/MarshallingErrorsTest.cs b/src/csharp/Grpc.Core.Tests/MarshallingErrorsTest.cs
index 83707e0c6da91c35da05d321341a6617537925d0..37fb36946aff1a3d8a5fce783eb94ea72e50e9bc 100644
--- a/src/csharp/Grpc.Core.Tests/MarshallingErrorsTest.cs
+++ b/src/csharp/Grpc.Core.Tests/MarshallingErrorsTest.cs
@@ -119,7 +119,7 @@ namespace Grpc.Core.Tests
         [Test]
         public void RequestParsingError_UnaryRequest()
         {
-            helper.UnaryHandler = new  UnaryServerMethod<string, string>((request, context) =>
+            helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
             {
                 return Task.FromResult("RESPONSE");
             });
@@ -161,7 +161,7 @@ namespace Grpc.Core.Tests
         {
             helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
             {
-                CollectionAssert.AreEqual(new [] {"A", "B"}, await requestStream.ToListAsync());
+                CollectionAssert.AreEqual(new[] { "A", "B" }, await requestStream.ToListAsync());
                 return "RESPONSE";
             });
             var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall());
diff --git a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
index 765732c7687eaa7a34e490cc6ad93c450fad5f9c..567e04eddccbc4cf95d1a11b79267ca1571fae23 100644
--- a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
+++ b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
@@ -154,7 +154,7 @@ namespace Grpc.Core.Tests
         {
             if (channel == null)
             {
-                channel = new Channel(Host, GetServer().Ports.Single().BoundPort, Credentials.Insecure);
+                channel = new Channel(Host, GetServer().Ports.Single().BoundPort, ChannelCredentials.Insecure);
             }
             return channel;
         }
diff --git a/src/csharp/Grpc.Core.Tests/PInvokeTest.cs b/src/csharp/Grpc.Core.Tests/PInvokeTest.cs
index 714c2f7494cc3aa071d43dcb1142d56f504543b1..073c502daf3f7583b9cd28f35ae4944d09a29ab4 100644
--- a/src/csharp/Grpc.Core.Tests/PInvokeTest.cs
+++ b/src/csharp/Grpc.Core.Tests/PInvokeTest.cs
@@ -60,7 +60,7 @@ namespace Grpc.Core.Tests
         public void CompletionQueueCreateDestroyBenchmark()
         {
             BenchmarkUtil.RunBenchmark(
-                100000, 1000000,
+                10, 10,
                 () =>
                 {
                     CompletionQueueSafeHandle cq = CompletionQueueSafeHandle.Create();
diff --git a/src/csharp/Grpc.Core/CallCredentials.cs b/src/csharp/Grpc.Core/CallCredentials.cs
new file mode 100644
index 0000000000000000000000000000000000000000..809c9f412d0ed4bf4126561fb5a6b9f5b29fe89f
--- /dev/null
+++ b/src/csharp/Grpc.Core/CallCredentials.cs
@@ -0,0 +1,142 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Client-side call credentials. Provide authorization with per-call granularity.
+    /// </summary>
+    public abstract class CallCredentials
+    {
+        /// <summary>
+        /// Composes multiple multiple <c>CallCredentials</c> objects into
+        /// a single <c>CallCredentials</c> object.
+        /// </summary>
+        /// <param name="credentials">credentials to compose</param>
+        /// <returns>The new <c>CompositeCallCredentials</c></returns>
+        public static CallCredentials Compose(params CallCredentials[] credentials)
+        {
+            return new CompositeCallCredentials(credentials);
+        }
+
+        /// <summary>
+        /// Creates native object for the credentials.
+        /// </summary>
+        /// <returns>The native credentials.</returns>
+        internal abstract CredentialsSafeHandle ToNativeCredentials();
+    }
+
+    /// <summary>
+    /// Asynchronous authentication interceptor for <see cref="MetadataCredentials"/>.
+    /// </summary>
+    /// <param name="authUri">URL of a service to which current remote call needs to authenticate</param>
+    /// <param name="metadata">Metadata to populate with entries that will be added to outgoing call's headers.</param>
+    /// <returns></returns>
+    public delegate Task AsyncAuthInterceptor(string authUri, Metadata metadata);
+
+    /// <summary>
+    /// Client-side credentials that delegate metadata based auth to an interceptor.
+    /// The interceptor is automatically invoked for each remote call that uses <c>MetadataCredentials.</c>
+    /// </summary>
+    public class MetadataCredentials : CallCredentials
+    {
+        readonly AsyncAuthInterceptor interceptor;
+
+        /// <summary>
+        /// Initializes a new instance of <c>MetadataCredentials</c> class.
+        /// </summary>
+        /// <param name="interceptor">authentication interceptor</param>
+        public MetadataCredentials(AsyncAuthInterceptor interceptor)
+        {
+            this.interceptor = interceptor;
+        }
+
+        internal override CredentialsSafeHandle ToNativeCredentials()
+        {
+            NativeMetadataCredentialsPlugin plugin = new NativeMetadataCredentialsPlugin(interceptor);
+            return plugin.Credentials;
+        }
+    }
+
+    /// <summary>
+    /// Credentials that allow composing multiple credentials objects into one <see cref="CallCredentials"/> object.
+    /// </summary>
+    internal sealed class CompositeCallCredentials : CallCredentials
+    {
+        readonly List<CallCredentials> credentials;
+
+        /// <summary>
+        /// Initializes a new instance of <c>CompositeCallCredentials</c> class.
+        /// The resulting credentials object will be composite of all the credentials specified as parameters.
+        /// </summary>
+        /// <param name="credentials">credentials to compose</param>
+        public CompositeCallCredentials(params CallCredentials[] credentials)
+        {
+            Preconditions.CheckArgument(credentials.Length >= 2, "Composite credentials object can only be created from 2 or more credentials.");
+            this.credentials = new List<CallCredentials>(credentials);
+        }
+
+        internal override CredentialsSafeHandle ToNativeCredentials()
+        {
+            return ToNativeRecursive(0);
+        }
+
+        // Recursive descent makes managing lifetime of intermediate CredentialSafeHandle instances easier.
+        // In practice, we won't usually see composites from more than two credentials anyway.
+        private CredentialsSafeHandle ToNativeRecursive(int startIndex)
+        {
+            if (startIndex == credentials.Count - 1)
+            {
+                return credentials[startIndex].ToNativeCredentials();
+            }
+
+            using (var cred1 = credentials[startIndex].ToNativeCredentials())
+            using (var cred2 = ToNativeRecursive(startIndex + 1))
+            {
+                var nativeComposite = CredentialsSafeHandle.CreateComposite(cred1, cred2);
+                if (nativeComposite.IsInvalid)
+                {
+                    throw new ArgumentException("Error creating native composite credentials. Likely, this is because you are trying to compose incompatible credentials.");
+                }
+                return nativeComposite;
+            }
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/CallOptions.cs b/src/csharp/Grpc.Core/CallOptions.cs
index c3bc9c31564753582c156b54a169353c8662430e..c0f94c63c2f01bc3853fd7b42f55b2f6812a1d94 100644
--- a/src/csharp/Grpc.Core/CallOptions.cs
+++ b/src/csharp/Grpc.Core/CallOptions.cs
@@ -49,6 +49,7 @@ namespace Grpc.Core
         CancellationToken cancellationToken;
         WriteOptions writeOptions;
         ContextPropagationToken propagationToken;
+        CallCredentials credentials;
 
         /// <summary>
         /// Creates a new instance of <c>CallOptions</c> struct.
@@ -58,14 +59,16 @@ namespace Grpc.Core
         /// <param name="cancellationToken">Can be used to request cancellation of the call.</param>
         /// <param name="writeOptions">Write options that will be used for this call.</param>
         /// <param name="propagationToken">Context propagation token obtained from <see cref="ServerCallContext"/>.</param>
+        /// <param name="credentials">Credentials to use for this call.</param>
         public CallOptions(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken),
-                           WriteOptions writeOptions = null, ContextPropagationToken propagationToken = null)
+                           WriteOptions writeOptions = null, ContextPropagationToken propagationToken = null, CallCredentials credentials = null)
         {
             this.headers = headers;
             this.deadline = deadline;
             this.cancellationToken = cancellationToken;
             this.writeOptions = writeOptions;
             this.propagationToken = propagationToken;
+            this.credentials = credentials;
         }
 
         /// <summary>
@@ -114,6 +117,17 @@ namespace Grpc.Core
             }
         }
 
+        /// <summary>
+        /// Credentials to use for this call.
+        /// </summary>
+        public CallCredentials Credentials
+        {
+            get
+            {
+                return this.credentials;
+            }
+        }
+
         /// <summary>
         /// Returns new instance of <see cref="CallOptions"/> with
         /// <c>Headers</c> set to the value provided. Values of all other fields are preserved.
diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs
index f1942727cde48a9837a533332e7f9a34bcd3aac1..6b99055d4c893cb7b92d703d11b4bdbc2360fb71 100644
--- a/src/csharp/Grpc.Core/Channel.cs
+++ b/src/csharp/Grpc.Core/Channel.cs
@@ -68,7 +68,7 @@ namespace Grpc.Core
         /// <param name="target">Target of the channel.</param>
         /// <param name="credentials">Credentials to secure the channel.</param>
         /// <param name="options">Channel options.</param>
-        public Channel(string target, Credentials credentials, IEnumerable<ChannelOption> options = null)
+        public Channel(string target, ChannelCredentials credentials, IEnumerable<ChannelOption> options = null)
         {
             this.target = Preconditions.CheckNotNull(target, "target");
             this.environment = GrpcEnvironment.AddRef();
@@ -96,7 +96,7 @@ namespace Grpc.Core
         /// <param name="port">The port.</param>
         /// <param name="credentials">Credentials to secure the channel.</param>
         /// <param name="options">Channel options.</param>
-        public Channel(string host, int port, Credentials credentials, IEnumerable<ChannelOption> options = null) :
+        public Channel(string host, int port, ChannelCredentials credentials, IEnumerable<ChannelOption> options = null) :
             this(string.Format("{0}:{1}", host, port), credentials, options)
         {
         }
diff --git a/src/csharp/Grpc.Core/ChannelCredentials.cs b/src/csharp/Grpc.Core/ChannelCredentials.cs
new file mode 100644
index 0000000000000000000000000000000000000000..599674e02bdbf71d430097693888c04da7318601
--- /dev/null
+++ b/src/csharp/Grpc.Core/ChannelCredentials.cs
@@ -0,0 +1,238 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Client-side channel credentials. Used for creation of a secure channel.
+    /// </summary>
+    public abstract class ChannelCredentials
+    {
+        static readonly ChannelCredentials InsecureInstance = new InsecureCredentialsImpl();
+
+        /// <summary>
+        /// Returns instance of credentials that provides no security and 
+        /// will result in creating an unsecure channel with no encryption whatsoever.
+        /// </summary>
+        public static ChannelCredentials Insecure
+        {
+            get
+            {
+                return InsecureInstance;
+            }
+        }
+
+        /// <summary>
+        /// Creates a new instance of <c>ChannelCredentials</c> class by composing
+        /// given channel credentials with call credentials.
+        /// </summary>
+        /// <param name="channelCredentials">Channel credentials.</param>
+        /// <param name="callCredentials">Call credentials.</param>
+        /// <returns>The new composite <c>ChannelCredentials</c></returns>
+        public static ChannelCredentials Create(ChannelCredentials channelCredentials, CallCredentials callCredentials)
+        {
+            return new CompositeChannelCredentials(channelCredentials, callCredentials);
+        }
+
+        /// <summary>
+        /// Creates a new instance of <c>ChannelCredentials</c> by wrapping
+        /// an instance of <c>CallCredentials</c>.
+        /// </summary>
+        /// <param name="callCredentials">Call credentials.</param>
+        /// <returns>The <c>ChannelCredentials</c> wrapping given call credentials.</returns>
+        public static ChannelCredentials Create(CallCredentials callCredentials)
+        {
+            return new WrappedCallCredentials(callCredentials);
+        }
+
+        /// <summary>
+        /// Creates native object for the credentials. May return null if insecure channel
+        /// should be created.
+        /// </summary>
+        /// <returns>The native credentials.</returns>
+        internal abstract CredentialsSafeHandle ToNativeCredentials();
+
+        /// <summary>
+        /// Returns <c>true</c> if this credential type allows being composed by <c>CompositeCredentials</c>.
+        /// </summary>
+        internal virtual bool IsComposable
+        {
+            get { return false; }
+        }
+
+        private sealed class InsecureCredentialsImpl : ChannelCredentials
+        {
+            internal override CredentialsSafeHandle ToNativeCredentials()
+            {
+                return null;
+            }
+        }
+    }
+
+    /// <summary>
+    /// Client-side SSL credentials.
+    /// </summary>
+    public sealed class SslCredentials : ChannelCredentials
+    {
+        readonly string rootCertificates;
+        readonly KeyCertificatePair keyCertificatePair;
+
+        /// <summary>
+        /// Creates client-side SSL credentials loaded from
+        /// disk file pointed to by the GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment variable.
+        /// If that fails, gets the roots certificates from a well known place on disk.
+        /// </summary>
+        public SslCredentials() : this(null, null)
+        {
+        }
+
+        /// <summary>
+        /// Creates client-side SSL credentials from
+        /// a string containing PEM encoded root certificates.
+        /// </summary>
+        public SslCredentials(string rootCertificates) : this(rootCertificates, null)
+        {
+        }
+            
+        /// <summary>
+        /// Creates client-side SSL credentials.
+        /// </summary>
+        /// <param name="rootCertificates">string containing PEM encoded server root certificates.</param>
+        /// <param name="keyCertificatePair">a key certificate pair.</param>
+        public SslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair)
+        {
+            this.rootCertificates = rootCertificates;
+            this.keyCertificatePair = keyCertificatePair;
+        }
+
+        /// <summary>
+        /// PEM encoding of the server root certificates.
+        /// </summary>
+        public string RootCertificates
+        {
+            get
+            {
+                return this.rootCertificates;
+            }
+        }
+
+        /// <summary>
+        /// Client side key and certificate pair.
+        /// If null, client will not use key and certificate pair.
+        /// </summary>
+        public KeyCertificatePair KeyCertificatePair
+        {
+            get
+            {
+                return this.keyCertificatePair;
+            }
+        }
+
+        // Composing composite makes no sense.
+        internal override bool IsComposable
+        {
+            get { return true; }
+        }
+
+        internal override CredentialsSafeHandle ToNativeCredentials()
+        {
+            return CredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair);
+        }
+    }
+
+    /// <summary>
+    /// Credentials that allow composing one <see cref="ChannelCredentials"/> object and 
+    /// one or more <see cref="CallCredentials"/> objects into a single <see cref="ChannelCredentials"/>.
+    /// </summary>
+    internal sealed class CompositeChannelCredentials : ChannelCredentials
+    {
+        readonly ChannelCredentials channelCredentials;
+        readonly CallCredentials callCredentials;
+
+        /// <summary>
+        /// Initializes a new instance of <c>CompositeChannelCredentials</c> class.
+        /// The resulting credentials object will be composite of all the credentials specified as parameters.
+        /// </summary>
+        /// <param name="channelCredentials">channelCredentials to compose</param>
+        /// <param name="callCredentials">channelCredentials to compose</param>
+        public CompositeChannelCredentials(ChannelCredentials channelCredentials, CallCredentials callCredentials)
+        {
+            this.channelCredentials = Preconditions.CheckNotNull(channelCredentials);
+            this.callCredentials = Preconditions.CheckNotNull(callCredentials);
+            Preconditions.CheckArgument(channelCredentials.IsComposable, "Supplied channel credentials do not allow composition.");
+        }
+
+        internal override CredentialsSafeHandle ToNativeCredentials()
+        {
+            using (var cred1 = channelCredentials.ToNativeCredentials())
+            using (var cred2 = callCredentials.ToNativeCredentials())
+            {
+                var nativeComposite = CredentialsSafeHandle.CreateComposite(cred1, cred2);
+                if (nativeComposite.IsInvalid)
+                {
+                    throw new ArgumentException("Error creating native composite credentials. Likely, this is because you are trying to compose incompatible credentials.");
+                }
+                return nativeComposite;
+            }
+        }
+    }
+
+    /// <summary>
+    /// Credentials wrapping <see cref="CallCredentials"/> as <see cref="ChannelCredentials"/>.
+    /// </summary>
+    internal sealed class WrappedCallCredentials : ChannelCredentials
+    {
+        readonly CallCredentials callCredentials;
+
+        /// <summary>
+        /// Wraps instance of <c>CallCredentials</c> as <c>ChannelCredentials</c>.
+        /// </summary>
+        /// <param name="callCredentials">credentials to wrap</param>
+        public WrappedCallCredentials(CallCredentials callCredentials)
+        {
+            this.callCredentials = Preconditions.CheckNotNull(callCredentials);
+        }
+
+        internal override CredentialsSafeHandle ToNativeCredentials()
+        {
+            return callCredentials.ToNativeCredentials();
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/ClientBase.cs b/src/csharp/Grpc.Core/ClientBase.cs
index f4533e735cbc802fbab34ee1766c13b3a5af7c7f..e5b398062b2c489891d38cc71ffee03eb274e059 100644
--- a/src/csharp/Grpc.Core/ClientBase.cs
+++ b/src/csharp/Grpc.Core/ClientBase.cs
@@ -40,18 +40,17 @@ namespace Grpc.Core
     /// <summary>
     /// Interceptor for call headers.
     /// </summary>
-    public delegate void HeaderInterceptor(IMethod method, string authUri, Metadata metadata);
+    /// <remarks>Header interceptor is no longer to recommented way to perform authentication.
+    /// For header (initial metadata) based auth such as OAuth2 or JWT access token, use <see cref="MetadataCredentials"/>.
+    /// </remarks>
+    public delegate void HeaderInterceptor(IMethod method, Metadata metadata);
 
     /// <summary>
     /// Base class for client-side stubs.
     /// </summary>
     public abstract class ClientBase
     {
-        // Regex for removal of the optional DNS scheme, trailing port, and trailing backslash
-        static readonly Regex ChannelTargetPattern = new Regex(@"^(dns:\/{3})?([^:\/]+)(:\d+)?\/?$");
-
         readonly Channel channel;
-        readonly string authUriBase;
 
         /// <summary>
         /// Initializes a new instance of <c>ClientBase</c> class.
@@ -60,13 +59,14 @@ namespace Grpc.Core
         public ClientBase(Channel channel)
         {
             this.channel = channel;
-            this.authUriBase = GetAuthUriBase(channel.Target);
         }
 
         /// <summary>
-        /// Can be used to register a custom header (request metadata) interceptor.
+        /// Can be used to register a custom header interceptor.
         /// The interceptor is invoked each time a new call on this client is started.
+        /// It is not recommented to use header interceptor to add auth headers to RPC calls.
         /// </summary>
+        /// <seealso cref="HeaderInterceptor"/>
         public HeaderInterceptor HeaderInterceptor
         {
             get;
@@ -115,24 +115,9 @@ namespace Grpc.Core
                 {
                     options = options.WithHeaders(new Metadata());
                 }
-                var authUri = authUriBase != null ? authUriBase + method.ServiceName : null;
-                interceptor(method, authUri, options.Headers);
+                interceptor(method, options.Headers);
             }
             return new CallInvocationDetails<TRequest, TResponse>(channel, method, Host, options);
         }
-
-        /// <summary>
-        /// Creates Auth URI base from channel's target (the one passed at channel creation).
-        /// Fully-qualified service name is to be appended to this.
-        /// </summary>
-        internal static string GetAuthUriBase(string target)
-        {
-            var match = ChannelTargetPattern.Match(target);
-            if (!match.Success)
-            {
-                return null;
-            }
-            return "https://" + match.Groups[2].Value + "/";
-        }
     }
 }
diff --git a/src/csharp/Grpc.Core/Credentials.cs b/src/csharp/Grpc.Core/Credentials.cs
deleted file mode 100644
index 4fcac0c4c00e97d6c5c25390ee1027c8912e8c0a..0000000000000000000000000000000000000000
--- a/src/csharp/Grpc.Core/Credentials.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-#region Copyright notice and license
-
-// Copyright 2015, Google Inc.
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//     * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#endregion
-
-using System;
-using Grpc.Core.Internal;
-
-namespace Grpc.Core
-{
-    /// <summary>
-    /// Client-side credentials. Used for creation of a secure channel.
-    /// </summary>
-    public abstract class Credentials
-    {
-        static readonly Credentials InsecureInstance = new InsecureCredentialsImpl();
-
-        /// <summary>
-        /// Returns instance of credential that provides no security and 
-        /// will result in creating an unsecure channel with no encryption whatsoever.
-        /// </summary>
-        public static Credentials Insecure
-        {
-            get
-            {
-                return InsecureInstance;
-            }
-        }
-
-        /// <summary>
-        /// Creates native object for the credentials. May return null if insecure channel
-        /// should be created.
-        /// </summary>
-        /// <returns>The native credentials.</returns>
-        internal abstract CredentialsSafeHandle ToNativeCredentials();
-
-        private sealed class InsecureCredentialsImpl : Credentials
-        {
-            internal override CredentialsSafeHandle ToNativeCredentials()
-            {
-                return null;
-            }
-        }
-    }
-
-    /// <summary>
-    /// Client-side SSL credentials.
-    /// </summary>
-    public sealed class SslCredentials : Credentials
-    {
-        readonly string rootCertificates;
-        readonly KeyCertificatePair keyCertificatePair;
-
-        /// <summary>
-        /// Creates client-side SSL credentials loaded from
-        /// disk file pointed to by the GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment variable.
-        /// If that fails, gets the roots certificates from a well known place on disk.
-        /// </summary>
-        public SslCredentials() : this(null, null)
-        {
-        }
-
-        /// <summary>
-        /// Creates client-side SSL credentials from
-        /// a string containing PEM encoded root certificates.
-        /// </summary>
-        public SslCredentials(string rootCertificates) : this(rootCertificates, null)
-        {
-        }
-            
-        /// <summary>
-        /// Creates client-side SSL credentials.
-        /// </summary>
-        /// <param name="rootCertificates">string containing PEM encoded server root certificates.</param>
-        /// <param name="keyCertificatePair">a key certificate pair.</param>
-        public SslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair)
-        {
-            this.rootCertificates = rootCertificates;
-            this.keyCertificatePair = keyCertificatePair;
-        }
-
-        /// <summary>
-        /// PEM encoding of the server root certificates.
-        /// </summary>
-        public string RootCertificates
-        {
-            get
-            {
-                return this.rootCertificates;
-            }
-        }
-
-        /// <summary>
-        /// Client side key and certificate pair.
-        /// If null, client will not use key and certificate pair.
-        /// </summary>
-        public KeyCertificatePair KeyCertificatePair
-        {
-            get
-            {
-                return this.keyCertificatePair;
-            }
-        }
-
-        internal override CredentialsSafeHandle ToNativeCredentials()
-        {
-            return CredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair);
-        }
-    }
-}
diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj
index ad2af17bc75abcd8b81568f35ee8b35a6a127c5a..92d4e19eac25bc10c5d908be065230273a5e8f85 100644
--- a/src/csharp/Grpc.Core/Grpc.Core.csproj
+++ b/src/csharp/Grpc.Core/Grpc.Core.csproj
@@ -48,7 +48,9 @@
   <ItemGroup>
     <Compile Include="AsyncDuplexStreamingCall.cs" />
     <Compile Include="AsyncServerStreamingCall.cs" />
+    <Compile Include="CallCredentials.cs" />
     <Compile Include="IClientStreamWriter.cs" />
+    <Compile Include="Internal\NativeMetadataCredentialsPlugin.cs" />
     <Compile Include="Internal\INativeCall.cs" />
     <Compile Include="IServerStreamWriter.cs" />
     <Compile Include="IAsyncStreamWriter.cs" />
@@ -79,7 +81,7 @@
     <Compile Include="Utils\AsyncStreamExtensions.cs" />
     <Compile Include="Utils\BenchmarkUtil.cs" />
     <Compile Include="Internal\CredentialsSafeHandle.cs" />
-    <Compile Include="Credentials.cs" />
+    <Compile Include="ChannelCredentials.cs" />
     <Compile Include="Internal\ChannelArgsSafeHandle.cs" />
     <Compile Include="Internal\AsyncCompletion.cs" />
     <Compile Include="Internal\AsyncCallBase.cs" />
diff --git a/src/csharp/Grpc.Core/Grpc.Core.nuspec b/src/csharp/Grpc.Core/Grpc.Core.nuspec
index 06de55c8c3c012149fa119c4eeeece7258c3d1c1..67a04afc22d2b83ed625e328c7bec3673d27ff3f 100644
--- a/src/csharp/Grpc.Core/Grpc.Core.nuspec
+++ b/src/csharp/Grpc.Core/Grpc.Core.nuspec
@@ -16,7 +16,7 @@
     <tags>gRPC RPC Protocol HTTP/2</tags>
     <dependencies>
       <dependency id="Ix-Async" version="1.2.3" />
-      <dependency id="grpc.native.csharp_ext" version="$GrpcNativeCsharpExtVersion$" />
+      <dependency id="grpc.native.csharp" version="$GrpcNativeCsharpVersion$" />
     </dependencies>
   </metadata>
   <files>
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
index e3b00781c6216885cc9025ed26f3ef35d388e5b7..800462c85407a45a822a4fa5e00aa863255787b3 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
@@ -344,9 +344,13 @@ namespace Grpc.Core.Internal
 
             var parentCall = details.Options.PropagationToken != null ? details.Options.PropagationToken.ParentCall : CallSafeHandle.NullInstance;
 
-            return details.Channel.Handle.CreateCall(environment.CompletionRegistry,
-                parentCall, ContextPropagationToken.DefaultMask, cq,
-                details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value));
+            var credentials = details.Options.Credentials;
+            using (var nativeCredentials = credentials != null ? credentials.ToNativeCredentials() : null)
+            {
+                return details.Channel.Handle.CreateCall(environment.CompletionRegistry,
+                    parentCall, ContextPropagationToken.DefaultMask, cq,
+                    details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value), nativeCredentials);
+            }
         }
 
         // Make sure that once cancellationToken for this call is cancelled, Cancel() will be called.
diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
index c3611a7761f5213e58c84a08a30a67b693a88f97..0be7a4dd3a1f3033321072360457ca7865baef95 100644
--- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
@@ -98,6 +98,9 @@ namespace Grpc.Core.Internal
         static extern GRPCCallError grpcsharp_call_send_initial_metadata(CallSafeHandle call,
             BatchContextSafeHandle ctx, MetadataArraySafeHandle metadataArray);
 
+        [DllImport("grpc_csharp_ext.dll")]
+        static extern GRPCCallError grpcsharp_call_set_credentials(CallSafeHandle call, CredentialsSafeHandle credentials);
+
         [DllImport("grpc_csharp_ext.dll")]
         static extern CStringSafeHandle grpcsharp_call_get_peer(CallSafeHandle call);
 
@@ -113,6 +116,11 @@ namespace Grpc.Core.Internal
             this.completionRegistry = completionRegistry;
         }
 
+        public void SetCredentials(CredentialsSafeHandle credentials)
+        {
+            grpcsharp_call_set_credentials(this, credentials).CheckOk();
+        }
+
         public void StartUnary(UnaryResponseClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
         {
             var ctx = BatchContextSafeHandle.Create();
diff --git a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
index 7a1c6e3dacd59885feaf6ce4020e3dc7ac30b168..d270d77526ffd44e69e6da66728bf614deef9852 100644
--- a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
@@ -82,9 +82,13 @@ namespace Grpc.Core.Internal
             return grpcsharp_secure_channel_create(credentials, target, channelArgs);
         }
 
-        public CallSafeHandle CreateCall(CompletionRegistry registry, CallSafeHandle parentCall, ContextPropagationFlags propagationMask, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline)
+        public CallSafeHandle CreateCall(CompletionRegistry registry, CallSafeHandle parentCall, ContextPropagationFlags propagationMask, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline, CredentialsSafeHandle credentials)
         {
             var result = grpcsharp_channel_create_call(this, parentCall, propagationMask, cq, method, host, deadline);
+            if (credentials != null)
+            {
+                result.SetCredentials(credentials);
+            }
             result.SetCompletionRegistry(registry);
             return result;
         }
diff --git a/src/csharp/Grpc.Core/Internal/CredentialsSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CredentialsSafeHandle.cs
index feed33536246720f578c66223165c44752dc36b9..bab45108e02a060952e8b96dcc64cd807dd63779 100644
--- a/src/csharp/Grpc.Core/Internal/CredentialsSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/CredentialsSafeHandle.cs
@@ -43,6 +43,9 @@ namespace Grpc.Core.Internal
         [DllImport("grpc_csharp_ext.dll", CharSet = CharSet.Ansi)]
         static extern CredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey);
 
+        [DllImport("grpc_csharp_ext.dll")]
+        static extern CredentialsSafeHandle grpcsharp_composite_credentials_create(CredentialsSafeHandle creds1, CredentialsSafeHandle creds2);
+
         [DllImport("grpc_csharp_ext.dll")]
         static extern void grpcsharp_credentials_release(IntPtr credentials);
 
@@ -69,6 +72,11 @@ namespace Grpc.Core.Internal
             }
         }
 
+        public static CredentialsSafeHandle CreateComposite(CredentialsSafeHandle creds1, CredentialsSafeHandle creds2)
+        {
+            return grpcsharp_composite_credentials_create(creds1, creds2);
+        }
+
         protected override bool ReleaseHandle()
         {
             grpcsharp_credentials_release(handle);
diff --git a/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs b/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f76492cba4a90b9b7151a52bafe5f026e62c9cfe
--- /dev/null
+++ b/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs
@@ -0,0 +1,113 @@
+#region Copyright notice and license
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#endregion
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Grpc.Core.Logging;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core.Internal
+{
+    internal delegate void NativeMetadataInterceptor(IntPtr statePtr, IntPtr serviceUrlPtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy);
+
+    internal class NativeMetadataCredentialsPlugin
+    {
+        const string GetMetadataExceptionMsg = "Exception occured in metadata credentials plugin.";
+        static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<NativeMetadataCredentialsPlugin>();
+
+        [DllImport("grpc_csharp_ext.dll")]
+        static extern CredentialsSafeHandle grpcsharp_metadata_credentials_create_from_plugin(NativeMetadataInterceptor interceptor);
+        
+        [DllImport("grpc_csharp_ext.dll", CharSet = CharSet.Ansi)]
+        static extern void grpcsharp_metadata_credentials_notify_from_plugin(IntPtr callbackPtr, IntPtr userData, MetadataArraySafeHandle metadataArray, StatusCode statusCode, string errorDetails);
+
+        AsyncAuthInterceptor interceptor;
+        GCHandle gcHandle;
+        NativeMetadataInterceptor nativeInterceptor;
+        CredentialsSafeHandle credentials;
+
+        public NativeMetadataCredentialsPlugin(AsyncAuthInterceptor interceptor)
+        {
+            this.interceptor = Preconditions.CheckNotNull(interceptor, "interceptor");
+            this.nativeInterceptor = NativeMetadataInterceptorHandler;
+
+            // Make sure the callback doesn't get garbage collected until it is destroyed.
+            this.gcHandle = GCHandle.Alloc(this.nativeInterceptor, GCHandleType.Normal);
+            this.credentials = grpcsharp_metadata_credentials_create_from_plugin(nativeInterceptor);
+        }
+
+        public CredentialsSafeHandle Credentials
+        {
+            get { return credentials; }
+        }
+
+        private void NativeMetadataInterceptorHandler(IntPtr statePtr, IntPtr serviceUrlPtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy)
+        {
+            if (isDestroy)
+            {
+                gcHandle.Free();
+                return;
+            }
+
+            try
+            {
+                string serviceUrl = Marshal.PtrToStringAnsi(serviceUrlPtr);
+                StartGetMetadata(serviceUrl, callbackPtr, userDataPtr);
+            }
+            catch (Exception e)
+            {
+                grpcsharp_metadata_credentials_notify_from_plugin(callbackPtr, userDataPtr, MetadataArraySafeHandle.Create(Metadata.Empty), StatusCode.Unknown, GetMetadataExceptionMsg);
+                Logger.Error(e, GetMetadataExceptionMsg);
+            }
+        }
+
+        private async void StartGetMetadata(string serviceUrl, IntPtr callbackPtr, IntPtr userDataPtr)
+        {
+            try
+            {
+                var metadata = new Metadata();
+                await interceptor(serviceUrl, metadata);
+
+                using (var metadataArray = MetadataArraySafeHandle.Create(metadata))
+                {
+                    grpcsharp_metadata_credentials_notify_from_plugin(callbackPtr, userDataPtr, metadataArray, StatusCode.OK, null);
+                }
+            }
+            catch (Exception e)
+            {
+                grpcsharp_metadata_credentials_notify_from_plugin(callbackPtr, userDataPtr, MetadataArraySafeHandle.Create(Metadata.Empty), StatusCode.Unknown, GetMetadataExceptionMsg);
+                Logger.Error(e, GetMetadataExceptionMsg);
+            }
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Examples.MathClient/MathClient.cs b/src/csharp/Grpc.Examples.MathClient/MathClient.cs
index 01e4a80babc119d666f69ce636c87f77286cd726..64e429ed5a32001bf89b10172ad73c40adb79ad0 100644
--- a/src/csharp/Grpc.Examples.MathClient/MathClient.cs
+++ b/src/csharp/Grpc.Examples.MathClient/MathClient.cs
@@ -39,7 +39,7 @@ namespace Math
     {
         public static void Main(string[] args)
         {
-            var channel = new Channel("127.0.0.1", 23456, Credentials.Insecure);
+            var channel = new Channel("127.0.0.1", 23456, ChannelCredentials.Insecure);
             Math.IMathClient client = new Math.MathClient(channel);
             MathExamples.DivExample(client);
 
diff --git a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
index e2975b5da93fd265899100aee3d5ec6d442119c2..290d42808e734cdae8aa4bcd07b1b1b29f6a69ea 100644
--- a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
+++ b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
@@ -61,7 +61,7 @@ namespace Math.Tests
                 Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
             };
             server.Start();
-            channel = new Channel(Host, server.Ports.Single().BoundPort, Credentials.Insecure);
+            channel = new Channel(Host, server.Ports.Single().BoundPort, ChannelCredentials.Insecure);
             client = Math.NewClient(channel);
         }
 
diff --git a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
index 6c3a53bec0559168b54aab151394a855b6470fd1..d90f21c2e1cb30b7a3fe2e01297fb423cfa4a998 100644
--- a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
+++ b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
@@ -63,7 +63,7 @@ namespace Grpc.HealthCheck.Tests
                 Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
             };
             server.Start();
-            channel = new Channel(Host, server.Ports.Single().BoundPort, Credentials.Insecure);
+            channel = new Channel(Host, server.Ports.Single().BoundPort, ChannelCredentials.Insecure);
 
             client = Grpc.Health.V1Alpha.Health.NewClient(channel);
         }
diff --git a/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj b/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj
index 2c38c9645c5f7804e73a69fb4576f3eab46a6703..8bc2082a1da0f736e833e9f442800449f2f44b12 100644
--- a/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj
+++ b/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj
@@ -9,6 +9,7 @@
     <AssemblyName>Grpc.IntegrationTesting.Client</AssemblyName>
     <StartupObject>Grpc.IntegrationTesting.Client.Program</StartupObject>
     <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <NuGetPackageImportStamp>6d22e68f</NuGetPackageImportStamp>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -38,7 +39,47 @@
     <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="BouncyCastle.Crypto, Version=1.7.4137.9688, Culture=neutral, PublicKeyToken=a4292a325f69b123, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll</HintPath>
+    </Reference>
+    <Reference Include="Google.Apis.Auth, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.dll</HintPath>
+    </Reference>
+    <Reference Include="Google.Apis.Auth.PlatformServices, Version=1.9.3.19383, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath>
+    </Reference>
+    <Reference Include="Google.Apis.Core, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Google.Apis.Core.1.9.3\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Threading.Tasks.Extensions">
+      <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath>
+    </Reference>
+    <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
+    <Reference Include="System.Net" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Net.Http.Extensions">
+      <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Net.Http.Primitives">
+      <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Net.Http.WebRequest" />
   </ItemGroup>
   <ItemGroup>
     <Compile Include="..\Grpc.Core\Version.cs">
@@ -60,5 +101,13 @@
   </ItemGroup>
   <ItemGroup>
     <None Include="app.config" />
+    <None Include="packages.config" />
   </ItemGroup>
+  <Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" />
+  </Target>
 </Project>
\ No newline at end of file
diff --git a/src/csharp/Grpc.IntegrationTesting.Client/packages.config b/src/csharp/Grpc.IntegrationTesting.Client/packages.config
new file mode 100644
index 0000000000000000000000000000000000000000..7a02c95db9138a30a7f1bf2d432e95050e008d91
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting.Client/packages.config
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="BouncyCastle" version="1.7.0" targetFramework="net45" />
+  <package id="Google.Apis.Auth" version="1.9.3" targetFramework="net45" />
+  <package id="Google.Apis.Core" version="1.9.3" targetFramework="net45" />
+  <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
+  <package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net45" />
+  <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />
+  <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
+  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
+</packages>
\ No newline at end of file
diff --git a/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj b/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj
index 949ad61375c4c0c15562cc8ab5a358e8c8c99e98..1eadbeb9206fb99dbf8f46df8b62f82e5079908e 100644
--- a/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj
+++ b/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj
@@ -9,6 +9,7 @@
     <AssemblyName>Grpc.IntegrationTesting.Server</AssemblyName>
     <StartupObject>Grpc.IntegrationTesting.Server.Program</StartupObject>
     <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <NuGetPackageImportStamp>d9ee8e52</NuGetPackageImportStamp>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -38,7 +39,47 @@
     <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="BouncyCastle.Crypto, Version=1.7.4137.9688, Culture=neutral, PublicKeyToken=a4292a325f69b123, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll</HintPath>
+    </Reference>
+    <Reference Include="Google.Apis.Auth, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.dll</HintPath>
+    </Reference>
+    <Reference Include="Google.Apis.Auth.PlatformServices, Version=1.9.3.19383, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath>
+    </Reference>
+    <Reference Include="Google.Apis.Core, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Google.Apis.Core.1.9.3\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Threading.Tasks.Extensions">
+      <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath>
+    </Reference>
+    <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
+    <Reference Include="System.Net" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Net.Http.Extensions">
+      <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Net.Http.Primitives">
+      <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Net.Http.WebRequest" />
   </ItemGroup>
   <ItemGroup>
     <Compile Include="..\Grpc.Core\Version.cs">
@@ -60,5 +101,13 @@
   </ItemGroup>
   <ItemGroup>
     <None Include="app.config" />
+    <None Include="packages.config" />
   </ItemGroup>
+  <Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" />
+  </Target>
 </Project>
\ No newline at end of file
diff --git a/src/csharp/Grpc.IntegrationTesting.Server/packages.config b/src/csharp/Grpc.IntegrationTesting.Server/packages.config
new file mode 100644
index 0000000000000000000000000000000000000000..7a02c95db9138a30a7f1bf2d432e95050e008d91
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting.Server/packages.config
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="BouncyCastle" version="1.7.0" targetFramework="net45" />
+  <package id="Google.Apis.Auth" version="1.9.3" targetFramework="net45" />
+  <package id="Google.Apis.Core" version="1.9.3" targetFramework="net45" />
+  <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
+  <package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net45" />
+  <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />
+  <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
+  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
+</packages>
\ No newline at end of file
diff --git a/src/csharp/Grpc.IntegrationTesting/Empty.cs b/src/csharp/Grpc.IntegrationTesting/Empty.cs
index 28c28c9afd5bd303a550a30cc64bedf158e65101..9bf2b8f7cf7aa5e6dfff229958bcc2044c1f6509 100644
--- a/src/csharp/Grpc.IntegrationTesting/Empty.cs
+++ b/src/csharp/Grpc.IntegrationTesting/Empty.cs
@@ -1,5 +1,5 @@
 // Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: empty.proto
+// source: test/proto/empty.proto
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -23,7 +23,8 @@ namespace Grpc.Testing {
       static Empty() {
         byte[] descriptorData = global::System.Convert.FromBase64String(
             string.Concat(
-              "CgtlbXB0eS5wcm90bxIMZ3JwYy50ZXN0aW5nIgcKBUVtcHR5YgZwcm90bzM="));
+              "ChZ0ZXN0L3Byb3RvL2VtcHR5LnByb3RvEgxncnBjLnRlc3RpbmciBwoFRW1w", 
+              "dHliBnByb3RvMw=="));
         descriptor = pbr::FileDescriptor.InternalBuildGeneratedFileFrom(descriptorData,
             new pbr::FileDescriptor[] { },
             new pbr::GeneratedCodeInfo(null, new pbr::GeneratedCodeInfo[] {
diff --git a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
index a0bcf431f7bee447b8428d51bd03be2c1014ee3f..2b7530573103cc7b64f65653c8010ced96ee284a 100644
--- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
+++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
@@ -96,6 +96,7 @@
     <Compile Include="Empty.cs" />
     <Compile Include="Messages.cs" />
     <Compile Include="InteropClientServerTest.cs" />
+    <Compile Include="MetadataCredentialsTest.cs" />
     <Compile Include="TestServiceImpl.cs" />
     <Compile Include="InteropServer.cs" />
     <Compile Include="InteropClient.cs" />
@@ -118,9 +119,6 @@
   <ItemGroup>
     <None Include="app.config" />
     <None Include="packages.config" />
-    <None Include="proto\test.proto" />
-    <None Include="proto\empty.proto" />
-    <None Include="proto\messages.proto" />
     <None Include="data\README">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
index 504d798b8936f7e3d54f75dcde1525a99d81077d..cb50b44841f0768966d0d801acd09f311f436b5d 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
@@ -33,20 +33,21 @@
 
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading.Tasks;
 
 using CommandLine;
+using CommandLine.Text;
 using Google.Apis.Auth.OAuth2;
 using Google.Protobuf;
 using Grpc.Auth;
 using Grpc.Core;
 using Grpc.Core.Utils;
 using Grpc.Testing;
+using Newtonsoft.Json.Linq;
 using NUnit.Framework;
-using CommandLine.Text;
-using System.IO;
 
 namespace Grpc.IntegrationTesting
 {
@@ -66,11 +67,13 @@ namespace Grpc.IntegrationTesting
             [Option("test_case", DefaultValue = "large_unary")]
             public string TestCase { get; set; }
 
-            [Option("use_tls")]
-            public bool UseTls { get; set; }
+            // Deliberately using nullable bool type to allow --use_tls=true syntax (as opposed to --use_tls)
+            [Option("use_tls", DefaultValue = false)]
+            public bool? UseTls { get; set; }
 
-            [Option("use_test_ca")]
-            public bool UseTestCa { get; set; }
+            // Deliberately using nullable bool type to allow --use_test_ca=true syntax (as opposed to --use_test_ca)
+            [Option("use_test_ca", DefaultValue = false)]
+            public bool? UseTestCa { get; set; }
 
             [Option("default_service_account", Required = false)]
             public string DefaultServiceAccount { get; set; }
@@ -116,7 +119,7 @@ namespace Grpc.IntegrationTesting
 
         private async Task Run()
         {
-            var credentials = options.UseTls ? TestCredentials.CreateTestClientCredentials(options.UseTestCa) : Credentials.Insecure;
+            var credentials = await CreateCredentialsAsync();
             
             List<ChannelOption> channelOptions = null;
             if (!string.IsNullOrEmpty(options.ServerHostOverride))
@@ -132,6 +135,26 @@ namespace Grpc.IntegrationTesting
             await channel.ShutdownAsync();
         }
 
+        private async Task<ChannelCredentials> CreateCredentialsAsync()
+        {
+            var credentials = options.UseTls.Value ? TestCredentials.CreateTestClientCredentials(options.UseTestCa.Value) : ChannelCredentials.Insecure;
+
+            if (options.TestCase == "jwt_token_creds")
+            {
+                var googleCredential = await GoogleCredential.GetApplicationDefaultAsync();
+                Assert.IsTrue(googleCredential.IsCreateScopedRequired);
+                credentials = ChannelCredentials.Create(credentials, googleCredential.ToGrpcCredentials());
+            }
+
+            if (options.TestCase == "compute_engine_creds")
+            {
+                var googleCredential = await GoogleCredential.GetApplicationDefaultAsync();
+                Assert.IsFalse(googleCredential.IsCreateScopedRequired);
+                credentials = ChannelCredentials.Create(credentials, googleCredential.ToGrpcCredentials());
+            }
+            return credentials;
+        }
+
         private async Task RunTestCaseAsync(TestService.TestServiceClient client, ClientOptions options)
         {
             switch (options.TestCase)
@@ -155,16 +178,16 @@ namespace Grpc.IntegrationTesting
                     await RunEmptyStreamAsync(client);
                     break;
                 case "compute_engine_creds":
-                    await RunComputeEngineCredsAsync(client, options.DefaultServiceAccount, options.OAuthScope);
+                    RunComputeEngineCreds(client, options.DefaultServiceAccount, options.OAuthScope);
                     break;
                 case "jwt_token_creds":
-                    await RunJwtTokenCredsAsync(client, options.DefaultServiceAccount);
+                    RunJwtTokenCreds(client);
                     break;
                 case "oauth2_auth_token":
-                    await RunOAuth2AuthTokenAsync(client, options.DefaultServiceAccount, options.OAuthScope);
+                    await RunOAuth2AuthTokenAsync(client, options.OAuthScope);
                     break;
                 case "per_rpc_creds":
-                    await RunPerRpcCredsAsync(client, options.DefaultServiceAccount, options.OAuthScope);
+                    await RunPerRpcCredsAsync(client, options.OAuthScope);
                     break;
                 case "cancel_after_begin":
                     await RunCancelAfterBeginAsync(client);
@@ -318,13 +341,10 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunComputeEngineCredsAsync(TestService.TestServiceClient client, string defaultServiceAccount, string oauthScope)
+        public static void RunComputeEngineCreds(TestService.TestServiceClient client, string defaultServiceAccount, string oauthScope)
         {
             Console.WriteLine("running compute_engine_creds");
-            var credential = await GoogleCredential.GetApplicationDefaultAsync();
-            Assert.IsFalse(credential.IsCreateScopedRequired);
-            client.HeaderInterceptor = AuthInterceptors.FromCredential(credential);
-            
+
             var request = new SimpleRequest
             {
                 ResponseType = PayloadType.COMPRESSABLE,
@@ -334,6 +354,7 @@ namespace Grpc.IntegrationTesting
                 FillOauthScope = true
             };
 
+            // not setting credentials here because they were set on channel already
             var response = client.UnaryCall(request);
 
             Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type);
@@ -344,13 +365,10 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunJwtTokenCredsAsync(TestService.TestServiceClient client, string defaultServiceAccount)
+        public static void RunJwtTokenCreds(TestService.TestServiceClient client)
         {
             Console.WriteLine("running jwt_token_creds");
-            var credential = await GoogleCredential.GetApplicationDefaultAsync();
-            Assert.IsTrue(credential.IsCreateScopedRequired);
-            client.HeaderInterceptor = AuthInterceptors.FromCredential(credential);
-
+           
             var request = new SimpleRequest
             {
                 ResponseType = PayloadType.COMPRESSABLE,
@@ -359,53 +377,50 @@ namespace Grpc.IntegrationTesting
                 FillUsername = true,
             };
 
+            // not setting credentials here because they were set on channel already
             var response = client.UnaryCall(request);
 
             Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type);
             Assert.AreEqual(314159, response.Payload.Body.Length);
-            Assert.AreEqual(defaultServiceAccount, response.Username);
+            Assert.AreEqual(GetEmailFromServiceAccountFile(), response.Username);
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunOAuth2AuthTokenAsync(TestService.TestServiceClient client, string defaultServiceAccount, string oauthScope)
+        public static async Task RunOAuth2AuthTokenAsync(TestService.TestServiceClient client, string oauthScope)
         {
             Console.WriteLine("running oauth2_auth_token");
             ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { oauthScope });
             string oauth2Token = await credential.GetAccessTokenForRequestAsync();
 
-            client.HeaderInterceptor = AuthInterceptors.FromAccessToken(oauth2Token);
-
+            var credentials = GrpcCredentials.FromAccessToken(oauth2Token);
             var request = new SimpleRequest
             {
                 FillUsername = true,
                 FillOauthScope = true
             };
 
-            var response = client.UnaryCall(request);
+            var response = client.UnaryCall(request, new CallOptions(credentials: credentials));
 
             Assert.False(string.IsNullOrEmpty(response.OauthScope));
             Assert.True(oauthScope.Contains(response.OauthScope));
-            Assert.AreEqual(defaultServiceAccount, response.Username);
+            Assert.AreEqual(GetEmailFromServiceAccountFile(), response.Username);
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunPerRpcCredsAsync(TestService.TestServiceClient client, string defaultServiceAccount, string oauthScope)
+        public static async Task RunPerRpcCredsAsync(TestService.TestServiceClient client, string oauthScope)
         {
             Console.WriteLine("running per_rpc_creds");
-            ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { oauthScope });
-            string accessToken = await credential.GetAccessTokenForRequestAsync();
-            var headerInterceptor = AuthInterceptors.FromAccessToken(accessToken);
+            ITokenAccess googleCredential = await GoogleCredential.GetApplicationDefaultAsync();
 
+            var credentials = GrpcCredentials.Create(googleCredential);
             var request = new SimpleRequest
             {
                 FillUsername = true,
             };
 
-            var headers = new Metadata();
-            headerInterceptor(null, "", headers);
-            var response = client.UnaryCall(request, headers: headers);
+            var response = client.UnaryCall(request, new CallOptions(credentials: credentials));
 
-            Assert.AreEqual(defaultServiceAccount, response.Username);
+            Assert.AreEqual(GetEmailFromServiceAccountFile(), response.Username);
             Console.WriteLine("Passed!");
         }
 
@@ -485,5 +500,17 @@ namespace Grpc.IntegrationTesting
         {
             return new Payload { Body = ByteString.CopyFrom(new byte[size]) };
         }
+
+        // extracts the client_email field from service account file used for auth test cases
+        private static string GetEmailFromServiceAccountFile()
+        {
+            string keyFile = Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS");
+            Assert.IsNotNull(keyFile);
+
+            var jobject = JObject.Parse(File.ReadAllText(keyFile));
+            string email = jobject.GetValue("client_email").Value<string>();
+            Assert.IsTrue(email.Length > 0);  // spec requires nonempty client email.
+            return email;
+        }
     }
 }
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropServer.cs b/src/csharp/Grpc.IntegrationTesting/InteropServer.cs
index 513f8722d64238299548af6fe4ec41605816f58a..29f842be2eceedfcd9b9045d856d1cd1da9d2d42 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropServer.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropServer.cs
@@ -54,8 +54,9 @@ namespace Grpc.IntegrationTesting
             [Option("port", DefaultValue = 8070)]
             public int Port { get; set; }
 
-            [Option("use_tls")]
-            public bool UseTls { get; set; }
+            // Deliberately using nullable bool type to allow --use_tls=true syntax (as opposed to --use_tls)
+            [Option("use_tls", DefaultValue = false)]
+            public bool? UseTls { get; set; }
 
             [HelpOption]
             public string GetUsage()
@@ -99,7 +100,7 @@ namespace Grpc.IntegrationTesting
 
             string host = "0.0.0.0";
             int port = options.Port;
-            if (options.UseTls)
+            if (options.UseTls.Value)
             {
                 server.Ports.Add(host, port, TestCredentials.CreateTestServerCredentials());
             }
diff --git a/src/csharp/Grpc.IntegrationTesting/Messages.cs b/src/csharp/Grpc.IntegrationTesting/Messages.cs
index a3cbb7d76ebe6dc7755401d4bd41cdf97d2848f7..51a56f067b5dd669cadb0bf8a846995c30f2d7a8 100644
--- a/src/csharp/Grpc.IntegrationTesting/Messages.cs
+++ b/src/csharp/Grpc.IntegrationTesting/Messages.cs
@@ -1,5 +1,5 @@
 // Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: messages.proto
+// source: test/proto/messages.proto
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -21,37 +21,48 @@ namespace Grpc.Testing {
     static Messages() {
       byte[] descriptorData = global::System.Convert.FromBase64String(
           string.Concat(
-            "Cg5tZXNzYWdlcy5wcm90bxIMZ3JwYy50ZXN0aW5nIkAKB1BheWxvYWQSJwoE", 
-            "dHlwZRgBIAEoDjIZLmdycGMudGVzdGluZy5QYXlsb2FkVHlwZRIMCgRib2R5", 
-            "GAIgASgMIrEBCg1TaW1wbGVSZXF1ZXN0EjAKDXJlc3BvbnNlX3R5cGUYASAB", 
-            "KA4yGS5ncnBjLnRlc3RpbmcuUGF5bG9hZFR5cGUSFQoNcmVzcG9uc2Vfc2l6", 
-            "ZRgCIAEoBRImCgdwYXlsb2FkGAMgASgLMhUuZ3JwYy50ZXN0aW5nLlBheWxv", 
-            "YWQSFQoNZmlsbF91c2VybmFtZRgEIAEoCBIYChBmaWxsX29hdXRoX3Njb3Bl", 
-            "GAUgASgIIl8KDlNpbXBsZVJlc3BvbnNlEiYKB3BheWxvYWQYASABKAsyFS5n", 
-            "cnBjLnRlc3RpbmcuUGF5bG9hZBIQCgh1c2VybmFtZRgCIAEoCRITCgtvYXV0", 
-            "aF9zY29wZRgDIAEoCSJDChlTdHJlYW1pbmdJbnB1dENhbGxSZXF1ZXN0EiYK", 
-            "B3BheWxvYWQYASABKAsyFS5ncnBjLnRlc3RpbmcuUGF5bG9hZCI9ChpTdHJl", 
-            "YW1pbmdJbnB1dENhbGxSZXNwb25zZRIfChdhZ2dyZWdhdGVkX3BheWxvYWRf", 
-            "c2l6ZRgBIAEoBSI3ChJSZXNwb25zZVBhcmFtZXRlcnMSDAoEc2l6ZRgBIAEo", 
-            "BRITCgtpbnRlcnZhbF91cxgCIAEoBSK1AQoaU3RyZWFtaW5nT3V0cHV0Q2Fs", 
-            "bFJlcXVlc3QSMAoNcmVzcG9uc2VfdHlwZRgBIAEoDjIZLmdycGMudGVzdGlu", 
-            "Zy5QYXlsb2FkVHlwZRI9ChNyZXNwb25zZV9wYXJhbWV0ZXJzGAIgAygLMiAu", 
-            "Z3JwYy50ZXN0aW5nLlJlc3BvbnNlUGFyYW1ldGVycxImCgdwYXlsb2FkGAMg", 
-            "ASgLMhUuZ3JwYy50ZXN0aW5nLlBheWxvYWQiRQobU3RyZWFtaW5nT3V0cHV0", 
-            "Q2FsbFJlc3BvbnNlEiYKB3BheWxvYWQYASABKAsyFS5ncnBjLnRlc3Rpbmcu", 
-            "UGF5bG9hZCo/CgtQYXlsb2FkVHlwZRIQCgxDT01QUkVTU0FCTEUQABISCg5V", 
-            "TkNPTVBSRVNTQUJMRRABEgoKBlJBTkRPTRACYgZwcm90bzM="));
+            "Chl0ZXN0L3Byb3RvL21lc3NhZ2VzLnByb3RvEgxncnBjLnRlc3RpbmciQAoH", 
+            "UGF5bG9hZBInCgR0eXBlGAEgASgOMhkuZ3JwYy50ZXN0aW5nLlBheWxvYWRU", 
+            "eXBlEgwKBGJvZHkYAiABKAwiKwoKRWNob1N0YXR1cxIMCgRjb2RlGAEgASgF", 
+            "Eg8KB21lc3NhZ2UYAiABKAkioQIKDVNpbXBsZVJlcXVlc3QSMAoNcmVzcG9u", 
+            "c2VfdHlwZRgBIAEoDjIZLmdycGMudGVzdGluZy5QYXlsb2FkVHlwZRIVCg1y", 
+            "ZXNwb25zZV9zaXplGAIgASgFEiYKB3BheWxvYWQYAyABKAsyFS5ncnBjLnRl", 
+            "c3RpbmcuUGF5bG9hZBIVCg1maWxsX3VzZXJuYW1lGAQgASgIEhgKEGZpbGxf", 
+            "b2F1dGhfc2NvcGUYBSABKAgSOwoUcmVzcG9uc2VfY29tcHJlc3Npb24YBiAB", 
+            "KA4yHS5ncnBjLnRlc3RpbmcuQ29tcHJlc3Npb25UeXBlEjEKD3Jlc3BvbnNl", 
+            "X3N0YXR1cxgHIAEoCzIYLmdycGMudGVzdGluZy5FY2hvU3RhdHVzIl8KDlNp", 
+            "bXBsZVJlc3BvbnNlEiYKB3BheWxvYWQYASABKAsyFS5ncnBjLnRlc3Rpbmcu", 
+            "UGF5bG9hZBIQCgh1c2VybmFtZRgCIAEoCRITCgtvYXV0aF9zY29wZRgDIAEo", 
+            "CSJDChlTdHJlYW1pbmdJbnB1dENhbGxSZXF1ZXN0EiYKB3BheWxvYWQYASAB", 
+            "KAsyFS5ncnBjLnRlc3RpbmcuUGF5bG9hZCI9ChpTdHJlYW1pbmdJbnB1dENh", 
+            "bGxSZXNwb25zZRIfChdhZ2dyZWdhdGVkX3BheWxvYWRfc2l6ZRgBIAEoBSI3", 
+            "ChJSZXNwb25zZVBhcmFtZXRlcnMSDAoEc2l6ZRgBIAEoBRITCgtpbnRlcnZh", 
+            "bF91cxgCIAEoBSKlAgoaU3RyZWFtaW5nT3V0cHV0Q2FsbFJlcXVlc3QSMAoN", 
+            "cmVzcG9uc2VfdHlwZRgBIAEoDjIZLmdycGMudGVzdGluZy5QYXlsb2FkVHlw", 
+            "ZRI9ChNyZXNwb25zZV9wYXJhbWV0ZXJzGAIgAygLMiAuZ3JwYy50ZXN0aW5n", 
+            "LlJlc3BvbnNlUGFyYW1ldGVycxImCgdwYXlsb2FkGAMgASgLMhUuZ3JwYy50", 
+            "ZXN0aW5nLlBheWxvYWQSOwoUcmVzcG9uc2VfY29tcHJlc3Npb24YBiABKA4y", 
+            "HS5ncnBjLnRlc3RpbmcuQ29tcHJlc3Npb25UeXBlEjEKD3Jlc3BvbnNlX3N0", 
+            "YXR1cxgHIAEoCzIYLmdycGMudGVzdGluZy5FY2hvU3RhdHVzIkUKG1N0cmVh", 
+            "bWluZ091dHB1dENhbGxSZXNwb25zZRImCgdwYXlsb2FkGAEgASgLMhUuZ3Jw", 
+            "Yy50ZXN0aW5nLlBheWxvYWQiMwoNUmVjb25uZWN0SW5mbxIOCgZwYXNzZWQY", 
+            "ASABKAgSEgoKYmFja29mZl9tcxgCIAMoBSo/CgtQYXlsb2FkVHlwZRIQCgxD", 
+            "T01QUkVTU0FCTEUQABISCg5VTkNPTVBSRVNTQUJMRRABEgoKBlJBTkRPTRAC", 
+            "KjIKD0NvbXByZXNzaW9uVHlwZRIICgROT05FEAASCAoER1pJUBABEgsKB0RF", 
+            "RkxBVEUQAmIGcHJvdG8z"));
       descriptor = pbr::FileDescriptor.InternalBuildGeneratedFileFrom(descriptorData,
           new pbr::FileDescriptor[] { },
-          new pbr::GeneratedCodeInfo(new[] {typeof(global::Grpc.Testing.PayloadType), }, new pbr::GeneratedCodeInfo[] {
+          new pbr::GeneratedCodeInfo(new[] {typeof(global::Grpc.Testing.PayloadType), typeof(global::Grpc.Testing.CompressionType), }, new pbr::GeneratedCodeInfo[] {
             new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.Payload), new[]{ "Type", "Body" }, null, null, null),
-            new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.SimpleRequest), new[]{ "ResponseType", "ResponseSize", "Payload", "FillUsername", "FillOauthScope" }, null, null, null),
+            new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.EchoStatus), new[]{ "Code", "Message" }, null, null, null),
+            new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.SimpleRequest), new[]{ "ResponseType", "ResponseSize", "Payload", "FillUsername", "FillOauthScope", "ResponseCompression", "ResponseStatus" }, null, null, null),
             new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.SimpleResponse), new[]{ "Payload", "Username", "OauthScope" }, null, null, null),
             new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.StreamingInputCallRequest), new[]{ "Payload" }, null, null, null),
             new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.StreamingInputCallResponse), new[]{ "AggregatedPayloadSize" }, null, null, null),
             new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.ResponseParameters), new[]{ "Size", "IntervalUs" }, null, null, null),
-            new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.StreamingOutputCallRequest), new[]{ "ResponseType", "ResponseParameters", "Payload" }, null, null, null),
-            new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.StreamingOutputCallResponse), new[]{ "Payload" }, null, null, null)
+            new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.StreamingOutputCallRequest), new[]{ "ResponseType", "ResponseParameters", "Payload", "ResponseCompression", "ResponseStatus" }, null, null, null),
+            new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.StreamingOutputCallResponse), new[]{ "Payload" }, null, null, null),
+            new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.ReconnectInfo), new[]{ "Passed", "BackoffMs" }, null, null, null)
           }));
     }
     #endregion
@@ -64,6 +75,12 @@ namespace Grpc.Testing {
     RANDOM = 2,
   }
 
+  public enum CompressionType {
+    NONE = 0,
+    GZIP = 1,
+    DEFLATE = 2,
+  }
+
   #endregion
 
   #region Messages
@@ -195,13 +212,141 @@ namespace Grpc.Testing {
 
   }
 
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class EchoStatus : pb::IMessage<EchoStatus> {
+    private static readonly pb::MessageParser<EchoStatus> _parser = new pb::MessageParser<EchoStatus>(() => new EchoStatus());
+    public static pb::MessageParser<EchoStatus> Parser { get { return _parser; } }
+
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[1]; }
+    }
+
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    public EchoStatus() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    public EchoStatus(EchoStatus other) : this() {
+      code_ = other.code_;
+      message_ = other.message_;
+    }
+
+    public EchoStatus Clone() {
+      return new EchoStatus(this);
+    }
+
+    public const int CodeFieldNumber = 1;
+    private int code_;
+    public int Code {
+      get { return code_; }
+      set {
+        code_ = value;
+      }
+    }
+
+    public const int MessageFieldNumber = 2;
+    private string message_ = "";
+    public string Message {
+      get { return message_; }
+      set {
+        message_ = pb::Preconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    public override bool Equals(object other) {
+      return Equals(other as EchoStatus);
+    }
+
+    public bool Equals(EchoStatus other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Code != other.Code) return false;
+      if (Message != other.Message) return false;
+      return true;
+    }
+
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Code != 0) hash ^= Code.GetHashCode();
+      if (Message.Length != 0) hash ^= Message.GetHashCode();
+      return hash;
+    }
+
+    public override string ToString() {
+      return pb::JsonFormatter.Default.Format(this);
+    }
+
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Code != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(Code);
+      }
+      if (Message.Length != 0) {
+        output.WriteRawTag(18);
+        output.WriteString(Message);
+      }
+    }
+
+    public int CalculateSize() {
+      int size = 0;
+      if (Code != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Code);
+      }
+      if (Message.Length != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeStringSize(Message);
+      }
+      return size;
+    }
+
+    public void MergeFrom(EchoStatus other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Code != 0) {
+        Code = other.Code;
+      }
+      if (other.Message.Length != 0) {
+        Message = other.Message;
+      }
+    }
+
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Code = input.ReadInt32();
+            break;
+          }
+          case 18: {
+            Message = input.ReadString();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
   [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
   public sealed partial class SimpleRequest : pb::IMessage<SimpleRequest> {
     private static readonly pb::MessageParser<SimpleRequest> _parser = new pb::MessageParser<SimpleRequest>(() => new SimpleRequest());
     public static pb::MessageParser<SimpleRequest> Parser { get { return _parser; } }
 
     public static pbr::MessageDescriptor Descriptor {
-      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[1]; }
+      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[2]; }
     }
 
     pbr::MessageDescriptor pb::IMessage.Descriptor {
@@ -220,6 +365,8 @@ namespace Grpc.Testing {
       Payload = other.payload_ != null ? other.Payload.Clone() : null;
       fillUsername_ = other.fillUsername_;
       fillOauthScope_ = other.fillOauthScope_;
+      responseCompression_ = other.responseCompression_;
+      ResponseStatus = other.responseStatus_ != null ? other.ResponseStatus.Clone() : null;
     }
 
     public SimpleRequest Clone() {
@@ -271,6 +418,24 @@ namespace Grpc.Testing {
       }
     }
 
+    public const int ResponseCompressionFieldNumber = 6;
+    private global::Grpc.Testing.CompressionType responseCompression_ = global::Grpc.Testing.CompressionType.NONE;
+    public global::Grpc.Testing.CompressionType ResponseCompression {
+      get { return responseCompression_; }
+      set {
+        responseCompression_ = value;
+      }
+    }
+
+    public const int ResponseStatusFieldNumber = 7;
+    private global::Grpc.Testing.EchoStatus responseStatus_;
+    public global::Grpc.Testing.EchoStatus ResponseStatus {
+      get { return responseStatus_; }
+      set {
+        responseStatus_ = value;
+      }
+    }
+
     public override bool Equals(object other) {
       return Equals(other as SimpleRequest);
     }
@@ -287,6 +452,8 @@ namespace Grpc.Testing {
       if (!object.Equals(Payload, other.Payload)) return false;
       if (FillUsername != other.FillUsername) return false;
       if (FillOauthScope != other.FillOauthScope) return false;
+      if (ResponseCompression != other.ResponseCompression) return false;
+      if (!object.Equals(ResponseStatus, other.ResponseStatus)) return false;
       return true;
     }
 
@@ -297,6 +464,8 @@ namespace Grpc.Testing {
       if (payload_ != null) hash ^= Payload.GetHashCode();
       if (FillUsername != false) hash ^= FillUsername.GetHashCode();
       if (FillOauthScope != false) hash ^= FillOauthScope.GetHashCode();
+      if (ResponseCompression != global::Grpc.Testing.CompressionType.NONE) hash ^= ResponseCompression.GetHashCode();
+      if (responseStatus_ != null) hash ^= ResponseStatus.GetHashCode();
       return hash;
     }
 
@@ -325,6 +494,14 @@ namespace Grpc.Testing {
         output.WriteRawTag(40);
         output.WriteBool(FillOauthScope);
       }
+      if (ResponseCompression != global::Grpc.Testing.CompressionType.NONE) {
+        output.WriteRawTag(48);
+        output.WriteEnum((int) ResponseCompression);
+      }
+      if (responseStatus_ != null) {
+        output.WriteRawTag(58);
+        output.WriteMessage(ResponseStatus);
+      }
     }
 
     public int CalculateSize() {
@@ -344,6 +521,12 @@ namespace Grpc.Testing {
       if (FillOauthScope != false) {
         size += 1 + 1;
       }
+      if (ResponseCompression != global::Grpc.Testing.CompressionType.NONE) {
+        size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) ResponseCompression);
+      }
+      if (responseStatus_ != null) {
+        size += 1 + pb::CodedOutputStream.ComputeMessageSize(ResponseStatus);
+      }
       return size;
     }
 
@@ -369,6 +552,15 @@ namespace Grpc.Testing {
       if (other.FillOauthScope != false) {
         FillOauthScope = other.FillOauthScope;
       }
+      if (other.ResponseCompression != global::Grpc.Testing.CompressionType.NONE) {
+        ResponseCompression = other.ResponseCompression;
+      }
+      if (other.responseStatus_ != null) {
+        if (responseStatus_ == null) {
+          responseStatus_ = new global::Grpc.Testing.EchoStatus();
+        }
+        ResponseStatus.MergeFrom(other.ResponseStatus);
+      }
     }
 
     public void MergeFrom(pb::CodedInputStream input) {
@@ -401,6 +593,17 @@ namespace Grpc.Testing {
             FillOauthScope = input.ReadBool();
             break;
           }
+          case 48: {
+            responseCompression_ = (global::Grpc.Testing.CompressionType) input.ReadEnum();
+            break;
+          }
+          case 58: {
+            if (responseStatus_ == null) {
+              responseStatus_ = new global::Grpc.Testing.EchoStatus();
+            }
+            input.ReadMessage(responseStatus_);
+            break;
+          }
         }
       }
     }
@@ -413,7 +616,7 @@ namespace Grpc.Testing {
     public static pb::MessageParser<SimpleResponse> Parser { get { return _parser; } }
 
     public static pbr::MessageDescriptor Descriptor {
-      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[2]; }
+      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[3]; }
     }
 
     pbr::MessageDescriptor pb::IMessage.Descriptor {
@@ -573,7 +776,7 @@ namespace Grpc.Testing {
     public static pb::MessageParser<StreamingInputCallRequest> Parser { get { return _parser; } }
 
     public static pbr::MessageDescriptor Descriptor {
-      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[3]; }
+      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[4]; }
     }
 
     pbr::MessageDescriptor pb::IMessage.Descriptor {
@@ -681,7 +884,7 @@ namespace Grpc.Testing {
     public static pb::MessageParser<StreamingInputCallResponse> Parser { get { return _parser; } }
 
     public static pbr::MessageDescriptor Descriptor {
-      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[4]; }
+      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[5]; }
     }
 
     pbr::MessageDescriptor pb::IMessage.Descriptor {
@@ -783,7 +986,7 @@ namespace Grpc.Testing {
     public static pb::MessageParser<ResponseParameters> Parser { get { return _parser; } }
 
     public static pbr::MessageDescriptor Descriptor {
-      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[5]; }
+      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[6]; }
     }
 
     pbr::MessageDescriptor pb::IMessage.Descriptor {
@@ -911,7 +1114,7 @@ namespace Grpc.Testing {
     public static pb::MessageParser<StreamingOutputCallRequest> Parser { get { return _parser; } }
 
     public static pbr::MessageDescriptor Descriptor {
-      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[6]; }
+      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[7]; }
     }
 
     pbr::MessageDescriptor pb::IMessage.Descriptor {
@@ -928,6 +1131,8 @@ namespace Grpc.Testing {
       responseType_ = other.responseType_;
       responseParameters_ = other.responseParameters_.Clone();
       Payload = other.payload_ != null ? other.Payload.Clone() : null;
+      responseCompression_ = other.responseCompression_;
+      ResponseStatus = other.responseStatus_ != null ? other.ResponseStatus.Clone() : null;
     }
 
     public StreamingOutputCallRequest Clone() {
@@ -960,6 +1165,24 @@ namespace Grpc.Testing {
       }
     }
 
+    public const int ResponseCompressionFieldNumber = 6;
+    private global::Grpc.Testing.CompressionType responseCompression_ = global::Grpc.Testing.CompressionType.NONE;
+    public global::Grpc.Testing.CompressionType ResponseCompression {
+      get { return responseCompression_; }
+      set {
+        responseCompression_ = value;
+      }
+    }
+
+    public const int ResponseStatusFieldNumber = 7;
+    private global::Grpc.Testing.EchoStatus responseStatus_;
+    public global::Grpc.Testing.EchoStatus ResponseStatus {
+      get { return responseStatus_; }
+      set {
+        responseStatus_ = value;
+      }
+    }
+
     public override bool Equals(object other) {
       return Equals(other as StreamingOutputCallRequest);
     }
@@ -974,6 +1197,8 @@ namespace Grpc.Testing {
       if (ResponseType != other.ResponseType) return false;
       if(!responseParameters_.Equals(other.responseParameters_)) return false;
       if (!object.Equals(Payload, other.Payload)) return false;
+      if (ResponseCompression != other.ResponseCompression) return false;
+      if (!object.Equals(ResponseStatus, other.ResponseStatus)) return false;
       return true;
     }
 
@@ -982,6 +1207,8 @@ namespace Grpc.Testing {
       if (ResponseType != global::Grpc.Testing.PayloadType.COMPRESSABLE) hash ^= ResponseType.GetHashCode();
       hash ^= responseParameters_.GetHashCode();
       if (payload_ != null) hash ^= Payload.GetHashCode();
+      if (ResponseCompression != global::Grpc.Testing.CompressionType.NONE) hash ^= ResponseCompression.GetHashCode();
+      if (responseStatus_ != null) hash ^= ResponseStatus.GetHashCode();
       return hash;
     }
 
@@ -999,6 +1226,14 @@ namespace Grpc.Testing {
         output.WriteRawTag(26);
         output.WriteMessage(Payload);
       }
+      if (ResponseCompression != global::Grpc.Testing.CompressionType.NONE) {
+        output.WriteRawTag(48);
+        output.WriteEnum((int) ResponseCompression);
+      }
+      if (responseStatus_ != null) {
+        output.WriteRawTag(58);
+        output.WriteMessage(ResponseStatus);
+      }
     }
 
     public int CalculateSize() {
@@ -1010,6 +1245,12 @@ namespace Grpc.Testing {
       if (payload_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Payload);
       }
+      if (ResponseCompression != global::Grpc.Testing.CompressionType.NONE) {
+        size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) ResponseCompression);
+      }
+      if (responseStatus_ != null) {
+        size += 1 + pb::CodedOutputStream.ComputeMessageSize(ResponseStatus);
+      }
       return size;
     }
 
@@ -1027,6 +1268,15 @@ namespace Grpc.Testing {
         }
         Payload.MergeFrom(other.Payload);
       }
+      if (other.ResponseCompression != global::Grpc.Testing.CompressionType.NONE) {
+        ResponseCompression = other.ResponseCompression;
+      }
+      if (other.responseStatus_ != null) {
+        if (responseStatus_ == null) {
+          responseStatus_ = new global::Grpc.Testing.EchoStatus();
+        }
+        ResponseStatus.MergeFrom(other.ResponseStatus);
+      }
     }
 
     public void MergeFrom(pb::CodedInputStream input) {
@@ -1051,6 +1301,17 @@ namespace Grpc.Testing {
             input.ReadMessage(payload_);
             break;
           }
+          case 48: {
+            responseCompression_ = (global::Grpc.Testing.CompressionType) input.ReadEnum();
+            break;
+          }
+          case 58: {
+            if (responseStatus_ == null) {
+              responseStatus_ = new global::Grpc.Testing.EchoStatus();
+            }
+            input.ReadMessage(responseStatus_);
+            break;
+          }
         }
       }
     }
@@ -1063,7 +1324,7 @@ namespace Grpc.Testing {
     public static pb::MessageParser<StreamingOutputCallResponse> Parser { get { return _parser; } }
 
     public static pbr::MessageDescriptor Descriptor {
-      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[7]; }
+      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[8]; }
     }
 
     pbr::MessageDescriptor pb::IMessage.Descriptor {
@@ -1165,6 +1426,127 @@ namespace Grpc.Testing {
 
   }
 
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class ReconnectInfo : pb::IMessage<ReconnectInfo> {
+    private static readonly pb::MessageParser<ReconnectInfo> _parser = new pb::MessageParser<ReconnectInfo>(() => new ReconnectInfo());
+    public static pb::MessageParser<ReconnectInfo> Parser { get { return _parser; } }
+
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::Grpc.Testing.Messages.Descriptor.MessageTypes[9]; }
+    }
+
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    public ReconnectInfo() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    public ReconnectInfo(ReconnectInfo other) : this() {
+      passed_ = other.passed_;
+      backoffMs_ = other.backoffMs_.Clone();
+    }
+
+    public ReconnectInfo Clone() {
+      return new ReconnectInfo(this);
+    }
+
+    public const int PassedFieldNumber = 1;
+    private bool passed_;
+    public bool Passed {
+      get { return passed_; }
+      set {
+        passed_ = value;
+      }
+    }
+
+    public const int BackoffMsFieldNumber = 2;
+    private static readonly pb::FieldCodec<int> _repeated_backoffMs_codec
+        = pb::FieldCodec.ForInt32(18);
+    private readonly pbc::RepeatedField<int> backoffMs_ = new pbc::RepeatedField<int>();
+    public pbc::RepeatedField<int> BackoffMs {
+      get { return backoffMs_; }
+    }
+
+    public override bool Equals(object other) {
+      return Equals(other as ReconnectInfo);
+    }
+
+    public bool Equals(ReconnectInfo other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Passed != other.Passed) return false;
+      if(!backoffMs_.Equals(other.backoffMs_)) return false;
+      return true;
+    }
+
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Passed != false) hash ^= Passed.GetHashCode();
+      hash ^= backoffMs_.GetHashCode();
+      return hash;
+    }
+
+    public override string ToString() {
+      return pb::JsonFormatter.Default.Format(this);
+    }
+
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Passed != false) {
+        output.WriteRawTag(8);
+        output.WriteBool(Passed);
+      }
+      backoffMs_.WriteTo(output, _repeated_backoffMs_codec);
+    }
+
+    public int CalculateSize() {
+      int size = 0;
+      if (Passed != false) {
+        size += 1 + 1;
+      }
+      size += backoffMs_.CalculateSize(_repeated_backoffMs_codec);
+      return size;
+    }
+
+    public void MergeFrom(ReconnectInfo other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Passed != false) {
+        Passed = other.Passed;
+      }
+      backoffMs_.Add(other.backoffMs_);
+    }
+
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Passed = input.ReadBool();
+            break;
+          }
+          case 18:
+          case 16: {
+            backoffMs_.AddEntriesFrom(input, _repeated_backoffMs_codec);
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
   #endregion
 
 }
diff --git a/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5325b2fa148a66cee882e8398909f404af1fe00f
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs
@@ -0,0 +1,97 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Utils;
+using Grpc.Testing;
+using NUnit.Framework;
+
+namespace Grpc.IntegrationTesting
+{
+    public class MetadataCredentialsTest
+    {
+        const string Host = "localhost";
+        Server server;
+        Channel channel;
+        TestService.ITestServiceClient client;
+
+        [TestFixtureSetUp]
+        public void Init()
+        {
+            var serverCredentials = new SslServerCredentials(new[] { new KeyCertificatePair(File.ReadAllText(TestCredentials.ServerCertChainPath), File.ReadAllText(TestCredentials.ServerPrivateKeyPath)) });
+            server = new Server
+            {
+                Services = { TestService.BindService(new TestServiceImpl()) },
+                Ports = { { Host, ServerPort.PickUnused, serverCredentials } }
+            };
+            server.Start();
+
+            var options = new List<ChannelOption>
+            {
+                new ChannelOption(ChannelOptions.SslTargetNameOverride, TestCredentials.DefaultHostOverride)
+            };
+
+            var asyncAuthInterceptor = new AsyncAuthInterceptor(async (authUri, metadata) =>
+            {
+                await Task.Delay(100);  // make sure the operation is asynchronous.
+                metadata.Add("authorization", "SECRET_TOKEN");
+            });
+
+            var clientCredentials = ChannelCredentials.Create(
+                new SslCredentials(File.ReadAllText(TestCredentials.ClientCertAuthorityPath)),
+                new MetadataCredentials(asyncAuthInterceptor));
+            channel = new Channel(Host, server.Ports.Single().BoundPort, clientCredentials, options);
+            client = TestService.NewClient(channel);
+        }
+
+        [TestFixtureTearDown]
+        public void Cleanup()
+        {
+            channel.ShutdownAsync().Wait();
+            server.ShutdownAsync().Wait();
+        }
+
+        [Test]
+        public void MetadataCredentials()
+        {
+            var response = client.UnaryCall(new SimpleRequest { ResponseSize = 10 });
+            Assert.AreEqual(10, response.Payload.Body.Length);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.IntegrationTesting/Test.cs b/src/csharp/Grpc.IntegrationTesting/Test.cs
index 466ec57d3dc59038722dfad44c13e04af2470050..cf477070587d20abea1104afa53a6db01e8c08b3 100644
--- a/src/csharp/Grpc.IntegrationTesting/Test.cs
+++ b/src/csharp/Grpc.IntegrationTesting/Test.cs
@@ -1,5 +1,5 @@
 // Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: test.proto
+// source: test/proto/test.proto
 #pragma warning disable 1591, 0612, 3021
 #region Designer generated code
 
@@ -21,21 +21,26 @@ namespace Grpc.Testing {
     static Test() {
       byte[] descriptorData = global::System.Convert.FromBase64String(
           string.Concat(
-            "Cgp0ZXN0LnByb3RvEgxncnBjLnRlc3RpbmcaC2VtcHR5LnByb3RvGg5tZXNz", 
-            "YWdlcy5wcm90bzK7BAoLVGVzdFNlcnZpY2USNQoJRW1wdHlDYWxsEhMuZ3Jw", 
-            "Yy50ZXN0aW5nLkVtcHR5GhMuZ3JwYy50ZXN0aW5nLkVtcHR5EkYKCVVuYXJ5", 
-            "Q2FsbBIbLmdycGMudGVzdGluZy5TaW1wbGVSZXF1ZXN0GhwuZ3JwYy50ZXN0", 
-            "aW5nLlNpbXBsZVJlc3BvbnNlEmwKE1N0cmVhbWluZ091dHB1dENhbGwSKC5n", 
-            "cnBjLnRlc3RpbmcuU3RyZWFtaW5nT3V0cHV0Q2FsbFJlcXVlc3QaKS5ncnBj", 
-            "LnRlc3RpbmcuU3RyZWFtaW5nT3V0cHV0Q2FsbFJlc3BvbnNlMAESaQoSU3Ry", 
-            "ZWFtaW5nSW5wdXRDYWxsEicuZ3JwYy50ZXN0aW5nLlN0cmVhbWluZ0lucHV0", 
-            "Q2FsbFJlcXVlc3QaKC5ncnBjLnRlc3RpbmcuU3RyZWFtaW5nSW5wdXRDYWxs", 
-            "UmVzcG9uc2UoARJpCg5GdWxsRHVwbGV4Q2FsbBIoLmdycGMudGVzdGluZy5T", 
-            "dHJlYW1pbmdPdXRwdXRDYWxsUmVxdWVzdBopLmdycGMudGVzdGluZy5TdHJl", 
-            "YW1pbmdPdXRwdXRDYWxsUmVzcG9uc2UoATABEmkKDkhhbGZEdXBsZXhDYWxs", 
-            "EiguZ3JwYy50ZXN0aW5nLlN0cmVhbWluZ091dHB1dENhbGxSZXF1ZXN0Giku", 
-            "Z3JwYy50ZXN0aW5nLlN0cmVhbWluZ091dHB1dENhbGxSZXNwb25zZSgBMAFi", 
-            "BnByb3RvMw=="));
+            "ChV0ZXN0L3Byb3RvL3Rlc3QucHJvdG8SDGdycGMudGVzdGluZxoWdGVzdC9w", 
+            "cm90by9lbXB0eS5wcm90bxoZdGVzdC9wcm90by9tZXNzYWdlcy5wcm90bzK7", 
+            "BAoLVGVzdFNlcnZpY2USNQoJRW1wdHlDYWxsEhMuZ3JwYy50ZXN0aW5nLkVt", 
+            "cHR5GhMuZ3JwYy50ZXN0aW5nLkVtcHR5EkYKCVVuYXJ5Q2FsbBIbLmdycGMu", 
+            "dGVzdGluZy5TaW1wbGVSZXF1ZXN0GhwuZ3JwYy50ZXN0aW5nLlNpbXBsZVJl", 
+            "c3BvbnNlEmwKE1N0cmVhbWluZ091dHB1dENhbGwSKC5ncnBjLnRlc3Rpbmcu", 
+            "U3RyZWFtaW5nT3V0cHV0Q2FsbFJlcXVlc3QaKS5ncnBjLnRlc3RpbmcuU3Ry", 
+            "ZWFtaW5nT3V0cHV0Q2FsbFJlc3BvbnNlMAESaQoSU3RyZWFtaW5nSW5wdXRD", 
+            "YWxsEicuZ3JwYy50ZXN0aW5nLlN0cmVhbWluZ0lucHV0Q2FsbFJlcXVlc3Qa", 
+            "KC5ncnBjLnRlc3RpbmcuU3RyZWFtaW5nSW5wdXRDYWxsUmVzcG9uc2UoARJp", 
+            "Cg5GdWxsRHVwbGV4Q2FsbBIoLmdycGMudGVzdGluZy5TdHJlYW1pbmdPdXRw", 
+            "dXRDYWxsUmVxdWVzdBopLmdycGMudGVzdGluZy5TdHJlYW1pbmdPdXRwdXRD", 
+            "YWxsUmVzcG9uc2UoATABEmkKDkhhbGZEdXBsZXhDYWxsEiguZ3JwYy50ZXN0", 
+            "aW5nLlN0cmVhbWluZ091dHB1dENhbGxSZXF1ZXN0GikuZ3JwYy50ZXN0aW5n", 
+            "LlN0cmVhbWluZ091dHB1dENhbGxSZXNwb25zZSgBMAEyVQoUVW5pbXBsZW1l", 
+            "bnRlZFNlcnZpY2USPQoRVW5pbXBsZW1lbnRlZENhbGwSEy5ncnBjLnRlc3Rp", 
+            "bmcuRW1wdHkaEy5ncnBjLnRlc3RpbmcuRW1wdHkyfwoQUmVjb25uZWN0U2Vy", 
+            "dmljZRIxCgVTdGFydBITLmdycGMudGVzdGluZy5FbXB0eRoTLmdycGMudGVz", 
+            "dGluZy5FbXB0eRI4CgRTdG9wEhMuZ3JwYy50ZXN0aW5nLkVtcHR5GhsuZ3Jw", 
+            "Yy50ZXN0aW5nLlJlY29ubmVjdEluZm9iBnByb3RvMw=="));
       descriptor = pbr::FileDescriptor.InternalBuildGeneratedFileFrom(descriptorData,
           new pbr::FileDescriptor[] { global::Grpc.Testing.Proto.Empty.Descriptor, global::Grpc.Testing.Messages.Descriptor, },
           new pbr::GeneratedCodeInfo(null, null));
diff --git a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
index f63e1484759c5510faef2e194fcd163dc47d37bd..8c884b74086e5a29a95c0c69874a02f6e03aa8b6 100644
--- a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
+++ b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
@@ -1,5 +1,5 @@
 // Generated by the protocol buffer compiler.  DO NOT EDIT!
-// source: test.proto
+// source: test/proto/test.proto
 #region Designer generated code
 
 using System;
@@ -207,5 +207,191 @@ namespace Grpc.Testing {
     }
 
   }
+  public static class UnimplementedService
+  {
+    static readonly string __ServiceName = "grpc.testing.UnimplementedService";
+
+    static readonly Marshaller<global::Grpc.Testing.Empty> __Marshaller_Empty = Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Testing.Empty.Parser.ParseFrom);
+
+    static readonly Method<global::Grpc.Testing.Empty, global::Grpc.Testing.Empty> __Method_UnimplementedCall = new Method<global::Grpc.Testing.Empty, global::Grpc.Testing.Empty>(
+        MethodType.Unary,
+        __ServiceName,
+        "UnimplementedCall",
+        __Marshaller_Empty,
+        __Marshaller_Empty);
+
+    // service descriptor
+    public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor
+    {
+      get { return global::Grpc.Testing.Test.Descriptor.Services[1]; }
+    }
+
+    // client interface
+    public interface IUnimplementedServiceClient
+    {
+      global::Grpc.Testing.Empty UnimplementedCall(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
+      global::Grpc.Testing.Empty UnimplementedCall(global::Grpc.Testing.Empty request, CallOptions options);
+      AsyncUnaryCall<global::Grpc.Testing.Empty> UnimplementedCallAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
+      AsyncUnaryCall<global::Grpc.Testing.Empty> UnimplementedCallAsync(global::Grpc.Testing.Empty request, CallOptions options);
+    }
+
+    // server-side interface
+    public interface IUnimplementedService
+    {
+      Task<global::Grpc.Testing.Empty> UnimplementedCall(global::Grpc.Testing.Empty request, ServerCallContext context);
+    }
+
+    // client stub
+    public class UnimplementedServiceClient : ClientBase, IUnimplementedServiceClient
+    {
+      public UnimplementedServiceClient(Channel channel) : base(channel)
+      {
+      }
+      public global::Grpc.Testing.Empty UnimplementedCall(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        var call = CreateCall(__Method_UnimplementedCall, new CallOptions(headers, deadline, cancellationToken));
+        return Calls.BlockingUnaryCall(call, request);
+      }
+      public global::Grpc.Testing.Empty UnimplementedCall(global::Grpc.Testing.Empty request, CallOptions options)
+      {
+        var call = CreateCall(__Method_UnimplementedCall, options);
+        return Calls.BlockingUnaryCall(call, request);
+      }
+      public AsyncUnaryCall<global::Grpc.Testing.Empty> UnimplementedCallAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        var call = CreateCall(__Method_UnimplementedCall, new CallOptions(headers, deadline, cancellationToken));
+        return Calls.AsyncUnaryCall(call, request);
+      }
+      public AsyncUnaryCall<global::Grpc.Testing.Empty> UnimplementedCallAsync(global::Grpc.Testing.Empty request, CallOptions options)
+      {
+        var call = CreateCall(__Method_UnimplementedCall, options);
+        return Calls.AsyncUnaryCall(call, request);
+      }
+    }
+
+    // creates service definition that can be registered with a server
+    public static ServerServiceDefinition BindService(IUnimplementedService serviceImpl)
+    {
+      return ServerServiceDefinition.CreateBuilder(__ServiceName)
+          .AddMethod(__Method_UnimplementedCall, serviceImpl.UnimplementedCall).Build();
+    }
+
+    // creates a new client
+    public static UnimplementedServiceClient NewClient(Channel channel)
+    {
+      return new UnimplementedServiceClient(channel);
+    }
+
+  }
+  public static class ReconnectService
+  {
+    static readonly string __ServiceName = "grpc.testing.ReconnectService";
+
+    static readonly Marshaller<global::Grpc.Testing.Empty> __Marshaller_Empty = Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Testing.Empty.Parser.ParseFrom);
+    static readonly Marshaller<global::Grpc.Testing.ReconnectInfo> __Marshaller_ReconnectInfo = Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Testing.ReconnectInfo.Parser.ParseFrom);
+
+    static readonly Method<global::Grpc.Testing.Empty, global::Grpc.Testing.Empty> __Method_Start = new Method<global::Grpc.Testing.Empty, global::Grpc.Testing.Empty>(
+        MethodType.Unary,
+        __ServiceName,
+        "Start",
+        __Marshaller_Empty,
+        __Marshaller_Empty);
+
+    static readonly Method<global::Grpc.Testing.Empty, global::Grpc.Testing.ReconnectInfo> __Method_Stop = new Method<global::Grpc.Testing.Empty, global::Grpc.Testing.ReconnectInfo>(
+        MethodType.Unary,
+        __ServiceName,
+        "Stop",
+        __Marshaller_Empty,
+        __Marshaller_ReconnectInfo);
+
+    // service descriptor
+    public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor
+    {
+      get { return global::Grpc.Testing.Test.Descriptor.Services[2]; }
+    }
+
+    // client interface
+    public interface IReconnectServiceClient
+    {
+      global::Grpc.Testing.Empty Start(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
+      global::Grpc.Testing.Empty Start(global::Grpc.Testing.Empty request, CallOptions options);
+      AsyncUnaryCall<global::Grpc.Testing.Empty> StartAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
+      AsyncUnaryCall<global::Grpc.Testing.Empty> StartAsync(global::Grpc.Testing.Empty request, CallOptions options);
+      global::Grpc.Testing.ReconnectInfo Stop(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
+      global::Grpc.Testing.ReconnectInfo Stop(global::Grpc.Testing.Empty request, CallOptions options);
+      AsyncUnaryCall<global::Grpc.Testing.ReconnectInfo> StopAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
+      AsyncUnaryCall<global::Grpc.Testing.ReconnectInfo> StopAsync(global::Grpc.Testing.Empty request, CallOptions options);
+    }
+
+    // server-side interface
+    public interface IReconnectService
+    {
+      Task<global::Grpc.Testing.Empty> Start(global::Grpc.Testing.Empty request, ServerCallContext context);
+      Task<global::Grpc.Testing.ReconnectInfo> Stop(global::Grpc.Testing.Empty request, ServerCallContext context);
+    }
+
+    // client stub
+    public class ReconnectServiceClient : ClientBase, IReconnectServiceClient
+    {
+      public ReconnectServiceClient(Channel channel) : base(channel)
+      {
+      }
+      public global::Grpc.Testing.Empty Start(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        var call = CreateCall(__Method_Start, new CallOptions(headers, deadline, cancellationToken));
+        return Calls.BlockingUnaryCall(call, request);
+      }
+      public global::Grpc.Testing.Empty Start(global::Grpc.Testing.Empty request, CallOptions options)
+      {
+        var call = CreateCall(__Method_Start, options);
+        return Calls.BlockingUnaryCall(call, request);
+      }
+      public AsyncUnaryCall<global::Grpc.Testing.Empty> StartAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        var call = CreateCall(__Method_Start, new CallOptions(headers, deadline, cancellationToken));
+        return Calls.AsyncUnaryCall(call, request);
+      }
+      public AsyncUnaryCall<global::Grpc.Testing.Empty> StartAsync(global::Grpc.Testing.Empty request, CallOptions options)
+      {
+        var call = CreateCall(__Method_Start, options);
+        return Calls.AsyncUnaryCall(call, request);
+      }
+      public global::Grpc.Testing.ReconnectInfo Stop(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        var call = CreateCall(__Method_Stop, new CallOptions(headers, deadline, cancellationToken));
+        return Calls.BlockingUnaryCall(call, request);
+      }
+      public global::Grpc.Testing.ReconnectInfo Stop(global::Grpc.Testing.Empty request, CallOptions options)
+      {
+        var call = CreateCall(__Method_Stop, options);
+        return Calls.BlockingUnaryCall(call, request);
+      }
+      public AsyncUnaryCall<global::Grpc.Testing.ReconnectInfo> StopAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        var call = CreateCall(__Method_Stop, new CallOptions(headers, deadline, cancellationToken));
+        return Calls.AsyncUnaryCall(call, request);
+      }
+      public AsyncUnaryCall<global::Grpc.Testing.ReconnectInfo> StopAsync(global::Grpc.Testing.Empty request, CallOptions options)
+      {
+        var call = CreateCall(__Method_Stop, options);
+        return Calls.AsyncUnaryCall(call, request);
+      }
+    }
+
+    // creates service definition that can be registered with a server
+    public static ServerServiceDefinition BindService(IReconnectService serviceImpl)
+    {
+      return ServerServiceDefinition.CreateBuilder(__ServiceName)
+          .AddMethod(__Method_Start, serviceImpl.Start)
+          .AddMethod(__Method_Stop, serviceImpl.Stop).Build();
+    }
+
+    // creates a new client
+    public static ReconnectServiceClient NewClient(Channel channel)
+    {
+      return new ReconnectServiceClient(channel);
+    }
+
+  }
 }
 #endregion
diff --git a/src/csharp/Grpc.IntegrationTesting/proto/messages.proto b/src/csharp/Grpc.IntegrationTesting/proto/messages.proto
deleted file mode 100644
index 7df85e3c13601ddc64e85a07eeadd502759a5761..0000000000000000000000000000000000000000
--- a/src/csharp/Grpc.IntegrationTesting/proto/messages.proto
+++ /dev/null
@@ -1,132 +0,0 @@
-
-// Copyright 2015, Google Inc.
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//     * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-// Message definitions to be used by integration test service definitions.
-
-syntax = "proto3";
-
-package grpc.testing;
-
-// The type of payload that should be returned.
-enum PayloadType {
-  // Compressable text format.
-  COMPRESSABLE = 0;
-
-  // Uncompressable binary format.
-  UNCOMPRESSABLE = 1;
-
-  // Randomly chosen from all other formats defined in this enum.
-  RANDOM = 2;
-}
-
-// A block of data, to simply increase gRPC message size.
-message Payload {
-  // The type of data in body.
-  PayloadType type = 1;
-  // Primary contents of payload.
-  bytes body = 2;
-}
-
-// Unary request.
-message SimpleRequest {
-  // Desired payload type in the response from the server.
-  // If response_type is RANDOM, server randomly chooses one from other formats.
-  PayloadType response_type = 1;
-
-  // Desired payload size in the response from the server.
-  // If response_type is COMPRESSABLE, this denotes the size before compression.
-  int32 response_size = 2;
-
-  // Optional input payload sent along with the request.
-  Payload payload = 3;
-
-  // Whether SimpleResponse should include username.
-  bool fill_username = 4;
-
-  // Whether SimpleResponse should include OAuth scope.
-  bool fill_oauth_scope = 5;
-}
-
-// Unary response, as configured by the request.
-message SimpleResponse {
-  // Payload to increase message size.
-  Payload payload = 1;
-  // The user the request came from, for verifying authentication was
-  // successful when the client expected it.
-  string username = 2;
-  // OAuth scope.
-  string oauth_scope = 3;
-}
-
-// Client-streaming request.
-message StreamingInputCallRequest {
-  // Optional input payload sent along with the request.
-  Payload payload = 1;
-
-  // Not expecting any payload from the response.
-}
-
-// Client-streaming response.
-message StreamingInputCallResponse {
-  // Aggregated size of payloads received from the client.
-  int32 aggregated_payload_size = 1;
-}
-
-// Configuration for a particular response.
-message ResponseParameters {
-  // Desired payload sizes in responses from the server.
-  // If response_type is COMPRESSABLE, this denotes the size before compression.
-  int32 size = 1;
-
-  // Desired interval between consecutive responses in the response stream in
-  // microseconds.
-  int32 interval_us = 2;
-}
-
-// Server-streaming request.
-message StreamingOutputCallRequest {
-  // Desired payload type in the response from the server.
-  // If response_type is RANDOM, the payload from each response in the stream
-  // might be of different types. This is to simulate a mixed type of payload
-  // stream.
-  PayloadType response_type = 1;
-
-  // Configuration for each expected response message.
-  repeated ResponseParameters response_parameters = 2;
-
-  // Optional input payload sent along with the request.
-  Payload payload = 3;
-}
-
-// Server-streaming response, as configured by the request and parameters.
-message StreamingOutputCallResponse {
-  // Payload to increase response size.
-  Payload payload = 1;
-}
diff --git a/src/csharp/Grpc.IntegrationTesting/proto/test.proto b/src/csharp/Grpc.IntegrationTesting/proto/test.proto
deleted file mode 100644
index 5496f72af07bcb3cab6505ac21258e6ae08bac46..0000000000000000000000000000000000000000
--- a/src/csharp/Grpc.IntegrationTesting/proto/test.proto
+++ /dev/null
@@ -1,71 +0,0 @@
-
-// Copyright 2015, Google Inc.
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//     * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-// An integration test service that covers all the method signature permutations
-// of unary/streaming requests/responses.
-syntax = "proto3";
-
-import "empty.proto";
-import "messages.proto";
-
-package grpc.testing;
-
-// A simple service to test the various types of RPCs and experiment with
-// performance with various types of payload.
-service TestService {
-  // One empty request followed by one empty response.
-  rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty);
-
-  // One request followed by one response.
-  rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
-
-  // One request followed by a sequence of responses (streamed download).
-  // The server returns the payload with client desired type and sizes.
-  rpc StreamingOutputCall(StreamingOutputCallRequest)
-      returns (stream StreamingOutputCallResponse);
-
-  // A sequence of requests followed by one response (streamed upload).
-  // The server returns the aggregated size of client payload as the result.
-  rpc StreamingInputCall(stream StreamingInputCallRequest)
-      returns (StreamingInputCallResponse);
-
-  // A sequence of requests with each request served by the server immediately.
-  // As one request could lead to multiple responses, this interface
-  // demonstrates the idea of full duplexing.
-  rpc FullDuplexCall(stream StreamingOutputCallRequest)
-      returns (stream StreamingOutputCallResponse);
-
-  // A sequence of requests followed by a sequence of responses.
-  // The server buffers all the client requests and then serves them in order. A
-  // stream of responses are returned to the client when the server starts with
-  // first request.
-  rpc HalfDuplexCall(stream StreamingOutputCallRequest)
-      returns (stream StreamingOutputCallResponse);
-}
diff --git a/src/csharp/build_packages.bat b/src/csharp/build_packages.bat
index a3505b1e0126358a64ff4fcb32a9209dac17c47a..b864a955a89d2b3b2badbf2f720f84b27497fa31 100644
--- a/src/csharp/build_packages.bat
+++ b/src/csharp/build_packages.bat
@@ -20,9 +20,9 @@ endlocal
 
 @call ..\..\vsprojects\build_plugins.bat || goto :error
 
-%NUGET% pack ..\..\vsprojects\nuget_package\grpc.native.csharp_ext.nuspec -Version %CORE_VERSION% || goto :error
+%NUGET% pack ..\..\vsprojects\nuget_package\grpc.native.csharp.nuspec -Version %CORE_VERSION% || goto :error
 %NUGET% pack Grpc.Auth\Grpc.Auth.nuspec -Symbols -Version %VERSION% || goto :error
-%NUGET% pack Grpc.Core\Grpc.Core.nuspec -Symbols -Version %VERSION% -Properties GrpcNativeCsharpExtVersion=%CORE_VERSION% || goto :error
+%NUGET% pack Grpc.Core\Grpc.Core.nuspec -Symbols -Version %VERSION% -Properties GrpcNativeCsharpVersion=%CORE_VERSION% || goto :error
 %NUGET% pack Grpc.HealthCheck\Grpc.HealthCheck.nuspec -Symbols -Version %VERSION_WITH_BETA% -Properties ProtobufVersion=%PROTOBUF_VERSION% || goto :error
 %NUGET% pack Grpc.Tools.nuspec -Version %VERSION% || goto :error
 %NUGET% pack Grpc.nuspec -Version %VERSION% || goto :error
diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c
index 51e0728fb9dd17b7a7c6b88ecb7ed27a2836a620..679ca43d74913c570a1b1c58988aae75af092b04 100644
--- a/src/csharp/ext/grpc_csharp_ext.c
+++ b/src/csharp/ext/grpc_csharp_ext.c
@@ -68,7 +68,7 @@ grpc_byte_buffer *string_to_byte_buffer(const char *buffer, size_t len) {
 /*
  * Helper to maintain lifetime of batch op inputs and store batch op outputs.
  */
-typedef struct gprcsharp_batch_context {
+typedef struct grpcsharp_batch_context {
   grpc_metadata_array send_initial_metadata;
   grpc_byte_buffer *send_message;
   struct {
@@ -665,16 +665,16 @@ grpcsharp_call_start_duplex_streaming(grpc_call *call,
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_recv_initial_metadata(
-	grpc_call *call, grpcsharp_batch_context *ctx) {
-	/* TODO: don't use magic number */
-	grpc_op ops[1];
-	ops[0].op = GRPC_OP_RECV_INITIAL_METADATA;
-	ops[0].data.recv_initial_metadata = &(ctx->recv_initial_metadata);
-	ops[0].flags = 0;
-	ops[0].reserved = NULL;
+  grpc_call *call, grpcsharp_batch_context *ctx) {
+  /* TODO: don't use magic number */
+  grpc_op ops[1];
+  ops[0].op = GRPC_OP_RECV_INITIAL_METADATA;
+  ops[0].data.recv_initial_metadata = &(ctx->recv_initial_metadata);
+  ops[0].flags = 0;
+  ops[0].reserved = NULL;
 
-	return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
-		NULL);
+  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
+    NULL);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE
@@ -785,6 +785,11 @@ grpcsharp_call_send_initial_metadata(grpc_call *call,
                                NULL);
 }
 
+GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_set_credentials(grpc_call *call,
+                                                            grpc_credentials *creds) {
+	return grpc_call_set_credentials(call, creds);
+}
+
 /* Server */
 
 GPR_EXPORT grpc_server *GPR_CALLTYPE
@@ -892,6 +897,47 @@ grpcsharp_server_add_secure_http2_port(grpc_server *server, const char *addr,
   return grpc_server_add_secure_http2_port(server, addr, creds);
 }
 
+GPR_EXPORT grpc_credentials *GPR_CALLTYPE grpcsharp_composite_credentials_create(
+  grpc_credentials *creds1,
+  grpc_credentials *creds2) {
+  return grpc_composite_credentials_create(creds1, creds2, NULL);
+}
+
+/* Metadata credentials plugin */
+
+GPR_EXPORT void GPR_CALLTYPE grpcsharp_metadata_credentials_notify_from_plugin(
+    grpc_credentials_plugin_metadata_cb cb,
+    void *user_data, grpc_metadata_array *metadata,
+  grpc_status_code status, const char *error_details) {
+  cb(user_data, metadata->metadata, metadata->count, status, error_details);
+}
+
+typedef void(GPR_CALLTYPE *grpcsharp_metadata_interceptor_func)(
+  void *state, const char *service_url, grpc_credentials_plugin_metadata_cb cb,
+  void *user_data, gpr_int32 is_destroy);
+
+static void grpcsharp_get_metadata_handler(void *state, const char *service_url,
+  grpc_credentials_plugin_metadata_cb cb, void *user_data) {
+  grpcsharp_metadata_interceptor_func interceptor =
+      (grpcsharp_metadata_interceptor_func)(gpr_intptr)state;
+  interceptor(state, service_url, cb, user_data, 0);
+}
+
+static void grpcsharp_metadata_credentials_destroy_handler(void *state) {
+  grpcsharp_metadata_interceptor_func interceptor =
+      (grpcsharp_metadata_interceptor_func)(gpr_intptr)state;
+  interceptor(state, NULL, NULL, NULL, 1);
+}
+
+GPR_EXPORT grpc_credentials *GPR_CALLTYPE grpcsharp_metadata_credentials_create_from_plugin(
+  grpcsharp_metadata_interceptor_func metadata_interceptor) {
+  grpc_metadata_credentials_plugin plugin;
+  plugin.get_metadata = grpcsharp_get_metadata_handler;
+  plugin.destroy = grpcsharp_metadata_credentials_destroy_handler;
+  plugin.state = (void*)(gpr_intptr)metadata_interceptor;
+  return grpc_metadata_credentials_create_from_plugin(plugin, NULL);
+}
+
 /* Logging */
 
 typedef void(GPR_CALLTYPE *grpcsharp_log_func)(const char *file, gpr_int32 line,
diff --git a/src/csharp/generate_proto_csharp.sh b/src/csharp/generate_proto_csharp.sh
index a17f45b58741850355e244d8ea5fce29b54e1edb..f879e074aa55a21b69dfe6c5f31e29fb34fdff6d 100755
--- a/src/csharp/generate_proto_csharp.sh
+++ b/src/csharp/generate_proto_csharp.sh
@@ -42,7 +42,7 @@ $PROTOC --plugin=$PLUGIN --csharp_out=$EXAMPLES_DIR --grpc_out=$EXAMPLES_DIR \
     -I $EXAMPLES_DIR/proto $EXAMPLES_DIR/proto/math.proto
 
 $PROTOC --plugin=$PLUGIN --csharp_out=$INTEROP_DIR --grpc_out=$INTEROP_DIR \
-    -I $INTEROP_DIR/proto $INTEROP_DIR/proto/*.proto
+    -I ../.. ../../test/proto/*.proto
 
 $PROTOC --plugin=$PLUGIN --csharp_out=$HEALTHCHECK_DIR --grpc_out=$HEALTHCHECK_DIR \
     -I $HEALTHCHECK_DIR/proto $HEALTHCHECK_DIR/proto/health.proto
diff --git a/src/node/interop/interop_client.js b/src/node/interop/interop_client.js
index 14cc6c0efe10ea79da81674a33d36a8e7b84d2eb..cb55083d1ae188ded69cce3976d8bd79e5497234 100644
--- a/src/node/interop/interop_client.js
+++ b/src/node/interop/interop_client.js
@@ -44,12 +44,14 @@ var GoogleAuth = require('google-auth-library');
 
 var assert = require('assert');
 
-var AUTH_SCOPE = 'https://www.googleapis.com/auth/xapi.zoo';
-var AUTH_SCOPE_RESPONSE = 'xapi.zoo';
-var AUTH_USER = ('155450119199-vefjjaekcc6cmsd5914v6lqufunmh9ue' +
-    '@developer.gserviceaccount.com');
-var COMPUTE_ENGINE_USER = ('155450119199-r5aaqa2vqoa9g5mv2m6s3m1l293rlmel' +
-    '@developer.gserviceaccount.com');
+var SERVICE_ACCOUNT_EMAIL;
+try {
+  SERVICE_ACCOUNT_EMAIL = require(
+      process.env.GOOGLE_APPLICATION_CREDENTIALS).client_email;
+} catch (e) {
+  // This will cause the tests to fail if they need that string
+  SERVICE_ACCOUNT_EMAIL = null;
+}
 
 var ECHO_INITIAL_KEY = 'x-grpc-test-echo-initial';
 var ECHO_TRAILING_KEY = 'x-grpc-test-echo-trailing-bin';
@@ -345,6 +347,41 @@ function customMetadata(client, done) {
   stream.end();
 }
 
+function statusCodeAndMessage(client, done) {
+  done = multiDone(done, 2);
+  var arg = {
+    response_status: {
+      code: 2,
+      message: 'test status message'
+    }
+  };
+  client.unaryCall(arg, function(err, resp) {
+    assert(err);
+    assert.strictEqual(err.code, 2);
+    assert.strictEqual(err.message, 'test status message');
+    done();
+  });
+  var duplex = client.fullDuplexCall();
+  duplex.on('status', function(status) {
+    assert(status);
+    assert.strictEqual(status.code, 2);
+    assert.strictEqual(status.details, 'test status message');
+    done();
+  });
+  duplex.on('error', function(){});
+  duplex.write(arg);
+  duplex.end();
+}
+
+function unimplementedMethod(client, done) {
+  client.unimplementedCall({}, function(err, resp) {
+    assert(err);
+    assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
+    assert(!err.message);
+    done();
+  });
+}
+
 /**
  * Run one of the authentication tests.
  * @param {string} expected_user The expected username in the response
@@ -369,7 +406,7 @@ function authTest(expected_user, scope, client, done) {
     assert.strictEqual(resp.payload.body.length, 314159);
     assert.strictEqual(resp.username, expected_user);
     if (scope) {
-      assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
+      assert(scope.indexOf(resp.oauth_scope) > -1);
     }
     if (done) {
       done();
@@ -377,56 +414,49 @@ function authTest(expected_user, scope, client, done) {
   });
 }
 
-function oauth2Test(expected_user, scope, per_rpc, client, done) {
-  (new GoogleAuth()).getApplicationDefault(function(err, credential) {
+function computeEngineCreds(client, done, extra) {
+  authTest(extra.service_account, null, client, done);
+}
+
+function serviceAccountCreds(client, done, extra) {
+  authTest(SERVICE_ACCOUNT_EMAIL, extra.oauth_scope, client, done);
+}
+
+function jwtTokenCreds(client, done, extra) {
+  authTest(SERVICE_ACCOUNT_EMAIL, null, client, done);
+}
+
+function oauth2Test(client, done, extra) {
+  var arg = {
+    fill_username: true,
+    fill_oauth_scope: true
+  };
+  client.unaryCall(arg, function(err, resp) {
     assert.ifError(err);
-    var arg = {
-      fill_username: true,
-      fill_oauth_scope: true
-    };
-    credential = credential.createScoped(scope);
-    credential.getAccessToken(function(err, token) {
-      assert.ifError(err);
-      var updateMetadata = function(authURI, metadata, callback) {
-        metadata.add('authorization', 'Bearer ' + token);
-        callback(null, metadata);
-      };
-      var makeTestCall = function(error, client_metadata) {
-        assert.ifError(error);
-        client.unaryCall(arg, function(err, resp) {
-          assert.ifError(err);
-          assert.strictEqual(resp.username, expected_user);
-          assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
-          if (done) {
-            done();
-          }
-        }, client_metadata);
-      };
-      if (per_rpc) {
-        updateMetadata('', new grpc.Metadata(), makeTestCall);
-      } else {
-        client.$updateMetadata = updateMetadata;
-        makeTestCall(null, new grpc.Metadata());
-      }
-    });
+    assert.strictEqual(resp.username, SERVICE_ACCOUNT_EMAIL);
+    assert(extra.oauth_scope.indexOf(resp.oauth_scope) > -1);
+    if (done) {
+      done();
+    }
   });
 }
 
-function perRpcAuthTest(expected_user, scope, per_rpc, client, done) {
+function perRpcAuthTest(client, done, extra) {
   (new GoogleAuth()).getApplicationDefault(function(err, credential) {
     assert.ifError(err);
     var arg = {
       fill_username: true,
       fill_oauth_scope: true
     };
+    var scope = extra.oauth_scope;
     if (credential.createScopedRequired() && scope) {
       credential = credential.createScoped(scope);
     }
     var creds = grpc.credentials.createFromGoogleCredential(credential);
     client.unaryCall(arg, function(err, resp) {
       assert.ifError(err);
-      assert.strictEqual(resp.username, expected_user);
-      assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
+      assert.strictEqual(resp.username, SERVICE_ACCOUNT_EMAIL);
+      assert(extra.oauth_scope.indexOf(resp.oauth_scope) > -1);
       if (done) {
         done();
       }
@@ -473,25 +503,44 @@ function getOauth2Creds(scope, callback) {
  * Map from test case names to test functions
  */
 var test_cases = {
-  empty_unary: {run: emptyUnary},
-  large_unary: {run: largeUnary},
-  client_streaming: {run: clientStreaming},
-  server_streaming: {run: serverStreaming},
-  ping_pong: {run: pingPong},
-  empty_stream: {run: emptyStream},
-  cancel_after_begin: {run: cancelAfterBegin},
-  cancel_after_first_response: {run: cancelAfterFirstResponse},
-  timeout_on_sleeping_server: {run: timeoutOnSleepingServer},
-  custom_metadata: {run: customMetadata},
-  compute_engine_creds: {run: _.partial(authTest, COMPUTE_ENGINE_USER, null),
-                         getCreds: _.partial(getApplicationCreds, null)},
-  service_account_creds: {run: _.partial(authTest, AUTH_USER, AUTH_SCOPE),
-                          getCreds: _.partial(getApplicationCreds, AUTH_SCOPE)},
-  jwt_token_creds: {run: _.partial(authTest, AUTH_USER, null),
-                    getCreds: _.partial(getApplicationCreds, null)},
-  oauth2_auth_token: {run: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, false),
-                      getCreds: _.partial(getOauth2Creds, AUTH_SCOPE)},
-  per_rpc_creds: {run: _.partial(perRpcAuthTest, AUTH_USER, AUTH_SCOPE, true)}
+  empty_unary: {run: emptyUnary,
+                Client: testProto.TestService},
+  large_unary: {run: largeUnary,
+                Client: testProto.TestService},
+  client_streaming: {run: clientStreaming,
+                     Client: testProto.TestService},
+  server_streaming: {run: serverStreaming,
+                     Client: testProto.TestService},
+  ping_pong: {run: pingPong,
+              Client: testProto.TestService},
+  empty_stream: {run: emptyStream,
+                 Client: testProto.TestService},
+  cancel_after_begin: {run: cancelAfterBegin,
+                       Client: testProto.TestService},
+  cancel_after_first_response: {run: cancelAfterFirstResponse,
+                                Client: testProto.TestService},
+  timeout_on_sleeping_server: {run: timeoutOnSleepingServer,
+                               Client: testProto.TestService},
+  custom_metadata: {run: customMetadata,
+                    Client: testProto.TestService},
+  status_code_and_message: {run: statusCodeAndMessage,
+                            Client: testProto.TestService},
+  unimplemented_method: {run: unimplementedMethod,
+                         Client: testProto.UnimplementedService},
+  compute_engine_creds: {run: computeEngineCreds,
+                         Client: testProto.TestService,
+                         getCreds: getApplicationCreds},
+  service_account_creds: {run: serviceAccountCreds,
+                          Client: testProto.TestService,
+                          getCreds: getApplicationCreds},
+  jwt_token_creds: {run: jwtTokenCreds,
+                    Client: testProto.TestService,
+                    getCreds: getApplicationCreds},
+  oauth2_auth_token: {run: oauth2Test,
+                      Client: testProto.TestService,
+                      getCreds: getOauth2Creds},
+  per_rpc_creds: {run: perRpcAuthTest,
+                  Client: testProto.TestService}
 };
 
 /**
@@ -504,8 +553,9 @@ var test_cases = {
  * @param {bool} tls Indicates that a secure channel should be used
  * @param {function} done Callback to call when the test is completed. Included
  *     primarily for use with mocha
+ * @param {object=} extra Extra options for some tests
  */
-function runTest(address, host_override, test_case, tls, test_ca, done) {
+function runTest(address, host_override, test_case, tls, test_ca, done, extra) {
   // TODO(mlumish): enable TLS functionality
   var options = {};
   var creds;
@@ -529,12 +579,13 @@ function runTest(address, host_override, test_case, tls, test_ca, done) {
 
   var execute = function(err, creds) {
     assert.ifError(err);
-    var client = new testProto.TestService(address, creds, options);
-    test.run(client, done);
+    var client = new test.Client(address, creds, options);
+    test.run(client, done, extra);
   };
 
   if (test.getCreds) {
-    test.getCreds(function(err, new_creds) {
+    test.getCreds(extra.oauth_scope, function(err, new_creds) {
+      assert.ifError(err);
       execute(err, grpc.credentials.combineChannelCredentials(
           creds, new_creds));
     });
@@ -547,13 +598,18 @@ if (require.main === module) {
   var parseArgs = require('minimist');
   var argv = parseArgs(process.argv, {
     string: ['server_host', 'server_host_override', 'server_port', 'test_case',
-             'use_tls', 'use_test_ca']
+             'use_tls', 'use_test_ca', 'default_service_account', 'oauth_scope',
+             'service_account_key_file']
   });
+  var extra_args = {
+    service_account: argv.default_service_account,
+    oauth_scope: argv.oauth_scope
+  };
   runTest(argv.server_host + ':' + argv.server_port, argv.server_host_override,
           argv.test_case, argv.use_tls === 'true', argv.use_test_ca === 'true',
           function () {
             console.log('OK:', argv.test_case);
-          });
+          }, extra_args);
 }
 
 /**
diff --git a/src/node/interop/interop_server.js b/src/node/interop/interop_server.js
index 3e83304faa39490dd0293c352dc14dbe79284cb8..5321005c863eda160f2d09e510f87bc6dde2ec62 100644
--- a/src/node/interop/interop_server.js
+++ b/src/node/interop/interop_server.js
@@ -44,6 +44,9 @@ var testProto = grpc.load({
 var ECHO_INITIAL_KEY = 'x-grpc-test-echo-initial';
 var ECHO_TRAILING_KEY = 'x-grpc-test-echo-trailing-bin';
 
+var incompressible_data = fs.readFileSync(
+    __dirname + '/../../../test/cpp/interop/rnd.dat');
+
 /**
  * Create a buffer filled with size zeroes
  * @param {number} size The length of the buffer
@@ -83,6 +86,19 @@ function getEchoTrailer(call) {
   return response_trailer;
 }
 
+function getPayload(payload_type, size) {
+  if (payload_type === 'RANDOM') {
+    payload_type = ['COMPRESSABLE',
+                    'UNCOMPRESSABLE'][Math.random() < 0.5 ? 0 : 1];
+  }
+  var body;
+  switch (payload_type) {
+    case 'COMPRESSABLE': body = zeroBuffer(size); break;
+    case 'UNCOMPRESSABLE': incompressible_data.slice(size); break;
+  }
+  return {type: payload_type, body: body};
+}
+
 /**
  * Respond to an empty parameter with an empty response.
  * NOTE: this currently does not work due to issue #137
@@ -104,13 +120,14 @@ function handleEmpty(call, callback) {
 function handleUnary(call, callback) {
   echoHeader(call);
   var req = call.request;
-  var zeros = zeroBuffer(req.response_size);
-  var payload_type = req.response_type;
-  if (payload_type === 'RANDOM') {
-    payload_type = ['COMPRESSABLE',
-                    'UNCOMPRESSABLE'][Math.random() < 0.5 ? 0 : 1];
+  if (req.response_status) {
+    var status = req.response_status;
+    status.metadata = getEchoTrailer(call);
+    callback(status);
+    return;
   }
-  callback(null, {payload: {type: payload_type, body: zeros}},
+  var payload = getPayload(req.response_type, req.response_size);
+  callback(null, {payload: payload},
            getEchoTrailer(call));
 }
 
@@ -139,18 +156,14 @@ function handleStreamingInput(call, callback) {
 function handleStreamingOutput(call) {
   echoHeader(call);
   var req = call.request;
-  var payload_type = req.response_type;
-  if (payload_type === 'RANDOM') {
-    payload_type = ['COMPRESSABLE',
-                    'UNCOMPRESSABLE'][Math.random() < 0.5 ? 0 : 1];
+  if (req.response_status) {
+    var status = req.response_status;
+    status.metadata = getEchoTrailer(call);
+    call.emit('error', status);
+    return;
   }
   _.each(req.response_parameters, function(resp_param) {
-    call.write({
-      payload: {
-        body: zeroBuffer(resp_param.size),
-        type: payload_type
-      }
-    });
+    call.write({payload: getPayload(req.response_type, resp_param.size)});
   });
   call.end(getEchoTrailer(call));
 }
@@ -163,18 +176,14 @@ function handleStreamingOutput(call) {
 function handleFullDuplex(call) {
   echoHeader(call);
   call.on('data', function(value) {
-    var payload_type = value.response_type;
-    if (payload_type === 'RANDOM') {
-      payload_type = ['COMPRESSABLE',
-                      'UNCOMPRESSABLE'][Math.random() < 0.5 ? 0 : 1];
+    if (value.response_status) {
+      var status = value.response_status;
+      status.metadata = getEchoTrailer(call);
+      call.emit('error', status);
+      return;
     }
     _.each(value.response_parameters, function(resp_param) {
-      call.write({
-        payload: {
-          body: zeroBuffer(resp_param.size),
-          type: payload_type
-        }
-      });
+      call.write({payload: getPayload(value.response_type, resp_param.size)});
     });
   });
   call.on('end', function() {
@@ -188,7 +197,7 @@ function handleFullDuplex(call) {
  * @param {Call} call Call to handle
  */
 function handleHalfDuplex(call) {
-  throw new Error('HalfDuplexCall not yet implemented');
+  call.emit('error', Error('HalfDuplexCall not yet implemented'));
 }
 
 /**
diff --git a/src/node/src/credentials.js b/src/node/src/credentials.js
index 009ff633067ae3bebe28a51274ae7cbf704434c7..ddc094f89684de28988a9633efaca44f5d02f848 100644
--- a/src/node/src/credentials.js
+++ b/src/node/src/credentials.js
@@ -153,7 +153,7 @@ exports.combineCallCredentials = function() {
     current = current.compose(arguments[i]);
   }
   return current;
-}
+};
 
 /**
  * Create an insecure credentials object. This is used to create a channel that
diff --git a/src/node/src/server.js b/src/node/src/server.js
index 87b5b7ad06ee739ea2ab6d21c765ea39cd70a37c..a974d593c95956e0e3a08f4d6fca5711d192d29b 100644
--- a/src/node/src/server.js
+++ b/src/node/src/server.js
@@ -629,7 +629,7 @@ function Server(options) {
             (new Metadata())._getCoreRepresentation();
         batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
           code: grpc.status.UNIMPLEMENTED,
-          details: 'This method is not available on this server.',
+          details: '',
           metadata: {}
         };
         batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
diff --git a/src/node/test/async_test.js b/src/node/test/async_test.js
index ce3ce50a2d6df113f8f4469b3920bf024ed94647..6d71ea24f54430160265b38d9ac0576d85b907a1 100644
--- a/src/node/test/async_test.js
+++ b/src/node/test/async_test.js
@@ -57,7 +57,7 @@ describe('Async functionality', function() {
                                grpc.ServerCredentials.createInsecure());
     server.start();
     math_client = new math.Math('localhost:' + port_num,
-                                grpc.Credentials.createInsecure());
+                                grpc.credentials.createInsecure());
     done();
   });
   after(function() {
diff --git a/src/node/test/channel_test.js b/src/node/test/channel_test.js
index b86f89b8a850ec8f6bf8292723f45b1129a94ffd..05269f7b6e4ef808d7d023e5baa9046af813fcb3 100644
--- a/src/node/test/channel_test.js
+++ b/src/node/test/channel_test.js
@@ -149,7 +149,7 @@ describe('channel', function() {
     afterEach(function() {
       channel.close();
     });
-    it.only('should time out if called alone', function(done) {
+    it('should time out if called alone', function(done) {
       var old_state = channel.getConnectivityState();
       var deadline = new Date();
       deadline.setSeconds(deadline.getSeconds() + 1);
diff --git a/src/node/test/credentials_test.js b/src/node/test/credentials_test.js
index 8eb91ee69e6a808442c9c65ea690eb368f5a09d0..7fc311a888dc0f139ecca1d7efb4e352c5f17536 100644
--- a/src/node/test/credentials_test.js
+++ b/src/node/test/credentials_test.js
@@ -130,8 +130,8 @@ describe('client credentials', function() {
       callback(null, metadata);
     };
     var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater);
-    var combined_creds = grpc.credentials.combineCredentials(client_ssl_creds,
-                                                             creds);
+    var combined_creds = grpc.credentials.combineChannelCredentials(
+        client_ssl_creds, creds);
     var client = new Client('localhost:' + port, combined_creds,
                             client_options);
     var call = client.unary({}, function(err, data) {
@@ -150,8 +150,8 @@ describe('client credentials', function() {
       callback(null, metadata);
     };
     var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater);
-    var combined_creds = grpc.credentials.combineCredentials(client_ssl_creds,
-                                                             creds);
+    var combined_creds = grpc.credentials.combineChannelCredentials(
+        client_ssl_creds, creds);
     var client = new Client('localhost:' + port, combined_creds,
                             client_options);
     var call = client.unary({}, function(err, data) {
@@ -231,7 +231,7 @@ describe('client credentials', function() {
           updater_creds, alt_updater_creds);
       var call = client.unary({}, function(err, data) {
         assert.ifError(err);
-      }, null, {credentials: updater_creds});
+      }, null, {credentials: combined_updater});
       call.on('metadata', function(metadata) {
         assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
         assert.deepEqual(metadata.get('other_plugin_key'),
diff --git a/src/node/test/interop_sanity_test.js b/src/node/test/interop_sanity_test.js
index 804c1d45e4a8cafcb60fc6252b288e9c98d22ec8..f8c0b14137796063457096d34047452cd820018f 100644
--- a/src/node/test/interop_sanity_test.js
+++ b/src/node/test/interop_sanity_test.js
@@ -71,7 +71,7 @@ describe('Interop tests', function() {
     interop_client.runTest(port, name_override, 'server_streaming', true, true,
                            done);
   });
-  it('should pass ping_pong', function(done) {
+  it.only('should pass ping_pong', function(done) {
     interop_client.runTest(port, name_override, 'ping_pong', true, true, done);
   });
   it('should pass empty_stream', function(done) {
@@ -94,4 +94,12 @@ describe('Interop tests', function() {
     interop_client.runTest(port, name_override, 'custom_metadata',
                            true, true, done);
   });
+  it('should pass status_code_and_message', function(done) {
+    interop_client.runTest(port, name_override, 'status_code_and_message',
+                           true, true, done);
+  });
+  it('should pass unimplemented_method', function(done) {
+    interop_client.runTest(port, name_override, 'unimplemented_method',
+                           true, true, done);
+  });
 });
diff --git a/src/objective-c/README.md b/src/objective-c/README.md
index 6c27657def06f859d96af55c8eb0cf67b6aeb6a2..a861a9f6f93cba29640887c0508912e5cff413ed 100644
--- a/src/objective-c/README.md
+++ b/src/objective-c/README.md
@@ -17,7 +17,7 @@ services.
 <a name="install"></a>
 ## Install protoc with the gRPC plugin
 
-On Mac OS X, install [homebrew][]. On Linux, install [linuxbrew][].
+On Mac OS X, install [homebrew][].
 
 Run the following command to install _protoc_ and the gRPC _protoc_ plugin:
 ```sh
@@ -153,7 +153,7 @@ _protoc_, in which case no system modification nor renaming is necessary.
 <a name="no-cocoapods"></a>
 ### Integrate the generated gRPC library without using Cocoapods
 
-You need to compile the generated `.pbpbjc.*` files (the enums and messages) without ARC support,
+You need to compile the generated `.pbobjc.*` files (the enums and messages) without ARC support,
 and the generated `.pbrpc.*` files (the services) with ARC support. The generated code depends on
 v0.5+ of the Objective-C gRPC runtime library and v3.0.0-alpha-3+ of the Objective-C Protobuf
 runtime library.
@@ -168,7 +168,6 @@ Objective-C Protobuf runtime library.
 
 [Protocol Buffers]:https://developers.google.com/protocol-buffers/
 [homebrew]:http://brew.sh
-[linuxbrew]:https://github.com/Homebrew/linuxbrew
 [gRPC install script]:https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install
 [example Podfile]:https://github.com/grpc/grpc/blob/master/src/objective-c/examples/Sample/Podfile
 [sample app]: https://github.com/grpc/grpc/tree/master/src/objective-c/examples/Sample
diff --git a/src/objective-c/RxLibrary/GRXWriter.m b/src/objective-c/RxLibrary/GRXWriter.m
index 019fcbd7858320819cb9648dde98f62ccb4874e8..fee33f5556343ff062b73d4bb39563ac255b036e 100644
--- a/src/objective-c/RxLibrary/GRXWriter.m
+++ b/src/objective-c/RxLibrary/GRXWriter.m
@@ -35,4 +35,14 @@
 
 @implementation GRXWriter
 
+- (void)startWithWriteable:(id<GRXWriteable>)writeable {
+  NSAssert(NO, @"Missing base implementation for %@", NSStringFromSelector(_cmd));
+  [self doesNotRecognizeSelector:_cmd];
+}
+
+- (void)finishWithError:(NSError *)errorOrNil {
+  NSAssert(NO, @"Missing base implementation for %@", NSStringFromSelector(_cmd));
+  [self doesNotRecognizeSelector:_cmd];
+}
+
 @end
diff --git a/src/php/ext/grpc/package.xml b/src/php/ext/grpc/package.xml
index f41902f041eb278b7e02d18a1b8ad1e895ab4387..921cfc6ae68d085120331c0de9db36ff4f2f7d69 100644
--- a/src/php/ext/grpc/package.xml
+++ b/src/php/ext/grpc/package.xml
@@ -10,10 +10,10 @@
   <email>grpc-packages@google.com</email>
   <active>yes</active>
  </lead>
- <date>2015-09-24</date>
- <time>09:51:04</time>
+ <date>2015-10-07</date>
+ <time>13:40:54</time>
  <version>
-  <release>0.6.0</release>
+  <release>0.6.1</release>
   <api>0.6.0</api>
  </version>
  <stability>
@@ -22,12 +22,7 @@
  </stability>
  <license>BSD</license>
  <notes>
-- support per message compression disable
-- expose per-call host override option
-- expose connectivity API
-- expose channel target and call peer
-- add user-agent
-- update to wrap gRPC C core library beta version 0.11.0
+- fixed undefined constant fatal error when run with apache/nginx #2275
  </notes>
  <contents>
   <dir baseinstalldir="/" name="/">
@@ -44,7 +39,7 @@
    <file baseinstalldir="/" md5sum="6988d6e97c19c8f8e3eb92371cf8246b" name="credentials.h" role="src" />
    <file baseinstalldir="/" md5sum="38a1bc979d810c36ebc2a52d4b7b5319" name="CREDITS" role="doc" />
    <file baseinstalldir="/" md5sum="3f35b472bbdef5a788cd90617d7d0847" name="LICENSE" role="doc" />
-   <file baseinstalldir="/" md5sum="6a550516a1423def0786851c76f87c85" name="php_grpc.c" role="src" />
+   <file baseinstalldir="/" md5sum="b77f1f3941aaf7a21090b493e9f26037" name="php_grpc.c" role="src" />
    <file baseinstalldir="/" md5sum="673b07859d9f69232f8a754c56780686" name="php_grpc.h" role="src" />
    <file baseinstalldir="/" md5sum="7533a6d3ea02c78cad23a9651de0825d" name="README.md" role="doc" />
    <file baseinstalldir="/" md5sum="3e4e960454ebb2fc7b78a840493f5315" name="server.c" role="src" />
@@ -118,5 +113,20 @@ Update to wrap gRPC C Core version 0.10.0
 - update to wrap gRPC C core library beta version 0.11.0
    </notes>
   </release>
+  <release>
+   <version>
+    <release>0.6.1</release>
+    <api>0.6.0</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2015-10-07</date>
+   <license>BSD</license>
+   <notes>
+- fixed undefined constant fatal error when run with apache/nginx #2275
+   </notes>
+  </release>
  </changelog>
 </package>
diff --git a/src/php/ext/grpc/php_grpc.c b/src/php/ext/grpc/php_grpc.c
index 4ad78ea0a31225264b1edcd122b0f684f850c8b9..fcd94a6306230db39d60f2ede6305c950978931c 100644
--- a/src/php/ext/grpc/php_grpc.c
+++ b/src/php/ext/grpc/php_grpc.c
@@ -150,7 +150,7 @@ PHP_MINIT_FUNCTION(grpc) {
                          CONST_CS | CONST_PERSISTENT);
   REGISTER_LONG_CONSTANT("Grpc\\STATUS_INVALID_ARGUMENT",
                          GRPC_STATUS_INVALID_ARGUMENT,
-                        CONST_CS | CONST_PERSISTENT);
+                         CONST_CS | CONST_PERSISTENT);
   REGISTER_LONG_CONSTANT("Grpc\\STATUS_DEADLINE_EXCEEDED",
                          GRPC_STATUS_DEADLINE_EXCEEDED,
                          CONST_CS | CONST_PERSISTENT);
@@ -173,7 +173,8 @@ PHP_MINIT_FUNCTION(grpc) {
                          CONST_CS | CONST_PERSISTENT);
   REGISTER_LONG_CONSTANT("Grpc\\STATUS_ABORTED", GRPC_STATUS_ABORTED,
                          CONST_CS | CONST_PERSISTENT);
-  REGISTER_LONG_CONSTANT("Grpc\\STATUS_OUT_OF_RANGE", GRPC_STATUS_OUT_OF_RANGE,
+  REGISTER_LONG_CONSTANT("Grpc\\STATUS_OUT_OF_RANGE",
+                         GRPC_STATUS_OUT_OF_RANGE,
                          CONST_CS | CONST_PERSISTENT);
   REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNIMPLEMENTED",
                          GRPC_STATUS_UNIMPLEMENTED,
@@ -202,7 +203,8 @@ PHP_MINIT_FUNCTION(grpc) {
                          GRPC_OP_RECV_INITIAL_METADATA,
                          CONST_CS | CONST_PERSISTENT);
   REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_MESSAGE",
-                         GRPC_OP_RECV_MESSAGE, CONST_CS | CONST_PERSISTENT);
+                         GRPC_OP_RECV_MESSAGE,
+                         CONST_CS | CONST_PERSISTENT);
   REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_STATUS_ON_CLIENT",
                          GRPC_OP_RECV_STATUS_ON_CLIENT,
                          CONST_CS | CONST_PERSISTENT);
@@ -212,11 +214,14 @@ PHP_MINIT_FUNCTION(grpc) {
 
   /* Register connectivity state constants */
   REGISTER_LONG_CONSTANT("Grpc\\CHANNEL_IDLE",
-                         GRPC_CHANNEL_IDLE, CONST_CS | CONST_PERSISTENT);
+                         GRPC_CHANNEL_IDLE,
+                         CONST_CS | CONST_PERSISTENT);
   REGISTER_LONG_CONSTANT("Grpc\\CHANNEL_CONNECTING",
-                         GRPC_CHANNEL_CONNECTING, CONST_CS | CONST_PERSISTENT);
+                         GRPC_CHANNEL_CONNECTING,
+                         CONST_CS | CONST_PERSISTENT);
   REGISTER_LONG_CONSTANT("Grpc\\CHANNEL_READY",
-                         GRPC_CHANNEL_READY, CONST_CS | CONST_PERSISTENT);
+                         GRPC_CHANNEL_READY,
+                         CONST_CS | CONST_PERSISTENT);
   REGISTER_LONG_CONSTANT("Grpc\\CHANNEL_TRANSIENT_FAILURE",
                          GRPC_CHANNEL_TRANSIENT_FAILURE,
                          CONST_CS | CONST_PERSISTENT);
diff --git a/src/php/lib/Grpc/BaseStub.php b/src/php/lib/Grpc/BaseStub.php
index 381b1143993301cf34dba9e1fb85f21c57d9ec49..0a3e1f78bf45ecf10c5e700da2b73a037e25ac2c 100755
--- a/src/php/lib/Grpc/BaseStub.php
+++ b/src/php/lib/Grpc/BaseStub.php
@@ -114,7 +114,7 @@ class BaseStub {
       return true;
     }
     if ($new_state == \Grpc\CHANNEL_FATAL_FAILURE) {
-      throw new Exception('Failed to connect to server');
+      throw new \Exception('Failed to connect to server');
     }
     return false;
   }
@@ -153,6 +153,25 @@ class BaseStub {
     return array($metadata_copy, $timeout);
   }
 
+  /**
+   * validate and normalize the metadata array
+   * @param $metadata The metadata map
+   * @return $metadata Validated and key-normalized metadata map
+   * @throw InvalidArgumentException if key contains invalid characters
+   */
+  private function _validate_and_normalize_metadata($metadata) {
+    $metadata_copy = array();
+    foreach ($metadata as $key => $value) {
+      if (!preg_match('/^[A-Za-z\d_-]+$/', $key)) {
+        throw new \InvalidArgumentException(
+            'Metadata keys must be nonempty strings containing only '.
+            'alphanumeric characters, hyphens and underscores');
+      }
+      $metadata_copy[strtolower($key)] = $value;
+    }
+    return $metadata_copy;
+  }
+
   /* This class is intended to be subclassed by generated code, so all functions
      begin with "_" to avoid name collisions. */
 
@@ -178,6 +197,7 @@ class BaseStub {
                                         $actual_metadata,
                                         $jwt_aud_uri);
     }
+    $actual_metadata = $this->_validate_and_normalize_metadata($actual_metadata);
     $call->start($argument, $actual_metadata, $options);
     return $call;
   }
@@ -204,6 +224,7 @@ class BaseStub {
                                         $actual_metadata,
                                         $jwt_aud_uri);
     }
+    $actual_metadata = $this->_validate_and_normalize_metadata($actual_metadata);
     $call->start($actual_metadata);
     return $call;
   }
@@ -231,6 +252,7 @@ class BaseStub {
                                         $actual_metadata,
                                         $jwt_aud_uri);
     }
+    $actual_metadata = $this->_validate_and_normalize_metadata($actual_metadata);
     $call->start($argument, $actual_metadata, $options);
     return $call;
   }
@@ -254,6 +276,7 @@ class BaseStub {
                                         $actual_metadata,
                                         $jwt_aud_uri);
     }
+    $actual_metadata = $this->_validate_and_normalize_metadata($actual_metadata);
     $call->start($actual_metadata);
     return $call;
   }
diff --git a/src/php/tests/generated_code/AbstractGeneratedCodeTest.php b/src/php/tests/generated_code/AbstractGeneratedCodeTest.php
index 9cee1886666c0011b03ef703c8de9615889eb92e..5cdba1e5a0fde706f4bb9501cab8d496f6895d55 100644
--- a/src/php/tests/generated_code/AbstractGeneratedCodeTest.php
+++ b/src/php/tests/generated_code/AbstractGeneratedCodeTest.php
@@ -51,6 +51,14 @@ abstract class AbstractGeneratedCodeTest extends PHPUnit_Framework_TestCase {
     $this->assertTrue(is_string(self::$client->getTarget()));
   }
 
+  /**
+   * @expectedException InvalidArgumentException
+   */
+  public function testInvalidMetadata() {
+    $div_arg = new math\DivArgs();
+    $call = self::$client->Div($div_arg, array(' ' => 'abc123'));
+  }
+
   public function testWriteFlags() {
     $div_arg = new math\DivArgs();
     $div_arg->setDividend(7);
diff --git a/src/php/tests/interop/interop_client.php b/src/php/tests/interop/interop_client.php
index d55d5629b74d15d231667ca965a1ac58ff978052..0590264ef80ba351f44a5535c46f7d798e80afda 100755
--- a/src/php/tests/interop/interop_client.php
+++ b/src/php/tests/interop/interop_client.php
@@ -250,6 +250,23 @@ function pingPong($stub) {
               'Call did not complete successfully');
 }
 
+/**
+ * Run the empty_stream test.
+ * Passes when run against the Node server as of 2015-10-09
+ * @param $stub Stub object that has service methods.
+ */
+function emptyStream($stub) {
+  // for the current PHP implementation, $call->read() will wait
+  // forever for a server response if the server is not sending any.
+  // so this test is imeplemented as a timeout to indicate the absence
+  // of receiving any response from the server
+  $call = $stub->FullDuplexCall(array('timeout' => 100000));
+  $call->writesDone();
+  hardAssert($call->read() === null, 'Server returned too many responses');
+  hardAssert($call->getStatus()->code === Grpc\STATUS_OK,
+              'Call did not complete successfully');
+}
+
 /**
  * Run the cancel_after_begin test.
  * Passes when run against the Node server as of 2015-08-28
@@ -370,6 +387,9 @@ switch ($args['test_case']) {
   case 'ping_pong':
     pingPong($stub);
     break;
+  case 'empty_stream':
+    emptyStream($stub);
+    break;
   case 'cancel_after_begin':
     cancelAfterBegin($stub);
     break;
diff --git a/src/ruby/lib/grpc/generic/client_stub.rb b/src/ruby/lib/grpc/generic/client_stub.rb
index 8c923845298e8ce65c5cd2605216d78cf8d88ade..b8e33ad295ab3c6e556446854346895f2df9189a 100644
--- a/src/ruby/lib/grpc/generic/client_stub.rb
+++ b/src/ruby/lib/grpc/generic/client_stub.rb
@@ -176,8 +176,7 @@ module GRPC
                           deadline: deadline,
                           timeout: timeout,
                           parent: parent)
-      kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
-      md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
+      md = update_metadata(kw, method)
       return c.request_response(req, **md) unless return_op
 
       # return the operation view of the active_call; define #execute as a
@@ -244,8 +243,7 @@ module GRPC
                           deadline: deadline,
                           timeout: timeout,
                           parent: parent)
-      kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
-      md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
+      md = update_metadata(kw, method)
       return c.client_streamer(requests, **md) unless return_op
 
       # return the operation view of the active_call; define #execute as a
@@ -322,8 +320,7 @@ module GRPC
                           deadline: deadline,
                           timeout: timeout,
                           parent: parent)
-      kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
-      md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
+      md = update_metadata(kw, method)
       return c.server_streamer(req, **md, &blk) unless return_op
 
       # return the operation view of the active_call; define #execute
@@ -439,8 +436,7 @@ module GRPC
                           deadline: deadline,
                           timeout: timeout,
                           parent: parent)
-      kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
-      md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
+      md = update_metadata(kw, method)
       return c.bidi_streamer(requests, **md, &blk) unless return_op
 
       # return the operation view of the active_call; define #execute
@@ -454,6 +450,16 @@ module GRPC
 
     private
 
+    def update_metadata(kw, method)
+      return kw if @update_metadata.nil?
+      just_jwt_uri = self.class.update_with_jwt_aud_uri({}, @host, method)
+      updated = @update_metadata.call(just_jwt_uri)
+
+      # keys should be lowercase
+      updated = Hash[updated.each_pair.map { |k, v|  [k.downcase, v] }]
+      kw.merge(updated)
+    end
+
     # Creates a new active stub
     #
     # @param method [string] the method being called.
diff --git a/src/ruby/spec/generic/client_stub_spec.rb b/src/ruby/spec/generic/client_stub_spec.rb
index a05433df75ea1893164952b4092f8e88236940b1..c5173aee1d59c62c2d2a462f3596abf4aedee0cf 100644
--- a/src/ruby/spec/generic/client_stub_spec.rb
+++ b/src/ruby/spec/generic/client_stub_spec.rb
@@ -159,6 +159,20 @@ describe 'ClientStub' do
         th.join
       end
 
+      it 'should downcase the keys provided by the metadata updater' do
+        server_port = create_test_server
+        host = "localhost:#{server_port}"
+        th = run_request_response(@sent_msg, @resp, @pass,
+                                  k1: 'downcased-key-v1', k2: 'v2')
+        update_md = proc do |md|
+          md[:K1] = 'downcased-key-v1'
+          md
+        end
+        stub = GRPC::ClientStub.new(host, @cq, update_metadata: update_md)
+        expect(get_response(stub)).to eq(@resp)
+        th.join
+      end
+
       it 'should send a request when configured using an override channel' do
         server_port = create_test_server
         alt_host = "localhost:#{server_port}"
diff --git a/templates/grpc.gyp.template b/templates/binding.gyp.template
similarity index 53%
rename from templates/grpc.gyp.template
rename to templates/binding.gyp.template
index 9a7637d8bf48a0e3aab979abaafcdb911043d57d..50d0823d1d4dfb9ae2954484891f5aa2b49b1b6d 100644
--- a/templates/grpc.gyp.template
+++ b/templates/binding.gyp.template
@@ -1,7 +1,7 @@
 %YAML 1.2
 --- |
-  # GRPC gyp file
-  # This currently builds C code.
+  # GRPC Node gyp file
+  # This currently builds the Node extension and dependencies
   # This file has been automatically generated from a template file.
   # Please look at the templates directory instead.
   # This file can be regenerated from the template by running
@@ -39,54 +39,24 @@
   # Some of this file is built with the help of
   # https://n8.io/converting-a-c-library-to-gyp/
   {
+    'variables': {
+      'config': '<!(echo $CONFIG)'
+    },
     # TODO: Finish windows support
     'target_defaults': {
-      'default_configuration': 'Debug',
-      'configurations': {
-        'Debug': {
-          'defines': [ 'DEBUG', '_DEBUG' ],
-          'msvs_settings': {
-            'VCCLCompilerTool': {
-              'RuntimeLibrary': 1, # static debug
-            },
-          },
-        },
-        'Release': {
-          'defines': [ 'NDEBUG' ],
-          'msvs_settings': {
-            'VCCLCompilerTool': {
-              'RuntimeLibrary': 0, # static release
-            },
-          },
-        }
-      },
-      'msvs_settings': {
-        'VCLinkerTool': {
-          'GenerateDebugInformation': 'true',
-        },
-      },
-      # TODO: Add fallback for Windows, and if pkg-config is not available
+        # Empirically, Node only exports ALPN symbols if its major version is >0.
+        # io.js always reports versions >0 and always exports ALPN symbols.
+        # Therefore, Node's major version will be truthy if and only if it
+        # supports ALPN. The output of "node -v" is v[major].[minor].[patch],
+        # like "v4.1.1" in a recent version. We use grep to extract just the
+        # major version. "4", would be the output for the example.
       'defines': [
-        'TSI_OPENSSL_ALPN_SUPPORT=<!(pkg-config --atleast-version=1.0.2 openssl >/dev/null 2>&1 && echo 1 || echo 0)'
+        'TSI_OPENSSL_ALPN_SUPPORT=<!(node -v | grep -oP "(?<=v)(\d+)(?=\.\d+\.\d+)")'
       ],
       'include_dirs': [
         '.',
         'include'
-      ],
-      # TODO: Check for libraries with pkg-config
-      'libraries': [
-        '-lcrypto',
-        '-lssl',
-        '-ldl',
-        '-lpthread',
-        '-lz'
-      ],
-      'direct_dependent_settings': {
-        'include_dirs': [
-          '.',
-          'include'
-        ],
-      }
+      ]
     },
     'targets': [
       % for lib in libs:
@@ -108,5 +78,65 @@
       },
       % endif
       % endfor
+      {
+        'include_dirs': [
+          "<!(node -e \"require('nan')\")"
+        ],
+        'cflags': [
+          '-std=c++0x',
+          '-Wall',
+          '-pthread',
+          '-g',
+          '-zdefs',
+          '-Werror',
+          '-Wno-error=deprecated-declarations'
+        ],
+        'ldflags': [
+          '-g'
+        ],
+        "conditions": [
+          ['OS != "win"', {
+            'conditions': [
+              ['config=="gcov"', {
+                'cflags': [
+                  '-ftest-coverage',
+                  '-fprofile-arcs',
+                  '-O0'
+                ],
+                'ldflags': [
+                  '-ftest-coverage',
+                  '-fprofile-arcs'
+                ]
+              }
+             ]
+            ]
+          }],
+          ['OS == "mac"', {
+            'xcode_settings': {
+              'MACOSX_DEPLOYMENT_TARGET': '10.9',
+              'OTHER_CFLAGS': [
+                '-std=c++11',
+                '-stdlib=libc++'
+              ]
+            }
+          }]
+        ],
+        "target_name": "grpc_node",
+        "sources": [
+          "src/node/ext/byte_buffer.cc",
+          "src/node/ext/call.cc",
+          "src/node/ext/call_credentials.cc",
+          "src/node/ext/channel.cc",
+          "src/node/ext/channel_credentials.cc",
+          "src/node/ext/completion_queue_async_worker.cc",
+          "src/node/ext/node_grpc.cc",
+          "src/node/ext/server.cc",
+          "src/node/ext/server_credentials.cc",
+          "src/node/ext/timeval.cc"
+        ],
+        "dependencies": [
+          "grpc"
+        ]
+      }
     ]
   }
diff --git a/test/core/end2end/tests/compressed_payload.c b/test/core/end2end/tests/compressed_payload.c
index a4614a2aba4b15d7cbfe52fc79b6b2bcfde52d4e..f321fe1e7c7f8f09769e64dab29394912f336172 100644
--- a/test/core/end2end/tests/compressed_payload.c
+++ b/test/core/end2end/tests/compressed_payload.c
@@ -46,7 +46,7 @@
 #include "test/core/end2end/cq_verifier.h"
 #include "src/core/channel/channel_args.h"
 #include "src/core/channel/compress_filter.h"
-#include "src/core/surface/call.h"
+#include "src/core/surface/call_test_only.h"
 
 enum { TIMEOUT = 200000 };
 
@@ -196,12 +196,13 @@ static void request_with_payload_template(
   cq_expect_completion(cqv, tag(101), 1);
   cq_verify(cqv);
 
-  GPR_ASSERT(GPR_BITCOUNT(grpc_call_get_encodings_accepted_by_peer(s)) == 3);
-  GPR_ASSERT(GPR_BITGET(grpc_call_get_encodings_accepted_by_peer(s),
+  GPR_ASSERT(
+      GPR_BITCOUNT(grpc_call_test_only_get_encodings_accepted_by_peer(s)) == 3);
+  GPR_ASSERT(GPR_BITGET(grpc_call_test_only_get_encodings_accepted_by_peer(s),
                         GRPC_COMPRESS_NONE) != 0);
-  GPR_ASSERT(GPR_BITGET(grpc_call_get_encodings_accepted_by_peer(s),
+  GPR_ASSERT(GPR_BITGET(grpc_call_test_only_get_encodings_accepted_by_peer(s),
                         GRPC_COMPRESS_DEFLATE) != 0);
-  GPR_ASSERT(GPR_BITGET(grpc_call_get_encodings_accepted_by_peer(s),
+  GPR_ASSERT(GPR_BITGET(grpc_call_test_only_get_encodings_accepted_by_peer(s),
                         GRPC_COMPRESS_GZIP) != 0);
 
   op = ops;
diff --git a/test/cpp/interop/client.cc b/test/cpp/interop/client.cc
index dbe29395b4f0efc58fdef53626a21d1421eecea0..cb9b396beb55cfeb17bb21e53365033ac1c14e7b 100644
--- a/test/cpp/interop/client.cc
+++ b/test/cpp/interop/client.cc
@@ -46,7 +46,7 @@
 #include "test/cpp/util/test_config.h"
 
 DEFINE_bool(use_tls, false, "Whether to use tls.");
-DEFINE_bool(use_prod_roots, false, "True to use SSL roots for google");
+DEFINE_bool(use_test_ca, false, "False to use SSL roots for google");
 DEFINE_int32(server_port, 0, "Server port.");
 DEFINE_string(server_host, "127.0.0.1", "Server host to connect to");
 DEFINE_string(server_host_override, "foo.test.google.fr",
diff --git a/test/cpp/interop/client_helper.cc b/test/cpp/interop/client_helper.cc
index cbad21e318e6d5cddb4a79a628ddb8546bab9873..61b46d25aa041a08f32dd901ec8b82b1ee02f786 100644
--- a/test/cpp/interop/client_helper.cc
+++ b/test/cpp/interop/client_helper.cc
@@ -52,7 +52,7 @@
 #include "test/cpp/util/create_test_channel.h"
 
 DECLARE_bool(use_tls);
-DECLARE_bool(use_prod_roots);
+DECLARE_bool(use_test_ca);
 DECLARE_int32(server_port);
 DECLARE_string(server_host);
 DECLARE_string(server_host_override);
@@ -102,7 +102,7 @@ std::shared_ptr<Channel> CreateChannelForTestCase(
     GPR_ASSERT(FLAGS_use_tls);
     creds = GoogleComputeEngineCredentials();
     return CreateTestChannel(host_port, FLAGS_server_host_override,
-                             FLAGS_use_tls, FLAGS_use_prod_roots, creds);
+                             FLAGS_use_tls, !FLAGS_use_test_ca, creds);
   } else if (test_case == "jwt_token_creds") {
     std::shared_ptr<Credentials> creds;
     GPR_ASSERT(FLAGS_use_tls);
@@ -111,15 +111,15 @@ std::shared_ptr<Channel> CreateChannelForTestCase(
     creds =
         ServiceAccountJWTAccessCredentials(json_key, token_lifetime.count());
     return CreateTestChannel(host_port, FLAGS_server_host_override,
-                             FLAGS_use_tls, FLAGS_use_prod_roots, creds);
+                             FLAGS_use_tls, !FLAGS_use_test_ca, creds);
   } else if (test_case == "oauth2_auth_token") {
     grpc::string raw_token = GetOauth2AccessToken();
     std::shared_ptr<Credentials> creds = AccessTokenCredentials(raw_token);
     return CreateTestChannel(host_port, FLAGS_server_host_override,
-                             FLAGS_use_tls, FLAGS_use_prod_roots, creds);
+                             FLAGS_use_tls, !FLAGS_use_test_ca, creds);
   } else {
     return CreateTestChannel(host_port, FLAGS_server_host_override,
-                             FLAGS_use_tls, FLAGS_use_prod_roots);
+                             FLAGS_use_tls, !FLAGS_use_test_ca);
   }
 }
 
diff --git a/test/cpp/interop/client_helper.h b/test/cpp/interop/client_helper.h
index 0221df93db5a715bd93781b2077297d1a64014b1..ace193042ee39b1ed88a4226773e4257a678e13d 100644
--- a/test/cpp/interop/client_helper.h
+++ b/test/cpp/interop/client_helper.h
@@ -38,7 +38,7 @@
 
 #include <grpc++/channel.h>
 
-#include "src/core/surface/call.h"
+#include "src/core/surface/call_test_only.h"
 
 namespace grpc {
 namespace testing {
@@ -57,11 +57,11 @@ class InteropClientContextInspector {
 
   // Inspector methods, able to peek inside ClientContext, follow.
   grpc_compression_algorithm GetCallCompressionAlgorithm() const {
-    return grpc_call_get_compression_algorithm(context_.call_);
+    return grpc_call_test_only_get_compression_algorithm(context_.call_);
   }
 
   gpr_uint32 GetMessageFlags() const {
-    return grpc_call_get_message_flags(context_.call_);
+    return grpc_call_test_only_get_message_flags(context_.call_);
   }
 
  private:
diff --git a/test/cpp/interop/server_helper.cc b/test/cpp/interop/server_helper.cc
index 4570750846674f276d0444f8ccbe782f0c8648f2..5138a38170a0797fcd2b77360a66afc45b4a8ca9 100644
--- a/test/cpp/interop/server_helper.cc
+++ b/test/cpp/interop/server_helper.cc
@@ -38,7 +38,7 @@
 #include <gflags/gflags.h>
 #include <grpc++/security/server_credentials.h>
 
-#include "src/core/surface/call.h"
+#include "src/core/surface/call_test_only.h"
 #include "test/core/end2end/data/ssl_test_data.h"
 
 DECLARE_bool(use_tls);
@@ -65,11 +65,11 @@ InteropServerContextInspector::InteropServerContextInspector(
 
 grpc_compression_algorithm
 InteropServerContextInspector::GetCallCompressionAlgorithm() const {
-  return grpc_call_get_compression_algorithm(context_.call_);
+  return grpc_call_test_only_get_compression_algorithm(context_.call_);
 }
 
 gpr_uint32 InteropServerContextInspector::GetEncodingsAcceptedByClient() const {
-  return grpc_call_get_encodings_accepted_by_peer(context_.call_);
+  return grpc_call_test_only_get_encodings_accepted_by_peer(context_.call_);
 }
 
 std::shared_ptr<const AuthContext>
diff --git a/test/cpp/qps/driver.cc b/test/cpp/qps/driver.cc
index ac763e4b3c617755ea5ef2a250c2d0ee19e98b9f..dd5c4f4f73fe5083d0bc0c549213107b5930c1aa 100644
--- a/test/cpp/qps/driver.cc
+++ b/test/cpp/qps/driver.cc
@@ -82,9 +82,12 @@ static deque<string> get_hosts(const string& name) {
 namespace runsc {
 
 // ClientContext allocator
-static ClientContext* AllocContext(list<ClientContext>* contexts) {
+template <class T>
+static ClientContext* AllocContext(list<ClientContext>* contexts, T deadline) {
   contexts->emplace_back();
-  return &contexts->back();
+  auto context = &contexts->back();
+  context->set_deadline(deadline);
+  return context;
 }
 
 struct ServerData {
@@ -147,6 +150,11 @@ std::unique_ptr<ScenarioResult> RunScenario(
   // Trim to just what we need
   workers.resize(num_clients + num_servers);
 
+  gpr_timespec deadline =
+      gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),
+                   gpr_time_from_seconds(
+                       warmup_seconds + benchmark_seconds + 20, GPR_TIMESPAN));
+
   // Start servers
   using runsc::ServerData;
   // servers is array rather than std::vector to avoid gcc-4.4 issues
@@ -160,7 +168,7 @@ std::unique_ptr<ScenarioResult> RunScenario(
     result_server_config.set_host(workers[i]);
     *args.mutable_setup() = server_config;
     servers[i].stream =
-        servers[i].stub->RunServer(runsc::AllocContext(&contexts));
+        servers[i].stub->RunServer(runsc::AllocContext(&contexts, deadline));
     GPR_ASSERT(servers[i].stream->Write(args));
     ServerStatus init_status;
     GPR_ASSERT(servers[i].stream->Read(&init_status));
@@ -188,7 +196,7 @@ std::unique_ptr<ScenarioResult> RunScenario(
     result_client_config.set_host(workers[i + num_servers]);
     *args.mutable_setup() = client_config;
     clients[i].stream =
-        clients[i].stub->RunTest(runsc::AllocContext(&contexts));
+        clients[i].stub->RunTest(runsc::AllocContext(&contexts, deadline));
     GPR_ASSERT(clients[i].stream->Write(args));
     ClientStatus init_status;
     GPR_ASSERT(clients[i].stream->Read(&init_status));
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index b782978a2a6aaef5005b5e4b0de5c68b8d49ce61..438097aa536c63cc2f3c24373d27ab8b1729bf2b 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -859,6 +859,7 @@ src/core/statistics/census_rpc_stats.h \
 src/core/surface/api_trace.h \
 src/core/surface/byte_buffer_queue.h \
 src/core/surface/call.h \
+src/core/surface/call_test_only.h \
 src/core/surface/channel.h \
 src/core/surface/completion_queue.h \
 src/core/surface/event_string.h \
diff --git a/tools/gce_setup/grpc_docker.sh b/tools/gce_setup/grpc_docker.sh
index fdb7736327306ab50840399d0d2db56bff79c510..e50706ec74b4c613306995730572aaca9fb63db7 100755
--- a/tools/gce_setup/grpc_docker.sh
+++ b/tools/gce_setup/grpc_docker.sh
@@ -1477,7 +1477,7 @@ grpc_cloud_prod_auth_compute_engine_creds_gen_node_cmd() {
 #   cmd=$($grpc_gen_test_cmd $flags)
 grpc_interop_gen_cxx_cmd() {
     local cmd_prefix="sudo docker run grpc/cxx";
-    local test_script="/var/local/git/grpc/bins/opt/interop_client --use_tls";
+    local test_script="/var/local/git/grpc/bins/opt/interop_client --use_tls --use_test_ca";
     local the_cmd="$cmd_prefix $test_script $@";
     echo $the_cmd
 }
@@ -1489,7 +1489,7 @@ grpc_interop_gen_cxx_cmd() {
 #   cmd=$($grpc_gen_test_cmd $flags)
 grpc_cloud_prod_gen_cxx_cmd() {
     local cmd_prefix="sudo docker run grpc/cxx";
-    local test_script="/var/local/git/grpc/bins/opt/interop_client --use_tls --use_prod_roots";
+    local test_script="/var/local/git/grpc/bins/opt/interop_client --use_tls";
     local gfe_flags=$(_grpc_prod_gfe_flags)
     local the_cmd="$cmd_prefix $test_script $gfe_flags $@";
     echo $the_cmd
@@ -1502,7 +1502,7 @@ grpc_cloud_prod_gen_cxx_cmd() {
 #   cmd=$($grpc_gen_test_cmd $flags)
 grpc_cloud_prod_auth_service_account_creds_gen_cxx_cmd() {
     local cmd_prefix="sudo docker run grpc/cxx";
-    local test_script="/var/local/git/grpc/bins/opt/interop_client --use_tls --use_prod_roots";
+    local test_script="/var/local/git/grpc/bins/opt/interop_client --use_tls";
     local gfe_flags=$(_grpc_prod_gfe_flags)
     local added_gfe_flags=$(_grpc_svc_acc_test_flags)
     local the_cmd="$cmd_prefix $test_script $gfe_flags $added_gfe_flags $@";
@@ -1516,7 +1516,7 @@ grpc_cloud_prod_auth_service_account_creds_gen_cxx_cmd() {
 #   cmd=$($grpc_gen_test_cmd $flags)
 grpc_cloud_prod_auth_compute_engine_creds_gen_cxx_cmd() {
     local cmd_prefix="sudo docker run grpc/cxx";
-    local test_script="/var/local/git/grpc/bins/opt/interop_client --use_tls --use_prod_roots";
+    local test_script="/var/local/git/grpc/bins/opt/interop_client --use_tls";
     local gfe_flags=$(_grpc_prod_gfe_flags)
     local added_gfe_flags=$(_grpc_gce_test_flags)
     local the_cmd="$cmd_prefix $test_script $gfe_flags $added_gfe_flags $@";
@@ -1530,7 +1530,7 @@ grpc_cloud_prod_auth_compute_engine_creds_gen_cxx_cmd() {
 #   cmd=$($grpc_gen_test_cmd $flags)
 grpc_cloud_prod_auth_jwt_token_creds_gen_cxx_cmd() {
     local cmd_prefix="sudo docker run grpc/cxx";
-    local test_script="/var/local/git/grpc/bins/opt/interop_client --use_tls --use_prod_roots";
+    local test_script="/var/local/git/grpc/bins/opt/interop_client --use_tls";
     local gfe_flags=$(_grpc_prod_gfe_flags)
     local added_gfe_flags=$(_grpc_jwt_token_test_flags)
     local the_cmd="$cmd_prefix $test_script $gfe_flags $added_gfe_flags $@";
diff --git a/tools/http2_interop/README.md b/tools/http2_interop/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..21688f09804c0e47d9c2db6df2ae65039c866064
--- /dev/null
+++ b/tools/http2_interop/README.md
@@ -0,0 +1,9 @@
+HTTP/2 Interop Tests
+====
+
+This is a suite of tests that check a server to see if it plays nicely with other HTTP/2 clients.  To run, just type:
+
+`go test -spec :1234`
+
+Where ":1234" is the ip:port of a running server.
+ 
diff --git a/tools/http2_interop/doc.go b/tools/http2_interop/doc.go
new file mode 100644
index 0000000000000000000000000000000000000000..6c6b5cb1938e0af0f52319b4d9e70657de460b0d
--- /dev/null
+++ b/tools/http2_interop/doc.go
@@ -0,0 +1,6 @@
+// http2interop project doc.go
+
+/*
+http2interop document
+*/
+package http2interop
diff --git a/tools/http2_interop/frame.go b/tools/http2_interop/frame.go
new file mode 100644
index 0000000000000000000000000000000000000000..12689e9b33d6967bed2e4569e467da47b31b0c48
--- /dev/null
+++ b/tools/http2_interop/frame.go
@@ -0,0 +1,11 @@
+package http2interop
+
+import (
+	"io"
+)
+
+type Frame interface {
+	GetHeader() *FrameHeader
+	ParsePayload(io.Reader) error
+	MarshalBinary() ([]byte, error)
+}
diff --git a/tools/http2_interop/frameheader.go b/tools/http2_interop/frameheader.go
new file mode 100644
index 0000000000000000000000000000000000000000..78fe4201f627c75fa985c434302d087ae28cf3db
--- /dev/null
+++ b/tools/http2_interop/frameheader.go
@@ -0,0 +1,109 @@
+package http2interop
+
+import (
+	"encoding/binary"
+	"fmt"
+	"io"
+)
+
+type FrameHeader struct {
+	Length   int
+	Type     FrameType
+	Flags    byte
+	Reserved Reserved
+	StreamID
+}
+
+type Reserved bool
+
+func (r Reserved) String() string {
+	if r {
+		return "R"
+	}
+	return ""
+}
+
+func (fh *FrameHeader) Parse(r io.Reader) error {
+	buf := make([]byte, 9)
+	if _, err := io.ReadFull(r, buf); err != nil {
+		return err
+	}
+	return fh.UnmarshalBinary(buf)
+}
+
+func (fh *FrameHeader) UnmarshalBinary(b []byte) error {
+	if len(b) != 9 {
+		return fmt.Errorf("Invalid frame header length %d", len(b))
+	}
+	*fh = FrameHeader{
+		Length:   int(b[0])<<16 | int(b[1])<<8 | int(b[2]),
+		Type:     FrameType(b[3]),
+		Flags:    b[4],
+		Reserved: Reserved(b[5]>>7 == 1),
+		StreamID: StreamID(binary.BigEndian.Uint32(b[5:9]) & 0x7fffffff),
+	}
+	return nil
+}
+
+func (fh *FrameHeader) MarshalBinary() ([]byte, error) {
+	buf := make([]byte, 9, 9+fh.Length)
+
+	if fh.Length > 0xFFFFFF || fh.Length < 0 {
+		return nil, fmt.Errorf("Invalid frame header length: %d", fh.Length)
+	}
+	if fh.StreamID < 0 {
+		return nil, fmt.Errorf("Invalid Stream ID: %v", fh.StreamID)
+	}
+
+	buf[0], buf[1], buf[2] = byte(fh.Length>>16), byte(fh.Length>>8), byte(fh.Length)
+	buf[3] = byte(fh.Type)
+	buf[4] = fh.Flags
+	binary.BigEndian.PutUint32(buf[5:], uint32(fh.StreamID))
+
+	return buf, nil
+}
+
+type StreamID int32
+
+type FrameType byte
+
+func (ft FrameType) String() string {
+	switch ft {
+	case DataFrameType:
+		return "DATA"
+	case HeadersFrameType:
+		return "HEADERS"
+	case PriorityFrameType:
+		return "PRIORITY"
+	case ResetStreamFrameType:
+		return "RST_STREAM"
+	case SettingsFrameType:
+		return "SETTINGS"
+	case PushPromiseFrameType:
+		return "PUSH_PROMISE"
+	case PingFrameType:
+		return "PING"
+	case GoAwayFrameType:
+		return "GOAWAY"
+	case WindowUpdateFrameType:
+		return "WINDOW_UPDATE"
+	case ContinuationFrameType:
+		return "CONTINUATION"
+	default:
+		return fmt.Sprintf("UNKNOWN(%d)", byte(ft))
+	}
+}
+
+// Types
+const (
+	DataFrameType         FrameType = 0
+	HeadersFrameType      FrameType = 1
+	PriorityFrameType     FrameType = 2
+	ResetStreamFrameType  FrameType = 3
+	SettingsFrameType     FrameType = 4
+	PushPromiseFrameType  FrameType = 5
+	PingFrameType         FrameType = 6
+	GoAwayFrameType       FrameType = 7
+	WindowUpdateFrameType FrameType = 8
+	ContinuationFrameType FrameType = 9
+)
diff --git a/tools/http2_interop/http2interop.go b/tools/http2_interop/http2interop.go
new file mode 100644
index 0000000000000000000000000000000000000000..f1bca7fe13dfdf52879a6c685be8ac03bac7bbc6
--- /dev/null
+++ b/tools/http2_interop/http2interop.go
@@ -0,0 +1,245 @@
+package http2interop
+
+import (
+	"crypto/tls"
+	"fmt"
+	"io"
+	"log"
+)
+
+const (
+	Preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
+)
+
+func parseFrame(r io.Reader) (Frame, error) {
+	fh := FrameHeader{}
+	if err := fh.Parse(r); err != nil {
+		return nil, err
+	}
+	var f Frame
+	switch fh.Type {
+	case PingFrameType:
+		f = &PingFrame{
+			Header: fh,
+		}
+	case SettingsFrameType:
+		f = &SettingsFrame{
+			Header: fh,
+		}
+	default:
+		f = &UnknownFrame{
+			Header: fh,
+		}
+	}
+	if err := f.ParsePayload(r); err != nil {
+		return nil, err
+	}
+
+	return f, nil
+}
+
+func streamFrame(w io.Writer, f Frame) error {
+	raw, err := f.MarshalBinary()
+	if err != nil {
+		return err
+	}
+	if _, err := w.Write(raw); err != nil {
+		return err
+	}
+	return nil
+}
+
+func getHttp2Conn(addr string) (*tls.Conn, error) {
+	config := &tls.Config{
+		InsecureSkipVerify: true,
+		NextProtos:         []string{"h2"},
+	}
+
+	conn, err := tls.Dial("tcp", addr, config)
+	if err != nil {
+		return nil, err
+	}
+
+	return conn, nil
+}
+
+func testClientShortSettings(addr string, length int) error {
+	c, err := getHttp2Conn(addr)
+	if err != nil {
+		return err
+	}
+	defer c.Close()
+
+	if _, err := c.Write([]byte(Preface)); err != nil {
+		return err
+	}
+
+	// Bad, settings, non multiple of 6
+	sf := &UnknownFrame{
+		Header: FrameHeader{
+			Type: SettingsFrameType,
+		},
+		Data: make([]byte, length),
+	}
+	if err := streamFrame(c, sf); err != nil {
+		return err
+	}
+
+	for {
+		frame, err := parseFrame(c)
+		if err != nil {
+			return err
+		}
+		log.Println(frame)
+	}
+
+	return nil
+}
+
+func testClientPrefaceWithStreamId(addr string) error {
+	c, err := getHttp2Conn(addr)
+	if err != nil {
+		return err
+	}
+	defer c.Close()
+
+	// Good so far
+	if _, err := c.Write([]byte(Preface)); err != nil {
+		return err
+	}
+
+	// Bad, settings do not have ids
+	sf := &SettingsFrame{
+		Header: FrameHeader{
+			StreamID: 1,
+		},
+	}
+	if err := streamFrame(c, sf); err != nil {
+		return err
+	}
+
+	for {
+		frame, err := parseFrame(c)
+		if err != nil {
+			return err
+		}
+		log.Println(frame)
+	}
+
+	return nil
+}
+
+func testUnknownFrameType(addr string) error {
+	c, err := getHttp2Conn(addr)
+	if err != nil {
+		return err
+	}
+	defer c.Close()
+
+	if _, err := c.Write([]byte(Preface)); err != nil {
+		return err
+	}
+
+	// Send some settings, which are part of the client preface
+	sf := &SettingsFrame{}
+	if err := streamFrame(c, sf); err != nil {
+		return err
+	}
+
+	// Write a bunch of invalid frame types.
+	for ft := ContinuationFrameType + 1; ft != 0; ft++ {
+		fh := &UnknownFrame{
+			Header: FrameHeader{
+				Type: ft,
+			},
+		}
+		if err := streamFrame(c, fh); err != nil {
+			return err
+		}
+	}
+
+	pf := &PingFrame{
+		Data: []byte("01234567"),
+	}
+	if err := streamFrame(c, pf); err != nil {
+		return err
+	}
+
+	for {
+		frame, err := parseFrame(c)
+		if err != nil {
+			return err
+		}
+		if npf, ok := frame.(*PingFrame); !ok {
+			continue
+		} else {
+			if string(npf.Data) != string(pf.Data) || npf.Header.Flags&PING_ACK == 0 {
+				return fmt.Errorf("Bad ping %+v", *npf)
+			}
+			return nil
+		}
+	}
+
+	return nil
+}
+
+func testShortPreface(addr string, prefacePrefix string) error {
+	c, err := getHttp2Conn(addr)
+	if err != nil {
+		return err
+	}
+	defer c.Close()
+
+	if _, err := c.Write([]byte(prefacePrefix)); err != nil {
+		return err
+	}
+
+	buf := make([]byte, 256)
+	for ; err == nil; _, err = c.Read(buf) {
+	}
+	// TODO: maybe check for a GOAWAY?
+	return err
+}
+
+func testTLSMaxVersion(addr string, version uint16) error {
+	config := &tls.Config{
+		InsecureSkipVerify: true,
+		NextProtos:         []string{"h2"},
+		MaxVersion:         version,
+	}
+	conn, err := tls.Dial("tcp", addr, config)
+	if err != nil {
+		return err
+	}
+	defer conn.Close()
+
+	buf := make([]byte, 256)
+	if n, err := conn.Read(buf); err != nil {
+		if n != 0 {
+			return fmt.Errorf("Expected no bytes to be read, but was %d", n)
+		}
+		return err
+	}
+	return nil
+}
+
+func testTLSApplicationProtocol(addr string) error {
+	config := &tls.Config{
+		InsecureSkipVerify: true,
+		NextProtos:         []string{"h2c"},
+	}
+	conn, err := tls.Dial("tcp", addr, config)
+	if err != nil {
+		return err
+	}
+	defer conn.Close()
+
+	buf := make([]byte, 256)
+	if n, err := conn.Read(buf); err != nil {
+		if n != 0 {
+			return fmt.Errorf("Expected no bytes to be read, but was %d", n)
+		}
+		return err
+	}
+	return nil
+}
diff --git a/tools/http2_interop/http2interop_test.go b/tools/http2_interop/http2interop_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3b687c035e8748bcba20952138d3b02f0cd2b2d2
--- /dev/null
+++ b/tools/http2_interop/http2interop_test.go
@@ -0,0 +1,50 @@
+package http2interop
+
+import (
+	"crypto/tls"
+	"flag"
+	"io"
+	"os"
+	"testing"
+)
+
+var (
+	serverSpec = flag.String("spec", ":50051", "The server spec to test")
+)
+
+func TestShortPreface(t *testing.T) {
+	for i := 0; i < len(Preface)-1; i++ {
+		if err := testShortPreface(*serverSpec, Preface[:i]+"X"); err != io.EOF {
+			t.Error("Expected an EOF but was", err)
+		}
+	}
+}
+
+func TestUnknownFrameType(t *testing.T) {
+	if err := testUnknownFrameType(*serverSpec); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestTLSApplicationProtocol(t *testing.T) {
+	if err := testTLSApplicationProtocol(*serverSpec); err != io.EOF {
+		t.Fatal("Expected an EOF but was", err)
+	}
+}
+
+func TestTLSMaxVersion(t *testing.T) {
+	if err := testTLSMaxVersion(*serverSpec, tls.VersionTLS11); err != io.EOF {
+		t.Fatal("Expected an EOF but was", err)
+	}
+}
+
+func TestClientPrefaceWithStreamId(t *testing.T) {
+	if err := testClientPrefaceWithStreamId(*serverSpec); err != io.EOF {
+		t.Fatal("Expected an EOF but was", err)
+	}
+}
+
+func TestMain(m *testing.M) {
+	flag.Parse()
+	os.Exit(m.Run())
+}
diff --git a/tools/http2_interop/ping.go b/tools/http2_interop/ping.go
new file mode 100644
index 0000000000000000000000000000000000000000..6011eed4511c4a9f46ed77c8d55b3aa8be935b99
--- /dev/null
+++ b/tools/http2_interop/ping.go
@@ -0,0 +1,65 @@
+package http2interop
+
+import (
+	"fmt"
+	"io"
+)
+
+type PingFrame struct {
+	Header FrameHeader
+	Data   []byte
+}
+
+const (
+	PING_ACK = 0x01
+)
+
+func (f *PingFrame) GetHeader() *FrameHeader {
+	return &f.Header
+}
+
+func (f *PingFrame) ParsePayload(r io.Reader) error {
+	raw := make([]byte, f.Header.Length)
+	if _, err := io.ReadFull(r, raw); err != nil {
+		return err
+	}
+	return f.UnmarshalPayload(raw)
+}
+
+func (f *PingFrame) UnmarshalPayload(raw []byte) error {
+	if f.Header.Length != len(raw) {
+		return fmt.Errorf("Invalid Payload length %d != %d", f.Header.Length, len(raw))
+	}
+	if f.Header.Length != 8 {
+		return fmt.Errorf("Invalid Payload length %d", f.Header.Length)
+	}
+
+	f.Data = []byte(string(raw))
+
+	return nil
+}
+
+func (f *PingFrame) MarshalPayload() ([]byte, error) {
+	if len(f.Data) != 8 {
+		return nil, fmt.Errorf("Invalid Payload length %d", len(f.Data))
+	}
+	return []byte(string(f.Data)), nil
+}
+
+func (f *PingFrame) MarshalBinary() ([]byte, error) {
+	payload, err := f.MarshalPayload()
+	if err != nil {
+		return nil, err
+	}
+
+	f.Header.Length = len(payload)
+	f.Header.Type = PingFrameType
+	header, err := f.Header.MarshalBinary()
+	if err != nil {
+		return nil, err
+	}
+
+	header = append(header, payload...)
+
+	return header, nil
+}
diff --git a/tools/http2_interop/settings.go b/tools/http2_interop/settings.go
new file mode 100644
index 0000000000000000000000000000000000000000..5a2b1ada6513c84df5134b7e87fc4122695f775b
--- /dev/null
+++ b/tools/http2_interop/settings.go
@@ -0,0 +1,109 @@
+package http2interop
+
+import (
+	"encoding/binary"
+	"fmt"
+	"io"
+)
+
+const (
+	SETTINGS_ACK = 1
+)
+
+type SettingsFrame struct {
+	Header FrameHeader
+	Params []SettingsParameter
+}
+
+type SettingsIdentifier uint16
+
+const (
+	SettingsHeaderTableSize      SettingsIdentifier = 1
+	SettingsEnablePush           SettingsIdentifier = 2
+	SettingsMaxConcurrentStreams SettingsIdentifier = 3
+	SettingsInitialWindowSize    SettingsIdentifier = 4
+	SettingsMaxFrameSize         SettingsIdentifier = 5
+	SettingsMaxHeaderListSize    SettingsIdentifier = 6
+)
+
+func (si SettingsIdentifier) String() string {
+	switch si {
+	case SettingsHeaderTableSize:
+		return "HEADER_TABLE_SIZE"
+	case SettingsEnablePush:
+		return "ENABLE_PUSH"
+	case SettingsMaxConcurrentStreams:
+		return "MAX_CONCURRENT_STREAMS"
+	case SettingsInitialWindowSize:
+		return "INITIAL_WINDOW_SIZE"
+	case SettingsMaxFrameSize:
+		return "MAX_FRAME_SIZE"
+	case SettingsMaxHeaderListSize:
+		return "MAX_HEADER_LIST_SIZE"
+	default:
+		return fmt.Sprintf("UNKNOWN(%d)", uint16(si))
+	}
+}
+
+type SettingsParameter struct {
+	Identifier SettingsIdentifier
+	Value      uint32
+}
+
+func (f *SettingsFrame) GetHeader() *FrameHeader {
+	return &f.Header
+}
+
+func (f *SettingsFrame) ParsePayload(r io.Reader) error {
+	raw := make([]byte, f.Header.Length)
+	if _, err := io.ReadFull(r, raw); err != nil {
+		return err
+	}
+	return f.UnmarshalPayload(raw)
+}
+
+func (f *SettingsFrame) UnmarshalPayload(raw []byte) error {
+	if f.Header.Length != len(raw) {
+		return fmt.Errorf("Invalid Payload length %d != %d", f.Header.Length, len(raw))
+	}
+
+	if f.Header.Length%6 != 0 {
+		return fmt.Errorf("Invalid Payload length %d", f.Header.Length)
+	}
+
+	f.Params = make([]SettingsParameter, 0, f.Header.Length/6)
+	for i := 0; i < len(raw); i += 6 {
+		f.Params = append(f.Params, SettingsParameter{
+			Identifier: SettingsIdentifier(binary.BigEndian.Uint16(raw[i : i+2])),
+			Value:      binary.BigEndian.Uint32(raw[i+2 : i+6]),
+		})
+	}
+	return nil
+}
+
+func (f *SettingsFrame) MarshalPayload() ([]byte, error) {
+	raw := make([]byte, 0, len(f.Params)*6)
+	for i, p := range f.Params {
+		binary.BigEndian.PutUint16(raw[i*6:i*6+2], uint16(p.Identifier))
+		binary.BigEndian.PutUint32(raw[i*6+2:i*6+6], p.Value)
+	}
+	return raw, nil
+}
+
+func (f *SettingsFrame) MarshalBinary() ([]byte, error) {
+	payload, err := f.MarshalPayload()
+	if err != nil {
+		return nil, err
+	}
+
+	f.Header.Length = len(payload)
+	f.Header.Type = SettingsFrameType
+	header, err := f.Header.MarshalBinary()
+	if err != nil {
+		return nil, err
+	}
+
+	header = append(header, payload...)
+
+	return header, nil
+}
diff --git a/tools/http2_interop/unknownframe.go b/tools/http2_interop/unknownframe.go
new file mode 100644
index 0000000000000000000000000000000000000000..0450e7e976caa551b8cf1c200893b64caeb49c83
--- /dev/null
+++ b/tools/http2_interop/unknownframe.go
@@ -0,0 +1,54 @@
+package http2interop
+
+import (
+	"fmt"
+	"io"
+)
+
+type UnknownFrame struct {
+	Header FrameHeader
+	Data   []byte
+}
+
+func (f *UnknownFrame) GetHeader() *FrameHeader {
+	return &f.Header
+}
+
+func (f *UnknownFrame) ParsePayload(r io.Reader) error {
+	raw := make([]byte, f.Header.Length)
+	if _, err := io.ReadFull(r, raw); err != nil {
+		return err
+	}
+	return f.UnmarshalPayload(raw)
+}
+
+func (f *UnknownFrame) UnmarshalPayload(raw []byte) error {
+	if f.Header.Length != len(raw) {
+		return fmt.Errorf("Invalid Payload length %d != %d", f.Header.Length, len(raw))
+	}
+
+	f.Data = []byte(string(raw))
+
+	return nil
+}
+
+func (f *UnknownFrame) MarshalPayload() ([]byte, error) {
+	return []byte(string(f.Data)), nil
+}
+
+func (f *UnknownFrame) MarshalBinary() ([]byte, error) {
+	f.Header.Length = len(f.Data)
+	buf, err := f.Header.MarshalBinary()
+	if err != nil {
+		return nil, err
+	}
+
+	payload, err := f.MarshalPayload()
+	if err != nil {
+		return nil, err
+	}
+
+	buf = append(buf, payload...)
+
+	return buf, nil
+}
diff --git a/tools/jenkins/build_docker_and_run_tests.sh b/tools/jenkins/build_docker_and_run_tests.sh
index 8b7809f2e23d213493f2e8871c4c6b56ba65b77a..5bb2b6b18860037262d67efa1513f7564e9685ba 100755
--- a/tools/jenkins/build_docker_and_run_tests.sh
+++ b/tools/jenkins/build_docker_and_run_tests.sh
@@ -53,8 +53,8 @@ DOCKER_IMAGE_NAME=grpc_jenkins_slave${docker_suffix}_`sha1sum tools/jenkins/grpc
 # Make sure docker image has been built. Should be instantaneous if so.
 docker build -t $DOCKER_IMAGE_NAME tools/jenkins/grpc_jenkins_slave$docker_suffix
 
-# Make sure the CID file is gone.
-rm -f docker.cid
+# Choose random name for docker container
+CONTAINER_NAME="run_tests_$(uuidgen)"
 
 # Run tests inside docker
 docker run \
@@ -66,27 +66,26 @@ docker run \
   -i $TTY_FLAG \
   -v "$git_root:/var/local/jenkins/grpc" \
   -v /tmp/ccache:/tmp/ccache \
+  -v /tmp/npm-cache:/tmp/npm-cache \
   -v /tmp/xdg-cache-home:/tmp/xdg-cache-home \
   -v /var/run/docker.sock:/var/run/docker.sock \
   -v $(which docker):/bin/docker \
   -w /var/local/git/grpc \
-  --cidfile=docker.cid \
+  --name=$CONTAINER_NAME \
   $DOCKER_IMAGE_NAME \
   bash -l /var/local/jenkins/grpc/tools/jenkins/docker_run_tests.sh || DOCKER_FAILED="true"
 
-DOCKER_CID=`cat docker.cid`
-
 if [ "$XML_REPORT" != "" ]
 then
-  docker cp "$DOCKER_CID:/var/local/git/grpc/$XML_REPORT" $git_root
+  docker cp "$CONTAINER_NAME:/var/local/git/grpc/$XML_REPORT" $git_root
 fi
 
-docker cp "$DOCKER_CID:/var/local/git/grpc/reports.zip" $git_root || true
+docker cp "$CONTAINER_NAME:/var/local/git/grpc/reports.zip" $git_root || true
 unzip $git_root/reports.zip -d $git_root || true
 rm -f reports.zip
 
 # remove the container, possibly killing it first
-docker rm -f $DOCKER_CID || true
+docker rm -f $CONTAINER_NAME || true
 
 if [ "$DOCKER_FAILED" != "" ] && [ "$XML_REPORT" == "" ]
 then
diff --git a/tools/jenkins/build_interop_image.sh b/tools/jenkins/build_interop_image.sh
index 3664eed84d9b0567f1ae53c10f0453f3593789e3..166efbd9e2f0123048ac22910fc356bdf205fc37 100755
--- a/tools/jenkins/build_interop_image.sh
+++ b/tools/jenkins/build_interop_image.sh
@@ -77,7 +77,7 @@ docker build -t $BASE_IMAGE --force-rm=true tools/jenkins/$BASE_NAME || exit $?
 # Create a local branch so the child Docker script won't complain
 git branch -f jenkins-docker
 
-CIDFILE=`mktemp -u --suffix=.cid`
+CONTAINER_NAME="build_${BASE_NAME}_$(uuidgen)"
 
 # Prepare image for interop tests, commit it on success.
 (docker run \
@@ -85,17 +85,14 @@ CIDFILE=`mktemp -u --suffix=.cid`
   -i $TTY_FLAG \
   $MOUNT_ARGS \
   -v /tmp/ccache:/tmp/ccache \
-  --cidfile=$CIDFILE \
+  --name=$CONTAINER_NAME \
   $BASE_IMAGE \
   bash -l /var/local/jenkins/grpc/tools/jenkins/$BASE_NAME/build_interop.sh \
-  && docker commit `cat $CIDFILE` $INTEROP_IMAGE \
+  && docker commit $CONTAINER_NAME $INTEROP_IMAGE \
   && echo "Successfully built image $INTEROP_IMAGE")
 EXITCODE=$?
 
 # remove intermediate container, possibly killing it first
-docker rm -f `cat $CIDFILE`
-
-# remove the cidfile
-rm -rf `cat $CIDFILE`
+docker rm -f $CONTAINER_NAME
 
 exit $EXITCODE
diff --git a/tools/jenkins/grpc_interop_node/Dockerfile b/tools/jenkins/grpc_interop_node/Dockerfile
index 587227b94294daec0c98f3de898a6f949f0a8af4..db5aff844de1aa96584e56d7a3ee3b474c7cf345 100644
--- a/tools/jenkins/grpc_interop_node/Dockerfile
+++ b/tools/jenkins/grpc_interop_node/Dockerfile
@@ -48,6 +48,7 @@ RUN apt-get update && apt-get install -y \
   libc6-dbg \
   libc6-dev \
   libgtest-dev \
+  libssl-dev \
   libtool \
   make \
   strace \
diff --git a/tools/jenkins/grpc_interop_node/build_interop.sh b/tools/jenkins/grpc_interop_node/build_interop.sh
index 84e25e330886ab3c15cacea0af121a25e49bd3d3..3b69715c9af983effb7126e0312b1588483a0e5c 100755
--- a/tools/jenkins/grpc_interop_node/build_interop.sh
+++ b/tools/jenkins/grpc_interop_node/build_interop.sh
@@ -45,5 +45,4 @@ make install-certs
 
 # build Node interop client & server
 npm install -g node-gyp
-make install_c -C /var/local/git/grpc
-(cd src/node && npm install && node-gyp rebuild)
+(npm install && node-gyp rebuild)
diff --git a/tools/jenkins/grpc_jenkins_slave/Dockerfile b/tools/jenkins/grpc_jenkins_slave/Dockerfile
index 5f2b425c8c27e2ef171365c6e4126546c23c88a0..5b8c24c076ca217204a539a5109f4f24151b8b66 100644
--- a/tools/jenkins/grpc_jenkins_slave/Dockerfile
+++ b/tools/jenkins/grpc_jenkins_slave/Dockerfile
@@ -101,7 +101,7 @@ ENV NUGET mono /var/local/NuGet.exe
 # Install nvm
 RUN touch .profile
 RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.25.4/install.sh | bash
-RUN /bin/bash -l -c "nvm install 0.12"
+RUN /bin/bash -l -c "nvm install 0.12 && npm config set cache /tmp/npm-cache"
 
 ##################
 # Ruby dependencies
@@ -157,6 +157,12 @@ RUN apt-get update && apt-get install -y \
 
 RUN apt-get install -y libzookeeper-mt-dev
 
+##################
+# Docker "inception".
+# Note this is quite the ugly hack.
+# This makes sure that the docker binary we inject has its dependencies.
+RUN curl https://get.docker.com/ | sh
+RUN apt-get remove --purge -y docker-engine
 
 RUN mkdir /var/local/jenkins
 
diff --git a/tools/jenkins/grpc_jenkins_slave_32bits/Dockerfile b/tools/jenkins/grpc_jenkins_slave_32bits/Dockerfile
index 81a039909160acc66f9a3222caf9011c7a2a9112..7179a50cc5993fd063c59e8805659e0c496e58d8 100644
--- a/tools/jenkins/grpc_jenkins_slave_32bits/Dockerfile
+++ b/tools/jenkins/grpc_jenkins_slave_32bits/Dockerfile
@@ -101,7 +101,7 @@ ENV NUGET mono /var/local/NuGet.exe
 # Install nvm
 RUN touch .profile
 RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.25.4/install.sh | bash
-RUN /bin/bash -l -c "nvm install 0.12"
+RUN /bin/bash -l -c "nvm install 0.12 && npm config set cache /tmp/npm-cache"
 
 ##################
 # Ruby dependencies
diff --git a/tools/jenkins/run_jenkins.sh b/tools/jenkins/run_jenkins.sh
index f79e739f6aecb92648165c79f134918bb2ff7cf1..0e1af2a2a9a94702a529645139ad418dd4edec64 100755
--- a/tools/jenkins/run_jenkins.sh
+++ b/tools/jenkins/run_jenkins.sh
@@ -63,10 +63,6 @@ then
   # Prevent msbuild from picking up "platform" env variable, which would break the build
   unset platform
 
-  # TODO(jtattermusch): integrate nuget restore in a nicer way.
-  /cygdrive/c/nuget/nuget.exe restore vsprojects/grpc.sln
-  /cygdrive/c/nuget/nuget.exe restore src/csharp/Grpc.sln
-
   python tools/run_tests/run_tests.py -t -l $language -x report.xml $@ || true
 
 elif [ "$platform" == "macos" ]
@@ -90,3 +86,9 @@ else
   echo "Unknown platform $platform"
   exit 1
 fi
+
+if [ ! -e reports/index.html ]
+then
+  mkdir -p reports
+  echo 'No reports generated.' > reports/index.html
+fi
diff --git a/tools/run_tests/build_csharp.sh b/tools/run_tests/build_csharp.sh
index eae7bd50405aa37fbacbda8bfe62a4efdc87d931..6737d88b273fa577083d535442bc70a53ebca3c5 100755
--- a/tools/run_tests/build_csharp.sh
+++ b/tools/run_tests/build_csharp.sh
@@ -37,14 +37,6 @@ else
   MSBUILD_CONFIG="Release"
 fi
 
-# change to gRPC repo root
-cd $(dirname $0)/../..
+cd $(dirname $0)/../../src/csharp
 
-root=`pwd`
-
-if [ -n "$NUGET" ]
-then
-  $NUGET restore src/csharp/Grpc.sln
-fi
-
-xbuild /p:Configuration=$MSBUILD_CONFIG src/csharp/Grpc.sln
+xbuild /p:Configuration=$MSBUILD_CONFIG Grpc.sln
diff --git a/tools/run_tests/build_ruby.sh b/tools/run_tests/build_ruby.sh
index 259f336ef225763a40da5d3b57dae65fd2de2ca6..6d23c316c5ad1e8781d4685ff34a82d6005104a3 100755
--- a/tools/run_tests/build_ruby.sh
+++ b/tools/run_tests/build_ruby.sh
@@ -37,6 +37,4 @@ export GRPC_CONFIG=${CONFIG:-opt}
 cd $(dirname $0)/../../src/ruby
 
 rm -rf ./tmp
-
-bundle install
 rake compile:grpc
diff --git a/tools/run_tests/dockerjob.py b/tools/run_tests/dockerjob.py
index 11686d46b09ed8267a157d216ae1b671e28bcb47..1d67fe3033e2d0880fedb512cc96ff0dbeaaf80f 100755
--- a/tools/run_tests/dockerjob.py
+++ b/tools/run_tests/dockerjob.py
@@ -38,24 +38,32 @@ import subprocess
 
 _DEVNULL = open(os.devnull, 'w')
 
-def wait_for_file(filepath, timeout_seconds=15):
-  """Wait until given file exists and returns its content."""
-  started = time.time()
-  while time.time() - started < timeout_seconds:
-    if os.path.isfile(filepath):
-      with open(filepath, 'r') as f:
-        content = f.read()
-        # make sure we don't return empty content
-        if content:
-          return content
-    time.sleep(1)
-  raise Exception('Failed to read file %s.' % filepath)
+
+def random_name(base_name):
+  """Randomizes given base name."""
+  return '%s_%s' % (base_name, uuid.uuid4())
+
+
+def docker_kill(cid):
+  """Kills a docker container. Returns True if successful."""
+  return subprocess.call(['docker','kill', str(cid)],
+                         stdout=_DEVNULL,
+                         stderr=subprocess.STDOUT) == 0
 
 
-def docker_mapped_port(cid, port):
+def docker_mapped_port(cid, port, timeout_seconds=15):
   """Get port mapped to internal given internal port for given container."""
-  output = subprocess.check_output('docker port %s %s' % (cid, port), shell=True)
-  return int(output.split(':', 2)[1])
+  started = time.time()
+  while time.time() - started < timeout_seconds:
+    try:
+      output = subprocess.check_output('docker port %s %s' % (cid, port),
+                                       stderr=_DEVNULL,
+                                       shell=True)
+      return int(output.split(':', 2)[1])
+    except subprocess.CalledProcessError as e:
+      pass
+  raise Exception('Failed to get exposed port %s for container %s.' %
+                  (port, cid))
 
 
 def finish_jobs(jobs):
@@ -71,7 +79,7 @@ def image_exists(image):
   """Returns True if given docker image exists."""
   return subprocess.call(['docker','inspect', image],
                          stdout=_DEVNULL,
-                         stderr=_DEVNULL) == 0
+                         stderr=subprocess.STDOUT) == 0
 
 
 def remove_image(image, skip_nonexistent=False, max_retries=10):
@@ -79,7 +87,9 @@ def remove_image(image, skip_nonexistent=False, max_retries=10):
   if skip_nonexistent and not image_exists(image):
     return True
   for attempt in range(0, max_retries):
-    if subprocess.call(['docker','rmi', '-f', image]) == 0:
+    if subprocess.call(['docker','rmi', '-f', image],
+                       stdout=_DEVNULL,
+                       stderr=subprocess.STDOUT) == 0:
       return True
     time.sleep(2)
   print 'Failed to remove docker image %s' % image
@@ -92,23 +102,16 @@ class DockerJob:
   def __init__(self, spec):
     self._spec = spec
     self._job = jobset.Job(spec, bin_hash=None, newline_on_success=True, travis=True, add_env={}, xml_report=None)
-    self._cidfile = spec.cidfile
-    self._cid = None
-
-  def cid(self):
-    """Gets cid of this container"""
-    if not self._cid:
-      self._cid = wait_for_file(self._cidfile)
-    return self._cid
+    self._container_name = spec.container_name
 
   def mapped_port(self, port):
-    return docker_mapped_port(self.cid(), port)
+    return docker_mapped_port(self._container_name, port)
 
   def kill(self, suppress_failure=False):
     """Sends kill signal to the container."""
     if suppress_failure:
       self._job.suppress_failure_message()
-    return subprocess.call(['docker','kill', self.cid()]) == 0
+    return docker_kill(self._container_name)
 
   def is_running(self):
     """Polls a job and returns True if given job is still running."""
diff --git a/tools/run_tests/jobset.py b/tools/run_tests/jobset.py
index 87be703b4cdfa6d1a3b859db603801b32a461717..17a63c02e8413a2c2c04fcd6b959afb6cded0fd6 100755
--- a/tools/run_tests/jobset.py
+++ b/tools/run_tests/jobset.py
@@ -135,13 +135,14 @@ class JobSpec(object):
 
   def __init__(self, cmdline, shortname=None, environ=None, hash_targets=None,
                cwd=None, shell=False, timeout_seconds=5*60, flake_retries=0,
-               timeout_retries=0):
+               timeout_retries=0, kill_handler=None):
     """
     Arguments:
       cmdline: a list of arguments to pass as the command line
       environ: a dictionary of environment variables to set in the child process
       hash_targets: which files to include in the hash representing the jobs version
                     (or empty, indicating the job should not be hashed)
+      kill_handler: a handler that will be called whenever job.kill() is invoked
     """
     if environ is None:
       environ = {}
@@ -156,6 +157,7 @@ class JobSpec(object):
     self.timeout_seconds = timeout_seconds
     self.flake_retries = flake_retries
     self.timeout_retries = timeout_retries
+    self.kill_handler = kill_handler
 
   def identity(self):
     return '%r %r %r' % (self.cmdline, self.environ, self.hash_targets)
@@ -254,6 +256,8 @@ class Job(object):
   def kill(self):
     if self._state == _RUNNING:
       self._state = _KILLED
+      if self._spec.kill_handler:
+        self._spec.kill_handler(self)
       self._process.terminate()
 
   def suppress_failure_message(self):
diff --git a/tools/run_tests/port_server.py b/tools/run_tests/port_server.py
index b953df952cbcac3af22e64223535fbf87cd47b06..3b85486ebfe599732f38f1db2756db9b03edc2e4 100755
--- a/tools/run_tests/port_server.py
+++ b/tools/run_tests/port_server.py
@@ -42,7 +42,7 @@ import time
 # increment this number whenever making a change to ensure that
 # the changes are picked up by running CI servers
 # note that all changes must be backwards compatible
-_MY_VERSION = 2
+_MY_VERSION = 5
 
 
 if len(sys.argv) == 2 and sys.argv[1] == 'dump_version':
@@ -52,8 +52,16 @@ if len(sys.argv) == 2 and sys.argv[1] == 'dump_version':
 
 argp = argparse.ArgumentParser(description='Server for httpcli_test')
 argp.add_argument('-p', '--port', default=12345, type=int)
+argp.add_argument('-l', '--logfile', default=None, type=str)
 args = argp.parse_args()
 
+if args.logfile is not None:
+  sys.stdin.close()
+  sys.stderr.close()
+  sys.stdout.close()
+  sys.stderr = open(args.logfile, 'w')
+  sys.stdout = sys.stderr
+
 print 'port server running on port %d' % args.port
 
 pool = []
@@ -119,9 +127,12 @@ class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
       self.send_header('Content-Type', 'text/plain')
       self.end_headers()
       p = int(self.path[6:])
-      del in_use[p]
-      pool.append(p)
-      self.log_message('drop port %d' % p)
+      if p in in_use:
+        del in_use[p]
+        pool.append(p)
+        self.log_message('drop known port %d' % p)
+      else:
+        self.log_message('drop unknown port %d' % p)
     elif self.path == '/version_number':
       # fetch a version string and the current process pid
       self.send_response(200)
@@ -146,6 +157,6 @@ class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
 httpd = BaseHTTPServer.HTTPServer(('', args.port), Handler)
 while keep_running:
   httpd.handle_request()
+  sys.stderr.flush()
 
 print 'done'
-
diff --git a/tools/run_tests/pre_build_c.bat b/tools/run_tests/pre_build_c.bat
new file mode 100644
index 0000000000000000000000000000000000000000..f0449f3c424af08e7a2c01ed2f215fc4033db451
--- /dev/null
+++ b/tools/run_tests/pre_build_c.bat
@@ -0,0 +1,21 @@
+@rem Performs nuget restore step for C/C++.
+
+setlocal
+
+@rem enter repo root
+cd /d %~dp0\..\..
+
+@rem Location of nuget.exe
+set NUGET=C:\nuget\nuget.exe
+
+if exist %NUGET% (
+  %NUGET% restore vsprojects/grpc.sln || goto :error
+)
+
+endlocal
+
+goto :EOF
+
+:error
+echo Failed!
+exit /b %errorlevel%
diff --git a/tools/run_tests/pre_build_csharp.bat b/tools/run_tests/pre_build_csharp.bat
new file mode 100644
index 0000000000000000000000000000000000000000..853a8f4325542c6529013912556c9ed22ae5d1bd
--- /dev/null
+++ b/tools/run_tests/pre_build_csharp.bat
@@ -0,0 +1,22 @@
+@rem Performs nuget restore step for C#.
+
+setlocal
+
+@rem enter repo root
+cd /d %~dp0\..\..
+
+@rem Location of nuget.exe
+set NUGET=C:\nuget\nuget.exe
+
+if exist %NUGET% (
+  %NUGET% restore vsprojects/grpc_csharp_ext.sln || goto :error
+  %NUGET% restore src/csharp/Grpc.sln || goto :error
+)
+
+endlocal
+
+goto :EOF
+
+:error
+echo Failed!
+exit /b %errorlevel%
diff --git a/tools/run_tests/pre_build_csharp.sh b/tools/run_tests/pre_build_csharp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..42ff60bea220f38f4a8ee0c30f71ddf94d0a0125
--- /dev/null
+++ b/tools/run_tests/pre_build_csharp.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+set -ex
+
+# cd to gRPC csharp directory
+cd $(dirname $0)/../../src/csharp
+
+root=`pwd`
+
+if [ -n "$NUGET" ]
+then
+  $NUGET restore Grpc.sln
+fi
diff --git a/tools/run_tests/pre_build_ruby.sh b/tools/run_tests/pre_build_ruby.sh
new file mode 100755
index 0000000000000000000000000000000000000000..569a1d0333b8876dfca778bd3c1c90dad23d6c5d
--- /dev/null
+++ b/tools/run_tests/pre_build_ruby.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+set -ex
+
+export GRPC_CONFIG=${CONFIG:-opt}
+
+# change to grpc's ruby directory
+cd $(dirname $0)/../../src/ruby
+
+bundle install
diff --git a/tools/run_tests/run_interop_tests.py b/tools/run_tests/run_interop_tests.py
index 48c34f68713639b7f21466d50678f6621930fe68..6daa967bba488f01f0e8a589a205aa72a3c079ce 100755
--- a/tools/run_tests/run_interop_tests.py
+++ b/tools/run_tests/run_interop_tests.py
@@ -62,7 +62,6 @@ _CLOUD_TO_CLOUD_BASE_ARGS = [
 _SSL_CERT_ENV = { 'SSL_CERT_FILE':'/usr/local/share/grpc/roots.pem' }
 
 # TODO(jtattermusch) unify usage of --use_tls and --use_tls=true
-# TODO(jtattermusch) unify usage of --use_prod_roots and --use_test_ca
 # TODO(jtattermusch) go uses --tls_ca_file instead of --use_test_ca
 
 
@@ -72,14 +71,15 @@ class CXXLanguage:
     self.client_cmdline_base = ['bins/opt/interop_client']
     self.client_cwd = None
     self.server_cwd = None
+    self.safename = 'cxx'
 
   def cloud_to_prod_args(self):
     return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
-            ['--use_tls=true','--use_prod_roots'])
+            ['--use_tls=true'])
 
   def cloud_to_cloud_args(self):
     return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
-            ['--use_tls=true'])
+            ['--use_tls=true', '--use_test_ca=true'])
 
   def cloud_to_prod_env(self):
     return {}
@@ -97,20 +97,21 @@ class CSharpLanguage:
     self.client_cmdline_base = ['mono', 'Grpc.IntegrationTesting.Client.exe']
     self.client_cwd = 'src/csharp/Grpc.IntegrationTesting.Client/bin/Debug'
     self.server_cwd = 'src/csharp/Grpc.IntegrationTesting.Server/bin/Debug'
+    self.safename = str(self)
 
   def cloud_to_prod_args(self):
     return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
-            ['--use_tls'])
+            ['--use_tls=true'])
 
   def cloud_to_cloud_args(self):
     return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
-            ['--use_tls', '--use_test_ca'])
+            ['--use_tls=true', '--use_test_ca=true'])
 
   def cloud_to_prod_env(self):
     return _SSL_CERT_ENV
 
   def server_args(self):
-    return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls']
+    return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls=true']
 
   def __str__(self):
     return 'csharp'
@@ -122,6 +123,7 @@ class JavaLanguage:
     self.client_cmdline_base = ['./run-test-client.sh']
     self.client_cwd = '../grpc-java'
     self.server_cwd = '../grpc-java'
+    self.safename = str(self)
 
   def cloud_to_prod_args(self):
     return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
@@ -148,6 +150,7 @@ class GoLanguage:
     # TODO: this relies on running inside docker
     self.client_cwd = '/go/src/google.golang.org/grpc/interop/client'
     self.server_cwd = '/go/src/google.golang.org/grpc/interop/server'
+    self.safename = str(self)
 
   def cloud_to_prod_args(self):
     return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
@@ -173,6 +176,7 @@ class NodeLanguage:
     self.client_cmdline_base = ['node', 'src/node/interop/interop_client.js']
     self.client_cwd = None
     self.server_cwd = None
+    self.safename = str(self)
 
   def cloud_to_prod_args(self):
     return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
@@ -197,6 +201,7 @@ class PHPLanguage:
   def __init__(self):
     self.client_cmdline_base = ['src/php/bin/interop_client.sh']
     self.client_cwd = None
+    self.safename = str(self)
 
   def cloud_to_prod_args(self):
     return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
@@ -219,6 +224,7 @@ class RubyLanguage:
     self.client_cmdline_base = ['ruby', 'src/ruby/bin/interop/interop_client.rb']
     self.client_cwd = None
     self.server_cwd = None
+    self.safename = str(self)
 
   def cloud_to_prod_args(self):
     return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
@@ -252,11 +258,9 @@ _LANGUAGES = {
 # languages supported as cloud_to_cloud servers
 _SERVERS = ['c++', 'node', 'csharp', 'java', 'go', 'ruby']
 
-# TODO(jtattermusch): add empty_stream once PHP starts supporting it.
 # TODO(jtattermusch): add timeout_on_sleeping_server once java starts supporting it.
-# TODO(jtattermusch): add support for auth tests.
 _TEST_CASES = ['large_unary', 'empty_unary', 'ping_pong',
-               'client_streaming', 'server_streaming',
+               'empty_stream', 'client_streaming', 'server_streaming',
                'cancel_after_begin', 'cancel_after_first_response']
 
 _AUTH_TEST_CASES = ['compute_engine_creds', 'jwt_token_creds',
@@ -312,23 +316,39 @@ def add_auth_options(language, test_case, cmdline, env):
   if test_case in ['per_rpc_creds', 'oauth2_auth_token']:
     cmdline += [oauth_scope_arg]
 
+  if test_case == 'oauth2_auth_token' and language == 'c++':
+    # C++ oauth2 test uses GCE creds and thus needs to know the default account
+    cmdline += [default_account_arg]
+
   if test_case == 'compute_engine_creds':
     cmdline += [oauth_scope_arg, default_account_arg]
 
   return (cmdline, env)
 
 
+def _job_kill_handler(job):
+  if job._spec.container_name:
+    dockerjob.docker_kill(job._spec.container_name)
+
+
 def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
   """Creates jobspec for cloud-to-prod interop test"""
   cmdline = language.cloud_to_prod_args() + ['--test_case=%s' % test_case]
   cwd = language.client_cwd
   environ = language.cloud_to_prod_env()
+  container_name = None
   if auth:
     cmdline, environ = add_auth_options(language, test_case, cmdline, environ)
   cmdline = bash_login_cmdline(cmdline)
 
   if docker_image:
-    cmdline = docker_run_cmdline(cmdline, image=docker_image, cwd=cwd, environ=environ)
+    container_name = dockerjob.random_name('interop_client_%s' % language.safename)
+    cmdline = docker_run_cmdline(cmdline,
+                                 image=docker_image,
+                                 cwd=cwd,
+                                 environ=environ,
+                                 docker_args=['--net=host',
+                                              '--name', container_name])
     cwd = None
     environ = None
 
@@ -340,7 +360,9 @@ def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
           shortname="%s:%s:%s" % (suite_name, language, test_case),
           timeout_seconds=2*60,
           flake_retries=5 if args.allow_flakes else 0,
-          timeout_retries=2 if args.allow_flakes else 0)
+          timeout_retries=2 if args.allow_flakes else 0,
+          kill_handler=_job_kill_handler)
+  test_job.container_name = container_name
   return test_job
 
 
@@ -353,11 +375,14 @@ def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
                                 '--server_port=%s' % server_port ])
   cwd = language.client_cwd
   if docker_image:
+    container_name = dockerjob.random_name('interop_client_%s' % language.safename)
     cmdline = docker_run_cmdline(cmdline,
                                  image=docker_image,
                                  cwd=cwd,
-                                 docker_args=['--net=host'])
+                                 docker_args=['--net=host',
+                                              '--name', container_name])
     cwd = None
+
   test_job = jobset.JobSpec(
           cmdline=cmdline,
           cwd=cwd,
@@ -365,34 +390,36 @@ def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
                                                  test_case),
           timeout_seconds=2*60,
           flake_retries=5 if args.allow_flakes else 0,
-          timeout_retries=2 if args.allow_flakes else 0)
+          timeout_retries=2 if args.allow_flakes else 0,
+          kill_handler=_job_kill_handler)
+  test_job.container_name = container_name
   return test_job
 
 
 def server_jobspec(language, docker_image):
   """Create jobspec for running a server"""
-  cidfile = tempfile.mktemp()
+  container_name = dockerjob.random_name('interop_server_%s' % language.safename)
   cmdline = bash_login_cmdline(language.server_args() +
                                ['--port=%s' % _DEFAULT_SERVER_PORT])
   docker_cmdline = docker_run_cmdline(cmdline,
                                       image=docker_image,
                                       cwd=language.server_cwd,
                                       docker_args=['-p', str(_DEFAULT_SERVER_PORT),
-                                                   '--cidfile', cidfile])
+                                                   '--name', container_name])
   server_job = jobset.JobSpec(
           cmdline=docker_cmdline,
-          shortname="interop_server:%s" % language,
+          shortname="interop_server_%s" % language,
           timeout_seconds=30*60)
-  server_job.cidfile = cidfile
+  server_job.container_name = container_name
   return server_job
 
 
 def build_interop_image_jobspec(language, tag=None):
   """Creates jobspec for building interop docker image for a language"""
-  safelang = str(language).replace("+", "x")
   if not tag:
-    tag = 'grpc_interop_%s:%s' % (safelang, uuid.uuid4())
-  env = {'INTEROP_IMAGE': tag, 'BASE_NAME': 'grpc_interop_%s' % safelang}
+    tag = 'grpc_interop_%s:%s' % (language.safename, uuid.uuid4())
+  env = {'INTEROP_IMAGE': tag,
+         'BASE_NAME': 'grpc_interop_%s' % language.safename}
   if not args.travis:
     env['TTY_FLAG'] = '-t'
   build_job = jobset.JobSpec(
diff --git a/tools/run_tests/run_node.sh b/tools/run_tests/run_node.sh
index 780969089daec2bb8d3540ee95d9251fce14f72a..0a11e87c37a7cfd023ae93cf84f3c0a60a4abde9 100755
--- a/tools/run_tests/run_node.sh
+++ b/tools/run_tests/run_node.sh
@@ -46,6 +46,8 @@ then
   lcov --base-directory . --directory . -c -o coverage.info
   genhtml -o ../reports/node_ext_coverage --num-spaces 2 \
     -t 'Node gRPC test coverage' coverage.info
+  echo '<html><head><meta http-equiv="refresh" content="0;URL=lcov-report/index.html"></head></html>' > \
+    ../reports/node_coverage/index.html
 else
   ./node_modules/mocha/bin/mocha --timeout 8000 src/node/test
 fi
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index 6423ecfd7cbacbf4890564e706203e988c142fbd..e9ae9f4795a08bd319fc497002cd7356de27bead 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -43,6 +43,8 @@ import re
 import socket
 import subprocess
 import sys
+import tempfile
+import traceback
 import time
 import xml.etree.cElementTree as ET
 import urllib2
@@ -71,16 +73,17 @@ def platform_string():
 # SimpleConfig: just compile with CONFIG=config, and run the binary to test
 class SimpleConfig(object):
 
-  def __init__(self, config, environ=None, timeout_seconds=5*60):
+  def __init__(self, config, environ=None, timeout_multiplier=1):
     if environ is None:
       environ = {}
     self.build_config = config
     self.allow_hashing = (config != 'gcov')
     self.environ = environ
     self.environ['CONFIG'] = config
-    self.timeout_seconds = timeout_seconds
+    self.timeout_multiplier = timeout_multiplier
 
-  def job_spec(self, cmdline, hash_targets, shortname=None, environ={}):
+  def job_spec(self, cmdline, hash_targets, timeout_seconds=5*60,
+               shortname=None, environ={}):
     """Construct a jobset.JobSpec for a test under this config
 
        Args:
@@ -98,10 +101,11 @@ class SimpleConfig(object):
     return jobset.JobSpec(cmdline=cmdline,
                           shortname=shortname,
                           environ=actual_environ,
-                          timeout_seconds=self.timeout_seconds,
+                          timeout_seconds=self.timeout_multiplier * timeout_seconds,
                           hash_targets=hash_targets
                               if self.allow_hashing else None,
-                          flake_retries=5 if args.allow_flakes else 0)
+                          flake_retries=5 if args.allow_flakes else 0,
+                          timeout_retries=3 if args.allow_flakes else 0)
 
 
 # ValgrindConfig: compile with some CONFIG=config, but use valgrind to run
@@ -121,7 +125,7 @@ class ValgrindConfig(object):
                           shortname='valgrind %s' % cmdline[0],
                           hash_targets=None,
                           flake_retries=5 if args.allow_flakes else 0,
-                          timeout_retries=2 if args.allow_flakes else 0)
+                          timeout_retries=3 if args.allow_flakes else 0)
 
 
 def get_c_tests(travis, test_lang) :
@@ -167,7 +171,10 @@ class CLanguage(object):
     return ['buildtests_%s' % self.make_target, 'tools_%s' % self.make_target]
 
   def pre_build_steps(self):
-    return []
+    if self.platform == 'windows':
+      return [['tools\\run_tests\\pre_build_c.bat']]
+    else:
+      return []
 
   def build_steps(self):
     return []
@@ -188,7 +195,8 @@ class NodeLanguage(object):
                             environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
 
   def pre_build_steps(self):
-    return []
+    # Default to 1 week cache expiration
+    return [['npm', 'update', '--cache-min', '604800']]
 
   def make_targets(self):
     return []
@@ -245,6 +253,7 @@ class PythonLanguage(object):
         None,
         environ=environment,
         shortname='py.test',
+        timeout_seconds=15*60
     )]
 
   def pre_build_steps(self):
@@ -284,7 +293,7 @@ class RubyLanguage(object):
                             environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
 
   def pre_build_steps(self):
-    return []
+    return [['tools/run_tests/pre_build_ruby.sh']]
 
   def make_targets(self):
     return ['static_c']
@@ -321,7 +330,10 @@ class CSharpLanguage(object):
             for assembly in assemblies]
 
   def pre_build_steps(self):
-    return []
+    if self.platform == 'windows':
+      return [['tools\\run_tests\\pre_build_csharp.bat']]
+    else:
+      return [['tools/run_tests/pre_build_csharp.sh']]
 
   def make_targets(self):
     # For Windows, this target doesn't really build anything,
@@ -425,11 +437,11 @@ class Build(object):
 _CONFIGS = {
     'dbg': SimpleConfig('dbg'),
     'opt': SimpleConfig('opt'),
-    'tsan': SimpleConfig('tsan', timeout_seconds=10*60, environ={
+    'tsan': SimpleConfig('tsan', timeout_multiplier=2, environ={
         'TSAN_OPTIONS': 'suppressions=tools/tsan_suppressions.txt:halt_on_error=1:second_deadlock_stack=1'}),
-    'msan': SimpleConfig('msan', timeout_seconds=7*60),
+    'msan': SimpleConfig('msan', timeout_multiplier=1.5),
     'ubsan': SimpleConfig('ubsan'),
-    'asan': SimpleConfig('asan', timeout_seconds=7*60, environ={
+    'asan': SimpleConfig('asan', timeout_multiplier=1.5, environ={
         'ASAN_OPTIONS': 'detect_leaks=1:color=always:suppressions=tools/tsan_suppressions.txt',
         'LSAN_OPTIONS': 'report_objects=1'}),
     'asan-noleaks': SimpleConfig('asan', environ={
@@ -567,7 +579,7 @@ run_configs = set(_CONFIGS[cfg]
 build_configs = set(cfg.build_config for cfg in run_configs)
 
 if args.travis:
-  _FORCE_ENVIRON_FOR_WRAPPERS = {'GRPC_TRACE': 'surface,batch'}
+  _FORCE_ENVIRON_FOR_WRAPPERS = {'GRPC_TRACE': 'api'}
 
 languages = set(_LANGUAGES[l]
                 for l in itertools.chain.from_iterable(
@@ -612,7 +624,7 @@ for l in languages:
       set(l.make_targets()))
 
 build_steps = list(set(
-                   jobset.JobSpec(cmdline, environ={'CONFIG': cfg})
+                   jobset.JobSpec(cmdline, environ={'CONFIG': cfg}, flake_retries=5)
                    for cfg in build_configs
                    for l in languages
                    for cmdline in l.pre_build_steps()))
@@ -694,35 +706,62 @@ def _start_port_server(port_server_port):
       urllib2.urlopen('http://localhost:%d/quitquitquit' % port_server_port).read()
       time.sleep(1)
   if not running:
-    print 'starting port_server'
-    port_log = open('portlog.txt', 'w')
-    port_server = subprocess.Popen(
-        [sys.executable, 'tools/run_tests/port_server.py', '-p', '%d' % port_server_port],
-        stderr=subprocess.STDOUT,
-        stdout=port_log)
+    fd, logfile = tempfile.mkstemp()
+    os.close(fd)
+    print 'starting port_server, with log file %s' % logfile
+    args = [sys.executable, 'tools/run_tests/port_server.py', '-p', '%d' % port_server_port, '-l', logfile]
+    env = dict(os.environ)
+    env['BUILD_ID'] = 'pleaseDontKillMeJenkins'
+    if platform.system() == 'Windows':
+      port_server = subprocess.Popen(
+          args,
+          env=env,
+          creationflags = 0x00000008, # detached process
+          close_fds=True)
+    else:
+      port_server = subprocess.Popen(
+          args,
+          env=env,
+          preexec_fn=os.setsid,
+          close_fds=True)
+    time.sleep(1)
     # ensure port server is up
     waits = 0
     while True:
       if waits > 10:
+        print 'killing port server due to excessive start up waits'
         port_server.kill()
       if port_server.poll() is not None:
         print 'port_server failed to start'
-        port_log = open('portlog.txt', 'r').read()
-        print port_log
-        sys.exit(1)
+        # try one final time: maybe another build managed to start one
+        time.sleep(1)
+        try:
+          urllib2.urlopen('http://localhost:%d/get' % port_server_port,
+                          timeout=1).read()
+          print 'last ditch attempt to contact port server succeeded'
+          break
+        except:
+          traceback.print_exc();
+          port_log = open(logfile, 'r').read()
+          print port_log
+          sys.exit(1)
       try:
         urllib2.urlopen('http://localhost:%d/get' % port_server_port,
                         timeout=1).read()
+        print 'port server is up and ready'
         break
       except socket.timeout:
         print 'waiting for port_server: timeout'
-        time.sleep(0.5)
+        traceback.print_exc();
+        time.sleep(1)
         waits += 1
       except urllib2.URLError:
         print 'waiting for port_server: urlerror'
-        time.sleep(0.5)
+        traceback.print_exc();
+        time.sleep(1)
         waits += 1
       except:
+        traceback.print_exc();
         port_server.kill()
         raise
 
@@ -731,7 +770,7 @@ def _build_and_run(
     check_cancelled, newline_on_success, travis, cache, xml_report=None):
   """Do one pass of building & running tests."""
   # build latest sequentially
-  if not jobset.run(build_steps, maxjobs=1,
+  if not jobset.run(build_steps, maxjobs=1, stop_on_failure=True,
                     newline_on_success=newline_on_success, travis=travis):
     return 1
 
diff --git a/tools/run_tests/sources_and_headers.json b/tools/run_tests/sources_and_headers.json
index af6a81bfd5c5b3281cc8fa58a20969dc294f854d..5fb6643f5949012f8949d1bf6119af09bd7e04ae 100644
--- a/tools/run_tests/sources_and_headers.json
+++ b/tools/run_tests/sources_and_headers.json
@@ -12366,6 +12366,7 @@
       "src/core/surface/api_trace.h", 
       "src/core/surface/byte_buffer_queue.h", 
       "src/core/surface/call.h", 
+      "src/core/surface/call_test_only.h", 
       "src/core/surface/channel.h", 
       "src/core/surface/completion_queue.h", 
       "src/core/surface/event_string.h", 
@@ -12610,6 +12611,7 @@
       "src/core/surface/call.h", 
       "src/core/surface/call_details.c", 
       "src/core/surface/call_log_batch.c", 
+      "src/core/surface/call_test_only.h", 
       "src/core/surface/channel.c", 
       "src/core/surface/channel.h", 
       "src/core/surface/channel_connectivity.c", 
@@ -12865,6 +12867,7 @@
       "src/core/surface/api_trace.h", 
       "src/core/surface/byte_buffer_queue.h", 
       "src/core/surface/call.h", 
+      "src/core/surface/call_test_only.h", 
       "src/core/surface/channel.h", 
       "src/core/surface/completion_queue.h", 
       "src/core/surface/event_string.h", 
@@ -13079,6 +13082,7 @@
       "src/core/surface/call.h", 
       "src/core/surface/call_details.c", 
       "src/core/surface/call_log_batch.c", 
+      "src/core/surface/call_test_only.h", 
       "src/core/surface/channel.c", 
       "src/core/surface/channel.h", 
       "src/core/surface/channel_connectivity.c", 
diff --git a/vsprojects/nuget_package/README.md b/vsprojects/nuget_package/README.md
index 00bb400d661f6e15bf0aeaeb56f19e094c87704d..9fcbb5f85df7c124d89cc77ef4191fb8e28cc4b1 100644
--- a/vsprojects/nuget_package/README.md
+++ b/vsprojects/nuget_package/README.md
@@ -16,7 +16,7 @@ Build all flavors of gRPC C# extension and package them as a NuGet package.
 ```
 buildall.bat
 
-nuget pack grpc.native.csharp_ext.nuspec
+nuget pack grpc.native.csharp.nuspec
 ```
 
 When building the NuGet package, ignore the "Assembly outside lib folder" warnings (they DLLs are not assemblies, they are native libraries).
diff --git a/vsprojects/nuget_package/grpc.native.csharp_ext.nuspec b/vsprojects/nuget_package/grpc.native.csharp.nuspec
similarity index 85%
rename from vsprojects/nuget_package/grpc.native.csharp_ext.nuspec
rename to vsprojects/nuget_package/grpc.native.csharp.nuspec
index 39791a5d8e040765ba9efc25ea60bb646aeca164..50b56e9e6e65e3dd605c79db44f7d7d5dbd68d75 100644
--- a/vsprojects/nuget_package/grpc.native.csharp_ext.nuspec
+++ b/vsprojects/nuget_package/grpc.native.csharp.nuspec
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <package>
   <metadata>
-    <id>grpc.native.csharp_ext</id>
+    <id>grpc.native.csharp</id>
     <version>$version$</version>
     <authors>Google Inc.</authors>
     <owners>grpc-packages</owners>
@@ -20,8 +20,8 @@
     </dependencies>
   </metadata>
   <files>
-    <file src="grpc.native.csharp_ext.props" target="\build\portable-net45\grpc.native.csharp_ext.props" />
-    <file src="grpc.native.csharp_ext.targets" target="\build\portable-net45\grpc.native.csharp_ext.targets" />
+    <file src="grpc.native.csharp.props" target="\build\portable-net45\grpc.native.csharp.props" />
+    <file src="grpc.native.csharp.targets" target="\build\portable-net45\grpc.native.csharp.targets" />
     <file src="output\v100\Win32\Release\grpc_csharp_ext.dll" target="/build/native/bin/v100\Win32\Release\grpc_csharp_ext.dll" />
     <file src="output\v120\Win32\Release\grpc_csharp_ext.dll" target="/build/native/bin/v120\Win32\Release\grpc_csharp_ext.dll" />
     <file src="output\v100\Win32\Debug\grpc_csharp_ext.dll" target="/build/native/bin/v100\Win32\Debug\grpc_csharp_ext.dll" />
diff --git a/vsprojects/nuget_package/grpc.native.csharp_ext.props b/vsprojects/nuget_package/grpc.native.csharp.props
similarity index 100%
rename from vsprojects/nuget_package/grpc.native.csharp_ext.props
rename to vsprojects/nuget_package/grpc.native.csharp.props
diff --git a/vsprojects/nuget_package/grpc.native.csharp_ext.targets b/vsprojects/nuget_package/grpc.native.csharp.targets
similarity index 100%
rename from vsprojects/nuget_package/grpc.native.csharp_ext.targets
rename to vsprojects/nuget_package/grpc.native.csharp.targets
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj b/vsprojects/vcxproj/grpc/grpc.vcxproj
index 8850bf469d17c541b89cc673d5a2c711cd6cf654..54c2579fd21dd8a1aaa478c08317e8937586af6d 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj
@@ -345,6 +345,7 @@
     <ClInclude Include="..\..\..\src\core\surface\api_trace.h" />
     <ClInclude Include="..\..\..\src\core\surface\byte_buffer_queue.h" />
     <ClInclude Include="..\..\..\src\core\surface\call.h" />
+    <ClInclude Include="..\..\..\src\core\surface\call_test_only.h" />
     <ClInclude Include="..\..\..\src\core\surface\channel.h" />
     <ClInclude Include="..\..\..\src\core\surface\completion_queue.h" />
     <ClInclude Include="..\..\..\src\core\surface\event_string.h" />
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
index 3e8ca92b98899ddbdbe19f6c20a29768261f9132..0fa97b04d600c5442eafa8aafe04e5b514c68c12 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
@@ -749,6 +749,9 @@
     <ClInclude Include="..\..\..\src\core\surface\call.h">
       <Filter>src\core\surface</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\src\core\surface\call_test_only.h">
+      <Filter>src\core\surface</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\..\src\core\surface\channel.h">
       <Filter>src\core\surface</Filter>
     </ClInclude>
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
index 4526c2e332c1057ec7e7fe891c9a7b5d2e6e1629..51752943251a96a3da41456d4508bb150c6f4234 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
@@ -324,6 +324,7 @@
     <ClInclude Include="..\..\..\src\core\surface\api_trace.h" />
     <ClInclude Include="..\..\..\src\core\surface\byte_buffer_queue.h" />
     <ClInclude Include="..\..\..\src\core\surface\call.h" />
+    <ClInclude Include="..\..\..\src\core\surface\call_test_only.h" />
     <ClInclude Include="..\..\..\src\core\surface\channel.h" />
     <ClInclude Include="..\..\..\src\core\surface\completion_queue.h" />
     <ClInclude Include="..\..\..\src\core\surface\event_string.h" />
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
index 8ba784e93384082723eecfd769259efd04becc79..5c856628df789c2d51c2dcd54cb69e07e611264d 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
@@ -647,6 +647,9 @@
     <ClInclude Include="..\..\..\src\core\surface\call.h">
       <Filter>src\core\surface</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\src\core\surface\call_test_only.h">
+      <Filter>src\core\surface</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\..\src\core\surface\channel.h">
       <Filter>src\core\surface</Filter>
     </ClInclude>