Merge pull request #6430 from myeh/example/non_blocking_async_client

Modified sample async client to be multi-threaded  
diff --git a/BUILD b/BUILD
index b4b10b5..fae3596 100644
--- a/BUILD
+++ b/BUILD
@@ -285,6 +285,42 @@
     "src/core/ext/client_config/subchannel_call_holder.h",
     "src/core/ext/client_config/subchannel_index.h",
     "src/core/ext/client_config/uri_parser.h",
+    "include/grpc/byte_buffer.h",
+    "include/grpc/grpc.h",
+    "include/grpc/impl/codegen/alloc.h",
+    "include/grpc/impl/codegen/atm.h",
+    "include/grpc/impl/codegen/atm_gcc_atomic.h",
+    "include/grpc/impl/codegen/atm_gcc_sync.h",
+    "include/grpc/impl/codegen/atm_win32.h",
+    "include/grpc/impl/codegen/byte_buffer.h",
+    "include/grpc/impl/codegen/compression_types.h",
+    "include/grpc/impl/codegen/connectivity_state.h",
+    "include/grpc/impl/codegen/grpc_types.h",
+    "include/grpc/impl/codegen/log.h",
+    "include/grpc/impl/codegen/port_platform.h",
+    "include/grpc/impl/codegen/propagation_bits.h",
+    "include/grpc/impl/codegen/slice.h",
+    "include/grpc/impl/codegen/slice_buffer.h",
+    "include/grpc/impl/codegen/status.h",
+    "include/grpc/impl/codegen/sync.h",
+    "include/grpc/impl/codegen/sync_generic.h",
+    "include/grpc/impl/codegen/sync_posix.h",
+    "include/grpc/impl/codegen/sync_win32.h",
+    "include/grpc/impl/codegen/time.h",
+    "include/grpc/status.h",
+    "include/grpc/support/alloc.h",
+    "include/grpc/support/atm.h",
+    "include/grpc/support/host_port.h",
+    "include/grpc/support/log.h",
+    "include/grpc/support/port_platform.h",
+    "include/grpc/support/slice.h",
+    "include/grpc/support/slice_buffer.h",
+    "include/grpc/support/string_util.h",
+    "include/grpc/support/sync.h",
+    "include/grpc/support/time.h",
+    "include/grpc/support/useful.h",
+    "src/core/lib/support/string.h",
+    "third_party/objective_c/Cronet/cronet_c_for_grpc.h",
     "src/core/ext/lb_policy/grpclb/load_balancer_api.h",
     "src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.h",
     "src/core/ext/census/aggregation.h",
@@ -439,6 +475,9 @@
     "src/core/ext/client_config/uri_parser.c",
     "src/core/ext/transport/chttp2/server/insecure/server_chttp2.c",
     "src/core/ext/transport/chttp2/client/insecure/channel_create.c",
+    "src/core/ext/transport/cronet/client/secure/cronet_channel_create.c",
+    "src/core/ext/transport/cronet/transport/cronet_api_dummy.c",
+    "src/core/ext/transport/cronet/transport/cronet_transport.c",
     "src/core/ext/lb_policy/grpclb/load_balancer_api.c",
     "src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.c",
     "src/core/ext/lb_policy/pick_first/pick_first.c",
@@ -463,6 +502,7 @@
     "include/grpc/grpc.h",
     "include/grpc/status.h",
     "include/grpc/impl/codegen/byte_buffer.h",
+    "include/grpc/impl/codegen/byte_buffer_reader.h",
     "include/grpc/impl/codegen/compression_types.h",
     "include/grpc/impl/codegen/connectivity_state.h",
     "include/grpc/impl/codegen/grpc_types.h",
@@ -482,6 +522,7 @@
     "include/grpc/impl/codegen/sync_posix.h",
     "include/grpc/impl/codegen/sync_win32.h",
     "include/grpc/impl/codegen/time.h",
+    "include/grpc/grpc_cronet.h",
     "include/grpc/grpc_security.h",
     "include/grpc/grpc_security_constants.h",
     "include/grpc/census.h",
@@ -774,6 +815,7 @@
     "include/grpc/grpc.h",
     "include/grpc/status.h",
     "include/grpc/impl/codegen/byte_buffer.h",
+    "include/grpc/impl/codegen/byte_buffer_reader.h",
     "include/grpc/impl/codegen/compression_types.h",
     "include/grpc/impl/codegen/connectivity_state.h",
     "include/grpc/impl/codegen/grpc_types.h",
@@ -946,6 +988,7 @@
     "include/grpc++/impl/codegen/sync_stream.h",
     "include/grpc++/impl/codegen/time.h",
     "include/grpc/impl/codegen/byte_buffer.h",
+    "include/grpc/impl/codegen/byte_buffer_reader.h",
     "include/grpc/impl/codegen/compression_types.h",
     "include/grpc/impl/codegen/connectivity_state.h",
     "include/grpc/impl/codegen/grpc_types.h",
@@ -1091,6 +1134,7 @@
     "include/grpc++/impl/codegen/sync_stream.h",
     "include/grpc++/impl/codegen/time.h",
     "include/grpc/impl/codegen/byte_buffer.h",
+    "include/grpc/impl/codegen/byte_buffer_reader.h",
     "include/grpc/impl/codegen/compression_types.h",
     "include/grpc/impl/codegen/connectivity_state.h",
     "include/grpc/impl/codegen/grpc_types.h",
@@ -1456,6 +1500,9 @@
     "src/core/ext/client_config/uri_parser.c",
     "src/core/ext/transport/chttp2/server/insecure/server_chttp2.c",
     "src/core/ext/transport/chttp2/client/insecure/channel_create.c",
+    "src/core/ext/transport/cronet/client/secure/cronet_channel_create.c",
+    "src/core/ext/transport/cronet/transport/cronet_api_dummy.c",
+    "src/core/ext/transport/cronet/transport/cronet_transport.c",
     "src/core/ext/lb_policy/grpclb/load_balancer_api.c",
     "src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.c",
     "src/core/ext/lb_policy/pick_first/pick_first.c",
@@ -1480,6 +1527,7 @@
     "include/grpc/grpc.h",
     "include/grpc/status.h",
     "include/grpc/impl/codegen/byte_buffer.h",
+    "include/grpc/impl/codegen/byte_buffer_reader.h",
     "include/grpc/impl/codegen/compression_types.h",
     "include/grpc/impl/codegen/connectivity_state.h",
     "include/grpc/impl/codegen/grpc_types.h",
@@ -1499,6 +1547,7 @@
     "include/grpc/impl/codegen/sync_posix.h",
     "include/grpc/impl/codegen/sync_win32.h",
     "include/grpc/impl/codegen/time.h",
+    "include/grpc/grpc_cronet.h",
     "include/grpc/grpc_security.h",
     "include/grpc/grpc_security_constants.h",
     "include/grpc/census.h",
@@ -1626,6 +1675,42 @@
     "src/core/ext/client_config/subchannel_call_holder.h",
     "src/core/ext/client_config/subchannel_index.h",
     "src/core/ext/client_config/uri_parser.h",
+    "include/grpc/byte_buffer.h",
+    "include/grpc/grpc.h",
+    "include/grpc/impl/codegen/alloc.h",
+    "include/grpc/impl/codegen/atm.h",
+    "include/grpc/impl/codegen/atm_gcc_atomic.h",
+    "include/grpc/impl/codegen/atm_gcc_sync.h",
+    "include/grpc/impl/codegen/atm_win32.h",
+    "include/grpc/impl/codegen/byte_buffer.h",
+    "include/grpc/impl/codegen/compression_types.h",
+    "include/grpc/impl/codegen/connectivity_state.h",
+    "include/grpc/impl/codegen/grpc_types.h",
+    "include/grpc/impl/codegen/log.h",
+    "include/grpc/impl/codegen/port_platform.h",
+    "include/grpc/impl/codegen/propagation_bits.h",
+    "include/grpc/impl/codegen/slice.h",
+    "include/grpc/impl/codegen/slice_buffer.h",
+    "include/grpc/impl/codegen/status.h",
+    "include/grpc/impl/codegen/sync.h",
+    "include/grpc/impl/codegen/sync_generic.h",
+    "include/grpc/impl/codegen/sync_posix.h",
+    "include/grpc/impl/codegen/sync_win32.h",
+    "include/grpc/impl/codegen/time.h",
+    "include/grpc/status.h",
+    "include/grpc/support/alloc.h",
+    "include/grpc/support/atm.h",
+    "include/grpc/support/host_port.h",
+    "include/grpc/support/log.h",
+    "include/grpc/support/port_platform.h",
+    "include/grpc/support/slice.h",
+    "include/grpc/support/slice_buffer.h",
+    "include/grpc/support/string_util.h",
+    "include/grpc/support/sync.h",
+    "include/grpc/support/time.h",
+    "include/grpc/support/useful.h",
+    "src/core/lib/support/string.h",
+    "third_party/objective_c/Cronet/cronet_c_for_grpc.h",
     "src/core/ext/lb_policy/grpclb/load_balancer_api.h",
     "src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.h",
     "src/core/ext/census/aggregation.h",
diff --git a/Makefile b/Makefile
index 922e0b0..e77aa2d 100644
--- a/Makefile
+++ b/Makefile
@@ -2623,6 +2623,9 @@
     src/core/ext/client_config/uri_parser.c \
     src/core/ext/transport/chttp2/server/insecure/server_chttp2.c \
     src/core/ext/transport/chttp2/client/insecure/channel_create.c \
+    src/core/ext/transport/cronet/client/secure/cronet_channel_create.c \
+    src/core/ext/transport/cronet/transport/cronet_api_dummy.c \
+    src/core/ext/transport/cronet/transport/cronet_transport.c \
     src/core/ext/lb_policy/grpclb/load_balancer_api.c \
     src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.c \
     third_party/nanopb/pb_common.c \
@@ -2650,6 +2653,7 @@
     include/grpc/grpc.h \
     include/grpc/status.h \
     include/grpc/impl/codegen/byte_buffer.h \
+    include/grpc/impl/codegen/byte_buffer_reader.h \
     include/grpc/impl/codegen/compression_types.h \
     include/grpc/impl/codegen/connectivity_state.h \
     include/grpc/impl/codegen/grpc_types.h \
@@ -2669,6 +2673,7 @@
     include/grpc/impl/codegen/sync_posix.h \
     include/grpc/impl/codegen/sync_win32.h \
     include/grpc/impl/codegen/time.h \
+    include/grpc/grpc_cronet.h \
     include/grpc/grpc_security.h \
     include/grpc/grpc_security_constants.h \
     include/grpc/census.h \
@@ -2970,6 +2975,7 @@
     include/grpc/grpc.h \
     include/grpc/status.h \
     include/grpc/impl/codegen/byte_buffer.h \
+    include/grpc/impl/codegen/byte_buffer_reader.h \
     include/grpc/impl/codegen/compression_types.h \
     include/grpc/impl/codegen/connectivity_state.h \
     include/grpc/impl/codegen/grpc_types.h \
@@ -3256,6 +3262,7 @@
     include/grpc++/impl/codegen/sync_stream.h \
     include/grpc++/impl/codegen/time.h \
     include/grpc/impl/codegen/byte_buffer.h \
+    include/grpc/impl/codegen/byte_buffer_reader.h \
     include/grpc/impl/codegen/compression_types.h \
     include/grpc/impl/codegen/connectivity_state.h \
     include/grpc/impl/codegen/grpc_types.h \
@@ -3559,6 +3566,7 @@
     include/grpc++/impl/codegen/sync_stream.h \
     include/grpc++/impl/codegen/time.h \
     include/grpc/impl/codegen/byte_buffer.h \
+    include/grpc/impl/codegen/byte_buffer_reader.h \
     include/grpc/impl/codegen/compression_types.h \
     include/grpc/impl/codegen/connectivity_state.h \
     include/grpc/impl/codegen/grpc_types.h \
@@ -14313,6 +14321,9 @@
 # otherwise parallel compilation will fail if a source is compiled first.
 src/core/ext/transport/chttp2/client/secure/secure_channel_create.c: $(OPENSSL_DEP)
 src/core/ext/transport/chttp2/server/secure/server_secure_chttp2.c: $(OPENSSL_DEP)
+src/core/ext/transport/cronet/client/secure/cronet_channel_create.c: $(OPENSSL_DEP)
+src/core/ext/transport/cronet/transport/cronet_api_dummy.c: $(OPENSSL_DEP)
+src/core/ext/transport/cronet/transport/cronet_transport.c: $(OPENSSL_DEP)
 src/core/lib/http/httpcli_security_connector.c: $(OPENSSL_DEP)
 src/core/lib/security/b64.c: $(OPENSSL_DEP)
 src/core/lib/security/client_auth_filter.c: $(OPENSSL_DEP)
diff --git a/binding.gyp b/binding.gyp
index 4314ab7..12a745f 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -709,6 +709,9 @@
         'src/core/ext/client_config/uri_parser.c',
         'src/core/ext/transport/chttp2/server/insecure/server_chttp2.c',
         'src/core/ext/transport/chttp2/client/insecure/channel_create.c',
+        'src/core/ext/transport/cronet/client/secure/cronet_channel_create.c',
+        'src/core/ext/transport/cronet/transport/cronet_api_dummy.c',
+        'src/core/ext/transport/cronet/transport/cronet_transport.c',
         'src/core/ext/lb_policy/grpclb/load_balancer_api.c',
         'src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.c',
         'third_party/nanopb/pb_common.c',
diff --git a/build.yaml b/build.yaml
index 441752d..1a0888b 100644
--- a/build.yaml
+++ b/build.yaml
@@ -351,6 +351,7 @@
 - name: grpc_codegen
   public_headers:
   - include/grpc/impl/codegen/byte_buffer.h
+  - include/grpc/impl/codegen/byte_buffer_reader.h
   - include/grpc/impl/codegen/compression_types.h
   - include/grpc/impl/codegen/connectivity_state.h
   - include/grpc/impl/codegen/grpc_types.h
@@ -399,6 +400,7 @@
   - grpc_client_config
 - name: grpc_secure
   public_headers:
+  - include/grpc/grpc_cronet.h
   - include/grpc/grpc_security.h
   - include/grpc/grpc_security_constants.h
   headers:
@@ -546,6 +548,63 @@
   - grpc_transport_chttp2
   - grpc_base
   - grpc_secure
+- name: grpc_transport_cronet_client_secure
+  headers:
+  - include/grpc/byte_buffer.h
+  - include/grpc/grpc.h
+  - include/grpc/impl/codegen/alloc.h
+  - include/grpc/impl/codegen/atm.h
+  - include/grpc/impl/codegen/atm_gcc_atomic.h
+  - include/grpc/impl/codegen/atm_gcc_sync.h
+  - include/grpc/impl/codegen/atm_win32.h
+  - include/grpc/impl/codegen/byte_buffer.h
+  - include/grpc/impl/codegen/compression_types.h
+  - include/grpc/impl/codegen/connectivity_state.h
+  - include/grpc/impl/codegen/grpc_types.h
+  - include/grpc/impl/codegen/log.h
+  - include/grpc/impl/codegen/port_platform.h
+  - include/grpc/impl/codegen/propagation_bits.h
+  - include/grpc/impl/codegen/slice.h
+  - include/grpc/impl/codegen/slice_buffer.h
+  - include/grpc/impl/codegen/status.h
+  - include/grpc/impl/codegen/sync.h
+  - include/grpc/impl/codegen/sync_generic.h
+  - include/grpc/impl/codegen/sync_posix.h
+  - include/grpc/impl/codegen/sync_win32.h
+  - include/grpc/impl/codegen/time.h
+  - include/grpc/status.h
+  - include/grpc/support/alloc.h
+  - include/grpc/support/atm.h
+  - include/grpc/support/host_port.h
+  - include/grpc/support/log.h
+  - include/grpc/support/port_platform.h
+  - include/grpc/support/slice.h
+  - include/grpc/support/slice_buffer.h
+  - include/grpc/support/string_util.h
+  - include/grpc/support/sync.h
+  - include/grpc/support/time.h
+  - include/grpc/support/useful.h
+  - src/core/ext/transport/chttp2/transport/incoming_metadata.h
+  - src/core/lib/channel/channel_stack.h
+  - src/core/lib/channel/context.h
+  - src/core/lib/debug/trace.h
+  - src/core/lib/iomgr/closure.h
+  - src/core/lib/iomgr/exec_ctx.h
+  - src/core/lib/iomgr/pollset.h
+  - src/core/lib/iomgr/pollset_set.h
+  - src/core/lib/support/string.h
+  - src/core/lib/surface/channel.h
+  - src/core/lib/surface/channel_stack_type.h
+  - src/core/lib/transport/byte_stream.h
+  - src/core/lib/transport/metadata.h
+  - src/core/lib/transport/metadata_batch.h
+  - src/core/lib/transport/transport.h
+  - src/core/lib/transport/transport_impl.h
+  - third_party/objective_c/Cronet/cronet_c_for_grpc.h
+  src:
+  - src/core/ext/transport/cronet/client/secure/cronet_channel_create.c
+  - src/core/ext/transport/cronet/transport/cronet_api_dummy.c
+  - src/core/ext/transport/cronet/transport/cronet_transport.c
 - name: nanopb
   headers:
   - third_party/nanopb/pb.h
@@ -733,6 +792,7 @@
   - grpc_transport_chttp2_client_secure
   - grpc_transport_chttp2_server_insecure
   - grpc_transport_chttp2_client_insecure
+  - grpc_transport_cronet_client_secure
   - grpc_lb_policy_grpclb
   - grpc_lb_policy_pick_first
   - grpc_lb_policy_round_robin
diff --git a/config.m4 b/config.m4
index 74f9ad2..5259e67 100644
--- a/config.m4
+++ b/config.m4
@@ -228,6 +228,9 @@
     src/core/ext/client_config/uri_parser.c \
     src/core/ext/transport/chttp2/server/insecure/server_chttp2.c \
     src/core/ext/transport/chttp2/client/insecure/channel_create.c \
+    src/core/ext/transport/cronet/client/secure/cronet_channel_create.c \
+    src/core/ext/transport/cronet/transport/cronet_api_dummy.c \
+    src/core/ext/transport/cronet/transport/cronet_transport.c \
     src/core/ext/lb_policy/grpclb/load_balancer_api.c \
     src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.c \
     third_party/nanopb/pb_common.c \
@@ -566,6 +569,8 @@
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/transport/chttp2/server/insecure)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/transport/chttp2/server/secure)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/transport/chttp2/transport)
+  PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/transport/cronet/client/secure)
+  PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/transport/cronet/transport)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/channel)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/compression)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/debug)
diff --git a/examples/node/greeter_client.js b/examples/node/greeter_client.js
index 7125c2f..2820acb 100644
--- a/examples/node/greeter_client.js
+++ b/examples/node/greeter_client.js
@@ -31,30 +31,22 @@
  *
  */
 
-var grpc = require('grpc');
+var PROTO_PATH = __dirname + '/../protos/helloworld.proto';
 
-var hello_messages = require('./helloworld_pb');
-var hello_service = require('./helloworld_grpc_pb');
+var grpc = require('grpc');
+var hello_proto = grpc.load(PROTO_PATH).helloworld;
 
 function main() {
-  var client = new hello_service.GreeterClient('localhost:50051',
-                                               grpc.credentials.createInsecure());
+  var client = new hello_proto.Greeter('localhost:50051',
+                                       grpc.credentials.createInsecure());
   var user;
   if (process.argv.length >= 3) {
     user = process.argv[2];
   } else {
     user = 'world';
   }
-
-  var request = new hello_messages.HelloRequest();
-  request.setName(user);
-
-  client.sayHello(request, function(err, response) {
-    if (err) {
-      debugger;
-      throw err;
-    }
-    console.log('Greeting:', response.getMessage());
+  client.sayHello({name: user}, function(err, response) {
+    console.log('Greeting:', response.message);
   });
 }
 
diff --git a/examples/node/greeter_server.js b/examples/node/greeter_server.js
index a4aebf6..e7ad51f 100644
--- a/examples/node/greeter_server.js
+++ b/examples/node/greeter_server.js
@@ -31,18 +31,16 @@
  *
  */
 
-var grpc = require('grpc');
+var PROTO_PATH = __dirname + '/../protos/helloworld.proto';
 
-var hello_messages = require('./helloworld_pb');
-var hello_service = require('./helloworld_grpc_pb');
+var grpc = require('grpc');
+var hello_proto = grpc.load(PROTO_PATH).helloworld;
 
 /**
  * Implements the SayHello RPC method.
  */
 function sayHello(call, callback) {
-  var reply = new hello_messages.HelloReply();
-  reply.setMessage("Hello " + call.request.getName());
-  callback(null, reply);
+  callback(null, {message: 'Hello ' + call.request.name});
 }
 
 /**
@@ -51,7 +49,7 @@
  */
 function main() {
   var server = new grpc.Server();
-  server.addService(hello_service.GreeterService, {sayHello: sayHello});
+  server.addProtoService(hello_proto.Greeter.service, {sayHello: sayHello});
   server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
   server.start();
 }
diff --git a/examples/node/helloworld_grpc_pb.js b/examples/node/helloworld_grpc_pb.js
deleted file mode 100644
index 3d070d7..0000000
--- a/examples/node/helloworld_grpc_pb.js
+++ /dev/null
@@ -1,39 +0,0 @@
-// GENERATED CODE -- DO NOT EDIT!
-
-var grpc = require('grpc');
-var helloworld_pb = require('./helloworld_pb.js');
-
-function serialize_HelloReply(arg) {
-  if (!(arg instanceof helloworld_pb.HelloReply)) {
-    throw new Error('Expected argument of type HelloReply');
-  }
-  return new Buffer(arg.serializeBinary());
-}
-function deserialize_HelloReply(buffer_arg) {
-  return helloworld_pb.HelloReply.deserializeBinary(new Uint8Array(buffer_arg));
-}
-function serialize_HelloRequest(arg) {
-  if (!(arg instanceof helloworld_pb.HelloRequest)) {
-    throw new Error('Expected argument of type HelloRequest');
-  }
-  return new Buffer(arg.serializeBinary());
-}
-function deserialize_HelloRequest(buffer_arg) {
-  return helloworld_pb.HelloRequest.deserializeBinary(new Uint8Array(buffer_arg));
-}
-
-var GreeterService = exports.GreeterService = {
-  sayHello: {
-    path: '/helloworld.Greeter/SayHello',
-    requestStream: false,
-    responseStream: false,
-    requestType: helloworld_pb.HelloRequest,
-    responseType: helloworld_pb.HelloReply,
-    requestSerialize: serialize_HelloRequest,
-    requestDeserialize: deserialize_HelloRequest,
-    responseSerialize: serialize_HelloReply,
-    responseDeserialize: deserialize_HelloReply,
-  },
-};
-
-exports.GreeterClient = grpc.makeGenericClientConstructor(GreeterService);
diff --git a/examples/node/helloworld_pb.js b/examples/node/helloworld_pb.js
deleted file mode 100644
index 6405bd9..0000000
--- a/examples/node/helloworld_pb.js
+++ /dev/null
@@ -1,332 +0,0 @@
-/**
- * @fileoverview
- * @enhanceable
- * @public
- */
-// GENERATED CODE -- DO NOT EDIT!
-
-var jspb = require('google-protobuf');
-var goog = jspb;
-var global = Function('return this')();
-
-goog.exportSymbol('proto.helloworld.HelloReply', null, global);
-goog.exportSymbol('proto.helloworld.HelloRequest', null, global);
-
-/**
- * Generated by JsPbCodeGenerator.
- * @param {Array=} opt_data Optional initial data array, typically from a
- * server response, or constructed directly in Javascript. The array is used
- * in place and becomes part of the constructed object. It is not cloned.
- * If no data is provided, the constructed object will be empty, but still
- * valid.
- * @extends {jspb.Message}
- * @constructor
- */
-proto.helloworld.HelloRequest = function(opt_data) {
-  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
-};
-goog.inherits(proto.helloworld.HelloRequest, jspb.Message);
-if (goog.DEBUG && !COMPILED) {
-  proto.helloworld.HelloRequest.displayName = 'proto.helloworld.HelloRequest';
-}
-
-
-if (jspb.Message.GENERATE_TO_OBJECT) {
-/**
- * Creates an object representation of this proto suitable for use in Soy templates.
- * Field names that are reserved in JavaScript and will be renamed to pb_name.
- * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
- * For the list of reserved names please see:
- *     com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
- * @param {boolean=} opt_includeInstance Whether to include the JSPB instance
- *     for transitional soy proto support: http://goto/soy-param-migration
- * @return {!Object}
- */
-proto.helloworld.HelloRequest.prototype.toObject = function(opt_includeInstance) {
-  return proto.helloworld.HelloRequest.toObject(opt_includeInstance, this);
-};
-
-
-/**
- * Static version of the {@see toObject} method.
- * @param {boolean|undefined} includeInstance Whether to include the JSPB
- *     instance for transitional soy proto support:
- *     http://goto/soy-param-migration
- * @param {!proto.helloworld.HelloRequest} msg The msg instance to transform.
- * @return {!Object}
- */
-proto.helloworld.HelloRequest.toObject = function(includeInstance, msg) {
-  var f, obj = {
-    name: msg.getName()
-  };
-
-  if (includeInstance) {
-    obj.$jspbMessageInstance = msg
-  }
-  return obj;
-};
-}
-
-
-/**
- * Deserializes binary data (in protobuf wire format).
- * @param {jspb.ByteSource} bytes The bytes to deserialize.
- * @return {!proto.helloworld.HelloRequest}
- */
-proto.helloworld.HelloRequest.deserializeBinary = function(bytes) {
-  var reader = new jspb.BinaryReader(bytes);
-  var msg = new proto.helloworld.HelloRequest;
-  return proto.helloworld.HelloRequest.deserializeBinaryFromReader(msg, reader);
-};
-
-
-/**
- * Deserializes binary data (in protobuf wire format) from the
- * given reader into the given message object.
- * @param {!proto.helloworld.HelloRequest} msg The message object to deserialize into.
- * @param {!jspb.BinaryReader} reader The BinaryReader to use.
- * @return {!proto.helloworld.HelloRequest}
- */
-proto.helloworld.HelloRequest.deserializeBinaryFromReader = function(msg, reader) {
-  while (reader.nextField()) {
-    if (reader.isEndGroup()) {
-      break;
-    }
-    var field = reader.getFieldNumber();
-    switch (field) {
-    case 1:
-      var value = /** @type {string} */ (reader.readString());
-      msg.setName(value);
-      break;
-    default:
-      reader.skipField();
-      break;
-    }
-  }
-  return msg;
-};
-
-
-/**
- * Class method variant: serializes the given message to binary data
- * (in protobuf wire format), writing to the given BinaryWriter.
- * @param {!proto.helloworld.HelloRequest} message
- * @param {!jspb.BinaryWriter} writer
- */
-proto.helloworld.HelloRequest.serializeBinaryToWriter = function(message, writer) {
-  message.serializeBinaryToWriter(writer);
-};
-
-
-/**
- * Serializes the message to binary data (in protobuf wire format).
- * @return {!Uint8Array}
- */
-proto.helloworld.HelloRequest.prototype.serializeBinary = function() {
-  var writer = new jspb.BinaryWriter();
-  this.serializeBinaryToWriter(writer);
-  return writer.getResultBuffer();
-};
-
-
-/**
- * Serializes the message to binary data (in protobuf wire format),
- * writing to the given BinaryWriter.
- * @param {!jspb.BinaryWriter} writer
- */
-proto.helloworld.HelloRequest.prototype.serializeBinaryToWriter = function (writer) {
-  var f = undefined;
-  f = this.getName();
-  if (f.length > 0) {
-    writer.writeString(
-      1,
-      f
-    );
-  }
-};
-
-
-/**
- * Creates a deep clone of this proto. No data is shared with the original.
- * @return {!proto.helloworld.HelloRequest} The clone.
- */
-proto.helloworld.HelloRequest.prototype.cloneMessage = function() {
-  return /** @type {!proto.helloworld.HelloRequest} */ (jspb.Message.cloneMessage(this));
-};
-
-
-/**
- * optional string name = 1;
- * @return {string}
- */
-proto.helloworld.HelloRequest.prototype.getName = function() {
-  return /** @type {string} */ (jspb.Message.getFieldProto3(this, 1, ""));
-};
-
-
-/** @param {string} value  */
-proto.helloworld.HelloRequest.prototype.setName = function(value) {
-  jspb.Message.setField(this, 1, value);
-};
-
-
-
-/**
- * Generated by JsPbCodeGenerator.
- * @param {Array=} opt_data Optional initial data array, typically from a
- * server response, or constructed directly in Javascript. The array is used
- * in place and becomes part of the constructed object. It is not cloned.
- * If no data is provided, the constructed object will be empty, but still
- * valid.
- * @extends {jspb.Message}
- * @constructor
- */
-proto.helloworld.HelloReply = function(opt_data) {
-  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
-};
-goog.inherits(proto.helloworld.HelloReply, jspb.Message);
-if (goog.DEBUG && !COMPILED) {
-  proto.helloworld.HelloReply.displayName = 'proto.helloworld.HelloReply';
-}
-
-
-if (jspb.Message.GENERATE_TO_OBJECT) {
-/**
- * Creates an object representation of this proto suitable for use in Soy templates.
- * Field names that are reserved in JavaScript and will be renamed to pb_name.
- * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
- * For the list of reserved names please see:
- *     com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
- * @param {boolean=} opt_includeInstance Whether to include the JSPB instance
- *     for transitional soy proto support: http://goto/soy-param-migration
- * @return {!Object}
- */
-proto.helloworld.HelloReply.prototype.toObject = function(opt_includeInstance) {
-  return proto.helloworld.HelloReply.toObject(opt_includeInstance, this);
-};
-
-
-/**
- * Static version of the {@see toObject} method.
- * @param {boolean|undefined} includeInstance Whether to include the JSPB
- *     instance for transitional soy proto support:
- *     http://goto/soy-param-migration
- * @param {!proto.helloworld.HelloReply} msg The msg instance to transform.
- * @return {!Object}
- */
-proto.helloworld.HelloReply.toObject = function(includeInstance, msg) {
-  var f, obj = {
-    message: msg.getMessage()
-  };
-
-  if (includeInstance) {
-    obj.$jspbMessageInstance = msg
-  }
-  return obj;
-};
-}
-
-
-/**
- * Deserializes binary data (in protobuf wire format).
- * @param {jspb.ByteSource} bytes The bytes to deserialize.
- * @return {!proto.helloworld.HelloReply}
- */
-proto.helloworld.HelloReply.deserializeBinary = function(bytes) {
-  var reader = new jspb.BinaryReader(bytes);
-  var msg = new proto.helloworld.HelloReply;
-  return proto.helloworld.HelloReply.deserializeBinaryFromReader(msg, reader);
-};
-
-
-/**
- * Deserializes binary data (in protobuf wire format) from the
- * given reader into the given message object.
- * @param {!proto.helloworld.HelloReply} msg The message object to deserialize into.
- * @param {!jspb.BinaryReader} reader The BinaryReader to use.
- * @return {!proto.helloworld.HelloReply}
- */
-proto.helloworld.HelloReply.deserializeBinaryFromReader = function(msg, reader) {
-  while (reader.nextField()) {
-    if (reader.isEndGroup()) {
-      break;
-    }
-    var field = reader.getFieldNumber();
-    switch (field) {
-    case 1:
-      var value = /** @type {string} */ (reader.readString());
-      msg.setMessage(value);
-      break;
-    default:
-      reader.skipField();
-      break;
-    }
-  }
-  return msg;
-};
-
-
-/**
- * Class method variant: serializes the given message to binary data
- * (in protobuf wire format), writing to the given BinaryWriter.
- * @param {!proto.helloworld.HelloReply} message
- * @param {!jspb.BinaryWriter} writer
- */
-proto.helloworld.HelloReply.serializeBinaryToWriter = function(message, writer) {
-  message.serializeBinaryToWriter(writer);
-};
-
-
-/**
- * Serializes the message to binary data (in protobuf wire format).
- * @return {!Uint8Array}
- */
-proto.helloworld.HelloReply.prototype.serializeBinary = function() {
-  var writer = new jspb.BinaryWriter();
-  this.serializeBinaryToWriter(writer);
-  return writer.getResultBuffer();
-};
-
-
-/**
- * Serializes the message to binary data (in protobuf wire format),
- * writing to the given BinaryWriter.
- * @param {!jspb.BinaryWriter} writer
- */
-proto.helloworld.HelloReply.prototype.serializeBinaryToWriter = function (writer) {
-  var f = undefined;
-  f = this.getMessage();
-  if (f.length > 0) {
-    writer.writeString(
-      1,
-      f
-    );
-  }
-};
-
-
-/**
- * Creates a deep clone of this proto. No data is shared with the original.
- * @return {!proto.helloworld.HelloReply} The clone.
- */
-proto.helloworld.HelloReply.prototype.cloneMessage = function() {
-  return /** @type {!proto.helloworld.HelloReply} */ (jspb.Message.cloneMessage(this));
-};
-
-
-/**
- * optional string message = 1;
- * @return {string}
- */
-proto.helloworld.HelloReply.prototype.getMessage = function() {
-  return /** @type {string} */ (jspb.Message.getFieldProto3(this, 1, ""));
-};
-
-
-/** @param {string} value  */
-proto.helloworld.HelloReply.prototype.setMessage = function(value) {
-  jspb.Message.setField(this, 1, value);
-};
-
-
-goog.object.extend(exports, proto.helloworld);
diff --git a/examples/node/package.json b/examples/node/package.json
index 49ab74d..d135df2 100644
--- a/examples/node/package.json
+++ b/examples/node/package.json
@@ -4,7 +4,6 @@
   "dependencies": {
     "async": "^1.5.2",
     "grpc": "0.13.0",
-    "google-protobuf": "*",
     "lodash": "^4.6.1",
     "minimist": "^1.2.0"
   }
diff --git a/examples/python/helloworld/helloworld_pb2.py b/examples/python/helloworld/helloworld_pb2.py
index 1b2674e..1ee80e4 100644
--- a/examples/python/helloworld/helloworld_pb2.py
+++ b/examples/python/helloworld/helloworld_pb2.py
@@ -1,6 +1,8 @@
 # Generated by the protocol buffer compiler.  DO NOT EDIT!
 # source: helloworld.proto
 
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
 from google.protobuf import descriptor as _descriptor
 from google.protobuf import message as _message
 from google.protobuf import reflection as _reflection
@@ -17,7 +19,7 @@
   name='helloworld.proto',
   package='helloworld',
   syntax='proto3',
-  serialized_pb=b'\n\x10helloworld.proto\x12\nhelloworld\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x01 \x01(\t2I\n\x07Greeter\x12>\n\x08SayHello\x12\x18.helloworld.HelloRequest\x1a\x16.helloworld.HelloReply\"\x00\x42\x18\n\x10io.grpc.examples\xa2\x02\x03HLWb\x06proto3'
+  serialized_pb=_b('\n\x10helloworld.proto\x12\nhelloworld\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x01 \x01(\t2I\n\x07Greeter\x12>\n\x08SayHello\x12\x18.helloworld.HelloRequest\x1a\x16.helloworld.HelloReply\"\x00\x42\x36\n\x1bio.grpc.examples.helloworldB\x0fHelloWorldProtoP\x01\xa2\x02\x03HLWb\x06proto3')
 )
 _sym_db.RegisterFileDescriptor(DESCRIPTOR)
 
@@ -34,7 +36,7 @@
     _descriptor.FieldDescriptor(
       name='name', full_name='helloworld.HelloRequest.name', index=0,
       number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
+      has_default_value=False, default_value=_b("").decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
@@ -65,7 +67,7 @@
     _descriptor.FieldDescriptor(
       name='message', full_name='helloworld.HelloReply.message', index=0,
       number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
+      has_default_value=False, default_value=_b("").decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
@@ -104,69 +106,28 @@
 
 
 DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), b'\n\020io.grpc.examples\242\002\003HLW')
+DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\033io.grpc.examples.helloworldB\017HelloWorldProtoP\001\242\002\003HLW'))
 import abc
+import six
 from grpc.beta import implementations as beta_implementations
-from grpc.early_adopter import implementations as early_adopter_implementations
-from grpc.framework.alpha import utilities as alpha_utilities
+from grpc.beta import interfaces as beta_interfaces
 from grpc.framework.common import cardinality
 from grpc.framework.interfaces.face import utilities as face_utilities
-class EarlyAdopterGreeterServicer(object):
-  """<fill me in later!>"""
-  __metaclass__ = abc.ABCMeta
-  @abc.abstractmethod
-  def SayHello(self, request, context):
-    raise NotImplementedError()
-class EarlyAdopterGreeterServer(object):
-  """<fill me in later!>"""
-  __metaclass__ = abc.ABCMeta
-  @abc.abstractmethod
-  def start(self):
-    raise NotImplementedError()
-  @abc.abstractmethod
-  def stop(self):
-    raise NotImplementedError()
-class EarlyAdopterGreeterStub(object):
-  """<fill me in later!>"""
-  __metaclass__ = abc.ABCMeta
-  @abc.abstractmethod
-  def SayHello(self, request):
-    raise NotImplementedError()
-  SayHello.async = None
-def early_adopter_create_Greeter_server(servicer, port, private_key=None, certificate_chain=None):
-  import helloworld_pb2
-  import helloworld_pb2
-  method_service_descriptions = {
-    "SayHello": alpha_utilities.unary_unary_service_description(
-      servicer.SayHello,
-      helloworld_pb2.HelloRequest.FromString,
-      helloworld_pb2.HelloReply.SerializeToString,
-    ),
-  }
-  return early_adopter_implementations.server("helloworld.Greeter", method_service_descriptions, port, private_key=private_key, certificate_chain=certificate_chain)
-def early_adopter_create_Greeter_stub(host, port, metadata_transformer=None, secure=False, root_certificates=None, private_key=None, certificate_chain=None, server_host_override=None):
-  import helloworld_pb2
-  import helloworld_pb2
-  method_invocation_descriptions = {
-    "SayHello": alpha_utilities.unary_unary_invocation_description(
-      helloworld_pb2.HelloRequest.SerializeToString,
-      helloworld_pb2.HelloReply.FromString,
-    ),
-  }
-  return early_adopter_implementations.stub("helloworld.Greeter", method_invocation_descriptions, host, port, metadata_transformer=metadata_transformer, secure=secure, root_certificates=root_certificates, private_key=private_key, certificate_chain=certificate_chain, server_host_override=server_host_override)
 
 class BetaGreeterServicer(object):
-  """<fill me in later!>"""
-  __metaclass__ = abc.ABCMeta
-  @abc.abstractmethod
+  """The greeting service definition.
+  """
   def SayHello(self, request, context):
-    raise NotImplementedError()
+    """Sends a greeting
+    """
+    context.code(beta_interfaces.StatusCode.UNIMPLEMENTED)
 
 class BetaGreeterStub(object):
-  """The interface to which stubs will conform."""
-  __metaclass__ = abc.ABCMeta
-  @abc.abstractmethod
+  """The greeting service definition.
+  """
   def SayHello(self, request, timeout):
+    """Sends a greeting
+    """
     raise NotImplementedError()
   SayHello.future = None
 
diff --git a/examples/python/route_guide/route_guide_pb2.py b/examples/python/route_guide/route_guide_pb2.py
index d4d9f8d..81d5d07 100644
--- a/examples/python/route_guide/route_guide_pb2.py
+++ b/examples/python/route_guide/route_guide_pb2.py
@@ -1,6 +1,8 @@
 # Generated by the protocol buffer compiler.  DO NOT EDIT!
 # source: route_guide.proto
 
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
 from google.protobuf import descriptor as _descriptor
 from google.protobuf import message as _message
 from google.protobuf import reflection as _reflection
@@ -17,7 +19,7 @@
   name='route_guide.proto',
   package='routeguide',
   syntax='proto3',
-  serialized_pb=b'\n\x11route_guide.proto\x12\nrouteguide\",\n\x05Point\x12\x10\n\x08latitude\x18\x01 \x01(\x05\x12\x11\n\tlongitude\x18\x02 \x01(\x05\"I\n\tRectangle\x12\x1d\n\x02lo\x18\x01 \x01(\x0b\x32\x11.routeguide.Point\x12\x1d\n\x02hi\x18\x02 \x01(\x0b\x32\x11.routeguide.Point\"<\n\x07\x46\x65\x61ture\x12\x0c\n\x04name\x18\x01 \x01(\t\x12#\n\x08location\x18\x02 \x01(\x0b\x32\x11.routeguide.Point\"A\n\tRouteNote\x12#\n\x08location\x18\x01 \x01(\x0b\x32\x11.routeguide.Point\x12\x0f\n\x07message\x18\x02 \x01(\t\"b\n\x0cRouteSummary\x12\x13\n\x0bpoint_count\x18\x01 \x01(\x05\x12\x15\n\rfeature_count\x18\x02 \x01(\x05\x12\x10\n\x08\x64istance\x18\x03 \x01(\x05\x12\x14\n\x0c\x65lapsed_time\x18\x04 \x01(\x05\x32\x85\x02\n\nRouteGuide\x12\x36\n\nGetFeature\x12\x11.routeguide.Point\x1a\x13.routeguide.Feature\"\x00\x12>\n\x0cListFeatures\x12\x15.routeguide.Rectangle\x1a\x13.routeguide.Feature\"\x00\x30\x01\x12>\n\x0bRecordRoute\x12\x11.routeguide.Point\x1a\x18.routeguide.RouteSummary\"\x00(\x01\x12?\n\tRouteChat\x12\x15.routeguide.RouteNote\x1a\x15.routeguide.RouteNote\"\x00(\x01\x30\x01\x42\x0f\n\x07\x65x.grpc\xa2\x02\x03RTGb\x06proto3'
+  serialized_pb=_b('\n\x11route_guide.proto\x12\nrouteguide\",\n\x05Point\x12\x10\n\x08latitude\x18\x01 \x01(\x05\x12\x11\n\tlongitude\x18\x02 \x01(\x05\"I\n\tRectangle\x12\x1d\n\x02lo\x18\x01 \x01(\x0b\x32\x11.routeguide.Point\x12\x1d\n\x02hi\x18\x02 \x01(\x0b\x32\x11.routeguide.Point\"<\n\x07\x46\x65\x61ture\x12\x0c\n\x04name\x18\x01 \x01(\t\x12#\n\x08location\x18\x02 \x01(\x0b\x32\x11.routeguide.Point\"A\n\tRouteNote\x12#\n\x08location\x18\x01 \x01(\x0b\x32\x11.routeguide.Point\x12\x0f\n\x07message\x18\x02 \x01(\t\"b\n\x0cRouteSummary\x12\x13\n\x0bpoint_count\x18\x01 \x01(\x05\x12\x15\n\rfeature_count\x18\x02 \x01(\x05\x12\x10\n\x08\x64istance\x18\x03 \x01(\x05\x12\x14\n\x0c\x65lapsed_time\x18\x04 \x01(\x05\x32\x85\x02\n\nRouteGuide\x12\x36\n\nGetFeature\x12\x11.routeguide.Point\x1a\x13.routeguide.Feature\"\x00\x12>\n\x0cListFeatures\x12\x15.routeguide.Rectangle\x1a\x13.routeguide.Feature\"\x00\x30\x01\x12>\n\x0bRecordRoute\x12\x11.routeguide.Point\x1a\x18.routeguide.RouteSummary\"\x00(\x01\x12?\n\tRouteChat\x12\x15.routeguide.RouteNote\x1a\x15.routeguide.RouteNote\"\x00(\x01\x30\x01\x42\x36\n\x1bio.grpc.examples.routeguideB\x0fRouteGuideProtoP\x01\xa2\x02\x03RTGb\x06proto3')
 )
 _sym_db.RegisterFileDescriptor(DESCRIPTOR)
 
@@ -110,7 +112,7 @@
     _descriptor.FieldDescriptor(
       name='name', full_name='routeguide.Feature.name', index=0,
       number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
+      has_default_value=False, default_value=_b("").decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
@@ -155,7 +157,7 @@
     _descriptor.FieldDescriptor(
       name='message', full_name='routeguide.RouteNote.message', index=1,
       number=2, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
+      has_default_value=False, default_value=_b("").decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
@@ -274,149 +276,86 @@
 
 
 DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), b'\n\007ex.grpc\242\002\003RTG')
+DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\033io.grpc.examples.routeguideB\017RouteGuideProtoP\001\242\002\003RTG'))
 import abc
+import six
 from grpc.beta import implementations as beta_implementations
-from grpc.early_adopter import implementations as early_adopter_implementations
-from grpc.framework.alpha import utilities as alpha_utilities
+from grpc.beta import interfaces as beta_interfaces
 from grpc.framework.common import cardinality
 from grpc.framework.interfaces.face import utilities as face_utilities
-class EarlyAdopterRouteGuideServicer(object):
-  """<fill me in later!>"""
-  __metaclass__ = abc.ABCMeta
-  @abc.abstractmethod
-  def GetFeature(self, request, context):
-    raise NotImplementedError()
-  @abc.abstractmethod
-  def ListFeatures(self, request, context):
-    raise NotImplementedError()
-  @abc.abstractmethod
-  def RecordRoute(self, request_iterator, context):
-    raise NotImplementedError()
-  @abc.abstractmethod
-  def RouteChat(self, request_iterator, context):
-    raise NotImplementedError()
-class EarlyAdopterRouteGuideServer(object):
-  """<fill me in later!>"""
-  __metaclass__ = abc.ABCMeta
-  @abc.abstractmethod
-  def start(self):
-    raise NotImplementedError()
-  @abc.abstractmethod
-  def stop(self):
-    raise NotImplementedError()
-class EarlyAdopterRouteGuideStub(object):
-  """<fill me in later!>"""
-  __metaclass__ = abc.ABCMeta
-  @abc.abstractmethod
-  def GetFeature(self, request):
-    raise NotImplementedError()
-  GetFeature.async = None
-  @abc.abstractmethod
-  def ListFeatures(self, request):
-    raise NotImplementedError()
-  ListFeatures.async = None
-  @abc.abstractmethod
-  def RecordRoute(self, request_iterator):
-    raise NotImplementedError()
-  RecordRoute.async = None
-  @abc.abstractmethod
-  def RouteChat(self, request_iterator):
-    raise NotImplementedError()
-  RouteChat.async = None
-def early_adopter_create_RouteGuide_server(servicer, port, private_key=None, certificate_chain=None):
-  import route_guide_pb2
-  import route_guide_pb2
-  import route_guide_pb2
-  import route_guide_pb2
-  import route_guide_pb2
-  import route_guide_pb2
-  import route_guide_pb2
-  import route_guide_pb2
-  method_service_descriptions = {
-    "GetFeature": alpha_utilities.unary_unary_service_description(
-      servicer.GetFeature,
-      route_guide_pb2.Point.FromString,
-      route_guide_pb2.Feature.SerializeToString,
-    ),
-    "ListFeatures": alpha_utilities.unary_stream_service_description(
-      servicer.ListFeatures,
-      route_guide_pb2.Rectangle.FromString,
-      route_guide_pb2.Feature.SerializeToString,
-    ),
-    "RecordRoute": alpha_utilities.stream_unary_service_description(
-      servicer.RecordRoute,
-      route_guide_pb2.Point.FromString,
-      route_guide_pb2.RouteSummary.SerializeToString,
-    ),
-    "RouteChat": alpha_utilities.stream_stream_service_description(
-      servicer.RouteChat,
-      route_guide_pb2.RouteNote.FromString,
-      route_guide_pb2.RouteNote.SerializeToString,
-    ),
-  }
-  return early_adopter_implementations.server("routeguide.RouteGuide", method_service_descriptions, port, private_key=private_key, certificate_chain=certificate_chain)
-def early_adopter_create_RouteGuide_stub(host, port, metadata_transformer=None, secure=False, root_certificates=None, private_key=None, certificate_chain=None, server_host_override=None):
-  import route_guide_pb2
-  import route_guide_pb2
-  import route_guide_pb2
-  import route_guide_pb2
-  import route_guide_pb2
-  import route_guide_pb2
-  import route_guide_pb2
-  import route_guide_pb2
-  method_invocation_descriptions = {
-    "GetFeature": alpha_utilities.unary_unary_invocation_description(
-      route_guide_pb2.Point.SerializeToString,
-      route_guide_pb2.Feature.FromString,
-    ),
-    "ListFeatures": alpha_utilities.unary_stream_invocation_description(
-      route_guide_pb2.Rectangle.SerializeToString,
-      route_guide_pb2.Feature.FromString,
-    ),
-    "RecordRoute": alpha_utilities.stream_unary_invocation_description(
-      route_guide_pb2.Point.SerializeToString,
-      route_guide_pb2.RouteSummary.FromString,
-    ),
-    "RouteChat": alpha_utilities.stream_stream_invocation_description(
-      route_guide_pb2.RouteNote.SerializeToString,
-      route_guide_pb2.RouteNote.FromString,
-    ),
-  }
-  return early_adopter_implementations.stub("routeguide.RouteGuide", method_invocation_descriptions, host, port, metadata_transformer=metadata_transformer, secure=secure, root_certificates=root_certificates, private_key=private_key, certificate_chain=certificate_chain, server_host_override=server_host_override)
 
 class BetaRouteGuideServicer(object):
-  """<fill me in later!>"""
-  __metaclass__ = abc.ABCMeta
-  @abc.abstractmethod
+  """Interface exported by the server.
+  """
   def GetFeature(self, request, context):
-    raise NotImplementedError()
-  @abc.abstractmethod
+    """A simple RPC.
+
+    Obtains the feature at a given position.
+
+    A feature with an empty name is returned if there's no feature at the given
+    position.
+    """
+    context.code(beta_interfaces.StatusCode.UNIMPLEMENTED)
   def ListFeatures(self, request, context):
-    raise NotImplementedError()
-  @abc.abstractmethod
+    """A server-to-client streaming RPC.
+
+    Obtains the Features available within the given Rectangle.  Results are
+    streamed rather than returned at once (e.g. in a response message with a
+    repeated field), as the rectangle may cover a large area and contain a
+    huge number of features.
+    """
+    context.code(beta_interfaces.StatusCode.UNIMPLEMENTED)
   def RecordRoute(self, request_iterator, context):
-    raise NotImplementedError()
-  @abc.abstractmethod
+    """A client-to-server streaming RPC.
+
+    Accepts a stream of Points on a route being traversed, returning a
+    RouteSummary when traversal is completed.
+    """
+    context.code(beta_interfaces.StatusCode.UNIMPLEMENTED)
   def RouteChat(self, request_iterator, context):
-    raise NotImplementedError()
+    """A Bidirectional streaming RPC.
+
+    Accepts a stream of RouteNotes sent while a route is being traversed,
+    while receiving other RouteNotes (e.g. from other users).
+    """
+    context.code(beta_interfaces.StatusCode.UNIMPLEMENTED)
 
 class BetaRouteGuideStub(object):
-  """The interface to which stubs will conform."""
-  __metaclass__ = abc.ABCMeta
-  @abc.abstractmethod
+  """Interface exported by the server.
+  """
   def GetFeature(self, request, timeout):
+    """A simple RPC.
+
+    Obtains the feature at a given position.
+
+    A feature with an empty name is returned if there's no feature at the given
+    position.
+    """
     raise NotImplementedError()
   GetFeature.future = None
-  @abc.abstractmethod
   def ListFeatures(self, request, timeout):
+    """A server-to-client streaming RPC.
+
+    Obtains the Features available within the given Rectangle.  Results are
+    streamed rather than returned at once (e.g. in a response message with a
+    repeated field), as the rectangle may cover a large area and contain a
+    huge number of features.
+    """
     raise NotImplementedError()
-  @abc.abstractmethod
   def RecordRoute(self, request_iterator, timeout):
+    """A client-to-server streaming RPC.
+
+    Accepts a stream of Points on a route being traversed, returning a
+    RouteSummary when traversal is completed.
+    """
     raise NotImplementedError()
   RecordRoute.future = None
-  @abc.abstractmethod
   def RouteChat(self, request_iterator, timeout):
+    """A Bidirectional streaming RPC.
+
+    Accepts a stream of RouteNotes sent while a route is being traversed,
+    while receiving other RouteNotes (e.g. from other users).
+    """
     raise NotImplementedError()
 
 def beta_create_RouteGuide_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None):
diff --git a/gRPC.podspec b/gRPC.podspec
index 77d35bd..018306c 100644
--- a/gRPC.podspec
+++ b/gRPC.podspec
@@ -287,6 +287,42 @@
                       'src/core/ext/client_config/subchannel_call_holder.h',
                       'src/core/ext/client_config/subchannel_index.h',
                       'src/core/ext/client_config/uri_parser.h',
+                      'include/grpc/byte_buffer.h',
+                      'include/grpc/grpc.h',
+                      'include/grpc/impl/codegen/alloc.h',
+                      'include/grpc/impl/codegen/atm.h',
+                      'include/grpc/impl/codegen/atm_gcc_atomic.h',
+                      'include/grpc/impl/codegen/atm_gcc_sync.h',
+                      'include/grpc/impl/codegen/atm_win32.h',
+                      'include/grpc/impl/codegen/byte_buffer.h',
+                      'include/grpc/impl/codegen/compression_types.h',
+                      'include/grpc/impl/codegen/connectivity_state.h',
+                      'include/grpc/impl/codegen/grpc_types.h',
+                      'include/grpc/impl/codegen/log.h',
+                      'include/grpc/impl/codegen/port_platform.h',
+                      'include/grpc/impl/codegen/propagation_bits.h',
+                      'include/grpc/impl/codegen/slice.h',
+                      'include/grpc/impl/codegen/slice_buffer.h',
+                      'include/grpc/impl/codegen/status.h',
+                      'include/grpc/impl/codegen/sync.h',
+                      'include/grpc/impl/codegen/sync_generic.h',
+                      'include/grpc/impl/codegen/sync_posix.h',
+                      'include/grpc/impl/codegen/sync_win32.h',
+                      'include/grpc/impl/codegen/time.h',
+                      'include/grpc/status.h',
+                      'include/grpc/support/alloc.h',
+                      'include/grpc/support/atm.h',
+                      'include/grpc/support/host_port.h',
+                      'include/grpc/support/log.h',
+                      'include/grpc/support/port_platform.h',
+                      'include/grpc/support/slice.h',
+                      'include/grpc/support/slice_buffer.h',
+                      'include/grpc/support/string_util.h',
+                      'include/grpc/support/sync.h',
+                      'include/grpc/support/time.h',
+                      'include/grpc/support/useful.h',
+                      'src/core/lib/support/string.h',
+                      'third_party/objective_c/Cronet/cronet_c_for_grpc.h',
                       'src/core/ext/lb_policy/grpclb/load_balancer_api.h',
                       'src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.h',
                       'third_party/nanopb/pb.h',
@@ -305,6 +341,7 @@
                       'include/grpc/grpc.h',
                       'include/grpc/status.h',
                       'include/grpc/impl/codegen/byte_buffer.h',
+                      'include/grpc/impl/codegen/byte_buffer_reader.h',
                       'include/grpc/impl/codegen/compression_types.h',
                       'include/grpc/impl/codegen/connectivity_state.h',
                       'include/grpc/impl/codegen/grpc_types.h',
@@ -324,6 +361,7 @@
                       'include/grpc/impl/codegen/sync_posix.h',
                       'include/grpc/impl/codegen/sync_win32.h',
                       'include/grpc/impl/codegen/time.h',
+                      'include/grpc/grpc_cronet.h',
                       'include/grpc/grpc_security.h',
                       'include/grpc/grpc_security_constants.h',
                       'include/grpc/census.h',
@@ -473,6 +511,9 @@
                       'src/core/ext/client_config/uri_parser.c',
                       'src/core/ext/transport/chttp2/server/insecure/server_chttp2.c',
                       'src/core/ext/transport/chttp2/client/insecure/channel_create.c',
+                      'src/core/ext/transport/cronet/client/secure/cronet_channel_create.c',
+                      'src/core/ext/transport/cronet/transport/cronet_api_dummy.c',
+                      'src/core/ext/transport/cronet/transport/cronet_transport.c',
                       'src/core/ext/lb_policy/grpclb/load_balancer_api.c',
                       'src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.c',
                       'third_party/nanopb/pb_common.c',
@@ -629,6 +670,42 @@
                               'src/core/ext/client_config/subchannel_call_holder.h',
                               'src/core/ext/client_config/subchannel_index.h',
                               'src/core/ext/client_config/uri_parser.h',
+                              'include/grpc/byte_buffer.h',
+                              'include/grpc/grpc.h',
+                              'include/grpc/impl/codegen/alloc.h',
+                              'include/grpc/impl/codegen/atm.h',
+                              'include/grpc/impl/codegen/atm_gcc_atomic.h',
+                              'include/grpc/impl/codegen/atm_gcc_sync.h',
+                              'include/grpc/impl/codegen/atm_win32.h',
+                              'include/grpc/impl/codegen/byte_buffer.h',
+                              'include/grpc/impl/codegen/compression_types.h',
+                              'include/grpc/impl/codegen/connectivity_state.h',
+                              'include/grpc/impl/codegen/grpc_types.h',
+                              'include/grpc/impl/codegen/log.h',
+                              'include/grpc/impl/codegen/port_platform.h',
+                              'include/grpc/impl/codegen/propagation_bits.h',
+                              'include/grpc/impl/codegen/slice.h',
+                              'include/grpc/impl/codegen/slice_buffer.h',
+                              'include/grpc/impl/codegen/status.h',
+                              'include/grpc/impl/codegen/sync.h',
+                              'include/grpc/impl/codegen/sync_generic.h',
+                              'include/grpc/impl/codegen/sync_posix.h',
+                              'include/grpc/impl/codegen/sync_win32.h',
+                              'include/grpc/impl/codegen/time.h',
+                              'include/grpc/status.h',
+                              'include/grpc/support/alloc.h',
+                              'include/grpc/support/atm.h',
+                              'include/grpc/support/host_port.h',
+                              'include/grpc/support/log.h',
+                              'include/grpc/support/port_platform.h',
+                              'include/grpc/support/slice.h',
+                              'include/grpc/support/slice_buffer.h',
+                              'include/grpc/support/string_util.h',
+                              'include/grpc/support/sync.h',
+                              'include/grpc/support/time.h',
+                              'include/grpc/support/useful.h',
+                              'src/core/lib/support/string.h',
+                              'third_party/objective_c/Cronet/cronet_c_for_grpc.h',
                               'src/core/ext/lb_policy/grpclb/load_balancer_api.h',
                               'src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.h',
                               'third_party/nanopb/pb.h',
diff --git a/grpc.def b/grpc.def
index 61948ed..09a94a6 100644
--- a/grpc.def
+++ b/grpc.def
@@ -87,6 +87,7 @@
     grpc_header_nonbin_value_is_legal
     grpc_is_binary_header
     grpc_call_error_to_string
+    grpc_cronet_secure_channel_create
     grpc_auth_property_iterator_next
     grpc_auth_context_property_iterator
     grpc_auth_context_peer_identity
diff --git a/grpc.gemspec b/grpc.gemspec
index e68cd81..ace2871 100755
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -149,6 +149,7 @@
   s.files += %w( include/grpc/grpc.h )
   s.files += %w( include/grpc/status.h )
   s.files += %w( include/grpc/impl/codegen/byte_buffer.h )
+  s.files += %w( include/grpc/impl/codegen/byte_buffer_reader.h )
   s.files += %w( include/grpc/impl/codegen/compression_types.h )
   s.files += %w( include/grpc/impl/codegen/connectivity_state.h )
   s.files += %w( include/grpc/impl/codegen/grpc_types.h )
@@ -168,6 +169,7 @@
   s.files += %w( include/grpc/impl/codegen/sync_posix.h )
   s.files += %w( include/grpc/impl/codegen/sync_win32.h )
   s.files += %w( include/grpc/impl/codegen/time.h )
+  s.files += %w( include/grpc/grpc_cronet.h )
   s.files += %w( include/grpc/grpc_security.h )
   s.files += %w( include/grpc/grpc_security_constants.h )
   s.files += %w( include/grpc/census.h )
@@ -295,6 +297,42 @@
   s.files += %w( src/core/ext/client_config/subchannel_call_holder.h )
   s.files += %w( src/core/ext/client_config/subchannel_index.h )
   s.files += %w( src/core/ext/client_config/uri_parser.h )
+  s.files += %w( include/grpc/byte_buffer.h )
+  s.files += %w( include/grpc/grpc.h )
+  s.files += %w( include/grpc/impl/codegen/alloc.h )
+  s.files += %w( include/grpc/impl/codegen/atm.h )
+  s.files += %w( include/grpc/impl/codegen/atm_gcc_atomic.h )
+  s.files += %w( include/grpc/impl/codegen/atm_gcc_sync.h )
+  s.files += %w( include/grpc/impl/codegen/atm_win32.h )
+  s.files += %w( include/grpc/impl/codegen/byte_buffer.h )
+  s.files += %w( include/grpc/impl/codegen/compression_types.h )
+  s.files += %w( include/grpc/impl/codegen/connectivity_state.h )
+  s.files += %w( include/grpc/impl/codegen/grpc_types.h )
+  s.files += %w( include/grpc/impl/codegen/log.h )
+  s.files += %w( include/grpc/impl/codegen/port_platform.h )
+  s.files += %w( include/grpc/impl/codegen/propagation_bits.h )
+  s.files += %w( include/grpc/impl/codegen/slice.h )
+  s.files += %w( include/grpc/impl/codegen/slice_buffer.h )
+  s.files += %w( include/grpc/impl/codegen/status.h )
+  s.files += %w( include/grpc/impl/codegen/sync.h )
+  s.files += %w( include/grpc/impl/codegen/sync_generic.h )
+  s.files += %w( include/grpc/impl/codegen/sync_posix.h )
+  s.files += %w( include/grpc/impl/codegen/sync_win32.h )
+  s.files += %w( include/grpc/impl/codegen/time.h )
+  s.files += %w( include/grpc/status.h )
+  s.files += %w( include/grpc/support/alloc.h )
+  s.files += %w( include/grpc/support/atm.h )
+  s.files += %w( include/grpc/support/host_port.h )
+  s.files += %w( include/grpc/support/log.h )
+  s.files += %w( include/grpc/support/port_platform.h )
+  s.files += %w( include/grpc/support/slice.h )
+  s.files += %w( include/grpc/support/slice_buffer.h )
+  s.files += %w( include/grpc/support/string_util.h )
+  s.files += %w( include/grpc/support/sync.h )
+  s.files += %w( include/grpc/support/time.h )
+  s.files += %w( include/grpc/support/useful.h )
+  s.files += %w( src/core/lib/support/string.h )
+  s.files += %w( third_party/objective_c/Cronet/cronet_c_for_grpc.h )
   s.files += %w( src/core/ext/lb_policy/grpclb/load_balancer_api.h )
   s.files += %w( src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.h )
   s.files += %w( third_party/nanopb/pb.h )
@@ -453,6 +491,9 @@
   s.files += %w( src/core/ext/client_config/uri_parser.c )
   s.files += %w( src/core/ext/transport/chttp2/server/insecure/server_chttp2.c )
   s.files += %w( src/core/ext/transport/chttp2/client/insecure/channel_create.c )
+  s.files += %w( src/core/ext/transport/cronet/client/secure/cronet_channel_create.c )
+  s.files += %w( src/core/ext/transport/cronet/transport/cronet_api_dummy.c )
+  s.files += %w( src/core/ext/transport/cronet/transport/cronet_transport.c )
   s.files += %w( src/core/ext/lb_policy/grpclb/load_balancer_api.c )
   s.files += %w( src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.c )
   s.files += %w( third_party/nanopb/pb_common.c )
diff --git a/include/grpc++/impl/codegen/call.h b/include/grpc++/impl/codegen/call.h
index aea1a6a..d081b7d 100644
--- a/include/grpc++/impl/codegen/call.h
+++ b/include/grpc++/impl/codegen/call.h
@@ -281,10 +281,9 @@
     if (message_ == nullptr) return;
     if (recv_buf_) {
       if (*status) {
-        got_message = true;
-        *status = SerializationTraits<R>::Deserialize(recv_buf_, message_,
-                                                      max_message_size)
-                      .ok();
+        got_message = *status = SerializationTraits<R>::Deserialize(
+                                    recv_buf_, message_, max_message_size)
+                                    .ok();
       } else {
         got_message = false;
         g_core_codegen_interface->grpc_byte_buffer_destroy(recv_buf_);
diff --git a/include/grpc++/impl/codegen/core_codegen_interface.h b/include/grpc++/impl/codegen/core_codegen_interface.h
index 16424ba..aa9013c 100644
--- a/include/grpc++/impl/codegen/core_codegen_interface.h
+++ b/include/grpc++/impl/codegen/core_codegen_interface.h
@@ -49,18 +49,6 @@
 /// \warning This interface should be considered internal and private.
 class CoreCodegenInterface {
  public:
-  // Serialize the msg into a buffer created inside the function. The caller
-  // should destroy the returned buffer when done with it. If serialization
-  // fails,
-  // false is returned and buffer is left unchanged.
-  virtual Status SerializeProto(const grpc::protobuf::Message& msg,
-                                grpc_byte_buffer** buffer) = 0;
-
-  // The caller keeps ownership of buffer and msg.
-  virtual Status DeserializeProto(grpc_byte_buffer* buffer,
-                                  grpc::protobuf::Message* msg,
-                                  int max_message_size) = 0;
-
   /// Upon a failed assertion, log the error.
   virtual void assert_fail(const char* failed_assertion) = 0;
 
@@ -76,9 +64,29 @@
   virtual void gpr_free(void* p) = 0;
 
   virtual void grpc_byte_buffer_destroy(grpc_byte_buffer* bb) = 0;
+
+  virtual void grpc_byte_buffer_reader_init(grpc_byte_buffer_reader* reader,
+                                            grpc_byte_buffer* buffer) = 0;
+  virtual void grpc_byte_buffer_reader_destroy(
+      grpc_byte_buffer_reader* reader) = 0;
+  virtual int grpc_byte_buffer_reader_next(grpc_byte_buffer_reader* reader,
+                                           gpr_slice* slice) = 0;
+
+  virtual grpc_byte_buffer* grpc_raw_byte_buffer_create(gpr_slice* slice,
+                                                        size_t nslices) = 0;
+
+  virtual gpr_slice gpr_slice_malloc(size_t length) = 0;
+  virtual void gpr_slice_unref(gpr_slice slice) = 0;
+  virtual gpr_slice gpr_slice_split_tail(gpr_slice* s, size_t split) = 0;
+  virtual void gpr_slice_buffer_add(gpr_slice_buffer* sb, gpr_slice slice) = 0;
+  virtual void gpr_slice_buffer_pop(gpr_slice_buffer* sb) = 0;
+
   virtual void grpc_metadata_array_init(grpc_metadata_array* array) = 0;
   virtual void grpc_metadata_array_destroy(grpc_metadata_array* array) = 0;
 
+  virtual const Status& ok() = 0;
+  virtual const Status& cancelled() = 0;
+
   virtual gpr_timespec gpr_inf_future(gpr_clock_type type) = 0;
 };
 
diff --git a/include/grpc++/impl/codegen/proto_utils.h b/include/grpc++/impl/codegen/proto_utils.h
index 2aaa3c3..d044ddc 100644
--- a/include/grpc++/impl/codegen/proto_utils.h
+++ b/include/grpc++/impl/codegen/proto_utils.h
@@ -41,26 +41,179 @@
 #include <grpc++/impl/codegen/serialization_traits.h>
 #include <grpc++/impl/codegen/status.h>
 #include <grpc/impl/codegen/byte_buffer.h>
+#include <grpc/impl/codegen/byte_buffer_reader.h>
 #include <grpc/impl/codegen/log.h>
+#include <grpc/impl/codegen/slice.h>
 
 namespace grpc {
 
 extern CoreCodegenInterface* g_core_codegen_interface;
 
+namespace {
+
+const int kGrpcBufferWriterMaxBufferLength = 8192;
+
+class GrpcBufferWriter GRPC_FINAL
+    : public ::grpc::protobuf::io::ZeroCopyOutputStream {
+ public:
+  explicit GrpcBufferWriter(grpc_byte_buffer** bp, int block_size)
+      : block_size_(block_size), byte_count_(0), have_backup_(false) {
+    *bp = g_core_codegen_interface->grpc_raw_byte_buffer_create(NULL, 0);
+    slice_buffer_ = &(*bp)->data.raw.slice_buffer;
+  }
+
+  ~GrpcBufferWriter() GRPC_OVERRIDE {
+    if (have_backup_) {
+      g_core_codegen_interface->gpr_slice_unref(backup_slice_);
+    }
+  }
+
+  bool Next(void** data, int* size) GRPC_OVERRIDE {
+    if (have_backup_) {
+      slice_ = backup_slice_;
+      have_backup_ = false;
+    } else {
+      slice_ = g_core_codegen_interface->gpr_slice_malloc(block_size_);
+    }
+    *data = GPR_SLICE_START_PTR(slice_);
+    // On win x64, int is only 32bit
+    GPR_CODEGEN_ASSERT(GPR_SLICE_LENGTH(slice_) <= INT_MAX);
+    byte_count_ += * size = (int)GPR_SLICE_LENGTH(slice_);
+    g_core_codegen_interface->gpr_slice_buffer_add(slice_buffer_, slice_);
+    return true;
+  }
+
+  void BackUp(int count) GRPC_OVERRIDE {
+    g_core_codegen_interface->gpr_slice_buffer_pop(slice_buffer_);
+    if (count == block_size_) {
+      backup_slice_ = slice_;
+    } else {
+      backup_slice_ = g_core_codegen_interface->gpr_slice_split_tail(
+          &slice_, GPR_SLICE_LENGTH(slice_) - count);
+      g_core_codegen_interface->gpr_slice_buffer_add(slice_buffer_, slice_);
+    }
+    have_backup_ = true;
+    byte_count_ -= count;
+  }
+
+  grpc::protobuf::int64 ByteCount() const GRPC_OVERRIDE { return byte_count_; }
+
+ private:
+  const int block_size_;
+  int64_t byte_count_;
+  gpr_slice_buffer* slice_buffer_;
+  bool have_backup_;
+  gpr_slice backup_slice_;
+  gpr_slice slice_;
+};
+
+class GrpcBufferReader GRPC_FINAL
+    : public ::grpc::protobuf::io::ZeroCopyInputStream {
+ public:
+  explicit GrpcBufferReader(grpc_byte_buffer* buffer)
+      : byte_count_(0), backup_count_(0) {
+    g_core_codegen_interface->grpc_byte_buffer_reader_init(&reader_, buffer);
+  }
+  ~GrpcBufferReader() GRPC_OVERRIDE {
+    g_core_codegen_interface->grpc_byte_buffer_reader_destroy(&reader_);
+  }
+
+  bool Next(const void** data, int* size) GRPC_OVERRIDE {
+    if (backup_count_ > 0) {
+      *data = GPR_SLICE_START_PTR(slice_) + GPR_SLICE_LENGTH(slice_) -
+              backup_count_;
+      GPR_CODEGEN_ASSERT(backup_count_ <= INT_MAX);
+      *size = (int)backup_count_;
+      backup_count_ = 0;
+      return true;
+    }
+    if (!g_core_codegen_interface->grpc_byte_buffer_reader_next(&reader_,
+                                                                &slice_)) {
+      return false;
+    }
+    g_core_codegen_interface->gpr_slice_unref(slice_);
+    *data = GPR_SLICE_START_PTR(slice_);
+    // On win x64, int is only 32bit
+    GPR_CODEGEN_ASSERT(GPR_SLICE_LENGTH(slice_) <= INT_MAX);
+    byte_count_ += * size = (int)GPR_SLICE_LENGTH(slice_);
+    return true;
+  }
+
+  void BackUp(int count) GRPC_OVERRIDE { backup_count_ = count; }
+
+  bool Skip(int count) GRPC_OVERRIDE {
+    const void* data;
+    int size;
+    while (Next(&data, &size)) {
+      if (size >= count) {
+        BackUp(size - count);
+        return true;
+      }
+      // size < count;
+      count -= size;
+    }
+    // error or we have too large count;
+    return false;
+  }
+
+  grpc::protobuf::int64 ByteCount() const GRPC_OVERRIDE {
+    return byte_count_ - backup_count_;
+  }
+
+ private:
+  int64_t byte_count_;
+  int64_t backup_count_;
+  grpc_byte_buffer_reader reader_;
+  gpr_slice slice_;
+};
+}  // namespace
+
 template <class T>
 class SerializationTraits<T, typename std::enable_if<std::is_base_of<
                                  grpc::protobuf::Message, T>::value>::type> {
  public:
   static Status Serialize(const grpc::protobuf::Message& msg,
-                          grpc_byte_buffer** buffer, bool* own_buffer) {
+                          grpc_byte_buffer** bp, bool* own_buffer) {
     *own_buffer = true;
-    return g_core_codegen_interface->SerializeProto(msg, buffer);
+    int byte_size = msg.ByteSize();
+    if (byte_size <= kGrpcBufferWriterMaxBufferLength) {
+      gpr_slice slice = g_core_codegen_interface->gpr_slice_malloc(byte_size);
+      GPR_CODEGEN_ASSERT(
+          GPR_SLICE_END_PTR(slice) ==
+          msg.SerializeWithCachedSizesToArray(GPR_SLICE_START_PTR(slice)));
+      *bp = g_core_codegen_interface->grpc_raw_byte_buffer_create(&slice, 1);
+      g_core_codegen_interface->gpr_slice_unref(slice);
+      return g_core_codegen_interface->ok();
+    } else {
+      GrpcBufferWriter writer(bp, kGrpcBufferWriterMaxBufferLength);
+      return msg.SerializeToZeroCopyStream(&writer)
+                 ? g_core_codegen_interface->ok()
+                 : Status(StatusCode::INTERNAL, "Failed to serialize message");
+    }
   }
+
   static Status Deserialize(grpc_byte_buffer* buffer,
                             grpc::protobuf::Message* msg,
                             int max_message_size) {
-    return g_core_codegen_interface->DeserializeProto(buffer, msg,
-                                                      max_message_size);
+    if (buffer == nullptr) {
+      return Status(StatusCode::INTERNAL, "No payload");
+    }
+    Status result = g_core_codegen_interface->ok();
+    {
+      GrpcBufferReader reader(buffer);
+      ::grpc::protobuf::io::CodedInputStream decoder(&reader);
+      if (max_message_size > 0) {
+        decoder.SetTotalBytesLimit(max_message_size, max_message_size);
+      }
+      if (!msg->ParseFromCodedStream(&decoder)) {
+        result = Status(StatusCode::INTERNAL, msg->InitializationErrorString());
+      }
+      if (!decoder.ConsumedEntireMessage()) {
+        result = Status(StatusCode::INTERNAL, "Did not read entire message");
+      }
+    }
+    g_core_codegen_interface->grpc_byte_buffer_destroy(buffer);
+    return result;
   }
 };
 
diff --git a/include/grpc/byte_buffer_reader.h b/include/grpc/byte_buffer_reader.h
index 9a1c617..e95bf2f 100644
--- a/include/grpc/byte_buffer_reader.h
+++ b/include/grpc/byte_buffer_reader.h
@@ -34,25 +34,6 @@
 #ifndef GRPC_BYTE_BUFFER_READER_H
 #define GRPC_BYTE_BUFFER_READER_H
 
-#include <grpc/byte_buffer.h>
-#include <grpc/grpc.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct grpc_byte_buffer_reader {
-  grpc_byte_buffer *buffer_in;
-  grpc_byte_buffer *buffer_out;
-  /* Different current objects correspond to different types of byte buffers */
-  union {
-    /* Index into a slice buffer's array of slices */
-    unsigned index;
-  } current;
-};
-
-#ifdef __cplusplus
-}
-#endif
+#include <grpc/impl/codegen/byte_buffer_reader.h>
 
 #endif /* GRPC_BYTE_BUFFER_READER_H */
diff --git a/include/grpc/grpc_cronet.h b/include/grpc/grpc_cronet.h
new file mode 100644
index 0000000..295e0f5
--- /dev/null
+++ b/include/grpc/grpc_cronet.h
@@ -0,0 +1,51 @@
+/*
+ *
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef GRPC_GRPC_CRONET_H
+#define GRPC_GRPC_CRONET_H
+
+#include <grpc/grpc.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+GRPCAPI grpc_channel *grpc_cronet_secure_channel_create(
+    void *engine, const char *target, const grpc_channel_args *args,
+    void *reserved);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GRPC_GRPC_CRONET_H */
diff --git a/include/grpc/impl/codegen/byte_buffer_reader.h b/include/grpc/impl/codegen/byte_buffer_reader.h
new file mode 100644
index 0000000..10c3829
--- /dev/null
+++ b/include/grpc/impl/codegen/byte_buffer_reader.h
@@ -0,0 +1,57 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef GRPC_IMPL_CODEGEN_BYTE_BUFFER_READER_H
+#define GRPC_IMPL_CODEGEN_BYTE_BUFFER_READER_H
+
+#include <grpc/impl/codegen/byte_buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct grpc_byte_buffer_reader {
+  grpc_byte_buffer *buffer_in;
+  grpc_byte_buffer *buffer_out;
+  /* Different current objects correspond to different types of byte buffers */
+  union {
+    /* Index into a slice buffer's array of slices */
+    unsigned index;
+  } current;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GRPC_IMPL_CODEGEN_BYTE_BUFFER_READER_H */
diff --git a/include/grpc/impl/codegen/grpc_types.h b/include/grpc/impl/codegen/grpc_types.h
index 4c73730..7b20cc1 100644
--- a/include/grpc/impl/codegen/grpc_types.h
+++ b/include/grpc/impl/codegen/grpc_types.h
@@ -307,7 +307,9 @@
   GRPC_OP_RECV_STATUS_ON_CLIENT,
   /** Receive close on the server: one and only one must be made on the
       server.
-      This op completes after the close has been received by the server. */
+      This op completes after the close has been received by the server.
+      This operation always succeeds, meaning ops paired with this operation
+      will also appear to succeed, even though they may not have. */
   GRPC_OP_RECV_CLOSE_ON_SERVER
 } grpc_op_type;
 
diff --git a/package.xml b/package.xml
index ffb1c56..716d6ed 100644
--- a/package.xml
+++ b/package.xml
@@ -156,6 +156,7 @@
     <file baseinstalldir="/" name="include/grpc/grpc.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/status.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/impl/codegen/byte_buffer.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/byte_buffer_reader.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/impl/codegen/compression_types.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/impl/codegen/connectivity_state.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/impl/codegen/grpc_types.h" role="src" />
@@ -175,6 +176,7 @@
     <file baseinstalldir="/" name="include/grpc/impl/codegen/sync_posix.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/impl/codegen/sync_win32.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/impl/codegen/time.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/grpc_cronet.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/grpc_security.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/grpc_security_constants.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/census.h" role="src" />
@@ -302,6 +304,42 @@
     <file baseinstalldir="/" name="src/core/ext/client_config/subchannel_call_holder.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/client_config/subchannel_index.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/client_config/uri_parser.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/byte_buffer.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/grpc.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/alloc.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/atm.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/atm_gcc_atomic.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/atm_gcc_sync.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/atm_win32.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/byte_buffer.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/compression_types.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/connectivity_state.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/grpc_types.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/log.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/port_platform.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/propagation_bits.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/slice.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/slice_buffer.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/status.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/sync.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/sync_generic.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/sync_posix.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/sync_win32.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/impl/codegen/time.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/status.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/support/alloc.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/support/atm.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/support/host_port.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/support/log.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/support/port_platform.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/support/slice.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/support/slice_buffer.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/support/string_util.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/support/sync.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/support/time.h" role="src" />
+    <file baseinstalldir="/" name="include/grpc/support/useful.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/support/string.h" role="src" />
+    <file baseinstalldir="/" name="third_party/objective_c/Cronet/cronet_c_for_grpc.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/lb_policy/grpclb/load_balancer_api.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.h" role="src" />
     <file baseinstalldir="/" name="third_party/nanopb/pb.h" role="src" />
@@ -460,6 +498,9 @@
     <file baseinstalldir="/" name="src/core/ext/client_config/uri_parser.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/transport/chttp2/server/insecure/server_chttp2.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/transport/chttp2/client/insecure/channel_create.c" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/transport/cronet/client/secure/cronet_channel_create.c" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/transport/cronet/transport/cronet_api_dummy.c" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/transport/cronet/transport/cronet_transport.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/lb_policy/grpclb/load_balancer_api.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.c" role="src" />
     <file baseinstalldir="/" name="third_party/nanopb/pb_common.c" role="src" />
diff --git a/src/compiler/python_generator.cc b/src/compiler/python_generator.cc
index 59137e1..8e76e6d 100644
--- a/src/compiler/python_generator.cc
+++ b/src/compiler/python_generator.cc
@@ -182,18 +182,40 @@
   return true;
 }
 
+// Get all comments (leading, leading_detached, trailing) and print them as a
+// docstring. Any leading space of a line will be removed, but the line wrapping
+// will not be changed.
+template <typename DescriptorType>
+static void PrintAllComments(const DescriptorType* desc, Printer* printer) {
+  std::vector<grpc::string> comments;
+  grpc_generator::GetComment(desc, grpc_generator::COMMENTTYPE_LEADING_DETACHED,
+                             &comments);
+  grpc_generator::GetComment(desc, grpc_generator::COMMENTTYPE_LEADING,
+                             &comments);
+  grpc_generator::GetComment(desc, grpc_generator::COMMENTTYPE_TRAILING,
+                             &comments);
+  if (comments.empty()) {
+    return;
+  }
+  printer->Print("\"\"\"");
+  for (auto it = comments.begin(); it != comments.end(); ++it) {
+    size_t start_pos = it->find_first_not_of(' ');
+    if (start_pos != grpc::string::npos) {
+      printer->Print(it->c_str() + start_pos);
+    }
+    printer->Print("\n");
+  }
+  printer->Print("\"\"\"\n");
+}
+
 bool PrintBetaServicer(const ServiceDescriptor* service,
                        Printer* out) {
-  grpc::string doc = "<fill me in later!>";
-  map<grpc::string, grpc::string> dict = ListToDict({
-        "Service", service->name(),
-        "Documentation", doc,
-      });
   out->Print("\n");
-  out->Print(dict, "class Beta$Service$Servicer(object):\n");
+  out->Print("class Beta$Service$Servicer(object):\n", "Service",
+             service->name());
   {
     IndentScope raii_class_indent(out);
-    out->Print(dict, "\"\"\"$Documentation$\"\"\"\n");
+    PrintAllComments(service, out);
     for (int i = 0; i < service->method_count(); ++i) {
       auto meth = service->method(i);
       grpc::string arg_name = meth->client_streaming() ?
@@ -202,6 +224,7 @@
                  "Method", meth->name(), "ArgName", arg_name);
       {
         IndentScope raii_method_indent(out);
+        PrintAllComments(meth, out);
         out->Print("context.code(beta_interfaces.StatusCode.UNIMPLEMENTED)\n");
       }
     }
@@ -211,16 +234,11 @@
 
 bool PrintBetaStub(const ServiceDescriptor* service,
                    Printer* out) {
-  grpc::string doc = "The interface to which stubs will conform.";
-  map<grpc::string, grpc::string> dict = ListToDict({
-        "Service", service->name(),
-        "Documentation", doc,
-      });
   out->Print("\n");
-  out->Print(dict, "class Beta$Service$Stub(object):\n");
+  out->Print("class Beta$Service$Stub(object):\n", "Service", service->name());
   {
     IndentScope raii_class_indent(out);
-    out->Print(dict, "\"\"\"$Documentation$\"\"\"\n");
+    PrintAllComments(service, out);
     for (int i = 0; i < service->method_count(); ++i) {
       const MethodDescriptor* meth = service->method(i);
       grpc::string arg_name = meth->client_streaming() ?
@@ -229,6 +247,7 @@
       out->Print(methdict, "def $Method$(self, $ArgName$, timeout):\n");
       {
         IndentScope raii_method_indent(out);
+        PrintAllComments(meth, out);
         out->Print("raise NotImplementedError()\n");
       }
       if (!meth->server_streaming()) {
diff --git a/src/core/ext/client_config/resolver_registry.c b/src/core/ext/client_config/resolver_registry.c
index 07f29bc..e7a4abd 100644
--- a/src/core/ext/client_config/resolver_registry.c
+++ b/src/core/ext/client_config/resolver_registry.c
@@ -47,7 +47,6 @@
 static char *g_default_resolver_prefix;
 
 void grpc_resolver_registry_init(const char *default_resolver_prefix) {
-  g_number_of_resolvers = 0;
   g_default_resolver_prefix = gpr_strdup(default_resolver_prefix);
 }
 
@@ -57,6 +56,13 @@
     grpc_resolver_factory_unref(g_all_of_the_resolvers[i]);
   }
   gpr_free(g_default_resolver_prefix);
+  // FIXME(ctiller): this should live in grpc_resolver_registry_init,
+  // however that would have the client_config plugin call this AFTER we start
+  // registering resolvers from third party plugins, and so they'd never show
+  // up.
+  // We likely need some kind of dependency system for plugins.... what form
+  // that takes is TBD.
+  g_number_of_resolvers = 0;
 }
 
 void grpc_register_resolver_type(grpc_resolver_factory *factory) {
diff --git a/src/core/ext/client_config/subchannel.c b/src/core/ext/client_config/subchannel.c
index 3a5af9f..bd45d38 100644
--- a/src/core/ext/client_config/subchannel.c
+++ b/src/core/ext/client_config/subchannel.c
@@ -268,7 +268,7 @@
   con = GET_CONNECTED_SUBCHANNEL(c, no_barrier);
   if (con != NULL) {
     GRPC_CONNECTED_SUBCHANNEL_UNREF(exec_ctx, con, "connection");
-    gpr_atm_no_barrier_store(&c->connected_subchannel, 0xdeadbeef);
+    gpr_atm_no_barrier_store(&c->connected_subchannel, (gpr_atm)0xdeadbeef);
   }
   gpr_mu_unlock(&c->mu);
 }
diff --git a/src/core/ext/transport/chttp2/transport/chttp2_transport.c b/src/core/ext/transport/chttp2/transport/chttp2_transport.c
index fcf2abf..8c85937 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_transport.c
+++ b/src/core/ext/transport/chttp2/transport/chttp2_transport.c
@@ -629,9 +629,10 @@
     check_read_ops(exec_ctx, &t->global);
 
     gpr_mu_lock(&t->executor.mu);
-    if (t->executor.pending_actions != NULL) {
-      hdr = t->executor.pending_actions;
-      t->executor.pending_actions = NULL;
+    if (t->executor.pending_actions_head != NULL) {
+      hdr = t->executor.pending_actions_head;
+      t->executor.pending_actions_head = t->executor.pending_actions_tail =
+          NULL;
       gpr_mu_unlock(&t->executor.mu);
       while (hdr != NULL) {
         hdr->action(exec_ctx, t, hdr->stream, hdr->arg);
@@ -686,8 +687,14 @@
         gpr_free(hdr);
         continue;
       }
-      hdr->next = t->executor.pending_actions;
-      t->executor.pending_actions = hdr;
+      hdr->next = NULL;
+      if (t->executor.pending_actions_head != NULL) {
+        t->executor.pending_actions_tail =
+            t->executor.pending_actions_tail->next = hdr;
+      } else {
+        t->executor.pending_actions_tail = t->executor.pending_actions_head =
+            hdr;
+      }
       REF_TRANSPORT(t, "pending_action");
       gpr_mu_unlock(&t->executor.mu);
     }
diff --git a/src/core/ext/transport/chttp2/transport/internal.h b/src/core/ext/transport/chttp2/transport/internal.h
index 7a80846..8c4fa2d 100644
--- a/src/core/ext/transport/chttp2/transport/internal.h
+++ b/src/core/ext/transport/chttp2/transport/internal.h
@@ -236,9 +236,6 @@
   /** was a goaway frame received? */
   uint8_t goaway_received;
 
-  /** the last sent max_table_size setting */
-  uint32_t last_sent_max_table_size;
-
   /** initial window change */
   int64_t initial_window_update;
 
@@ -272,6 +269,9 @@
   uint32_t incoming_frame_size;
   uint32_t incoming_stream_id;
 
+  /* current max frame size */
+  uint32_t max_frame_size;
+
   /* active parser */
   void *parser_data;
   grpc_chttp2_stream_parsing *incoming_stream;
@@ -282,6 +282,8 @@
 
   /* received settings */
   uint32_t settings[GRPC_CHTTP2_NUM_SETTINGS];
+  /* last settings that were sent */
+  uint32_t last_sent_settings[GRPC_CHTTP2_NUM_SETTINGS];
 
   /* goaway data */
   grpc_status_code goaway_error;
@@ -321,7 +323,8 @@
     /** is a thread currently parsing */
     bool parsing_active;
 
-    grpc_chttp2_executor_action_header *pending_actions;
+    grpc_chttp2_executor_action_header *pending_actions_head;
+    grpc_chttp2_executor_action_header *pending_actions_tail;
   } executor;
 
   /** is the transport destroying itself? */
diff --git a/src/core/ext/transport/chttp2/transport/parsing.c b/src/core/ext/transport/chttp2/transport/parsing.c
index e827a43..2995066 100644
--- a/src/core/ext/transport/chttp2/transport/parsing.c
+++ b/src/core/ext/transport/chttp2/transport/parsing.c
@@ -79,9 +79,12 @@
   GPR_TIMER_BEGIN("grpc_chttp2_prepare_to_read", 0);
 
   transport_parsing->next_stream_id = transport_global->next_stream_id;
-  transport_parsing->last_sent_max_table_size =
-      transport_global->settings[GRPC_SENT_SETTINGS]
-                                [GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE];
+  memcpy(transport_parsing->last_sent_settings,
+         transport_global->settings[GRPC_SENT_SETTINGS],
+         sizeof(transport_parsing->last_sent_settings));
+  transport_parsing->max_frame_size =
+      transport_global->settings[GRPC_ACKED_SETTINGS]
+                                [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE];
 
   /* update the parsing view of incoming window */
   while (grpc_chttp2_list_pop_unannounced_incoming_window_available(
@@ -388,6 +391,12 @@
           return 1;
         }
         goto dts_fh_0; /* loop */
+      } else if (transport_parsing->incoming_frame_size >
+                 transport_parsing->max_frame_size) {
+        gpr_log(GPR_DEBUG, "Frame size %d is larger than max frame size %d",
+                transport_parsing->incoming_frame_size,
+                transport_parsing->max_frame_size);
+        return 0;
       }
       if (++cur == end) {
         return 1;
@@ -840,7 +849,11 @@
     transport_parsing->settings_ack_received = 1;
     grpc_chttp2_hptbl_set_max_bytes(
         &transport_parsing->hpack_parser.table,
-        transport_parsing->last_sent_max_table_size);
+        transport_parsing
+            ->last_sent_settings[GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE]);
+    transport_parsing->max_frame_size =
+        transport_parsing
+            ->last_sent_settings[GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE];
   }
   transport_parsing->parser = grpc_chttp2_settings_parser_parse;
   transport_parsing->parser_data = &transport_parsing->simple.settings;
diff --git a/src/core/ext/transport/cronet/client/secure/cronet_channel_create.c b/src/core/ext/transport/cronet/client/secure/cronet_channel_create.c
new file mode 100644
index 0000000..df1acdd
--- /dev/null
+++ b/src/core/ext/transport/cronet/client/secure/cronet_channel_create.c
@@ -0,0 +1,69 @@
+/*
+ *
+ * Copyright 2016, 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.
+ *
+ */
+
+#include <grpc/impl/codegen/port_platform.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+#include "src/core/lib/surface/channel.h"
+#include "src/core/lib/transport/transport_impl.h"
+
+// Cronet transport object
+typedef struct cronet_transport {
+  grpc_transport base;  // must be first element in this structure
+  void *engine;
+  char *host;
+} cronet_transport;
+
+extern grpc_transport_vtable grpc_cronet_vtable;
+
+GRPCAPI grpc_channel *grpc_cronet_secure_channel_create(
+    void *engine, const char *target, const grpc_channel_args *args,
+    void *reserved) {
+  cronet_transport *ct = gpr_malloc(sizeof(cronet_transport));
+  ct->base.vtable = &grpc_cronet_vtable;
+  ct->engine = engine;
+  ct->host = gpr_malloc(strlen(target) + 1);
+  strcpy(ct->host, target);
+  gpr_log(GPR_DEBUG,
+          "grpc_create_cronet_transport: cronet_engine = %p, target=%s", engine,
+          ct->host);
+
+  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  return grpc_channel_create(&exec_ctx, target, args,
+                             GRPC_CLIENT_DIRECT_CHANNEL, (grpc_transport *)ct);
+}
diff --git a/src/core/ext/transport/cronet/transport/cronet_api_dummy.c b/src/core/ext/transport/cronet/transport/cronet_api_dummy.c
new file mode 100644
index 0000000..687026c
--- /dev/null
+++ b/src/core/ext/transport/cronet/transport/cronet_api_dummy.c
@@ -0,0 +1,85 @@
+/*
+ *
+ * Copyright 2016, 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.
+ *
+ */
+
+/* This file has empty implementation of all the functions exposed by the cronet
+library, so we can build it in all environments */
+
+#include <stdbool.h>
+
+#include <grpc/support/log.h>
+
+#include "third_party/objective_c/Cronet/cronet_c_for_grpc.h"
+
+#ifdef GRPC_COMPILE_WITH_CRONET
+/* link with the real CRONET library in the build system */
+#else
+/* Dummy implementation of cronet API just to test for build-ability */
+cronet_bidirectional_stream* cronet_bidirectional_stream_create(
+    cronet_engine* engine, void* annotation,
+    cronet_bidirectional_stream_callback* callback) {
+  GPR_ASSERT(0);
+  return NULL;
+}
+
+int cronet_bidirectional_stream_destroy(cronet_bidirectional_stream* stream) {
+  GPR_ASSERT(0);
+  return 0;
+}
+
+int cronet_bidirectional_stream_start(
+    cronet_bidirectional_stream* stream, const char* url, int priority,
+    const char* method, const cronet_bidirectional_stream_header_array* headers,
+    bool end_of_stream) {
+  GPR_ASSERT(0);
+  return 0;
+}
+
+int cronet_bidirectional_stream_read(cronet_bidirectional_stream* stream,
+                                     char* buffer, int capacity) {
+  GPR_ASSERT(0);
+  return 0;
+}
+
+int cronet_bidirectional_stream_write(cronet_bidirectional_stream* stream,
+                                      const char* buffer, int count,
+                                      bool end_of_stream) {
+  GPR_ASSERT(0);
+  return 0;
+}
+
+int cronet_bidirectional_stream_cancel(cronet_bidirectional_stream* stream) {
+  GPR_ASSERT(0);
+  return 0;
+}
+
+#endif /* GRPC_COMPILE_WITH_CRONET */
diff --git a/src/core/ext/transport/cronet/transport/cronet_transport.c b/src/core/ext/transport/cronet/transport/cronet_transport.c
new file mode 100644
index 0000000..5bb0851
--- /dev/null
+++ b/src/core/ext/transport/cronet/transport/cronet_transport.c
@@ -0,0 +1,640 @@
+/*
+ *
+ * Copyright 2016, 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.
+ *
+ */
+
+#include <string.h>
+
+#include <grpc/impl/codegen/port_platform.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/host_port.h>
+#include <grpc/support/log.h>
+#include <grpc/support/slice_buffer.h>
+#include <grpc/support/string_util.h>
+#include <grpc/support/useful.h>
+
+#include "src/core/ext/transport/chttp2/transport/incoming_metadata.h"
+#include "src/core/lib/iomgr/exec_ctx.h"
+#include "src/core/lib/support/string.h"
+#include "src/core/lib/surface/channel.h"
+#include "src/core/lib/transport/metadata_batch.h"
+#include "src/core/lib/transport/transport_impl.h"
+#include "third_party/objective_c/Cronet/cronet_c_for_grpc.h"
+
+#define GRPC_HEADER_SIZE_IN_BYTES 5
+
+// Global flag that gets set with GRPC_TRACE env variable
+int grpc_cronet_trace = 1;
+
+// Cronet transport object
+struct grpc_cronet_transport {
+  grpc_transport base; /* must be first element in this structure */
+  cronet_engine *engine;
+  char *host;
+};
+
+typedef struct grpc_cronet_transport grpc_cronet_transport;
+
+enum send_state {
+  CRONET_SEND_IDLE = 0,
+  CRONET_REQ_STARTED,
+  CRONET_SEND_HEADER,
+  CRONET_WRITE,
+  CRONET_WRITE_COMPLETED,
+};
+
+enum recv_state {
+  CRONET_RECV_IDLE = 0,
+  CRONET_RECV_READ_LENGTH,
+  CRONET_RECV_READ_DATA,
+  CRONET_RECV_CLOSED,
+};
+
+static const char *recv_state_name[] = {
+    "CRONET_RECV_IDLE", "CRONET_RECV_READ_LENGTH", "CRONET_RECV_READ_DATA,",
+    "CRONET_RECV_CLOSED"};
+
+// Enum that identifies calling function.
+enum e_caller {
+  PERFORM_STREAM_OP,
+  ON_READ_COMPLETE,
+  ON_RESPONSE_HEADERS_RECEIVED,
+  ON_RESPONSE_TRAILERS_RECEIVED
+};
+
+enum callback_id {
+  CB_SEND_INITIAL_METADATA = 0,
+  CB_SEND_MESSAGE,
+  CB_SEND_TRAILING_METADATA,
+  CB_RECV_MESSAGE,
+  CB_RECV_INITIAL_METADATA,
+  CB_RECV_TRAILING_METADATA,
+  CB_NUM_CALLBACKS
+};
+
+struct stream_obj {
+  // we store received bytes here as they trickle in.
+  gpr_slice_buffer write_slice_buffer;
+  cronet_bidirectional_stream *cbs;
+  gpr_slice slice;
+  gpr_slice_buffer read_slice_buffer;
+  struct grpc_slice_buffer_stream sbs;
+  char *read_buffer;
+  int remaining_read_bytes;
+  int total_read_bytes;
+
+  char *write_buffer;
+  size_t write_buffer_size;
+
+  // Hold the URL
+  char *url;
+
+  bool response_headers_received;
+  bool read_requested;
+  bool response_trailers_received;
+  bool read_closed;
+
+  // Recv message stuff
+  grpc_byte_buffer **recv_message;
+  // Initial metadata stuff
+  grpc_metadata_batch *recv_initial_metadata;
+  // Trailing metadata stuff
+  grpc_metadata_batch *recv_trailing_metadata;
+  grpc_chttp2_incoming_metadata_buffer imb;
+
+  // This mutex protects receive state machine execution
+  gpr_mu recv_mu;
+  // we can queue up up to 2 callbacks for each OP
+  grpc_closure *callback_list[CB_NUM_CALLBACKS][2];
+
+  // storage for header
+  cronet_bidirectional_stream_header *headers;
+  uint32_t num_headers;
+  cronet_bidirectional_stream_header_array header_array;
+  // state tracking
+  enum recv_state cronet_recv_state;
+  enum send_state cronet_send_state;
+};
+
+typedef struct stream_obj stream_obj;
+
+static void next_send_step(stream_obj *s);
+static void next_recv_step(stream_obj *s, enum e_caller caller);
+
+static void set_pollset_do_nothing(grpc_exec_ctx *exec_ctx, grpc_transport *gt,
+                                   grpc_stream *gs, grpc_pollset *pollset) {}
+
+static void enqueue_callbacks(grpc_closure *callback_list[]) {
+  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  if (callback_list[0]) {
+    grpc_exec_ctx_enqueue(&exec_ctx, callback_list[0], true, NULL);
+    callback_list[0] = NULL;
+  }
+  if (callback_list[1]) {
+    grpc_exec_ctx_enqueue(&exec_ctx, callback_list[1], true, NULL);
+    callback_list[1] = NULL;
+  }
+  grpc_exec_ctx_finish(&exec_ctx);
+}
+
+static void on_canceled(cronet_bidirectional_stream *stream) {
+  if (grpc_cronet_trace) {
+    gpr_log(GPR_DEBUG, "on_canceled %p", stream);
+  }
+}
+
+static void on_failed(cronet_bidirectional_stream *stream, int net_error) {
+  if (grpc_cronet_trace) {
+    gpr_log(GPR_DEBUG, "on_failed %p, error = %d", stream, net_error);
+  }
+}
+
+static void on_succeeded(cronet_bidirectional_stream *stream) {
+  if (grpc_cronet_trace) {
+    gpr_log(GPR_DEBUG, "on_succeeded %p", stream);
+  }
+}
+
+static void on_response_trailers_received(
+    cronet_bidirectional_stream *stream,
+    const cronet_bidirectional_stream_header_array *trailers) {
+  if (grpc_cronet_trace) {
+    gpr_log(GPR_DEBUG, "R: on_response_trailers_received");
+  }
+  stream_obj *s = (stream_obj *)stream->annotation;
+
+  memset(&s->imb, 0, sizeof(s->imb));
+  grpc_chttp2_incoming_metadata_buffer_init(&s->imb);
+  unsigned int i = 0;
+  for (i = 0; i < trailers->count; i++) {
+    grpc_chttp2_incoming_metadata_buffer_add(
+        &s->imb, grpc_mdelem_from_metadata_strings(
+                     grpc_mdstr_from_string(trailers->headers[i].key),
+                     grpc_mdstr_from_string(trailers->headers[i].value)));
+  }
+  s->response_trailers_received = true;
+  next_recv_step(s, ON_RESPONSE_TRAILERS_RECEIVED);
+}
+
+static void on_write_completed(cronet_bidirectional_stream *stream,
+                               const char *data) {
+  if (grpc_cronet_trace) {
+    gpr_log(GPR_DEBUG, "W: on_write_completed");
+  }
+  stream_obj *s = (stream_obj *)stream->annotation;
+  enqueue_callbacks(s->callback_list[CB_SEND_MESSAGE]);
+  s->cronet_send_state = CRONET_WRITE_COMPLETED;
+  next_send_step(s);
+}
+
+static void process_recv_message(stream_obj *s, const uint8_t *recv_data) {
+  gpr_slice read_data_slice = gpr_slice_malloc((uint32_t)s->total_read_bytes);
+  uint8_t *dst_p = GPR_SLICE_START_PTR(read_data_slice);
+  memcpy(dst_p, recv_data, (size_t)s->total_read_bytes);
+  gpr_slice_buffer_add(&s->read_slice_buffer, read_data_slice);
+  grpc_slice_buffer_stream_init(&s->sbs, &s->read_slice_buffer, 0);
+  *s->recv_message = (grpc_byte_buffer *)&s->sbs;
+}
+
+static int parse_grpc_header(const uint8_t *data) {
+  const uint8_t *p = data + 1;
+  int length = 0;
+  length |= ((uint8_t)*p++) << 24;
+  length |= ((uint8_t)*p++) << 16;
+  length |= ((uint8_t)*p++) << 8;
+  length |= ((uint8_t)*p++);
+  return length;
+}
+
+static void on_read_completed(cronet_bidirectional_stream *stream, char *data,
+                              int count) {
+  stream_obj *s = (stream_obj *)stream->annotation;
+  if (grpc_cronet_trace) {
+    gpr_log(GPR_DEBUG, "R: on_read_completed count=%d, total=%d, remaining=%d",
+            count, s->total_read_bytes, s->remaining_read_bytes);
+  }
+  if (count > 0) {
+    GPR_ASSERT(s->recv_message);
+    s->remaining_read_bytes -= count;
+    next_recv_step(s, ON_READ_COMPLETE);
+  } else {
+    s->read_closed = true;
+    next_recv_step(s, ON_READ_COMPLETE);
+  }
+}
+
+static void on_response_headers_received(
+    cronet_bidirectional_stream *stream,
+    const cronet_bidirectional_stream_header_array *headers,
+    const char *negotiated_protocol) {
+  if (grpc_cronet_trace) {
+    gpr_log(GPR_DEBUG, "R: on_response_headers_received");
+  }
+  stream_obj *s = (stream_obj *)stream->annotation;
+  enqueue_callbacks(s->callback_list[CB_RECV_INITIAL_METADATA]);
+  s->response_headers_received = true;
+  next_recv_step(s, ON_RESPONSE_HEADERS_RECEIVED);
+}
+
+static void on_request_headers_sent(cronet_bidirectional_stream *stream) {
+  if (grpc_cronet_trace) {
+    gpr_log(GPR_DEBUG, "W: on_request_headers_sent");
+  }
+  stream_obj *s = (stream_obj *)stream->annotation;
+  enqueue_callbacks(s->callback_list[CB_SEND_INITIAL_METADATA]);
+  s->cronet_send_state = CRONET_SEND_HEADER;
+  next_send_step(s);
+}
+
+// Callback function pointers (invoked by cronet in response to events)
+static cronet_bidirectional_stream_callback callbacks = {
+    on_request_headers_sent,
+    on_response_headers_received,
+    on_read_completed,
+    on_write_completed,
+    on_response_trailers_received,
+    on_succeeded,
+    on_failed,
+    on_canceled};
+
+static void invoke_closing_callback(stream_obj *s) {
+  grpc_chttp2_incoming_metadata_buffer_publish(&s->imb,
+                                               s->recv_trailing_metadata);
+  if (s->callback_list[CB_RECV_TRAILING_METADATA]) {
+    enqueue_callbacks(s->callback_list[CB_RECV_TRAILING_METADATA]);
+  }
+}
+
+static void set_recv_state(stream_obj *s, enum recv_state state) {
+  if (grpc_cronet_trace) {
+    gpr_log(GPR_DEBUG, "next_state = %s", recv_state_name[state]);
+  }
+  s->cronet_recv_state = state;
+}
+
+// This is invoked from perform_stream_op, and all on_xxxx callbacks.
+static void next_recv_step(stream_obj *s, enum e_caller caller) {
+  gpr_mu_lock(&s->recv_mu);
+  switch (s->cronet_recv_state) {
+    case CRONET_RECV_IDLE:
+      if (grpc_cronet_trace) {
+        gpr_log(GPR_DEBUG, "cronet_recv_state = CRONET_RECV_IDLE");
+      }
+      if (caller == PERFORM_STREAM_OP ||
+          caller == ON_RESPONSE_HEADERS_RECEIVED) {
+        if (s->read_closed && s->response_trailers_received) {
+          invoke_closing_callback(s);
+          set_recv_state(s, CRONET_RECV_CLOSED);
+        } else if (s->response_headers_received == true &&
+                   s->read_requested == true) {
+          set_recv_state(s, CRONET_RECV_READ_LENGTH);
+          s->total_read_bytes = s->remaining_read_bytes =
+              GRPC_HEADER_SIZE_IN_BYTES;
+          GPR_ASSERT(s->read_buffer);
+          if (grpc_cronet_trace) {
+            gpr_log(GPR_DEBUG, "R: cronet_bidirectional_stream_read()");
+          }
+          cronet_bidirectional_stream_read(s->cbs, s->read_buffer,
+                                           s->remaining_read_bytes);
+        }
+      }
+      break;
+    case CRONET_RECV_READ_LENGTH:
+      if (grpc_cronet_trace) {
+        gpr_log(GPR_DEBUG, "cronet_recv_state = CRONET_RECV_READ_LENGTH");
+      }
+      if (caller == ON_READ_COMPLETE) {
+        if (s->read_closed) {
+          invoke_closing_callback(s);
+          enqueue_callbacks(s->callback_list[CB_RECV_MESSAGE]);
+          set_recv_state(s, CRONET_RECV_CLOSED);
+        } else {
+          GPR_ASSERT(s->remaining_read_bytes == 0);
+          set_recv_state(s, CRONET_RECV_READ_DATA);
+          s->total_read_bytes = s->remaining_read_bytes =
+              parse_grpc_header((const uint8_t *)s->read_buffer);
+          s->read_buffer =
+              gpr_realloc(s->read_buffer, (uint32_t)s->remaining_read_bytes);
+          GPR_ASSERT(s->read_buffer);
+          if (grpc_cronet_trace) {
+            gpr_log(GPR_DEBUG, "R: cronet_bidirectional_stream_read()");
+          }
+          cronet_bidirectional_stream_read(s->cbs, (char *)s->read_buffer,
+                                           s->remaining_read_bytes);
+        }
+      }
+      break;
+    case CRONET_RECV_READ_DATA:
+      if (grpc_cronet_trace) {
+        gpr_log(GPR_DEBUG, "cronet_recv_state = CRONET_RECV_READ_DATA");
+      }
+      if (caller == ON_READ_COMPLETE) {
+        if (s->remaining_read_bytes > 0) {
+          int offset = s->total_read_bytes - s->remaining_read_bytes;
+          GPR_ASSERT(s->read_buffer);
+          if (grpc_cronet_trace) {
+            gpr_log(GPR_DEBUG, "R: cronet_bidirectional_stream_read()");
+          }
+          cronet_bidirectional_stream_read(
+              s->cbs, (char *)s->read_buffer + offset, s->remaining_read_bytes);
+        } else {
+          gpr_slice_buffer_init(&s->read_slice_buffer);
+          uint8_t *p = (uint8_t *)s->read_buffer;
+          process_recv_message(s, p);
+          set_recv_state(s, CRONET_RECV_IDLE);
+          enqueue_callbacks(s->callback_list[CB_RECV_MESSAGE]);
+        }
+      }
+      break;
+    case CRONET_RECV_CLOSED:
+      break;
+    default:
+      GPR_ASSERT(0);  // Should not reach here
+      break;
+  }
+  gpr_mu_unlock(&s->recv_mu);
+}
+
+// This function takes the data from s->write_slice_buffer and assembles into
+// a contiguous byte stream with 5 byte gRPC header prepended.
+static void create_grpc_frame(stream_obj *s) {
+  gpr_slice slice = gpr_slice_buffer_take_first(&s->write_slice_buffer);
+  uint8_t *raw_data = GPR_SLICE_START_PTR(slice);
+  size_t length = GPR_SLICE_LENGTH(slice);
+  s->write_buffer_size = length + GRPC_HEADER_SIZE_IN_BYTES;
+  s->write_buffer = gpr_realloc(s->write_buffer, s->write_buffer_size);
+  uint8_t *p = (uint8_t *)s->write_buffer;
+  // Append 5 byte header
+  *p++ = 0;
+  *p++ = (uint8_t)(length >> 24);
+  *p++ = (uint8_t)(length >> 16);
+  *p++ = (uint8_t)(length >> 8);
+  *p++ = (uint8_t)(length);
+  // append actual data
+  memcpy(p, raw_data, length);
+}
+
+static void do_write(stream_obj *s) {
+  gpr_slice_buffer *sb = &s->write_slice_buffer;
+  GPR_ASSERT(sb->count <= 1);
+  if (sb->count > 0) {
+    create_grpc_frame(s);
+    if (grpc_cronet_trace) {
+      gpr_log(GPR_DEBUG, "W: cronet_bidirectional_stream_write");
+    }
+    cronet_bidirectional_stream_write(s->cbs, s->write_buffer,
+                                      (int)s->write_buffer_size, false);
+  }
+}
+
+//
+static void next_send_step(stream_obj *s) {
+  switch (s->cronet_send_state) {
+    case CRONET_SEND_IDLE:
+      GPR_ASSERT(
+          s->cbs);  // cronet_bidirectional_stream is not initialized yet.
+      s->cronet_send_state = CRONET_REQ_STARTED;
+      if (grpc_cronet_trace) {
+        gpr_log(GPR_DEBUG, "cronet_bidirectional_stream_start to %s", s->url);
+      }
+      cronet_bidirectional_stream_start(s->cbs, s->url, 0, "POST",
+                                        &s->header_array, false);
+      // we no longer need the memory that was allocated earlier.
+      gpr_free(s->header_array.headers);
+      break;
+    case CRONET_SEND_HEADER:
+      do_write(s);
+      s->cronet_send_state = CRONET_WRITE;
+      break;
+    case CRONET_WRITE_COMPLETED:
+      do_write(s);
+      break;
+    default:
+      GPR_ASSERT(0);
+      break;
+  }
+}
+
+static void convert_metadata_to_cronet_headers(grpc_linked_mdelem *head,
+                                               const char *host,
+                                               stream_obj *s) {
+  grpc_linked_mdelem *curr = head;
+  // Walk the linked list and get number of header fields
+  uint32_t num_headers_available = 0;
+  while (curr != NULL) {
+    curr = curr->next;
+    num_headers_available++;
+  }
+  // Allocate enough memory
+  s->headers = (cronet_bidirectional_stream_header *)gpr_malloc(
+      sizeof(cronet_bidirectional_stream_header) * num_headers_available);
+
+  // Walk the linked list again, this time copying the header fields.
+  // s->num_headers
+  // can be less than num_headers_available, as some headers are not used for
+  // cronet
+  curr = head;
+  s->num_headers = 0;
+  while (s->num_headers < num_headers_available) {
+    grpc_mdelem *mdelem = curr->md;
+    curr = curr->next;
+    const char *key = grpc_mdstr_as_c_string(mdelem->key);
+    const char *value = grpc_mdstr_as_c_string(mdelem->value);
+    if (strcmp(key, ":scheme") == 0 || strcmp(key, ":method") == 0 ||
+        strcmp(key, ":authority") == 0) {
+      // Cronet populates these fields on its own.
+      continue;
+    }
+    if (strcmp(key, ":path") == 0) {
+      // Create URL by appending :path value to the hostname
+      gpr_asprintf(&s->url, "https://%s%s", host, value);
+      if (grpc_cronet_trace) {
+        gpr_log(GPR_DEBUG, "extracted URL = %s", s->url);
+      }
+      continue;
+    }
+    s->headers[s->num_headers].key = key;
+    s->headers[s->num_headers].value = value;
+    s->num_headers++;
+    if (curr == NULL) {
+      break;
+    }
+  }
+}
+
+static void perform_stream_op(grpc_exec_ctx *exec_ctx, grpc_transport *gt,
+                              grpc_stream *gs, grpc_transport_stream_op *op) {
+  grpc_cronet_transport *ct = (grpc_cronet_transport *)gt;
+  GPR_ASSERT(ct->engine);
+  stream_obj *s = (stream_obj *)gs;
+  if (op->recv_trailing_metadata) {
+    if (grpc_cronet_trace) {
+      gpr_log(GPR_DEBUG,
+              "perform_stream_op - recv_trailing_metadata: on_complete=%p",
+              op->on_complete);
+    }
+    s->recv_trailing_metadata = op->recv_trailing_metadata;
+    GPR_ASSERT(!s->callback_list[CB_RECV_TRAILING_METADATA][0]);
+    s->callback_list[CB_RECV_TRAILING_METADATA][0] = op->on_complete;
+  }
+  if (op->recv_message) {
+    if (grpc_cronet_trace) {
+      gpr_log(GPR_DEBUG, "perform_stream_op - recv_message: on_complete=%p",
+              op->on_complete);
+    }
+    s->recv_message = (grpc_byte_buffer **)op->recv_message;
+    GPR_ASSERT(!s->callback_list[CB_RECV_MESSAGE][0]);
+    GPR_ASSERT(!s->callback_list[CB_RECV_MESSAGE][1]);
+    s->callback_list[CB_RECV_MESSAGE][0] = op->recv_message_ready;
+    s->callback_list[CB_RECV_MESSAGE][1] = op->on_complete;
+    s->read_requested = true;
+    next_recv_step(s, PERFORM_STREAM_OP);
+  }
+  if (op->recv_initial_metadata) {
+    if (grpc_cronet_trace) {
+      gpr_log(GPR_DEBUG, "perform_stream_op - recv_initial_metadata:=%p",
+              op->on_complete);
+    }
+    s->recv_initial_metadata = op->recv_initial_metadata;
+    GPR_ASSERT(!s->callback_list[CB_RECV_INITIAL_METADATA][0]);
+    GPR_ASSERT(!s->callback_list[CB_RECV_INITIAL_METADATA][1]);
+    s->callback_list[CB_RECV_INITIAL_METADATA][0] =
+        op->recv_initial_metadata_ready;
+    s->callback_list[CB_RECV_INITIAL_METADATA][1] = op->on_complete;
+  }
+  if (op->send_initial_metadata) {
+    if (grpc_cronet_trace) {
+      gpr_log(GPR_DEBUG,
+              "perform_stream_op - send_initial_metadata: on_complete=%p",
+              op->on_complete);
+    }
+    s->num_headers = 0;
+    convert_metadata_to_cronet_headers(op->send_initial_metadata->list.head,
+                                       ct->host, s);
+    s->header_array.count = s->num_headers;
+    s->header_array.capacity = s->num_headers;
+    s->header_array.headers = s->headers;
+    GPR_ASSERT(!s->callback_list[CB_SEND_INITIAL_METADATA][0]);
+    s->callback_list[CB_SEND_INITIAL_METADATA][0] = op->on_complete;
+  }
+  if (op->send_message) {
+    if (grpc_cronet_trace) {
+      gpr_log(GPR_DEBUG, "perform_stream_op - send_message: on_complete=%p",
+              op->on_complete);
+    }
+    grpc_byte_stream_next(exec_ctx, op->send_message, &s->slice,
+                          op->send_message->length, NULL);
+    // Check that compression flag is not ON. We don't support compression yet.
+    // TODO (makdharma): add compression support
+    GPR_ASSERT(op->send_message->flags == 0);
+    gpr_slice_buffer_add(&s->write_slice_buffer, s->slice);
+    if (s->cbs == NULL) {
+      if (grpc_cronet_trace) {
+        gpr_log(GPR_DEBUG, "cronet_bidirectional_stream_create");
+      }
+      s->cbs = cronet_bidirectional_stream_create(ct->engine, s, &callbacks);
+      GPR_ASSERT(s->cbs);
+      s->read_closed = false;
+      s->response_trailers_received = false;
+      s->response_headers_received = false;
+      s->cronet_send_state = CRONET_SEND_IDLE;
+      s->cronet_recv_state = CRONET_RECV_IDLE;
+    }
+    GPR_ASSERT(!s->callback_list[CB_SEND_MESSAGE][0]);
+    s->callback_list[CB_SEND_MESSAGE][0] = op->on_complete;
+    next_send_step(s);
+  }
+  if (op->send_trailing_metadata) {
+    if (grpc_cronet_trace) {
+      gpr_log(GPR_DEBUG,
+              "perform_stream_op - send_trailing_metadata: on_complete=%p",
+              op->on_complete);
+    }
+    GPR_ASSERT(!s->callback_list[CB_SEND_TRAILING_METADATA][0]);
+    s->callback_list[CB_SEND_TRAILING_METADATA][0] = op->on_complete;
+    if (s->cbs) {
+      // Send an "empty" write to the far end to signal that we're done.
+      // This will induce the server to send down trailers.
+      if (grpc_cronet_trace) {
+        gpr_log(GPR_DEBUG, "W: cronet_bidirectional_stream_write");
+      }
+      cronet_bidirectional_stream_write(s->cbs, "abc", 0, true);
+    } else {
+      // We never created a stream. This was probably an empty request.
+      invoke_closing_callback(s);
+    }
+  }
+}
+
+static int init_stream(grpc_exec_ctx *exec_ctx, grpc_transport *gt,
+                       grpc_stream *gs, grpc_stream_refcount *refcount,
+                       const void *server_data) {
+  stream_obj *s = (stream_obj *)gs;
+  memset(s->callback_list, 0, sizeof(s->callback_list));
+  s->cbs = NULL;
+  gpr_mu_init(&s->recv_mu);
+  s->read_buffer = gpr_malloc(GRPC_HEADER_SIZE_IN_BYTES);
+  s->write_buffer = gpr_malloc(GRPC_HEADER_SIZE_IN_BYTES);
+  gpr_slice_buffer_init(&s->write_slice_buffer);
+  if (grpc_cronet_trace) {
+    gpr_log(GPR_DEBUG, "cronet_transport - init_stream");
+  }
+  return 0;
+}
+
+static void destroy_stream(grpc_exec_ctx *exec_ctx, grpc_transport *gt,
+                           grpc_stream *gs, void *and_free_memory) {
+  if (grpc_cronet_trace) {
+    gpr_log(GPR_DEBUG, "Destroy stream");
+  }
+  stream_obj *s = (stream_obj *)gs;
+  s->cbs = NULL;
+  gpr_free(s->read_buffer);
+  gpr_free(s->write_buffer);
+  gpr_free(s->url);
+  gpr_mu_destroy(&s->recv_mu);
+  if (and_free_memory) {
+    gpr_free(and_free_memory);
+  }
+}
+
+static void destroy_transport(grpc_exec_ctx *exec_ctx, grpc_transport *gt) {
+  grpc_cronet_transport *ct = (grpc_cronet_transport *)gt;
+  gpr_free(ct->host);
+  if (grpc_cronet_trace) {
+    gpr_log(GPR_DEBUG, "Destroy transport");
+  }
+}
+
+const grpc_transport_vtable grpc_cronet_vtable = {
+    sizeof(stream_obj),     "cronet_http",     init_stream,
+    set_pollset_do_nothing, perform_stream_op, NULL,
+    destroy_stream,         destroy_transport, NULL};
diff --git a/src/core/lib/channel/channel_args.h b/src/core/lib/channel/channel_args.h
index 0a51780..23c7b7b 100644
--- a/src/core/lib/channel/channel_args.h
+++ b/src/core/lib/channel/channel_args.h
@@ -56,10 +56,6 @@
 /** Destroy arguments created by \a grpc_channel_args_copy */
 void grpc_channel_args_destroy(grpc_channel_args *a);
 
-/** Reads census_enabled settings from channel args. Returns 1 if census_enabled
- * is specified in channel args, otherwise returns 0. */
-int grpc_channel_args_is_census_enabled(const grpc_channel_args *a);
-
 /** Returns the compression algorithm set in \a a. */
 grpc_compression_algorithm grpc_channel_args_get_compression_algorithm(
     const grpc_channel_args *a);
diff --git a/src/cpp/common/core_codegen.cc b/src/cpp/common/core_codegen.cc
index 33a8f75..8e8d42e 100644
--- a/src/cpp/common/core_codegen.cc
+++ b/src/cpp/common/core_codegen.cc
@@ -48,124 +48,6 @@
 
 #include "src/core/lib/profiling/timers.h"
 
-namespace {
-
-const int kGrpcBufferWriterMaxBufferLength = 8192;
-
-class GrpcBufferWriter GRPC_FINAL
-    : public ::grpc::protobuf::io::ZeroCopyOutputStream {
- public:
-  explicit GrpcBufferWriter(grpc_byte_buffer** bp, int block_size)
-      : block_size_(block_size), byte_count_(0), have_backup_(false) {
-    *bp = grpc_raw_byte_buffer_create(NULL, 0);
-    slice_buffer_ = &(*bp)->data.raw.slice_buffer;
-  }
-
-  ~GrpcBufferWriter() GRPC_OVERRIDE {
-    if (have_backup_) {
-      gpr_slice_unref(backup_slice_);
-    }
-  }
-
-  bool Next(void** data, int* size) GRPC_OVERRIDE {
-    if (have_backup_) {
-      slice_ = backup_slice_;
-      have_backup_ = false;
-    } else {
-      slice_ = gpr_slice_malloc(block_size_);
-    }
-    *data = GPR_SLICE_START_PTR(slice_);
-    // On win x64, int is only 32bit
-    GPR_ASSERT(GPR_SLICE_LENGTH(slice_) <= INT_MAX);
-    byte_count_ += * size = (int)GPR_SLICE_LENGTH(slice_);
-    gpr_slice_buffer_add(slice_buffer_, slice_);
-    return true;
-  }
-
-  void BackUp(int count) GRPC_OVERRIDE {
-    gpr_slice_buffer_pop(slice_buffer_);
-    if (count == block_size_) {
-      backup_slice_ = slice_;
-    } else {
-      backup_slice_ =
-          gpr_slice_split_tail(&slice_, GPR_SLICE_LENGTH(slice_) - count);
-      gpr_slice_buffer_add(slice_buffer_, slice_);
-    }
-    have_backup_ = true;
-    byte_count_ -= count;
-  }
-
-  grpc::protobuf::int64 ByteCount() const GRPC_OVERRIDE { return byte_count_; }
-
- private:
-  const int block_size_;
-  int64_t byte_count_;
-  gpr_slice_buffer* slice_buffer_;
-  bool have_backup_;
-  gpr_slice backup_slice_;
-  gpr_slice slice_;
-};
-
-class GrpcBufferReader GRPC_FINAL
-    : public ::grpc::protobuf::io::ZeroCopyInputStream {
- public:
-  explicit GrpcBufferReader(grpc_byte_buffer* buffer)
-      : byte_count_(0), backup_count_(0) {
-    grpc_byte_buffer_reader_init(&reader_, buffer);
-  }
-  ~GrpcBufferReader() GRPC_OVERRIDE {
-    grpc_byte_buffer_reader_destroy(&reader_);
-  }
-
-  bool Next(const void** data, int* size) GRPC_OVERRIDE {
-    if (backup_count_ > 0) {
-      *data = GPR_SLICE_START_PTR(slice_) + GPR_SLICE_LENGTH(slice_) -
-              backup_count_;
-      GPR_ASSERT(backup_count_ <= INT_MAX);
-      *size = (int)backup_count_;
-      backup_count_ = 0;
-      return true;
-    }
-    if (!grpc_byte_buffer_reader_next(&reader_, &slice_)) {
-      return false;
-    }
-    gpr_slice_unref(slice_);
-    *data = GPR_SLICE_START_PTR(slice_);
-    // On win x64, int is only 32bit
-    GPR_ASSERT(GPR_SLICE_LENGTH(slice_) <= INT_MAX);
-    byte_count_ += * size = (int)GPR_SLICE_LENGTH(slice_);
-    return true;
-  }
-
-  void BackUp(int count) GRPC_OVERRIDE { backup_count_ = count; }
-
-  bool Skip(int count) GRPC_OVERRIDE {
-    const void* data;
-    int size;
-    while (Next(&data, &size)) {
-      if (size >= count) {
-        BackUp(size - count);
-        return true;
-      }
-      // size < count;
-      count -= size;
-    }
-    // error or we have too large count;
-    return false;
-  }
-
-  grpc::protobuf::int64 ByteCount() const GRPC_OVERRIDE {
-    return byte_count_ - backup_count_;
-  }
-
- private:
-  int64_t byte_count_;
-  int64_t backup_count_;
-  grpc_byte_buffer_reader reader_;
-  gpr_slice slice_;
-};
-}  // namespace
-
 namespace grpc {
 
 grpc_completion_queue* CoreCodegen::grpc_completion_queue_create(
@@ -192,6 +74,44 @@
   ::grpc_byte_buffer_destroy(bb);
 }
 
+void CoreCodegen::grpc_byte_buffer_reader_init(grpc_byte_buffer_reader* reader,
+                                               grpc_byte_buffer* buffer) {
+  ::grpc_byte_buffer_reader_init(reader, buffer);
+}
+
+void CoreCodegen::grpc_byte_buffer_reader_destroy(
+    grpc_byte_buffer_reader* reader) {
+  ::grpc_byte_buffer_reader_destroy(reader);
+}
+
+int CoreCodegen::grpc_byte_buffer_reader_next(grpc_byte_buffer_reader* reader,
+                                              gpr_slice* slice) {
+  return ::grpc_byte_buffer_reader_next(reader, slice);
+}
+
+grpc_byte_buffer* CoreCodegen::grpc_raw_byte_buffer_create(gpr_slice* slice,
+                                                           size_t nslices) {
+  return ::grpc_raw_byte_buffer_create(slice, nslices);
+}
+
+gpr_slice CoreCodegen::gpr_slice_malloc(size_t length) {
+  return ::gpr_slice_malloc(length);
+}
+
+void CoreCodegen::gpr_slice_unref(gpr_slice slice) { ::gpr_slice_unref(slice); }
+
+gpr_slice CoreCodegen::gpr_slice_split_tail(gpr_slice* s, size_t split) {
+  return ::gpr_slice_split_tail(s, split);
+}
+
+void CoreCodegen::gpr_slice_buffer_add(gpr_slice_buffer* sb, gpr_slice slice) {
+  ::gpr_slice_buffer_add(sb, slice);
+}
+
+void CoreCodegen::gpr_slice_buffer_pop(gpr_slice_buffer* sb) {
+  ::gpr_slice_buffer_pop(sb);
+}
+
 void CoreCodegen::grpc_metadata_array_init(grpc_metadata_array* array) {
   ::grpc_metadata_array_init(array);
 }
@@ -200,6 +120,10 @@
   ::grpc_metadata_array_destroy(array);
 }
 
+const Status& CoreCodegen::ok() { return grpc::Status::OK; }
+
+const Status& CoreCodegen::cancelled() { return grpc::Status::CANCELLED; }
+
 gpr_timespec CoreCodegen::gpr_inf_future(gpr_clock_type type) {
   return ::gpr_inf_future(type);
 }
@@ -209,48 +133,4 @@
   abort();
 }
 
-Status CoreCodegen::SerializeProto(const grpc::protobuf::Message& msg,
-                                   grpc_byte_buffer** bp) {
-  GPR_TIMER_SCOPE("SerializeProto", 0);
-  int byte_size = msg.ByteSize();
-  if (byte_size <= kGrpcBufferWriterMaxBufferLength) {
-    gpr_slice slice = gpr_slice_malloc(byte_size);
-    GPR_ASSERT(GPR_SLICE_END_PTR(slice) ==
-               msg.SerializeWithCachedSizesToArray(GPR_SLICE_START_PTR(slice)));
-    *bp = grpc_raw_byte_buffer_create(&slice, 1);
-    gpr_slice_unref(slice);
-    return Status::OK;
-  } else {
-    GrpcBufferWriter writer(bp, kGrpcBufferWriterMaxBufferLength);
-    return msg.SerializeToZeroCopyStream(&writer)
-               ? Status::OK
-               : Status(StatusCode::INTERNAL, "Failed to serialize message");
-  }
-}
-
-Status CoreCodegen::DeserializeProto(grpc_byte_buffer* buffer,
-                                     grpc::protobuf::Message* msg,
-                                     int max_message_size) {
-  GPR_TIMER_SCOPE("DeserializeProto", 0);
-  if (buffer == nullptr) {
-    return Status(StatusCode::INTERNAL, "No payload");
-  }
-  Status result = Status::OK;
-  {
-    GrpcBufferReader reader(buffer);
-    ::grpc::protobuf::io::CodedInputStream decoder(&reader);
-    if (max_message_size > 0) {
-      decoder.SetTotalBytesLimit(max_message_size, max_message_size);
-    }
-    if (!msg->ParseFromCodedStream(&decoder)) {
-      result = Status(StatusCode::INTERNAL, msg->InitializationErrorString());
-    }
-    if (!decoder.ConsumedEntireMessage()) {
-      result = Status(StatusCode::INTERNAL, "Did not read entire message");
-    }
-  }
-  grpc_byte_buffer_destroy(buffer);
-  return result;
-}
-
 }  // namespace grpc
diff --git a/src/cpp/common/core_codegen.h b/src/cpp/common/core_codegen.h
index e15cb4c..656b11e 100644
--- a/src/cpp/common/core_codegen.h
+++ b/src/cpp/common/core_codegen.h
@@ -42,13 +42,6 @@
 /// Implementation of the core codegen interface.
 class CoreCodegen : public CoreCodegenInterface {
  private:
-  Status SerializeProto(const grpc::protobuf::Message& msg,
-                        grpc_byte_buffer** bp) override;
-
-  Status DeserializeProto(grpc_byte_buffer* buffer,
-                          grpc::protobuf::Message* msg,
-                          int max_message_size) override;
-
   grpc_completion_queue* grpc_completion_queue_create(void* reserved) override;
   void grpc_completion_queue_destroy(grpc_completion_queue* cq) override;
   grpc_event grpc_completion_queue_pluck(grpc_completion_queue* cq, void* tag,
@@ -60,11 +53,30 @@
 
   void grpc_byte_buffer_destroy(grpc_byte_buffer* bb) override;
 
+  void grpc_byte_buffer_reader_init(grpc_byte_buffer_reader* reader,
+                                    grpc_byte_buffer* buffer) override;
+  void grpc_byte_buffer_reader_destroy(
+      grpc_byte_buffer_reader* reader) override;
+  int grpc_byte_buffer_reader_next(grpc_byte_buffer_reader* reader,
+                                   gpr_slice* slice) override;
+
+  grpc_byte_buffer* grpc_raw_byte_buffer_create(gpr_slice* slice,
+                                                size_t nslices) override;
+
+  gpr_slice gpr_slice_malloc(size_t length) override;
+  void gpr_slice_unref(gpr_slice slice) override;
+  gpr_slice gpr_slice_split_tail(gpr_slice* s, size_t split) override;
+  void gpr_slice_buffer_add(gpr_slice_buffer* sb, gpr_slice slice) override;
+  void gpr_slice_buffer_pop(gpr_slice_buffer* sb) override;
+
   void grpc_metadata_array_init(grpc_metadata_array* array) override;
   void grpc_metadata_array_destroy(grpc_metadata_array* array) override;
 
   gpr_timespec gpr_inf_future(gpr_clock_type type) override;
 
+  virtual const Status& ok() override;
+  virtual const Status& cancelled() override;
+
   void assert_fail(const char* failed_assertion) override;
 };
 
diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
index 6c13a4f..d92addb 100644
--- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
@@ -167,6 +167,37 @@
         }
 
         [Test]
+        public async Task ServerStreamingCall_EndOfStreamIsIdempotent()
+        {
+            helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
+            {
+            });
+
+            var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "");
+
+            Assert.IsFalse(await call.ResponseStream.MoveNext());
+            Assert.IsFalse(await call.ResponseStream.MoveNext());
+        }
+
+        [Test]
+        public async Task ServerStreamingCall_ErrorCanBeAwaitedTwice()
+        {
+            helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
+            {
+                context.Status = new Status(StatusCode.InvalidArgument, "");
+            });
+
+            var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "");
+
+            var ex = Assert.ThrowsAsync<RpcException>(async () => await call.ResponseStream.MoveNext());
+            Assert.AreEqual(StatusCode.InvalidArgument, ex.Status.StatusCode);
+
+            // attempting MoveNext again should result in throwing the same exception.
+            var ex2 = Assert.ThrowsAsync<RpcException>(async () => await call.ResponseStream.MoveNext());
+            Assert.AreEqual(StatusCode.InvalidArgument, ex2.Status.StatusCode);
+        }
+
+        [Test]
         public async Task DuplexStreamingCall()
         {
             helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
@@ -209,6 +240,38 @@
         }
 
         [Test]
+        public async Task ClientStreamingCall_ServerSideReadAfterCancelNotificationReturnsNull()
+        {
+            var handlerStartedBarrier = new TaskCompletionSource<object>();
+            var cancelNotificationReceivedBarrier = new TaskCompletionSource<object>();
+            var successTcs = new TaskCompletionSource<string>();
+
+            helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+            {
+                handlerStartedBarrier.SetResult(null);
+
+                // wait for cancellation to be delivered.
+                context.CancellationToken.Register(() => cancelNotificationReceivedBarrier.SetResult(null));
+                await cancelNotificationReceivedBarrier.Task;
+
+                var moveNextResult = await requestStream.MoveNext();
+                successTcs.SetResult(!moveNextResult ? "SUCCESS" : "FAIL");
+                return "";
+            });
+
+            var cts = new CancellationTokenSource();
+            var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token)));
+
+            await handlerStartedBarrier.Task;
+            cts.Cancel();
+
+            var ex = Assert.ThrowsAsync<RpcException>(async () => await call.ResponseAsync);
+            Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
+
+            Assert.AreEqual("SUCCESS", await successTcs.Task);
+        }
+
+        [Test]
         public async Task AsyncUnaryCall_EchoMetadata()
         {
             helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
index 0cd059c..47131fc 100644
--- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
+++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
@@ -84,6 +84,8 @@
     <Compile Include="SanityTest.cs" />
     <Compile Include="HalfcloseTest.cs" />
     <Compile Include="NUnitMain.cs" />
+    <Compile Include="Internal\FakeNativeCall.cs" />
+    <Compile Include="Internal\AsyncCallServerTest.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>
diff --git a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs
new file mode 100644
index 0000000..0e20476
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs
@@ -0,0 +1,191 @@
+#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.Runtime.InteropServices;
+using System.Threading.Tasks;
+
+using Grpc.Core.Internal;
+using NUnit.Framework;
+
+namespace Grpc.Core.Internal.Tests
+{
+    /// <summary>
+    /// Uses fake native call to test interaction of <c>AsyncCallServer</c> wrapping code with C core in different situations.
+    /// </summary>
+    public class AsyncCallServerTest
+    {
+        Server server;
+        FakeNativeCall fakeCall;
+        AsyncCallServer<string, string> asyncCallServer;
+
+        [SetUp]
+        public void Init()
+        {
+            var environment = GrpcEnvironment.AddRef();
+
+            // Create a fake server just so we have an instance to refer to.
+            // The server won't actually be used at all.
+            server = new Server()
+            {
+                Ports = { { "localhost", 0, ServerCredentials.Insecure } }
+            };
+            server.Start();
+
+            fakeCall = new FakeNativeCall();
+            asyncCallServer = new AsyncCallServer<string, string>(
+                Marshallers.StringMarshaller.Serializer, Marshallers.StringMarshaller.Deserializer,
+                environment,
+                server);
+            asyncCallServer.InitializeForTesting(fakeCall);
+        }
+
+        [TearDown]
+        public void Cleanup()
+        {
+            server.ShutdownAsync().Wait();
+            GrpcEnvironment.Release();
+        }
+
+        [Test]
+        public void CancelNotificationAfterStartDisposes()
+        {
+            var finishedTask = asyncCallServer.ServerSideCallAsync();
+            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+            AssertFinished(asyncCallServer, fakeCall, finishedTask);
+        }
+
+        [Test]
+        public void CancelNotificationAfterStartDisposesAfterPendingReadFinishes()
+        {
+            var finishedTask = asyncCallServer.ServerSideCallAsync();
+            var requestStream = new ServerRequestStream<string, string>(asyncCallServer);
+
+            var moveNextTask = requestStream.MoveNext();
+
+            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+            fakeCall.ReceivedMessageHandler(true, null);
+            Assert.IsFalse(moveNextTask.Result);
+
+            AssertFinished(asyncCallServer, fakeCall, finishedTask);
+        }
+
+        [Test]
+        public void ReadAfterCancelNotificationCanSucceed()
+        {
+            var finishedTask = asyncCallServer.ServerSideCallAsync();
+            var requestStream = new ServerRequestStream<string, string>(asyncCallServer);
+
+            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+
+            // Check that starting a read after cancel notification has been processed is legal.
+            var moveNextTask = requestStream.MoveNext();
+            Assert.IsFalse(moveNextTask.Result);
+
+            AssertFinished(asyncCallServer, fakeCall, finishedTask);
+        }
+
+        [Test]
+        public void ReadCompletionFailureClosesRequestStream()
+        {
+            var finishedTask = asyncCallServer.ServerSideCallAsync();
+            var requestStream = new ServerRequestStream<string, string>(asyncCallServer);
+
+            // if a read completion's success==false, the request stream will silently finish
+            // and we rely on C core cancelling the call.
+            var moveNextTask = requestStream.MoveNext();
+            fakeCall.ReceivedMessageHandler(false, null);
+            Assert.IsFalse(moveNextTask.Result);
+
+            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+            AssertFinished(asyncCallServer, fakeCall, finishedTask);
+        }
+
+        [Test]
+        public void WriteAfterCancelNotificationFails()
+        {
+            var finishedTask = asyncCallServer.ServerSideCallAsync();
+            var requestStream = new ServerRequestStream<string, string>(asyncCallServer);
+            var responseStream = new ServerResponseStream<string, string>(asyncCallServer);
+
+            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+
+            // TODO(jtattermusch): should we throw a different exception type instead?
+            Assert.Throws(typeof(InvalidOperationException), () => responseStream.WriteAsync("request1"));
+            AssertFinished(asyncCallServer, fakeCall, finishedTask);
+        }
+
+        [Test]
+        public void WriteCompletionFailureThrows()
+        {
+            var finishedTask = asyncCallServer.ServerSideCallAsync();
+            var responseStream = new ServerResponseStream<string, string>(asyncCallServer);
+
+            var writeTask = responseStream.WriteAsync("request1");
+            fakeCall.SendCompletionHandler(false);
+            // TODO(jtattermusch): should we throw a different exception type instead?
+            Assert.ThrowsAsync(typeof(InvalidOperationException), async () => await writeTask);
+
+            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+            AssertFinished(asyncCallServer, fakeCall, finishedTask);
+        }
+
+        [Test]
+        public void WriteAndWriteStatusCanRunConcurrently()
+        {
+            var finishedTask = asyncCallServer.ServerSideCallAsync();
+            var responseStream = new ServerResponseStream<string, string>(asyncCallServer);
+
+            var writeTask = responseStream.WriteAsync("request1");
+            var writeStatusTask = asyncCallServer.SendStatusFromServerAsync(Status.DefaultSuccess, new Metadata(), null);
+
+            fakeCall.SendCompletionHandler(true);
+            fakeCall.SendStatusFromServerHandler(true);
+
+            Assert.DoesNotThrowAsync(async () => await writeTask);
+            Assert.DoesNotThrowAsync(async () => await writeStatusTask);
+
+            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+
+            AssertFinished(asyncCallServer, fakeCall, finishedTask);
+        }
+
+        static void AssertFinished(AsyncCallServer<string, string> asyncCallServer, FakeNativeCall fakeCall, Task finishedTask)
+        {
+            Assert.IsTrue(fakeCall.IsDisposed);
+            Assert.IsTrue(finishedTask.IsCompleted);
+            Assert.DoesNotThrow(() => finishedTask.Wait());
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
index 60530d3..abe9d4a 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
@@ -32,6 +32,7 @@
 #endregion
 
 using System;
+using System.Collections.Generic;
 using System.Runtime.InteropServices;
 using System.Threading.Tasks;
 
@@ -40,6 +41,9 @@
 
 namespace Grpc.Core.Internal.Tests
 {
+    /// <summary>
+    /// Uses fake native call to test interaction of <c>AsyncCall</c> wrapping code with C core in different situations.
+    /// </summary>
     public class AsyncCallTest
     {
         Channel channel;
@@ -64,159 +68,421 @@
         }
 
         [Test]
-        public void AsyncUnary_CompletionSuccess()
+        public void AsyncUnary_CanBeStartedOnlyOnce()
         {
-            var resultTask = asyncCall.UnaryCallAsync("abc");
-            fakeCall.UnaryResponseClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()), new byte[] { 1, 2, 3 }, new Metadata());
-            Assert.IsTrue(resultTask.IsCompleted);
-            Assert.IsTrue(fakeCall.IsDisposed);
-            Assert.AreEqual(Status.DefaultSuccess, asyncCall.GetStatus());
+            asyncCall.UnaryCallAsync("request1");
+            Assert.Throws(typeof(InvalidOperationException),
+                () => asyncCall.UnaryCallAsync("abc"));
         }
 
         [Test]
-        public void AsyncUnary_CompletionFailure()
+        public void AsyncUnary_StreamingOperationsNotAllowed()
         {
-            var resultTask = asyncCall.UnaryCallAsync("abc");
-            fakeCall.UnaryResponseClientHandler(false, new ClientSideStatus(new Status(StatusCode.Internal, ""), null), new byte[] { 1, 2, 3 }, new Metadata());
+            asyncCall.UnaryCallAsync("request1");
+            Assert.ThrowsAsync(typeof(InvalidOperationException),
+                async () => await asyncCall.ReadMessageAsync());
+            Assert.Throws(typeof(InvalidOperationException),
+                () => asyncCall.StartSendMessage("abc", new WriteFlags(), (x,y) => {}));
+        }
 
+        [Test]
+        public void AsyncUnary_Success()
+        {
+            var resultTask = asyncCall.UnaryCallAsync("request1");
+            fakeCall.UnaryResponseClientHandler(true,
+                new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
+                CreateResponsePayload(),
+                new Metadata());
+
+            AssertUnaryResponseSuccess(asyncCall, fakeCall, resultTask);
+        }
+
+        [Test]
+        public void AsyncUnary_NonSuccessStatusCode()
+        {
+            var resultTask = asyncCall.UnaryCallAsync("request1");
+            fakeCall.UnaryResponseClientHandler(true,
+                CreateClientSideStatus(StatusCode.InvalidArgument),
+                CreateResponsePayload(),
+                new Metadata());
+
+            AssertUnaryResponseError(asyncCall, fakeCall, resultTask, StatusCode.InvalidArgument);
+        }
+
+        [Test]
+        public void AsyncUnary_NullResponsePayload()
+        {
+            var resultTask = asyncCall.UnaryCallAsync("request1");
+            fakeCall.UnaryResponseClientHandler(true,
+                new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
+                null,
+                new Metadata());
+
+            // failure to deserialize will result in InvalidArgument status.
+            AssertUnaryResponseError(asyncCall, fakeCall, resultTask, StatusCode.Internal);
+        }
+
+        [Test]
+        public void ClientStreaming_StreamingReadNotAllowed()
+        {
+            asyncCall.ClientStreamingCallAsync();
+            Assert.ThrowsAsync(typeof(InvalidOperationException),
+                async () => await asyncCall.ReadMessageAsync());
+        }
+
+        [Test]
+        public void ClientStreaming_NoRequest_Success()
+        {
+            var resultTask = asyncCall.ClientStreamingCallAsync();
+            fakeCall.UnaryResponseClientHandler(true,
+                new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
+                CreateResponsePayload(),
+                new Metadata());
+
+            AssertUnaryResponseSuccess(asyncCall, fakeCall, resultTask);
+        }
+
+        [Test]
+        public void ClientStreaming_NoRequest_NonSuccessStatusCode()
+        {
+            var resultTask = asyncCall.ClientStreamingCallAsync();
+            fakeCall.UnaryResponseClientHandler(true,
+                CreateClientSideStatus(StatusCode.InvalidArgument),
+                CreateResponsePayload(),
+                new Metadata());
+
+            AssertUnaryResponseError(asyncCall, fakeCall, resultTask, StatusCode.InvalidArgument);
+        }
+
+        [Test]
+        public void ClientStreaming_MoreRequests_Success()
+        {
+            var resultTask = asyncCall.ClientStreamingCallAsync();
+            var requestStream = new ClientRequestStream<string, string>(asyncCall);
+
+            var writeTask = requestStream.WriteAsync("request1");
+            fakeCall.SendCompletionHandler(true);
+            writeTask.Wait();
+
+            var writeTask2 = requestStream.WriteAsync("request2");
+            fakeCall.SendCompletionHandler(true);
+            writeTask2.Wait();
+
+            var completeTask = requestStream.CompleteAsync();
+            fakeCall.SendCompletionHandler(true);
+            completeTask.Wait();
+
+            fakeCall.UnaryResponseClientHandler(true,
+                new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
+                CreateResponsePayload(),
+                new Metadata());
+
+            AssertUnaryResponseSuccess(asyncCall, fakeCall, resultTask);
+        }
+
+        [Test]
+        public void ClientStreaming_WriteFailure()
+        {
+            var resultTask = asyncCall.ClientStreamingCallAsync();
+            var requestStream = new ClientRequestStream<string, string>(asyncCall);
+
+            var writeTask = requestStream.WriteAsync("request1");
+            fakeCall.SendCompletionHandler(false);
+            Assert.ThrowsAsync(typeof(InvalidOperationException), async () => await writeTask);
+
+            fakeCall.UnaryResponseClientHandler(true,
+                CreateClientSideStatus(StatusCode.Internal),
+                CreateResponsePayload(),
+                new Metadata());
+
+            AssertUnaryResponseError(asyncCall, fakeCall, resultTask, StatusCode.Internal);
+        }
+
+        [Test]
+        public void ClientStreaming_WriteAfterReceivingStatusFails()
+        {
+            var resultTask = asyncCall.ClientStreamingCallAsync();
+            var requestStream = new ClientRequestStream<string, string>(asyncCall);
+
+            fakeCall.UnaryResponseClientHandler(true,
+                new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
+                CreateResponsePayload(),
+                new Metadata());
+
+            AssertUnaryResponseSuccess(asyncCall, fakeCall, resultTask);
+            Assert.Throws(typeof(InvalidOperationException), () => requestStream.WriteAsync("request1"));
+        }
+
+        [Test]
+        public void ClientStreaming_CompleteAfterReceivingStatusSucceeds()
+        {
+            var resultTask = asyncCall.ClientStreamingCallAsync();
+            var requestStream = new ClientRequestStream<string, string>(asyncCall);
+
+            fakeCall.UnaryResponseClientHandler(true,
+                new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
+                CreateResponsePayload(),
+                new Metadata());
+
+            AssertUnaryResponseSuccess(asyncCall, fakeCall, resultTask);
+            Assert.DoesNotThrowAsync(async () => await requestStream.CompleteAsync());
+        }
+
+        [Test]
+        public void ClientStreaming_WriteAfterCancellationRequestFails()
+        {
+            var resultTask = asyncCall.ClientStreamingCallAsync();
+            var requestStream = new ClientRequestStream<string, string>(asyncCall);
+
+            asyncCall.Cancel();
+            Assert.IsTrue(fakeCall.IsCancelled);
+
+            Assert.Throws(typeof(OperationCanceledException), () => requestStream.WriteAsync("request1"));
+
+            fakeCall.UnaryResponseClientHandler(true,
+                CreateClientSideStatus(StatusCode.Cancelled),
+                CreateResponsePayload(),
+                new Metadata());
+
+            AssertUnaryResponseError(asyncCall, fakeCall, resultTask, StatusCode.Cancelled);
+        }
+
+        [Test]
+        public void ServerStreaming_StreamingSendNotAllowed()
+        {
+            asyncCall.StartServerStreamingCall("request1");
+            Assert.Throws(typeof(InvalidOperationException),
+                () => asyncCall.StartSendMessage("abc", new WriteFlags(), (x,y) => {}));
+        }
+
+        [Test]
+        public void ServerStreaming_NoResponse_Success1()
+        {
+            asyncCall.StartServerStreamingCall("request1");
+            var responseStream = new ClientResponseStream<string, string>(asyncCall);
+            var readTask = responseStream.MoveNext();
+
+            fakeCall.ReceivedResponseHeadersHandler(true, new Metadata());
+            Assert.AreEqual(0, asyncCall.ResponseHeadersAsync.Result.Count);
+
+            fakeCall.ReceivedMessageHandler(true, null);
+            fakeCall.ReceivedStatusOnClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
+
+            AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
+        }
+
+        [Test]
+        public void ServerStreaming_NoResponse_Success2()
+        {
+            asyncCall.StartServerStreamingCall("request1");
+            var responseStream = new ClientResponseStream<string, string>(asyncCall);
+            var readTask = responseStream.MoveNext();
+
+            // try alternative order of completions
+            fakeCall.ReceivedStatusOnClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
+            fakeCall.ReceivedMessageHandler(true, null);
+
+            AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
+        }
+
+        [Test]
+        public void ServerStreaming_NoResponse_ReadFailure()
+        {
+            asyncCall.StartServerStreamingCall("request1");
+            var responseStream = new ClientResponseStream<string, string>(asyncCall);
+            var readTask = responseStream.MoveNext();
+
+            fakeCall.ReceivedMessageHandler(false, null);  // after a failed read, we rely on C core to deliver appropriate status code.
+            fakeCall.ReceivedStatusOnClientHandler(true, CreateClientSideStatus(StatusCode.Internal));
+
+            AssertStreamingResponseError(asyncCall, fakeCall, readTask, StatusCode.Internal);
+        }
+
+        [Test]
+        public void ServerStreaming_MoreResponses_Success()
+        {
+            asyncCall.StartServerStreamingCall("request1");
+            var responseStream = new ClientResponseStream<string, string>(asyncCall);
+
+            var readTask1 = responseStream.MoveNext();
+            fakeCall.ReceivedMessageHandler(true, CreateResponsePayload());
+            Assert.IsTrue(readTask1.Result);
+            Assert.AreEqual("response1", responseStream.Current);
+
+            var readTask2 = responseStream.MoveNext();
+            fakeCall.ReceivedMessageHandler(true, CreateResponsePayload());
+            Assert.IsTrue(readTask2.Result);
+            Assert.AreEqual("response1", responseStream.Current);
+
+            var readTask3 = responseStream.MoveNext();
+            fakeCall.ReceivedStatusOnClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
+            fakeCall.ReceivedMessageHandler(true, null);
+
+            AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask3);
+        }
+
+        [Test]
+        public void DuplexStreaming_NoRequestNoResponse_Success()
+        {
+            asyncCall.StartDuplexStreamingCall();
+            var requestStream = new ClientRequestStream<string, string>(asyncCall);
+            var responseStream = new ClientResponseStream<string, string>(asyncCall);
+
+            var writeTask1 = requestStream.CompleteAsync();
+            fakeCall.SendCompletionHandler(true);
+            Assert.DoesNotThrowAsync(async () => await writeTask1);
+
+            var readTask = responseStream.MoveNext();
+            fakeCall.ReceivedMessageHandler(true, null);
+            fakeCall.ReceivedStatusOnClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
+
+            AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
+        }
+
+        [Test]
+        public void DuplexStreaming_WriteAfterReceivingStatusFails()
+        {
+            asyncCall.StartDuplexStreamingCall();
+            var requestStream = new ClientRequestStream<string, string>(asyncCall);
+            var responseStream = new ClientResponseStream<string, string>(asyncCall);
+
+            var readTask = responseStream.MoveNext();
+            fakeCall.ReceivedMessageHandler(true, null);
+            fakeCall.ReceivedStatusOnClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
+
+            AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
+
+            Assert.ThrowsAsync(typeof(InvalidOperationException), async () => await requestStream.WriteAsync("request1"));
+        }
+
+        [Test]
+        public void DuplexStreaming_CompleteAfterReceivingStatusFails()
+        {
+            asyncCall.StartDuplexStreamingCall();
+            var requestStream = new ClientRequestStream<string, string>(asyncCall);
+            var responseStream = new ClientResponseStream<string, string>(asyncCall);
+
+            var readTask = responseStream.MoveNext();
+            fakeCall.ReceivedMessageHandler(true, null);
+            fakeCall.ReceivedStatusOnClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
+
+            AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
+
+            Assert.DoesNotThrowAsync(async () => await requestStream.CompleteAsync());
+        }
+
+        [Test]
+        public void DuplexStreaming_WriteAfterCancellationRequestFails()
+        {
+            asyncCall.StartDuplexStreamingCall();
+            var requestStream = new ClientRequestStream<string, string>(asyncCall);
+            var responseStream = new ClientResponseStream<string, string>(asyncCall);
+
+            asyncCall.Cancel();
+            Assert.IsTrue(fakeCall.IsCancelled);
+            Assert.Throws(typeof(OperationCanceledException), () => requestStream.WriteAsync("request1"));
+
+            var readTask = responseStream.MoveNext();
+            fakeCall.ReceivedMessageHandler(true, null);
+            fakeCall.ReceivedStatusOnClientHandler(true, CreateClientSideStatus(StatusCode.Cancelled));
+
+            AssertStreamingResponseError(asyncCall, fakeCall, readTask, StatusCode.Cancelled);
+        }
+
+        [Test]
+        public void DuplexStreaming_ReadAfterCancellationRequestCanSucceed()
+        {
+            asyncCall.StartDuplexStreamingCall();
+            var responseStream = new ClientResponseStream<string, string>(asyncCall);
+
+            asyncCall.Cancel();
+            Assert.IsTrue(fakeCall.IsCancelled);
+
+            var readTask1 = responseStream.MoveNext();
+            fakeCall.ReceivedMessageHandler(true, CreateResponsePayload());
+            Assert.IsTrue(readTask1.Result);
+            Assert.AreEqual("response1", responseStream.Current);
+
+            var readTask2 = responseStream.MoveNext();
+            fakeCall.ReceivedMessageHandler(true, null);
+            fakeCall.ReceivedStatusOnClientHandler(true, CreateClientSideStatus(StatusCode.Cancelled));
+
+            AssertStreamingResponseError(asyncCall, fakeCall, readTask2, StatusCode.Cancelled);
+        }
+
+        [Test]
+        public void DuplexStreaming_ReadStartedBeforeCancellationRequestCanSucceed()
+        {
+            asyncCall.StartDuplexStreamingCall();
+            var responseStream = new ClientResponseStream<string, string>(asyncCall);
+
+            var readTask1 = responseStream.MoveNext();  // initiate the read before cancel request
+            asyncCall.Cancel();
+            Assert.IsTrue(fakeCall.IsCancelled);
+
+            fakeCall.ReceivedMessageHandler(true, CreateResponsePayload());
+            Assert.IsTrue(readTask1.Result);
+            Assert.AreEqual("response1", responseStream.Current);
+
+            var readTask2 = responseStream.MoveNext();
+            fakeCall.ReceivedMessageHandler(true, null);
+            fakeCall.ReceivedStatusOnClientHandler(true, CreateClientSideStatus(StatusCode.Cancelled));
+
+            AssertStreamingResponseError(asyncCall, fakeCall, readTask2, StatusCode.Cancelled);
+        }
+
+        ClientSideStatus CreateClientSideStatus(StatusCode statusCode)
+        {
+            return new ClientSideStatus(new Status(statusCode, ""), new Metadata());
+        }
+
+        byte[] CreateResponsePayload()
+        {
+            return Marshallers.StringMarshaller.Serializer("response1");
+        }
+
+        static void AssertUnaryResponseSuccess(AsyncCall<string, string> asyncCall, FakeNativeCall fakeCall, Task<string> resultTask)
+        {
             Assert.IsTrue(resultTask.IsCompleted);
             Assert.IsTrue(fakeCall.IsDisposed);
 
-            Assert.AreEqual(StatusCode.Internal, asyncCall.GetStatus().StatusCode);
-            Assert.IsNull(asyncCall.GetTrailers());
-            var ex = Assert.ThrowsAsync<RpcException>(async () => await resultTask);
-            Assert.AreEqual(StatusCode.Internal, ex.Status.StatusCode);
+            Assert.AreEqual(Status.DefaultSuccess, asyncCall.GetStatus());
+            Assert.AreEqual(0, asyncCall.ResponseHeadersAsync.Result.Count);
+            Assert.AreEqual(0, asyncCall.GetTrailers().Count);
+            Assert.AreEqual("response1", resultTask.Result);
         }
 
-        internal class FakeNativeCall : INativeCall
+        static void AssertStreamingResponseSuccess(AsyncCall<string, string> asyncCall, FakeNativeCall fakeCall, Task<bool> moveNextTask)
         {
-            public UnaryResponseClientHandler UnaryResponseClientHandler
-            {
-                get;
-                set;
-            }
+            Assert.IsTrue(moveNextTask.IsCompleted);
+            Assert.IsTrue(fakeCall.IsDisposed);
 
-            public ReceivedStatusOnClientHandler ReceivedStatusOnClientHandler
-            {
-                get;
-                set;
-            }
+            Assert.IsFalse(moveNextTask.Result);
+            Assert.AreEqual(Status.DefaultSuccess, asyncCall.GetStatus());
+            Assert.AreEqual(0, asyncCall.GetTrailers().Count);
+        }
 
-            public ReceivedMessageHandler ReceivedMessageHandler
-            {
-                get;
-                set;
-            }
+        static void AssertUnaryResponseError(AsyncCall<string, string> asyncCall, FakeNativeCall fakeCall, Task<string> resultTask, StatusCode expectedStatusCode)
+        {
+            Assert.IsTrue(resultTask.IsCompleted);
+            Assert.IsTrue(fakeCall.IsDisposed);
 
-            public ReceivedResponseHeadersHandler ReceivedResponseHeadersHandler
-            {
-                get;
-                set;
-            }
+            Assert.AreEqual(expectedStatusCode, asyncCall.GetStatus().StatusCode);
+            var ex = Assert.ThrowsAsync<RpcException>(async () => await resultTask);
+            Assert.AreEqual(expectedStatusCode, ex.Status.StatusCode);
+            Assert.AreEqual(0, asyncCall.ResponseHeadersAsync.Result.Count);
+            Assert.AreEqual(0, asyncCall.GetTrailers().Count);
+        }
 
-            public SendCompletionHandler SendCompletionHandler
-            {
-                get;
-                set;
-            }
+        static void AssertStreamingResponseError(AsyncCall<string, string> asyncCall, FakeNativeCall fakeCall, Task<bool> moveNextTask, StatusCode expectedStatusCode)
+        {
+            Assert.IsTrue(moveNextTask.IsCompleted);
+            Assert.IsTrue(fakeCall.IsDisposed);
 
-            public ReceivedCloseOnServerHandler ReceivedCloseOnServerHandler
-            {
-                get;
-                set;
-            }
-
-            public bool IsCancelled
-            {
-                get;
-                set;
-            }
-
-            public bool IsDisposed
-            {
-                get;
-                set;
-            }
-
-            public void Cancel()
-            {
-                IsCancelled = true;
-            }
-
-            public void CancelWithStatus(Status status)
-            {
-                IsCancelled = true;
-            }
-
-            public string GetPeer()
-            {
-                return "PEER";
-            }
-
-            public void StartUnary(UnaryResponseClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
-            {
-                UnaryResponseClientHandler = callback;
-            }
-
-            public void StartUnary(BatchContextSafeHandle ctx, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
-            {
-                throw new NotImplementedException();
-            }
-
-            public void StartClientStreaming(UnaryResponseClientHandler callback, MetadataArraySafeHandle metadataArray)
-            {
-                UnaryResponseClientHandler = callback;
-            }
-
-            public void StartServerStreaming(ReceivedStatusOnClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
-            {
-                ReceivedStatusOnClientHandler = callback;
-            }
-
-            public void StartDuplexStreaming(ReceivedStatusOnClientHandler callback, MetadataArraySafeHandle metadataArray)
-            {
-                ReceivedStatusOnClientHandler = callback;
-            }
-
-            public void StartReceiveMessage(ReceivedMessageHandler callback)
-            {
-                ReceivedMessageHandler = callback;
-            }
-
-            public void StartReceiveInitialMetadata(ReceivedResponseHeadersHandler callback)
-            {
-                ReceivedResponseHeadersHandler = callback;
-            }
-
-            public void StartSendInitialMetadata(SendCompletionHandler callback, MetadataArraySafeHandle metadataArray)
-            {
-                SendCompletionHandler = callback;
-            }
-
-            public void StartSendMessage(SendCompletionHandler callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata)
-            {
-                SendCompletionHandler = callback;
-            }
-
-            public void StartSendCloseFromClient(SendCompletionHandler callback)
-            {
-                SendCompletionHandler = callback;
-            }
-
-            public void StartSendStatusFromServer(SendCompletionHandler callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata)
-            {
-                SendCompletionHandler = callback;
-            }
-
-            public void StartServerSide(ReceivedCloseOnServerHandler callback)
-            {
-                ReceivedCloseOnServerHandler = callback;
-            }
-
-            public void Dispose()
-            {
-                IsDisposed = true;
-            }
+            var ex = Assert.ThrowsAsync<RpcException>(async () => await moveNextTask);
+            Assert.AreEqual(expectedStatusCode, ex.Status.StatusCode);
+            Assert.AreEqual(expectedStatusCode, asyncCall.GetStatus().StatusCode);
+            Assert.AreEqual(0, asyncCall.GetTrailers().Count);
         }
     }
 }
diff --git a/src/csharp/Grpc.Core.Tests/Internal/FakeNativeCall.cs b/src/csharp/Grpc.Core.Tests/Internal/FakeNativeCall.cs
new file mode 100644
index 0000000..909112a
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/Internal/FakeNativeCall.cs
@@ -0,0 +1,184 @@
+#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.Runtime.InteropServices;
+using System.Threading.Tasks;
+
+using Grpc.Core.Internal;
+using NUnit.Framework;
+
+namespace Grpc.Core.Internal.Tests
+{
+    /// <summary>
+    /// For testing purposes.
+    /// </summary>
+    internal class FakeNativeCall : INativeCall
+    {
+        public UnaryResponseClientHandler UnaryResponseClientHandler
+        {
+            get;
+            set;
+        }
+
+        public ReceivedStatusOnClientHandler ReceivedStatusOnClientHandler
+        {
+            get;
+            set;
+        }
+
+        public ReceivedMessageHandler ReceivedMessageHandler
+        {
+            get;
+            set;
+        }
+
+        public ReceivedResponseHeadersHandler ReceivedResponseHeadersHandler
+        {
+            get;
+            set;
+        }
+
+        public SendCompletionHandler SendCompletionHandler
+        {
+            get;
+            set;
+        }
+
+        public SendCompletionHandler SendStatusFromServerHandler
+        {
+            get;
+            set;
+        }
+
+        public ReceivedCloseOnServerHandler ReceivedCloseOnServerHandler
+        {
+            get;
+            set;
+        }
+
+        public bool IsCancelled
+        {
+            get;
+            set;
+        }
+
+        public bool IsDisposed
+        {
+            get;
+            set;
+        }
+
+        public void Cancel()
+        {
+            IsCancelled = true;
+        }
+
+        public void CancelWithStatus(Status status)
+        {
+            IsCancelled = true;
+        }
+
+        public string GetPeer()
+        {
+            return "PEER";
+        }
+
+        public void StartUnary(UnaryResponseClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
+        {
+            UnaryResponseClientHandler = callback;
+        }
+
+        public void StartUnary(BatchContextSafeHandle ctx, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void StartClientStreaming(UnaryResponseClientHandler callback, MetadataArraySafeHandle metadataArray)
+        {
+            UnaryResponseClientHandler = callback;
+        }
+
+        public void StartServerStreaming(ReceivedStatusOnClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
+        {
+            ReceivedStatusOnClientHandler = callback;
+        }
+
+        public void StartDuplexStreaming(ReceivedStatusOnClientHandler callback, MetadataArraySafeHandle metadataArray)
+        {
+            ReceivedStatusOnClientHandler = callback;
+        }
+
+        public void StartReceiveMessage(ReceivedMessageHandler callback)
+        {
+            ReceivedMessageHandler = callback;
+        }
+
+        public void StartReceiveInitialMetadata(ReceivedResponseHeadersHandler callback)
+        {
+            ReceivedResponseHeadersHandler = callback;
+        }
+
+        public void StartSendInitialMetadata(SendCompletionHandler callback, MetadataArraySafeHandle metadataArray)
+        {
+            SendCompletionHandler = callback;
+        }
+
+        public void StartSendMessage(SendCompletionHandler callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata)
+        {
+            SendCompletionHandler = callback;
+        }
+
+        public void StartSendCloseFromClient(SendCompletionHandler callback)
+        {
+            SendCompletionHandler = callback;
+        }
+
+        public void StartSendStatusFromServer(SendCompletionHandler callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata,
+            byte[] optionalPayload, WriteFlags writeFlags)
+        {
+            SendStatusFromServerHandler = callback;
+        }
+
+        public void StartServerSide(ReceivedCloseOnServerHandler callback)
+        {
+            ReceivedCloseOnServerHandler = callback;
+        }
+
+        public void Dispose()
+        {
+            IsDisposed = true;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
index 016e1b8..f522174 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
@@ -241,11 +241,10 @@
 
         /// <summary>
         /// Receives a streaming response. Only one pending read action is allowed at any given time.
-        /// completionDelegate is called when the operation finishes.
         /// </summary>
-        public void StartReadMessage(AsyncCompletionDelegate<TResponse> completionDelegate)
+        public Task<TResponse> ReadMessageAsync()
         {
-            StartReadMessageInternal(completionDelegate);
+            return ReadMessageInternalAsync();
         }
 
         /// <summary>
@@ -409,10 +408,13 @@
         /// </summary>
         private void HandleUnaryResponse(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders)
         {
+            // NOTE: because this event is a result of batch containing GRPC_OP_RECV_STATUS_ON_CLIENT,
+            // success will be always set to true.
+
             using (Profilers.ForCurrentThread().NewScope("AsyncCall.HandleUnaryResponse"))
             {
                 TResponse msg = default(TResponse);
-                var deserializeException = success ? TryDeserialize(receivedMessage, out msg) : null;
+                var deserializeException = TryDeserialize(receivedMessage, out msg);
 
                 lock (myLock)
                 {
@@ -425,14 +427,13 @@
                     finishedStatus = receivedStatus;
 
                     ReleaseResourcesIfPossible();
-
                 }
 
                 responseHeadersTcs.SetResult(responseHeaders);
 
                 var status = receivedStatus.Status;
 
-                if (!success || status.StatusCode != StatusCode.OK)
+                if (status.StatusCode != StatusCode.OK)
                 {
                     unaryResponseTcs.SetException(new RpcException(status));
                     return;
@@ -447,6 +448,9 @@
         /// </summary>
         private void HandleFinished(bool success, ClientSideStatus receivedStatus)
         {
+            // NOTE: because this event is a result of batch containing GRPC_OP_RECV_STATUS_ON_CLIENT,
+            // success will be always set to true.
+
             lock (myLock)
             {
                 finished = true;
@@ -457,7 +461,7 @@
 
             var status = receivedStatus.Status;
 
-            if (!success || status.StatusCode != StatusCode.OK)
+            if (status.StatusCode != StatusCode.OK)
             {
                 streamingCallFinishedTcs.SetException(new RpcException(status));
                 return;
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
index ccd047f..42234dc 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
@@ -68,7 +68,8 @@
         protected bool cancelRequested;
 
         protected AsyncCompletionDelegate<object> sendCompletionDelegate;  // Completion of a pending send or sendclose if not null.
-        protected AsyncCompletionDelegate<TRead> readCompletionDelegate;  // Completion of a pending send or sendclose if not null.
+        protected TaskCompletionSource<TRead> streamingReadTcs;  // Completion of a pending streaming read if not null.
+        protected TaskCompletionSource<object> sendStatusFromServerTcs;
 
         protected bool readingDone;  // True if last read (i.e. read with null payload) was already received.
         protected bool halfcloseRequested;  // True if send close have been initiated.
@@ -150,15 +151,25 @@
         /// Initiates reading a message. Only one read operation can be active at a time.
         /// completionDelegate is invoked upon completion.
         /// </summary>
-        protected void StartReadMessageInternal(AsyncCompletionDelegate<TRead> completionDelegate)
+        protected Task<TRead> ReadMessageInternalAsync()
         {
             lock (myLock)
             {
-                GrpcPreconditions.CheckNotNull(completionDelegate, "Completion delegate cannot be null");
-                CheckReadingAllowed();
+                GrpcPreconditions.CheckState(started);
+                if (readingDone)
+                {
+                    // the last read that returns null or throws an exception is idempotent
+                    // and maintain its state.
+                    GrpcPreconditions.CheckState(streamingReadTcs != null, "Call does not support streaming reads.");
+                    return streamingReadTcs.Task;
+                }
+
+                GrpcPreconditions.CheckState(streamingReadTcs == null, "Only one read can be pending at a time");
+                GrpcPreconditions.CheckState(!disposed);
 
                 call.StartReceiveMessage(HandleReadFinished);
-                readCompletionDelegate = completionDelegate;
+                streamingReadTcs = new TaskCompletionSource<TRead>();
+                return streamingReadTcs.Task;
             }
         }
 
@@ -213,15 +224,6 @@
             GrpcPreconditions.CheckState(sendCompletionDelegate == null, "Only one write can be pending at a time");
         }
 
-        protected virtual void CheckReadingAllowed()
-        {
-            GrpcPreconditions.CheckState(started);
-            GrpcPreconditions.CheckState(!disposed);
-
-            GrpcPreconditions.CheckState(!readingDone, "Stream has already been closed.");
-            GrpcPreconditions.CheckState(readCompletionDelegate == null, "Only one read can be pending at a time");
-        }
-
         protected void CheckNotCancelled()
         {
             if (cancelRequested)
@@ -322,22 +324,18 @@
         /// </summary>
         protected void HandleSendStatusFromServerFinished(bool success)
         {
-            AsyncCompletionDelegate<object> origCompletionDelegate = null;
             lock (myLock)
             {
-                origCompletionDelegate = sendCompletionDelegate;
-                sendCompletionDelegate = null;
-
                 ReleaseResourcesIfPossible();
             }
 
             if (!success)
             {
-                FireCompletion(origCompletionDelegate, null, new InvalidOperationException("Error sending status from server."));
+                sendStatusFromServerTcs.SetException(new InvalidOperationException("Error sending status from server."));
             }
             else
             {
-                FireCompletion(origCompletionDelegate, null, null);
+                sendStatusFromServerTcs.SetResult(null);
             }
         }
 
@@ -346,15 +344,17 @@
         /// </summary>
         protected void HandleReadFinished(bool success, byte[] receivedMessage)
         {
+            // if success == false, received message will be null. It that case we will
+            // treat this completion as the last read an rely on C core to handle the failed
+            // read (e.g. deliver approriate statusCode on the clientside).
+
             TRead msg = default(TRead);
             var deserializeException = (success && receivedMessage != null) ? TryDeserialize(receivedMessage, out msg) : null;
 
-            AsyncCompletionDelegate<TRead> origCompletionDelegate = null;
+            TaskCompletionSource<TRead> origTcs = null;
             lock (myLock)
             {
-                origCompletionDelegate = readCompletionDelegate;
-                readCompletionDelegate = null;
-
+                origTcs = streamingReadTcs;
                 if (receivedMessage == null)
                 {
                     // This was the last read.
@@ -364,20 +364,25 @@
                 if (deserializeException != null && IsClient)
                 {
                     readingDone = true;
+
+                    // TODO(jtattermusch): it might be too late to set the status
                     CancelWithStatus(DeserializeResponseFailureStatus);
                 }
 
+                if (!readingDone)
+                {
+                    streamingReadTcs = null;
+                }
+
                 ReleaseResourcesIfPossible();
             }
 
-            // TODO: handle the case when success==false
-
             if (deserializeException != null && !IsClient)
             {
-                FireCompletion(origCompletionDelegate, default(TRead), new IOException("Failed to deserialize request message.", deserializeException));
+                origTcs.SetException(new IOException("Failed to deserialize request message.", deserializeException));
                 return;
             }
-            FireCompletion(origCompletionDelegate, msg, null);
+            origTcs.SetResult(msg);
         }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
index bea2b36..b1566b4 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
@@ -65,6 +65,15 @@
         }
 
         /// <summary>
+        /// Only for testing purposes.
+        /// </summary>
+        public void InitializeForTesting(INativeCall call)
+        {
+            server.AddCallReference(this);
+            InitializeInternal(call);
+        }
+
+        /// <summary>
         /// Starts a server side call.
         /// </summary>
         public Task ServerSideCallAsync()
@@ -91,11 +100,10 @@
 
         /// <summary>
         /// Receives a streaming request. Only one pending read action is allowed at any given time.
-        /// completionDelegate is called when the operation finishes.
         /// </summary>
-        public void StartReadMessage(AsyncCompletionDelegate<TRequest> completionDelegate)
+        public Task<TRequest> ReadMessageAsync()
         {
-            StartReadMessageInternal(completionDelegate);
+            return ReadMessageInternalAsync();
         }
 
         /// <summary>
@@ -128,24 +136,33 @@
         }
 
         /// <summary>
-        /// Sends call result status, also indicating server is done with streaming responses.
-        /// Only one pending send action is allowed at any given time.
-        /// completionDelegate is called when the operation finishes.
+        /// Sends call result status, indicating we are done with writes.
+        /// Sending a status different from StatusCode.OK will also implicitly cancel the call.
         /// </summary>
-        public void StartSendStatusFromServer(Status status, Metadata trailers, AsyncCompletionDelegate<object> completionDelegate)
+        public Task SendStatusFromServerAsync(Status status, Metadata trailers, Tuple<TResponse, WriteFlags> optionalWrite)
         {
+            byte[] payload = optionalWrite != null ? UnsafeSerialize(optionalWrite.Item1) : null;
+            var writeFlags = optionalWrite != null ? optionalWrite.Item2 : default(WriteFlags);
+
             lock (myLock)
             {
-                GrpcPreconditions.CheckNotNull(completionDelegate, "Completion delegate cannot be null");
-                CheckSendingAllowed(allowFinished: false);
+                GrpcPreconditions.CheckState(started);
+                GrpcPreconditions.CheckState(!disposed);
+                GrpcPreconditions.CheckState(!halfcloseRequested, "Can only send status from server once.");
 
                 using (var metadataArray = MetadataArraySafeHandle.Create(trailers))
                 {
-                    call.StartSendStatusFromServer(HandleSendStatusFromServerFinished, status, metadataArray, !initialMetadataSent);
+                    call.StartSendStatusFromServer(HandleSendStatusFromServerFinished, status, metadataArray, !initialMetadataSent,
+                        payload, writeFlags);
                 }
                 halfcloseRequested = true;
-                readingDone = true;
-                sendCompletionDelegate = completionDelegate;
+                initialMetadataSent = true;
+                sendStatusFromServerTcs = new TaskCompletionSource<object>();
+                if (optionalWrite != null)
+                {
+                    streamingWritesCounter++;
+                }
+                return sendStatusFromServerTcs.Task;
             }
         }
 
@@ -174,12 +191,6 @@
             get { return false; }
         }
 
-        protected override void CheckReadingAllowed()
-        {
-            base.CheckReadingAllowed();
-            GrpcPreconditions.CheckArgument(!cancelRequested);
-        }
-
         protected override void OnAfterReleaseResources()
         {
             server.RemoveCallReference(this);
@@ -190,12 +201,21 @@
         /// </summary>
         private void HandleFinishedServerside(bool success, bool cancelled)
         {
+            // NOTE: because this event is a result of batch containing GRPC_OP_RECV_CLOSE_ON_SERVER,
+            // success will be always set to true.
             lock (myLock)
             {
                 finished = true;
+                if (streamingReadTcs == null)
+                {
+                    // if there's no pending read, readingDone=true will dispose now.
+                    // if there is a pending read, we will dispose once that read finishes.
+                    readingDone = true;
+                    streamingReadTcs = new TaskCompletionSource<TRequest>();
+                    streamingReadTcs.SetResult(default(TRequest));
+                }
                 ReleaseResourcesIfPossible();
             }
-            // TODO(jtattermusch): handle error
 
             if (cancelled)
             {
diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
index 500653b..244b97d 100644
--- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
@@ -135,13 +135,16 @@
             }
         }
 
-        public void StartSendStatusFromServer(SendCompletionHandler callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata)
+        public void StartSendStatusFromServer(SendCompletionHandler callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata,
+            byte[] optionalPayload, WriteFlags writeFlags)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
+                var optionalPayloadLength = optionalPayload != null ? new UIntPtr((ulong)optionalPayload.Length) : UIntPtr.Zero;
                 completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success));
-                Native.grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail, metadataArray, sendEmptyInitialMetadata).CheckOk();
+                Native.grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail, metadataArray, sendEmptyInitialMetadata,
+                    optionalPayload, optionalPayloadLength, writeFlags).CheckOk();
             }
         }
 
diff --git a/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs b/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs
index d6e34a0..ad9423f 100644
--- a/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs
+++ b/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs
@@ -68,9 +68,7 @@
             {
                 throw new InvalidOperationException("Cancellation of individual reads is not supported.");
             }
-            var taskSource = new AsyncCompletionTaskSource<TResponse>();
-            call.StartReadMessage(taskSource.CompletionDelegate);
-            var result = await taskSource.Task.ConfigureAwait(false);
+            var result = await call.ReadMessageAsync().ConfigureAwait(false);
             this.current = result;
 
             if (result == null)
diff --git a/src/csharp/Grpc.Core/Internal/INativeCall.cs b/src/csharp/Grpc.Core/Internal/INativeCall.cs
index cbef599..cd3719c 100644
--- a/src/csharp/Grpc.Core/Internal/INativeCall.cs
+++ b/src/csharp/Grpc.Core/Internal/INativeCall.cs
@@ -78,7 +78,7 @@
 
         void StartSendCloseFromClient(SendCompletionHandler callback);
 
-        void StartSendStatusFromServer(SendCompletionHandler callback, Grpc.Core.Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata);
+        void StartSendStatusFromServer(SendCompletionHandler callback, Grpc.Core.Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata, byte[] optionalPayload, Grpc.Core.WriteFlags writeFlags);
 
         void StartServerSide(ReceivedCloseOnServerHandler callback);
     }
diff --git a/src/csharp/Grpc.Core/Internal/NativeMethods.cs b/src/csharp/Grpc.Core/Internal/NativeMethods.cs
index 9ee0ba3..c277c73 100644
--- a/src/csharp/Grpc.Core/Internal/NativeMethods.cs
+++ b/src/csharp/Grpc.Core/Internal/NativeMethods.cs
@@ -421,20 +421,21 @@
             public delegate GRPCCallError grpcsharp_call_cancel_delegate(CallSafeHandle call);
             public delegate GRPCCallError grpcsharp_call_cancel_with_status_delegate(CallSafeHandle call, StatusCode status, string description);
             public delegate GRPCCallError grpcsharp_call_start_unary_delegate(CallSafeHandle call,
-                BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags);
+                BatchContextSafeHandle ctx, byte[] sendBuffer, UIntPtr sendBufferLen, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags);
             public delegate GRPCCallError grpcsharp_call_start_client_streaming_delegate(CallSafeHandle call,
                 BatchContextSafeHandle ctx, MetadataArraySafeHandle metadataArray);
             public delegate GRPCCallError grpcsharp_call_start_server_streaming_delegate(CallSafeHandle call,
-                BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len,
+                BatchContextSafeHandle ctx, byte[] sendBuffer, UIntPtr sendBufferLen,
                 MetadataArraySafeHandle metadataArray, WriteFlags writeFlags);
             public delegate GRPCCallError grpcsharp_call_start_duplex_streaming_delegate(CallSafeHandle call,
                 BatchContextSafeHandle ctx, MetadataArraySafeHandle metadataArray);
             public delegate GRPCCallError grpcsharp_call_send_message_delegate(CallSafeHandle call,
-                BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, WriteFlags writeFlags, bool sendEmptyInitialMetadata);
+                BatchContextSafeHandle ctx, byte[] sendBuffer, UIntPtr sendBufferLen, WriteFlags writeFlags, bool sendEmptyInitialMetadata);
             public delegate GRPCCallError grpcsharp_call_send_close_from_client_delegate(CallSafeHandle call,
                 BatchContextSafeHandle ctx);
             public delegate GRPCCallError grpcsharp_call_send_status_from_server_delegate(CallSafeHandle call,
-                BatchContextSafeHandle ctx, StatusCode statusCode, string statusMessage, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata);
+                BatchContextSafeHandle ctx, StatusCode statusCode, string statusMessage, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata,
+                byte[] optionalSendBuffer, UIntPtr optionalSendBufferLen, WriteFlags writeFlags);
             public delegate GRPCCallError grpcsharp_call_recv_message_delegate(CallSafeHandle call,
                 BatchContextSafeHandle ctx);
             public delegate GRPCCallError grpcsharp_call_recv_initial_metadata_delegate(CallSafeHandle call,
@@ -593,7 +594,7 @@
 
             [DllImport("grpc_csharp_ext.dll")]
             public static extern GRPCCallError grpcsharp_call_start_unary(CallSafeHandle call,
-                BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags);
+                BatchContextSafeHandle ctx, byte[] sendBuffer, UIntPtr sendBufferLen, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags);
 
             [DllImport("grpc_csharp_ext.dll")]
             public static extern GRPCCallError grpcsharp_call_start_client_streaming(CallSafeHandle call,
@@ -601,7 +602,7 @@
 
             [DllImport("grpc_csharp_ext.dll")]
             public static extern GRPCCallError grpcsharp_call_start_server_streaming(CallSafeHandle call,
-                BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len,
+                BatchContextSafeHandle ctx, byte[] sendBuffer, UIntPtr sendBufferLen,
                 MetadataArraySafeHandle metadataArray, WriteFlags writeFlags);
 
             [DllImport("grpc_csharp_ext.dll")]
@@ -610,7 +611,7 @@
 
             [DllImport("grpc_csharp_ext.dll")]
             public static extern GRPCCallError grpcsharp_call_send_message(CallSafeHandle call,
-                BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, WriteFlags writeFlags, bool sendEmptyInitialMetadata);
+                BatchContextSafeHandle ctx, byte[] sendBuffer, UIntPtr sendBufferLen, WriteFlags writeFlags, bool sendEmptyInitialMetadata);
 
             [DllImport("grpc_csharp_ext.dll")]
             public static extern GRPCCallError grpcsharp_call_send_close_from_client(CallSafeHandle call,
@@ -618,7 +619,8 @@
 
             [DllImport("grpc_csharp_ext.dll")]
             public static extern GRPCCallError grpcsharp_call_send_status_from_server(CallSafeHandle call,
-                BatchContextSafeHandle ctx, StatusCode statusCode, string statusMessage, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata);
+                BatchContextSafeHandle ctx, StatusCode statusCode, string statusMessage, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata,
+                byte[] optionalSendBuffer, UIntPtr optionalSendBufferLen, WriteFlags writeFlags);
 
             [DllImport("grpc_csharp_ext.dll")]
             public static extern GRPCCallError grpcsharp_call_recv_message(CallSafeHandle call,
diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
index 1f83e51..85b7a4b 100644
--- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
@@ -75,29 +75,32 @@
             var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall);
 
             Status status;
+            Tuple<TResponse,WriteFlags> responseTuple = null;
             var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
             try
             {
                 GrpcPreconditions.CheckArgument(await requestStream.MoveNext().ConfigureAwait(false));
                 var request = requestStream.Current;
-                // TODO(jtattermusch): we need to read the full stream so that native callhandle gets deallocated.
-                GrpcPreconditions.CheckArgument(!await requestStream.MoveNext().ConfigureAwait(false));
-                var result = await handler(request, context).ConfigureAwait(false);
+                var response = await handler(request, context).ConfigureAwait(false);
                 status = context.Status;
-                await responseStream.WriteAsync(result).ConfigureAwait(false);
+                responseTuple = Tuple.Create(response, HandlerUtils.GetWriteFlags(context.WriteOptions));
             } 
             catch (Exception e)
             {
-                Logger.Error(e, "Exception occured in handler.");
+                if (!(e is RpcException))
+                {
+                    Logger.Warning(e, "Exception occured in handler.");
+                }
                 status = HandlerUtils.StatusFromException(e);
             }
             try
             {
-                await responseStream.WriteStatusAsync(status, context.ResponseTrailers).ConfigureAwait(false);
+                await asyncCall.SendStatusFromServerAsync(status, context.ResponseTrailers, responseTuple).ConfigureAwait(false);
             }
-            catch (OperationCanceledException)
+            catch (Exception)
             {
-                // Call has been already cancelled.
+                asyncCall.Cancel();
+                throw;
             }
             await finishedTask.ConfigureAwait(false);
         }
@@ -136,24 +139,26 @@
             {
                 GrpcPreconditions.CheckArgument(await requestStream.MoveNext().ConfigureAwait(false));
                 var request = requestStream.Current;
-                // TODO(jtattermusch): we need to read the full stream so that native callhandle gets deallocated.
-                GrpcPreconditions.CheckArgument(!await requestStream.MoveNext().ConfigureAwait(false));
                 await handler(request, responseStream, context).ConfigureAwait(false);
                 status = context.Status;
             }
             catch (Exception e)
             {
-                Logger.Error(e, "Exception occured in handler.");
+                if (!(e is RpcException))
+                {
+                    Logger.Warning(e, "Exception occured in handler.");
+                }
                 status = HandlerUtils.StatusFromException(e);
             }
 
             try
             {
-                await responseStream.WriteStatusAsync(status, context.ResponseTrailers).ConfigureAwait(false);
+                await asyncCall.SendStatusFromServerAsync(status, context.ResponseTrailers, null).ConfigureAwait(false);
             }
-            catch (OperationCanceledException)
+            catch (Exception)
             {
-                // Call has been already cancelled.
+                asyncCall.Cancel();
+                throw;
             }
             await finishedTask.ConfigureAwait(false);
         }
@@ -187,33 +192,31 @@
             var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall);
 
             Status status;
+            Tuple<TResponse,WriteFlags> responseTuple = null;
             var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
             try
             {
-                var result = await handler(requestStream, context).ConfigureAwait(false);
+                var response = await handler(requestStream, context).ConfigureAwait(false);
                 status = context.Status;
-                try
-                {
-                    await responseStream.WriteAsync(result).ConfigureAwait(false);
-                }
-                catch (OperationCanceledException)
-                {
-                    status = Status.DefaultCancelled;
-                }
+                responseTuple = Tuple.Create(response, HandlerUtils.GetWriteFlags(context.WriteOptions));
             }
             catch (Exception e)
             {
-                Logger.Error(e, "Exception occured in handler.");
+                if (!(e is RpcException))
+                {
+                    Logger.Warning(e, "Exception occured in handler.");
+                }
                 status = HandlerUtils.StatusFromException(e);
             }
 
             try
             {
-                await responseStream.WriteStatusAsync(status, context.ResponseTrailers).ConfigureAwait(false);
+                await asyncCall.SendStatusFromServerAsync(status, context.ResponseTrailers, responseTuple).ConfigureAwait(false);
             }
-            catch (OperationCanceledException)
+            catch (Exception)
             {
-                // Call has been already cancelled.
+                asyncCall.Cancel();
+                throw;
             }
             await finishedTask.ConfigureAwait(false);
         }
@@ -255,16 +258,20 @@
             }
             catch (Exception e)
             {
-                Logger.Error(e, "Exception occured in handler.");
+                if (!(e is RpcException))
+                {
+                    Logger.Warning(e, "Exception occured in handler.");
+                }
                 status = HandlerUtils.StatusFromException(e);
             }
             try
             {
-                await responseStream.WriteStatusAsync(status, context.ResponseTrailers).ConfigureAwait(false);
+                await asyncCall.SendStatusFromServerAsync(status, context.ResponseTrailers, null).ConfigureAwait(false);
             }
-            catch (OperationCanceledException)
+            catch (Exception)
             {
-                // Call has been already cancelled.
+                asyncCall.Cancel();
+                throw;
             }
             await finishedTask.ConfigureAwait(false);
         }
@@ -282,9 +289,7 @@
             
             asyncCall.Initialize(newRpc.Call);
             var finishedTask = asyncCall.ServerSideCallAsync();
-            var responseStream = new ServerResponseStream<byte[], byte[]>(asyncCall);
-
-            await responseStream.WriteStatusAsync(new Status(StatusCode.Unimplemented, ""), Metadata.Empty).ConfigureAwait(false);
+            await asyncCall.SendStatusFromServerAsync(new Status(StatusCode.Unimplemented, ""), Metadata.Empty, null).ConfigureAwait(false);
             await finishedTask.ConfigureAwait(false);
         }
     }
@@ -300,10 +305,14 @@
                 return rpcException.Status;
             }
 
-            // TODO(jtattermusch): what is the right status code here?
             return new Status(StatusCode.Unknown, "Exception was thrown by handler.");
         }
 
+        public static WriteFlags GetWriteFlags(WriteOptions writeOptions)
+        {
+            return writeOptions != null ? writeOptions.Flags : default(WriteFlags);
+        }
+
         public static ServerCallContext NewContext<TRequest, TResponse>(ServerRpcNew newRpc, string peer, ServerResponseStream<TRequest, TResponse> serverResponseStream, CancellationToken cancellationToken)
             where TRequest : class
             where TResponse : class
diff --git a/src/csharp/Grpc.Core/Internal/ServerRequestStream.cs b/src/csharp/Grpc.Core/Internal/ServerRequestStream.cs
index e7be82c..d76030d 100644
--- a/src/csharp/Grpc.Core/Internal/ServerRequestStream.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerRequestStream.cs
@@ -68,9 +68,7 @@
             {
                 throw new InvalidOperationException("Cancellation of individual reads is not supported.");
             }
-            var taskSource = new AsyncCompletionTaskSource<TRequest>();
-            call.StartReadMessage(taskSource.CompletionDelegate);
-            var result = await taskSource.Task.ConfigureAwait(false);
+            var result = await call.ReadMessageAsync().ConfigureAwait(false);
             this.current = result;
             return result != null;
         }
diff --git a/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs b/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs
index 03e39ef..ecfee0b 100644
--- a/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs
@@ -57,13 +57,6 @@
             return taskSource.Task;
         }
 
-        public Task WriteStatusAsync(Status status, Metadata trailers)
-        {
-            var taskSource = new AsyncCompletionTaskSource<object>();
-            call.StartSendStatusFromServer(status, trailers, taskSource.CompletionDelegate);
-            return taskSource.Task;
-        }
-
         public Task WriteResponseHeadersAsync(Metadata responseHeaders)
         {
             var taskSource = new AsyncCompletionTaskSource<object>();
diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c
index aeef8a7..5b8ff9b 100644
--- a/src/csharp/ext/grpc_csharp_ext.c
+++ b/src/csharp/ext/grpc_csharp_ext.c
@@ -715,10 +715,11 @@
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_send_status_from_server(
     grpc_call *call, grpcsharp_batch_context *ctx, grpc_status_code status_code,
     const char *status_details, grpc_metadata_array *trailing_metadata,
-    int32_t send_empty_initial_metadata) {
+    int32_t send_empty_initial_metadata, const char* optional_send_buffer,
+    size_t optional_send_buffer_len, uint32_t write_flags) {
   /* TODO: don't use magic number */
-  grpc_op ops[2];
-  size_t nops = send_empty_initial_metadata ? 2 : 1;
+  grpc_op ops[3];
+  size_t nops = 1;
   ops[0].op = GRPC_OP_SEND_STATUS_FROM_SERVER;
   ops[0].data.send_status_from_server.status = status_code;
   ops[0].data.send_status_from_server.status_details =
@@ -731,12 +732,23 @@
       ctx->send_status_from_server.trailing_metadata.metadata;
   ops[0].flags = 0;
   ops[0].reserved = NULL;
-  ops[1].op = GRPC_OP_SEND_INITIAL_METADATA;
-  ops[1].data.send_initial_metadata.count = 0;
-  ops[1].data.send_initial_metadata.metadata = NULL;
-  ops[1].flags = 0;
-  ops[1].reserved = NULL;
-
+  if (optional_send_buffer) {
+    ops[nops].op = GRPC_OP_SEND_MESSAGE;
+    ctx->send_message = string_to_byte_buffer(optional_send_buffer,
+                                              optional_send_buffer_len);
+    ops[nops].data.send_message = ctx->send_message;
+    ops[nops].flags = write_flags;
+    ops[nops].reserved = NULL;
+    nops ++;
+  }
+  if (send_empty_initial_metadata) {
+    ops[nops].op = GRPC_OP_SEND_INITIAL_METADATA;
+    ops[nops].data.send_initial_metadata.count = 0;
+    ops[nops].data.send_initial_metadata.metadata = NULL;
+    ops[nops].flags = 0;
+    ops[nops].reserved = NULL;
+    nops++;
+  }
   return grpc_call_start_batch(call, ops, nops, ctx, NULL);
 }
 
diff --git a/src/csharp/tests.json b/src/csharp/tests.json
index f733352..f6af340 100644
--- a/src/csharp/tests.json
+++ b/src/csharp/tests.json
@@ -1,5 +1,6 @@
 {
   "Grpc.Core.Tests": [
+    "Grpc.Core.Internal.Tests.AsyncCallServerTest",
     "Grpc.Core.Internal.Tests.AsyncCallTest",
     "Grpc.Core.Internal.Tests.ChannelArgsSafeHandleTest",
     "Grpc.Core.Internal.Tests.CompletionQueueEventTest",
diff --git a/src/node/tools/bin/protoc.js b/src/node/tools/bin/protoc.js
index 0c6d7ce..4d50c94 100755
--- a/src/node/tools/bin/protoc.js
+++ b/src/node/tools/bin/protoc.js
@@ -43,7 +43,9 @@
 var path = require('path');
 var execFile = require('child_process').execFile;
 
-var protoc = path.resolve(__dirname, 'protoc');
+var exe_ext = process.platform === 'win32' ? '.exe' : '';
+
+var protoc = path.resolve(__dirname, 'protoc' + exe_ext);
 
 execFile(protoc, process.argv.slice(2), function(error, stdout, stderr) {
   if (error) {
diff --git a/src/node/tools/bin/protoc_plugin.js b/src/node/tools/bin/protoc_plugin.js
index 0e0bb94..281ec0d 100755
--- a/src/node/tools/bin/protoc_plugin.js
+++ b/src/node/tools/bin/protoc_plugin.js
@@ -43,9 +43,11 @@
 var path = require('path');
 var execFile = require('child_process').execFile;
 
-var protoc = path.resolve(__dirname, 'grpc_node_plugin');
+var exe_ext = process.platform === 'win32' ? '.exe' : '';
 
-execFile(protoc, process.argv.slice(2), function(error, stdout, stderr) {
+var plugin = path.resolve(__dirname, 'grpc_node_plugin' + exe_ext);
+
+execFile(plugin, process.argv.slice(2), function(error, stdout, stderr) {
   if (error) {
     throw error;
   }
diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m
index 1847d60..0eb1065 100644
--- a/src/objective-c/GRPCClient/GRPCCall.m
+++ b/src/objective-c/GRPCClient/GRPCCall.m
@@ -136,6 +136,10 @@
 #pragma mark Finish
 
 - (void)finishWithError:(NSError *)errorOrNil {
+  @synchronized(self) {
+    _state = GRXWriterStateFinished;
+  }
+
   // If the call isn't retained anywhere else, it can be deallocated now.
   _retainSelf = nil;
 
@@ -342,6 +346,10 @@
 #pragma mark GRXWriter implementation
 
 - (void)startWithWriteable:(id<GRXWriteable>)writeable {
+  @synchronized(self) {
+    _state = GRXWriterStateStarted;
+  }
+
   // Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled).
   // This makes RPCs in which the call isn't externally retained possible (as long as it is started
   // before being autoreleased).
@@ -375,30 +383,32 @@
 }
 
 - (void)setState:(GRXWriterState)newState {
-  // Manual transitions are only allowed from the started or paused states.
-  if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
-    return;
-  }
+  @synchronized(self) {
+    // Manual transitions are only allowed from the started or paused states.
+    if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
+      return;
+    }
 
-  switch (newState) {
-    case GRXWriterStateFinished:
-      _state = newState;
-      // Per GRXWriter's contract, setting the state to Finished manually
-      // means one doesn't wish the writeable to be messaged anymore.
-      [_responseWriteable cancelSilently];
-      _responseWriteable = nil;
-      return;
-    case GRXWriterStatePaused:
-      _state = newState;
-      return;
-    case GRXWriterStateStarted:
-      if (_state == GRXWriterStatePaused) {
+    switch (newState) {
+      case GRXWriterStateFinished:
         _state = newState;
-        [self startNextRead];
-      }
-      return;
-    case GRXWriterStateNotStarted:
-      return;
+        // Per GRXWriter's contract, setting the state to Finished manually
+        // means one doesn't wish the writeable to be messaged anymore.
+        [_responseWriteable cancelSilently];
+        _responseWriteable = nil;
+        return;
+      case GRXWriterStatePaused:
+        _state = newState;
+        return;
+      case GRXWriterStateStarted:
+        if (_state == GRXWriterStatePaused) {
+          _state = newState;
+          [self startNextRead];
+        }
+        return;
+      case GRXWriterStateNotStarted:
+        return;
+    }
   }
 }
 
diff --git a/src/objective-c/tests/InteropTests.m b/src/objective-c/tests/InteropTests.m
index 26877b1..4f096b9 100644
--- a/src/objective-c/tests/InteropTests.m
+++ b/src/objective-c/tests/InteropTests.m
@@ -272,8 +272,14 @@
     XCTAssertEqual(error.code, GRPC_STATUS_CANCELLED);
     [expectation fulfill];
   }];
+  XCTAssertEqual(call.state, GRXWriterStateNotStarted);
+
   [call start];
+  XCTAssertEqual(call.state, GRXWriterStateStarted);
+
   [call cancel];
+  XCTAssertEqual(call.state, GRXWriterStateFinished);
+
   [self waitForExpectationsWithTimeout:1 handler:nil];
 }
 
diff --git a/src/proto/grpc/testing/echo.proto b/src/proto/grpc/testing/echo.proto
index 0eef53a..c596aab 100644
--- a/src/proto/grpc/testing/echo.proto
+++ b/src/proto/grpc/testing/echo.proto
@@ -45,3 +45,7 @@
 service UnimplementedService {
   rpc Unimplemented(EchoRequest) returns (EchoResponse);
 }
+
+// A service without any rpc defined to test coverage.
+service NoRpcService {
+}
diff --git a/src/python/grpcio/grpc/_cython/imports.generated.c b/src/python/grpcio/grpc/_cython/imports.generated.c
index f0a40db..0955147 100644
--- a/src/python/grpcio/grpc/_cython/imports.generated.c
+++ b/src/python/grpcio/grpc/_cython/imports.generated.c
@@ -125,6 +125,7 @@
 grpc_header_nonbin_value_is_legal_type grpc_header_nonbin_value_is_legal_import;
 grpc_is_binary_header_type grpc_is_binary_header_import;
 grpc_call_error_to_string_type grpc_call_error_to_string_import;
+grpc_cronet_secure_channel_create_type grpc_cronet_secure_channel_create_import;
 grpc_auth_property_iterator_next_type grpc_auth_property_iterator_next_import;
 grpc_auth_context_property_iterator_type grpc_auth_context_property_iterator_import;
 grpc_auth_context_peer_identity_type grpc_auth_context_peer_identity_import;
@@ -395,6 +396,7 @@
   grpc_header_nonbin_value_is_legal_import = (grpc_header_nonbin_value_is_legal_type) GetProcAddress(library, "grpc_header_nonbin_value_is_legal");
   grpc_is_binary_header_import = (grpc_is_binary_header_type) GetProcAddress(library, "grpc_is_binary_header");
   grpc_call_error_to_string_import = (grpc_call_error_to_string_type) GetProcAddress(library, "grpc_call_error_to_string");
+  grpc_cronet_secure_channel_create_import = (grpc_cronet_secure_channel_create_type) GetProcAddress(library, "grpc_cronet_secure_channel_create");
   grpc_auth_property_iterator_next_import = (grpc_auth_property_iterator_next_type) GetProcAddress(library, "grpc_auth_property_iterator_next");
   grpc_auth_context_property_iterator_import = (grpc_auth_context_property_iterator_type) GetProcAddress(library, "grpc_auth_context_property_iterator");
   grpc_auth_context_peer_identity_import = (grpc_auth_context_peer_identity_type) GetProcAddress(library, "grpc_auth_context_peer_identity");
diff --git a/src/python/grpcio/grpc/_cython/imports.generated.h b/src/python/grpcio/grpc/_cython/imports.generated.h
index d5e810b..54c8aaa 100644
--- a/src/python/grpcio/grpc/_cython/imports.generated.h
+++ b/src/python/grpcio/grpc/_cython/imports.generated.h
@@ -43,6 +43,7 @@
 #include <grpc/census.h>
 #include <grpc/compression.h>
 #include <grpc/grpc.h>
+#include <grpc/grpc_cronet.h>
 #include <grpc/grpc_security.h>
 #include <grpc/impl/codegen/alloc.h>
 #include <grpc/impl/codegen/byte_buffer.h>
@@ -325,6 +326,9 @@
 typedef const char *(*grpc_call_error_to_string_type)(grpc_call_error error);
 extern grpc_call_error_to_string_type grpc_call_error_to_string_import;
 #define grpc_call_error_to_string grpc_call_error_to_string_import
+typedef grpc_channel *(*grpc_cronet_secure_channel_create_type)(void *engine, const char *target, const grpc_channel_args *args, void *reserved);
+extern grpc_cronet_secure_channel_create_type grpc_cronet_secure_channel_create_import;
+#define grpc_cronet_secure_channel_create grpc_cronet_secure_channel_create_import
 typedef const grpc_auth_property *(*grpc_auth_property_iterator_next_type)(grpc_auth_property_iterator *it);
 extern grpc_auth_property_iterator_next_type grpc_auth_property_iterator_next_import;
 #define grpc_auth_property_iterator_next grpc_auth_property_iterator_next_import
diff --git a/src/python/grpcio/grpc/beta/implementations.py b/src/python/grpcio/grpc/beta/implementations.py
index 742e94d..822f593 100644
--- a/src/python/grpcio/grpc/beta/implementations.py
+++ b/src/python/grpcio/grpc/beta/implementations.py
@@ -1,4 +1,4 @@
-# Copyright 2015, Google Inc.
+# Copyright 2015-2016, Google Inc.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -188,12 +188,13 @@
   Args:
     host: The name of the remote host to which to connect.
     port: The port of the remote host to which to connect.
+      If None only the 'host' part will be used.
 
   Returns:
     A Channel to the remote host through which RPCs may be conducted.
   """
   intermediary_low_channel = _intermediary_low.Channel(
-      '%s:%d' % (host, port), None)
+      '%s:%d' % (host, port) if port else host, None)
   return Channel(intermediary_low_channel._internal, intermediary_low_channel)  # pylint: disable=protected-access
 
 
@@ -203,13 +204,15 @@
   Args:
     host: The name of the remote host to which to connect.
     port: The port of the remote host to which to connect.
+      If None only the 'host' part will be used.
     channel_credentials: A ChannelCredentials.
 
   Returns:
     A secure Channel to the remote host through which RPCs may be conducted.
   """
   intermediary_low_channel = _intermediary_low.Channel(
-      '%s:%d' % (host, port), channel_credentials._low_credentials)
+      '%s:%d' % (host, port) if port else host,
+      channel_credentials._low_credentials)
   return Channel(intermediary_low_channel._internal, intermediary_low_channel)  # pylint: disable=protected-access
 
 
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index dab6253..5314329 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -222,6 +222,9 @@
   'src/core/ext/client_config/uri_parser.c',
   'src/core/ext/transport/chttp2/server/insecure/server_chttp2.c',
   'src/core/ext/transport/chttp2/client/insecure/channel_create.c',
+  'src/core/ext/transport/cronet/client/secure/cronet_channel_create.c',
+  'src/core/ext/transport/cronet/transport/cronet_api_dummy.c',
+  'src/core/ext/transport/cronet/transport/cronet_transport.c',
   'src/core/ext/lb_policy/grpclb/load_balancer_api.c',
   'src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.c',
   'third_party/nanopb/pb_common.c',
diff --git a/src/python/grpcio/tests/stress/__init__.py b/src/python/grpcio/tests/stress/__init__.py
new file mode 100644
index 0000000..100a624
--- /dev/null
+++ b/src/python/grpcio/tests/stress/__init__.py
@@ -0,0 +1,28 @@
+# Copyright 2016, 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.
diff --git a/src/python/grpcio/tests/stress/client.py b/src/python/grpcio/tests/stress/client.py
new file mode 100644
index 0000000..a733741
--- /dev/null
+++ b/src/python/grpcio/tests/stress/client.py
@@ -0,0 +1,132 @@
+# Copyright 2016, 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.
+
+"""Entry point for running stress tests."""
+
+import argparse
+import Queue
+import threading
+
+from grpc.beta import implementations
+from src.proto.grpc.testing import metrics_pb2
+from src.proto.grpc.testing import test_pb2
+
+from tests.interop import methods
+from tests.qps import histogram
+from tests.stress import metrics_server
+from tests.stress import test_runner
+
+
+def _args():
+  parser = argparse.ArgumentParser(description='gRPC Python stress test client')
+  parser.add_argument(
+      '--server_addresses',
+      help='comma seperated list of hostname:port to run servers on',
+      default='localhost:8080', type=str)
+  parser.add_argument(
+      '--test_cases',
+      help='comma seperated list of testcase:weighting of tests to run',
+      default='large_unary:100',
+      type=str)
+  parser.add_argument(
+      '--test_duration_secs',
+      help='number of seconds to run the stress test',
+      default=-1, type=int)
+  parser.add_argument(
+      '--num_channels_per_server',
+      help='number of channels per server',
+      default=1, type=int)
+  parser.add_argument(
+      '--num_stubs_per_channel',
+      help='number of stubs to create per channel',
+      default=1, type=int)
+  parser.add_argument(
+      '--metrics_port',
+      help='the port to listen for metrics requests on',
+      default=8081, type=int)
+  return parser.parse_args()
+
+
+def _test_case_from_arg(test_case_arg):
+  for test_case in methods.TestCase:
+    if test_case_arg == test_case.value:
+      return test_case
+  else:
+    raise ValueError('No test case {}!'.format(test_case_arg))
+
+
+def _parse_weighted_test_cases(test_case_args):
+  weighted_test_cases = {}
+  for test_case_arg in test_case_args.split(','):
+    name, weight = test_case_arg.split(':', 1)
+    test_case = _test_case_from_arg(name)
+    weighted_test_cases[test_case] = int(weight)
+  return weighted_test_cases
+
+
+def run_test(args):
+  test_cases = _parse_weighted_test_cases(args.test_cases)
+  test_servers = args.server_addresses.split(',')
+  # Propagate any client exceptions with a queue
+  exception_queue = Queue.Queue()
+  stop_event = threading.Event()
+  hist = histogram.Histogram(1, 1)
+  runners = []
+
+  server = metrics_pb2.beta_create_MetricsService_server(
+      metrics_server.MetricsServer(hist))
+  server.add_insecure_port('[::]:{}'.format(args.metrics_port))
+  server.start()
+
+  for test_server in test_servers:
+    host, port = test_server.split(':', 1)
+    for _ in xrange(args.num_channels_per_server):
+      channel = implementations.insecure_channel(host, int(port))
+      for _ in xrange(args.num_stubs_per_channel):
+        stub = test_pb2.beta_create_TestService_stub(channel)
+        runner = test_runner.TestRunner(stub, test_cases, hist,
+                                        exception_queue, stop_event)
+        runners.append(runner)
+
+  for runner in runners:
+    runner.start()
+  try:
+    raise exception_queue.get(block=True, timeout=args.test_duration_secs)
+  except Queue.Empty:
+    # No exceptions thrown, success
+    pass
+  finally:
+    stop_event.set()
+    for runner in runners:
+      runner.join()
+      runner = None
+    server.stop(0)
+
+if __name__ == '__main__':
+  run_test(_args())
diff --git a/src/python/grpcio/tests/stress/metrics_server.py b/src/python/grpcio/tests/stress/metrics_server.py
new file mode 100644
index 0000000..b994e46
--- /dev/null
+++ b/src/python/grpcio/tests/stress/metrics_server.py
@@ -0,0 +1,60 @@
+# Copyright 2016, 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.
+
+"""MetricsService for publishing stress test qps data."""
+
+import time
+
+from src.proto.grpc.testing import metrics_pb2
+
+GAUGE_NAME = 'python_overall_qps'
+
+
+class MetricsServer(metrics_pb2.BetaMetricsServiceServicer):
+
+  def __init__(self, histogram):
+    self._start_time = time.time()
+    self._histogram = histogram
+
+  def _get_qps(self):
+    count = self._histogram.get_data().count
+    delta = time.time() - self._start_time
+    self._histogram.reset()
+    self._start_time = time.time()
+    return int(count/delta)
+
+  def GetAllGauges(self, request, context):
+    qps = self._get_qps()
+    return [metrics_pb2.GaugeResponse(name=GAUGE_NAME, long_value=qps)]
+
+  def GetGauge(self, request, context):
+    if request.name != GAUGE_NAME:
+      raise Exception('Gauge {} does not exist'.format(request.name))
+    qps = self._get_qps()
+    return metrics_pb2.GaugeResponse(name=GAUGE_NAME, long_value=qps)
diff --git a/src/python/grpcio/tests/stress/test_runner.py b/src/python/grpcio/tests/stress/test_runner.py
new file mode 100644
index 0000000..88f1372
--- /dev/null
+++ b/src/python/grpcio/tests/stress/test_runner.py
@@ -0,0 +1,73 @@
+# Copyright 2016, 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.
+
+"""Thread that sends random weighted requests on a TestService stub."""
+
+import random
+import threading
+import time
+import traceback
+
+
+def _weighted_test_case_generator(weighted_cases):
+  weight_sum = sum(weighted_cases.itervalues())
+
+  while True:
+    val = random.uniform(0, weight_sum)
+    partial_sum = 0
+    for case in weighted_cases:
+      partial_sum += weighted_cases[case]
+      if val <= partial_sum:
+        yield case
+        break
+
+
+class TestRunner(threading.Thread):
+
+  def __init__(self, stub, test_cases, hist, exception_queue, stop_event):
+    super(TestRunner, self).__init__()
+    self._exception_queue = exception_queue
+    self._stop_event = stop_event
+    self._stub = stub
+    self._test_cases = _weighted_test_case_generator(test_cases)
+    self._histogram = hist
+
+  def run(self):
+    while not self._stop_event.is_set():
+      try:
+        test_case = next(self._test_cases)
+        start_time = time.time()
+        test_case.test_interoperability(self._stub, None)
+        end_time = time.time()
+        self._histogram.add((end_time - start_time)*1e9)
+      except Exception as e:
+        traceback.print_exc()
+        self._exception_queue.put(
+            Exception("An exception occured during test {}"
+                      .format(test_case), e))
diff --git a/src/ruby/.rubocop.yml b/src/ruby/.rubocop.yml
index d13ce42..34bb477 100644
--- a/src/ruby/.rubocop.yml
+++ b/src/ruby/.rubocop.yml
@@ -11,10 +11,10 @@
     - 'pb/test/**/*'
 
 Metrics/CyclomaticComplexity:
-  Max: 8
+  Max: 9
 
 Metrics/PerceivedComplexity:
-  Max: 8
+  Max: 9
 
 Metrics/ClassLength:
   Max: 250
diff --git a/src/ruby/ext/grpc/extconf.rb b/src/ruby/ext/grpc/extconf.rb
index 82b6d31..07f7bb9 100644
--- a/src/ruby/ext/grpc/extconf.rb
+++ b/src/ruby/ext/grpc/extconf.rb
@@ -60,35 +60,27 @@
 
 grpc_config = ENV['GRPC_CONFIG'] || 'opt'
 
-if ENV.key?('GRPC_LIB_DIR')
-  grpc_lib_dir = File.join(grpc_root, ENV['GRPC_LIB_DIR'])
-else
-  grpc_lib_dir = File.join(grpc_root, 'libs', grpc_config)
-end
-
 ENV['MACOSX_DEPLOYMENT_TARGET'] = '10.7'
 
-unless File.exist?(File.join(grpc_lib_dir, 'libgrpc.a')) or windows
-  ENV['AR'] = RbConfig::CONFIG['AR'] + ' rcs'
-  ENV['CC'] = RbConfig::CONFIG['CC']
-  ENV['LD'] = ENV['CC']
+ENV['AR'] = RbConfig::CONFIG['AR'] + ' rcs'
+ENV['CC'] = RbConfig::CONFIG['CC']
+ENV['LD'] = ENV['CC']
 
-  ENV['AR'] = 'libtool -o' if RUBY_PLATFORM =~ /darwin/
+ENV['AR'] = 'libtool -o' if RUBY_PLATFORM =~ /darwin/
 
-  ENV['EMBED_OPENSSL'] = 'true'
-  ENV['EMBED_ZLIB'] = 'true'
-  ENV['ARCH_FLAGS'] = RbConfig::CONFIG['ARCH_FLAG']
-  ENV['ARCH_FLAGS'] = '-arch i386 -arch x86_64' if RUBY_PLATFORM =~ /darwin/
-  ENV['CFLAGS'] = '-DGPR_BACKWARDS_COMPATIBILITY_MODE'
+ENV['EMBED_OPENSSL'] = 'true'
+ENV['EMBED_ZLIB'] = 'true'
+ENV['ARCH_FLAGS'] = RbConfig::CONFIG['ARCH_FLAG']
+ENV['ARCH_FLAGS'] = '-arch i386 -arch x86_64' if RUBY_PLATFORM =~ /darwin/
+ENV['CFLAGS'] = '-DGPR_BACKWARDS_COMPATIBILITY_MODE'
 
-  output_dir = File.expand_path(RbConfig::CONFIG['topdir'])
-  grpc_lib_dir = File.join(output_dir, 'libs', grpc_config)
-  ENV['BUILDDIR'] = output_dir
+output_dir = File.expand_path(RbConfig::CONFIG['topdir'])
+grpc_lib_dir = File.join(output_dir, 'libs', grpc_config)
+ENV['BUILDDIR'] = output_dir
 
-  puts 'Building internal gRPC into ' + grpc_lib_dir
-  system("make -j -C #{grpc_root} #{grpc_lib_dir}/libgrpc.a CONFIG=#{grpc_config}")
-  exit 1 unless $? == 0
-end
+puts 'Building internal gRPC into ' + grpc_lib_dir
+system("make -j -C #{grpc_root} #{grpc_lib_dir}/libgrpc.a CONFIG=#{grpc_config}")
+exit 1 unless $? == 0
 
 $CFLAGS << ' -I' + File.join(grpc_root, 'include')
 $LDFLAGS << ' ' + File.join(grpc_lib_dir, 'libgrpc.a') unless windows
diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.c b/src/ruby/ext/grpc/rb_grpc_imports.generated.c
index bc43f9d..cebbe8c 100644
--- a/src/ruby/ext/grpc/rb_grpc_imports.generated.c
+++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.c
@@ -125,6 +125,7 @@
 grpc_header_nonbin_value_is_legal_type grpc_header_nonbin_value_is_legal_import;
 grpc_is_binary_header_type grpc_is_binary_header_import;
 grpc_call_error_to_string_type grpc_call_error_to_string_import;
+grpc_cronet_secure_channel_create_type grpc_cronet_secure_channel_create_import;
 grpc_auth_property_iterator_next_type grpc_auth_property_iterator_next_import;
 grpc_auth_context_property_iterator_type grpc_auth_context_property_iterator_import;
 grpc_auth_context_peer_identity_type grpc_auth_context_peer_identity_import;
@@ -391,6 +392,7 @@
   grpc_header_nonbin_value_is_legal_import = (grpc_header_nonbin_value_is_legal_type) GetProcAddress(library, "grpc_header_nonbin_value_is_legal");
   grpc_is_binary_header_import = (grpc_is_binary_header_type) GetProcAddress(library, "grpc_is_binary_header");
   grpc_call_error_to_string_import = (grpc_call_error_to_string_type) GetProcAddress(library, "grpc_call_error_to_string");
+  grpc_cronet_secure_channel_create_import = (grpc_cronet_secure_channel_create_type) GetProcAddress(library, "grpc_cronet_secure_channel_create");
   grpc_auth_property_iterator_next_import = (grpc_auth_property_iterator_next_type) GetProcAddress(library, "grpc_auth_property_iterator_next");
   grpc_auth_context_property_iterator_import = (grpc_auth_context_property_iterator_type) GetProcAddress(library, "grpc_auth_context_property_iterator");
   grpc_auth_context_peer_identity_import = (grpc_auth_context_peer_identity_type) GetProcAddress(library, "grpc_auth_context_peer_identity");
diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.h b/src/ruby/ext/grpc/rb_grpc_imports.generated.h
index b67361c..d7ea6c5 100644
--- a/src/ruby/ext/grpc/rb_grpc_imports.generated.h
+++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.h
@@ -43,6 +43,7 @@
 #include <grpc/census.h>
 #include <grpc/compression.h>
 #include <grpc/grpc.h>
+#include <grpc/grpc_cronet.h>
 #include <grpc/grpc_security.h>
 #include <grpc/impl/codegen/alloc.h>
 #include <grpc/impl/codegen/byte_buffer.h>
@@ -325,6 +326,9 @@
 typedef const char *(*grpc_call_error_to_string_type)(grpc_call_error error);
 extern grpc_call_error_to_string_type grpc_call_error_to_string_import;
 #define grpc_call_error_to_string grpc_call_error_to_string_import
+typedef grpc_channel *(*grpc_cronet_secure_channel_create_type)(void *engine, const char *target, const grpc_channel_args *args, void *reserved);
+extern grpc_cronet_secure_channel_create_type grpc_cronet_secure_channel_create_import;
+#define grpc_cronet_secure_channel_create grpc_cronet_secure_channel_create_import
 typedef const grpc_auth_property *(*grpc_auth_property_iterator_next_type)(grpc_auth_property_iterator *it);
 extern grpc_auth_property_iterator_next_type grpc_auth_property_iterator_next_import;
 #define grpc_auth_property_iterator_next grpc_auth_property_iterator_next_import
diff --git a/src/ruby/lib/grpc/generic/rpc_server.rb b/src/ruby/lib/grpc/generic/rpc_server.rb
index 7f3a38a..a0f4071 100644
--- a/src/ruby/lib/grpc/generic/rpc_server.rb
+++ b/src/ruby/lib/grpc/generic/rpc_server.rb
@@ -332,15 +332,13 @@
     # the current thread to terminate it.
     def run_till_terminated
       GRPC.trap_signals
-      stopped = false
       t = Thread.new do
         run
-        stopped = true
       end
+      t.abort_on_exception = true
       wait_till_running
-      loop do
+      until running_state == :stopped
         sleep SIGNAL_CHECK_PERIOD
-        break if stopped
         break unless GRPC.handle_signals
       end
       stop
@@ -416,7 +414,7 @@
       GRPC.logger.warn("NOT AVAILABLE: too many jobs_waiting: #{an_rpc}")
       noop = proc { |x| x }
       c = ActiveCall.new(an_rpc.call, @cq, noop, noop, an_rpc.deadline)
-      c.send_status(StatusCodes::RESOURCE_EXHAUSTED, '')
+      c.send_status(GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED, '')
       nil
     end
 
@@ -427,7 +425,7 @@
       GRPC.logger.warn("UNIMPLEMENTED: #{an_rpc}")
       noop = proc { |x| x }
       c = ActiveCall.new(an_rpc.call, @cq, noop, noop, an_rpc.deadline)
-      c.send_status(StatusCodes::UNIMPLEMENTED, '')
+      c.send_status(GRPC::Core::StatusCodes::UNIMPLEMENTED, '')
       nil
     end
 
@@ -443,7 +441,12 @@
           unless active_call.nil?
             @pool.schedule(active_call) do |ac|
               c, mth = ac
-              rpc_descs[mth].run_server_method(c, rpc_handlers[mth])
+              begin
+                rpc_descs[mth].run_server_method(c, rpc_handlers[mth])
+              rescue StandardError
+                c.send_status(GRPC::Core::StatusCodes::INTERNAL,
+                              'Server handler failed')
+              end
             end
           end
         rescue Core::CallError, RuntimeError => e
diff --git a/src/ruby/tools/README.md b/src/ruby/tools/README.md
new file mode 100644
index 0000000..e43f223
--- /dev/null
+++ b/src/ruby/tools/README.md
@@ -0,0 +1,12 @@
+# Ruby gRPC Tools
+
+This package distributes protoc and the Ruby gRPC protoc plugin for Windows, Linux, and Mac.
+
+Before this package is published, the following directories should be filled with the corresponding `protoc` and `grpc_ruby_plugin` executables.
+
+ - `bin/x86-linux`
+ - `bin/x86_64-linux`
+ - `bin/x86-macos`
+ - `bin/x86_64-macos`
+ - `bin/x86-windows`
+ - `bin/x86_64-windows`
diff --git a/src/ruby/tools/bin/protoc.rb b/src/ruby/tools/bin/protoc.rb
new file mode 100755
index 0000000..3a2a5b8
--- /dev/null
+++ b/src/ruby/tools/bin/protoc.rb
@@ -0,0 +1,41 @@
+#!/usr/bin/env ruby
+# Copyright 2016, 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.
+
+require 'rbconfig'
+
+require_relative '../os_check'
+
+protoc_name = 'protoc' + RbConfig::CONFIG['EXEEXT']
+
+protoc_path = File.join(File.dirname(__FILE__),
+                        RbConfig::CONFIG['host_cpu'] + '-' + OS.os_name,
+                        protoc_name)
+
+exec([ protoc_path, protoc_path ], *ARGV)
diff --git a/src/ruby/tools/bin/protoc_grpc_ruby_plugin.rb b/src/ruby/tools/bin/protoc_grpc_ruby_plugin.rb
new file mode 100755
index 0000000..4b296de
--- /dev/null
+++ b/src/ruby/tools/bin/protoc_grpc_ruby_plugin.rb
@@ -0,0 +1,41 @@
+#!/usr/bin/env ruby
+# Copyright 2016, 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.
+
+require 'rbconfig'
+
+require_relative '../os_check'
+
+plugin_name = 'grpc_ruby_plugin' + RbConfig::CONFIG['EXEEXT']
+
+plugin_path = File.join(File.dirname(__FILE__),
+                        RbConfig::CONFIG['host_cpu'] + '-' + OS.os_name,
+                        plugin_name)
+
+exec([ plugin_path, plugin_path ], *ARGV)
diff --git a/src/ruby/tools/grpc-tools.gemspec b/src/ruby/tools/grpc-tools.gemspec
new file mode 100644
index 0000000..af904de
--- /dev/null
+++ b/src/ruby/tools/grpc-tools.gemspec
@@ -0,0 +1,22 @@
+# -*- ruby -*-
+# encoding: utf-8
+require_relative 'version.rb'
+Gem::Specification.new do |s|
+  s.name = 'grpc-tools'
+  s.version = GRPC::Tools::VERSION
+  s.authors = ['grpc Authors']
+  s.email = 'grpc-io@googlegroups.com'
+  s.homepage = 'https://github.com/google/grpc/tree/master/src/ruby/tools'
+  s.summary = 'Development tools for Ruby gRPC'
+  s.description = 'protoc and the Ruby gRPC protoc plugin'
+  s.license = 'BSD-3-Clause'
+
+  s.files = %w( version.rb os_check.rb README.md )
+  s.files += Dir.glob('bin/**/*')
+
+  s.bindir = 'bin'
+
+  s.platform = Gem::Platform::RUBY
+
+  s.executables = %w( protoc.rb protoc_grpc_ruby_plugin.rb )
+end
diff --git a/src/ruby/tools/os_check.rb b/src/ruby/tools/os_check.rb
new file mode 100644
index 0000000..2677306
--- /dev/null
+++ b/src/ruby/tools/os_check.rb
@@ -0,0 +1,45 @@
+# Copyright 2016, 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.
+
+# This is based on http://stackoverflow.com/a/171011/159388 by Aaron Hinni
+
+require 'rbconfig'
+
+module OS
+  def OS.os_name
+    case RbConfig::CONFIG['host_os']
+    when /cygwin|mswin|mingw|bccwin|wince|emx/
+      'windows'
+    when /darwin/
+      'macos'
+    else
+      'linux'
+    end
+  end
+end
diff --git a/src/ruby/tools/version.rb b/src/ruby/tools/version.rb
new file mode 100644
index 0000000..12ad21b
--- /dev/null
+++ b/src/ruby/tools/version.rb
@@ -0,0 +1,34 @@
+# 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.
+
+module GRPC
+  module Tools
+    VERSION = '0.14.0.dev'
+  end
+end
diff --git a/templates/src/ruby/tools/version.rb.template b/templates/src/ruby/tools/version.rb.template
new file mode 100644
index 0000000..dbc5f48
--- /dev/null
+++ b/templates/src/ruby/tools/version.rb.template
@@ -0,0 +1,36 @@
+%YAML 1.2
+--- |
+  # 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.
+
+  module GRPC
+    module Tools
+      VERSION = '${settings.ruby_version.ruby()}'
+    end
+  end
diff --git a/templates/tools/dockerfile/node_deps.include b/templates/tools/dockerfile/node_deps.include
index 7d37d67..7855fbf 100644
--- a/templates/tools/dockerfile/node_deps.include
+++ b/templates/tools/dockerfile/node_deps.include
@@ -4,4 +4,8 @@
 # Install nvm
 RUN touch .profile
 RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.25.4/install.sh | bash
+# Install all versions of node that we want to test
 RUN /bin/bash -l -c "nvm install 0.12 && npm config set cache /tmp/npm-cache"
+RUN /bin/bash -l -c "nvm install 4 && npm config set cache /tmp/npm-cache"
+RUN /bin/bash -l -c "nvm install 5 && npm config set cache /tmp/npm-cache"
+RUN /bin/bash -l -c "nvm alias default 4"
\ No newline at end of file
diff --git a/templates/tools/dockerfile/stress_test/grpc_interop_stress_csharp/Dockerfile.template b/templates/tools/dockerfile/stress_test/grpc_interop_stress_csharp/Dockerfile.template
new file mode 100644
index 0000000..0741782
--- /dev/null
+++ b/templates/tools/dockerfile/stress_test/grpc_interop_stress_csharp/Dockerfile.template
@@ -0,0 +1,41 @@
+%YAML 1.2
+--- |
+  # 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.
+  
+  FROM debian:jessie
+  
+  <%include file="../../apt_get_basic.include"/>
+  <%include file="../../ccache_setup.include"/>
+  <%include file="../../cxx_deps.include"/>
+  <%include file="../../gcp_api_libraries.include"/>
+  <%include file="../../csharp_deps.include"/>
+  # Define the default command.
+  CMD ["bash"]
+  
diff --git a/templates/tools/dockerfile/stress_test/grpc_interop_stress_ruby/Dockerfile.template b/templates/tools/dockerfile/stress_test/grpc_interop_stress_ruby/Dockerfile.template
new file mode 100644
index 0000000..8b933aa
--- /dev/null
+++ b/templates/tools/dockerfile/stress_test/grpc_interop_stress_ruby/Dockerfile.template
@@ -0,0 +1,41 @@
+%YAML 1.2
+--- |
+  # 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.
+  
+  FROM debian:jessie
+  
+  <%include file="../../apt_get_basic.include"/>
+  <%include file="../../ccache_setup.include"/>
+  <%include file="../../cxx_deps.include"/>
+  <%include file="../../gcp_api_libraries.include"/>
+  <%include file="../../ruby_deps.include"/>
+  # Define the default command.
+  CMD ["bash"]
+  
diff --git a/test/core/end2end/fixtures/h2_census.c b/test/core/end2end/fixtures/h2_census.c
index ff2f028..e46b39e 100644
--- a/test/core/end2end/fixtures/h2_census.c
+++ b/test/core/end2end/fixtures/h2_census.c
@@ -111,7 +111,7 @@
 
 /* All test configurations */
 static grpc_end2end_test_config configs[] = {
-    {"chttp2/fullstack", FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION,
+    {"chttp2/fullstack+census", FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION,
      chttp2_create_fixture_fullstack, chttp2_init_client_fullstack,
      chttp2_init_server_fullstack, chttp2_tear_down_fullstack},
 };
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/03a72675e1969f836094f1ecfec2a7b34418e306 b/test/core/end2end/fuzzers/server_fuzzer_corpus/03a72675e1969f836094f1ecfec2a7b34418e306
new file mode 100644
index 0000000..503af15
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/03a72675e1969f836094f1ecfec2a7b34418e306
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/0416afd6875d9ba55f1e5f86a6456a5445d5e576 b/test/core/end2end/fuzzers/server_fuzzer_corpus/0416afd6875d9ba55f1e5f86a6456a5445d5e576
new file mode 100644
index 0000000..30229f9
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/0416afd6875d9ba55f1e5f86a6456a5445d5e576
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/08c42ef29eff83052c5887855f2fa3e07ebe470c b/test/core/end2end/fuzzers/server_fuzzer_corpus/08c42ef29eff83052c5887855f2fa3e07ebe470c
new file mode 100644
index 0000000..828275e
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/08c42ef29eff83052c5887855f2fa3e07ebe470c
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/1ba889ea1543297824e99e641e6ca8b91f45732e b/test/core/end2end/fuzzers/server_fuzzer_corpus/1ba889ea1543297824e99e641e6ca8b91f45732e
new file mode 100644
index 0000000..6ed060d
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/1ba889ea1543297824e99e641e6ca8b91f45732e
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/3b09bf453c6f93983c24c4d5481e55d66213f93a b/test/core/end2end/fuzzers/server_fuzzer_corpus/3b09bf453c6f93983c24c4d5481e55d66213f93a
new file mode 100644
index 0000000..1a7a213
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/3b09bf453c6f93983c24c4d5481e55d66213f93a
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/49cb33cbb60f041e8e99dd718993acd2c3354416 b/test/core/end2end/fuzzers/server_fuzzer_corpus/49cb33cbb60f041e8e99dd718993acd2c3354416
new file mode 100644
index 0000000..7f97525
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/49cb33cbb60f041e8e99dd718993acd2c3354416
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/59743fe120be6ae1aed1c02230ee1bb460f621ee b/test/core/end2end/fuzzers/server_fuzzer_corpus/59743fe120be6ae1aed1c02230ee1bb460f621ee
new file mode 100644
index 0000000..3038fde
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/59743fe120be6ae1aed1c02230ee1bb460f621ee
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/a5ccb8f124d8ddb5350b90bc0d6b96db280cb7c9 b/test/core/end2end/fuzzers/server_fuzzer_corpus/a5ccb8f124d8ddb5350b90bc0d6b96db280cb7c9
new file mode 100644
index 0000000..9d39854
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/a5ccb8f124d8ddb5350b90bc0d6b96db280cb7c9
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/a7fac1265a384fe9e45a9ee3d708b79c4e80505e b/test/core/end2end/fuzzers/server_fuzzer_corpus/a7fac1265a384fe9e45a9ee3d708b79c4e80505e
new file mode 100644
index 0000000..338f61b
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/a7fac1265a384fe9e45a9ee3d708b79c4e80505e
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/aaf049720c707d4e14e47e7eb31d6a2dda60e66a b/test/core/end2end/fuzzers/server_fuzzer_corpus/aaf049720c707d4e14e47e7eb31d6a2dda60e66a
new file mode 100644
index 0000000..dab9c75
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/aaf049720c707d4e14e47e7eb31d6a2dda60e66a
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/c4e4c7572e005e18d56eac407033da058737a5ab b/test/core/end2end/fuzzers/server_fuzzer_corpus/c4e4c7572e005e18d56eac407033da058737a5ab
new file mode 100644
index 0000000..070a581
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/c4e4c7572e005e18d56eac407033da058737a5ab
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/crash-dae0f07934a527989f23f06e630710ff6ca8c809 b/test/core/end2end/fuzzers/server_fuzzer_corpus/crash-dae0f07934a527989f23f06e630710ff6ca8c809
new file mode 100644
index 0000000..b6dfd77
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/crash-dae0f07934a527989f23f06e630710ff6ca8c809
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/e96ad9c17795e52edc810a08d4fc61fe8790002a b/test/core/end2end/fuzzers/server_fuzzer_corpus/e96ad9c17795e52edc810a08d4fc61fe8790002a
new file mode 100644
index 0000000..df9241d
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/e96ad9c17795e52edc810a08d4fc61fe8790002a
Binary files differ
diff --git a/test/core/end2end/fuzzers/server_fuzzer_corpus/fa202a5f51cd49f8ea5af60c5f403f797c01c504 b/test/core/end2end/fuzzers/server_fuzzer_corpus/fa202a5f51cd49f8ea5af60c5f403f797c01c504
new file mode 100644
index 0000000..0ba5935
--- /dev/null
+++ b/test/core/end2end/fuzzers/server_fuzzer_corpus/fa202a5f51cd49f8ea5af60c5f403f797c01c504
Binary files differ
diff --git a/test/core/surface/public_headers_must_be_c89.c b/test/core/surface/public_headers_must_be_c89.c
index 0eede6c..fd6ff2c 100644
--- a/test/core/surface/public_headers_must_be_c89.c
+++ b/test/core/surface/public_headers_must_be_c89.c
@@ -36,11 +36,13 @@
 #include <grpc/census.h>
 #include <grpc/compression.h>
 #include <grpc/grpc.h>
+#include <grpc/grpc_cronet.h>
 #include <grpc/grpc_security.h>
 #include <grpc/grpc_security_constants.h>
 #include <grpc/impl/codegen/alloc.h>
 #include <grpc/impl/codegen/atm.h>
 #include <grpc/impl/codegen/byte_buffer.h>
+#include <grpc/impl/codegen/byte_buffer_reader.h>
 #include <grpc/impl/codegen/compression_types.h>
 #include <grpc/impl/codegen/connectivity_state.h>
 #include <grpc/impl/codegen/grpc_types.h>
diff --git a/test/cpp/end2end/async_end2end_test.cc b/test/cpp/end2end/async_end2end_test.cc
index 7e4d604..0232a9f 100644
--- a/test/cpp/end2end/async_end2end_test.cc
+++ b/test/cpp/end2end/async_end2end_test.cc
@@ -51,6 +51,7 @@
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 #include "test/cpp/util/string_ref_helper.h"
+#include "test/cpp/util/test_credentials_provider.h"
 
 #ifdef GPR_POSIX_SOCKET
 #include "src/core/lib/iomgr/ev_posix.h"
@@ -58,6 +59,7 @@
 
 using grpc::testing::EchoRequest;
 using grpc::testing::EchoResponse;
+using grpc::testing::kTlsCredentialsType;
 using std::chrono::system_clock;
 
 GPR_TLS_DECL(g_is_async_end2end_test);
@@ -197,20 +199,37 @@
   bool spin_;
 };
 
-class AsyncEnd2endTest : public ::testing::TestWithParam<bool> {
+class TestScenario {
+ public:
+  TestScenario(bool non_block, const grpc::string& creds_type,
+               const grpc::string& content)
+      : disable_blocking(non_block),
+        credentials_type(creds_type),
+        message_content(content) {}
+  void Log() const {
+    gpr_log(GPR_INFO,
+            "Scenario: disable_blocking %d, credentials %s, message size %d",
+            disable_blocking, credentials_type.c_str(), message_content.size());
+  }
+  bool disable_blocking;
+  const grpc::string credentials_type;
+  const grpc::string message_content;
+};
+
+class AsyncEnd2endTest : public ::testing::TestWithParam<TestScenario> {
  protected:
-  AsyncEnd2endTest() {}
+  AsyncEnd2endTest() { GetParam().Log(); }
 
   void SetUp() GRPC_OVERRIDE {
-    poll_overrider_.reset(new PollingOverrider(!GetParam()));
+    poll_overrider_.reset(new PollingOverrider(!GetParam().disable_blocking));
 
     int port = grpc_pick_unused_port_or_die();
     server_address_ << "localhost:" << port;
 
     // Setup server
     ServerBuilder builder;
-    builder.AddListeningPort(server_address_.str(),
-                             grpc::InsecureServerCredentials());
+    auto server_creds = GetServerCredentials(GetParam().credentials_type);
+    builder.AddListeningPort(server_address_.str(), server_creds);
     builder.RegisterService(&service_);
     cq_ = builder.AddCompletionQueue();
     server_ = builder.BuildAndStart();
@@ -230,8 +249,11 @@
   }
 
   void ResetStub() {
+    ChannelArguments args;
+    auto channel_creds =
+        GetChannelCredentials(GetParam().credentials_type, &args);
     std::shared_ptr<Channel> channel =
-        CreateChannel(server_address_.str(), InsecureChannelCredentials());
+        CreateCustomChannel(server_address_.str(), channel_creds, args);
     stub_ = grpc::testing::EchoTestService::NewStub(channel);
   }
 
@@ -247,22 +269,23 @@
       ServerContext srv_ctx;
       grpc::ServerAsyncResponseWriter<EchoResponse> response_writer(&srv_ctx);
 
-      send_request.set_message("Hello");
+      send_request.set_message(GetParam().message_content);
       std::unique_ptr<ClientAsyncResponseReader<EchoResponse>> response_reader(
           stub_->AsyncEcho(&cli_ctx, send_request, cq_.get()));
 
       service_.RequestEcho(&srv_ctx, &recv_request, &response_writer, cq_.get(),
                            cq_.get(), tag(2));
 
-      Verifier(GetParam()).Expect(2, true).Verify(cq_.get());
+      Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
       EXPECT_EQ(send_request.message(), recv_request.message());
 
       send_response.set_message(recv_request.message());
       response_writer.Finish(send_response, Status::OK, tag(3));
-      Verifier(GetParam()).Expect(3, true).Verify(cq_.get());
-
       response_reader->Finish(&recv_response, &recv_status, tag(4));
-      Verifier(GetParam()).Expect(4, true).Verify(cq_.get());
+      Verifier(GetParam().disable_blocking)
+          .Expect(3, true)
+          .Expect(4, true)
+          .Verify(cq_.get());
 
       EXPECT_EQ(send_response.message(), recv_response.message());
       EXPECT_TRUE(recv_status.ok());
@@ -302,7 +325,7 @@
   ServerContext srv_ctx;
   grpc::ServerAsyncResponseWriter<EchoResponse> response_writer(&srv_ctx);
 
-  send_request.set_message("Hello");
+  send_request.set_message(GetParam().message_content);
   std::unique_ptr<ClientAsyncResponseReader<EchoResponse>> response_reader(
       stub_->AsyncEcho(&cli_ctx, send_request, cq_.get()));
 
@@ -310,23 +333,22 @@
       std::chrono::system_clock::now());
   std::chrono::system_clock::time_point time_limit(
       std::chrono::system_clock::now() + std::chrono::seconds(10));
-  Verifier(GetParam()).Verify(cq_.get(), time_now);
-  Verifier(GetParam()).Verify(cq_.get(), time_now);
+  Verifier(GetParam().disable_blocking).Verify(cq_.get(), time_now);
+  Verifier(GetParam().disable_blocking).Verify(cq_.get(), time_now);
 
   service_.RequestEcho(&srv_ctx, &recv_request, &response_writer, cq_.get(),
                        cq_.get(), tag(2));
 
-  Verifier(GetParam()).Expect(2, true).Verify(cq_.get(), time_limit);
+  Verifier(GetParam().disable_blocking)
+      .Expect(2, true)
+      .Verify(cq_.get(), time_limit);
   EXPECT_EQ(send_request.message(), recv_request.message());
 
   send_response.set_message(recv_request.message());
   response_writer.Finish(send_response, Status::OK, tag(3));
-  Verifier(GetParam())
-      .Expect(3, true)
-      .Verify(cq_.get(), std::chrono::system_clock::time_point::max());
-
   response_reader->Finish(&recv_response, &recv_status, tag(4));
-  Verifier(GetParam())
+  Verifier(GetParam().disable_blocking)
+      .Expect(3, true)
       .Expect(4, true)
       .Verify(cq_.get(), std::chrono::system_clock::time_point::max());
 
@@ -347,41 +369,48 @@
   ServerContext srv_ctx;
   ServerAsyncReader<EchoResponse, EchoRequest> srv_stream(&srv_ctx);
 
-  send_request.set_message("Hello");
+  send_request.set_message(GetParam().message_content);
   std::unique_ptr<ClientAsyncWriter<EchoRequest>> cli_stream(
       stub_->AsyncRequestStream(&cli_ctx, &recv_response, cq_.get(), tag(1)));
 
   service_.RequestRequestStream(&srv_ctx, &srv_stream, cq_.get(), cq_.get(),
                                 tag(2));
 
-  Verifier(GetParam()).Expect(2, true).Expect(1, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(2, true)
+      .Expect(1, true)
+      .Verify(cq_.get());
 
   cli_stream->Write(send_request, tag(3));
-  Verifier(GetParam()).Expect(3, true).Verify(cq_.get());
-
   srv_stream.Read(&recv_request, tag(4));
-  Verifier(GetParam()).Expect(4, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(3, true)
+      .Expect(4, true)
+      .Verify(cq_.get());
   EXPECT_EQ(send_request.message(), recv_request.message());
 
   cli_stream->Write(send_request, tag(5));
-  Verifier(GetParam()).Expect(5, true).Verify(cq_.get());
-
   srv_stream.Read(&recv_request, tag(6));
-  Verifier(GetParam()).Expect(6, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(5, true)
+      .Expect(6, true)
+      .Verify(cq_.get());
 
   EXPECT_EQ(send_request.message(), recv_request.message());
   cli_stream->WritesDone(tag(7));
-  Verifier(GetParam()).Expect(7, true).Verify(cq_.get());
-
   srv_stream.Read(&recv_request, tag(8));
-  Verifier(GetParam()).Expect(8, false).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(7, true)
+      .Expect(8, false)
+      .Verify(cq_.get());
 
   send_response.set_message(recv_request.message());
   srv_stream.Finish(send_response, Status::OK, tag(9));
-  Verifier(GetParam()).Expect(9, true).Verify(cq_.get());
-
   cli_stream->Finish(&recv_status, tag(10));
-  Verifier(GetParam()).Expect(10, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(9, true)
+      .Expect(10, true)
+      .Verify(cq_.get());
 
   EXPECT_EQ(send_response.message(), recv_response.message());
   EXPECT_TRUE(recv_status.ok());
@@ -400,39 +429,45 @@
   ServerContext srv_ctx;
   ServerAsyncWriter<EchoResponse> srv_stream(&srv_ctx);
 
-  send_request.set_message("Hello");
+  send_request.set_message(GetParam().message_content);
   std::unique_ptr<ClientAsyncReader<EchoResponse>> cli_stream(
       stub_->AsyncResponseStream(&cli_ctx, send_request, cq_.get(), tag(1)));
 
   service_.RequestResponseStream(&srv_ctx, &recv_request, &srv_stream,
                                  cq_.get(), cq_.get(), tag(2));
 
-  Verifier(GetParam()).Expect(1, true).Expect(2, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(1, true)
+      .Expect(2, true)
+      .Verify(cq_.get());
   EXPECT_EQ(send_request.message(), recv_request.message());
 
   send_response.set_message(recv_request.message());
   srv_stream.Write(send_response, tag(3));
-  Verifier(GetParam()).Expect(3, true).Verify(cq_.get());
-
   cli_stream->Read(&recv_response, tag(4));
-  Verifier(GetParam()).Expect(4, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(3, true)
+      .Expect(4, true)
+      .Verify(cq_.get());
   EXPECT_EQ(send_response.message(), recv_response.message());
 
   srv_stream.Write(send_response, tag(5));
-  Verifier(GetParam()).Expect(5, true).Verify(cq_.get());
-
   cli_stream->Read(&recv_response, tag(6));
-  Verifier(GetParam()).Expect(6, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(5, true)
+      .Expect(6, true)
+      .Verify(cq_.get());
   EXPECT_EQ(send_response.message(), recv_response.message());
 
   srv_stream.Finish(Status::OK, tag(7));
-  Verifier(GetParam()).Expect(7, true).Verify(cq_.get());
-
   cli_stream->Read(&recv_response, tag(8));
-  Verifier(GetParam()).Expect(8, false).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(7, true)
+      .Expect(8, false)
+      .Verify(cq_.get());
 
   cli_stream->Finish(&recv_status, tag(9));
-  Verifier(GetParam()).Expect(9, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(9, true).Verify(cq_.get());
 
   EXPECT_TRUE(recv_status.ok());
 }
@@ -450,41 +485,48 @@
   ServerContext srv_ctx;
   ServerAsyncReaderWriter<EchoResponse, EchoRequest> srv_stream(&srv_ctx);
 
-  send_request.set_message("Hello");
+  send_request.set_message(GetParam().message_content);
   std::unique_ptr<ClientAsyncReaderWriter<EchoRequest, EchoResponse>>
       cli_stream(stub_->AsyncBidiStream(&cli_ctx, cq_.get(), tag(1)));
 
   service_.RequestBidiStream(&srv_ctx, &srv_stream, cq_.get(), cq_.get(),
                              tag(2));
 
-  Verifier(GetParam()).Expect(1, true).Expect(2, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(1, true)
+      .Expect(2, true)
+      .Verify(cq_.get());
 
   cli_stream->Write(send_request, tag(3));
-  Verifier(GetParam()).Expect(3, true).Verify(cq_.get());
-
   srv_stream.Read(&recv_request, tag(4));
-  Verifier(GetParam()).Expect(4, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(3, true)
+      .Expect(4, true)
+      .Verify(cq_.get());
   EXPECT_EQ(send_request.message(), recv_request.message());
 
   send_response.set_message(recv_request.message());
   srv_stream.Write(send_response, tag(5));
-  Verifier(GetParam()).Expect(5, true).Verify(cq_.get());
-
   cli_stream->Read(&recv_response, tag(6));
-  Verifier(GetParam()).Expect(6, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(5, true)
+      .Expect(6, true)
+      .Verify(cq_.get());
   EXPECT_EQ(send_response.message(), recv_response.message());
 
   cli_stream->WritesDone(tag(7));
-  Verifier(GetParam()).Expect(7, true).Verify(cq_.get());
-
   srv_stream.Read(&recv_request, tag(8));
-  Verifier(GetParam()).Expect(8, false).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(7, true)
+      .Expect(8, false)
+      .Verify(cq_.get());
 
   srv_stream.Finish(Status::OK, tag(9));
-  Verifier(GetParam()).Expect(9, true).Verify(cq_.get());
-
   cli_stream->Finish(&recv_status, tag(10));
-  Verifier(GetParam()).Expect(10, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(9, true)
+      .Expect(10, true)
+      .Verify(cq_.get());
 
   EXPECT_TRUE(recv_status.ok());
 }
@@ -503,7 +545,7 @@
   ServerContext srv_ctx;
   grpc::ServerAsyncResponseWriter<EchoResponse> response_writer(&srv_ctx);
 
-  send_request.set_message("Hello");
+  send_request.set_message(GetParam().message_content);
   std::pair<grpc::string, grpc::string> meta1("key1", "val1");
   std::pair<grpc::string, grpc::string> meta2("key2", "val2");
   std::pair<grpc::string, grpc::string> meta3("g.r.d-bin", "xyz");
@@ -516,7 +558,7 @@
 
   service_.RequestEcho(&srv_ctx, &recv_request, &response_writer, cq_.get(),
                        cq_.get(), tag(2));
-  Verifier(GetParam()).Expect(2, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
   EXPECT_EQ(send_request.message(), recv_request.message());
   auto client_initial_metadata = srv_ctx.client_metadata();
   EXPECT_EQ(meta1.second,
@@ -529,11 +571,11 @@
 
   send_response.set_message(recv_request.message());
   response_writer.Finish(send_response, Status::OK, tag(3));
-
-  Verifier(GetParam()).Expect(3, true).Verify(cq_.get());
-
   response_reader->Finish(&recv_response, &recv_status, tag(4));
-  Verifier(GetParam()).Expect(4, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(3, true)
+      .Expect(4, true)
+      .Verify(cq_.get());
 
   EXPECT_EQ(send_response.message(), recv_response.message());
   EXPECT_TRUE(recv_status.ok());
@@ -552,7 +594,7 @@
   ServerContext srv_ctx;
   grpc::ServerAsyncResponseWriter<EchoResponse> response_writer(&srv_ctx);
 
-  send_request.set_message("Hello");
+  send_request.set_message(GetParam().message_content);
   std::pair<grpc::string, grpc::string> meta1("key1", "val1");
   std::pair<grpc::string, grpc::string> meta2("key2", "val2");
 
@@ -561,15 +603,15 @@
 
   service_.RequestEcho(&srv_ctx, &recv_request, &response_writer, cq_.get(),
                        cq_.get(), tag(2));
-  Verifier(GetParam()).Expect(2, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
   EXPECT_EQ(send_request.message(), recv_request.message());
   srv_ctx.AddInitialMetadata(meta1.first, meta1.second);
   srv_ctx.AddInitialMetadata(meta2.first, meta2.second);
   response_writer.SendInitialMetadata(tag(3));
-  Verifier(GetParam()).Expect(3, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(3, true).Verify(cq_.get());
 
   response_reader->ReadInitialMetadata(tag(4));
-  Verifier(GetParam()).Expect(4, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(4, true).Verify(cq_.get());
   auto server_initial_metadata = cli_ctx.GetServerInitialMetadata();
   EXPECT_EQ(meta1.second,
             ToString(server_initial_metadata.find(meta1.first)->second));
@@ -579,10 +621,11 @@
 
   send_response.set_message(recv_request.message());
   response_writer.Finish(send_response, Status::OK, tag(5));
-  Verifier(GetParam()).Expect(5, true).Verify(cq_.get());
-
   response_reader->Finish(&recv_response, &recv_status, tag(6));
-  Verifier(GetParam()).Expect(6, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(5, true)
+      .Expect(6, true)
+      .Verify(cq_.get());
 
   EXPECT_EQ(send_response.message(), recv_response.message());
   EXPECT_TRUE(recv_status.ok());
@@ -601,7 +644,7 @@
   ServerContext srv_ctx;
   grpc::ServerAsyncResponseWriter<EchoResponse> response_writer(&srv_ctx);
 
-  send_request.set_message("Hello");
+  send_request.set_message(GetParam().message_content);
   std::pair<grpc::string, grpc::string> meta1("key1", "val1");
   std::pair<grpc::string, grpc::string> meta2("key2", "val2");
 
@@ -610,20 +653,22 @@
 
   service_.RequestEcho(&srv_ctx, &recv_request, &response_writer, cq_.get(),
                        cq_.get(), tag(2));
-  Verifier(GetParam()).Expect(2, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
   EXPECT_EQ(send_request.message(), recv_request.message());
   response_writer.SendInitialMetadata(tag(3));
-  Verifier(GetParam()).Expect(3, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(3, true).Verify(cq_.get());
 
   send_response.set_message(recv_request.message());
   srv_ctx.AddTrailingMetadata(meta1.first, meta1.second);
   srv_ctx.AddTrailingMetadata(meta2.first, meta2.second);
   response_writer.Finish(send_response, Status::OK, tag(4));
-
-  Verifier(GetParam()).Expect(4, true).Verify(cq_.get());
-
   response_reader->Finish(&recv_response, &recv_status, tag(5));
-  Verifier(GetParam()).Expect(5, true).Verify(cq_.get());
+
+  Verifier(GetParam().disable_blocking)
+      .Expect(4, true)
+      .Expect(5, true)
+      .Verify(cq_.get());
+
   EXPECT_EQ(send_response.message(), recv_response.message());
   EXPECT_TRUE(recv_status.ok());
   auto server_trailing_metadata = cli_ctx.GetServerTrailingMetadata();
@@ -647,7 +692,7 @@
   ServerContext srv_ctx;
   grpc::ServerAsyncResponseWriter<EchoResponse> response_writer(&srv_ctx);
 
-  send_request.set_message("Hello");
+  send_request.set_message(GetParam().message_content);
   std::pair<grpc::string, grpc::string> meta1("key1", "val1");
   std::pair<grpc::string, grpc::string> meta2(
       "key2-bin",
@@ -671,7 +716,7 @@
 
   service_.RequestEcho(&srv_ctx, &recv_request, &response_writer, cq_.get(),
                        cq_.get(), tag(2));
-  Verifier(GetParam()).Expect(2, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
   EXPECT_EQ(send_request.message(), recv_request.message());
   auto client_initial_metadata = srv_ctx.client_metadata();
   EXPECT_EQ(meta1.second,
@@ -683,9 +728,9 @@
   srv_ctx.AddInitialMetadata(meta3.first, meta3.second);
   srv_ctx.AddInitialMetadata(meta4.first, meta4.second);
   response_writer.SendInitialMetadata(tag(3));
-  Verifier(GetParam()).Expect(3, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(3, true).Verify(cq_.get());
   response_reader->ReadInitialMetadata(tag(4));
-  Verifier(GetParam()).Expect(4, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(4, true).Verify(cq_.get());
   auto server_initial_metadata = cli_ctx.GetServerInitialMetadata();
   EXPECT_EQ(meta3.second,
             ToString(server_initial_metadata.find(meta3.first)->second));
@@ -697,11 +742,13 @@
   srv_ctx.AddTrailingMetadata(meta5.first, meta5.second);
   srv_ctx.AddTrailingMetadata(meta6.first, meta6.second);
   response_writer.Finish(send_response, Status::OK, tag(5));
-
-  Verifier(GetParam()).Expect(5, true).Verify(cq_.get());
-
   response_reader->Finish(&recv_response, &recv_status, tag(6));
-  Verifier(GetParam()).Expect(6, true).Verify(cq_.get());
+
+  Verifier(GetParam().disable_blocking)
+      .Expect(5, true)
+      .Expect(6, true)
+      .Verify(cq_.get());
+
   EXPECT_EQ(send_response.message(), recv_response.message());
   EXPECT_TRUE(recv_status.ok());
   auto server_trailing_metadata = cli_ctx.GetServerTrailingMetadata();
@@ -726,7 +773,7 @@
   ServerContext srv_ctx;
   grpc::ServerAsyncResponseWriter<EchoResponse> response_writer(&srv_ctx);
 
-  send_request.set_message("Hello");
+  send_request.set_message(GetParam().message_content);
   std::unique_ptr<ClientAsyncResponseReader<EchoResponse>> response_reader(
       stub_->AsyncEcho(&cli_ctx, send_request, cq_.get()));
 
@@ -734,15 +781,15 @@
   service_.RequestEcho(&srv_ctx, &recv_request, &response_writer, cq_.get(),
                        cq_.get(), tag(2));
 
-  Verifier(GetParam()).Expect(2, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
   EXPECT_EQ(send_request.message(), recv_request.message());
 
   cli_ctx.TryCancel();
-  Verifier(GetParam()).Expect(5, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(5, true).Verify(cq_.get());
   EXPECT_TRUE(srv_ctx.IsCancelled());
 
   response_reader->Finish(&recv_response, &recv_status, tag(4));
-  Verifier(GetParam()).Expect(4, false).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(4, false).Verify(cq_.get());
 
   EXPECT_EQ(StatusCode::CANCELLED, recv_status.error_code());
 }
@@ -761,7 +808,7 @@
   ServerContext srv_ctx;
   grpc::ServerAsyncResponseWriter<EchoResponse> response_writer(&srv_ctx);
 
-  send_request.set_message("Hello");
+  send_request.set_message(GetParam().message_content);
   std::unique_ptr<ClientAsyncResponseReader<EchoResponse>> response_reader(
       stub_->AsyncEcho(&cli_ctx, send_request, cq_.get()));
 
@@ -769,25 +816,29 @@
   service_.RequestEcho(&srv_ctx, &recv_request, &response_writer, cq_.get(),
                        cq_.get(), tag(2));
 
-  Verifier(GetParam()).Expect(2, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
   EXPECT_EQ(send_request.message(), recv_request.message());
 
   send_response.set_message(recv_request.message());
   response_writer.Finish(send_response, Status::OK, tag(3));
-  Verifier(GetParam()).Expect(3, true).Verify(cq_.get());
-  Verifier(GetParam()).Expect(5, true).Verify(cq_.get());
-  EXPECT_FALSE(srv_ctx.IsCancelled());
-
   response_reader->Finish(&recv_response, &recv_status, tag(4));
-  Verifier(GetParam()).Expect(4, true).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking)
+      .Expect(3, true)
+      .Expect(4, true)
+      .Expect(5, true)
+      .Verify(cq_.get());
+  EXPECT_FALSE(srv_ctx.IsCancelled());
 
   EXPECT_EQ(send_response.message(), recv_response.message());
   EXPECT_TRUE(recv_status.ok());
 }
 
 TEST_P(AsyncEnd2endTest, UnimplementedRpc) {
+  ChannelArguments args;
+  auto channel_creds =
+      GetChannelCredentials(GetParam().credentials_type, &args);
   std::shared_ptr<Channel> channel =
-      CreateChannel(server_address_.str(), InsecureChannelCredentials());
+      CreateCustomChannel(server_address_.str(), channel_creds, args);
   std::unique_ptr<grpc::testing::UnimplementedService::Stub> stub;
   stub = grpc::testing::UnimplementedService::NewStub(channel);
   EchoRequest send_request;
@@ -795,12 +846,12 @@
   Status recv_status;
 
   ClientContext cli_ctx;
-  send_request.set_message("Hello");
+  send_request.set_message(GetParam().message_content);
   std::unique_ptr<ClientAsyncResponseReader<EchoResponse>> response_reader(
       stub->AsyncUnimplemented(&cli_ctx, send_request, cq_.get()));
 
   response_reader->Finish(&recv_response, &recv_status, tag(4));
-  Verifier(GetParam()).Expect(4, false).Verify(cq_.get());
+  Verifier(GetParam().disable_blocking).Expect(4, false).Verify(cq_.get());
 
   EXPECT_EQ(StatusCode::UNIMPLEMENTED, recv_status.error_code());
   EXPECT_EQ("", recv_status.error_message());
@@ -847,23 +898,25 @@
     // Initiate the 'RequestStream' call on client
     std::unique_ptr<ClientAsyncWriter<EchoRequest>> cli_stream(
         stub_->AsyncRequestStream(&cli_ctx, &recv_response, cq_.get(), tag(1)));
-    Verifier(GetParam()).Expect(1, true).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(1, true).Verify(cq_.get());
 
     // On the server, request to be notified of 'RequestStream' calls
     // and receive the 'RequestStream' call just made by the client
     srv_ctx.AsyncNotifyWhenDone(tag(11));
     service_.RequestRequestStream(&srv_ctx, &srv_stream, cq_.get(), cq_.get(),
                                   tag(2));
-    Verifier(GetParam()).Expect(2, true).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
 
     // Client sends 3 messages (tags 3, 4 and 5)
     for (int tag_idx = 3; tag_idx <= 5; tag_idx++) {
       send_request.set_message("Ping " + std::to_string(tag_idx));
       cli_stream->Write(send_request, tag(tag_idx));
-      Verifier(GetParam()).Expect(tag_idx, true).Verify(cq_.get());
+      Verifier(GetParam().disable_blocking)
+          .Expect(tag_idx, true)
+          .Verify(cq_.get());
     }
     cli_stream->WritesDone(tag(6));
-    Verifier(GetParam()).Expect(6, true).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(6, true).Verify(cq_.get());
 
     bool expected_server_cq_result = true;
     bool ignore_cq_result = false;
@@ -871,7 +924,7 @@
 
     if (server_try_cancel == CANCEL_BEFORE_PROCESSING) {
       srv_ctx.TryCancel();
-      Verifier(GetParam()).Expect(11, true).Verify(cq_.get());
+      Verifier(GetParam().disable_blocking).Expect(11, true).Verify(cq_.get());
       EXPECT_TRUE(srv_ctx.IsCancelled());
 
       // Since cancellation is done before server reads any results, we know
@@ -881,7 +934,7 @@
 
     std::thread* server_try_cancel_thd = NULL;
 
-    auto verif = Verifier(GetParam());
+    auto verif = Verifier(GetParam().disable_blocking);
 
     if (server_try_cancel == CANCEL_DURING_PROCESSING) {
       server_try_cancel_thd =
@@ -939,13 +992,13 @@
     // Server sends the final message and cancelled status (but the RPC is
     // already cancelled at this point. So we expect the operation to fail)
     srv_stream.Finish(send_response, Status::CANCELLED, tag(9));
-    Verifier(GetParam()).Expect(9, false).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(9, false).Verify(cq_.get());
 
     // Client will see the cancellation
     cli_stream->Finish(&recv_status, tag(10));
     // TODO(sreek): The expectation here should be true. This is a bug (github
     // issue #4972)
-    Verifier(GetParam()).Expect(10, false).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(10, false).Verify(cq_.get());
     EXPECT_FALSE(recv_status.ok());
     EXPECT_EQ(::grpc::StatusCode::CANCELLED, recv_status.error_code());
   }
@@ -979,13 +1032,13 @@
     // Initiate the 'ResponseStream' call on the client
     std::unique_ptr<ClientAsyncReader<EchoResponse>> cli_stream(
         stub_->AsyncResponseStream(&cli_ctx, send_request, cq_.get(), tag(1)));
-    Verifier(GetParam()).Expect(1, true).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(1, true).Verify(cq_.get());
     // On the server, request to be notified of 'ResponseStream' calls and
     // receive the call just made by the client
     srv_ctx.AsyncNotifyWhenDone(tag(11));
     service_.RequestResponseStream(&srv_ctx, &recv_request, &srv_stream,
                                    cq_.get(), cq_.get(), tag(2));
-    Verifier(GetParam()).Expect(2, true).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
     EXPECT_EQ(send_request.message(), recv_request.message());
 
     bool expected_cq_result = true;
@@ -994,7 +1047,7 @@
 
     if (server_try_cancel == CANCEL_BEFORE_PROCESSING) {
       srv_ctx.TryCancel();
-      Verifier(GetParam()).Expect(11, true).Verify(cq_.get());
+      Verifier(GetParam().disable_blocking).Expect(11, true).Verify(cq_.get());
       EXPECT_TRUE(srv_ctx.IsCancelled());
 
       // We know for sure that all cq results will be false from this point
@@ -1004,7 +1057,7 @@
 
     std::thread* server_try_cancel_thd = NULL;
 
-    auto verif = Verifier(GetParam());
+    auto verif = Verifier(GetParam().disable_blocking);
 
     if (server_try_cancel == CANCEL_DURING_PROCESSING) {
       server_try_cancel_thd =
@@ -1064,7 +1117,7 @@
     // Client attemts to read the three messages from the server
     for (int tag_idx = 6; tag_idx <= 8; tag_idx++) {
       cli_stream->Read(&recv_response, tag(tag_idx));
-      Verifier(GetParam())
+      Verifier(GetParam().disable_blocking)
           .Expect(tag_idx, expected_cq_result)
           .Verify(cq_.get(), ignore_cq_result);
     }
@@ -1075,11 +1128,11 @@
 
     // Server finishes the stream (but the RPC is already cancelled)
     srv_stream.Finish(Status::CANCELLED, tag(9));
-    Verifier(GetParam()).Expect(9, false).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(9, false).Verify(cq_.get());
 
     // Client will see the cancellation
     cli_stream->Finish(&recv_status, tag(10));
-    Verifier(GetParam()).Expect(10, true).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(10, true).Verify(cq_.get());
     EXPECT_FALSE(recv_status.ok());
     EXPECT_EQ(::grpc::StatusCode::CANCELLED, recv_status.error_code());
   }
@@ -1114,19 +1167,19 @@
     // Initiate the call from the client side
     std::unique_ptr<ClientAsyncReaderWriter<EchoRequest, EchoResponse>>
         cli_stream(stub_->AsyncBidiStream(&cli_ctx, cq_.get(), tag(1)));
-    Verifier(GetParam()).Expect(1, true).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(1, true).Verify(cq_.get());
 
     // On the server, request to be notified of the 'BidiStream' call and
     // receive the call just made by the client
     srv_ctx.AsyncNotifyWhenDone(tag(11));
     service_.RequestBidiStream(&srv_ctx, &srv_stream, cq_.get(), cq_.get(),
                                tag(2));
-    Verifier(GetParam()).Expect(2, true).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
 
     // Client sends the first and the only message
     send_request.set_message("Ping");
     cli_stream->Write(send_request, tag(3));
-    Verifier(GetParam()).Expect(3, true).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(3, true).Verify(cq_.get());
 
     bool expected_cq_result = true;
     bool ignore_cq_result = false;
@@ -1134,7 +1187,7 @@
 
     if (server_try_cancel == CANCEL_BEFORE_PROCESSING) {
       srv_ctx.TryCancel();
-      Verifier(GetParam()).Expect(11, true).Verify(cq_.get());
+      Verifier(GetParam().disable_blocking).Expect(11, true).Verify(cq_.get());
       EXPECT_TRUE(srv_ctx.IsCancelled());
 
       // We know for sure that all cq results will be false from this point
@@ -1144,7 +1197,7 @@
 
     std::thread* server_try_cancel_thd = NULL;
 
-    auto verif = Verifier(GetParam());
+    auto verif = Verifier(GetParam().disable_blocking);
 
     if (server_try_cancel == CANCEL_DURING_PROCESSING) {
       server_try_cancel_thd =
@@ -1244,10 +1297,10 @@
     // know that cq results are supposed to return false on server.
 
     srv_stream.Finish(Status::CANCELLED, tag(9));
-    Verifier(GetParam()).Expect(9, false).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(9, false).Verify(cq_.get());
 
     cli_stream->Finish(&recv_status, tag(10));
-    Verifier(GetParam()).Expect(10, true).Verify(cq_.get());
+    Verifier(GetParam().disable_blocking).Expect(10, true).Verify(cq_.get());
     EXPECT_FALSE(recv_status.ok());
     EXPECT_EQ(grpc::StatusCode::CANCELLED, recv_status.error_code());
   }
@@ -1289,11 +1342,48 @@
   TestBidiStreamingServerCancel(CANCEL_AFTER_PROCESSING);
 }
 
+std::vector<TestScenario> CreateTestScenarios(bool test_disable_blocking,
+                                              bool test_secure,
+                                              int test_big_limit) {
+  std::vector<TestScenario> scenarios;
+  std::vector<grpc::string> credentials_types;
+  std::vector<grpc::string> messages;
+
+  credentials_types.push_back(kInsecureCredentialsType);
+  auto sec_list = GetSecureCredentialsTypeList();
+  for (auto sec = sec_list.begin(); sec != sec_list.end(); sec++) {
+    credentials_types.push_back(*sec);
+  }
+
+  messages.push_back("Hello");
+  for (int sz = 1; sz < test_big_limit; sz *= 2) {
+    grpc::string big_msg;
+    for (int i = 0; i < sz * 1024; i++) {
+      char c = 'a' + (i % 26);
+      big_msg += c;
+    }
+    messages.push_back(big_msg);
+  }
+
+  for (auto cred = credentials_types.begin(); cred != credentials_types.end();
+       ++cred) {
+    for (auto msg = messages.begin(); msg != messages.end(); msg++) {
+      scenarios.push_back(TestScenario(false, *cred, *msg));
+      if (test_disable_blocking) {
+        scenarios.push_back(TestScenario(true, *cred, *msg));
+      }
+    }
+  }
+  return scenarios;
+}
+
 INSTANTIATE_TEST_CASE_P(AsyncEnd2end, AsyncEnd2endTest,
-                        ::testing::Values(false, true));
+                        ::testing::ValuesIn(CreateTestScenarios(true, true,
+                                                                1024)));
 INSTANTIATE_TEST_CASE_P(AsyncEnd2endServerTryCancel,
                         AsyncEnd2endServerTryCancelTest,
-                        ::testing::Values(false));
+                        ::testing::ValuesIn(CreateTestScenarios(false, false,
+                                                                0)));
 
 }  // namespace
 }  // namespace testing
diff --git a/test/cpp/qps/driver.cc b/test/cpp/qps/driver.cc
index 2583ceb..04b2b45 100644
--- a/test/cpp/qps/driver.cc
+++ b/test/cpp/qps/driver.cc
@@ -83,6 +83,7 @@
       auto stub = WorkerService::NewStub(
           CreateChannel(*it, InsecureChannelCredentials()));
       grpc::ClientContext ctx;
+      ctx.set_fail_fast(false);
       CoreRequest dummy;
       CoreResponse cores;
       grpc::Status s = stub->CoreCount(&ctx, dummy, &cores);
@@ -166,6 +167,7 @@
 static ClientContext* AllocContext(list<ClientContext>* contexts) {
   contexts->emplace_back();
   auto context = &contexts->back();
+  context->set_fail_fast(false);
   return context;
 }
 
@@ -435,6 +437,7 @@
         CreateChannel(workers[i], InsecureChannelCredentials()));
     Void dummy;
     grpc::ClientContext ctx;
+    ctx.set_fail_fast(false);
     GPR_ASSERT(stub->QuitWorker(&ctx, dummy, &dummy).ok());
   }
 }
diff --git a/third_party/objective_c/Cronet/cronet_c_for_grpc.h b/third_party/objective_c/Cronet/cronet_c_for_grpc.h
new file mode 100644
index 0000000..15a511a
--- /dev/null
+++ b/third_party/objective_c/Cronet/cronet_c_for_grpc.h
@@ -0,0 +1,202 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CRONET_IOS_CRONET_C_FOR_GRPC_H_
+#define COMPONENTS_CRONET_IOS_CRONET_C_FOR_GRPC_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+
+/* Cronet Engine API. */
+
+/* Opaque object representing Cronet Engine. Created and configured outside
+ * of this API to facilitate sharing with other components */
+typedef struct cronet_engine { void* obj; } cronet_engine;
+
+void cronet_engine_add_quic_hint(cronet_engine* engine,
+                                 const char* host,
+                                 int port,
+                                 int alternate_port);
+
+/* Cronet Bidirectional Stream API */
+
+/* Opaque object representing Cronet Bidirectional Stream. */
+typedef struct cronet_bidirectional_stream {
+  void* obj;
+  void* annotation;
+} cronet_bidirectional_stream;
+
+/* A single request or response header element. */
+typedef struct cronet_bidirectional_stream_header {
+  const char* key;
+  const char* value;
+} cronet_bidirectional_stream_header;
+
+/* Array of request or response headers or trailers. */
+typedef struct cronet_bidirectional_stream_header_array {
+  size_t count;
+  size_t capacity;
+  cronet_bidirectional_stream_header* headers;
+} cronet_bidirectional_stream_header_array;
+
+/* Set of callbacks used to receive callbacks from bidirectional stream. */
+typedef struct cronet_bidirectional_stream_callback {
+  /* Invoked when request headers are sent. Indicates that stream has initiated
+   * the request. Consumer may call cronet_bidirectional_stream_write() to start
+   * writing data.
+   */
+  void (*on_request_headers_sent)(cronet_bidirectional_stream* stream);
+
+  /* Invoked when initial response headers are received.
+   * Consumer must call cronet_bidirectional_stream_read() to start reading.
+   * Consumer may call cronet_bidirectional_stream_write() to start writing or
+   * close the stream. Contents of |headers| is valid for duration of the call.
+   */
+  void (*on_response_headers_received)(
+      cronet_bidirectional_stream* stream,
+      const cronet_bidirectional_stream_header_array* headers,
+      const char* negotiated_protocol);
+
+  /* Invoked when data is read into the buffer passed to
+   * cronet_bidirectional_stream_read(). Only part of the buffer may be
+   * populated. To continue reading, call cronet_bidirectional_stream_read().
+   * It may be invoked after on_response_trailers_received()}, if there was
+   * pending read data before trailers were received.
+   *
+   * If count is 0, it means the remote side has signaled that it will send no
+   * more data; future calls to cronet_bidirectional_stream_read() will result
+   * in the on_data_read() callback or on_succeded() callback if
+   * cronet_bidirectional_stream_write() was invoked with end_of_stream set to
+   * true.
+   */
+  void (*on_read_completed)(cronet_bidirectional_stream* stream,
+                            char* data,
+                            int count);
+
+  /**
+   * Invoked when all data passed to cronet_bidirectional_stream_write() is
+   * sent.
+   * To continue writing, call cronet_bidirectional_stream_write().
+   */
+  void (*on_write_completed)(cronet_bidirectional_stream* stream,
+                             const char* data);
+
+  /* Invoked when trailers are received before closing the stream. Only invoked
+   * when server sends trailers, which it may not. May be invoked while there is
+   * read data remaining in local buffer. Contents of |trailers| is valid for
+   * duration of the call.
+   */
+  void (*on_response_trailers_received)(
+      cronet_bidirectional_stream* stream,
+      const cronet_bidirectional_stream_header_array* trailers);
+
+  /**
+   * Invoked when there is no data to be read or written and the stream is
+   * closed successfully remotely and locally. Once invoked, no further callback
+   * methods will be invoked.
+   */
+  void (*on_succeded)(cronet_bidirectional_stream* stream);
+
+  /**
+   * Invoked if the stream failed for any reason after
+   * cronet_bidirectional_stream_start(). HTTP/2 error codes are
+   * mapped to chrome net error codes. Once invoked, no further callback methods
+   * will be invoked.
+   */
+  void (*on_failed)(cronet_bidirectional_stream* stream, int net_error);
+
+  /**
+   * Invoked if the stream was canceled via
+   * cronet_bidirectional_stream_cancel(). Once invoked, no further callback
+   * methods will be invoked.
+   */
+  void (*on_canceled)(cronet_bidirectional_stream* stream);
+} cronet_bidirectional_stream_callback;
+
+/* Create a new stream object that uses |engine| and |callback|. All stream
+ * tasks are performed asynchronously on the |engine| network thread. |callback|
+ * methods are invoked synchronously on the |engine| network thread, but must
+ * not run tasks on the current thread to prevent blocking networking operations
+ * and causing exceptions during shutdown. The |annotation| is stored in
+ * bidirectional stream for arbitrary use by application.
+ *
+ * Returned |cronet_bidirectional_stream*| is owned by the caller, and must be
+ * destroyed using |cronet_bidirectional_stream_destroy|.
+ *
+ * Both |calback| and |engine| must remain valid until stream is destroyed.
+ */
+cronet_bidirectional_stream* cronet_bidirectional_stream_create(
+    cronet_engine* engine,
+    void* annotation,
+    cronet_bidirectional_stream_callback* callback);
+
+/* TBD: The following methods return int. Should it be a custom type? */
+
+/* Destroy stream object. Destroy could be called from any thread, including
+ * network thread, but is posted, so |stream| is valid until calling task is
+ * complete.
+ */
+int cronet_bidirectional_stream_destroy(cronet_bidirectional_stream* stream);
+
+/* Start the stream by sending request to |url| using |method| and |headers|. If
+ * |end_of_stream| is true, then no data is expected to be written.
+ */
+int cronet_bidirectional_stream_start(
+    cronet_bidirectional_stream* stream,
+    const char* url,
+    int priority,
+    const char* method,
+    const cronet_bidirectional_stream_header_array* headers,
+    bool end_of_stream);
+
+/* Read response data into |buffer| of |capacity| length. Must only be called at
+ * most once in response to each invocation of the
+ * on_response_headers_received() and on_read_completed() methods of the
+ * cronet_bidirectional_stream_callback.
+ * Each call will result in an invocation of one of the callback's
+ * on_read_completed  method if data is read, its on_succeeded() method if
+ * the stream is closed, or its on_failed() method if there's an error.
+ */
+int cronet_bidirectional_stream_read(cronet_bidirectional_stream* stream,
+                                     char* buffer,
+                                     int capacity);
+
+/* Read response data into |buffer| of |capacity| length. Must only be called at
+ * most once in response to each invocation of the
+ * on_response_headers_received() and on_read_completed() methods of the
+ * cronet_bidirectional_stream_callback.
+ * Each call will result in an invocation of one of the callback's
+ * on_read_completed  method if data is read, its on_succeeded() method if
+ * the stream is closed, or its on_failed() method if there's an error.
+ */
+int cronet_bidirectional_stream_write(cronet_bidirectional_stream* stream,
+                                      const char* buffer,
+                                      int count,
+                                      bool end_of_stream);
+
+/* Cancels the stream. Can be called at any time after
+ * cronet_bidirectional_stream_start(). The on_canceled() method of
+ * cronet_bidirectional_stream_callback will be invoked when cancelation
+ * is complete and no further callback methods will be invoked. If the
+ * stream has completed or has not started, calling
+ * cronet_bidirectional_stream_cancel() has no effect and on_canceled() will not
+ * be  invoked. At most one callback method may be invoked after
+ * cronet_bidirectional_stream_cancel() has completed.
+ */
+int cronet_bidirectional_stream_cancel(cronet_bidirectional_stream* stream);
+
+/* Returns true if the |stream| was successfully started and is now done
+ * (succeeded, canceled, or failed).
+ * Returns false if the |stream| stream is not yet started or is in progress.
+ */
+bool cronet_bidirectional_stream_is_done(cronet_bidirectional_stream* stream);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // COMPONENTS_CRONET_IOS_CRONET_C_FOR_GRPC_H_
diff --git a/tools/buildgen/plugins/list_api.py b/tools/buildgen/plugins/list_api.py
index ff937a0..1fc4f41 100755
--- a/tools/buildgen/plugins/list_api.py
+++ b/tools/buildgen/plugins/list_api.py
@@ -64,12 +64,13 @@
 
 def mako_plugin(dictionary):
   apis = []
+  headers = []
 
-#  for lib in dictionary['libs']:
-#    if lib['name'] == 'grpc':
-#      apis.extend(list_c_apis(lib['public_headers']))
-  apis.extend(list_c_apis(sorted(headers_under('include/grpc'))))
+  for lib in dictionary['libs']:
+    if lib['name'] in ['grpc', 'gpr']:
+      headers.extend(lib['public_headers'])
 
+  apis.extend(list_c_apis(sorted(set(headers))))
   dictionary['c_apis'] = apis
 
 
diff --git a/tools/distrib/check_include_guards.py b/tools/distrib/check_include_guards.py
index 897a899..6c160c6 100755
--- a/tools/distrib/check_include_guards.py
+++ b/tools/distrib/check_include_guards.py
@@ -31,6 +31,7 @@
 
 import argparse
 import os
+import os.path
 import re
 import sys
 import subprocess
@@ -187,6 +188,8 @@
 try:
   filename_list = subprocess.check_output(FILE_LIST_COMMAND,
                                           shell=True).splitlines()
+  # Filter out non-existent files (ie, file removed or renamed)
+  filename_list = (f for f in filename_list if os.path.isfile(f))
 except subprocess.CalledProcessError:
   sys.exit(0)
 
diff --git a/tools/dockerfile/interoptest/grpc_interop_node/Dockerfile b/tools/dockerfile/interoptest/grpc_interop_node/Dockerfile
index 64314f8..2a8d35a 100644
--- a/tools/dockerfile/interoptest/grpc_interop_node/Dockerfile
+++ b/tools/dockerfile/interoptest/grpc_interop_node/Dockerfile
@@ -69,8 +69,11 @@
 # Install nvm
 RUN touch .profile
 RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.25.4/install.sh | bash
+# Install all versions of node that we want to test
 RUN /bin/bash -l -c "nvm install 0.12 && npm config set cache /tmp/npm-cache"
-
+RUN /bin/bash -l -c "nvm install 4 && npm config set cache /tmp/npm-cache"
+RUN /bin/bash -l -c "nvm install 5 && npm config set cache /tmp/npm-cache"
+RUN /bin/bash -l -c "nvm alias default 4"
 # Prepare ccache
 RUN ln -s /usr/bin/ccache /usr/local/bin/gcc
 RUN ln -s /usr/bin/ccache /usr/local/bin/g++
diff --git a/tools/dockerfile/interoptest/grpc_interop_node/build_interop.sh b/tools/dockerfile/interoptest/grpc_interop_node/build_interop.sh
index b99fd44..976f55d 100755
--- a/tools/dockerfile/interoptest/grpc_interop_node/build_interop.sh
+++ b/tools/dockerfile/interoptest/grpc_interop_node/build_interop.sh
@@ -38,8 +38,6 @@
 cp -r /var/local/jenkins/service_account $HOME || true
 
 cd /var/local/git/grpc
-nvm use 0.12
-nvm alias default 0.12  # prevent the need to run 'nvm use' in every shell
 
 # build Node interop client & server
 npm install -g node-gyp
diff --git a/tools/dockerfile/stress_test/grpc_interop_stress_csharp/Dockerfile b/tools/dockerfile/stress_test/grpc_interop_stress_csharp/Dockerfile
new file mode 100644
index 0000000..823fe94
--- /dev/null
+++ b/tools/dockerfile/stress_test/grpc_interop_stress_csharp/Dockerfile
@@ -0,0 +1,101 @@
+# 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.
+
+FROM debian:jessie
+
+# Install Git and basic packages.
+RUN apt-get update && apt-get install -y \
+  autoconf \
+  autotools-dev \
+  build-essential \
+  bzip2 \
+  ccache \
+  curl \
+  gcc \
+  gcc-multilib \
+  git \
+  golang \
+  gyp \
+  lcov \
+  libc6 \
+  libc6-dbg \
+  libc6-dev \
+  libgtest-dev \
+  libtool \
+  make \
+  perl \
+  strace \
+  python-dev \
+  python-setuptools \
+  python-yaml \
+  telnet \
+  unzip \
+  wget \
+  zip && apt-get clean
+
+#================
+# Build profiling
+RUN apt-get update && apt-get install -y time && apt-get clean
+
+# Prepare ccache
+RUN ln -s /usr/bin/ccache /usr/local/bin/gcc
+RUN ln -s /usr/bin/ccache /usr/local/bin/g++
+RUN ln -s /usr/bin/ccache /usr/local/bin/cc
+RUN ln -s /usr/bin/ccache /usr/local/bin/c++
+RUN ln -s /usr/bin/ccache /usr/local/bin/clang
+RUN ln -s /usr/bin/ccache /usr/local/bin/clang++
+
+#=================
+# C++ dependencies
+RUN apt-get update && apt-get -y install libgflags-dev libgtest-dev libc++-dev clang && apt-get clean
+
+# Google Cloud platform API libraries
+RUN apt-get update && apt-get install -y python-pip && apt-get clean
+RUN pip install --upgrade google-api-python-client
+
+
+#================
+# C# dependencies
+
+# Update to a newer version of mono
+RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
+RUN echo "deb http://download.mono-project.com/repo/debian wheezy main" | tee /etc/apt/sources.list.d/mono-xamarin.list
+RUN echo "deb http://download.mono-project.com/repo/debian wheezy-apache24-compat main" | tee -a /etc/apt/sources.list.d/mono-xamarin.list
+RUN echo "deb http://download.mono-project.com/repo/debian wheezy-libjpeg62-compat main" | tee -a /etc/apt/sources.list.d/mono-xamarin.list
+RUN echo "deb http://download.mono-project.com/repo/debian wheezy-libtiff-compat main" | tee -a /etc/apt/sources.list.d/mono-xamarin.list
+
+# Install dependencies
+RUN apt-get update && apt-get -y dist-upgrade && apt-get install -y \
+    mono-devel \
+    ca-certificates-mono \
+    nuget \
+    && apt-get clean
+
+# Define the default command.
+CMD ["bash"]
diff --git a/tools/dockerfile/stress_test/grpc_interop_stress_csharp/build_interop_stress.sh b/tools/dockerfile/stress_test/grpc_interop_stress_csharp/build_interop_stress.sh
new file mode 100755
index 0000000..1f4bf89
--- /dev/null
+++ b/tools/dockerfile/stress_test/grpc_interop_stress_csharp/build_interop_stress.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Builds C# interop server and client in a base image.
+set -e
+
+mkdir -p /var/local/git
+git clone --recursive /var/local/jenkins/grpc /var/local/git/grpc
+
+# Copy service account keys if available
+cp -r /var/local/jenkins/service_account $HOME || true
+
+cd /var/local/git/grpc
+
+# Build C++ metrics client (to query the metrics from csharp stress client)
+make metrics_client -j
+
+# Build C# interop client & server
+tools/run_tests/run_tests.py -l csharp -c dbg --build_only
+
diff --git a/tools/dockerfile/stress_test/grpc_interop_stress_node/Dockerfile b/tools/dockerfile/stress_test/grpc_interop_stress_node/Dockerfile
index f70add4..4fd7cc2 100644
--- a/tools/dockerfile/stress_test/grpc_interop_stress_node/Dockerfile
+++ b/tools/dockerfile/stress_test/grpc_interop_stress_node/Dockerfile
@@ -69,8 +69,11 @@
 # Install nvm
 RUN touch .profile
 RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.25.4/install.sh | bash
+# Install all versions of node that we want to test
 RUN /bin/bash -l -c "nvm install 0.12 && npm config set cache /tmp/npm-cache"
-
+RUN /bin/bash -l -c "nvm install 4 && npm config set cache /tmp/npm-cache"
+RUN /bin/bash -l -c "nvm install 5 && npm config set cache /tmp/npm-cache"
+RUN /bin/bash -l -c "nvm alias default 4"
 # Google Cloud platform API libraries
 RUN apt-get update && apt-get install -y python-pip && apt-get clean
 RUN pip install --upgrade google-api-python-client
diff --git a/tools/dockerfile/stress_test/grpc_interop_stress_node/build_interop_stress.sh b/tools/dockerfile/stress_test/grpc_interop_stress_node/build_interop_stress.sh
index b99fd44..976f55d 100755
--- a/tools/dockerfile/stress_test/grpc_interop_stress_node/build_interop_stress.sh
+++ b/tools/dockerfile/stress_test/grpc_interop_stress_node/build_interop_stress.sh
@@ -38,8 +38,6 @@
 cp -r /var/local/jenkins/service_account $HOME || true
 
 cd /var/local/git/grpc
-nvm use 0.12
-nvm alias default 0.12  # prevent the need to run 'nvm use' in every shell
 
 # build Node interop client & server
 npm install -g node-gyp
diff --git a/tools/dockerfile/stress_test/grpc_interop_stress_ruby/Dockerfile b/tools/dockerfile/stress_test/grpc_interop_stress_ruby/Dockerfile
new file mode 100644
index 0000000..36b54dd
--- /dev/null
+++ b/tools/dockerfile/stress_test/grpc_interop_stress_ruby/Dockerfile
@@ -0,0 +1,99 @@
+# 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.
+
+FROM debian:jessie
+
+# Install Git and basic packages.
+RUN apt-get update && apt-get install -y \
+  autoconf \
+  autotools-dev \
+  build-essential \
+  bzip2 \
+  ccache \
+  curl \
+  gcc \
+  gcc-multilib \
+  git \
+  golang \
+  gyp \
+  lcov \
+  libc6 \
+  libc6-dbg \
+  libc6-dev \
+  libgtest-dev \
+  libtool \
+  make \
+  perl \
+  strace \
+  python-dev \
+  python-setuptools \
+  python-yaml \
+  telnet \
+  unzip \
+  wget \
+  zip && apt-get clean
+
+#================
+# Build profiling
+RUN apt-get update && apt-get install -y time && apt-get clean
+
+# Prepare ccache
+RUN ln -s /usr/bin/ccache /usr/local/bin/gcc
+RUN ln -s /usr/bin/ccache /usr/local/bin/g++
+RUN ln -s /usr/bin/ccache /usr/local/bin/cc
+RUN ln -s /usr/bin/ccache /usr/local/bin/c++
+RUN ln -s /usr/bin/ccache /usr/local/bin/clang
+RUN ln -s /usr/bin/ccache /usr/local/bin/clang++
+
+#=================
+# C++ dependencies
+RUN apt-get update && apt-get -y install libgflags-dev libgtest-dev libc++-dev clang && apt-get clean
+
+# Google Cloud platform API libraries
+RUN apt-get update && apt-get install -y python-pip && apt-get clean
+RUN pip install --upgrade google-api-python-client
+
+
+#==================
+# Ruby dependencies
+
+# Install rvm
+RUN gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
+RUN \curl -sSL https://get.rvm.io | bash -s stable
+
+# Install Ruby 2.1
+RUN /bin/bash -l -c "rvm install ruby-2.1"
+RUN /bin/bash -l -c "rvm use --default ruby-2.1"
+RUN /bin/bash -l -c "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
+RUN /bin/bash -l -c "echo 'export PATH=/usr/local/rvm/bin:$PATH' >> ~/.bashrc"
+RUN /bin/bash -l -c "echo 'rvm --default use ruby-2.1' >> ~/.bashrc"
+RUN /bin/bash -l -c "gem install bundler --no-ri --no-rdoc"
+
+# Define the default command.
+CMD ["bash"]
diff --git a/tools/dockerfile/stress_test/grpc_interop_stress_ruby/build_interop_stress.sh b/tools/dockerfile/stress_test/grpc_interop_stress_ruby/build_interop_stress.sh
new file mode 100755
index 0000000..1b7567d
--- /dev/null
+++ b/tools/dockerfile/stress_test/grpc_interop_stress_ruby/build_interop_stress.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Builds Ruby interop server and client in a base image.
+set -e
+
+mkdir -p /var/local/git
+git clone --recursive /var/local/jenkins/grpc /var/local/git/grpc
+
+# Copy service account keys if available
+cp -r /var/local/jenkins/service_account $HOME || true
+
+cd /var/local/git/grpc
+rvm --default use ruby-2.1
+
+# Build Ruby interop client and server
+(cd src/ruby && gem update bundler && bundle && rake compile)
+
+# Build c++ metrics client to query the metrics from ruby stress client
+make metrics_client -j
+
diff --git a/tools/dockerfile/test/multilang_jessie_x64/Dockerfile b/tools/dockerfile/test/multilang_jessie_x64/Dockerfile
index 71ebf2b..5c3f7740 100644
--- a/tools/dockerfile/test/multilang_jessie_x64/Dockerfile
+++ b/tools/dockerfile/test/multilang_jessie_x64/Dockerfile
@@ -90,8 +90,11 @@
 # Install nvm
 RUN touch .profile
 RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.25.4/install.sh | bash
+# Install all versions of node that we want to test
 RUN /bin/bash -l -c "nvm install 0.12 && npm config set cache /tmp/npm-cache"
-
+RUN /bin/bash -l -c "nvm install 4 && npm config set cache /tmp/npm-cache"
+RUN /bin/bash -l -c "nvm install 5 && npm config set cache /tmp/npm-cache"
+RUN /bin/bash -l -c "nvm alias default 4"
 #=================
 # PHP dependencies
 
diff --git a/tools/dockerfile/test/node_jessie_x64/Dockerfile b/tools/dockerfile/test/node_jessie_x64/Dockerfile
index 64314f8..2a8d35a 100644
--- a/tools/dockerfile/test/node_jessie_x64/Dockerfile
+++ b/tools/dockerfile/test/node_jessie_x64/Dockerfile
@@ -69,8 +69,11 @@
 # Install nvm
 RUN touch .profile
 RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.25.4/install.sh | bash
+# Install all versions of node that we want to test
 RUN /bin/bash -l -c "nvm install 0.12 && npm config set cache /tmp/npm-cache"
-
+RUN /bin/bash -l -c "nvm install 4 && npm config set cache /tmp/npm-cache"
+RUN /bin/bash -l -c "nvm install 5 && npm config set cache /tmp/npm-cache"
+RUN /bin/bash -l -c "nvm alias default 4"
 # Prepare ccache
 RUN ln -s /usr/bin/ccache /usr/local/bin/gcc
 RUN ln -s /usr/bin/ccache /usr/local/bin/g++
diff --git a/tools/doxygen/Doxyfile.c++ b/tools/doxygen/Doxyfile.c++
index 7dc0496..664ca03 100644
--- a/tools/doxygen/Doxyfile.c++
+++ b/tools/doxygen/Doxyfile.c++
@@ -833,6 +833,7 @@
 include/grpc++/impl/codegen/sync_stream.h \
 include/grpc++/impl/codegen/time.h \
 include/grpc/impl/codegen/byte_buffer.h \
+include/grpc/impl/codegen/byte_buffer_reader.h \
 include/grpc/impl/codegen/compression_types.h \
 include/grpc/impl/codegen/connectivity_state.h \
 include/grpc/impl/codegen/grpc_types.h \
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index 312fd17..5188ef1 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -833,6 +833,7 @@
 include/grpc++/impl/codegen/sync_stream.h \
 include/grpc++/impl/codegen/time.h \
 include/grpc/impl/codegen/byte_buffer.h \
+include/grpc/impl/codegen/byte_buffer_reader.h \
 include/grpc/impl/codegen/compression_types.h \
 include/grpc/impl/codegen/connectivity_state.h \
 include/grpc/impl/codegen/grpc_types.h \
diff --git a/tools/doxygen/Doxyfile.core b/tools/doxygen/Doxyfile.core
index 034d9c6..84b5c2a 100644
--- a/tools/doxygen/Doxyfile.core
+++ b/tools/doxygen/Doxyfile.core
@@ -766,6 +766,7 @@
 include/grpc/grpc.h \
 include/grpc/status.h \
 include/grpc/impl/codegen/byte_buffer.h \
+include/grpc/impl/codegen/byte_buffer_reader.h \
 include/grpc/impl/codegen/compression_types.h \
 include/grpc/impl/codegen/connectivity_state.h \
 include/grpc/impl/codegen/grpc_types.h \
@@ -785,6 +786,7 @@
 include/grpc/impl/codegen/sync_posix.h \
 include/grpc/impl/codegen/sync_win32.h \
 include/grpc/impl/codegen/time.h \
+include/grpc/grpc_cronet.h \
 include/grpc/grpc_security.h \
 include/grpc/grpc_security_constants.h \
 include/grpc/census.h \
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index 1b1453f..228c1d9 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -766,6 +766,7 @@
 include/grpc/grpc.h \
 include/grpc/status.h \
 include/grpc/impl/codegen/byte_buffer.h \
+include/grpc/impl/codegen/byte_buffer_reader.h \
 include/grpc/impl/codegen/compression_types.h \
 include/grpc/impl/codegen/connectivity_state.h \
 include/grpc/impl/codegen/grpc_types.h \
@@ -785,6 +786,7 @@
 include/grpc/impl/codegen/sync_posix.h \
 include/grpc/impl/codegen/sync_win32.h \
 include/grpc/impl/codegen/time.h \
+include/grpc/grpc_cronet.h \
 include/grpc/grpc_security.h \
 include/grpc/grpc_security_constants.h \
 include/grpc/census.h \
@@ -912,6 +914,42 @@
 src/core/ext/client_config/subchannel_call_holder.h \
 src/core/ext/client_config/subchannel_index.h \
 src/core/ext/client_config/uri_parser.h \
+include/grpc/byte_buffer.h \
+include/grpc/grpc.h \
+include/grpc/impl/codegen/alloc.h \
+include/grpc/impl/codegen/atm.h \
+include/grpc/impl/codegen/atm_gcc_atomic.h \
+include/grpc/impl/codegen/atm_gcc_sync.h \
+include/grpc/impl/codegen/atm_win32.h \
+include/grpc/impl/codegen/byte_buffer.h \
+include/grpc/impl/codegen/compression_types.h \
+include/grpc/impl/codegen/connectivity_state.h \
+include/grpc/impl/codegen/grpc_types.h \
+include/grpc/impl/codegen/log.h \
+include/grpc/impl/codegen/port_platform.h \
+include/grpc/impl/codegen/propagation_bits.h \
+include/grpc/impl/codegen/slice.h \
+include/grpc/impl/codegen/slice_buffer.h \
+include/grpc/impl/codegen/status.h \
+include/grpc/impl/codegen/sync.h \
+include/grpc/impl/codegen/sync_generic.h \
+include/grpc/impl/codegen/sync_posix.h \
+include/grpc/impl/codegen/sync_win32.h \
+include/grpc/impl/codegen/time.h \
+include/grpc/status.h \
+include/grpc/support/alloc.h \
+include/grpc/support/atm.h \
+include/grpc/support/host_port.h \
+include/grpc/support/log.h \
+include/grpc/support/port_platform.h \
+include/grpc/support/slice.h \
+include/grpc/support/slice_buffer.h \
+include/grpc/support/string_util.h \
+include/grpc/support/sync.h \
+include/grpc/support/time.h \
+include/grpc/support/useful.h \
+src/core/lib/support/string.h \
+third_party/objective_c/Cronet/cronet_c_for_grpc.h \
 src/core/ext/lb_policy/grpclb/load_balancer_api.h \
 src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.h \
 third_party/nanopb/pb.h \
@@ -1070,6 +1108,9 @@
 src/core/ext/client_config/uri_parser.c \
 src/core/ext/transport/chttp2/server/insecure/server_chttp2.c \
 src/core/ext/transport/chttp2/client/insecure/channel_create.c \
+src/core/ext/transport/cronet/client/secure/cronet_channel_create.c \
+src/core/ext/transport/cronet/transport/cronet_api_dummy.c \
+src/core/ext/transport/cronet/transport/cronet_transport.c \
 src/core/ext/lb_policy/grpclb/load_balancer_api.c \
 src/core/ext/lb_policy/grpclb/proto/grpc/lb/v0/load_balancer.pb.c \
 third_party/nanopb/pb_common.c \
diff --git a/tools/gce/linux_performance_worker_init.sh b/tools/gce/linux_performance_worker_init.sh
index 25ac3bc..df29581 100755
--- a/tools/gce/linux_performance_worker_init.sh
+++ b/tools/gce/linux_performance_worker_init.sh
@@ -95,6 +95,9 @@
 touch .profile
 curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.25.4/install.sh | bash
 nvm install 0.12 && npm config set cache /tmp/npm-cache
+nvm install 4 && npm config set cache /tmp/npm-cache
+nvm install 5 && npm config set cache /tmp/npm-cache
+nvm alias default 4
 
 # C# dependencies (http://www.mono-project.com/docs/getting-started/install/linux/#debian-ubuntu-and-derivatives)
 
diff --git a/tools/gcp/stress_test/run_ruby.sh b/tools/gcp/stress_test/run_ruby.sh
new file mode 100755
index 0000000..80d0567
--- /dev/null
+++ b/tools/gcp/stress_test/run_ruby.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+# Copyright 2015-2016, 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.
+
+# This is a wrapper script that was created to help run_server.py and
+# run_client.py to launch 'node js' stress clients and stress servers
+source /etc/profile.d/rvm.sh
+
+set -ex
+
+$@
diff --git a/tools/jenkins/README.md b/tools/jenkins/README.md
new file mode 100644
index 0000000..8e06b68
--- /dev/null
+++ b/tools/jenkins/README.md
@@ -0,0 +1 @@
+Scripts invoked by Jenkins (our CI platform) to run gRPC test suites.
diff --git a/tools/jenkins/run_fuzzer.sh b/tools/jenkins/run_fuzzer.sh
index 3f25a93..cfa7ace 100755
--- a/tools/jenkins/run_fuzzer.sh
+++ b/tools/jenkins/run_fuzzer.sh
@@ -33,14 +33,14 @@
 set -ex
 
 export RUN_COMMAND="tools/fuzzer/build_and_run_fuzzer.sh $1"
-export DOCKER_RUN_SCRIPT=tools/jenkins/docker_run.sh
+export DOCKER_RUN_SCRIPT=tools/run_tests/dockerize/docker_run.sh
 export DOCKERFILE_DIR=tools/dockerfile/test/fuzzer
 export OUTPUT_DIR=fuzzer_output
 
 runtime=${runtime:-3600}
 jobs=${jobs:-3}
 
-tools/jenkins/build_and_run_docker.sh \
+tools/run_tests/dockerize/build_and_run_docker.sh \
   -e RUN_COMMAND="$RUN_COMMAND" \
   -e OUTPUT_DIR="$OUTPUT_DIR" \
   -e config="$config" \
diff --git a/tools/run_tests/artifact_targets.py b/tools/run_tests/artifact_targets.py
index e61c46d..3e08c1d 100644
--- a/tools/run_tests/artifact_targets.py
+++ b/tools/run_tests/artifact_targets.py
@@ -43,10 +43,10 @@
   for k,v in environ.iteritems():
     docker_args += ['-e', '%s=%s' % (k, v)]
   docker_env = {'DOCKERFILE_DIR': dockerfile_dir,
-                'DOCKER_RUN_SCRIPT': 'tools/jenkins/docker_run.sh',
+                'DOCKER_RUN_SCRIPT': 'tools/run_tests/dockerize/docker_run.sh',
                 'OUTPUT_DIR': 'artifacts'}
   jobspec = jobset.JobSpec(
-          cmdline=['tools/jenkins/build_and_run_docker.sh'] + docker_args,
+          cmdline=['tools/run_tests/dockerize/build_and_run_docker.sh'] + docker_args,
           environ=docker_env,
           shortname='build_artifact.%s' % (name),
           timeout_seconds=30*60,
diff --git a/tools/run_tests/build_package_ruby.sh b/tools/run_tests/build_package_ruby.sh
index 1a5b943..e44428b 100755
--- a/tools/run_tests/build_package_ruby.sh
+++ b/tools/run_tests/build_package_ruby.sh
@@ -32,6 +32,8 @@
 
 cd $(dirname $0)/../..
 
+base=$(pwd)
+
 mkdir -p artifacts/
 
 # All the ruby packages have been built in the artifact phase already
@@ -41,3 +43,25 @@
 # TODO: all the artifact builder configurations generate a grpc-VERSION.gem
 # source distribution package, and only one of them will end up
 # in the artifacts/ directory. They should be all equivalent though.
+
+for arch in {x86,x64}; do
+  case $arch in
+    x64)
+      ruby_arch=x86_64
+      ;;
+    *)
+      ruby_arch=$arch
+      ;;
+  esac
+  for plat in {windows,linux,macos}; do
+    input_dir="$EXTERNAL_GIT_ROOT/architecture=$arch,language=protoc,platform=$plat/artifacts"
+    output_dir="$base/src/ruby/tools/bin/${ruby_arch}-${plat}"
+    mkdir -p $output_dir
+    cp $input_dir/protoc* $output_dir/
+    cp $input_dir/grpc_ruby_plugin* $output_dir/
+  done
+done
+
+cd $base/src/ruby/tools
+gem build grpc-tools.gemspec
+cp ./grpc-tools*.gem $base/artifacts/
diff --git a/tools/run_tests/distribtest_targets.py b/tools/run_tests/distribtest_targets.py
index 34cc1cd..ae918be 100644
--- a/tools/run_tests/distribtest_targets.py
+++ b/tools/run_tests/distribtest_targets.py
@@ -44,9 +44,9 @@
   for k,v in environ.iteritems():
     docker_args += ['-e', '%s=%s' % (k, v)]
   docker_env = {'DOCKERFILE_DIR': dockerfile_dir,
-                'DOCKER_RUN_SCRIPT': 'tools/jenkins/docker_run.sh'}
+                'DOCKER_RUN_SCRIPT': 'tools/run_tests/dockerize/docker_run.sh'}
   jobspec = jobset.JobSpec(
-          cmdline=['tools/jenkins/build_and_run_docker.sh'] + docker_args,
+          cmdline=['tools/run_tests/dockerize/build_and_run_docker.sh'] + docker_args,
           environ=docker_env,
           shortname='distribtest.%s' % (name),
           timeout_seconds=30*60,
diff --git a/tools/jenkins/build_and_run_docker.sh b/tools/run_tests/dockerize/build_and_run_docker.sh
similarity index 98%
rename from tools/jenkins/build_and_run_docker.sh
rename to tools/run_tests/dockerize/build_and_run_docker.sh
index 92dbbc6..1ef34b2 100755
--- a/tools/jenkins/build_and_run_docker.sh
+++ b/tools/run_tests/dockerize/build_and_run_docker.sh
@@ -33,7 +33,7 @@
 
 set -ex
 
-cd $(dirname $0)/../..
+cd $(dirname $0)/../../..
 git_root=$(pwd)
 cd -
 
diff --git a/tools/jenkins/build_docker_and_run_tests.sh b/tools/run_tests/dockerize/build_docker_and_run_tests.sh
similarity index 98%
rename from tools/jenkins/build_docker_and_run_tests.sh
rename to tools/run_tests/dockerize/build_docker_and_run_tests.sh
index 5779e63..c2ea6f2 100755
--- a/tools/jenkins/build_docker_and_run_tests.sh
+++ b/tools/run_tests/dockerize/build_docker_and_run_tests.sh
@@ -33,7 +33,7 @@
 
 set -ex
 
-cd $(dirname $0)/../..
+cd $(dirname $0)/../../..
 git_root=$(pwd)
 cd -
 
diff --git a/tools/jenkins/build_interop_image.sh b/tools/run_tests/dockerize/build_interop_image.sh
similarity index 98%
rename from tools/jenkins/build_interop_image.sh
rename to tools/run_tests/dockerize/build_interop_image.sh
index d2ba97c..48a216a 100755
--- a/tools/jenkins/build_interop_image.sh
+++ b/tools/run_tests/dockerize/build_interop_image.sh
@@ -40,7 +40,7 @@
 #  BUILD_INTEROP_DOCKER_EXTRA_ARGS - optional args to be passed to the
 #    docker run command
 
-cd `dirname $0`/../..
+cd `dirname $0`/../../..
 GRPC_ROOT=`pwd`
 MOUNT_ARGS="-v $GRPC_ROOT:/var/local/jenkins/grpc:ro"
 
diff --git a/tools/jenkins/build_interop_stress_image.sh b/tools/run_tests/dockerize/build_interop_stress_image.sh
similarity index 98%
rename from tools/jenkins/build_interop_stress_image.sh
rename to tools/run_tests/dockerize/build_interop_stress_image.sh
index 31ffa75..4407c8d 100755
--- a/tools/jenkins/build_interop_stress_image.sh
+++ b/tools/run_tests/dockerize/build_interop_stress_image.sh
@@ -44,7 +44,7 @@
 #  BUILD_INTEROP_DOCKER_EXTRA_ARGS - optional args to be passed to the
 #    docker run command
 
-cd `dirname $0`/../..
+cd `dirname $0`/../../..
 GRPC_ROOT=`pwd`
 MOUNT_ARGS="-v $GRPC_ROOT:/var/local/jenkins/grpc:ro"
 
diff --git a/tools/jenkins/docker_run.sh b/tools/run_tests/dockerize/docker_run.sh
similarity index 100%
rename from tools/jenkins/docker_run.sh
rename to tools/run_tests/dockerize/docker_run.sh
diff --git a/tools/jenkins/docker_run_tests.sh b/tools/run_tests/dockerize/docker_run_tests.sh
similarity index 100%
rename from tools/jenkins/docker_run_tests.sh
rename to tools/run_tests/dockerize/docker_run_tests.sh
diff --git a/tools/run_tests/package_targets.py b/tools/run_tests/package_targets.py
index 87bc486..820b539 100644
--- a/tools/run_tests/package_targets.py
+++ b/tools/run_tests/package_targets.py
@@ -42,10 +42,10 @@
   for k,v in environ.iteritems():
     docker_args += ['-e', '%s=%s' % (k, v)]
   docker_env = {'DOCKERFILE_DIR': dockerfile_dir,
-                'DOCKER_RUN_SCRIPT': 'tools/jenkins/docker_run.sh',
+                'DOCKER_RUN_SCRIPT': 'tools/run_tests/dockerize/docker_run.sh',
                 'OUTPUT_DIR': 'artifacts'}
   jobspec = jobset.JobSpec(
-          cmdline=['tools/jenkins/build_and_run_docker.sh'] + docker_args,
+          cmdline=['tools/run_tests/dockerize/build_and_run_docker.sh'] + docker_args,
           environ=docker_env,
           shortname='build_package.%s' % (name),
           timeout_seconds=30*60,
diff --git a/tools/run_tests/performance/run_worker_node.sh b/tools/run_tests/performance/run_worker_node.sh
index 46b6ff0..9a53a31 100755
--- a/tools/run_tests/performance/run_worker_node.sh
+++ b/tools/run_tests/performance/run_worker_node.sh
@@ -29,7 +29,7 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 source ~/.nvm/nvm.sh
-nvm use 0.12
+nvm use 4
 
 set -ex
 
diff --git a/tools/run_tests/run_interop_tests.py b/tools/run_tests/run_interop_tests.py
index 758be93..e813473 100755
--- a/tools/run_tests/run_interop_tests.py
+++ b/tools/run_tests/run_interop_tests.py
@@ -542,7 +542,7 @@
     env['BUILD_INTEROP_DOCKER_EXTRA_ARGS'] = \
       '-v %s:/root/.composer/auth.json:ro' % host_file
   build_job = jobset.JobSpec(
-          cmdline=['tools/jenkins/build_interop_image.sh'],
+          cmdline=['tools/run_tests/dockerize/build_interop_image.sh'],
           environ=env,
           shortname='build_docker_%s' % (language),
           timeout_seconds=30*60)
diff --git a/tools/run_tests/run_stress_tests.py b/tools/run_tests/run_stress_tests.py
index 0ba8f51..e42ee24 100755
--- a/tools/run_tests/run_stress_tests.py
+++ b/tools/run_tests/run_stress_tests.py
@@ -195,7 +195,7 @@
     tag = 'grpc_interop_stress_%s:%s' % (language.safename, uuid.uuid4())
   env = {'INTEROP_IMAGE': tag,
          'BASE_NAME': 'grpc_interop_stress_%s' % language.safename}
-  build_job = jobset.JobSpec(cmdline=['tools/jenkins/build_interop_stress_image.sh'],
+  build_job = jobset.JobSpec(cmdline=['tools/run_tests/dockerize/build_interop_stress_image.sh'],
                              environ=env,
                              shortname='build_docker_%s' % (language),
                              timeout_seconds=30 * 60)
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index dea481e..37291f4 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -272,12 +272,17 @@
 
   def __init__(self):
     self.platform = platform_string()
-    self.node_version = '0.12'
 
   def configure(self, config, args):
     self.config = config
     self.args = args
-    _check_compiler(self.args.compiler, ['default'])
+    _check_compiler(self.args.compiler, ['default', 'node0.12',
+                                         'node4', 'node5'])
+    if self.args.compiler == 'default':
+      self.node_version = '4'
+    else:
+      # Take off the word "node"
+      self.node_version = self.args.compiler[4:]
 
   def test_specs(self):
     if self.platform == 'windows':
@@ -802,7 +807,8 @@
                            'gcc4.4', 'gcc4.9', 'gcc5.3',
                            'clang3.4', 'clang3.6',
                            'vs2010', 'vs2013', 'vs2015',
-                           'python2.7', 'python3.4'],
+                           'python2.7', 'python3.4',
+                           'node0.12', 'node4', 'node5'],
                   default='default',
                   help='Selects compiler to use. Allowed values depend on the platform and language.')
 argp.add_argument('--build_only',
@@ -906,13 +912,13 @@
   env = os.environ.copy()
   env['RUN_TESTS_COMMAND'] = run_tests_cmd
   env['DOCKERFILE_DIR'] = dockerfile_dir
-  env['DOCKER_RUN_SCRIPT'] = 'tools/jenkins/docker_run_tests.sh'
+  env['DOCKER_RUN_SCRIPT'] = 'tools/run_tests/dockerize/docker_run_tests.sh'
   if args.xml_report:
     env['XML_REPORT'] = args.xml_report
   if not args.travis:
     env['TTY_FLAG'] = '-t'  # enables Ctrl-C when not on Jenkins.
 
-  subprocess.check_call(['tools/jenkins/build_docker_and_run_tests.sh'],
+  subprocess.check_call(['tools/run_tests/dockerize/build_docker_and_run_tests.sh'],
                         shell=True,
                         env=env)
   sys.exit(0)
diff --git a/tools/run_tests/sources_and_headers.json b/tools/run_tests/sources_and_headers.json
index f546f3b..8c67d2f 100644
--- a/tools/run_tests/sources_and_headers.json
+++ b/tools/run_tests/sources_and_headers.json
@@ -4140,7 +4140,8 @@
       "grpc_transport_chttp2_client_insecure", 
       "grpc_transport_chttp2_client_secure", 
       "grpc_transport_chttp2_server_insecure", 
-      "grpc_transport_chttp2_server_secure"
+      "grpc_transport_chttp2_server_secure", 
+      "grpc_transport_cronet_client_secure"
     ], 
     "headers": [], 
     "language": "c", 
@@ -5903,6 +5904,7 @@
     ], 
     "headers": [
       "include/grpc/impl/codegen/byte_buffer.h", 
+      "include/grpc/impl/codegen/byte_buffer_reader.h", 
       "include/grpc/impl/codegen/compression_types.h", 
       "include/grpc/impl/codegen/connectivity_state.h", 
       "include/grpc/impl/codegen/grpc_types.h", 
@@ -5913,6 +5915,7 @@
     "name": "grpc_codegen", 
     "src": [
       "include/grpc/impl/codegen/byte_buffer.h", 
+      "include/grpc/impl/codegen/byte_buffer_reader.h", 
       "include/grpc/impl/codegen/compression_types.h", 
       "include/grpc/impl/codegen/connectivity_state.h", 
       "include/grpc/impl/codegen/grpc_types.h", 
@@ -6012,6 +6015,7 @@
       "tsi"
     ], 
     "headers": [
+      "include/grpc/grpc_cronet.h", 
       "include/grpc/grpc_security.h", 
       "include/grpc/grpc_security_constants.h", 
       "src/core/lib/security/auth_filters.h", 
@@ -6027,6 +6031,7 @@
     "language": "c", 
     "name": "grpc_secure", 
     "src": [
+      "include/grpc/grpc_cronet.h", 
       "include/grpc/grpc_security.h", 
       "include/grpc/grpc_security_constants.h", 
       "src/core/lib/http/httpcli_security_connector.c", 
@@ -6265,6 +6270,121 @@
   {
     "deps": [], 
     "headers": [
+      "include/grpc/byte_buffer.h", 
+      "include/grpc/grpc.h", 
+      "include/grpc/impl/codegen/alloc.h", 
+      "include/grpc/impl/codegen/atm.h", 
+      "include/grpc/impl/codegen/atm_gcc_atomic.h", 
+      "include/grpc/impl/codegen/atm_gcc_sync.h", 
+      "include/grpc/impl/codegen/atm_win32.h", 
+      "include/grpc/impl/codegen/byte_buffer.h", 
+      "include/grpc/impl/codegen/compression_types.h", 
+      "include/grpc/impl/codegen/connectivity_state.h", 
+      "include/grpc/impl/codegen/grpc_types.h", 
+      "include/grpc/impl/codegen/log.h", 
+      "include/grpc/impl/codegen/port_platform.h", 
+      "include/grpc/impl/codegen/propagation_bits.h", 
+      "include/grpc/impl/codegen/slice.h", 
+      "include/grpc/impl/codegen/slice_buffer.h", 
+      "include/grpc/impl/codegen/status.h", 
+      "include/grpc/impl/codegen/sync.h", 
+      "include/grpc/impl/codegen/sync_generic.h", 
+      "include/grpc/impl/codegen/sync_posix.h", 
+      "include/grpc/impl/codegen/sync_win32.h", 
+      "include/grpc/impl/codegen/time.h", 
+      "include/grpc/status.h", 
+      "include/grpc/support/alloc.h", 
+      "include/grpc/support/atm.h", 
+      "include/grpc/support/host_port.h", 
+      "include/grpc/support/log.h", 
+      "include/grpc/support/port_platform.h", 
+      "include/grpc/support/slice.h", 
+      "include/grpc/support/slice_buffer.h", 
+      "include/grpc/support/string_util.h", 
+      "include/grpc/support/sync.h", 
+      "include/grpc/support/time.h", 
+      "include/grpc/support/useful.h", 
+      "src/core/ext/transport/chttp2/transport/incoming_metadata.h", 
+      "src/core/lib/channel/channel_stack.h", 
+      "src/core/lib/channel/context.h", 
+      "src/core/lib/debug/trace.h", 
+      "src/core/lib/iomgr/closure.h", 
+      "src/core/lib/iomgr/exec_ctx.h", 
+      "src/core/lib/iomgr/pollset.h", 
+      "src/core/lib/iomgr/pollset_set.h", 
+      "src/core/lib/support/string.h", 
+      "src/core/lib/surface/channel.h", 
+      "src/core/lib/surface/channel_stack_type.h", 
+      "src/core/lib/transport/byte_stream.h", 
+      "src/core/lib/transport/metadata.h", 
+      "src/core/lib/transport/metadata_batch.h", 
+      "src/core/lib/transport/transport.h", 
+      "src/core/lib/transport/transport_impl.h", 
+      "third_party/objective_c/Cronet/cronet_c_for_grpc.h"
+    ], 
+    "language": "c", 
+    "name": "grpc_transport_cronet_client_secure", 
+    "src": [
+      "include/grpc/byte_buffer.h", 
+      "include/grpc/grpc.h", 
+      "include/grpc/impl/codegen/alloc.h", 
+      "include/grpc/impl/codegen/atm.h", 
+      "include/grpc/impl/codegen/atm_gcc_atomic.h", 
+      "include/grpc/impl/codegen/atm_gcc_sync.h", 
+      "include/grpc/impl/codegen/atm_win32.h", 
+      "include/grpc/impl/codegen/byte_buffer.h", 
+      "include/grpc/impl/codegen/compression_types.h", 
+      "include/grpc/impl/codegen/connectivity_state.h", 
+      "include/grpc/impl/codegen/grpc_types.h", 
+      "include/grpc/impl/codegen/log.h", 
+      "include/grpc/impl/codegen/port_platform.h", 
+      "include/grpc/impl/codegen/propagation_bits.h", 
+      "include/grpc/impl/codegen/slice.h", 
+      "include/grpc/impl/codegen/slice_buffer.h", 
+      "include/grpc/impl/codegen/status.h", 
+      "include/grpc/impl/codegen/sync.h", 
+      "include/grpc/impl/codegen/sync_generic.h", 
+      "include/grpc/impl/codegen/sync_posix.h", 
+      "include/grpc/impl/codegen/sync_win32.h", 
+      "include/grpc/impl/codegen/time.h", 
+      "include/grpc/status.h", 
+      "include/grpc/support/alloc.h", 
+      "include/grpc/support/atm.h", 
+      "include/grpc/support/host_port.h", 
+      "include/grpc/support/log.h", 
+      "include/grpc/support/port_platform.h", 
+      "include/grpc/support/slice.h", 
+      "include/grpc/support/slice_buffer.h", 
+      "include/grpc/support/string_util.h", 
+      "include/grpc/support/sync.h", 
+      "include/grpc/support/time.h", 
+      "include/grpc/support/useful.h", 
+      "src/core/ext/transport/chttp2/transport/incoming_metadata.h", 
+      "src/core/ext/transport/cronet/client/secure/cronet_channel_create.c", 
+      "src/core/ext/transport/cronet/transport/cronet_api_dummy.c", 
+      "src/core/ext/transport/cronet/transport/cronet_transport.c", 
+      "src/core/lib/channel/channel_stack.h", 
+      "src/core/lib/channel/context.h", 
+      "src/core/lib/debug/trace.h", 
+      "src/core/lib/iomgr/closure.h", 
+      "src/core/lib/iomgr/exec_ctx.h", 
+      "src/core/lib/iomgr/pollset.h", 
+      "src/core/lib/iomgr/pollset_set.h", 
+      "src/core/lib/support/string.h", 
+      "src/core/lib/surface/channel.h", 
+      "src/core/lib/surface/channel_stack_type.h", 
+      "src/core/lib/transport/byte_stream.h", 
+      "src/core/lib/transport/metadata.h", 
+      "src/core/lib/transport/metadata_batch.h", 
+      "src/core/lib/transport/transport.h", 
+      "src/core/lib/transport/transport_impl.h"
+    ], 
+    "third_party": false, 
+    "type": "filegroup"
+  }, 
+  {
+    "deps": [], 
+    "headers": [
       "third_party/nanopb/pb.h", 
       "third_party/nanopb/pb_common.h", 
       "third_party/nanopb/pb_decode.h", 
diff --git a/tools/run_tests/stress_test/configs/asan.json b/tools/run_tests/stress_test/configs/asan.json
index cb9f557..7ae11cc 100644
--- a/tools/run_tests/stress_test/configs/asan.json
+++ b/tools/run_tests/stress_test/configs/asan.json
@@ -1,7 +1,7 @@
 {
   "dockerImages": {
     "grpc_stress_cxx_asan" : {
-      "buildScript": "tools/jenkins/build_interop_stress_image.sh",
+      "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
       "dockerFileDir": "grpc_interop_stress_cxx",
       "buildType": "asan"
     }
diff --git a/tools/run_tests/stress_test/configs/csharp.json b/tools/run_tests/stress_test/configs/csharp.json
new file mode 100644
index 0000000..4069495
--- /dev/null
+++ b/tools/run_tests/stress_test/configs/csharp.json
@@ -0,0 +1,90 @@
+{
+  "dockerImages": {
+    "grpc_stress_csharp" : {
+      "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
+      "dockerFileDir": "grpc_interop_stress_csharp"
+    }
+  },
+
+  "clientTemplates": {
+    "baseTemplates": {
+      "default": {
+        "wrapperScriptPath": "/var/local/git/grpc/tools/gcp/stress_test/run_client.py",
+        "pollIntervalSecs": 60,
+        "clientArgs": {
+          "num_channels_per_server":5,
+          "num_stubs_per_channel":10,
+          "test_cases": "empty_unary:1,large_unary:1,client_streaming:1,server_streaming:1,empty_stream:1",
+          "metrics_port": 8081
+        },
+        "metricsPort": 8081,
+        "metricsArgs": {
+          "metrics_server_address": "localhost:8081",
+          "total_only": "true"
+        }
+      }
+    },
+    "templates": {
+      "csharp_client": {
+        "baseTemplate": "default",
+        "stressClientCmd": [
+          "mono",
+          "/var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.StressClient/bin/Debug/Grpc.IntegrationTesting.StressClient.exe"
+		],
+        "metricsClientCmd": ["/var/local/git/grpc/bins/opt/metrics_client"]
+      }
+    }
+  },
+
+  "serverTemplates": {
+    "baseTemplates":{
+      "default": {
+        "wrapperScriptPath": "/var/local/git/grpc/tools/gcp/stress_test/run_server.py",
+        "serverPort": 8080,
+        "serverArgs": {
+          "port": 8080
+        }
+      }
+    },
+    "templates": {
+      "csharp_server": {
+        "baseTemplate": "default",
+        "stressServerCmd": [
+          "mono",
+          "/var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Server/bin/Debug/Grpc.IntegrationTesting.Server.exe"
+		]
+      }
+    }
+  },
+
+  "testMatrix": {
+    "serverPodSpecs": {
+      "stress-server-csharp": {
+        "serverTemplate": "csharp_server",
+        "dockerImage": "grpc_stress_csharp",
+        "numInstances": 1
+      }
+    },
+
+    "clientPodSpecs": {
+      "stress-client-csharp": {
+        "clientTemplate": "csharp_client",
+        "dockerImage": "grpc_stress_csharp",
+        "numInstances": 10,
+        "serverPodSpec": "stress-server-csharp"
+      }
+    }
+  },
+
+  "globalSettings": {
+    "buildDockerImages": true,
+    "pollIntervalSecs": 60,
+    "testDurationSecs": 7200,
+    "kubernetesProxyPort": 8001,
+    "datasetIdNamePrefix": "stress_test_csharp",
+    "summaryTableId": "summary",
+    "qpsTableId": "qps",
+    "podWarmupSecs": 60
+  }
+}
+
diff --git a/tools/run_tests/stress_test/configs/go.json b/tools/run_tests/stress_test/configs/go.json
index 36b465e..f1b2b52 100644
--- a/tools/run_tests/stress_test/configs/go.json
+++ b/tools/run_tests/stress_test/configs/go.json
@@ -1,7 +1,7 @@
 {
   "dockerImages": {
     "grpc_stress_go" : {
-      "buildScript": "tools/jenkins/build_interop_stress_image.sh",
+      "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
       "dockerFileDir": "grpc_interop_stress_go"
     }
   },
diff --git a/tools/run_tests/stress_test/configs/java.json b/tools/run_tests/stress_test/configs/java.json
index 275384c..2ce6c00 100644
--- a/tools/run_tests/stress_test/configs/java.json
+++ b/tools/run_tests/stress_test/configs/java.json
@@ -1,7 +1,7 @@
 {
   "dockerImages": {
     "grpc_stress_java" : {
-      "buildScript": "tools/jenkins/build_interop_stress_image.sh",
+      "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
       "dockerFileDir": "grpc_interop_stress_java"
     }
   },
diff --git a/tools/run_tests/stress_test/configs/node-cxx.json b/tools/run_tests/stress_test/configs/node-cxx.json
index c4245bf..094c123 100644
--- a/tools/run_tests/stress_test/configs/node-cxx.json
+++ b/tools/run_tests/stress_test/configs/node-cxx.json
@@ -1,12 +1,12 @@
 {
   "dockerImages": {
     "grpc_stress_cxx_opt" : {
-      "buildScript": "tools/jenkins/build_interop_stress_image.sh",
+      "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
       "dockerFileDir": "grpc_interop_stress_cxx",
       "buildType": "opt"
     },
    "grpc_stress_node": {
-     "buildScript": "tools/jenkins/build_interop_stress_image.sh",
+     "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
      "dockerFileDir": "grpc_interop_stress_node"
    }
   },
diff --git a/tools/run_tests/stress_test/configs/node.json b/tools/run_tests/stress_test/configs/node.json
index 7a48c56..85eb9e0 100644
--- a/tools/run_tests/stress_test/configs/node.json
+++ b/tools/run_tests/stress_test/configs/node.json
@@ -1,7 +1,7 @@
 {
   "dockerImages": {
     "grpc_stress_node" : {
-      "buildScript": "tools/jenkins/build_interop_stress_image.sh",
+      "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
       "dockerFileDir": "grpc_interop_stress_node"
     }
   },
diff --git a/tools/run_tests/stress_test/configs/opt-tsan-asan.json b/tools/run_tests/stress_test/configs/opt-tsan-asan.json
index 936d151..fcb3678 100644
--- a/tools/run_tests/stress_test/configs/opt-tsan-asan.json
+++ b/tools/run_tests/stress_test/configs/opt-tsan-asan.json
@@ -1,17 +1,17 @@
 {
   "dockerImages": {
     "grpc_stress_cxx_opt" : {
-      "buildScript": "tools/jenkins/build_interop_stress_image.sh",
+      "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
       "dockerFileDir": "grpc_interop_stress_cxx",
       "buildType": "opt"
     },
     "grpc_stress_cxx_tsan": {
-      "buildScript": "tools/jenkins/build_interop_stress_image.sh",
+      "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
       "dockerFileDir": "grpc_interop_stress_cxx",
       "buildType": "tsan"
     },
     "grpc_stress_cxx_asan": {
-      "buildScript": "tools/jenkins/build_interop_stress_image.sh",
+      "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
       "dockerFileDir": "grpc_interop_stress_cxx",
       "buildType": "asan"
     }
diff --git a/tools/run_tests/stress_test/configs/opt.json b/tools/run_tests/stress_test/configs/opt.json
index f45b824..5e0e930 100644
--- a/tools/run_tests/stress_test/configs/opt.json
+++ b/tools/run_tests/stress_test/configs/opt.json
@@ -1,7 +1,7 @@
 {
   "dockerImages": {
     "grpc_stress_cxx_opt" : {
-      "buildScript": "tools/jenkins/build_interop_stress_image.sh",
+      "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
       "dockerFileDir": "grpc_interop_stress_cxx",
       "buildType": "opt"
     }
diff --git a/tools/run_tests/stress_test/configs/ruby.json b/tools/run_tests/stress_test/configs/ruby.json
new file mode 100644
index 0000000..7e2afcb
--- /dev/null
+++ b/tools/run_tests/stress_test/configs/ruby.json
@@ -0,0 +1,92 @@
+{
+  "dockerImages": {
+    "grpc_stress_ruby" : {
+      "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
+      "dockerFileDir": "grpc_interop_stress_ruby"
+    }
+  },
+
+  "clientTemplates": {
+    "baseTemplates": {
+      "default": {
+        "wrapperScriptPath": "/var/local/git/grpc/tools/gcp/stress_test/run_client.py",
+        "pollIntervalSecs": 60,
+        "clientArgs": {
+          "num_channels_per_server":5,
+          "num_stubs_per_channel":10,
+          "test_cases": "empty_unary:1,large_unary:1,client_streaming:1,server_streaming:1,empty_stream:1",
+          "metrics_port": 8081
+        },
+        "metricsPort": 8081,
+        "metricsArgs": {
+          "metrics_server_address": "localhost:8081",
+          "total_only": "true"
+        }
+      }
+    },
+    "templates": {
+      "ruby_client": {
+        "baseTemplate": "default",
+        "stressClientCmd": [
+          "/var/local/git/grpc/tools/gcp/stress_test/run_ruby.sh",
+          "ruby",
+          "/var/local/git/grpc/src/ruby/stress/stress_client.rb"
+        ],
+        "metricsClientCmd": ["/var/local/git/grpc/bins/opt/metrics_client"]
+      }
+    }
+  },
+
+  "serverTemplates": {
+    "baseTemplates":{
+      "default": {
+        "wrapperScriptPath": "/var/local/git/grpc/tools/gcp/stress_test/run_server.py",
+        "serverPort": 8080,
+        "serverArgs": {
+          "port": 8080
+        }
+      }
+    },
+    "templates": {
+      "ruby_server": {
+        "baseTemplate": "default",
+        "stressServerCmd": [
+          "/var/local/git/grpc/tools/gcp/stress_test/run_ruby.sh",
+          "ruby",
+          "/var/local/git/grpc/src/ruby/pb/test/server.rb"
+        ]
+      }
+    }
+  },
+
+  "testMatrix": {
+    "serverPodSpecs": {
+      "stress-server-ruby": {
+        "serverTemplate": "ruby_server",
+        "dockerImage": "grpc_stress_ruby",
+        "numInstances": 1
+      }
+    },
+
+    "clientPodSpecs": {
+      "stress-client-ruby": {
+        "clientTemplate": "ruby_client",
+        "dockerImage": "grpc_stress_ruby",
+        "numInstances": 10,
+        "serverPodSpec": "stress-server-ruby"
+      }
+    }
+  },
+
+  "globalSettings": {
+    "buildDockerImages": true,
+    "pollIntervalSecs": 60,
+    "testDurationSecs": 7200,
+    "kubernetesProxyPort": 8001,
+    "datasetIdNamePrefix": "stress_test_ruby",
+    "summaryTableId": "summary",
+    "qpsTableId": "qps",
+    "podWarmupSecs": 60
+  }
+}
+
diff --git a/tools/run_tests/stress_test/configs/tsan.json b/tools/run_tests/stress_test/configs/tsan.json
index 6ef3bdf..abc759c 100644
--- a/tools/run_tests/stress_test/configs/tsan.json
+++ b/tools/run_tests/stress_test/configs/tsan.json
@@ -1,7 +1,7 @@
 {
   "dockerImages": {
     "grpc_stress_cxx_tsan" : {
-      "buildScript": "tools/jenkins/build_interop_stress_image.sh",
+      "buildScript": "tools/run_tests/dockerize/build_interop_stress_image.sh",
       "dockerFileDir": "grpc_interop_stress_cxx",
       "buildType": "tsan"
     }
diff --git a/tools/run_tests/tests.json b/tools/run_tests/tests.json
index 0fd7785..cf11544 100644
--- a/tools/run_tests/tests.json
+++ b/tools/run_tests/tests.json
@@ -57759,6 +57759,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/03a72675e1969f836094f1ecfec2a7b34418e306"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/03b9be1fa172dff5d1543be079b9c64fa2c9a278"
     ], 
     "ci_platforms": [
@@ -57775,6 +57791,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/0416afd6875d9ba55f1e5f86a6456a5445d5e576"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/052c8f28e5884bb48f0d504461272cd3a5893215"
     ], 
     "ci_platforms": [
@@ -57919,6 +57951,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/08c42ef29eff83052c5887855f2fa3e07ebe470c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/09938e3256d06a8e168eb038d8a58b8462f7f697"
     ], 
     "ci_platforms": [
@@ -58367,6 +58415,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/1ba889ea1543297824e99e641e6ca8b91f45732e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/1cf17783de9e662f3720847f2d83d86dcdcab500"
     ], 
     "ci_platforms": [
@@ -59151,6 +59215,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/3b09bf453c6f93983c24c4d5481e55d66213f93a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/3ca5da2f.bin"
     ], 
     "ci_platforms": [
@@ -59503,6 +59583,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/49cb33cbb60f041e8e99dd718993acd2c3354416"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/4aa883d0.bin"
     ], 
     "ci_platforms": [
@@ -59951,6 +60047,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/59743fe120be6ae1aed1c02230ee1bb460f621ee"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/597fdab5.bin"
     ], 
     "ci_platforms": [
@@ -61343,6 +61455,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/a5ccb8f124d8ddb5350b90bc0d6b96db280cb7c9"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/a7e64803.bin"
     ], 
     "ci_platforms": [
@@ -61359,6 +61487,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/a7fac1265a384fe9e45a9ee3d708b79c4e80505e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/a8d229374635fa6f2a75ca1669892e1bc244e719"
     ], 
     "ci_platforms": [
@@ -61503,6 +61647,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/aaf049720c707d4e14e47e7eb31d6a2dda60e66a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/ad810f7f.bin"
     ], 
     "ci_platforms": [
@@ -61967,6 +62127,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/c4e4c7572e005e18d56eac407033da058737a5ab"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/c559f565.bin"
     ], 
     "ci_platforms": [
@@ -62271,6 +62447,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/crash-dae0f07934a527989f23f06e630710ff6ca8c809"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/crash-e34b0a9a428001cb4094a9ebca76329f578811a4"
     ], 
     "ci_platforms": [
@@ -62591,6 +62783,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/e96ad9c17795e52edc810a08d4fc61fe8790002a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/e9bbe2fe47b7b9c2683e7f17f4a33625c6ffbd8c"
     ], 
     "ci_platforms": [
@@ -62911,6 +63119,22 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/server_fuzzer_corpus/fa202a5f51cd49f8ea5af60c5f403f797c01c504"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "server_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ]
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/server_fuzzer_corpus/fa36b4280d9e28edd81c5e4d192d1a5c2765e5e4"
     ], 
     "ci_platforms": [
diff --git a/vsprojects/vcxproj/grpc++/grpc++.vcxproj b/vsprojects/vcxproj/grpc++/grpc++.vcxproj
index 29cab37..0ec53ac 100644
--- a/vsprojects/vcxproj/grpc++/grpc++.vcxproj
+++ b/vsprojects/vcxproj/grpc++/grpc++.vcxproj
@@ -331,6 +331,7 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc++\impl\codegen\sync_stream.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc++\impl\codegen\time.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer_reader.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\connectivity_state.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\grpc_types.h" />
diff --git a/vsprojects/vcxproj/grpc++/grpc++.vcxproj.filters b/vsprojects/vcxproj/grpc++/grpc++.vcxproj.filters
index 15e2807..491aeae 100644
--- a/vsprojects/vcxproj/grpc++/grpc++.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc++/grpc++.vcxproj.filters
@@ -315,6 +315,9 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer_reader.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>
diff --git a/vsprojects/vcxproj/grpc++_unsecure/grpc++_unsecure.vcxproj b/vsprojects/vcxproj/grpc++_unsecure/grpc++_unsecure.vcxproj
index fcda361..96bee41 100644
--- a/vsprojects/vcxproj/grpc++_unsecure/grpc++_unsecure.vcxproj
+++ b/vsprojects/vcxproj/grpc++_unsecure/grpc++_unsecure.vcxproj
@@ -331,6 +331,7 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc++\impl\codegen\sync_stream.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc++\impl\codegen\time.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer_reader.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\connectivity_state.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\grpc_types.h" />
diff --git a/vsprojects/vcxproj/grpc++_unsecure/grpc++_unsecure.vcxproj.filters b/vsprojects/vcxproj/grpc++_unsecure/grpc++_unsecure.vcxproj.filters
index 1dc95f9..fe9eed7 100644
--- a/vsprojects/vcxproj/grpc++_unsecure/grpc++_unsecure.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc++_unsecure/grpc++_unsecure.vcxproj.filters
@@ -300,6 +300,9 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer_reader.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj b/vsprojects/vcxproj/grpc/grpc.vcxproj
index 4eec05a..7f0e5a8 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj
@@ -273,6 +273,7 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc\grpc.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\status.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer_reader.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\connectivity_state.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\grpc_types.h" />
@@ -292,6 +293,7 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\sync_posix.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\sync_win32.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\time.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\grpc_cronet.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\grpc_security.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\grpc_security_constants.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\census.h" />
@@ -421,6 +423,42 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\client_config\subchannel_call_holder.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\client_config\subchannel_index.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\client_config\uri_parser.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\byte_buffer.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\grpc.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\alloc.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\atm.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\atm_gcc_atomic.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\atm_gcc_sync.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\atm_win32.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\connectivity_state.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\grpc_types.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\log.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\port_platform.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\propagation_bits.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\slice.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\slice_buffer.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\status.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\sync.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\sync_generic.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\sync_posix.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\sync_win32.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\time.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\status.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\alloc.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\atm.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\host_port.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\log.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\port_platform.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\slice.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\slice_buffer.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\string_util.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\sync.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\time.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\useful.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\support\string.h" />
+    <ClInclude Include="$(SolutionDir)\..\third_party\objective_c\Cronet\cronet_c_for_grpc.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\lb_policy\grpclb\load_balancer_api.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\lb_policy\grpclb\proto\grpc\lb\v0\load_balancer.pb.h" />
     <ClInclude Include="$(SolutionDir)\..\third_party\nanopb\pb.h" />
@@ -727,6 +765,12 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\transport\chttp2\client\insecure\channel_create.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\transport\cronet\client\secure\cronet_channel_create.c">
+    </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\transport\cronet\transport\cronet_api_dummy.c">
+    </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\transport\cronet\transport\cronet_transport.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\lb_policy\grpclb\load_balancer_api.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\lb_policy\grpclb\proto\grpc\lb\v0\load_balancer.pb.c">
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
index 17c88c4..6d1d69a 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
@@ -439,6 +439,15 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\transport\chttp2\client\insecure\channel_create.c">
       <Filter>src\core\ext\transport\chttp2\client\insecure</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\transport\cronet\client\secure\cronet_channel_create.c">
+      <Filter>src\core\ext\transport\cronet\client\secure</Filter>
+    </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\transport\cronet\transport\cronet_api_dummy.c">
+      <Filter>src\core\ext\transport\cronet\transport</Filter>
+    </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\transport\cronet\transport\cronet_transport.c">
+      <Filter>src\core\ext\transport\cronet\transport</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\lb_policy\grpclb\load_balancer_api.c">
       <Filter>src\core\ext\lb_policy\grpclb</Filter>
     </ClCompile>
@@ -516,6 +525,9 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer_reader.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>
@@ -573,6 +585,9 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\time.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\grpc_cronet.h">
+      <Filter>include\grpc</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\include\grpc\grpc_security.h">
       <Filter>include\grpc</Filter>
     </ClInclude>
@@ -956,6 +971,114 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\client_config\uri_parser.h">
       <Filter>src\core\ext\client_config</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\byte_buffer.h">
+      <Filter>include\grpc</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\grpc.h">
+      <Filter>include\grpc</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\alloc.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\atm.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\atm_gcc_atomic.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\atm_gcc_sync.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\atm_win32.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\connectivity_state.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\grpc_types.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\log.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\port_platform.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\propagation_bits.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\slice.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\slice_buffer.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\status.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\sync.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\sync_generic.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\sync_posix.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\sync_win32.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\time.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\status.h">
+      <Filter>include\grpc</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\alloc.h">
+      <Filter>include\grpc\support</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\atm.h">
+      <Filter>include\grpc\support</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\host_port.h">
+      <Filter>include\grpc\support</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\log.h">
+      <Filter>include\grpc\support</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\port_platform.h">
+      <Filter>include\grpc\support</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\slice.h">
+      <Filter>include\grpc\support</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\slice_buffer.h">
+      <Filter>include\grpc\support</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\string_util.h">
+      <Filter>include\grpc\support</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\sync.h">
+      <Filter>include\grpc\support</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\time.h">
+      <Filter>include\grpc\support</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\support\useful.h">
+      <Filter>include\grpc\support</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\support\string.h">
+      <Filter>src\core\lib\support</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\third_party\objective_c\Cronet\cronet_c_for_grpc.h">
+      <Filter>third_party\objective_c\Cronet</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\lb_policy\grpclb\load_balancer_api.h">
       <Filter>src\core\ext\lb_policy\grpclb</Filter>
     </ClInclude>
@@ -1007,6 +1130,9 @@
     <Filter Include="include\grpc\impl\codegen">
       <UniqueIdentifier>{def748f5-ed2a-a9bb-40d9-c31d00f0e13b}</UniqueIdentifier>
     </Filter>
+    <Filter Include="include\grpc\support">
+      <UniqueIdentifier>{31de82ea-dc6c-73fb-a640-979b8a7b240c}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src">
       <UniqueIdentifier>{d538af37-07b2-062b-fa2a-d9f882cb2737}</UniqueIdentifier>
     </Filter>
@@ -1088,6 +1214,18 @@
     <Filter Include="src\core\ext\transport\chttp2\transport">
       <UniqueIdentifier>{6f34254e-e69f-c9b4-156d-5024bade5408}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\core\ext\transport\cronet">
+      <UniqueIdentifier>{1e9c85e9-5522-7ef8-0017-7e19990a6194}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="src\core\ext\transport\cronet\client">
+      <UniqueIdentifier>{d0530883-75d9-b5f7-d594-26735a70ac7b}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="src\core\ext\transport\cronet\client\secure">
+      <UniqueIdentifier>{4fa6fe90-b7a8-5c8f-d629-db1e68d89eed}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="src\core\ext\transport\cronet\transport">
+      <UniqueIdentifier>{31518af8-5860-6d0d-ff78-4059fce29ec2}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\core\lib">
       <UniqueIdentifier>{5b2ded3f-84a5-f6b4-2060-286c7d1dc945}</UniqueIdentifier>
     </Filter>
@@ -1112,6 +1250,9 @@
     <Filter Include="src\core\lib\security">
       <UniqueIdentifier>{c4661d64-349f-01c1-1ba8-0602f9047595}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\core\lib\support">
+      <UniqueIdentifier>{27f30339-d694-40f5-db07-4b89b9aeea73}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\core\lib\surface">
       <UniqueIdentifier>{a21971fb-304f-da08-b1b2-7bd8df8ac373}</UniqueIdentifier>
     </Filter>
@@ -1130,6 +1271,12 @@
     <Filter Include="third_party\nanopb">
       <UniqueIdentifier>{93d6596d-330c-1d27-6f84-3c840e57869e}</UniqueIdentifier>
     </Filter>
+    <Filter Include="third_party\objective_c">
+      <UniqueIdentifier>{3a56a516-857e-d2aa-95cc-11685baf4e8c}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="third_party\objective_c\Cronet">
+      <UniqueIdentifier>{a165c6e3-0776-6f40-7351-d7865668e220}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
 </Project>
 
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
index 26050dc..0eb6535 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
@@ -264,6 +264,7 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc\grpc.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\status.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer_reader.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\connectivity_state.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\grpc_types.h" />
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
index a4acf51..f544fe6 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
@@ -456,6 +456,9 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer_reader.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>
diff --git a/vsprojects/vcxproj/test/codegen_test_full/codegen_test_full.vcxproj b/vsprojects/vcxproj/test/codegen_test_full/codegen_test_full.vcxproj
index cd0b40c..34e939c 100644
--- a/vsprojects/vcxproj/test/codegen_test_full/codegen_test_full.vcxproj
+++ b/vsprojects/vcxproj/test/codegen_test_full/codegen_test_full.vcxproj
@@ -191,6 +191,7 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc++\impl\codegen\sync_stream.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc++\impl\codegen\time.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer_reader.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\connectivity_state.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\grpc_types.h" />
diff --git a/vsprojects/vcxproj/test/codegen_test_full/codegen_test_full.vcxproj.filters b/vsprojects/vcxproj/test/codegen_test_full/codegen_test_full.vcxproj.filters
index 029b8ef..d662365 100644
--- a/vsprojects/vcxproj/test/codegen_test_full/codegen_test_full.vcxproj.filters
+++ b/vsprojects/vcxproj/test/codegen_test_full/codegen_test_full.vcxproj.filters
@@ -120,6 +120,9 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer_reader.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>
diff --git a/vsprojects/vcxproj/test/codegen_test_minimal/codegen_test_minimal.vcxproj b/vsprojects/vcxproj/test/codegen_test_minimal/codegen_test_minimal.vcxproj
index 6d138fa..890d77d 100644
--- a/vsprojects/vcxproj/test/codegen_test_minimal/codegen_test_minimal.vcxproj
+++ b/vsprojects/vcxproj/test/codegen_test_minimal/codegen_test_minimal.vcxproj
@@ -191,6 +191,7 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc++\impl\codegen\sync_stream.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc++\impl\codegen\time.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h" />
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer_reader.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\connectivity_state.h" />
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\grpc_types.h" />
diff --git a/vsprojects/vcxproj/test/codegen_test_minimal/codegen_test_minimal.vcxproj.filters b/vsprojects/vcxproj/test/codegen_test_minimal/codegen_test_minimal.vcxproj.filters
index dc3f0b2..4e0ba65 100644
--- a/vsprojects/vcxproj/test/codegen_test_minimal/codegen_test_minimal.vcxproj.filters
+++ b/vsprojects/vcxproj/test/codegen_test_minimal/codegen_test_minimal.vcxproj.filters
@@ -120,6 +120,9 @@
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\byte_buffer_reader.h">
+      <Filter>include\grpc\impl\codegen</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\include\grpc\impl\codegen\compression_types.h">
       <Filter>include\grpc\impl\codegen</Filter>
     </ClInclude>