diff --git a/include/grpc++/create_channel.h b/include/grpc++/create_channel.h
index 0e559ac53e45e14e925968862ed62f8dc11ad08d..916f3b0b97c0e133e539b0bccb59c5d5937d14cd 100644
--- a/include/grpc++/create_channel.h
+++ b/include/grpc++/create_channel.h
@@ -44,6 +44,12 @@ namespace grpc {
 
 // If creds does not hold an object or is invalid, a lame channel is returned.
 std::shared_ptr<Channel> CreateChannel(
+    const grpc::string& target, const std::shared_ptr<Credentials>& creds);
+
+// For advanced use and testing ONLY. Override default channel arguments only
+// if necessary.
+// If creds does not hold an object or is invalid, a lame channel is returned.
+std::shared_ptr<Channel> CreateCustomChannel(
     const grpc::string& target, const std::shared_ptr<Credentials>& creds,
     const ChannelArguments& args);
 
diff --git a/include/grpc++/credentials.h b/include/grpc++/credentials.h
index a1488add1e453e006cd283813507b6ac0c8c5921..ce5a9e0606cdbe8a0d5ece7265ab542f0beeff65 100644
--- a/include/grpc++/credentials.h
+++ b/include/grpc++/credentials.h
@@ -57,7 +57,7 @@ class Credentials : public GrpcLibrary {
   virtual SecureCredentials* AsSecureCredentials() = 0;
 
  private:
-  friend std::shared_ptr<Channel> CreateChannel(
+  friend std::shared_ptr<Channel> CreateCustomChannel(
       const grpc::string& target, const std::shared_ptr<Credentials>& creds,
       const ChannelArguments& args);
 
diff --git a/src/compiler/csharp_generator.cc b/src/compiler/csharp_generator.cc
index 51d8d982e2d25d8f9cbfdd1f64e124c41cb601d7..7b497df7f4775edafbac825b104633615737e66f 100644
--- a/src/compiler/csharp_generator.cc
+++ b/src/compiler/csharp_generator.cc
@@ -33,6 +33,7 @@
 
 #include <cctype>
 #include <map>
+#include <sstream>
 #include <vector>
 
 #include "src/compiler/csharp_generator.h"
@@ -44,7 +45,6 @@
 using google::protobuf::compiler::csharp::GetFileNamespace;
 using google::protobuf::compiler::csharp::GetClassName;
 using google::protobuf::compiler::csharp::GetUmbrellaClassName;
-using google::protobuf::SimpleItoa;
 using grpc::protobuf::FileDescriptor;
 using grpc::protobuf::Descriptor;
 using grpc::protobuf::ServiceDescriptor;
@@ -228,11 +228,14 @@ void GenerateStaticMethodField(Printer* out, const MethodDescriptor *method) {
 }
 
 void GenerateServiceDescriptorProperty(Printer* out, const ServiceDescriptor *service) {
+  std::ostringstream index;
+  index << service->index();
   out->Print("// service descriptor\n");
   out->Print("public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor\n");
   out->Print("{\n");
   out->Print("  get { return $umbrella$.Descriptor.Services[$index$]; }\n",
-             "umbrella", GetUmbrellaClassName(service->file()), "index", SimpleItoa(service->index()));
+             "umbrella", GetUmbrellaClassName(service->file()), "index",
+             index.str());
   out->Print("}\n");
   out->Print("\n");
 }
diff --git a/src/compiler/csharp_generator.h b/src/compiler/csharp_generator.h
index 67e3ee30b54f32f676a467ce6b1a74b054dc3a9a..90eb7e298466d3eda10adb1d912d82045dbcfb36 100644
--- a/src/compiler/csharp_generator.h
+++ b/src/compiler/csharp_generator.h
@@ -36,10 +36,7 @@
 
 #include "src/compiler/config.h"
 
-using namespace std;
-
 #include <google/protobuf/compiler/csharp/csharp_names.h>
-#include <google/protobuf/stubs/strutil.h>
 
 namespace grpc_csharp_generator {
 
diff --git a/src/core/census/grpc_filter.c b/src/core/census/grpc_filter.c
index fbedb35661ff8520cb8f6d3913489c7a92e691d9..b78445595c8e2b2857ddf61dbc6fd968aa7e6346 100644
--- a/src/core/census/grpc_filter.c
+++ b/src/core/census/grpc_filter.c
@@ -36,12 +36,12 @@
 #include <stdio.h>
 #include <string.h>
 
-#include "include/grpc/census.h"
 #include "src/core/census/rpc_stat_id.h"
 #include "src/core/channel/channel_stack.h"
 #include "src/core/channel/noop_filter.h"
 #include "src/core/statistics/census_interface.h"
 #include "src/core/statistics/census_rpc_stats.h"
+#include <grpc/census.h>
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/slice.h>
diff --git a/src/core/transport/chttp2/stream_lists.c b/src/core/transport/chttp2/stream_lists.c
index 38c6052f9c489049b707e2c2cd6d7ca9b8000bf8..781db7b0d6963bc384ca854af6d5d807e4b15190 100644
--- a/src/core/transport/chttp2/stream_lists.c
+++ b/src/core/transport/chttp2/stream_lists.c
@@ -177,8 +177,10 @@ int grpc_chttp2_list_pop_writable_stream(
   grpc_chttp2_stream *stream;
   int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
                           GRPC_CHTTP2_LIST_WRITABLE);
-  *stream_global = &stream->global;
-  *stream_writing = &stream->writing;
+  if (r != 0) {
+    *stream_global = &stream->global;
+    *stream_writing = &stream->writing;
+  }
   return r;
 }
 
@@ -210,7 +212,9 @@ int grpc_chttp2_list_pop_writing_stream(
   grpc_chttp2_stream *stream;
   int r = stream_list_pop(TRANSPORT_FROM_WRITING(transport_writing), &stream,
                           GRPC_CHTTP2_LIST_WRITING);
-  *stream_writing = &stream->writing;
+  if (r != 0) {
+    *stream_writing = &stream->writing;
+  }
   return r;
 }
 
@@ -230,8 +234,10 @@ int grpc_chttp2_list_pop_written_stream(
   grpc_chttp2_stream *stream;
   int r = stream_list_pop(TRANSPORT_FROM_WRITING(transport_writing), &stream,
                           GRPC_CHTTP2_LIST_WRITTEN);
-  *stream_global = &stream->global;
-  *stream_writing = &stream->writing;
+  if (r != 0) {
+    *stream_global = &stream->global;
+    *stream_writing = &stream->writing;
+  }
   return r;
 }
 
@@ -251,8 +257,10 @@ int grpc_chttp2_list_pop_parsing_seen_stream(
   grpc_chttp2_stream *stream;
   int r = stream_list_pop(TRANSPORT_FROM_PARSING(transport_parsing), &stream,
                           GRPC_CHTTP2_LIST_PARSING_SEEN);
-  *stream_global = &stream->global;
-  *stream_parsing = &stream->parsing;
+  if (r != 0) {
+    *stream_global = &stream->global;
+    *stream_parsing = &stream->parsing;
+  }
   return r;
 }
 
@@ -270,7 +278,9 @@ int grpc_chttp2_list_pop_waiting_for_concurrency(
   grpc_chttp2_stream *stream;
   int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
                           GRPC_CHTTP2_LIST_WAITING_FOR_CONCURRENCY);
-  *stream_global = &stream->global;
+  if (r != 0) {
+    *stream_global = &stream->global;
+  }
   return r;
 }
 
@@ -288,7 +298,9 @@ int grpc_chttp2_list_pop_closed_waiting_for_parsing(
   grpc_chttp2_stream *stream;
   int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
                           GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_PARSING);
-  *stream_global = &stream->global;
+  if (r != 0) {
+    *stream_global = &stream->global;
+  }
   return r;
 }
 
@@ -306,7 +318,9 @@ int grpc_chttp2_list_pop_cancelled_waiting_for_writing(
   grpc_chttp2_stream *stream;
   int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
                           GRPC_CHTTP2_LIST_CANCELLED_WAITING_FOR_WRITING);
-  *stream_global = &stream->global;
+  if (r != 0) {
+    *stream_global = &stream->global;
+  }
   return r;
 }
 
@@ -326,8 +340,10 @@ int grpc_chttp2_list_pop_incoming_window_updated(
   grpc_chttp2_stream *stream;
   int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
                           GRPC_CHTTP2_LIST_INCOMING_WINDOW_UPDATED);
-  *stream_global = &stream->global;
-  *stream_parsing = &stream->parsing;
+  if (r != 0) {
+    *stream_global = &stream->global;
+    *stream_parsing = &stream->parsing;
+  }
   return r;
 }
 
@@ -353,7 +369,9 @@ int grpc_chttp2_list_pop_read_write_state_changed(
   grpc_chttp2_stream *stream;
   int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
                           GRPC_CHTTP2_LIST_READ_WRITE_STATE_CHANGED);
-  *stream_global = &stream->global;
+  if (r != 0) {
+    *stream_global = &stream->global;
+  }
   return r;
 }
 
diff --git a/src/core/transport/metadata.c b/src/core/transport/metadata.c
index 3fd21a2f5dc6c18fd201f2846d7d8b2c3b0a84d4..61638764a61bbc335e288513b52afaa5c514963d 100644
--- a/src/core/transport/metadata.c
+++ b/src/core/transport/metadata.c
@@ -703,7 +703,7 @@ int grpc_mdstr_is_legal_header(grpc_mdstr *s) {
 
 int grpc_mdstr_is_legal_nonbin_header(grpc_mdstr *s) {
   static const gpr_uint8 legal_header_bits[256 / 8] = {
-      0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+      0x00, 0x00, 0x00, 0x00, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff,
       0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
   return conforms_to(s, legal_header_bits);
diff --git a/src/cpp/client/create_channel.cc b/src/cpp/client/create_channel.cc
index 8c571cbbaad24f65591294d9e74ddb704dfd2cf2..1dac9600170c0b419f61f5bc21caf0140c84b815 100644
--- a/src/cpp/client/create_channel.cc
+++ b/src/cpp/client/create_channel.cc
@@ -44,6 +44,11 @@ namespace grpc {
 class ChannelArguments;
 
 std::shared_ptr<Channel> CreateChannel(
+    const grpc::string& target, const std::shared_ptr<Credentials>& creds) {
+  return CreateCustomChannel(target, creds, ChannelArguments());
+}
+
+std::shared_ptr<Channel> CreateCustomChannel(
     const grpc::string& target, const std::shared_ptr<Credentials>& creds,
     const ChannelArguments& args) {
   ChannelArguments cp_args = args;
@@ -57,4 +62,5 @@ std::shared_ptr<Channel> CreateChannel(
                                              NULL, GRPC_STATUS_INVALID_ARGUMENT,
                                              "Invalid credentials."));
 }
+
 }  // namespace grpc
diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
index b571fe90259d580a86fc63364ec166a13bc48120..f730936062daa1efeec6c00c425a27cbcde9a25d 100644
--- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
+++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
@@ -64,6 +64,7 @@
       <Link>Version.cs</Link>
     </Compile>
     <Compile Include="ClientBaseTest.cs" />
+    <Compile Include="MarshallingErrorsTest.cs" />
     <Compile Include="ShutdownTest.cs" />
     <Compile Include="Internal\AsyncCallTest.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
diff --git a/src/csharp/Grpc.Core.Tests/MarshallingErrorsTest.cs b/src/csharp/Grpc.Core.Tests/MarshallingErrorsTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..83707e0c6da91c35da05d321341a6617537925d0
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/MarshallingErrorsTest.cs
@@ -0,0 +1,176 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+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 MarshallingErrorsTest
+    {
+        const string Host = "127.0.0.1";
+
+        MockServiceHelper helper;
+        Server server;
+        Channel channel;
+
+        [SetUp]
+        public void Init()
+        {
+            var marshaller = new Marshaller<string>(
+                (str) =>
+                {
+                    if (str == "UNSERIALIZABLE_VALUE")
+                    {
+                        // Google.Protobuf throws exception inherited from IOException
+                        throw new IOException("Error serializing the message.");
+                    }
+                    return System.Text.Encoding.UTF8.GetBytes(str); 
+                },
+                (payload) =>
+                {
+                    var s = System.Text.Encoding.UTF8.GetString(payload);
+                    if (s == "UNPARSEABLE_VALUE")
+                    {
+                        // Google.Protobuf throws exception inherited from IOException
+                        throw new IOException("Error parsing the message.");
+                    }
+                    return s;
+                });
+            helper = new MockServiceHelper(Host, marshaller);
+            server = helper.GetServer();
+            server.Start();
+            channel = helper.GetChannel();
+        }
+
+        [TearDown]
+        public void Cleanup()
+        {
+            channel.ShutdownAsync().Wait();
+            server.ShutdownAsync().Wait();
+        }
+
+        [Test]
+        public void ResponseParsingError_UnaryResponse()
+        {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
+            {
+                return Task.FromResult("UNPARSEABLE_VALUE");
+            });
+
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "REQUEST"));
+            Assert.AreEqual(StatusCode.Internal, ex.Status.StatusCode);
+        }
+
+        [Test]
+        public void ResponseParsingError_StreamingResponse()
+        {
+            helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
+            {
+                await responseStream.WriteAsync("UNPARSEABLE_VALUE");
+                await Task.Delay(10000);
+            });
+
+            var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "REQUEST");
+            var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.MoveNext());
+            Assert.AreEqual(StatusCode.Internal, ex.Status.StatusCode);
+        }
+
+        [Test]
+        public void RequestParsingError_UnaryRequest()
+        {
+            helper.UnaryHandler = new  UnaryServerMethod<string, string>((request, context) =>
+            {
+                return Task.FromResult("RESPONSE");
+            });
+
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "UNPARSEABLE_VALUE"));
+            // Spec doesn't define the behavior. With the current implementation server handler throws exception which results in StatusCode.Unknown.
+            Assert.AreEqual(StatusCode.Unknown, ex.Status.StatusCode);
+        }
+
+        [Test]
+        public async Task RequestParsingError_StreamingRequest()
+        {
+            helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+            {
+                Assert.Throws<IOException>(async () => await requestStream.MoveNext());
+                return "RESPONSE";
+            });
+
+            var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall());
+            await call.RequestStream.WriteAsync("UNPARSEABLE_VALUE");
+
+            Assert.AreEqual("RESPONSE", await call);
+        }
+
+        [Test]
+        public void RequestSerializationError_BlockingUnary()
+        {
+            Assert.Throws<IOException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "UNSERIALIZABLE_VALUE"));
+        }
+
+        [Test]
+        public void RequestSerializationError_AsyncUnary()
+        {
+            Assert.Throws<IOException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "UNSERIALIZABLE_VALUE"));
+        }
+
+        [Test]
+        public async Task RequestSerializationError_ClientStreaming()
+        {
+            helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+            {
+                CollectionAssert.AreEqual(new [] {"A", "B"}, await requestStream.ToListAsync());
+                return "RESPONSE";
+            });
+            var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall());
+            await call.RequestStream.WriteAsync("A");
+            Assert.Throws<IOException>(async () => await call.RequestStream.WriteAsync("UNSERIALIZABLE_VALUE"));
+            await call.RequestStream.WriteAsync("B");
+            await call.RequestStream.CompleteAsync();
+
+            Assert.AreEqual("RESPONSE", await call);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/MetadataTest.cs b/src/csharp/Grpc.Core.Tests/MetadataTest.cs
index c00f945d6a76be8d7cf11f7650410ef86eb538e7..ddeb7d09260306d1dc2032238ed2e4bc3e92dc12 100644
--- a/src/csharp/Grpc.Core.Tests/MetadataTest.cs
+++ b/src/csharp/Grpc.Core.Tests/MetadataTest.cs
@@ -74,6 +74,17 @@ namespace Grpc.Core.Tests
             Assert.AreEqual("[Entry: key=abc-bin, valueBytes=System.Byte[]]", entry.ToString());
         }
 
+        [Test]
+        public void AsciiEntry_KeyValidity()
+        {
+            new Metadata.Entry("ABC", "XYZ");
+            new Metadata.Entry("0123456789abc", "XYZ");
+            new Metadata.Entry("-abc", "XYZ");
+            new Metadata.Entry("a_bc_", "XYZ");
+            Assert.Throws(typeof(ArgumentException), () => new Metadata.Entry("abc[", "xyz"));
+            Assert.Throws(typeof(ArgumentException), () => new Metadata.Entry("abc/", "xyz"));
+        }
+
         [Test]
         public void Entry_ConstructionPreconditions()
         {
diff --git a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
index bb69648d8bf01177d35839c6a61dcfe6ee255435..765732c7687eaa7a34e490cc6ad93c450fad5f9c 100644
--- a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
+++ b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
@@ -50,37 +50,14 @@ namespace Grpc.Core.Tests
     {
         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;
 
+        readonly Method<string, string> unaryMethod;
+        readonly Method<string, string> clientStreamingMethod;
+        readonly Method<string, string> serverStreamingMethod;
+        readonly Method<string, string> duplexStreamingMethod;
+
         UnaryServerMethod<string, string> unaryHandler;
         ClientStreamingServerMethod<string, string> clientStreamingHandler;
         ServerStreamingServerMethod<string, string> serverStreamingHandler;
@@ -89,15 +66,44 @@ namespace Grpc.Core.Tests
         Server server;
         Channel channel;
 
-        public MockServiceHelper(string host = null)
+        public MockServiceHelper(string host = null, Marshaller<string> marshaller = null)
         {
             this.host = host ?? "localhost";
+            marshaller = marshaller ?? Marshallers.StringMarshaller;
+
+            unaryMethod = new Method<string, string>(
+                MethodType.Unary,
+                ServiceName,
+                "Unary",
+                marshaller,
+                marshaller);
+
+            clientStreamingMethod = new Method<string, string>(
+                MethodType.ClientStreaming,
+                ServiceName,
+                "ClientStreaming",
+                marshaller,
+                marshaller);
+
+            serverStreamingMethod = new Method<string, string>(
+                MethodType.ServerStreaming,
+                ServiceName,
+                "ServerStreaming",
+                marshaller,
+                marshaller);
+
+            duplexStreamingMethod = new Method<string, string>(
+                MethodType.DuplexStreaming,
+                ServiceName,
+                "DuplexStreaming",
+                marshaller,
+                marshaller);
 
             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))
+                .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.");
@@ -155,22 +161,22 @@ namespace Grpc.Core.Tests
 
         public CallInvocationDetails<string, string> CreateUnaryCall(CallOptions options = default(CallOptions))
         {
-            return new CallInvocationDetails<string, string>(channel, UnaryMethod, options);
+            return new CallInvocationDetails<string, string>(channel, unaryMethod, options);
         }
 
         public CallInvocationDetails<string, string> CreateClientStreamingCall(CallOptions options = default(CallOptions))
         {
-            return new CallInvocationDetails<string, string>(channel, ClientStreamingMethod, options);
+            return new CallInvocationDetails<string, string>(channel, clientStreamingMethod, options);
         }
 
         public CallInvocationDetails<string, string> CreateServerStreamingCall(CallOptions options = default(CallOptions))
         {
-            return new CallInvocationDetails<string, string>(channel, ServerStreamingMethod, options);
+            return new CallInvocationDetails<string, string>(channel, serverStreamingMethod, options);
         }
 
         public CallInvocationDetails<string, string> CreateDuplexStreamingCall(CallOptions options = default(CallOptions))
         {
-            return new CallInvocationDetails<string, string>(channel, DuplexStreamingMethod, options);
+            return new CallInvocationDetails<string, string>(channel, duplexStreamingMethod, options);
         }
 
         public string Host
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
index be5d611a53826f6400d6ff4bdc2bd0373c7ed93d..e3b00781c6216885cc9025ed26f3ef35d388e5b7 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
@@ -322,6 +322,11 @@ namespace Grpc.Core.Internal
             details.Channel.RemoveCallReference(this);
         }
 
+        protected override bool IsClient
+        {
+            get { return true; }
+        }
+
         private void Initialize(CompletionQueueSafeHandle cq)
         {
             var call = CreateNativeCall(cq);
@@ -376,9 +381,17 @@ namespace Grpc.Core.Internal
         /// </summary>
         private void HandleUnaryResponse(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders)
         {
+            TResponse msg = default(TResponse);
+            var deserializeException = success ? TryDeserialize(receivedMessage, out msg) : null;
+
             lock (myLock)
             {
                 finished = true;
+
+                if (deserializeException != null && receivedStatus.Status.StatusCode == StatusCode.OK)
+                {
+                    receivedStatus = new ClientSideStatus(DeserializeResponseFailureStatus, receivedStatus.Trailers);
+                }
                 finishedStatus = receivedStatus;
 
                 ReleaseResourcesIfPossible();
@@ -394,10 +407,6 @@ namespace Grpc.Core.Internal
                 return;
             }
 
-            // TODO: handle deserialization error
-            TResponse msg;
-            TryDeserialize(receivedMessage, out msg);
-
             unaryResponseTcs.SetResult(msg);
         }
 
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
index 4d2039464497ec6861270ed45e6a5f0e62e1504e..3e2c57c9b5b0a8dc00440df0a2f2b864211fa214 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
@@ -33,10 +33,12 @@
 
 using System;
 using System.Diagnostics;
+using System.IO;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Threading;
 using System.Threading.Tasks;
+
 using Grpc.Core.Internal;
 using Grpc.Core.Logging;
 using Grpc.Core.Utils;
@@ -50,6 +52,7 @@ namespace Grpc.Core.Internal
     internal abstract class AsyncCallBase<TWrite, TRead>
     {
         static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<AsyncCallBase<TWrite, TRead>>();
+        protected static readonly Status DeserializeResponseFailureStatus = new Status(StatusCode.Internal, "Failed to deserialize response message.");
 
         readonly Func<TWrite, byte[]> serializer;
         readonly Func<byte[], TRead> deserializer;
@@ -100,11 +103,10 @@ namespace Grpc.Core.Internal
         /// <summary>
         /// Requests cancelling the call with given status.
         /// </summary>
-        public void CancelWithStatus(Status status)
+        protected void CancelWithStatus(Status status)
         {
             lock (myLock)
             {
-                Preconditions.CheckState(started);
                 cancelRequested = true;
 
                 if (!disposed)
@@ -177,6 +179,11 @@ namespace Grpc.Core.Internal
             return false;
         }
 
+        protected abstract bool IsClient
+        {
+            get;
+        }
+
         private void ReleaseResources()
         {
             if (call != null)
@@ -224,33 +231,31 @@ namespace Grpc.Core.Internal
             return serializer(msg);
         }
 
-        protected bool TrySerialize(TWrite msg, out byte[] payload)
+        protected Exception TrySerialize(TWrite msg, out byte[] payload)
         {
             try
             {
                 payload = serializer(msg);
-                return true;
+                return null;
             }
             catch (Exception e)
             {
-                Logger.Error(e, "Exception occured while trying to serialize message");
                 payload = null;
-                return false;
+                return e;
             }
         }
 
-        protected bool TryDeserialize(byte[] payload, out TRead msg)
+        protected Exception TryDeserialize(byte[] payload, out TRead msg)
         {
             try
             {
                 msg = deserializer(payload);
-                return true;
+                return null;
             } 
             catch (Exception e)
             {
-                Logger.Error(e, "Exception occured while trying to deserialize message.");
                 msg = default(TRead);
-                return false;
+                return e;
             }
         }
 
@@ -319,6 +324,9 @@ namespace Grpc.Core.Internal
         /// </summary>
         protected void HandleReadFinished(bool success, byte[] receivedMessage)
         {
+            TRead msg = default(TRead);
+            var deserializeException = (success && receivedMessage != null) ? TryDeserialize(receivedMessage, out msg) : null;
+
             AsyncCompletionDelegate<TRead> origCompletionDelegate = null;
             lock (myLock)
             {
@@ -331,23 +339,23 @@ namespace Grpc.Core.Internal
                     readingDone = true;
                 }
 
+                if (deserializeException != null && IsClient)
+                {
+                    readingDone = true;
+                    CancelWithStatus(DeserializeResponseFailureStatus);
+                }
+
                 ReleaseResourcesIfPossible();
             }
 
-            // TODO: handle the case when error occured...
+            // TODO: handle the case when success==false
 
-            if (receivedMessage != null)
-            {
-                // TODO: handle deserialization error
-                TRead msg;
-                TryDeserialize(receivedMessage, out msg);
-
-                FireCompletion(origCompletionDelegate, msg, null);
-            }
-            else
+            if (deserializeException != null && !IsClient)
             {
-                FireCompletion(origCompletionDelegate, default(TRead), null);
+                FireCompletion(origCompletionDelegate, default(TRead), new IOException("Failed to deserialize request message.", deserializeException));
+                return;
             }
+            FireCompletion(origCompletionDelegate, msg, null);
         }
     }
 }
\ No newline at end of file
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
index 5c47251030e4525622d3ad46d413d1d7f3806712..46ca45934937534520edb8c87bd6171f4fe45bf7 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
@@ -169,6 +169,11 @@ namespace Grpc.Core.Internal
             }
         }
 
+        protected override bool IsClient
+        {
+            get { return false; }
+        }
+
         protected override void CheckReadingAllowed()
         {
             base.CheckReadingAllowed();
diff --git a/src/csharp/Grpc.Core/Marshaller.cs b/src/csharp/Grpc.Core/Marshaller.cs
index f38cb0863ffe0099475d200428a53ef0b573c548..3493d2d38f05b1886531991e6f3addece16de670 100644
--- a/src/csharp/Grpc.Core/Marshaller.cs
+++ b/src/csharp/Grpc.Core/Marshaller.cs
@@ -39,7 +39,7 @@ namespace Grpc.Core
     /// <summary>
     /// Encapsulates the logic for serializing and deserializing messages.
     /// </summary>
-    public struct Marshaller<T>
+    public class Marshaller<T>
     {
         readonly Func<T, byte[]> serializer;
         readonly Func<byte[], T> deserializer;
diff --git a/src/csharp/Grpc.Core/Metadata.cs b/src/csharp/Grpc.Core/Metadata.cs
index 2b08e0de51bb02143092108cb3b47b5a14370df2..21bdf4f114507752ddcf13bd3ad6a7cf78d12c14 100644
--- a/src/csharp/Grpc.Core/Metadata.cs
+++ b/src/csharp/Grpc.Core/Metadata.cs
@@ -36,6 +36,7 @@ using System.Collections.Specialized;
 using System.Globalization;
 using System.Runtime.InteropServices;
 using System.Text;
+using System.Text.RegularExpressions;
 
 using Grpc.Core.Utils;
 
@@ -189,6 +190,7 @@ namespace Grpc.Core
         public struct Entry
         {
             private static readonly Encoding Encoding = Encoding.ASCII;
+            private static readonly Regex ValidKeyRegex = new Regex("^[a-z0-9_-]+$");
 
             readonly string key;
             readonly string value;
@@ -321,7 +323,10 @@ namespace Grpc.Core
 
             private static string NormalizeKey(string key)
             {
-                return Preconditions.CheckNotNull(key, "key").ToLower(CultureInfo.InvariantCulture);
+                var normalized = Preconditions.CheckNotNull(key, "key").ToLower(CultureInfo.InvariantCulture);
+                Preconditions.CheckArgument(ValidKeyRegex.IsMatch(normalized), 
+                    "Metadata entry key not valid. Keys can only contain lowercase alphanumeric characters, underscores and hyphens.");
+                return normalized;
             }
         }
     }
diff --git a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
index d8547758d24eabf0ac8406583f24c3fb8d392ce4..e2975b5da93fd265899100aee3d5ec6d442119c2 100644
--- a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
+++ b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
@@ -162,7 +162,7 @@ namespace Math.Tests
         {
             using (var call = client.Sum())
             {
-                var numbers = new List<long> { 10, 20, 30 }.ConvertAll(n => new Num{ Num_ = n });
+                var numbers = new List<long> { 10, 20, 30 }.ConvertAll(n => new Num { Num_ = n });
 
                 await call.RequestStream.WriteAllAsync(numbers);
                 var result = await call.ResponseAsync;
diff --git a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
index 95f742cc99d316681929dc62b311d66429bbdd98..6c3a53bec0559168b54aab151394a855b6470fd1 100644
--- a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
+++ b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
@@ -88,7 +88,7 @@ namespace Grpc.HealthCheck.Tests
         [Test]
         public void ServiceDoesntExist()
         {
-            Assert.Throws(Is.TypeOf(typeof(RpcException)).And.Property("Status").Property("StatusCode").EqualTo(StatusCode.NotFound), () => client.Check(new HealthCheckRequest{ Host = "", Service = "nonexistent.service" }));
+            Assert.Throws(Is.TypeOf(typeof(RpcException)).And.Property("Status").Property("StatusCode").EqualTo(StatusCode.NotFound), () => client.Check(new HealthCheckRequest { Host = "", Service = "nonexistent.service" }));
         }
 
         // TODO(jtattermusch): add test with timeout once timeouts are supported
diff --git a/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs b/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs
index 8de8645cd187bdf18379c265a15d4c3d7f4bdf22..2097c0dc8cf7daa2c79f72a4706299af0ee76d0d 100644
--- a/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs
+++ b/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs
@@ -101,7 +101,7 @@ namespace Grpc.HealthCheck.Tests
 
         private static HealthCheckResponse.Types.ServingStatus GetStatusHelper(HealthServiceImpl impl, string host, string service)
         {
-            return impl.Check(new HealthCheckRequest{ Host = host, Service = service}, null).Result.Status;
+            return impl.Check(new HealthCheckRequest { Host = host, Service = service }, null).Result.Status;
         }
     }
 }
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
index ed51af19421c7f1c53f9bc453b0552737330c8e8..8343e54122cc0a4ab6e6f52952b6e992f744578c 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
@@ -37,13 +37,12 @@ using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading.Tasks;
 
+using Google.Apis.Auth.OAuth2;
+using Google.Protobuf;
 using Grpc.Auth;
 using Grpc.Core;
 using Grpc.Core.Utils;
 using Grpc.Testing;
-using Google.Protobuf;
-using Google.Apis.Auth.OAuth2;
-
 using NUnit.Framework;
 
 namespace Grpc.IntegrationTesting
diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc
index 18858fa334ad8e7763a4c37b8ed4006f85c48801..fddc1e214f5b72950c40199c754c4e3307fa7e92 100644
--- a/src/node/ext/call.cc
+++ b/src/node/ext/call.cc
@@ -111,17 +111,19 @@ bool CreateMetadataArray(Handle<Object> metadata, grpc_metadata_array *array,
           NanAssignPersistent(*handle, value);
           resources->handles.push_back(unique_ptr<PersistentHolder>(
               new PersistentHolder(handle)));
-          continue;
+        } else {
+          return false;
         }
-      }
-      if (value->IsString()) {
-        Handle<String> string_value = value->ToString();
-        NanUtf8String *utf8_value = new NanUtf8String(string_value);
-        resources->strings.push_back(unique_ptr<NanUtf8String>(utf8_value));
-        current->value = **utf8_value;
-        current->value_length = string_value->Length();
       } else {
-        return false;
+        if (value->IsString()) {
+          Handle<String> string_value = value->ToString();
+          NanUtf8String *utf8_value = new NanUtf8String(string_value);
+          resources->strings.push_back(unique_ptr<NanUtf8String>(utf8_value));
+          current->value = **utf8_value;
+          current->value_length = string_value->Length();
+        } else {
+          return false;
+        }
       }
       array->count += 1;
     }
@@ -156,8 +158,7 @@ Handle<Value> ParseMetadata(const grpc_metadata_array *metadata_array) {
     }
     if (EndsWith(elem->key, "-bin")) {
       array->Set(index_map[elem->key],
-                 MakeFastBuffer(
-                     NanNewBufferHandle(elem->value, elem->value_length)));
+                 NanNewBufferHandle(elem->value, elem->value_length));
     } else {
       array->Set(index_map[elem->key], NanNew(elem->value));
     }
diff --git a/src/node/index.js b/src/node/index.js
index 889b0ac0e92eee151670d8b11495638535cd6f90..51d3fa590cd3130a60d00daf346f00a1de46f08f 100644
--- a/src/node/index.js
+++ b/src/node/index.js
@@ -41,6 +41,8 @@ var client = require('./src/client.js');
 
 var server = require('./src/server.js');
 
+var Metadata = require('./src/metadata.js');
+
 var grpc = require('bindings')('grpc');
 
 /**
@@ -107,18 +109,12 @@ exports.getGoogleAuthDelegate = function getGoogleAuthDelegate(credential) {
    * @param {function(Error, Object)} callback
    */
   return function updateMetadata(authURI, metadata, callback) {
-    metadata = _.clone(metadata);
-    if (metadata.Authorization) {
-      metadata.Authorization = _.clone(metadata.Authorization);
-    } else {
-      metadata.Authorization = [];
-    }
     credential.getRequestMetadata(authURI, function(err, header) {
       if (err) {
         callback(err);
         return;
       }
-      metadata.Authorization.push(header.Authorization);
+      metadata.add('authorization', header.Authorization);
       callback(null, metadata);
     });
   };
@@ -129,6 +125,11 @@ exports.getGoogleAuthDelegate = function getGoogleAuthDelegate(credential) {
  */
 exports.Server = server.Server;
 
+/**
+ * @see module:src/metadata
+ */
+exports.Metadata = Metadata;
+
 /**
  * Status name to code number mapping
  */
diff --git a/src/node/interop/interop_client.js b/src/node/interop/interop_client.js
index 612dcf01f655c9c8aa15e966e60bb30dd2b804a1..8fb8d669206810cbb8cb200bc4bdad3e0c9d0276 100644
--- a/src/node/interop/interop_client.js
+++ b/src/node/interop/interop_client.js
@@ -321,13 +321,7 @@ function oauth2Test(expected_user, scope, per_rpc, client, done) {
     credential.getAccessToken(function(err, token) {
       assert.ifError(err);
       var updateMetadata = function(authURI, metadata, callback) {
-        metadata = _.clone(metadata);
-        if (metadata.Authorization) {
-          metadata.Authorization = _.clone(metadata.Authorization);
-        } else {
-          metadata.Authorization = [];
-        }
-        metadata.Authorization.push('Bearer ' + token);
+        metadata.Add('authorization', 'Bearer ' + token);
         callback(null, metadata);
       };
       var makeTestCall = function(error, client_metadata) {
diff --git a/src/node/src/client.js b/src/node/src/client.js
index 7b7eae51d2622b5042c6d14acff5e53e5aa11fe8..e1bed3512e9b12b5dfb5d8917fe66c59bdfdccb4 100644
--- a/src/node/src/client.js
+++ b/src/node/src/client.js
@@ -42,7 +42,9 @@ var _ = require('lodash');
 
 var grpc = require('bindings')('grpc.node');
 
-var common = require('./common.js');
+var common = require('./common');
+
+var Metadata = require('./metadata');
 
 var EventEmitter = require('events').EventEmitter;
 
@@ -254,8 +256,7 @@ function makeUnaryRequestFunction(method, serialize, deserialize) {
    *     serialize
    * @param {function(?Error, value=)} callback The callback to for when the
    *     response is received
-   * @param {array=} metadata Array of metadata key/value pairs to add to the
-   *     call
+   * @param {Metadata=} metadata Metadata to add to the call
    * @param {Object=} options Options map
    * @return {EventEmitter} An event emitter for stream related events
    */
@@ -264,7 +265,9 @@ function makeUnaryRequestFunction(method, serialize, deserialize) {
     var emitter = new EventEmitter();
     var call = getCall(this.channel, method, options);
     if (metadata === null || metadata === undefined) {
-      metadata = {};
+      metadata = new Metadata();
+    } else {
+      metadata = metadata.clone();
     }
     emitter.cancel = function cancel() {
       call.cancel();
@@ -283,13 +286,16 @@ function makeUnaryRequestFunction(method, serialize, deserialize) {
       if (options) {
         message.grpcWriteFlags = options.flags;
       }
-      client_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
+      client_batch[grpc.opType.SEND_INITIAL_METADATA] =
+          metadata._getCoreRepresentation();
       client_batch[grpc.opType.SEND_MESSAGE] = message;
       client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
       client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
       client_batch[grpc.opType.RECV_MESSAGE] = true;
       client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
       call.startBatch(client_batch, function(err, response) {
+        response.status.metadata = Metadata._fromCoreRepresentation(
+              response.status.metadata);
         emitter.emit('status', response.status);
         if (response.status.code !== grpc.status.OK) {
           var error = new Error(response.status.details);
@@ -304,7 +310,8 @@ function makeUnaryRequestFunction(method, serialize, deserialize) {
             return;
           }
         }
-        emitter.emit('metadata', response.metadata);
+        emitter.emit('metadata', Metadata._fromCoreRepresentation(
+            response.metadata));
         callback(null, deserialize(response.read));
       });
     });
@@ -328,7 +335,7 @@ function makeClientStreamRequestFunction(method, serialize, deserialize) {
    * @this {Client} Client object. Must have a channel member.
    * @param {function(?Error, value=)} callback The callback to for when the
    *     response is received
-   * @param {array=} metadata Array of metadata key/value pairs to add to the
+   * @param {Metadata=} metadata Array of metadata key/value pairs to add to the
    *     call
    * @param {Object=} options Options map
    * @return {EventEmitter} An event emitter for stream related events
@@ -337,7 +344,9 @@ function makeClientStreamRequestFunction(method, serialize, deserialize) {
     /* jshint validthis: true */
     var call = getCall(this.channel, method, options);
     if (metadata === null || metadata === undefined) {
-      metadata = {};
+      metadata = new Metadata();
+    } else {
+      metadata = metadata.clone();
     }
     var stream = new ClientWritableStream(call, serialize);
     this.updateMetadata(this.auth_uri, metadata, function(error, metadata) {
@@ -347,7 +356,8 @@ function makeClientStreamRequestFunction(method, serialize, deserialize) {
         return;
       }
       var metadata_batch = {};
-      metadata_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
+      metadata_batch[grpc.opType.SEND_INITIAL_METADATA] =
+          metadata._getCoreRepresentation();
       metadata_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
       call.startBatch(metadata_batch, function(err, response) {
         if (err) {
@@ -355,12 +365,15 @@ function makeClientStreamRequestFunction(method, serialize, deserialize) {
           // in the other batch.
           return;
         }
-        stream.emit('metadata', response.metadata);
+        stream.emit('metadata', Metadata._fromCoreRepresentation(
+            response.metadata));
       });
       var client_batch = {};
       client_batch[grpc.opType.RECV_MESSAGE] = true;
       client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
       call.startBatch(client_batch, function(err, response) {
+        response.status.metadata = Metadata._fromCoreRepresentation(
+              response.status.metadata);
         stream.emit('status', response.status);
         if (response.status.code !== grpc.status.OK) {
           var error = new Error(response.status.details);
@@ -398,7 +411,7 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
    * @this {SurfaceClient} Client object. Must have a channel member.
    * @param {*} argument The argument to the call. Should be serializable with
    *     serialize
-   * @param {array=} metadata Array of metadata key/value pairs to add to the
+   * @param {Metadata=} metadata Array of metadata key/value pairs to add to the
    *     call
    * @param {Object} options Options map
    * @return {EventEmitter} An event emitter for stream related events
@@ -407,7 +420,9 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
     /* jshint validthis: true */
     var call = getCall(this.channel, method, options);
     if (metadata === null || metadata === undefined) {
-      metadata = {};
+      metadata = new Metadata();
+    } else {
+      metadata = metadata.clone();
     }
     var stream = new ClientReadableStream(call, deserialize);
     this.updateMetadata(this.auth_uri, metadata, function(error, metadata) {
@@ -421,7 +436,8 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
       if (options) {
         message.grpcWriteFlags = options.flags;
       }
-      start_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
+      start_batch[grpc.opType.SEND_INITIAL_METADATA] =
+          metadata._getCoreRepresentation();
       start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
       start_batch[grpc.opType.SEND_MESSAGE] = message;
       start_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
@@ -431,11 +447,14 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
           // in the other batch.
           return;
         }
-        stream.emit('metadata', response.metadata);
+        stream.emit('metadata', Metadata._fromCoreRepresentation(
+            response.metadata));
       });
       var status_batch = {};
       status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
       call.startBatch(status_batch, function(err, response) {
+        response.status.metadata = Metadata._fromCoreRepresentation(
+              response.status.metadata);
         stream.emit('status', response.status);
         if (response.status.code !== grpc.status.OK) {
           var error = new Error(response.status.details);
@@ -470,7 +489,7 @@ function makeBidiStreamRequestFunction(method, serialize, deserialize) {
   /**
    * Make a bidirectional stream request with this method on the given channel.
    * @this {SurfaceClient} Client object. Must have a channel member.
-   * @param {array=} metadata Array of metadata key/value pairs to add to the
+   * @param {Metadata=} metadata Array of metadata key/value pairs to add to the
    *     call
    * @param {Options} options Options map
    * @return {EventEmitter} An event emitter for stream related events
@@ -479,7 +498,9 @@ function makeBidiStreamRequestFunction(method, serialize, deserialize) {
     /* jshint validthis: true */
     var call = getCall(this.channel, method, options);
     if (metadata === null || metadata === undefined) {
-      metadata = {};
+      metadata = new Metadata();
+    } else {
+      metadata = metadata.clone();
     }
     var stream = new ClientDuplexStream(call, serialize, deserialize);
     this.updateMetadata(this.auth_uri, metadata, function(error, metadata) {
@@ -489,7 +510,8 @@ function makeBidiStreamRequestFunction(method, serialize, deserialize) {
         return;
       }
       var start_batch = {};
-      start_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
+      start_batch[grpc.opType.SEND_INITIAL_METADATA] =
+          metadata._getCoreRepresentation();
       start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
       call.startBatch(start_batch, function(err, response) {
         if (err) {
@@ -497,11 +519,14 @@ function makeBidiStreamRequestFunction(method, serialize, deserialize) {
           // in the other batch.
           return;
         }
-        stream.emit('metadata', response.metadata);
+        stream.emit('metadata', Metadata._fromCoreRepresentation(
+            response.metadata));
       });
       var status_batch = {};
       status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
       call.startBatch(status_batch, function(err, response) {
+        response.status.metadata = Metadata._fromCoreRepresentation(
+              response.status.metadata);
         stream.emit('status', response.status);
         if (response.status.code !== grpc.status.OK) {
           var error = new Error(response.status.details);
diff --git a/src/node/src/metadata.js b/src/node/src/metadata.js
new file mode 100644
index 0000000000000000000000000000000000000000..c1da70b1974f61aaef7e529a9f31de78ce46751e
--- /dev/null
+++ b/src/node/src/metadata.js
@@ -0,0 +1,181 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+/**
+ * Metadata module
+ * @module
+ */
+
+'use strict';
+
+var _ = require('lodash');
+
+/**
+ * Class for storing metadata. Keys are normalized to lowercase ASCII.
+ * @constructor
+ */
+function Metadata() {
+  this._internal_repr = {};
+}
+
+function normalizeKey(key) {
+  if (!(/^[A-Za-z\d_-]+$/.test(key))) {
+    throw new Error('Metadata keys must be nonempty strings containing only ' +
+        'alphanumeric characters and hyphens');
+  }
+  return key.toLowerCase();
+}
+
+function validate(key, value) {
+  if (_.endsWith(key, '-bin')) {
+    if (!(value instanceof Buffer)) {
+      throw new Error('keys that end with \'-bin\' must have Buffer values');
+    }
+  } else {
+    if (!_.isString(value)) {
+      throw new Error(
+          'keys that don\'t end with \'-bin\' must have String values');
+    }
+    if (!(/^[\x20-\x7E]*$/.test(value))) {
+      throw new Error('Metadata string values can only contain printable ' +
+          'ASCII characters and space');
+    }
+  }
+}
+
+/**
+ * Sets the given value for the given key, replacing any other values associated
+ * with that key. Normalizes the key.
+ * @param {String} key The key to set
+ * @param {String|Buffer} value The value to set. Must be a buffer if and only
+ *     if the normalized key ends with '-bin'
+ */
+Metadata.prototype.set = function(key, value) {
+  key = normalizeKey(key);
+  validate(key, value);
+  this._internal_repr[key] = [value];
+};
+
+/**
+ * Adds the given value for the given key. Normalizes the key.
+ * @param {String} key The key to add to.
+ * @param {String|Buffer} value The value to add. Must be a buffer if and only
+ *     if the normalized key ends with '-bin'
+ */
+Metadata.prototype.add = function(key, value) {
+  key = normalizeKey(key);
+  validate(key, value);
+  if (!this._internal_repr[key]) {
+    this._internal_repr[key] = [];
+  }
+  this._internal_repr[key].push(value);
+};
+
+/**
+ * Remove the given key and any associated values. Normalizes the key.
+ * @param {String} key The key to remove
+ */
+Metadata.prototype.remove = function(key) {
+  key = normalizeKey(key);
+  if (Object.prototype.hasOwnProperty.call(this._internal_repr, key)) {
+    delete this._internal_repr[key];
+  }
+};
+
+/**
+ * Gets a list of all values associated with the key. Normalizes the key.
+ * @param {String} key The key to get
+ * @return {Array.<String|Buffer>} The values associated with that key
+ */
+Metadata.prototype.get = function(key) {
+  key = normalizeKey(key);
+  if (Object.prototype.hasOwnProperty.call(this._internal_repr, key)) {
+    return this._internal_repr[key];
+  } else {
+    return [];
+  }
+};
+
+/**
+ * Get a map of each key to a single associated value. This reflects the most
+ * common way that people will want to see metadata.
+ * @return {Object.<String,String|Buffer>} A key/value mapping of the metadata
+ */
+Metadata.prototype.getMap = function() {
+  var result = {};
+  _.forOwn(this._internal_repr, function(values, key) {
+    if(values.length > 0) {
+      result[key] = values[0];
+    }
+  });
+  return result;
+};
+
+/**
+ * Clone the metadata object.
+ * @return {Metadata} The new cloned object
+ */
+Metadata.prototype.clone = function() {
+  var copy = new Metadata();
+  _.forOwn(this._internal_repr, function(value, key) {
+    copy._internal_repr[key] = _.clone(value);
+  });
+  return copy;
+};
+
+/**
+ * Gets the metadata in the format used by interal code. Intended for internal
+ * use only. API stability is not guaranteed.
+ * @private
+ * @return {Object.<String, Array.<String|Buffer>>} The metadata
+ */
+Metadata.prototype._getCoreRepresentation = function() {
+  return this._internal_repr;
+};
+
+/**
+ * Creates a Metadata object from a metadata map in the internal format.
+ * Intended for internal use only. API stability is not guaranteed.
+ * @private
+ * @param {Object.<String, Array.<String|Buffer>>} The metadata
+ * @return {Metadata} The new Metadata object
+ */
+Metadata._fromCoreRepresentation = function(metadata) {
+  var newMetadata = new Metadata();
+  if (metadata) {
+    newMetadata._internal_repr = _.cloneDeep(metadata);
+  }
+  return newMetadata;
+};
+
+module.exports = Metadata;
diff --git a/src/node/src/server.js b/src/node/src/server.js
index 137f60ed1220d62093deadcd3e2e5727994560c2..b6f162adf85ccdcbb0029d742288c7e84b81feec 100644
--- a/src/node/src/server.js
+++ b/src/node/src/server.js
@@ -44,6 +44,8 @@ var grpc = require('bindings')('grpc.node');
 
 var common = require('./common');
 
+var Metadata = require('./metadata');
+
 var stream = require('stream');
 
 var Readable = stream.Readable;
@@ -60,10 +62,10 @@ var EventEmitter = require('events').EventEmitter;
  * @param {Object} error The error object
  */
 function handleError(call, error) {
+  var statusMetadata = new Metadata();
   var status = {
     code: grpc.status.UNKNOWN,
-    details: 'Unknown Error',
-    metadata: {}
+    details: 'Unknown Error'
   };
   if (error.hasOwnProperty('message')) {
     status.details = error.message;
@@ -75,11 +77,13 @@ function handleError(call, error) {
     }
   }
   if (error.hasOwnProperty('metadata')) {
-    status.metadata = error.metadata;
+    statusMetadata = error.metadata;
   }
+  status.metadata = statusMetadata._getCoreRepresentation();
   var error_batch = {};
   if (!call.metadataSent) {
-    error_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
+    error_batch[grpc.opType.SEND_INITIAL_METADATA] =
+        (new Metadata())._getCoreRepresentation();
   }
   error_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = status;
   call.startBatch(error_batch, function(){});
@@ -114,22 +118,24 @@ function waitForCancel(call, emitter) {
  * @param {*} value The value to respond with
  * @param {function(*):Buffer=} serialize Serialization function for the
  *     response
- * @param {Object=} metadata Optional trailing metadata to send with status
+ * @param {Metadata=} metadata Optional trailing metadata to send with status
  * @param {number=} flags Flags for modifying how the message is sent.
  *     Defaults to 0.
  */
 function sendUnaryResponse(call, value, serialize, metadata, flags) {
   var end_batch = {};
+  var statusMetadata = new Metadata();
   var status = {
     code: grpc.status.OK,
-    details: 'OK',
-    metadata: {}
+    details: 'OK'
   };
   if (metadata) {
-    status.metadata = metadata;
+    statusMetadata = metadata;
   }
+  status.metadata = statusMetadata._getCoreRepresentation();
   if (!call.metadataSent) {
-    end_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
+    end_batch[grpc.opType.SEND_INITIAL_METADATA] =
+        (new Metadata())._getCoreRepresentation();
     call.metadataSent = true;
   }
   var message = serialize(value);
@@ -151,14 +157,19 @@ function setUpWritable(stream, serialize) {
   stream.status = {
     code : grpc.status.OK,
     details : 'OK',
-    metadata : {}
+    metadata : new Metadata()
   };
   stream.serialize = common.wrapIgnoreNull(serialize);
   function sendStatus() {
     var batch = {};
     if (!stream.call.metadataSent) {
       stream.call.metadataSent = true;
-      batch[grpc.opType.SEND_INITIAL_METADATA] = {};
+      batch[grpc.opType.SEND_INITIAL_METADATA] =
+          (new Metadata())._getCoreRepresentation();
+    }
+
+    if (stream.status.metadata) {
+      stream.status.metadata = stream.status.metadata._getCoreRepresentation();
     }
     batch[grpc.opType.SEND_STATUS_FROM_SERVER] = stream.status;
     stream.call.startBatch(batch, function(){});
@@ -173,7 +184,7 @@ function setUpWritable(stream, serialize) {
   function setStatus(err) {
     var code = grpc.status.UNKNOWN;
     var details = 'Unknown Error';
-    var metadata = {};
+    var metadata = new Metadata();
     if (err.hasOwnProperty('message')) {
       details = err.message;
     }
@@ -203,7 +214,7 @@ function setUpWritable(stream, serialize) {
   /**
    * Override of Writable#end method that allows for sending metadata with a
    * success status.
-   * @param {Object=} metadata Metadata to send with the status
+   * @param {Metadata=} metadata Metadata to send with the status
    */
   stream.end = function(metadata) {
     if (metadata) {
@@ -266,7 +277,8 @@ function _write(chunk, encoding, callback) {
   /* jshint validthis: true */
   var batch = {};
   if (!this.call.metadataSent) {
-    batch[grpc.opType.SEND_INITIAL_METADATA] = {};
+    batch[grpc.opType.SEND_INITIAL_METADATA] =
+        (new Metadata())._getCoreRepresentation();
     this.call.metadataSent = true;
   }
   var message = this.serialize(chunk);
@@ -289,15 +301,15 @@ ServerWritableStream.prototype._write = _write;
 
 /**
  * Send the initial metadata for a writable stream.
- * @param {Object<String, Array<(String|Buffer)>>} responseMetadata Metadata
- *   to send
+ * @param {Metadata} responseMetadata Metadata to send
  */
 function sendMetadata(responseMetadata) {
   /* jshint validthis: true */
   if (!this.call.metadataSent) {
     this.call.metadataSent = true;
     var batch = [];
-    batch[grpc.opType.SEND_INITIAL_METADATA] = responseMetadata;
+    batch[grpc.opType.SEND_INITIAL_METADATA] =
+        responseMetadata._getCoreRepresentation();
     this.call.startBatch(batch, function(err) {
       if (err) {
         this.emit('error', err);
@@ -422,7 +434,7 @@ ServerDuplexStream.prototype.getPeer = getPeer;
  * @access private
  * @param {grpc.Call} call The call to handle
  * @param {Object} handler Request handler object for the method that was called
- * @param {Object} metadata Metadata from the client
+ * @param {Metadata} metadata Metadata from the client
  */
 function handleUnary(call, handler, metadata) {
   var emitter = new EventEmitter();
@@ -430,7 +442,8 @@ function handleUnary(call, handler, metadata) {
     if (!call.metadataSent) {
       call.metadataSent = true;
       var batch = {};
-      batch[grpc.opType.SEND_INITIAL_METADATA] = responseMetadata;
+      batch[grpc.opType.SEND_INITIAL_METADATA] =
+          responseMetadata._getCoreRepresentation();
       call.startBatch(batch, function() {});
     }
   };
@@ -478,7 +491,7 @@ function handleUnary(call, handler, metadata) {
  * @access private
  * @param {grpc.Call} call The call to handle
  * @param {Object} handler Request handler object for the method that was called
- * @param {Object} metadata Metadata from the client
+ * @param {Metadata} metadata Metadata from the client
  */
 function handleServerStreaming(call, handler, metadata) {
   var stream = new ServerWritableStream(call, handler.serialize);
@@ -507,7 +520,7 @@ function handleServerStreaming(call, handler, metadata) {
  * @access private
  * @param {grpc.Call} call The call to handle
  * @param {Object} handler Request handler object for the method that was called
- * @param {Object} metadata Metadata from the client
+ * @param {Metadata} metadata Metadata from the client
  */
 function handleClientStreaming(call, handler, metadata) {
   var stream = new ServerReadableStream(call, handler.deserialize);
@@ -515,7 +528,8 @@ function handleClientStreaming(call, handler, metadata) {
     if (!call.metadataSent) {
       call.metadataSent = true;
       var batch = {};
-      batch[grpc.opType.SEND_INITIAL_METADATA] = responseMetadata;
+      batch[grpc.opType.SEND_INITIAL_METADATA] =
+          responseMetadata._getCoreRepresentation();
       call.startBatch(batch, function() {});
     }
   };
@@ -542,7 +556,7 @@ function handleClientStreaming(call, handler, metadata) {
  * @access private
  * @param {grpc.Call} call The call to handle
  * @param {Object} handler Request handler object for the method that was called
- * @param {Object} metadata Metadata from the client
+ * @param {Metadata} metadata Metadata from the client
  */
 function handleBidiStreaming(call, handler, metadata) {
   var stream = new ServerDuplexStream(call, handler.serialize,
@@ -599,7 +613,7 @@ function Server(options) {
       var details = event.new_call;
       var call = details.call;
       var method = details.method;
-      var metadata = details.metadata;
+      var metadata = Metadata._fromCoreRepresentation(details.metadata);
       if (method === null) {
         return;
       }
@@ -609,7 +623,8 @@ function Server(options) {
         handler = handlers[method];
       } else {
         var batch = {};
-        batch[grpc.opType.SEND_INITIAL_METADATA] = {};
+        batch[grpc.opType.SEND_INITIAL_METADATA] =
+            (new Metadata())._getCoreRepresentation();
         batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
           code: grpc.status.UNIMPLEMENTED,
           details: 'This method is not available on this server.',
diff --git a/src/node/test/metadata_test.js b/src/node/test/metadata_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..86383f1badc719189ca711c9008065a165955542
--- /dev/null
+++ b/src/node/test/metadata_test.js
@@ -0,0 +1,193 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+'use strict';
+
+var Metadata = require('../src/metadata.js');
+
+var assert = require('assert');
+
+describe('Metadata', function() {
+  var metadata;
+  beforeEach(function() {
+    metadata = new Metadata();
+  });
+  describe('#set', function() {
+    it('Only accepts string values for non "-bin" keys', function() {
+      assert.throws(function() {
+        metadata.set('key', new Buffer('value'));
+      });
+      assert.doesNotThrow(function() {
+        metadata.set('key', 'value');
+      });
+    });
+    it('Only accepts Buffer values for "-bin" keys', function() {
+      assert.throws(function() {
+        metadata.set('key-bin', 'value');
+      });
+      assert.doesNotThrow(function() {
+        metadata.set('key-bin', new Buffer('value'));
+      });
+    });
+    it('Rejects invalid keys', function() {
+      assert.throws(function() {
+        metadata.set('key$', 'value');
+      });
+      assert.throws(function() {
+        metadata.set('', 'value');
+      });
+    });
+    it('Rejects values with non-ASCII characters', function() {
+      assert.throws(function() {
+        metadata.set('key', 'résumé');
+      });
+    });
+    it('Saves values that can be retrieved', function() {
+      metadata.set('key', 'value');
+      assert.deepEqual(metadata.get('key'), ['value']);
+    });
+    it('Overwrites previous values', function() {
+      metadata.set('key', 'value1');
+      metadata.set('key', 'value2');
+      assert.deepEqual(metadata.get('key'), ['value2']);
+    });
+    it('Normalizes keys', function() {
+      metadata.set('Key', 'value1');
+      assert.deepEqual(metadata.get('key'), ['value1']);
+      metadata.set('KEY', 'value2');
+      assert.deepEqual(metadata.get('key'), ['value2']);
+    });
+  });
+  describe('#add', function() {
+    it('Only accepts string values for non "-bin" keys', function() {
+      assert.throws(function() {
+        metadata.add('key', new Buffer('value'));
+      });
+      assert.doesNotThrow(function() {
+        metadata.add('key', 'value');
+      });
+    });
+    it('Only accepts Buffer values for "-bin" keys', function() {
+      assert.throws(function() {
+        metadata.add('key-bin', 'value');
+      });
+      assert.doesNotThrow(function() {
+        metadata.add('key-bin', new Buffer('value'));
+      });
+    });
+    it('Rejects invalid keys', function() {
+      assert.throws(function() {
+        metadata.add('key$', 'value');
+      });
+      assert.throws(function() {
+        metadata.add('', 'value');
+      });
+    });
+    it('Saves values that can be retrieved', function() {
+      metadata.add('key', 'value');
+      assert.deepEqual(metadata.get('key'), ['value']);
+    });
+    it('Combines with previous values', function() {
+      metadata.add('key', 'value1');
+      metadata.add('key', 'value2');
+      assert.deepEqual(metadata.get('key'), ['value1', 'value2']);
+    });
+    it('Normalizes keys', function() {
+      metadata.add('Key', 'value1');
+      assert.deepEqual(metadata.get('key'), ['value1']);
+      metadata.add('KEY', 'value2');
+      assert.deepEqual(metadata.get('key'), ['value1', 'value2']);
+    });
+  });
+  describe('#remove', function() {
+    it('clears values from a key', function() {
+      metadata.add('key', 'value');
+      metadata.remove('key');
+      assert.deepEqual(metadata.get('key'), []);
+    });
+    it('Normalizes keys', function() {
+      metadata.add('key', 'value');
+      metadata.remove('KEY');
+      assert.deepEqual(metadata.get('key'), []);
+    });
+  });
+  describe('#get', function() {
+    beforeEach(function() {
+      metadata.add('key', 'value1');
+      metadata.add('key', 'value2');
+      metadata.add('key-bin', new Buffer('value'));
+    });
+    it('gets all values associated with a key', function() {
+      assert.deepEqual(metadata.get('key'), ['value1', 'value2']);
+    });
+    it('Normalizes keys', function() {
+      assert.deepEqual(metadata.get('KEY'), ['value1', 'value2']);
+    });
+    it('returns an empty list for non-existent keys', function() {
+      assert.deepEqual(metadata.get('non-existent-key'), []);
+    });
+    it('returns Buffers for "-bin" keys', function() {
+      assert(metadata.get('key-bin')[0] instanceof Buffer);
+    });
+  });
+  describe('#getMap', function() {
+    it('gets a map of keys to values', function() {
+      metadata.add('key1', 'value1');
+      metadata.add('Key2', 'value2');
+      metadata.add('KEY3', 'value3');
+      assert.deepEqual(metadata.getMap(),
+                       {key1: 'value1',
+                        key2: 'value2',
+                        key3: 'value3'});
+    });
+  });
+  describe('#clone', function() {
+    it('retains values from the original', function() {
+      metadata.add('key', 'value');
+      var copy = metadata.clone();
+      assert.deepEqual(copy.get('key'), ['value']);
+    });
+    it('Does not see newly added values', function() {
+      metadata.add('key', 'value1');
+      var copy = metadata.clone();
+      metadata.add('key', 'value2');
+      assert.deepEqual(copy.get('key'), ['value1']);
+    });
+    it('Does not add new values to the original', function() {
+      metadata.add('key', 'value1');
+      var copy = metadata.clone();
+      copy.add('key', 'value2');
+      assert.deepEqual(metadata.get('key'), ['value1']);
+    });
+  });
+});
diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js
index 6e45871fc8e7716faac169260f5dd19c922c92ce..7c2a8d72583dba3372317d719caff010e6737215 100644
--- a/src/node/test/surface_test.js
+++ b/src/node/test/surface_test.js
@@ -262,6 +262,7 @@ describe('Generic client and server', function() {
 describe('Echo metadata', function() {
   var client;
   var server;
+  var metadata;
   before(function() {
     var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
     var test_service = test_proto.lookup('TestService');
@@ -294,6 +295,8 @@ describe('Echo metadata', function() {
     var Client = surface_client.makeProtobufClientConstructor(test_service);
     client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
     server.start();
+    metadata = new grpc.Metadata();
+    metadata.set('key', 'value');
   });
   after(function() {
     server.forceShutdown();
@@ -301,35 +304,35 @@ describe('Echo metadata', function() {
   it('with unary call', function(done) {
     var call = client.unary({}, function(err, data) {
       assert.ifError(err);
-    }, {key: ['value']});
+    }, metadata);
     call.on('metadata', function(metadata) {
-      assert.deepEqual(metadata.key, ['value']);
+      assert.deepEqual(metadata.get('key'), ['value']);
       done();
     });
   });
   it('with client stream call', function(done) {
     var call = client.clientStream(function(err, data) {
       assert.ifError(err);
-    }, {key: ['value']});
+    }, metadata);
     call.on('metadata', function(metadata) {
-      assert.deepEqual(metadata.key, ['value']);
+      assert.deepEqual(metadata.get('key'), ['value']);
       done();
     });
     call.end();
   });
   it('with server stream call', function(done) {
-    var call = client.serverStream({}, {key: ['value']});
+    var call = client.serverStream({}, metadata);
     call.on('data', function() {});
     call.on('metadata', function(metadata) {
-      assert.deepEqual(metadata.key, ['value']);
+      assert.deepEqual(metadata.get('key'), ['value']);
       done();
     });
   });
   it('with bidi stream call', function(done) {
-    var call = client.bidiStream({key: ['value']});
+    var call = client.bidiStream(metadata);
     call.on('data', function() {});
     call.on('metadata', function(metadata) {
-      assert.deepEqual(metadata.key, ['value']);
+      assert.deepEqual(metadata.get('key'), ['value']);
       done();
     });
     call.end();
@@ -337,9 +340,10 @@ describe('Echo metadata', function() {
   it('shows the correct user-agent string', function(done) {
     var version = require('../package.json').version;
     var call = client.unary({}, function(err, data) { assert.ifError(err); },
-                            {key: ['value']});
+                            metadata);
     call.on('metadata', function(metadata) {
-      assert(_.startsWith(metadata['user-agent'], 'grpc-node/' + version));
+      assert(_.startsWith(metadata.get('user-agent')[0],
+                          'grpc-node/' + version));
       done();
     });
   });
@@ -354,13 +358,15 @@ describe('Other conditions', function() {
     var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
     test_service = test_proto.lookup('TestService');
     server = new grpc.Server();
+    var trailer_metadata = new grpc.Metadata();
+    trailer_metadata.add('trailer-present', 'yes');
     server.addProtoService(test_service, {
       unary: function(call, cb) {
         var req = call.request;
         if (req.error) {
-          cb(new Error('Requested error'), null, {trailer_present: ['yes']});
+          cb(new Error('Requested error'), null, trailer_metadata);
         } else {
-          cb(null, {count: 1}, {trailer_present: ['yes']});
+          cb(null, {count: 1}, trailer_metadata);
         }
       },
       clientStream: function(stream, cb){
@@ -369,14 +375,14 @@ describe('Other conditions', function() {
         stream.on('data', function(data) {
           if (data.error) {
             errored = true;
-            cb(new Error('Requested error'), null, {trailer_present: ['yes']});
+            cb(new Error('Requested error'), null, trailer_metadata);
           } else {
             count += 1;
           }
         });
         stream.on('end', function() {
           if (!errored) {
-            cb(null, {count: count}, {trailer_present: ['yes']});
+            cb(null, {count: count}, trailer_metadata);
           }
         });
       },
@@ -384,13 +390,13 @@ describe('Other conditions', function() {
         var req = stream.request;
         if (req.error) {
           var err = new Error('Requested error');
-          err.metadata = {trailer_present: ['yes']};
+          err.metadata = trailer_metadata;
           stream.emit('error', err);
         } else {
           for (var i = 0; i < 5; i++) {
             stream.write({count: i});
           }
-          stream.end({trailer_present: ['yes']});
+          stream.end(trailer_metadata);
         }
       },
       bidiStream: function(stream) {
@@ -398,10 +404,8 @@ describe('Other conditions', function() {
         stream.on('data', function(data) {
           if (data.error) {
             var err = new Error('Requested error');
-            err.metadata = {
-              trailer_present: ['yes'],
-              count: ['' + count]
-            };
+            err.metadata = trailer_metadata.clone();
+            err.metadata.add('count', '' + count);
             stream.emit('error', err);
           } else {
             stream.write({count: count});
@@ -409,7 +413,7 @@ describe('Other conditions', function() {
           }
         });
         stream.on('end', function() {
-          stream.end({trailer_present: ['yes']});
+          stream.end(trailer_metadata);
         });
       }
     });
@@ -510,7 +514,7 @@ describe('Other conditions', function() {
         assert.ifError(err);
       });
       call.on('status', function(status) {
-        assert.deepEqual(status.metadata.trailer_present, ['yes']);
+        assert.deepEqual(status.metadata.get('trailer-present'), ['yes']);
         done();
       });
     });
@@ -519,7 +523,7 @@ describe('Other conditions', function() {
         assert(err);
       });
       call.on('status', function(status) {
-        assert.deepEqual(status.metadata.trailer_present, ['yes']);
+        assert.deepEqual(status.metadata.get('trailer-present'), ['yes']);
         done();
       });
     });
@@ -531,7 +535,7 @@ describe('Other conditions', function() {
       call.write({error: false});
       call.end();
       call.on('status', function(status) {
-        assert.deepEqual(status.metadata.trailer_present, ['yes']);
+        assert.deepEqual(status.metadata.get('trailer-present'), ['yes']);
         done();
       });
     });
@@ -543,7 +547,7 @@ describe('Other conditions', function() {
       call.write({error: true});
       call.end();
       call.on('status', function(status) {
-        assert.deepEqual(status.metadata.trailer_present, ['yes']);
+        assert.deepEqual(status.metadata.get('trailer-present'), ['yes']);
         done();
       });
     });
@@ -552,7 +556,7 @@ describe('Other conditions', function() {
       call.on('data', function(){});
       call.on('status', function(status) {
         assert.strictEqual(status.code, grpc.status.OK);
-        assert.deepEqual(status.metadata.trailer_present, ['yes']);
+        assert.deepEqual(status.metadata.get('trailer-present'), ['yes']);
         done();
       });
     });
@@ -560,7 +564,7 @@ describe('Other conditions', function() {
       var call = client.serverStream({error: true});
       call.on('data', function(){});
       call.on('error', function(error) {
-        assert.deepEqual(error.metadata.trailer_present, ['yes']);
+        assert.deepEqual(error.metadata.get('trailer-present'), ['yes']);
         done();
       });
     });
@@ -572,7 +576,7 @@ describe('Other conditions', function() {
       call.on('data', function(){});
       call.on('status', function(status) {
         assert.strictEqual(status.code, grpc.status.OK);
-        assert.deepEqual(status.metadata.trailer_present, ['yes']);
+        assert.deepEqual(status.metadata.get('trailer-present'), ['yes']);
         done();
       });
     });
@@ -583,7 +587,7 @@ describe('Other conditions', function() {
       call.end();
       call.on('data', function(){});
       call.on('error', function(error) {
-        assert.deepEqual(error.metadata.trailer_present, ['yes']);
+        assert.deepEqual(error.metadata.get('trailer-present'), ['yes']);
         done();
       });
     });
diff --git a/src/python/grpcio/grpc/_adapter/_intermediary_low.py b/src/python/grpcio/grpc/_adapter/_intermediary_low.py
index 1fb6a2b27dad79ed352bfc31eab542aa2eef6097..06358e72bc27b2d0b116b5ef506b6ad9eca966f5 100644
--- a/src/python/grpcio/grpc/_adapter/_intermediary_low.py
+++ b/src/python/grpcio/grpc/_adapter/_intermediary_low.py
@@ -255,6 +255,6 @@ class ClientCredentials(object):
 class ServerCredentials(object):
   """Adapter from old _low.ServerCredentials interface to new _low.ServerCredentials."""
 
-  def __init__(self, root_credentials, pair_sequence):
+  def __init__(self, root_credentials, pair_sequence, force_client_auth):
     self._internal = _low.ServerCredentials.ssl(
-        root_credentials, list(pair_sequence), False)
+        root_credentials, list(pair_sequence), force_client_auth)
diff --git a/src/python/grpcio/grpc/_adapter/fore.py b/src/python/grpcio/grpc/_adapter/fore.py
index 7d88bda26312eb97aa3e7abed385b523bc38f792..daa41e8bde530637603d9ffb436240c238ff4304 100644
--- a/src/python/grpcio/grpc/_adapter/fore.py
+++ b/src/python/grpcio/grpc/_adapter/fore.py
@@ -288,7 +288,7 @@ class ForeLink(base_interfaces.ForeLink, activated.Activated):
         self._port = self._server.add_http2_addr(address)
       else:
         server_credentials = _low.ServerCredentials(
-          self._root_certificates, self._key_chain_pairs)
+          self._root_certificates, self._key_chain_pairs, False)
         self._server = _low.Server(self._completion_queue)
         self._port = self._server.add_secure_http2_addr(
             address, server_credentials)
diff --git a/src/python/grpcio/grpc/_links/service.py b/src/python/grpcio/grpc/_links/service.py
index 43c4c0e80cac7d7f3814047b0cfe37bc3e983bfe..393f80c1cc3afc1a64faaf48fa1cdeebb066e967 100644
--- a/src/python/grpcio/grpc/_links/service.py
+++ b/src/python/grpcio/grpc/_links/service.py
@@ -316,9 +316,8 @@ class _Kernel(object):
         call.status(status, call)
         self._rpc_states.pop(call, None)
 
-  def add_port(self, port, server_credentials):
+  def add_port(self, address, server_credentials):
     with self._lock:
-      address = '[::]:%d' % port
       if self._server is None:
         self._completion_queue = _intermediary_low.CompletionQueue()
         self._server = _intermediary_low.Server(self._completion_queue)
@@ -362,17 +361,20 @@ class ServiceLink(links.Link):
   """
 
   @abc.abstractmethod
-  def add_port(self, port, server_credentials):
+  def add_port(self, address, server_credentials):
     """Adds a port on which to service RPCs after this link has been started.
 
     Args:
-      port: The port on which to service RPCs, or zero to request that a port be
-        automatically selected and used.
-      server_credentials: A ServerCredentials object, or None for insecure
-        service.
+      address: The address on which to service RPCs with a port number of zero
+        requesting that a port number be automatically selected and used.
+      server_credentials: An _intermediary_low.ServerCredentials object, or
+        None for insecure service.
 
     Returns:
-      A port on which RPCs will be serviced after this link has been started.
+      A integer port on which RPCs will be serviced after this link has been
+        started. This is typically the same number as the port number contained
+        in the passed address, but will likely be different if the port number
+        contained in the passed address was zero.
     """
     raise NotImplementedError()
 
@@ -417,8 +419,8 @@ class _ServiceLink(ServiceLink):
   def join_link(self, link):
     self._relay.set_behavior(link.accept_ticket)
 
-  def add_port(self, port, server_credentials):
-    return self._kernel.add_port(port, server_credentials)
+  def add_port(self, address, server_credentials):
+    return self._kernel.add_port(address, server_credentials)
 
   def start(self):
     self._relay.start()
diff --git a/src/python/grpcio/grpc/framework/core/_end.py b/src/python/grpcio/grpc/framework/core/_end.py
index fb2c532df6147c917f0705c0189ee8eabf4cece4..5ef2f6d3a36728ab8d12c85b13bb0a05b873a7b9 100644
--- a/src/python/grpcio/grpc/framework/core/_end.py
+++ b/src/python/grpcio/grpc/framework/core/_end.py
@@ -30,7 +30,6 @@
 """Implementation of base.End."""
 
 import abc
-import enum
 import threading
 import uuid
 
@@ -75,7 +74,7 @@ def _abort(operations):
 
 def _cancel_futures(futures):
   for future in futures:
-    futures.cancel()
+    future.cancel()
 
 
 def _future_shutdown(lock, cycle, event):
@@ -83,8 +82,6 @@ def _future_shutdown(lock, cycle, event):
     with lock:
       _abort(cycle.operations.values())
       _cancel_futures(cycle.futures)
-      pool = cycle.pool
-    cycle.pool.shutdown(wait=True)
   return in_future
 
 
@@ -113,6 +110,7 @@ def _termination_action(lock, stats, operation_id, cycle):
         cycle.idle_actions = []
         if cycle.grace:
           _cancel_futures(cycle.futures)
+          cycle.pool.shutdown(wait=False)
   return termination_action
 
 
diff --git a/src/python/grpcio_test/grpc_interop/client.py b/src/python/grpcio_test/grpc_interop/client.py
index 2dd2103cbec4caabbfc22db1c35a464c5e85f41c..36afe6c09654df74f295416a8181189bc1b80e29 100644
--- a/src/python/grpcio_test/grpc_interop/client.py
+++ b/src/python/grpcio_test/grpc_interop/client.py
@@ -70,7 +70,13 @@ def _oauth_access_token(args):
 
 def _stub(args):
   if args.oauth_scope:
-    metadata_transformer = lambda x: [('Authorization', 'Bearer %s' % _oauth_access_token(args))]
+    if args.test_case == 'oauth2_auth_token':
+      access_token = _oauth_access_token(args)
+      metadata_transformer = lambda x: [
+          ('Authorization', 'Bearer %s' % access_token)]
+    else:
+      metadata_transformer = lambda x: [
+          ('Authorization', 'Bearer %s' % _oauth_access_token(args))]
   else:
     metadata_transformer = lambda x: []
   if args.use_tls:
diff --git a/src/python/grpcio_test/grpc_interop/methods.py b/src/python/grpcio_test/grpc_interop/methods.py
index 19a1e17c3e5f55e43b81f58d2fd323ae4e897fb4..52b800af7a182dc814cdbc406625dd4ea39e9962 100644
--- a/src/python/grpcio_test/grpc_interop/methods.py
+++ b/src/python/grpcio_test/grpc_interop/methods.py
@@ -346,6 +346,19 @@ def _compute_engine_creds(stub, args):
                                           response.username))
 
 
+def _oauth2_auth_token(stub, args):
+  json_key_filename = os.environ[
+      oauth2client_client.GOOGLE_APPLICATION_CREDENTIALS]
+  wanted_email = json.load(open(json_key_filename, 'rb'))['client_email']
+  response = _large_unary_common_behavior(stub, True, True)
+  if wanted_email != response.username:
+    raise ValueError(
+        'expected username %s, got %s' % (wanted_email, response.username))
+  if args.oauth_scope.find(response.oauth_scope) == -1:
+    raise ValueError(
+        'expected to find oauth scope "%s" in received "%s"' %
+            (response.oauth_scope, args.oauth_scope))
+
 @enum.unique
 class TestCase(enum.Enum):
   EMPTY_UNARY = 'empty_unary'
@@ -356,6 +369,7 @@ class TestCase(enum.Enum):
   CANCEL_AFTER_BEGIN = 'cancel_after_begin'
   CANCEL_AFTER_FIRST_RESPONSE = 'cancel_after_first_response'
   COMPUTE_ENGINE_CREDS = 'compute_engine_creds'
+  OAUTH2_AUTH_TOKEN = 'oauth2_auth_token'
   TIMEOUT_ON_SLEEPING_SERVER = 'timeout_on_sleeping_server'
 
   def test_interoperability(self, stub, args):
@@ -377,5 +391,7 @@ class TestCase(enum.Enum):
       _timeout_on_sleeping_server(stub)
     elif self is TestCase.COMPUTE_ENGINE_CREDS:
       _compute_engine_creds(stub, args)
+    elif self is TestCase.OAUTH2_AUTH_TOKEN:
+      _oauth2_auth_token(stub, args)
     else:
       raise NotImplementedError('Test case "%s" not implemented!' % self.name)
diff --git a/src/python/grpcio_test/grpc_test/_core_over_links_base_interface_test.py b/src/python/grpcio_test/grpc_test/_core_over_links_base_interface_test.py
index 7fa90fe35f03b39e3f43e3d6942cea2e6de44f6f..5ed5ec0b9a976bdddc2bd61267100bca12cfc16b 100644
--- a/src/python/grpcio_test/grpc_test/_core_over_links_base_interface_test.py
+++ b/src/python/grpcio_test/grpc_test/_core_over_links_base_interface_test.py
@@ -45,11 +45,7 @@ from grpc_test.framework.common import test_constants
 from grpc_test.framework.interfaces.base import test_cases
 from grpc_test.framework.interfaces.base import test_interfaces
 
-_INVOCATION_INITIAL_METADATA = ((b'0', b'abc'), (b'1', b'def'), (b'2', b'ghi'),)
-_SERVICE_INITIAL_METADATA = ((b'3', b'jkl'), (b'4', b'mno'), (b'5', b'pqr'),)
-_SERVICE_TERMINAL_METADATA = ((b'6', b'stu'), (b'7', b'vwx'), (b'8', b'yza'),)
 _CODE = _intermediary_low.Code.OK
-_MESSAGE = b'test message'
 
 
 class _SerializationBehaviors(
@@ -95,7 +91,7 @@ class _Implementation(test_interfaces.Implementation):
     service_grpc_link = service.service_link(
         serialization_behaviors.request_deserializers,
         serialization_behaviors.response_serializers)
-    port = service_grpc_link.add_port(0, None)
+    port = service_grpc_link.add_port('[::]:0', None)
     channel = _intermediary_low.Channel('localhost:%d' % port, None)
     invocation_grpc_link = invocation.invocation_link(
         channel, b'localhost',
@@ -117,16 +113,18 @@ class _Implementation(test_interfaces.Implementation):
     service_grpc_link.stop_gracefully()
 
   def invocation_initial_metadata(self):
-    return _INVOCATION_INITIAL_METADATA
+    return grpc_test_common.INVOCATION_INITIAL_METADATA
 
   def service_initial_metadata(self):
-    return _SERVICE_INITIAL_METADATA
+    return grpc_test_common.SERVICE_INITIAL_METADATA
 
   def invocation_completion(self):
     return utilities.completion(None, None, None)
 
   def service_completion(self):
-    return utilities.completion(_SERVICE_TERMINAL_METADATA, _CODE, _MESSAGE)
+    return utilities.completion(
+        grpc_test_common.SERVICE_TERMINAL_METADATA, _CODE,
+        grpc_test_common.DETAILS)
 
   def metadata_transmitted(self, original_metadata, transmitted_metadata):
     return original_metadata is None or grpc_test_common.metadata_transmitted(
@@ -146,14 +144,6 @@ class _Implementation(test_interfaces.Implementation):
       return True
 
 
-def setUpModule():
-  logging.warn('setUpModule!')
-
-
-def tearDownModule():
-  logging.warn('tearDownModule!')
-
-
 def load_tests(loader, tests, pattern):
   return unittest.TestSuite(
       tests=tuple(
diff --git a/src/python/grpcio_test/grpc_test/_crust_over_core_over_links_face_interface_test.py b/src/python/grpcio_test/grpc_test/_crust_over_core_over_links_face_interface_test.py
index 25b99cbbaf5acb8e7565f3844ea319eef14e62f0..ce7c6f9e7a16229a2a0c832614b3c69fc83fda1b 100644
--- a/src/python/grpcio_test/grpc_test/_crust_over_core_over_links_face_interface_test.py
+++ b/src/python/grpcio_test/grpc_test/_crust_over_core_over_links_face_interface_test.py
@@ -39,11 +39,10 @@ from grpc.framework.core import implementations as core_implementations
 from grpc.framework.crust import implementations as crust_implementations
 from grpc.framework.foundation import logging_pool
 from grpc.framework.interfaces.links import utilities
-from grpc_test import test_common
+from grpc_test import test_common as grpc_test_common
 from grpc_test.framework.common import test_constants
 from grpc_test.framework.interfaces.face import test_cases
 from grpc_test.framework.interfaces.face import test_interfaces
-from grpc_test.framework.interfaces.links import test_utilities
 
 
 class _SerializationBehaviors(
@@ -85,7 +84,7 @@ class _Implementation(test_interfaces.Implementation):
     service_grpc_link = service.service_link(
         serialization_behaviors.request_deserializers,
         serialization_behaviors.response_serializers)
-    port = service_grpc_link.add_port(0, None)
+    port = service_grpc_link.add_port('[::]:0', None)
     channel = _intermediary_low.Channel('localhost:%d' % port, None)
     invocation_grpc_link = invocation.invocation_link(
         channel, b'localhost',
@@ -130,19 +129,19 @@ class _Implementation(test_interfaces.Implementation):
     pool.shutdown(wait=True)
 
   def invocation_metadata(self):
-    return test_common.INVOCATION_INITIAL_METADATA
+    return grpc_test_common.INVOCATION_INITIAL_METADATA
 
   def initial_metadata(self):
-    return test_common.SERVICE_INITIAL_METADATA
+    return grpc_test_common.SERVICE_INITIAL_METADATA
 
   def terminal_metadata(self):
-    return test_common.SERVICE_TERMINAL_METADATA
+    return grpc_test_common.SERVICE_TERMINAL_METADATA
 
   def code(self):
     return _intermediary_low.Code.OK
 
   def details(self):
-    return test_common.DETAILS
+    return grpc_test_common.DETAILS
 
   def metadata_transmitted(self, original_metadata, transmitted_metadata):
     return original_metadata is None or grpc_test_common.metadata_transmitted(
diff --git a/src/python/grpcio_test/grpc_test/_links/_transmission_test.py b/src/python/grpcio_test/grpc_test/_links/_transmission_test.py
index db011bca66d77a08dc33ef4acb5d9d473352c080..0fef9b0c5a1df7876706a3ee292e0b5bb78663a6 100644
--- a/src/python/grpcio_test/grpc_test/_links/_transmission_test.py
+++ b/src/python/grpcio_test/grpc_test/_links/_transmission_test.py
@@ -50,7 +50,7 @@ class TransmissionTest(test_cases.TransmissionTest, unittest.TestCase):
     service_link = service.service_link(
         {self.group_and_method(): self.deserialize_request},
         {self.group_and_method(): self.serialize_response})
-    port = service_link.add_port(0, None)
+    port = service_link.add_port('[::]:0', None)
     service_link.start()
     channel = _intermediary_low.Channel('localhost:%d' % port, None)
     invocation_link = invocation.invocation_link(
@@ -116,7 +116,7 @@ class RoundTripTest(unittest.TestCase):
         identity_transformation, identity_transformation)
     service_mate = test_utilities.RecordingLink()
     service_link.join_link(service_mate)
-    port = service_link.add_port(0, None)
+    port = service_link.add_port('[::]:0', None)
     service_link.start()
     channel = _intermediary_low.Channel('localhost:%d' % port, None)
     invocation_link = invocation.invocation_link(
@@ -160,7 +160,7 @@ class RoundTripTest(unittest.TestCase):
         {(test_group, test_method): scenario.serialize_response})
     service_mate = test_utilities.RecordingLink()
     service_link.join_link(service_mate)
-    port = service_link.add_port(0, None)
+    port = service_link.add_port('[::]:0', None)
     service_link.start()
     channel = _intermediary_low.Channel('localhost:%d' % port, None)
     invocation_link = invocation.invocation_link(
diff --git a/src/python/grpcio_test/grpc_test/test_common.py b/src/python/grpcio_test/grpc_test/test_common.py
index f8e1f1e43ff11d1268d598dd60b6a1bfb5b52d5e..44284be88bbe4b2da908033f6534a8ba1722b990 100644
--- a/src/python/grpcio_test/grpc_test/test_common.py
+++ b/src/python/grpcio_test/grpc_test/test_common.py
@@ -31,6 +31,11 @@
 
 import collections
 
+INVOCATION_INITIAL_METADATA = ((b'0', b'abc'), (b'1', b'def'), (b'2', b'ghi'),)
+SERVICE_INITIAL_METADATA = ((b'3', b'jkl'), (b'4', b'mno'), (b'5', b'pqr'),)
+SERVICE_TERMINAL_METADATA = ((b'6', b'stu'), (b'7', b'vwx'), (b'8', b'yza'),)
+DETAILS = b'test details'
+
 
 def metadata_transmitted(original_metadata, transmitted_metadata):
   """Judges whether or not metadata was acceptably transmitted.
diff --git a/test/core/iomgr/udp_server_test.c b/test/core/iomgr/udp_server_test.c
index 471d5b50c7c67b9de31b138ea3b7cb714a20cc2f..c91752b9373cbcd87840c3ef92dd87880f5fb270 100644
--- a/test/core/iomgr/udp_server_test.c
+++ b/test/core/iomgr/udp_server_test.c
@@ -135,7 +135,7 @@ static void test_receive(int number_of_clients) {
   gpr_mu_lock(GRPC_POLLSET_MU(&g_pollset));
 
   for (i = 0; i < number_of_clients; i++) {
-    deadline = GRPC_TIMEOUT_SECONDS_TO_DEADLINE(4000);
+    deadline = GRPC_TIMEOUT_SECONDS_TO_DEADLINE(10);
 
     number_of_reads_before = g_number_of_reads;
     /* Create a socket, send a packet to the UDP server. */
diff --git a/test/core/util/test_config.h b/test/core/util/test_config.h
index b2cc40bb47bfbd040f90f69f26eb249694eb29b8..ccef8620c13a9b97d47c8389fa51a915a59df89c 100644
--- a/test/core/util/test_config.h
+++ b/test/core/util/test_config.h
@@ -56,7 +56,7 @@ extern double g_fixture_slowdown_factor;
 
 #define GRPC_TIMEOUT_SECONDS_TO_DEADLINE(x)                                \
   gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),                               \
-               gpr_time_from_micros(GRPC_TEST_SLOWDOWN_FACTOR * 1e6 * (x), \
+               gpr_time_from_millis(GRPC_TEST_SLOWDOWN_FACTOR * 1e3 * (x), \
                                     GPR_TIMESPAN))
 
 #define GRPC_TIMEOUT_MILLIS_TO_DEADLINE(x)                                 \
diff --git a/test/cpp/end2end/async_end2end_test.cc b/test/cpp/end2end/async_end2end_test.cc
index 41b91e459bc23ae963b5a343c3e1acaf0009cd0d..bbcac9ba34343a4337515423918dd8d952240f37 100644
--- a/test/cpp/end2end/async_end2end_test.cc
+++ b/test/cpp/end2end/async_end2end_test.cc
@@ -200,8 +200,8 @@ class AsyncEnd2endTest : public ::testing::TestWithParam<bool> {
   }
 
   void ResetStub() {
-    std::shared_ptr<Channel> channel = CreateChannel(
-        server_address_.str(), InsecureCredentials(), ChannelArguments());
+    std::shared_ptr<Channel> channel =
+        CreateChannel(server_address_.str(), InsecureCredentials());
     stub_ = std::move(grpc::cpp::test::util::TestService::NewStub(channel));
   }
 
@@ -750,8 +750,8 @@ TEST_P(AsyncEnd2endTest, ServerCheckDone) {
 }
 
 TEST_P(AsyncEnd2endTest, UnimplementedRpc) {
-  std::shared_ptr<Channel> channel = CreateChannel(
-      server_address_.str(), InsecureCredentials(), ChannelArguments());
+  std::shared_ptr<Channel> channel =
+      CreateChannel(server_address_.str(), InsecureCredentials());
   std::unique_ptr<grpc::cpp::test::util::UnimplementedService::Stub> stub;
   stub =
       std::move(grpc::cpp::test::util::UnimplementedService::NewStub(channel));
diff --git a/test/cpp/end2end/client_crash_test.cc b/test/cpp/end2end/client_crash_test.cc
index 3359080cec50c4cd57a223c7b5d2a4cc74fc54cf..3a6e55216af29b670e3142eda72f912ead416ff4 100644
--- a/test/cpp/end2end/client_crash_test.cc
+++ b/test/cpp/end2end/client_crash_test.cc
@@ -76,7 +76,7 @@ class CrashTest : public ::testing::Test {
     }));
     GPR_ASSERT(server_);
     return grpc::cpp::test::util::TestService::NewStub(
-        CreateChannel(addr, InsecureCredentials(), ChannelArguments()));
+        CreateChannel(addr, InsecureCredentials()));
   }
 
   void KillServer() { server_.reset(); }
diff --git a/test/cpp/end2end/end2end_test.cc b/test/cpp/end2end/end2end_test.cc
index 39eb72a23e1f842c0b6293a75eb81138d558abda..5e2332cbe93b5559e03898b19a184bd9053699aa 100644
--- a/test/cpp/end2end/end2end_test.cc
+++ b/test/cpp/end2end/end2end_test.cc
@@ -291,8 +291,8 @@ class End2endTest : public ::testing::TestWithParam<bool> {
     ChannelArguments args;
     args.SetSslTargetNameOverride("foo.test.google.fr");
     args.SetString(GRPC_ARG_SECONDARY_USER_AGENT_STRING, "end2end_test");
-    channel_ =
-        CreateChannel(server_address_.str(), SslCredentials(ssl_opts), args);
+    channel_ = CreateCustomChannel(server_address_.str(),
+                                   SslCredentials(ssl_opts), args);
   }
 
   void ResetStub(bool use_proxy) {
@@ -307,8 +307,7 @@ class End2endTest : public ::testing::TestWithParam<bool> {
       builder.RegisterService(proxy_service_.get());
       proxy_server_ = builder.BuildAndStart();
 
-      channel_ = CreateChannel(proxyaddr.str(), InsecureCredentials(),
-                               ChannelArguments());
+      channel_ = CreateChannel(proxyaddr.str(), InsecureCredentials());
     }
 
     stub_ = std::move(grpc::cpp::test::util::TestService::NewStub(channel_));
@@ -566,7 +565,7 @@ TEST_F(End2endTest, BadCredentials) {
   std::shared_ptr<Credentials> bad_creds = GoogleRefreshTokenCredentials("");
   EXPECT_EQ(static_cast<Credentials*>(nullptr), bad_creds.get());
   std::shared_ptr<Channel> channel =
-      CreateChannel(server_address_.str(), bad_creds, ChannelArguments());
+      CreateChannel(server_address_.str(), bad_creds);
   std::unique_ptr<grpc::cpp::test::util::TestService::Stub> stub(
       grpc::cpp::test::util::TestService::NewStub(channel));
   EchoRequest request;
diff --git a/test/cpp/end2end/generic_end2end_test.cc b/test/cpp/end2end/generic_end2end_test.cc
index 809eef058cf9d41d88f6ccbf4eaf005d83f8056d..7acbc711fb87d486e461b632bf1fdd3b5bfc623c 100644
--- a/test/cpp/end2end/generic_end2end_test.cc
+++ b/test/cpp/end2end/generic_end2end_test.cc
@@ -121,8 +121,8 @@ class GenericEnd2endTest : public ::testing::Test {
   }
 
   void ResetStub() {
-    std::shared_ptr<Channel> channel = CreateChannel(
-        server_address_.str(), InsecureCredentials(), ChannelArguments());
+    std::shared_ptr<Channel> channel =
+        CreateChannel(server_address_.str(), InsecureCredentials());
     generic_stub_.reset(new GenericStub(channel));
   }
 
diff --git a/test/cpp/end2end/mock_test.cc b/test/cpp/end2end/mock_test.cc
index b2c6dc39a8124ea281d50288e799fa0b15b68921..077d21aa729444612cf45bb6884177cadd0ab798 100644
--- a/test/cpp/end2end/mock_test.cc
+++ b/test/cpp/end2end/mock_test.cc
@@ -245,8 +245,8 @@ class MockTest : public ::testing::Test {
   void TearDown() GRPC_OVERRIDE { server_->Shutdown(); }
 
   void ResetStub() {
-    std::shared_ptr<Channel> channel = CreateChannel(
-        server_address_.str(), InsecureCredentials(), ChannelArguments());
+    std::shared_ptr<Channel> channel =
+        CreateChannel(server_address_.str(), InsecureCredentials());
     stub_ = std::move(grpc::cpp::test::util::TestService::NewStub(channel));
   }
 
diff --git a/test/cpp/end2end/server_crash_test_client.cc b/test/cpp/end2end/server_crash_test_client.cc
index 7ca43a0c5b867916be84d47ea83bd10e9fe39663..6ff42fcb301a9d146336b7837dd39fd2a53640c4 100644
--- a/test/cpp/end2end/server_crash_test_client.cc
+++ b/test/cpp/end2end/server_crash_test_client.cc
@@ -58,8 +58,8 @@ using namespace gflags;
 
 int main(int argc, char** argv) {
   ParseCommandLineFlags(&argc, &argv, true);
-  auto stub = grpc::cpp::test::util::TestService::NewStub(grpc::CreateChannel(
-      FLAGS_address, grpc::InsecureCredentials(), grpc::ChannelArguments()));
+  auto stub = grpc::cpp::test::util::TestService::NewStub(
+      grpc::CreateChannel(FLAGS_address, grpc::InsecureCredentials()));
 
   EchoRequest request;
   EchoResponse response;
diff --git a/test/cpp/end2end/shutdown_test.cc b/test/cpp/end2end/shutdown_test.cc
index e83f86f7ec5c0107d0c3194b8af99a834a98fd77..59fec6ad40619ca07c6f64738009672fbf2d2ac8 100644
--- a/test/cpp/end2end/shutdown_test.cc
+++ b/test/cpp/end2end/shutdown_test.cc
@@ -95,7 +95,7 @@ class ShutdownTest : public ::testing::Test {
 
   void ResetStub() {
     string target = "dns:localhost:" + to_string(port_);
-    channel_ = CreateChannel(target, InsecureCredentials(), ChannelArguments());
+    channel_ = CreateChannel(target, InsecureCredentials());
     stub_ = std::move(grpc::cpp::test::util::TestService::NewStub(channel_));
   }
 
diff --git a/test/cpp/end2end/thread_stress_test.cc b/test/cpp/end2end/thread_stress_test.cc
index 8304f04d56ab8cb5f72a7ce08cb8e2b66e595e39..2a16481972581e301cf3f9813b8a254cfda8d514 100644
--- a/test/cpp/end2end/thread_stress_test.cc
+++ b/test/cpp/end2end/thread_stress_test.cc
@@ -191,8 +191,8 @@ class End2endTest : public ::testing::Test {
   void TearDown() GRPC_OVERRIDE { server_->Shutdown(); }
 
   void ResetStub() {
-    std::shared_ptr<Channel> channel = CreateChannel(
-        server_address_.str(), InsecureCredentials(), ChannelArguments());
+    std::shared_ptr<Channel> channel =
+        CreateChannel(server_address_.str(), InsecureCredentials());
     stub_ = std::move(grpc::cpp::test::util::TestService::NewStub(channel));
   }
 
diff --git a/test/cpp/end2end/zookeeper_test.cc b/test/cpp/end2end/zookeeper_test.cc
index e7d95b1c4625c77210772158fcd6e7e76df49b6b..931541ca3442dbf8c051b842023bf89e3100f0e6 100644
--- a/test/cpp/end2end/zookeeper_test.cc
+++ b/test/cpp/end2end/zookeeper_test.cc
@@ -159,7 +159,7 @@ class ZookeeperTest : public ::testing::Test {
 
   void ResetStub() {
     string target = "zookeeper://" + zookeeper_address_ + "/test";
-    channel_ = CreateChannel(target, InsecureCredentials(), ChannelArguments());
+    channel_ = CreateChannel(target, InsecureCredentials());
     stub_ = std::move(grpc::cpp::test::util::TestService::NewStub(channel_));
   }
 
diff --git a/test/cpp/qps/driver.cc b/test/cpp/qps/driver.cc
index 3bd61ea4e8bafe3e48f0bea5e1add7b2b9dc865b..0e771d6b8154427c61332e9b0c9a8bcf29c62a98 100644
--- a/test/cpp/qps/driver.cc
+++ b/test/cpp/qps/driver.cc
@@ -154,8 +154,8 @@ std::unique_ptr<ScenarioResult> RunScenario(
   // where class contained in std::vector must have a copy constructor
   auto* servers = new ServerData[num_servers];
   for (size_t i = 0; i < num_servers; i++) {
-    servers[i].stub = std::move(Worker::NewStub(
-        CreateChannel(workers[i], InsecureCredentials(), ChannelArguments())));
+    servers[i].stub = std::move(
+        Worker::NewStub(CreateChannel(workers[i], InsecureCredentials())));
     ServerArgs args;
     result_server_config = server_config;
     result_server_config.set_host(workers[i]);
@@ -182,8 +182,8 @@ std::unique_ptr<ScenarioResult> RunScenario(
   // where class contained in std::vector must have a copy constructor
   auto* clients = new ClientData[num_clients];
   for (size_t i = 0; i < num_clients; i++) {
-    clients[i].stub = std::move(Worker::NewStub(CreateChannel(
-        workers[i + num_servers], InsecureCredentials(), ChannelArguments())));
+    clients[i].stub = std::move(Worker::NewStub(
+        CreateChannel(workers[i + num_servers], InsecureCredentials())));
     ClientArgs args;
     result_client_config = client_config;
     result_client_config.set_host(workers[i + num_servers]);
diff --git a/test/cpp/qps/report.h b/test/cpp/qps/report.h
index 620abade39bee4544c996b5711e8a082a9bd6eaf..5914fc4e308437a79d582528ab0a3a5d3309ada4 100644
--- a/test/cpp/qps/report.h
+++ b/test/cpp/qps/report.h
@@ -116,8 +116,8 @@ class PerfDbReporter : public Reporter {
         test_name_(test_name),
         sys_info_(sys_info),
         tag_(tag) {
-    perf_db_client_.init(grpc::CreateChannel(
-        server_address, grpc::InsecureCredentials(), ChannelArguments()));
+    perf_db_client_.init(
+        grpc::CreateChannel(server_address, grpc::InsecureCredentials()));
   }
   ~PerfDbReporter() GRPC_OVERRIDE { SendData(); };
 
diff --git a/test/cpp/util/cli_call_test.cc b/test/cpp/util/cli_call_test.cc
index 111a0e9f76281ca8cd215589cb864f835d3db7f3..0efa201622017f3065b94108598e17209a0bef6a 100644
--- a/test/cpp/util/cli_call_test.cc
+++ b/test/cpp/util/cli_call_test.cc
@@ -91,8 +91,7 @@ class CliCallTest : public ::testing::Test {
   void TearDown() GRPC_OVERRIDE { server_->Shutdown(); }
 
   void ResetStub() {
-    channel_ = CreateChannel(server_address_.str(), InsecureCredentials(),
-                             ChannelArguments());
+    channel_ = CreateChannel(server_address_.str(), InsecureCredentials());
     stub_ = std::move(grpc::cpp::test::util::TestService::NewStub(channel_));
   }
 
diff --git a/test/cpp/util/create_test_channel.cc b/test/cpp/util/create_test_channel.cc
index 161b4bdc1d5ea9b6d98b12a9002bc559c9f184b2..e993d14e716c2582db8f2fc358e810dac54077e5 100644
--- a/test/cpp/util/create_test_channel.cc
+++ b/test/cpp/util/create_test_channel.cc
@@ -74,9 +74,9 @@ std::shared_ptr<Channel> CreateTestChannel(
     if (creds.get()) {
       channel_creds = CompositeCredentials(creds, channel_creds);
     }
-    return CreateChannel(connect_to, channel_creds, channel_args);
+    return CreateCustomChannel(connect_to, channel_creds, channel_args);
   } else {
-    return CreateChannel(server, InsecureCredentials(), channel_args);
+    return CreateChannel(server, InsecureCredentials());
   }
 }
 
diff --git a/test/cpp/util/grpc_cli.cc b/test/cpp/util/grpc_cli.cc
index a4888efebe2b0e2b8bac0960eca7edd2139f94f1..22cac21f77e0f9c542b4869a6e0d6e61c61b4f2d 100644
--- a/test/cpp/util/grpc_cli.cc
+++ b/test/cpp/util/grpc_cli.cc
@@ -159,7 +159,7 @@ int main(int argc, char** argv) {
     }
   }
   std::shared_ptr<grpc::Channel> channel =
-      grpc::CreateChannel(server_address, creds, grpc::ChannelArguments());
+      grpc::CreateChannel(server_address, creds);
 
   grpc::string response;
   std::multimap<grpc::string, grpc::string> client_metadata;
diff --git a/tools/codegen/core/gen_legal_metadata_characters.c b/tools/codegen/core/gen_legal_metadata_characters.c
index 5c290f2923229eecd31154ebca623c3cc3cf4947..0fbc545d8d16c8acde0e76f2ce62c8269fab25cf 100644
--- a/tools/codegen/core/gen_legal_metadata_characters.c
+++ b/tools/codegen/core/gen_legal_metadata_characters.c
@@ -66,7 +66,10 @@ int main(void) {
   dump();
 
   clear();
-  for (i = 32; i <= 126; i++) legal(i);
+  for (i = 32; i <= 126; i++) {
+    if (i == ',') continue;
+    legal(i);
+  }
   dump();
 
   return 0;