Merge pull request #2873 from jtattermusch/expose_version_string

expose C core version string to C#
diff --git a/Makefile b/Makefile
index 181194f..5c021a7 100644
--- a/Makefile
+++ b/Makefile
@@ -313,7 +313,7 @@
 Q = @
 endif
 
-VERSION = 0.10.0.0
+VERSION = 0.10.1.0
 
 CPPFLAGS_NO_ARCH += $(addprefix -I, $(INCLUDES)) $(addprefix -D, $(DEFINES))
 CPPFLAGS += $(CPPFLAGS_NO_ARCH) $(ARCH_FLAGS)
diff --git a/build.json b/build.json
index 515cecd..bf4c7a2 100644
--- a/build.json
+++ b/build.json
@@ -7,7 +7,7 @@
     "version": {
       "major": 0,
       "minor": 10,
-      "micro": 0,
+      "micro": 1,
       "build": 0
     }
   },
diff --git a/src/core/security/google_default_credentials.c b/src/core/security/google_default_credentials.c
index f368819..d1f2286 100644
--- a/src/core/security/google_default_credentials.c
+++ b/src/core/security/google_default_credentials.c
@@ -84,6 +84,8 @@
   gpr_mu_unlock(GRPC_POLLSET_MU(&detector->pollset));
 }
 
+static void destroy_pollset(void *p) { grpc_pollset_destroy(p); }
+
 static int is_stack_running_on_compute_engine(void) {
   compute_engine_detector detector;
   grpc_httpcli_request request;
@@ -114,12 +116,12 @@
   while (!detector.is_done) {
     grpc_pollset_worker worker;
     grpc_pollset_work(&detector.pollset, &worker,
-                      gpr_inf_future(GPR_CLOCK_REALTIME));
+                      gpr_inf_future(GPR_CLOCK_MONOTONIC));
   }
   gpr_mu_unlock(GRPC_POLLSET_MU(&detector.pollset));
 
   grpc_httpcli_context_destroy(&context);
-  grpc_pollset_destroy(&detector.pollset);
+  grpc_pollset_shutdown(&detector.pollset, destroy_pollset, &detector.pollset);
 
   return detector.success;
 }
diff --git a/src/core/surface/version.c b/src/core/surface/version.c
index 4f5d648..d7aaba3 100644
--- a/src/core/surface/version.c
+++ b/src/core/surface/version.c
@@ -37,5 +37,5 @@
 #include <grpc/grpc.h>
 
 const char *grpc_version_string(void) {
-	return "0.10.0.0";
+	return "0.10.1.0";
 }
diff --git a/src/core/tsi/transport_security_interface.h b/src/core/tsi/transport_security_interface.h
index 936b0c25..e27e6b9 100644
--- a/src/core/tsi/transport_security_interface.h
+++ b/src/core/tsi/transport_security_interface.h
@@ -158,6 +158,8 @@
      value is expected to be at most max_protected_frame_size minus overhead
      which means that max_protected_frame_size is a safe bet. The output value
      is the number of bytes actually written.
+     If *unprotected_bytes_size is unchanged, there may be more data remaining
+     to unprotect, and the caller should call this function again.
 
    - This method returns TSI_OK in case of success. Success includes cases where
      there is not enough data to output a frame in which case
diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
index 64ea218..c5fc85b 100644
--- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
@@ -46,47 +46,18 @@
     public class ClientServerTest
     {
         const string Host = "127.0.0.1";
-        const string ServiceName = "tests.Test";
 
-        static readonly Method<string, string> EchoMethod = new Method<string, string>(
-            MethodType.Unary,
-            ServiceName,
-            "Echo",
-            Marshallers.StringMarshaller,
-            Marshallers.StringMarshaller);
-
-        static readonly Method<string, string> ConcatAndEchoMethod = new Method<string, string>(
-            MethodType.ClientStreaming,
-            ServiceName,
-            "ConcatAndEcho",
-            Marshallers.StringMarshaller,
-            Marshallers.StringMarshaller);
-
-        static readonly Method<string, string> NonexistentMethod = new Method<string, string>(
-            MethodType.Unary,
-            ServiceName,
-            "NonexistentMethod",
-            Marshallers.StringMarshaller,
-            Marshallers.StringMarshaller);
-
-        static readonly ServerServiceDefinition ServiceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName)
-            .AddMethod(EchoMethod, EchoHandler)
-            .AddMethod(ConcatAndEchoMethod, ConcatAndEchoHandler)
-            .Build();
-
+        MockServiceHelper helper;
         Server server;
         Channel channel;
 
         [SetUp]
         public void Init()
         {
-            server = new Server
-            {
-                Services = { ServiceDefinition },
-                Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
-            };
+            helper = new MockServiceHelper(Host);
+            server = helper.GetServer();
             server.Start();
-            channel = new Channel(Host, server.Ports.Single().BoundPort, Credentials.Insecure);
+            channel = helper.GetChannel();
         }
 
         [TearDown]
@@ -103,86 +74,79 @@
         }
 
         [Test]
-        public void UnaryCall()
+        public async Task UnaryCall()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            Assert.AreEqual("ABC", Calls.BlockingUnaryCall(callDetails, "ABC"));
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                return request;
+            });
+
+            Assert.AreEqual("ABC", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "ABC"));
+
+            Assert.AreEqual("ABC", await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "ABC"));
         }
 
         [Test]
         public void UnaryCall_ServerHandlerThrows()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            try
+            helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
             {
-                Calls.BlockingUnaryCall(callDetails, "THROW");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode); 
-            }
+                throw new Exception("This was thrown on purpose by a test");
+            });
+                
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unknown, ex.Status.StatusCode); 
+
+            var ex2 = Assert.Throws<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unknown, ex2.Status.StatusCode);
         }
 
         [Test]
         public void UnaryCall_ServerHandlerThrowsRpcException()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            try
+            helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
             {
-                Calls.BlockingUnaryCall(callDetails, "THROW_UNAUTHENTICATED");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                Assert.AreEqual(StatusCode.Unauthenticated, e.Status.StatusCode);
-            }
+                throw new RpcException(new Status(StatusCode.Unauthenticated, ""));
+            });
+
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
+
+            var ex2 = Assert.Throws<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
         }
 
         [Test]
         public void UnaryCall_ServerHandlerSetsStatus()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            try
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
             {
-                Calls.BlockingUnaryCall(callDetails, "SET_UNAUTHENTICATED");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                Assert.AreEqual(StatusCode.Unauthenticated, e.Status.StatusCode); 
-            }
-        }
+                context.Status = new Status(StatusCode.Unauthenticated, "");
+                return "";
+            });
 
-        [Test]
-        public async Task AsyncUnaryCall()
-        {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            var result = await Calls.AsyncUnaryCall(callDetails, "ABC");
-            Assert.AreEqual("ABC", result);
-        }
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
 
-        [Test]
-        public async Task AsyncUnaryCall_ServerHandlerThrows()
-        {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            try
-            {
-                await Calls.AsyncUnaryCall(callDetails, "THROW");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
-            }
+            var ex2 = Assert.Throws<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
         }
 
         [Test]
         public async Task ClientStreamingCall()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, ConcatAndEchoMethod, new CallOptions());
-            var call = Calls.AsyncClientStreamingCall(callDetails);
+            helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+            {
+                string result = "";
+                await requestStream.ForEach(async (request) =>
+                {
+                    result += request;
+                });
+                await Task.Delay(100);
+                return result;
+            });
 
+            var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall());
             await call.RequestStream.WriteAll(new string[] { "A", "B", "C" });
             Assert.AreEqual("ABC", await call.ResponseAsync);
         }
@@ -190,36 +154,47 @@
         [Test]
         public async Task ClientStreamingCall_CancelAfterBegin()
         {
-            var cts = new CancellationTokenSource();
-            var callDetails = new CallInvocationDetails<string, string>(channel, ConcatAndEchoMethod, new CallOptions(cancellationToken: cts.Token));
-            var call = Calls.AsyncClientStreamingCall(callDetails);
+            var barrier = new TaskCompletionSource<object>();
 
-            // TODO(jtattermusch): we need this to ensure call has been initiated once we cancel it.
-            await Task.Delay(1000);
+            helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+            {
+                barrier.SetResult(null);
+                await requestStream.ToList();
+                return "";
+            });
+
+            var cts = new CancellationTokenSource();
+            var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token)));
+
+            await barrier.Task;  // make sure the handler has started.
             cts.Cancel();
 
-            try
-            {
-                await call.ResponseAsync;
-            }
-            catch (RpcException e)
-            {
-                Assert.AreEqual(StatusCode.Cancelled, e.Status.StatusCode);
-            }
+            var ex = Assert.Throws<RpcException>(async () => await call.ResponseAsync);
+            Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
         }
 
         [Test]
-        public void AsyncUnaryCall_EchoMetadata()
+        public async Task AsyncUnaryCall_EchoMetadata()
         {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                foreach (Metadata.Entry metadataEntry in context.RequestHeaders)
+                {
+                    if (metadataEntry.Key != "user-agent")
+                    {
+                        context.ResponseTrailers.Add(metadataEntry);
+                    }
+                }
+                return "";
+            });
+
             var headers = new Metadata
             {
-                new Metadata.Entry("ascii-header", "abcdefg"),
-                new Metadata.Entry("binary-header-bin", new byte[] { 1, 2, 3, 0, 0xff }),
+                { "ascii-header", "abcdefg" },
+                { "binary-header-bin", new byte[] { 1, 2, 3, 0, 0xff } }
             };
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions(headers: headers));
-            var call = Calls.AsyncUnaryCall(callDetails, "ABC");
-
-            Assert.AreEqual("ABC", call.ResponseAsync.Result);
+            var call = Calls.AsyncUnaryCall(helper.CreateUnaryCall(new CallOptions(headers: headers)), "ABC");
+            await call;
 
             Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
 
@@ -236,15 +211,18 @@
         public void UnaryCall_DisposedChannel()
         {
             channel.Dispose();
-
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(callDetails, "ABC"));
+            Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "ABC"));
         }
 
         [Test]
         public void UnaryCallPerformance()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                return request;
+            });
+
+            var callDetails = helper.CreateUnaryCall();
             BenchmarkUtil.RunBenchmark(100, 100,
                                        () => { Calls.BlockingUnaryCall(callDetails, "ABC"); });
         }
@@ -252,44 +230,57 @@
         [Test]
         public void UnknownMethodHandler()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, NonexistentMethod, new CallOptions());
-            try
-            {
-                Calls.BlockingUnaryCall(callDetails, "ABC");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                Assert.AreEqual(StatusCode.Unimplemented, e.Status.StatusCode);
-            }
+            var nonexistentMethod = new Method<string, string>(
+                MethodType.Unary,
+                MockServiceHelper.ServiceName,
+                "NonExistentMethod",
+                Marshallers.StringMarshaller,
+                Marshallers.StringMarshaller);
+
+            var callDetails = new CallInvocationDetails<string, string>(channel, nonexistentMethod, new CallOptions());
+
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(callDetails, "abc"));
+            Assert.AreEqual(StatusCode.Unimplemented, ex.Status.StatusCode);
         }
 
         [Test]
         public void UserAgentStringPresent()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            string userAgent = Calls.BlockingUnaryCall(callDetails, "RETURN-USER-AGENT");
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                return context.RequestHeaders.Where(entry => entry.Key == "user-agent").Single().Value;
+            });
+
+            string userAgent = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc");
             Assert.IsTrue(userAgent.StartsWith("grpc-csharp/"));
         }
 
         [Test]
         public void PeerInfoPresent()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            string peer = Calls.BlockingUnaryCall(callDetails, "RETURN-PEER");
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                return context.Peer;
+            });
+
+            string peer = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc");
             Assert.IsTrue(peer.Contains(Host));
         }
 
         [Test]
         public async Task Channel_WaitForStateChangedAsync()
         {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                return request;
+            });
+
             Assert.Throws(typeof(TaskCanceledException), 
                 async () => await channel.WaitForStateChangedAsync(channel.State, DateTime.UtcNow.AddMilliseconds(10)));
 
             var stateChangedTask = channel.WaitForStateChangedAsync(channel.State);
 
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            await Calls.AsyncUnaryCall(callDetails, "abc");
+            await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc");
 
             await stateChangedTask;
             Assert.AreEqual(ChannelState.Ready, channel.State);
@@ -300,62 +291,9 @@
         {
             await channel.ConnectAsync();
             Assert.AreEqual(ChannelState.Ready, channel.State);
+
             await channel.ConnectAsync(DateTime.UtcNow.AddMilliseconds(1000));
             Assert.AreEqual(ChannelState.Ready, channel.State);
         }
-
-        private static async Task<string> EchoHandler(string request, ServerCallContext context)
-        {
-            foreach (Metadata.Entry metadataEntry in context.RequestHeaders)
-            {
-                if (metadataEntry.Key != "user-agent")
-                {
-                    context.ResponseTrailers.Add(metadataEntry);
-                }
-            }
-
-            if (request == "RETURN-USER-AGENT")
-            {
-                return context.RequestHeaders.Where(entry => entry.Key == "user-agent").Single().Value;
-            }
-
-            if (request == "RETURN-PEER")
-            {
-                return context.Peer;
-            }
-
-            if (request == "THROW")
-            {
-                throw new Exception("This was thrown on purpose by a test");
-            }
-
-            if (request == "THROW_UNAUTHENTICATED")
-            {
-                throw new RpcException(new Status(StatusCode.Unauthenticated, ""));
-            }
-
-            if (request == "SET_UNAUTHENTICATED")
-            {
-                context.Status = new Status(StatusCode.Unauthenticated, "");
-            }
-
-            return request;
-        }
-
-        private static async Task<string> ConcatAndEchoHandler(IAsyncStreamReader<string> requestStream, ServerCallContext context)
-        {
-            string result = "";
-            await requestStream.ForEach(async (request) =>
-            {
-                if (request == "THROW")
-                {
-                    throw new Exception("This was thrown on purpose by a test");
-                }
-                result += request;
-            });
-            // simulate processing takes some time.
-            await Task.Delay(250);
-            return result;
-        }
     }
 }
diff --git a/src/csharp/Grpc.Core.Tests/CompressionTest.cs b/src/csharp/Grpc.Core.Tests/CompressionTest.cs
new file mode 100644
index 0000000..ac0c3d6
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/CompressionTest.cs
@@ -0,0 +1,128 @@
+#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.Linq;
+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 CompressionTest
+    {
+        MockServiceHelper helper;
+        Server server;
+        Channel channel;
+
+        [SetUp]
+        public void Init()
+        {
+            helper = new MockServiceHelper();
+
+            server = helper.GetServer();
+            server.Start();
+            channel = helper.GetChannel();
+        }
+
+        [TearDown]
+        public void Cleanup()
+        {
+            channel.Dispose();
+            server.ShutdownAsync().Wait();
+        }
+
+        [TestFixtureTearDown]
+        public void CleanupClass()
+        {
+            GrpcEnvironment.Shutdown();
+        }
+
+        [Test]
+        public void WriteOptions_Unary()
+        {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                context.WriteOptions = new WriteOptions(WriteFlags.NoCompress);
+                return request;
+            });
+
+            var callOptions = new CallOptions(writeOptions: new WriteOptions(WriteFlags.NoCompress));
+            Calls.BlockingUnaryCall(helper.CreateUnaryCall(callOptions), "abc");
+        }
+
+        [Test]
+        public async Task WriteOptions_DuplexStreaming()
+        {
+            helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
+            {
+                await requestStream.ToList();
+
+                context.WriteOptions = new WriteOptions(WriteFlags.NoCompress);
+
+                await context.WriteResponseHeadersAsync(new Metadata { { "ascii-header", "abcdefg" } });
+
+                await responseStream.WriteAsync("X");
+
+                responseStream.WriteOptions = null;
+                await responseStream.WriteAsync("Y");
+
+                responseStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress);
+                await responseStream.WriteAsync("Z");
+            });
+
+            var callOptions = new CallOptions(writeOptions: new WriteOptions(WriteFlags.NoCompress));
+            var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall(callOptions));
+
+            // check that write options from call options are propagated to request stream.
+            Assert.IsTrue((call.RequestStream.WriteOptions.Flags & WriteFlags.NoCompress) != 0);
+
+            call.RequestStream.WriteOptions = new WriteOptions();
+            await call.RequestStream.WriteAsync("A");
+
+            call.RequestStream.WriteOptions = null;
+            await call.RequestStream.WriteAsync("B");
+
+            call.RequestStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress);
+            await call.RequestStream.WriteAsync("C");
+
+            await call.RequestStream.CompleteAsync();
+
+            await call.ResponseStream.ToList();
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
new file mode 100644
index 0000000..a7f5075
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
@@ -0,0 +1,122 @@
+#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.Linq;
+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 ContextPropagationTest
+    {
+        MockServiceHelper helper;
+        Server server;
+        Channel channel;
+
+        [SetUp]
+        public void Init()
+        {
+            helper = new MockServiceHelper();
+
+            server = helper.GetServer();
+            server.Start();
+            channel = helper.GetChannel();
+        }
+
+        [TearDown]
+        public void Cleanup()
+        {
+            channel.Dispose();
+            server.ShutdownAsync().Wait();
+        }
+
+        [TestFixtureTearDown]
+        public void CleanupClass()
+        {
+            GrpcEnvironment.Shutdown();
+        }
+
+        [Test]
+        public async Task PropagateCancellation()
+        {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                // check that we didn't obtain the default cancellation token.
+                Assert.IsTrue(context.CancellationToken.CanBeCanceled);
+                return "PASS";
+            });
+
+            helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+            {
+                var propagationToken = context.CreatePropagationToken();
+                Assert.IsNotNull(propagationToken.ParentCall);
+
+                var callOptions = new CallOptions(propagationToken: propagationToken);
+                return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz");
+            });
+                
+            var cts = new CancellationTokenSource();
+            var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token)));
+            await call.RequestStream.CompleteAsync();
+            Assert.AreEqual("PASS", await call);
+        }
+
+        [Test]
+        public async Task PropagateDeadline()
+        {
+            var deadline = DateTime.UtcNow.AddDays(7);
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                Assert.IsTrue(context.Deadline < deadline.AddMinutes(1));
+                Assert.IsTrue(context.Deadline > deadline.AddMinutes(-1));
+                return "PASS";
+            });
+
+            helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+            {
+                var callOptions = new CallOptions(propagationToken: context.CreatePropagationToken());
+                return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz");
+            });
+                
+            var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(deadline: deadline)));
+            await call.RequestStream.CompleteAsync();
+            Assert.AreEqual("PASS", await call);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
index f2bf459..97ee045 100644
--- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
+++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
@@ -77,6 +77,10 @@
     <Compile Include="TimeoutsTest.cs" />
     <Compile Include="NUnitVersionTest.cs" />
     <Compile Include="ChannelTest.cs" />
+    <Compile Include="MockServiceHelper.cs" />
+    <Compile Include="ResponseHeadersTest.cs" />
+    <Compile Include="CompressionTest.cs" />
+    <Compile Include="ContextPropagationTest.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>
diff --git a/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs b/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs
index 4646911..33534fd 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs
@@ -53,8 +53,8 @@
         {
             var metadata = new Metadata
             {
-                new Metadata.Entry("host", "somehost"),
-                new Metadata.Entry("header2", "header value"),
+                { "host", "somehost" },
+                { "header2", "header value" },
             };
             var nativeMetadata = MetadataArraySafeHandle.Create(metadata);
             nativeMetadata.Dispose();
@@ -65,8 +65,8 @@
         {
             var metadata = new Metadata
             {
-                new Metadata.Entry("host", "somehost"),
-                new Metadata.Entry("header2", "header value"),
+                { "host", "somehost" },
+                { "header2", "header value" }
             };
             var nativeMetadata = MetadataArraySafeHandle.Create(metadata);
 
diff --git a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
new file mode 100644
index 0000000..b642286
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
@@ -0,0 +1,248 @@
+#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.Linq;
+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
+{
+    /// <summary>
+    /// Allows setting up a mock service in the client-server tests easily.
+    /// </summary>
+    public class MockServiceHelper
+    {
+        public const string ServiceName = "tests.Test";
+
+        public static readonly Method<string, string> UnaryMethod = new Method<string, string>(
+            MethodType.Unary,
+            ServiceName,
+            "Unary",
+            Marshallers.StringMarshaller,
+            Marshallers.StringMarshaller);
+
+        public static readonly Method<string, string> ClientStreamingMethod = new Method<string, string>(
+            MethodType.ClientStreaming,
+            ServiceName,
+            "ClientStreaming",
+            Marshallers.StringMarshaller,
+            Marshallers.StringMarshaller);
+
+        public static readonly Method<string, string> ServerStreamingMethod = new Method<string, string>(
+            MethodType.ServerStreaming,
+            ServiceName,
+            "ServerStreaming",
+            Marshallers.StringMarshaller,
+            Marshallers.StringMarshaller);
+
+        public static readonly Method<string, string> DuplexStreamingMethod = new Method<string, string>(
+            MethodType.DuplexStreaming,
+            ServiceName,
+            "DuplexStreaming",
+            Marshallers.StringMarshaller,
+            Marshallers.StringMarshaller);
+
+        readonly string host;
+        readonly ServerServiceDefinition serviceDefinition;
+
+        UnaryServerMethod<string, string> unaryHandler;
+        ClientStreamingServerMethod<string, string> clientStreamingHandler;
+        ServerStreamingServerMethod<string, string> serverStreamingHandler;
+        DuplexStreamingServerMethod<string, string> duplexStreamingHandler;
+
+        Server server;
+        Channel channel;
+
+        public MockServiceHelper(string host = null)
+        {
+            this.host = host ?? "localhost";
+
+            serviceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName)
+                .AddMethod(UnaryMethod, (request, context) => unaryHandler(request, context))
+                .AddMethod(ClientStreamingMethod, (requestStream, context) => clientStreamingHandler(requestStream, context))
+                .AddMethod(ServerStreamingMethod, (request, responseStream, context) => serverStreamingHandler(request, responseStream, context))
+                .AddMethod(DuplexStreamingMethod, (requestStream, responseStream, context) => duplexStreamingHandler(requestStream, responseStream, context))
+                .Build();
+
+            var defaultStatus = new Status(StatusCode.Unknown, "Default mock implementation. Please provide your own.");
+
+            unaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                context.Status = defaultStatus;
+                return "";
+            });
+
+            clientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+            {
+                context.Status = defaultStatus;
+                return "";
+            });
+
+            serverStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
+            {
+                context.Status = defaultStatus;
+            });
+
+            duplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
+            {
+                context.Status = defaultStatus;
+            });
+        }
+
+        /// <summary>
+        /// Returns the default server for this service and creates one if not yet created.
+        /// </summary>
+        public Server GetServer()
+        {
+            if (server == null)
+            {
+                server = new Server
+                {
+                    Services = { serviceDefinition },
+                    Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
+                };
+            }
+            return server;
+        }
+
+        /// <summary>
+        /// Returns the default channel for this service and creates one if not yet created.
+        /// </summary>
+        public Channel GetChannel()
+        {
+            if (channel == null)
+            {
+                channel = new Channel(Host, GetServer().Ports.Single().BoundPort, Credentials.Insecure);
+            }
+            return channel;
+        }
+
+        public CallInvocationDetails<string, string> CreateUnaryCall(CallOptions options = null)
+        {
+            options = options ?? new CallOptions();
+            return new CallInvocationDetails<string, string>(channel, UnaryMethod, options);
+        }
+
+        public CallInvocationDetails<string, string> CreateClientStreamingCall(CallOptions options = null)
+        {
+            options = options ?? new CallOptions();
+            return new CallInvocationDetails<string, string>(channel, ClientStreamingMethod, options);
+        }
+
+        public CallInvocationDetails<string, string> CreateServerStreamingCall(CallOptions options = null)
+        {
+            options = options ?? new CallOptions();
+            return new CallInvocationDetails<string, string>(channel, ServerStreamingMethod, options);
+        }
+
+        public CallInvocationDetails<string, string> CreateDuplexStreamingCall(CallOptions options = null)
+        {
+            options = options ?? new CallOptions();
+            return new CallInvocationDetails<string, string>(channel, DuplexStreamingMethod, options);
+        }
+
+        public string Host
+        {
+            get
+            {
+                return this.host;
+            }
+        }
+
+        public ServerServiceDefinition ServiceDefinition
+        {
+            get
+            {
+                return this.serviceDefinition;
+            }
+        }
+      
+        public UnaryServerMethod<string, string> UnaryHandler
+        {
+            get
+            {
+                return this.unaryHandler;
+            }
+
+            set
+            {
+                unaryHandler = value;
+            }
+        }
+
+        public ClientStreamingServerMethod<string, string> ClientStreamingHandler
+        {
+            get
+            {
+                return this.clientStreamingHandler;
+            }
+
+            set
+            {
+                clientStreamingHandler = value;
+            }
+        }
+
+        public ServerStreamingServerMethod<string, string> ServerStreamingHandler
+        {
+            get
+            {
+                return this.serverStreamingHandler;
+            }
+
+            set
+            {
+                serverStreamingHandler = value;
+            }
+        }
+
+        public DuplexStreamingServerMethod<string, string> DuplexStreamingHandler
+        {
+            get
+            {
+                return this.duplexStreamingHandler;
+            }
+
+            set
+            {
+                duplexStreamingHandler = value;
+            }
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs b/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs
new file mode 100644
index 0000000..8925041
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs
@@ -0,0 +1,136 @@
+#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.Linq;
+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
+{
+    /// <summary>
+    /// Tests for response headers support.
+    /// </summary>
+    public class ResponseHeadersTest
+    {
+        MockServiceHelper helper;
+        Server server;
+        Channel channel;
+
+        Metadata headers;
+
+        [SetUp]
+        public void Init()
+        {
+            helper = new MockServiceHelper();
+
+            server = helper.GetServer();
+            server.Start();
+            channel = helper.GetChannel();
+
+            headers = new Metadata { { "ascii-header", "abcdefg" } };
+        }
+
+        [TearDown]
+        public void Cleanup()
+        {
+            channel.Dispose();
+            server.ShutdownAsync().Wait();
+        }
+
+        [TestFixtureTearDown]
+        public void CleanupClass()
+        {
+            GrpcEnvironment.Shutdown();
+        }
+
+        [Test]
+        public void WriteResponseHeaders_NullNotAllowed()
+        {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                Assert.Throws(typeof(NullReferenceException), async () => await context.WriteResponseHeadersAsync(null));
+                return "PASS";
+            });
+
+            Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), ""));
+        }
+
+        [Test]
+        public void WriteResponseHeaders_AllowedOnlyOnce()
+        {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                await context.WriteResponseHeadersAsync(headers);
+                try
+                {
+                    await context.WriteResponseHeadersAsync(headers);
+                    Assert.Fail();
+                }
+                catch (InvalidOperationException expected)
+                {
+                }
+                return "PASS";
+            });
+                
+            Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), ""));
+        }
+
+        [Test]
+        public async Task WriteResponseHeaders_NotAllowedAfterWrite()
+        {
+            helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
+            {
+                await responseStream.WriteAsync("A");
+                try
+                {
+                    await context.WriteResponseHeadersAsync(headers);
+                    Assert.Fail();
+                }
+                catch (InvalidOperationException expected)
+                {
+                }
+                await responseStream.WriteAsync("B");
+            });
+
+            var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "");
+            var responses = await call.ResponseStream.ToList();
+            CollectionAssert.AreEqual(new[] { "A", "B" }, responses);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs b/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
index fc395b0..d875d60 100644
--- a/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
+++ b/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
@@ -48,38 +48,18 @@
     /// </summary>
     public class TimeoutsTest
     {
-        const string Host = "localhost";
-        const string ServiceName = "tests.Test";
-
-        static readonly Method<string, string> TestMethod = new Method<string, string>(
-            MethodType.Unary,
-            ServiceName,
-            "Test",
-            Marshallers.StringMarshaller,
-            Marshallers.StringMarshaller);
-
-        static readonly ServerServiceDefinition ServiceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName)
-            .AddMethod(TestMethod, TestMethodHandler)
-            .Build();
-
-        // provides a way how to retrieve an out-of-band result value from server handler
-        static TaskCompletionSource<string> stringFromServerHandlerTcs;
-
+        MockServiceHelper helper;
         Server server;
         Channel channel;
 
         [SetUp]
         public void Init()
         {
-            server = new Server
-            {
-                Services = { ServiceDefinition },
-                Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
-            };
-            server.Start();
-            channel = new Channel(Host, server.Ports.Single().BoundPort, Credentials.Insecure);
+            helper = new MockServiceHelper();
 
-            stringFromServerHandlerTcs = new TaskCompletionSource<string>();
+            server = helper.GetServer();
+            server.Start();
+            channel = helper.GetChannel();
         }
 
         [TearDown]
@@ -98,115 +78,83 @@
         [Test]
         public void InfiniteDeadline()
         {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                Assert.AreEqual(DateTime.MaxValue, context.Deadline);
+                return "PASS";
+            });
+
             // no deadline specified, check server sees infinite deadline
-            var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions());
-            Assert.AreEqual("DATETIME_MAXVALUE", Calls.BlockingUnaryCall(callDetails, "RETURN_DEADLINE"));
+            Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
 
             // DateTime.MaxValue deadline specified, check server sees infinite deadline
-            var callDetails2 = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions());
-            Assert.AreEqual("DATETIME_MAXVALUE", Calls.BlockingUnaryCall(callDetails2, "RETURN_DEADLINE"));
+            Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.MaxValue)), "abc"));
         }
 
         [Test]
         public void DeadlineTransferredToServer()
         {
-            var remainingTimeClient = TimeSpan.FromDays(7);
-            var deadline = DateTime.UtcNow + remainingTimeClient;
-            Thread.Sleep(1000);
-            var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: deadline));
+            var clientDeadline = DateTime.UtcNow + TimeSpan.FromDays(7);
 
-            var serverDeadlineTicksString = Calls.BlockingUnaryCall(callDetails, "RETURN_DEADLINE");
-            var serverDeadline = new DateTime(long.Parse(serverDeadlineTicksString), DateTimeKind.Utc);
-
-            // A fairly relaxed check that the deadline set by client and deadline seen by server
-            // are in agreement. C core takes care of the work with transferring deadline over the wire,
-            // so we don't need an exact check here.
-            Assert.IsTrue(Math.Abs((deadline - serverDeadline).TotalMilliseconds) < 5000);
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                // A fairly relaxed check that the deadline set by client and deadline seen by server
+                // are in agreement. C core takes care of the work with transferring deadline over the wire,
+                // so we don't need an exact check here.
+                Assert.IsTrue(Math.Abs((clientDeadline - context.Deadline).TotalMilliseconds) < 5000);
+                return "PASS";
+            });
+            Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: clientDeadline)), "abc");
         }
 
         [Test]
         public void DeadlineInThePast()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: DateTime.MinValue));
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                await Task.Delay(60000);
+                return "FAIL";
+            });
 
-            try
-            {
-                Calls.BlockingUnaryCall(callDetails, "TIMEOUT");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
-                Assert.Contains(e.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
-            }
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.MinValue)), "abc"));
+            // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
+            Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
         }
 
         [Test]
         public void DeadlineExceededStatusOnTimeout()
         {
-            var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
-            var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: deadline));
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                await Task.Delay(60000);
+                return "FAIL";
+            });
 
-            try
-            {
-                Calls.BlockingUnaryCall(callDetails, "TIMEOUT");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
-                Assert.Contains(e.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
-            }
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)))), "abc"));
+            // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
+            Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
         }
 
         [Test]
-        public void ServerReceivesCancellationOnTimeout()
+        public async Task ServerReceivesCancellationOnTimeout()
         {
-            var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
-            var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: deadline));
+            var serverReceivedCancellationTcs = new TaskCompletionSource<bool>();
 
-            try
-            {
-                Calls.BlockingUnaryCall(callDetails, "CHECK_CANCELLATION_RECEIVED");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                // We can't guarantee the status code is always DeadlineExceeded. See issue #2685.
-                Assert.Contains(e.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
-            }
-            Assert.AreEqual("CANCELLED", stringFromServerHandlerTcs.Task.Result);
-        }
-            
-        private static async Task<string> TestMethodHandler(string request, ServerCallContext context)
-        {
-            if (request == "TIMEOUT")
-            {
-                await Task.Delay(60000);
-                return "";
-            }
-
-            if (request == "RETURN_DEADLINE")
-            {
-                if (context.Deadline == DateTime.MaxValue)
-                {
-                    return "DATETIME_MAXVALUE";
-                }
-
-                return context.Deadline.Ticks.ToString();
-            }
-
-            if (request == "CHECK_CANCELLATION_RECEIVED")
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => 
             {
                 // wait until cancellation token is fired.
                 var tcs = new TaskCompletionSource<object>();
                 context.CancellationToken.Register(() => { tcs.SetResult(null); });
                 await tcs.Task;
-                stringFromServerHandlerTcs.SetResult("CANCELLED");
+                serverReceivedCancellationTcs.SetResult(true);
                 return "";
-            }
+            });
 
-            return "";
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)))), "abc"));
+            // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
+            Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
+
+            Assert.IsTrue(await serverReceivedCancellationTcs.Task);
         }
     }
 }
diff --git a/src/csharp/Grpc.Core/CallOptions.cs b/src/csharp/Grpc.Core/CallOptions.cs
index 8e97393..0d82b5a 100644
--- a/src/csharp/Grpc.Core/CallOptions.cs
+++ b/src/csharp/Grpc.Core/CallOptions.cs
@@ -47,6 +47,8 @@
         readonly Metadata headers;
         readonly DateTime deadline;
         readonly CancellationToken cancellationToken;
+        readonly WriteOptions writeOptions;
+        readonly ContextPropagationToken propagationToken;
 
         /// <summary>
         /// Creates a new instance of <c>CallOptions</c>.
@@ -54,12 +56,17 @@
         /// <param name="headers">Headers to be sent with the call.</param>
         /// <param name="deadline">Deadline for the call to finish. null means no deadline.</param>
         /// <param name="cancellationToken">Can be used to request cancellation of the call.</param>
-        public CallOptions(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+        /// <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>
+        public CallOptions(Metadata headers = null, DateTime? deadline = null, CancellationToken? cancellationToken = null,
+                           WriteOptions writeOptions = null, ContextPropagationToken propagationToken = null)
         {
             // TODO(jtattermusch): consider only creating metadata object once it's really needed.
-            this.headers = headers != null ? headers : new Metadata();
-            this.deadline = deadline.HasValue ? deadline.Value : DateTime.MaxValue;
-            this.cancellationToken = cancellationToken;
+            this.headers = headers ?? new Metadata();
+            this.deadline = deadline ?? (propagationToken != null ? propagationToken.Deadline : DateTime.MaxValue);
+            this.cancellationToken = cancellationToken ?? (propagationToken != null ? propagationToken.CancellationToken : CancellationToken.None);
+            this.writeOptions = writeOptions;
+            this.propagationToken = propagationToken;
         }
 
         /// <summary>
@@ -85,5 +92,27 @@
         {
             get { return cancellationToken; }
         }
+
+        /// <summary>
+        /// Write options that will be used for this call.
+        /// </summary>
+        public WriteOptions WriteOptions
+        {
+            get
+            {
+                return this.writeOptions;
+            }
+        }
+
+        /// <summary>
+        /// Token for propagating parent call context.
+        /// </summary>
+        public ContextPropagationToken PropagationToken
+        {
+            get
+            {
+                return this.propagationToken;
+            }
+        }
     }
 }
diff --git a/src/csharp/Grpc.Core/CompressionLevel.cs b/src/csharp/Grpc.Core/CompressionLevel.cs
new file mode 100644
index 0000000..399652b
--- /dev/null
+++ b/src/csharp/Grpc.Core/CompressionLevel.cs
@@ -0,0 +1,63 @@
+#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;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Compression level based on grpc_compression_level from grpc/compression.h
+    /// </summary>
+    public enum CompressionLevel
+    {
+        /// <summary>
+        /// No compression.
+        /// </summary>
+        None = 0,
+
+        /// <summary>
+        /// Low compression.
+        /// </summary>
+        Low,
+
+        /// <summary>
+        /// Medium compression.
+        /// </summary>
+        Medium,
+
+        /// <summary>
+        /// High compression.
+        /// </summary>
+        High,
+    }
+}
diff --git a/src/csharp/Grpc.Core/ContextPropagationToken.cs b/src/csharp/Grpc.Core/ContextPropagationToken.cs
new file mode 100644
index 0000000..b6ea511
--- /dev/null
+++ b/src/csharp/Grpc.Core/ContextPropagationToken.cs
@@ -0,0 +1,139 @@
+#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 Grpc.Core.Internal;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Token for propagating context of server side handlers to child calls.
+    /// In situations when a backend is making calls to another backend,
+    /// it makes sense to propagate properties like deadline and cancellation 
+    /// token of the server call to the child call.
+    /// C core provides some other contexts (like tracing context) that
+    /// are not accessible to C# layer, but this token still allows propagating them.
+    /// </summary>
+    public class ContextPropagationToken
+    {
+        /// <summary>
+        /// Default propagation mask used by C core.
+        /// </summary>
+        const ContextPropagationFlags DefaultCoreMask = (ContextPropagationFlags)0xffff;
+
+        /// <summary>
+        /// Default propagation mask used by C# - we want to propagate deadline 
+        /// and cancellation token by our own means.
+        /// </summary>
+        internal const ContextPropagationFlags DefaultMask = DefaultCoreMask
+            & ~ContextPropagationFlags.Deadline & ~ContextPropagationFlags.Cancellation;
+
+        readonly CallSafeHandle parentCall;
+        readonly DateTime deadline;
+        readonly CancellationToken cancellationToken;
+        readonly ContextPropagationOptions options;
+
+        internal ContextPropagationToken(CallSafeHandle parentCall, DateTime deadline, CancellationToken cancellationToken, ContextPropagationOptions options)
+        {
+            this.parentCall = Preconditions.CheckNotNull(parentCall);
+            this.deadline = deadline;
+            this.cancellationToken = cancellationToken;
+            this.options = options ?? ContextPropagationOptions.Default;
+        }
+
+        internal CallSafeHandle ParentCall
+        {
+            get
+            {
+                return this.parentCall;
+            }
+        }
+
+        internal DateTime Deadline
+        {
+            get
+            {
+                return this.deadline;
+            }
+        }
+
+        internal CancellationToken CancellationToken
+        {
+            get
+            {
+                return this.cancellationToken;
+            }
+        }
+
+        internal ContextPropagationOptions Options
+        {
+            get
+            {
+                return this.options;
+            }
+        }
+
+        internal bool IsPropagateDeadline
+        {
+            get { return false; }
+        }
+
+        internal bool IsPropagateCancellation
+        {
+            get { return false; }
+        }
+    }
+
+    /// <summary>
+    /// Options for <see cref="ContextPropagationToken"/>.
+    /// </summary>
+    public class ContextPropagationOptions
+    {
+        public static readonly ContextPropagationOptions Default = new ContextPropagationOptions();
+    }
+
+    /// <summary>
+    /// Context propagation flags from grpc/grpc.h.
+    /// </summary>
+    [Flags]
+    internal enum ContextPropagationFlags
+    {
+        Deadline = 1,
+        CensusStatsContext = 2,
+        CensusTracingContext = 4,
+        Cancellation = 8
+    }
+}
diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj
index 52defd1..e535c47 100644
--- a/src/csharp/Grpc.Core/Grpc.Core.csproj
+++ b/src/csharp/Grpc.Core/Grpc.Core.csproj
@@ -115,6 +115,9 @@
     <Compile Include="ChannelState.cs" />
     <Compile Include="CallInvocationDetails.cs" />
     <Compile Include="CallOptions.cs" />
+    <Compile Include="CompressionLevel.cs" />
+    <Compile Include="WriteOptions.cs" />
+    <Compile Include="ContextPropagationToken.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="Grpc.Core.nuspec" />
diff --git a/src/csharp/Grpc.Core/IAsyncStreamWriter.cs b/src/csharp/Grpc.Core/IAsyncStreamWriter.cs
index 2000210..4e2acb9 100644
--- a/src/csharp/Grpc.Core/IAsyncStreamWriter.cs
+++ b/src/csharp/Grpc.Core/IAsyncStreamWriter.cs
@@ -50,5 +50,13 @@
         /// </summary>
         /// <param name="message">the message to be written. Cannot be null.</param>
         Task WriteAsync(T message);
+
+        /// <summary>
+        /// Write options that will be used for the next write.
+        /// If null, default options will be used.
+        /// Once set, this property maintains its value across subsequent
+        /// writes.
+        /// <value>The write options.</value>
+        WriteOptions WriteOptions { get; set; }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
index 414b5c4..0db9d2a 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
@@ -50,7 +50,7 @@
     {
         static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<AsyncCall<TRequest, TResponse>>();
 
-        readonly CallInvocationDetails<TRequest, TResponse> callDetails;
+        readonly CallInvocationDetails<TRequest, TResponse> details;
 
         // Completion of a pending unary response if not null.
         TaskCompletionSource<TResponse> unaryResponseTcs;
@@ -63,7 +63,8 @@
         public AsyncCall(CallInvocationDetails<TRequest, TResponse> callDetails)
             : base(callDetails.RequestMarshaller.Serializer, callDetails.ResponseMarshaller.Deserializer)
         {
-            this.callDetails = callDetails;
+            this.details = callDetails;
+            this.initialMetadataSent = true;  // we always send metadata at the very beginning of the call.
         }
 
         // TODO: this method is not Async, so it shouldn't be in AsyncCall class, but 
@@ -89,11 +90,11 @@
                     readingDone = true;
                 }
 
-                using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers))
+                using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
                 {
                     using (var ctx = BatchContextSafeHandle.Create())
                     {
-                        call.StartUnary(payload, ctx, metadataArray);
+                        call.StartUnary(ctx, payload, metadataArray, GetWriteFlagsForCall());
                         var ev = cq.Pluck(ctx.Handle);
 
                         bool success = (ev.success != 0);
@@ -130,7 +131,7 @@
                 Preconditions.CheckState(!started);
                 started = true;
 
-                Initialize(callDetails.Channel.Environment.CompletionQueue);
+                Initialize(details.Channel.Environment.CompletionQueue);
 
                 halfcloseRequested = true;
                 readingDone = true;
@@ -138,9 +139,9 @@
                 byte[] payload = UnsafeSerialize(msg);
 
                 unaryResponseTcs = new TaskCompletionSource<TResponse>();
-                using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers))
+                using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
                 {
-                    call.StartUnary(payload, HandleUnaryResponse, metadataArray);
+                    call.StartUnary(HandleUnaryResponse, payload, metadataArray, GetWriteFlagsForCall());
                 }
                 return unaryResponseTcs.Task;
             }
@@ -157,12 +158,12 @@
                 Preconditions.CheckState(!started);
                 started = true;
 
-                Initialize(callDetails.Channel.Environment.CompletionQueue);
+                Initialize(details.Channel.Environment.CompletionQueue);
 
                 readingDone = true;
 
                 unaryResponseTcs = new TaskCompletionSource<TResponse>();
-                using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers))
+                using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
                 {
                     call.StartClientStreaming(HandleUnaryResponse, metadataArray);
                 }
@@ -181,16 +182,16 @@
                 Preconditions.CheckState(!started);
                 started = true;
 
-                Initialize(callDetails.Channel.Environment.CompletionQueue);
+                Initialize(details.Channel.Environment.CompletionQueue);
 
                 halfcloseRequested = true;
                 halfclosed = true;  // halfclose not confirmed yet, but it will be once finishedHandler is called.
 
                 byte[] payload = UnsafeSerialize(msg);
 
-                using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers))
+                using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
                 {
-                    call.StartServerStreaming(payload, HandleFinished, metadataArray);
+                    call.StartServerStreaming(HandleFinished, payload, metadataArray, GetWriteFlagsForCall());
                 }
             }
         }
@@ -206,9 +207,9 @@
                 Preconditions.CheckState(!started);
                 started = true;
 
-                Initialize(callDetails.Channel.Environment.CompletionQueue);
+                Initialize(details.Channel.Environment.CompletionQueue);
 
-                using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers))
+                using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
                 {
                     call.StartDuplexStreaming(HandleFinished, metadataArray);
                 }
@@ -219,9 +220,9 @@
         /// Sends a streaming request. Only one pending send action is allowed at any given time.
         /// completionDelegate is called when the operation finishes.
         /// </summary>
-        public void StartSendMessage(TRequest msg, AsyncCompletionDelegate<object> completionDelegate)
+        public void StartSendMessage(TRequest msg, WriteFlags writeFlags, AsyncCompletionDelegate<object> completionDelegate)
         {
-            StartSendMessageInternal(msg, completionDelegate);
+            StartSendMessageInternal(msg, writeFlags, completionDelegate);
         }
 
         /// <summary>
@@ -278,6 +279,14 @@
             }
         }
 
+        public CallInvocationDetails<TRequest, TResponse> Details
+        {
+            get
+            {
+                return this.details;
+            }
+        }
+
         /// <summary>
         /// On client-side, we only fire readCompletionDelegate once all messages have been read 
         /// and status has been received.
@@ -310,14 +319,18 @@
 
         protected override void OnReleaseResources()
         {
-            callDetails.Channel.Environment.DebugStats.ActiveClientCalls.Decrement();
+            details.Channel.Environment.DebugStats.ActiveClientCalls.Decrement();
         }
 
         private void Initialize(CompletionQueueSafeHandle cq)
         {
-            var call = callDetails.Channel.Handle.CreateCall(callDetails.Channel.Environment.CompletionRegistry, cq,
-                callDetails.Method, callDetails.Host, Timespec.FromDateTime(callDetails.Options.Deadline));
-            callDetails.Channel.Environment.DebugStats.ActiveClientCalls.Increment();
+            var propagationToken = details.Options.PropagationToken;
+            var parentCall = propagationToken != null ? propagationToken.ParentCall : CallSafeHandle.NullInstance;
+
+            var call = details.Channel.Handle.CreateCall(details.Channel.Environment.CompletionRegistry,
+                parentCall, ContextPropagationToken.DefaultMask, cq,
+                details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline));
+            details.Channel.Environment.DebugStats.ActiveClientCalls.Increment();
             InitializeInternal(call);
             RegisterCancellationCallback();
         }
@@ -325,7 +338,7 @@
         // Make sure that once cancellationToken for this call is cancelled, Cancel() will be called.
         private void RegisterCancellationCallback()
         {
-            var token = callDetails.Options.CancellationToken;
+            var token = details.Options.CancellationToken;
             if (token.CanBeCanceled)
             {
                 token.Register(() => this.Cancel());
@@ -333,6 +346,15 @@
         }
 
         /// <summary>
+        /// Gets WriteFlags set in callDetails.Options.WriteOptions
+        /// </summary>
+        private WriteFlags GetWriteFlagsForCall()
+        {
+            var writeOptions = details.Options.WriteOptions;
+            return writeOptions != null ? writeOptions.Flags : default(WriteFlags);
+        }
+
+        /// <summary>
         /// Handler for unary response completion.
         /// </summary>
         private void HandleUnaryResponse(bool success, BatchContextSafeHandle ctx)
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
index 38f2a5b..9fa0bac 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
@@ -71,6 +71,9 @@
         protected bool halfclosed;
         protected bool finished;  // True if close has been received from the peer.
 
+        protected bool initialMetadataSent;
+        protected long streamingWritesCounter;
+
         public AsyncCallBase(Func<TWrite, byte[]> serializer, Func<byte[], TRead> deserializer)
         {
             this.serializer = Preconditions.CheckNotNull(serializer);
@@ -123,7 +126,7 @@
         /// Initiates sending a message. Only one send operation can be active at a time.
         /// completionDelegate is invoked upon completion.
         /// </summary>
-        protected void StartSendMessageInternal(TWrite msg, AsyncCompletionDelegate<object> completionDelegate)
+        protected void StartSendMessageInternal(TWrite msg, WriteFlags writeFlags, AsyncCompletionDelegate<object> completionDelegate)
         {
             byte[] payload = UnsafeSerialize(msg);
 
@@ -132,8 +135,11 @@
                 Preconditions.CheckNotNull(completionDelegate, "Completion delegate cannot be null");
                 CheckSendingAllowed();
 
-                call.StartSendMessage(payload, HandleSendFinished);
+                call.StartSendMessage(HandleSendFinished, payload, writeFlags, !initialMetadataSent);
+
                 sendCompletionDelegate = completionDelegate;
+                initialMetadataSent = true;
+                streamingWritesCounter++;
             }
         }
 
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
index 513902e..3710a65 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
@@ -83,9 +83,9 @@
         /// Sends a streaming response. Only one pending send action is allowed at any given time.
         /// completionDelegate is called when the operation finishes.
         /// </summary>
-        public void StartSendMessage(TResponse msg, AsyncCompletionDelegate<object> completionDelegate)
+        public void StartSendMessage(TResponse msg, WriteFlags writeFlags, AsyncCompletionDelegate<object> completionDelegate)
         {
-            StartSendMessageInternal(msg, completionDelegate);
+            StartSendMessageInternal(msg, writeFlags, completionDelegate);
         }
 
         /// <summary>
@@ -98,6 +98,35 @@
         }
 
         /// <summary>
+        /// Initiates sending a initial metadata. 
+        /// Even though C-core allows sending metadata in parallel to sending messages, we will treat sending metadata as a send message operation
+        /// to make things simpler.
+        /// completionDelegate is invoked upon completion.
+        /// </summary>
+        public void StartSendInitialMetadata(Metadata headers, AsyncCompletionDelegate<object> completionDelegate)
+        {
+            lock (myLock)
+            {
+                Preconditions.CheckNotNull(headers, "metadata");
+                Preconditions.CheckNotNull(completionDelegate, "Completion delegate cannot be null");
+
+                Preconditions.CheckState(!initialMetadataSent, "Response headers can only be sent once per call.");
+                Preconditions.CheckState(streamingWritesCounter == 0, "Response headers can only be sent before the first write starts.");
+                CheckSendingAllowed();
+
+                Preconditions.CheckNotNull(completionDelegate, "Completion delegate cannot be null");
+
+                using (var metadataArray = MetadataArraySafeHandle.Create(headers))
+                {
+                    call.StartSendInitialMetadata(HandleSendFinished, metadataArray);
+                }
+
+                this.initialMetadataSent = true;
+                sendCompletionDelegate = completionDelegate;
+            }
+        }
+
+        /// <summary>
         /// Sends call result status, also indicating server is done with streaming responses.
         /// Only one pending send action is allowed at any given time.
         /// completionDelegate is called when the operation finishes.
@@ -111,7 +140,7 @@
 
                 using (var metadataArray = MetadataArraySafeHandle.Create(trailers))
                 {
-                    call.StartSendStatusFromServer(status, HandleHalfclosed, metadataArray);
+                    call.StartSendStatusFromServer(HandleHalfclosed, status, metadataArray, !initialMetadataSent);
                 }
                 halfcloseRequested = true;
                 readingDone = true;
diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
index 714749b..3cb01e2 100644
--- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
@@ -42,6 +42,8 @@
     /// </summary>
     internal class CallSafeHandle : SafeHandleZeroIsInvalid
     {
+        public static readonly CallSafeHandle NullInstance = new CallSafeHandle();
+
         const uint GRPC_WRITE_BUFFER_HINT = 1;
         CompletionRegistry completionRegistry;
 
@@ -53,7 +55,7 @@
 
         [DllImport("grpc_csharp_ext.dll")]
         static extern GRPCCallError grpcsharp_call_start_unary(CallSafeHandle call,
-            BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, MetadataArraySafeHandle metadataArray);
+            BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags);
 
         [DllImport("grpc_csharp_ext.dll")]
         static extern GRPCCallError grpcsharp_call_start_client_streaming(CallSafeHandle call,
@@ -62,7 +64,7 @@
         [DllImport("grpc_csharp_ext.dll")]
         static extern GRPCCallError grpcsharp_call_start_server_streaming(CallSafeHandle call,
             BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len,
-            MetadataArraySafeHandle metadataArray);
+            MetadataArraySafeHandle metadataArray, WriteFlags writeFlags);
 
         [DllImport("grpc_csharp_ext.dll")]
         static extern GRPCCallError grpcsharp_call_start_duplex_streaming(CallSafeHandle call,
@@ -70,7 +72,7 @@
 
         [DllImport("grpc_csharp_ext.dll")]
         static extern GRPCCallError grpcsharp_call_send_message(CallSafeHandle call,
-            BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len);
+            BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, WriteFlags writeFlags, bool sendEmptyInitialMetadata);
 
         [DllImport("grpc_csharp_ext.dll")]
         static extern GRPCCallError grpcsharp_call_send_close_from_client(CallSafeHandle call,
@@ -78,7 +80,7 @@
 
         [DllImport("grpc_csharp_ext.dll")]
         static extern GRPCCallError grpcsharp_call_send_status_from_server(CallSafeHandle call, 
-            BatchContextSafeHandle ctx, StatusCode statusCode, string statusMessage, MetadataArraySafeHandle metadataArray);
+            BatchContextSafeHandle ctx, StatusCode statusCode, string statusMessage, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata);
 
         [DllImport("grpc_csharp_ext.dll")]
         static extern GRPCCallError grpcsharp_call_recv_message(CallSafeHandle call,
@@ -89,6 +91,10 @@
             BatchContextSafeHandle ctx);
 
         [DllImport("grpc_csharp_ext.dll")]
+        static extern GRPCCallError grpcsharp_call_send_initial_metadata(CallSafeHandle call,
+            BatchContextSafeHandle ctx, MetadataArraySafeHandle metadataArray);
+
+        [DllImport("grpc_csharp_ext.dll")]
         static extern CStringSafeHandle grpcsharp_call_get_peer(CallSafeHandle call);
 
         [DllImport("grpc_csharp_ext.dll")]
@@ -103,17 +109,17 @@
             this.completionRegistry = completionRegistry;
         }
 
-        public void StartUnary(byte[] payload, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
+        public void StartUnary(BatchCompletionDelegate callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
         {
             var ctx = BatchContextSafeHandle.Create();
             completionRegistry.RegisterBatchCompletion(ctx, callback);
-            grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray)
+            grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags)
                 .CheckOk();
         }
 
-        public void StartUnary(byte[] payload, BatchContextSafeHandle ctx, MetadataArraySafeHandle metadataArray)
+        public void StartUnary(BatchContextSafeHandle ctx, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
         {
-            grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray)
+            grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags)
                 .CheckOk();
         }
 
@@ -124,11 +130,11 @@
             grpcsharp_call_start_client_streaming(this, ctx, metadataArray).CheckOk();
         }
 
-        public void StartServerStreaming(byte[] payload, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
+        public void StartServerStreaming(BatchCompletionDelegate callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
         {
             var ctx = BatchContextSafeHandle.Create();
             completionRegistry.RegisterBatchCompletion(ctx, callback);
-            grpcsharp_call_start_server_streaming(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray).CheckOk();
+            grpcsharp_call_start_server_streaming(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags).CheckOk();
         }
 
         public void StartDuplexStreaming(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
@@ -138,11 +144,11 @@
             grpcsharp_call_start_duplex_streaming(this, ctx, metadataArray).CheckOk();
         }
 
-        public void StartSendMessage(byte[] payload, BatchCompletionDelegate callback)
+        public void StartSendMessage(BatchCompletionDelegate callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata)
         {
             var ctx = BatchContextSafeHandle.Create();
             completionRegistry.RegisterBatchCompletion(ctx, callback);
-            grpcsharp_call_send_message(this, ctx, payload, new UIntPtr((ulong)payload.Length)).CheckOk();
+            grpcsharp_call_send_message(this, ctx, payload, new UIntPtr((ulong)payload.Length), writeFlags, sendEmptyInitialMetadata).CheckOk();
         }
 
         public void StartSendCloseFromClient(BatchCompletionDelegate callback)
@@ -152,11 +158,11 @@
             grpcsharp_call_send_close_from_client(this, ctx).CheckOk();
         }
 
-        public void StartSendStatusFromServer(Status status, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
+        public void StartSendStatusFromServer(BatchCompletionDelegate callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata)
         {
             var ctx = BatchContextSafeHandle.Create();
             completionRegistry.RegisterBatchCompletion(ctx, callback);
-            grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail, metadataArray).CheckOk();
+            grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail, metadataArray, sendEmptyInitialMetadata).CheckOk();
         }
 
         public void StartReceiveMessage(BatchCompletionDelegate callback)
@@ -173,6 +179,13 @@
             grpcsharp_call_start_serverside(this, ctx).CheckOk();
         }
 
+        public void StartSendInitialMetadata(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
+        {
+            var ctx = BatchContextSafeHandle.Create();
+            completionRegistry.RegisterBatchCompletion(ctx, callback);
+            grpcsharp_call_send_initial_metadata(this, ctx, metadataArray).CheckOk();
+        }
+
         public void Cancel()
         {
             grpcsharp_call_cancel(this).CheckOk();
diff --git a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
index 7324ebd..7f03bf4 100644
--- a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
@@ -47,7 +47,7 @@
         static extern ChannelSafeHandle grpcsharp_secure_channel_create(CredentialsSafeHandle credentials, string target, ChannelArgsSafeHandle channelArgs);
 
         [DllImport("grpc_csharp_ext.dll")]
-        static extern CallSafeHandle grpcsharp_channel_create_call(ChannelSafeHandle channel, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline);
+        static extern CallSafeHandle grpcsharp_channel_create_call(ChannelSafeHandle channel, CallSafeHandle parentCall, ContextPropagationFlags propagationMask, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline);
 
         [DllImport("grpc_csharp_ext.dll")]
         static extern ChannelState grpcsharp_channel_check_connectivity_state(ChannelSafeHandle channel, int tryToConnect);
@@ -76,9 +76,9 @@
             return grpcsharp_secure_channel_create(credentials, target, channelArgs);
         }
 
-        public CallSafeHandle CreateCall(CompletionRegistry registry, 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)
         {
-            var result = grpcsharp_channel_create_call(this, cq, method, host, deadline);
+            var result = grpcsharp_channel_create_call(this, parentCall, propagationMask, cq, method, host, deadline);
             result.SetCompletionRegistry(registry);
             return result;
         }
diff --git a/src/csharp/Grpc.Core/Internal/ClientRequestStream.cs b/src/csharp/Grpc.Core/Internal/ClientRequestStream.cs
index 58f4934..013f00f 100644
--- a/src/csharp/Grpc.Core/Internal/ClientRequestStream.cs
+++ b/src/csharp/Grpc.Core/Internal/ClientRequestStream.cs
@@ -40,16 +40,18 @@
     internal class ClientRequestStream<TRequest, TResponse> : IClientStreamWriter<TRequest>
     {
         readonly AsyncCall<TRequest, TResponse> call;
+        WriteOptions writeOptions;
 
         public ClientRequestStream(AsyncCall<TRequest, TResponse> call)
         {
             this.call = call;
+            this.writeOptions = call.Details.Options.WriteOptions;
         }
 
         public Task WriteAsync(TRequest message)
         {
             var taskSource = new AsyncCompletionTaskSource<object>();
-            call.StartSendMessage(message, taskSource.CompletionDelegate);
+            call.StartSendMessage(message, GetWriteFlags(), taskSource.CompletionDelegate);
             return taskSource.Task;
         }
 
@@ -59,5 +61,24 @@
             call.StartSendCloseFromClient(taskSource.CompletionDelegate);
             return taskSource.Task;
         }
+
+        public WriteOptions WriteOptions
+        {
+            get
+            {
+                return this.writeOptions;
+            }
+
+            set
+            {
+                writeOptions = value;
+            }
+        }
+
+        private WriteFlags GetWriteFlags()
+        {
+            var options = writeOptions;
+            return options != null ? options.Flags : default(WriteFlags);
+        }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
index 19f0e3c..688f9f6 100644
--- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
@@ -75,7 +75,7 @@
             var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall);
 
             Status status;
-            var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, asyncCall.CancellationToken);
+            var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
             try
             {
                 Preconditions.CheckArgument(await requestStream.MoveNext());
@@ -131,7 +131,7 @@
             var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall);
 
             Status status;
-            var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, asyncCall.CancellationToken);
+            var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
             try
             {
                 Preconditions.CheckArgument(await requestStream.MoveNext());
@@ -187,7 +187,7 @@
             var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall);
 
             Status status;
-            var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, asyncCall.CancellationToken);
+            var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
             try
             {
                 var result = await handler(requestStream, context);
@@ -247,7 +247,7 @@
             var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall);
 
             Status status;
-            var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, asyncCall.CancellationToken);
+            var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
             try
             {
                 await handler(requestStream, responseStream, context);
@@ -304,13 +304,14 @@
             return new Status(StatusCode.Unknown, "Exception was thrown by handler.");
         }
 
-        public static ServerCallContext NewContext(ServerRpcNew newRpc, string peer, CancellationToken cancellationToken)
+        public static ServerCallContext NewContext<TRequest, TResponse>(ServerRpcNew newRpc, string peer, ServerResponseStream<TRequest, TResponse> serverResponseStream, CancellationToken cancellationToken)
+            where TRequest : class
+            where TResponse : class
         {
             DateTime realtimeDeadline = newRpc.Deadline.ToClockType(GPRClockType.Realtime).ToDateTime();
 
-            return new ServerCallContext(
-                newRpc.Method, newRpc.Host, peer, realtimeDeadline,
-                newRpc.RequestMetadata, cancellationToken);
+            return new ServerCallContext(newRpc.Call, newRpc.Method, newRpc.Host, peer, realtimeDeadline,
+                newRpc.RequestMetadata, cancellationToken, serverResponseStream.WriteResponseHeadersAsync, serverResponseStream);
         }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs b/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs
index 756dcee..03e39ef 100644
--- a/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs
@@ -38,11 +38,12 @@
     /// <summary>
     /// Writes responses asynchronously to an underlying AsyncCallServer object.
     /// </summary>
-    internal class ServerResponseStream<TRequest, TResponse> : IServerStreamWriter<TResponse>
+    internal class ServerResponseStream<TRequest, TResponse> : IServerStreamWriter<TResponse>, IHasWriteOptions
         where TRequest : class
         where TResponse : class
     {
         readonly AsyncCallServer<TRequest, TResponse> call;
+        WriteOptions writeOptions;
 
         public ServerResponseStream(AsyncCallServer<TRequest, TResponse> call)
         {
@@ -52,7 +53,7 @@
         public Task WriteAsync(TResponse message)
         {
             var taskSource = new AsyncCompletionTaskSource<object>();
-            call.StartSendMessage(message, taskSource.CompletionDelegate);
+            call.StartSendMessage(message, GetWriteFlags(), taskSource.CompletionDelegate);
             return taskSource.Task;
         }
 
@@ -62,5 +63,31 @@
             call.StartSendStatusFromServer(status, trailers, taskSource.CompletionDelegate);
             return taskSource.Task;
         }
+
+        public Task WriteResponseHeadersAsync(Metadata responseHeaders)
+        {
+            var taskSource = new AsyncCompletionTaskSource<object>();
+            call.StartSendInitialMetadata(responseHeaders, taskSource.CompletionDelegate);
+            return taskSource.Task;
+        }
+
+        public WriteOptions WriteOptions
+        {
+            get
+            {
+                return writeOptions;
+            }
+
+            set
+            {
+                writeOptions = value;
+            }
+        }
+
+        private WriteFlags GetWriteFlags()
+        {
+            var options = writeOptions;
+            return options != null ? options.Flags : default(WriteFlags);
+        }
     }
 }
diff --git a/src/csharp/Grpc.Core/Metadata.cs b/src/csharp/Grpc.Core/Metadata.cs
index 6fd0a71..a58dbdb 100644
--- a/src/csharp/Grpc.Core/Metadata.cs
+++ b/src/csharp/Grpc.Core/Metadata.cs
@@ -114,6 +114,16 @@
             entries.Add(item);
         }
 
+        public void Add(string key, string value)
+        {
+            Add(new Entry(key, value));
+        }
+
+        public void Add(string key, byte[] valueBytes)
+        {
+            Add(new Entry(key, valueBytes));
+        }
+
         public void Clear()
         {
             CheckWriteable();
diff --git a/src/csharp/Grpc.Core/ServerCallContext.cs b/src/csharp/Grpc.Core/ServerCallContext.cs
index 032b139..75d81c6 100644
--- a/src/csharp/Grpc.Core/ServerCallContext.cs
+++ b/src/csharp/Grpc.Core/ServerCallContext.cs
@@ -36,15 +36,16 @@
 using System.Threading;
 using System.Threading.Tasks;
 
+using Grpc.Core.Internal;
+
 namespace Grpc.Core
 {
     /// <summary>
     /// Context for a server-side call.
     /// </summary>
-    public sealed class ServerCallContext
+    public class ServerCallContext
     {
-        // TODO(jtattermusch): expose method to send initial metadata back to client
-
+        private readonly CallSafeHandle callHandle;
         private readonly string method;
         private readonly string host;
         private readonly string peer;
@@ -54,15 +55,34 @@
         private readonly Metadata responseTrailers = new Metadata();
 
         private Status status = Status.DefaultSuccess;
+        private Func<Metadata, Task> writeHeadersFunc;
+        private IHasWriteOptions writeOptionsHolder;
 
-        public ServerCallContext(string method, string host, string peer, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken)
+        internal ServerCallContext(CallSafeHandle callHandle, string method, string host, string peer, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken,
+            Func<Metadata, Task> writeHeadersFunc, IHasWriteOptions writeOptionsHolder)
         {
+            this.callHandle = callHandle;
             this.method = method;
             this.host = host;
             this.peer = peer;
             this.deadline = deadline;
             this.requestHeaders = requestHeaders;
             this.cancellationToken = cancellationToken;
+            this.writeHeadersFunc = writeHeadersFunc;
+            this.writeOptionsHolder = writeOptionsHolder;
+        }
+
+        public Task WriteResponseHeadersAsync(Metadata responseHeaders)
+        {
+            return writeHeadersFunc(responseHeaders);
+        }
+
+        /// <summary>
+        /// Creates a propagation token to be used to propagate call context to a child call.
+        /// </summary>
+        public ContextPropagationToken CreatePropagationToken(ContextPropagationOptions options = null)
+        {
+            return new ContextPropagationToken(callHandle, deadline, cancellationToken, options);
         }
             
         /// <summary>Name of method called in this RPC.</summary>
@@ -110,7 +130,7 @@
             }
         }
 
-        ///<summary>Cancellation token signals when call is cancelled.</summary>
+        /// <summary>Cancellation token signals when call is cancelled.</summary>
         public CancellationToken CancellationToken
         {
             get
@@ -141,5 +161,31 @@
                 status = value;
             }
         }
+
+        /// <summary>
+        /// Allows setting write options for the following write.
+        /// For streaming response calls, this property is also exposed as on IServerStreamWriter for convenience.
+        /// Both properties are backed by the same underlying value.
+        /// </summary>
+        public WriteOptions WriteOptions
+        {
+            get
+            {
+                return writeOptionsHolder.WriteOptions;
+            }
+
+            set
+            {
+                writeOptionsHolder.WriteOptions = value;
+            }
+        }
+    }
+
+    /// <summary>
+    /// Allows sharing write options between ServerCallContext and other objects.
+    /// </summary>
+    public interface IHasWriteOptions
+    {
+        WriteOptions WriteOptions { get; set; }
     }
 }
diff --git a/src/csharp/Grpc.Core/VersionInfo.cs b/src/csharp/Grpc.Core/VersionInfo.cs
index 656a3d4..939372e 100644
--- a/src/csharp/Grpc.Core/VersionInfo.cs
+++ b/src/csharp/Grpc.Core/VersionInfo.cs
@@ -8,6 +8,6 @@
         /// <summary>
         /// Current version of gRPC
         /// </summary>
-        public const string CurrentVersion = "0.6.0";
+        public const string CurrentVersion = "0.6.1";
     }
 }
diff --git a/src/csharp/Grpc.Core/WriteOptions.cs b/src/csharp/Grpc.Core/WriteOptions.cs
new file mode 100644
index 0000000..7ef3189
--- /dev/null
+++ b/src/csharp/Grpc.Core/WriteOptions.cs
@@ -0,0 +1,82 @@
+#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;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Flags for write operations.
+    /// </summary>
+    [Flags]
+    public enum WriteFlags
+    {
+        /// <summary>
+        /// Hint that the write may be buffered and need not go out on the wire immediately.
+        /// gRPC is free to buffer the message until the next non-buffered
+        /// write, or until write stream completion, but it need not buffer completely or at all.
+        /// </summary>
+        BufferHint = 0x1,
+
+        /// <summary>
+        /// Force compression to be disabled for a particular write.
+        /// </summary>
+        NoCompress = 0x2
+    }
+
+    /// <summary>
+    /// Options for write operations.
+    /// </summary>
+    public class WriteOptions
+    {
+        /// <summary>
+        /// Default write options.
+        /// </summary>
+        public static readonly WriteOptions Default = new WriteOptions();
+            
+        private WriteFlags flags;
+
+        public WriteOptions(WriteFlags flags = default(WriteFlags))
+        {
+            this.flags = flags;
+        }
+
+        public WriteFlags Flags
+        {
+            get
+            {
+                return this.flags;
+            }
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
index 08aece7..73d2a1c 100644
--- a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
+++ b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
@@ -92,15 +92,8 @@
         [Test]
         public void DivByZero()
         {
-            try
-            {
-                DivReply response = client.Div(new DivArgs.Builder { Dividend = 0, Divisor = 0 }.Build());
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
-            }   
+            var ex = Assert.Throws<RpcException>(() => client.Div(new DivArgs.Builder { Dividend = 0, Divisor = 0 }.Build()));
+            Assert.AreEqual(StatusCode.Unknown, ex.Status.StatusCode);
         }
 
         [Test]
@@ -158,15 +151,10 @@
             using (var call = client.Fib(new FibArgs.Builder { Limit = 0 }.Build(), 
                 deadline: DateTime.UtcNow.AddMilliseconds(500)))
             {
-                try
-                {
-                    await call.ResponseStream.ToList();
-                    Assert.Fail();
-                }
-                catch (RpcException e)
-                {
-                    Assert.AreEqual(StatusCode.DeadlineExceeded, e.Status.StatusCode);
-                }
+                var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.ToList());
+
+                // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
+                Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
             }
         }
 
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
index 7411d91..6802de4 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
@@ -404,15 +404,8 @@
                 await Task.Delay(1000);
                 cts.Cancel();
 
-                try
-                {
-                    var response = await call.ResponseAsync;
-                    Assert.Fail();
-                }
-                catch (RpcException e)
-                {
-                    Assert.AreEqual(StatusCode.Cancelled, e.Status.StatusCode);
-                }
+                var ex = Assert.Throws<RpcException>(async () => await call.ResponseAsync);
+                Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
             }
             Console.WriteLine("Passed!");
         }
@@ -435,15 +428,8 @@
 
                 cts.Cancel();
 
-                try
-                {
-                    await call.ResponseStream.MoveNext();
-                    Assert.Fail();
-                }
-                catch (RpcException e)
-                {
-                    Assert.AreEqual(StatusCode.Cancelled, e.Status.StatusCode);
-                }
+                var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.MoveNext());
+                Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
             }
             Console.WriteLine("Passed!");
         }
diff --git a/src/csharp/build_packages.bat b/src/csharp/build_packages.bat
index 9e1253b..8a11d01 100644
--- a/src/csharp/build_packages.bat
+++ b/src/csharp/build_packages.bat
@@ -1,8 +1,8 @@
 @rem Builds gRPC NuGet packages
 
 @rem Current package versions
-set VERSION=0.6.0
-set CORE_VERSION=0.10.0
+set VERSION=0.6.1
+set CORE_VERSION=0.10.1
 
 @rem Adjust the location of nuget.exe
 set NUGET=C:\nuget\nuget.exe
diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c
index 0eb2365..9379ae0 100644
--- a/src/csharp/ext/grpc_csharp_ext.c
+++ b/src/csharp/ext/grpc_csharp_ext.c
@@ -376,10 +376,12 @@
 }
 
 GPR_EXPORT grpc_call *GPR_CALLTYPE
-grpcsharp_channel_create_call(grpc_channel *channel, grpc_completion_queue *cq,
+grpcsharp_channel_create_call(grpc_channel *channel, grpc_call *parent_call,
+                              gpr_uint32 propagation_mask,
+                              grpc_completion_queue *cq,
                               const char *method, const char *host,
                               gpr_timespec deadline) {
-  return grpc_channel_create_call(channel, NULL, GRPC_PROPAGATE_DEFAULTS, cq,
+  return grpc_channel_create_call(channel, parent_call, propagation_mask, cq,
                                   method, host, deadline);
 }
 
@@ -497,7 +499,7 @@
 GPR_EXPORT grpc_call_error GPR_CALLTYPE
 grpcsharp_call_start_unary(grpc_call *call, grpcsharp_batch_context *ctx,
                            const char *send_buffer, size_t send_buffer_len,
-                           grpc_metadata_array *initial_metadata) {
+                           grpc_metadata_array *initial_metadata, gpr_uint32 write_flags) {
   /* TODO: don't use magic number */
   grpc_op ops[6];
   ops[0].op = GRPC_OP_SEND_INITIAL_METADATA;
@@ -511,7 +513,7 @@
   ops[1].op = GRPC_OP_SEND_MESSAGE;
   ctx->send_message = string_to_byte_buffer(send_buffer, send_buffer_len);
   ops[1].data.send_message = ctx->send_message;
-  ops[1].flags = 0;
+  ops[1].flags = write_flags;
 
   ops[2].op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   ops[2].flags = 0;
@@ -578,7 +580,7 @@
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_server_streaming(
     grpc_call *call, grpcsharp_batch_context *ctx, const char *send_buffer,
-    size_t send_buffer_len, grpc_metadata_array *initial_metadata) {
+    size_t send_buffer_len, grpc_metadata_array *initial_metadata, gpr_uint32 write_flags) {
   /* TODO: don't use magic number */
   grpc_op ops[5];
   ops[0].op = GRPC_OP_SEND_INITIAL_METADATA;
@@ -592,7 +594,7 @@
   ops[1].op = GRPC_OP_SEND_MESSAGE;
   ctx->send_message = string_to_byte_buffer(send_buffer, send_buffer_len);
   ops[1].data.send_message = ctx->send_message;
-  ops[1].flags = 0;
+  ops[1].flags = write_flags;
 
   ops[2].op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   ops[2].flags = 0;
@@ -651,15 +653,22 @@
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE
 grpcsharp_call_send_message(grpc_call *call, grpcsharp_batch_context *ctx,
-                            const char *send_buffer, size_t send_buffer_len) {
+                            const char *send_buffer, size_t send_buffer_len,
+                            gpr_uint32 write_flags,
+                            gpr_int32 send_empty_initial_metadata) {
   /* TODO: don't use magic number */
-  grpc_op ops[1];
+  grpc_op ops[2];
+  size_t nops = send_empty_initial_metadata ? 2 : 1;
   ops[0].op = GRPC_OP_SEND_MESSAGE;
   ctx->send_message = string_to_byte_buffer(send_buffer, send_buffer_len);
   ops[0].data.send_message = ctx->send_message;
-  ops[0].flags = 0;
+  ops[0].flags = write_flags;
+  ops[1].op = GRPC_OP_SEND_INITIAL_METADATA;
+  ops[1].data.send_initial_metadata.count = 0;
+  ops[1].data.send_initial_metadata.metadata = NULL;
+  ops[1].flags = 0;
 
-  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx);
+  return grpc_call_start_batch(call, ops, nops, ctx);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE
@@ -675,9 +684,11 @@
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_send_status_from_server(
     grpc_call *call, grpcsharp_batch_context *ctx, grpc_status_code status_code,
-    const char *status_details, grpc_metadata_array *trailing_metadata) {
+    const char *status_details, grpc_metadata_array *trailing_metadata,
+    gpr_int32 send_empty_initial_metadata) {
   /* TODO: don't use magic number */
-  grpc_op ops[1];
+  grpc_op ops[2];
+  size_t nops = send_empty_initial_metadata ? 2 : 1;
   ops[0].op = GRPC_OP_SEND_STATUS_FROM_SERVER;
   ops[0].data.send_status_from_server.status = status_code;
   ops[0].data.send_status_from_server.status_details =
@@ -689,8 +700,12 @@
   ops[0].data.send_status_from_server.trailing_metadata =
       ctx->send_status_from_server.trailing_metadata.metadata;
   ops[0].flags = 0;
+  ops[1].op = GRPC_OP_SEND_INITIAL_METADATA;
+  ops[1].data.send_initial_metadata.count = 0;
+  ops[1].data.send_initial_metadata.metadata = NULL;
+  ops[1].flags = 0;
 
-  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx);
+  return grpc_call_start_batch(call, ops, nops, ctx);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE
@@ -706,16 +721,28 @@
 GPR_EXPORT grpc_call_error GPR_CALLTYPE
 grpcsharp_call_start_serverside(grpc_call *call, grpcsharp_batch_context *ctx) {
   /* TODO: don't use magic number */
-  grpc_op ops[2];
-  ops[0].op = GRPC_OP_SEND_INITIAL_METADATA;
-  ops[0].data.send_initial_metadata.count = 0;
-  ops[0].data.send_initial_metadata.metadata = NULL;
+  grpc_op ops[1];
+  ops[0].op = GRPC_OP_RECV_CLOSE_ON_SERVER;
+  ops[0].data.recv_close_on_server.cancelled =
+      (&ctx->recv_close_on_server_cancelled);
   ops[0].flags = 0;
 
-  ops[1].op = GRPC_OP_RECV_CLOSE_ON_SERVER;
-  ops[1].data.recv_close_on_server.cancelled =
-      (&ctx->recv_close_on_server_cancelled);
-  ops[1].flags = 0;
+  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx);
+}
+
+GPR_EXPORT grpc_call_error GPR_CALLTYPE
+grpcsharp_call_send_initial_metadata(grpc_call *call,
+                                     grpcsharp_batch_context *ctx,
+                                     grpc_metadata_array *initial_metadata) {
+  /* TODO: don't use magic number */
+  grpc_op ops[1];
+  ops[0].op = GRPC_OP_SEND_INITIAL_METADATA;
+  grpcsharp_metadata_array_move(&(ctx->send_initial_metadata),
+                                initial_metadata);
+  ops[0].data.send_initial_metadata.count = ctx->send_initial_metadata.count;
+  ops[0].data.send_initial_metadata.metadata =
+      ctx->send_initial_metadata.metadata;
+  ops[0].flags = 0;
 
   return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx);
 }
diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannel.m b/src/objective-c/GRPCClient/private/GRPCSecureChannel.m
index 9b4b676..0a54804 100644
--- a/src/objective-c/GRPCClient/private/GRPCSecureChannel.m
+++ b/src/objective-c/GRPCClient/private/GRPCSecureChannel.m
@@ -38,15 +38,18 @@
 // Returns NULL if the file at path couldn't be read. In that case, if errorPtr isn't NULL,
 // *errorPtr will be an object describing what went wrong.
 static grpc_credentials *CertificatesAtPath(NSString *path, NSError **errorPtr) {
-  NSString *certsContent = [NSString stringWithContentsOfFile:path
-                                                     encoding:NSASCIIStringEncoding
+  // Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the
+  // issuer). Load them as UTF8 and produce an ASCII equivalent.
+  NSString *contentInUTF8 = [NSString stringWithContentsOfFile:path
+                                                     encoding:NSUTF8StringEncoding
                                                         error:errorPtr];
-  if (!certsContent) {
+  NSData *contentInASCII = [contentInUTF8 dataUsingEncoding:NSASCIIStringEncoding
+                                       allowLossyConversion:YES];
+  if (!contentInASCII.bytes) {
     // Passing NULL to grpc_ssl_credentials_create produces behavior we don't want, so return.
     return NULL;
   }
-  const char * asCString = [certsContent cStringUsingEncoding:NSASCIIStringEncoding];
-  return grpc_ssl_credentials_create(asCString, NULL);
+  return grpc_ssl_credentials_create(contentInASCII.bytes, NULL);
 }
 
 @implementation GRPCSecureChannel
diff --git a/src/objective-c/tests/GRPCClientTests.m b/src/objective-c/tests/GRPCClientTests.m
index e85dd6e..f231029 100644
--- a/src/objective-c/tests/GRPCClientTests.m
+++ b/src/objective-c/tests/GRPCClientTests.m
@@ -114,7 +114,7 @@
 
   [call startWithWriteable:responsesWriteable];
 
-  [self waitForExpectationsWithTimeout:4 handler:nil];
+  [self waitForExpectationsWithTimeout:8 handler:nil];
 }
 
 - (void)testSimpleProtoRPC {
@@ -146,7 +146,7 @@
 
   [call startWithWriteable:responsesWriteable];
 
-  [self waitForExpectationsWithTimeout:4 handler:nil];
+  [self waitForExpectationsWithTimeout:8 handler:nil];
 }
 
 - (void)testMetadata {
diff --git a/src/objective-c/tests/InteropTests.h b/src/objective-c/tests/InteropTests.h
index 4eb97e9..1045c3d 100644
--- a/src/objective-c/tests/InteropTests.h
+++ b/src/objective-c/tests/InteropTests.h
@@ -37,8 +37,7 @@
 // https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md
 
 @interface InteropTests : XCTestCase
-// Returns @"localhost:5050".
+// Returns @"grpc-test.sandbox.google.com".
 // Override in a subclass to perform the same tests against a different address.
-// For interop tests, use @"grpc-test.sandbox.google.com".
 + (NSString *)host;
 @end
diff --git a/src/objective-c/tests/InteropTests.m b/src/objective-c/tests/InteropTests.m
index b61d567..1b63fe2 100644
--- a/src/objective-c/tests/InteropTests.m
+++ b/src/objective-c/tests/InteropTests.m
@@ -78,20 +78,17 @@
 
 #pragma mark Tests
 
-static NSString * const kLocalCleartextHost = @"localhost:5050";
+static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.google.com";
 
 @implementation InteropTests {
   RMTTestService *_service;
 }
 
 + (NSString *)host {
-  return kLocalCleartextHost;
+  return kRemoteSSLHost;
 }
 
 - (void)setUp {
-  // Register test server as non-SSL.
-  [GRPCCall useInsecureConnectionsForHost:kLocalCleartextHost];
-
   _service = [[RMTTestService alloc] initWithHost:self.class.host];
 }
 
@@ -131,7 +128,7 @@
     [expectation fulfill];
   }];
 
-  [self waitForExpectationsWithTimeout:8 handler:nil];
+  [self waitForExpectationsWithTimeout:16 handler:nil];
 }
 
 - (void)testClientStreamingRPC {
diff --git a/src/objective-c/tests/InteropTestsLocalCleartext.m b/src/objective-c/tests/InteropTestsLocalCleartext.m
new file mode 100644
index 0000000..2d7d3c4
--- /dev/null
+++ b/src/objective-c/tests/InteropTestsLocalCleartext.m
@@ -0,0 +1,59 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+// Repeat of the tests in InteropTests.m, but sending the RPCs to a local cleartext server instead
+// of the remote SSL one.
+
+#import <GRPCClient/GRPCCall+Tests.h>
+
+#import "InteropTests.h"
+
+static NSString * const kLocalCleartextHost = @"localhost:5050";
+
+@interface InteropTestsLocalCleartext : InteropTests
+@end
+
+@implementation InteropTestsLocalCleartext
+
++ (NSString *)host {
+  return kLocalCleartextHost;
+}
+
+- (void)setUp {
+  // Register test server as non-SSL.
+  [GRPCCall useInsecureConnectionsForHost:kLocalCleartextHost];
+
+  [super setUp];
+}
+
+@end
diff --git a/src/objective-c/tests/InteropTestsLocalSSL.m b/src/objective-c/tests/InteropTestsLocalSSL.m
index 227ca79..f69f806 100644
--- a/src/objective-c/tests/InteropTestsLocalSSL.m
+++ b/src/objective-c/tests/InteropTestsLocalSSL.m
@@ -31,8 +31,8 @@
  *
  */
 
-// Repeat of the tests in InteropTests.m, but using SSL to communicate with the local server instead
-// of cleartext.
+// Repeat of the tests in InteropTests.m, but sending the RPCs to a local SSL server instead of the
+// remote one.
 
 #import <GRPCClient/GRPCCall+Tests.h>
 
diff --git a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj
index af98aba..3a1c3d9 100644
--- a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj
+++ b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj
@@ -13,6 +13,7 @@
 		63423F511B151B77006CF63C /* RxLibraryUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63423F501B151B77006CF63C /* RxLibraryUnitTests.m */; };
 		635697CD1B14FC11007A7283 /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635697CC1B14FC11007A7283 /* Tests.m */; };
 		635ED2EC1B1A3BC400FDE5C3 /* InteropTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */; };
+		63715F561B780C020029CB0B /* InteropTestsLocalCleartext.m in Sources */ = {isa = PBXBuildFile; fileRef = 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */; };
 		63E240CE1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */; };
 		63E240D01B6C63DC005F3B0E /* TestCertificates.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */; };
 		7D8A186224D39101F90230F6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */; };
@@ -51,6 +52,7 @@
 		635697CC1B14FC11007A7283 /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = "<group>"; };
 		635697D81B14FC11007A7283 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTests.m; sourceTree = "<group>"; };
+		63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTestsLocalCleartext.m; sourceTree = "<group>"; };
 		63E240CC1B6C4D3A005F3B0E /* InteropTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InteropTests.h; sourceTree = "<group>"; };
 		63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTestsLocalSSL.m; sourceTree = "<group>"; };
 		63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = TestCertificates.bundle; sourceTree = "<group>"; };
@@ -117,14 +119,15 @@
 		635697C91B14FC11007A7283 /* Tests */ = {
 			isa = PBXGroup;
 			children = (
-				63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */,
 				6312AE4D1B1BF49B00341DEE /* GRPCClientTests.m */,
-				63175DFE1B1B9FAF00027841 /* LocalClearTextTests.m */,
+				63E240CC1B6C4D3A005F3B0E /* InteropTests.h */,
 				635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */,
+				63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */,
+				63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */,
 				63423F501B151B77006CF63C /* RxLibraryUnitTests.m */,
+				63175DFE1B1B9FAF00027841 /* LocalClearTextTests.m */,
 				635697CC1B14FC11007A7283 /* Tests.m */,
 				635697D71B14FC11007A7283 /* Supporting Files */,
-				63E240CC1B6C4D3A005F3B0E /* InteropTests.h */,
 			);
 			name = Tests;
 			sourceTree = SOURCE_ROOT;
@@ -261,6 +264,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				63715F561B780C020029CB0B /* InteropTestsLocalCleartext.m in Sources */,
 				63175DFF1B1B9FAF00027841 /* LocalClearTextTests.m in Sources */,
 				63423F511B151B77006CF63C /* RxLibraryUnitTests.m in Sources */,
 				63E240CE1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m in Sources */,
diff --git a/tools/doxygen/Doxyfile.c++ b/tools/doxygen/Doxyfile.c++
index eb14925..dd359f1 100644
--- a/tools/doxygen/Doxyfile.c++
+++ b/tools/doxygen/Doxyfile.c++
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 0.10.0.0
+PROJECT_NUMBER         = 0.10.1.0
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index 3d6649b..6e8437d 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 0.10.0.0
+PROJECT_NUMBER         = 0.10.1.0
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/tools/doxygen/Doxyfile.core b/tools/doxygen/Doxyfile.core
index 2fb27ef..d219ca7 100644
--- a/tools/doxygen/Doxyfile.core
+++ b/tools/doxygen/Doxyfile.core
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 0.10.0.0
+PROJECT_NUMBER         = 0.10.1.0
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index cbf5c50..1f66fe1 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 0.10.0.0
+PROJECT_NUMBER         = 0.10.1.0
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a