diff --git a/BUILD b/BUILD
index 3e2a45b8a0adc19f03b37bf15e1ffdcdbf517476..ffeb31e424b852f5fdbd9a63039f08212306de96 100644
--- a/BUILD
+++ b/BUILD
@@ -223,6 +223,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",
@@ -509,6 +510,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",
@@ -1296,6 +1298,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/build.yaml b/build.yaml
index 98fb0348ca3bc32e78bda06193350b9fa9ff1795..e277b60b77e782c43c76e3710261e7b88a7b9dfa 100644
--- a/build.yaml
+++ b/build.yaml
@@ -183,6 +183,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 717e7005ee5d21208b301835a219ef70eb7cdc77..6378cf0413cde1dc51f3e5dea2fd800d58a576d7 100644
--- a/gRPC.podspec
+++ b/gRPC.podspec
@@ -227,6 +227,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',
@@ -518,6 +519,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/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/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/surface/byte_buffer.c b/src/core/surface/byte_buffer.c
index a930949f2d4690fea58ee987205659cc66c7e1f7..295ef5ab0ee4faa3bcab596f31ad845234f0453e 100644
--- a/src/core/surface/byte_buffer.c
+++ b/src/core/surface/byte_buffer.c
@@ -97,4 +97,5 @@ size_t grpc_byte_buffer_length(grpc_byte_buffer *bb) {
   }
   gpr_log(GPR_ERROR, "should never reach here");
   abort();
+  return 0;
 }
diff --git a/src/core/surface/call.c b/src/core/surface/call.c
index 07c3ff6ae4ec7871e028a0105e35465c01e24a3c..022ca191cd741b797dcbbd71ce05308437a94e40 100644
--- a/src/core/surface/call.c
+++ b/src/core/surface/call.c
@@ -436,6 +436,7 @@ static grpc_cq_completion *allocate_completion(grpc_call *call) {
   }
   gpr_log(GPR_ERROR, "should never reach here");
   abort();
+  return NULL;
 }
 
 static void done_completion(grpc_exec_ctx *exec_ctx, void *call,
@@ -526,9 +527,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 +567,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/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/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/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/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/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 5658a102d75d36deccc0e1aaa99210a32c3253a2..5fd3ee70d635517f8678b6f851a20b98a5333488 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -858,6 +858,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..6e3166ce5799cece3cf8862c0787416d862b34ba 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 \
@@ -70,23 +70,21 @@ docker run \
   -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/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..266edd4375b3479b7b618921d7c8f1fbf52fd449 100755
--- a/tools/run_tests/dockerjob.py
+++ b/tools/run_tests/dockerjob.py
@@ -38,18 +38,15 @@ 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)]) == 0
 
 
 def docker_mapped_port(cid, port):
@@ -92,23 +89,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/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..4d09ae7fcdafc87b2294d19e95d044e31853a848 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
 
 
@@ -75,11 +74,11 @@ class CXXLanguage:
 
   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 {}
@@ -100,17 +99,17 @@ class CSharpLanguage:
 
   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'
@@ -312,23 +311,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)
+    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 +355,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 +370,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)
     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,25 +385,27 @@ 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)
   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
 
 
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index eadf09f192afebb72c08e4e9be382f8ddb4cce19..a3bed4ef8793d7ec9f59b7e4e2dbae536eb5fd89 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -167,7 +167,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 []
@@ -284,7 +287,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 +324,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,
@@ -612,7 +618,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()))
@@ -620,7 +626,7 @@ if make_targets:
   make_commands = itertools.chain.from_iterable(make_jobspec(cfg, list(targets), makefile) for cfg in build_configs for (makefile, targets) in make_targets.iteritems())
   build_steps.extend(set(make_commands))
 build_steps.extend(set(
-                   jobset.JobSpec(cmdline, environ={'CONFIG': cfg})
+                   jobset.JobSpec(cmdline, environ={'CONFIG': cfg}, timeout_seconds=10*60)
                    for cfg in build_configs
                    for l in languages
                    for cmdline in l.build_steps()))
@@ -731,7 +737,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 1ceff15a3be19ad9625bd90a0650f9b07652b58b..76d66ed7a07a81052fe74dc641249df75d26ba0d 100644
--- a/tools/run_tests/sources_and_headers.json
+++ b/tools/run_tests/sources_and_headers.json
@@ -12365,6 +12365,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", 
@@ -12607,6 +12608,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", 
@@ -12861,6 +12863,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", 
@@ -13073,6 +13076,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 183edbc05bf7cd124537104857d68049da4de391..88b883f67cfc519d0c0f8c5697fea7be4671fa7c 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj
@@ -344,6 +344,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 66ce9ca05be6387db553db3a0be7571d201cf373..1a46ac8e34b79036b9be797bb278f767b8fc197a 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
@@ -743,6 +743,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 b527179f9f314935c5bac6aade3c72617646481c..cddd00374b2d2c1d3d80512873e3497c571aa9cc 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
@@ -323,6 +323,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 7be3c9ec93b65d5cfd028d9f04de7f987e01d998..01e0d8daddea8ddac0725b3814bc39fafb7fc68e 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
@@ -641,6 +641,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>