Merge pull request #387 from maxwell-demon/census_active_ops

V0 implementation of census_get_active_ops().
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..f744516
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,7 @@
+root = true
+[**]
+end_of_line = LF
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+tab_width = 8
diff --git a/.gitignore b/.gitignore
index 6eb55b1..9c9ae5a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,11 @@
 # python compiled objects
 *.pyc
 
+#eclipse project files
+.cproject
+.project
+.settings
+
 # cache for run_tests.py
 .run_tests_cache
 
diff --git a/Makefile b/Makefile
index 1465f36..40f98b9 100644
--- a/Makefile
+++ b/Makefile
@@ -189,11 +189,13 @@
 ZLIB_CHECK_CMD = $(CC) $(CFLAGS) $(CPPFLAGS) -o /dev/null test/build/zlib.c -lz $(LDFLAGS)
 PERFTOOLS_CHECK_CMD = $(CC) $(CFLAGS) $(CPPFLAGS) -o /dev/null test/build/perftools.c -lprofiler $(LDFLAGS)
 
+ifndef REQUIRE_CUSTOM_LIBRARIES_$(CONFIG)
 HAS_SYSTEM_PERFTOOLS = $(shell $(PERFTOOLS_CHECK_CMD) 2> /dev/null && echo true || echo false)
 ifeq ($(HAS_SYSTEM_PERFTOOLS),true)
 DEFINES += GRPC_HAVE_PERFTOOLS
 LIBS += profiler
 endif
+endif
 
 ifndef REQUIRE_CUSTOM_LIBRARIES_$(CONFIG)
 HAS_SYSTEM_OPENSSL_ALPN = $(shell $(OPENSSL_ALPN_CHECK_CMD) 2> /dev/null && echo true || echo false)
@@ -336,6 +338,8 @@
 gpr_histogram_test: bins/$(CONFIG)/gpr_histogram_test
 gpr_host_port_test: bins/$(CONFIG)/gpr_host_port_test
 gpr_log_test: bins/$(CONFIG)/gpr_log_test
+gpr_file_test: bins/$(CONFIG)/gpr_file_test
+gpr_env_test: bins/$(CONFIG)/gpr_env_test
 gpr_slice_buffer_test: bins/$(CONFIG)/gpr_slice_buffer_test
 gpr_slice_test: bins/$(CONFIG)/gpr_slice_test
 gpr_string_test: bins/$(CONFIG)/gpr_string_test
@@ -357,6 +361,9 @@
 httpcli_format_request_test: bins/$(CONFIG)/httpcli_format_request_test
 httpcli_parser_test: bins/$(CONFIG)/httpcli_parser_test
 httpcli_test: bins/$(CONFIG)/httpcli_test
+json_rewrite: bins/$(CONFIG)/json_rewrite
+json_rewrite_test: bins/$(CONFIG)/json_rewrite_test
+json_test: bins/$(CONFIG)/json_test
 lame_client_test: bins/$(CONFIG)/lame_client_test
 low_level_ping_pong_benchmark: bins/$(CONFIG)/low_level_ping_pong_benchmark
 message_compress_test: bins/$(CONFIG)/message_compress_test
@@ -374,9 +381,6 @@
 time_test: bins/$(CONFIG)/time_test
 timeout_encoding_test: bins/$(CONFIG)/timeout_encoding_test
 transport_metadata_test: bins/$(CONFIG)/transport_metadata_test
-json_test: bins/$(CONFIG)/json_test
-json_rewrite: bins/$(CONFIG)/json_rewrite
-json_rewrite_test: bins/$(CONFIG)/json_rewrite_test
 channel_arguments_test: bins/$(CONFIG)/channel_arguments_test
 cpp_plugin: bins/$(CONFIG)/cpp_plugin
 credentials_test: bins/$(CONFIG)/credentials_test
@@ -384,7 +388,8 @@
 interop_client: bins/$(CONFIG)/interop_client
 interop_server: bins/$(CONFIG)/interop_server
 tips_client: bins/$(CONFIG)/tips_client
-tips_client_test: bins/$(CONFIG)/tips_client_test
+tips_publisher_test: bins/$(CONFIG)/tips_publisher_test
+tips_subscriber_test: bins/$(CONFIG)/tips_subscriber_test
 qps_client: bins/$(CONFIG)/qps_client
 qps_server: bins/$(CONFIG)/qps_server
 ruby_plugin: bins/$(CONFIG)/ruby_plugin
@@ -550,13 +555,13 @@
 
 static: static_c static_cxx
 
-static_c:  libs/$(CONFIG)/libgpr.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgrpc_unsecure.a
+static_c:  libs/$(CONFIG)/libgpr.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgrpc_unsecure.a libs/$(CONFIG)/libgrpc_csharp_ext.a
 
 static_cxx:  libs/$(CONFIG)/libgrpc++.a
 
 shared: shared_c shared_cxx
 
-shared_c:  libs/$(CONFIG)/libgpr.$(SHARED_EXT) libs/$(CONFIG)/libgrpc.$(SHARED_EXT) libs/$(CONFIG)/libgrpc_unsecure.$(SHARED_EXT)
+shared_c:  libs/$(CONFIG)/libgpr.$(SHARED_EXT) libs/$(CONFIG)/libgrpc.$(SHARED_EXT) libs/$(CONFIG)/libgrpc_unsecure.$(SHARED_EXT) libs/$(CONFIG)/libgrpc_csharp_ext.$(SHARED_EXT)
 
 shared_cxx:  libs/$(CONFIG)/libgrpc++.$(SHARED_EXT)
 
@@ -568,9 +573,9 @@
 
 buildtests: buildtests_c buildtests_cxx
 
-buildtests_c: privatelibs_c bins/$(CONFIG)/alarm_heap_test bins/$(CONFIG)/alarm_list_test bins/$(CONFIG)/alarm_test bins/$(CONFIG)/alpn_test bins/$(CONFIG)/bin_encoder_test bins/$(CONFIG)/census_hash_table_test bins/$(CONFIG)/census_statistics_multiple_writers_circular_buffer_test bins/$(CONFIG)/census_statistics_multiple_writers_test bins/$(CONFIG)/census_statistics_performance_test bins/$(CONFIG)/census_statistics_quick_test bins/$(CONFIG)/census_statistics_small_log_test bins/$(CONFIG)/census_stub_test bins/$(CONFIG)/census_window_stats_test bins/$(CONFIG)/chttp2_status_conversion_test bins/$(CONFIG)/chttp2_stream_encoder_test bins/$(CONFIG)/chttp2_stream_map_test bins/$(CONFIG)/chttp2_transport_end2end_test bins/$(CONFIG)/dualstack_socket_test bins/$(CONFIG)/echo_client bins/$(CONFIG)/echo_server bins/$(CONFIG)/echo_test bins/$(CONFIG)/fd_posix_test bins/$(CONFIG)/fling_client bins/$(CONFIG)/fling_server bins/$(CONFIG)/fling_stream_test bins/$(CONFIG)/fling_test bins/$(CONFIG)/gpr_cancellable_test bins/$(CONFIG)/gpr_cmdline_test bins/$(CONFIG)/gpr_histogram_test bins/$(CONFIG)/gpr_host_port_test bins/$(CONFIG)/gpr_log_test bins/$(CONFIG)/gpr_slice_buffer_test bins/$(CONFIG)/gpr_slice_test bins/$(CONFIG)/gpr_string_test bins/$(CONFIG)/gpr_sync_test bins/$(CONFIG)/gpr_thd_test bins/$(CONFIG)/gpr_time_test bins/$(CONFIG)/gpr_useful_test bins/$(CONFIG)/grpc_base64_test bins/$(CONFIG)/grpc_byte_buffer_reader_test bins/$(CONFIG)/grpc_channel_stack_test bins/$(CONFIG)/grpc_completion_queue_test bins/$(CONFIG)/grpc_credentials_test bins/$(CONFIG)/grpc_json_token_test bins/$(CONFIG)/grpc_stream_op_test bins/$(CONFIG)/hpack_parser_test bins/$(CONFIG)/hpack_table_test bins/$(CONFIG)/httpcli_format_request_test bins/$(CONFIG)/httpcli_parser_test bins/$(CONFIG)/httpcli_test bins/$(CONFIG)/lame_client_test bins/$(CONFIG)/message_compress_test bins/$(CONFIG)/metadata_buffer_test bins/$(CONFIG)/murmur_hash_test bins/$(CONFIG)/no_server_test bins/$(CONFIG)/poll_kick_posix_test bins/$(CONFIG)/resolve_address_test bins/$(CONFIG)/secure_endpoint_test bins/$(CONFIG)/sockaddr_utils_test bins/$(CONFIG)/tcp_client_posix_test bins/$(CONFIG)/tcp_posix_test bins/$(CONFIG)/tcp_server_posix_test bins/$(CONFIG)/time_averaged_stats_test bins/$(CONFIG)/time_test bins/$(CONFIG)/timeout_encoding_test bins/$(CONFIG)/transport_metadata_test bins/$(CONFIG)/json_test bins/$(CONFIG)/json_rewrite bins/$(CONFIG)/json_rewrite_test bins/$(CONFIG)/chttp2_fake_security_cancel_after_accept_test bins/$(CONFIG)/chttp2_fake_security_cancel_after_accept_and_writes_closed_test bins/$(CONFIG)/chttp2_fake_security_cancel_after_invoke_test bins/$(CONFIG)/chttp2_fake_security_cancel_before_invoke_test bins/$(CONFIG)/chttp2_fake_security_cancel_in_a_vacuum_test bins/$(CONFIG)/chttp2_fake_security_census_simple_request_test bins/$(CONFIG)/chttp2_fake_security_disappearing_server_test bins/$(CONFIG)/chttp2_fake_security_early_server_shutdown_finishes_inflight_calls_test bins/$(CONFIG)/chttp2_fake_security_early_server_shutdown_finishes_tags_test bins/$(CONFIG)/chttp2_fake_security_graceful_server_shutdown_test bins/$(CONFIG)/chttp2_fake_security_invoke_large_request_test bins/$(CONFIG)/chttp2_fake_security_max_concurrent_streams_test bins/$(CONFIG)/chttp2_fake_security_no_op_test bins/$(CONFIG)/chttp2_fake_security_ping_pong_streaming_test bins/$(CONFIG)/chttp2_fake_security_request_response_with_binary_metadata_and_payload_test bins/$(CONFIG)/chttp2_fake_security_request_response_with_metadata_and_payload_test bins/$(CONFIG)/chttp2_fake_security_request_response_with_payload_test bins/$(CONFIG)/chttp2_fake_security_request_response_with_trailing_metadata_and_payload_test bins/$(CONFIG)/chttp2_fake_security_simple_delayed_request_test bins/$(CONFIG)/chttp2_fake_security_simple_request_test bins/$(CONFIG)/chttp2_fake_security_thread_stress_test bins/$(CONFIG)/chttp2_fake_security_writes_done_hangs_with_pending_read_test bins/$(CONFIG)/chttp2_fullstack_cancel_after_accept_test bins/$(CONFIG)/chttp2_fullstack_cancel_after_accept_and_writes_closed_test bins/$(CONFIG)/chttp2_fullstack_cancel_after_invoke_test bins/$(CONFIG)/chttp2_fullstack_cancel_before_invoke_test bins/$(CONFIG)/chttp2_fullstack_cancel_in_a_vacuum_test bins/$(CONFIG)/chttp2_fullstack_census_simple_request_test bins/$(CONFIG)/chttp2_fullstack_disappearing_server_test bins/$(CONFIG)/chttp2_fullstack_early_server_shutdown_finishes_inflight_calls_test bins/$(CONFIG)/chttp2_fullstack_early_server_shutdown_finishes_tags_test bins/$(CONFIG)/chttp2_fullstack_graceful_server_shutdown_test bins/$(CONFIG)/chttp2_fullstack_invoke_large_request_test bins/$(CONFIG)/chttp2_fullstack_max_concurrent_streams_test bins/$(CONFIG)/chttp2_fullstack_no_op_test bins/$(CONFIG)/chttp2_fullstack_ping_pong_streaming_test bins/$(CONFIG)/chttp2_fullstack_request_response_with_binary_metadata_and_payload_test bins/$(CONFIG)/chttp2_fullstack_request_response_with_metadata_and_payload_test bins/$(CONFIG)/chttp2_fullstack_request_response_with_payload_test bins/$(CONFIG)/chttp2_fullstack_request_response_with_trailing_metadata_and_payload_test bins/$(CONFIG)/chttp2_fullstack_simple_delayed_request_test bins/$(CONFIG)/chttp2_fullstack_simple_request_test bins/$(CONFIG)/chttp2_fullstack_thread_stress_test bins/$(CONFIG)/chttp2_fullstack_writes_done_hangs_with_pending_read_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_after_accept_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_after_accept_and_writes_closed_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_after_invoke_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_before_invoke_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_in_a_vacuum_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_census_simple_request_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_disappearing_server_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_early_server_shutdown_finishes_inflight_calls_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_early_server_shutdown_finishes_tags_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_graceful_server_shutdown_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_invoke_large_request_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_max_concurrent_streams_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_no_op_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_ping_pong_streaming_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_binary_metadata_and_payload_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_metadata_and_payload_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_payload_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_trailing_metadata_and_payload_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_simple_delayed_request_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_simple_request_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_thread_stress_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_writes_done_hangs_with_pending_read_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_after_accept_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_after_accept_and_writes_closed_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_after_invoke_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_before_invoke_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_in_a_vacuum_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_census_simple_request_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_disappearing_server_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_early_server_shutdown_finishes_inflight_calls_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_early_server_shutdown_finishes_tags_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_graceful_server_shutdown_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_invoke_large_request_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_max_concurrent_streams_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_no_op_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_ping_pong_streaming_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_binary_metadata_and_payload_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_metadata_and_payload_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_payload_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_trailing_metadata_and_payload_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_simple_delayed_request_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_simple_request_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_thread_stress_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_writes_done_hangs_with_pending_read_test bins/$(CONFIG)/chttp2_socket_pair_cancel_after_accept_test bins/$(CONFIG)/chttp2_socket_pair_cancel_after_accept_and_writes_closed_test bins/$(CONFIG)/chttp2_socket_pair_cancel_after_invoke_test bins/$(CONFIG)/chttp2_socket_pair_cancel_before_invoke_test bins/$(CONFIG)/chttp2_socket_pair_cancel_in_a_vacuum_test bins/$(CONFIG)/chttp2_socket_pair_census_simple_request_test bins/$(CONFIG)/chttp2_socket_pair_disappearing_server_test bins/$(CONFIG)/chttp2_socket_pair_early_server_shutdown_finishes_inflight_calls_test bins/$(CONFIG)/chttp2_socket_pair_early_server_shutdown_finishes_tags_test bins/$(CONFIG)/chttp2_socket_pair_graceful_server_shutdown_test bins/$(CONFIG)/chttp2_socket_pair_invoke_large_request_test bins/$(CONFIG)/chttp2_socket_pair_max_concurrent_streams_test bins/$(CONFIG)/chttp2_socket_pair_no_op_test bins/$(CONFIG)/chttp2_socket_pair_ping_pong_streaming_test bins/$(CONFIG)/chttp2_socket_pair_request_response_with_binary_metadata_and_payload_test bins/$(CONFIG)/chttp2_socket_pair_request_response_with_metadata_and_payload_test bins/$(CONFIG)/chttp2_socket_pair_request_response_with_payload_test bins/$(CONFIG)/chttp2_socket_pair_request_response_with_trailing_metadata_and_payload_test bins/$(CONFIG)/chttp2_socket_pair_simple_delayed_request_test bins/$(CONFIG)/chttp2_socket_pair_simple_request_test bins/$(CONFIG)/chttp2_socket_pair_thread_stress_test bins/$(CONFIG)/chttp2_socket_pair_writes_done_hangs_with_pending_read_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_after_accept_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_after_accept_and_writes_closed_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_after_invoke_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_before_invoke_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_in_a_vacuum_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_census_simple_request_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_disappearing_server_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_early_server_shutdown_finishes_inflight_calls_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_early_server_shutdown_finishes_tags_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_graceful_server_shutdown_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_invoke_large_request_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_max_concurrent_streams_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_no_op_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_ping_pong_streaming_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_binary_metadata_and_payload_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_metadata_and_payload_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_payload_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_trailing_metadata_and_payload_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_simple_delayed_request_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_simple_request_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_thread_stress_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_writes_done_hangs_with_pending_read_test
+buildtests_c: privatelibs_c bins/$(CONFIG)/alarm_heap_test bins/$(CONFIG)/alarm_list_test bins/$(CONFIG)/alarm_test bins/$(CONFIG)/alpn_test bins/$(CONFIG)/bin_encoder_test bins/$(CONFIG)/census_hash_table_test bins/$(CONFIG)/census_statistics_multiple_writers_circular_buffer_test bins/$(CONFIG)/census_statistics_multiple_writers_test bins/$(CONFIG)/census_statistics_performance_test bins/$(CONFIG)/census_statistics_quick_test bins/$(CONFIG)/census_statistics_small_log_test bins/$(CONFIG)/census_stub_test bins/$(CONFIG)/census_window_stats_test bins/$(CONFIG)/chttp2_status_conversion_test bins/$(CONFIG)/chttp2_stream_encoder_test bins/$(CONFIG)/chttp2_stream_map_test bins/$(CONFIG)/chttp2_transport_end2end_test bins/$(CONFIG)/dualstack_socket_test bins/$(CONFIG)/echo_client bins/$(CONFIG)/echo_server bins/$(CONFIG)/echo_test bins/$(CONFIG)/fd_posix_test bins/$(CONFIG)/fling_client bins/$(CONFIG)/fling_server bins/$(CONFIG)/fling_stream_test bins/$(CONFIG)/fling_test bins/$(CONFIG)/gpr_cancellable_test bins/$(CONFIG)/gpr_cmdline_test bins/$(CONFIG)/gpr_histogram_test bins/$(CONFIG)/gpr_host_port_test bins/$(CONFIG)/gpr_log_test bins/$(CONFIG)/gpr_file_test bins/$(CONFIG)/gpr_env_test bins/$(CONFIG)/gpr_slice_buffer_test bins/$(CONFIG)/gpr_slice_test bins/$(CONFIG)/gpr_string_test bins/$(CONFIG)/gpr_sync_test bins/$(CONFIG)/gpr_thd_test bins/$(CONFIG)/gpr_time_test bins/$(CONFIG)/gpr_useful_test bins/$(CONFIG)/grpc_base64_test bins/$(CONFIG)/grpc_byte_buffer_reader_test bins/$(CONFIG)/grpc_channel_stack_test bins/$(CONFIG)/grpc_completion_queue_test bins/$(CONFIG)/grpc_credentials_test bins/$(CONFIG)/grpc_json_token_test bins/$(CONFIG)/grpc_stream_op_test bins/$(CONFIG)/hpack_parser_test bins/$(CONFIG)/hpack_table_test bins/$(CONFIG)/httpcli_format_request_test bins/$(CONFIG)/httpcli_parser_test bins/$(CONFIG)/httpcli_test bins/$(CONFIG)/json_rewrite bins/$(CONFIG)/json_rewrite_test bins/$(CONFIG)/json_test bins/$(CONFIG)/lame_client_test bins/$(CONFIG)/message_compress_test bins/$(CONFIG)/metadata_buffer_test bins/$(CONFIG)/murmur_hash_test bins/$(CONFIG)/no_server_test bins/$(CONFIG)/poll_kick_posix_test bins/$(CONFIG)/resolve_address_test bins/$(CONFIG)/secure_endpoint_test bins/$(CONFIG)/sockaddr_utils_test bins/$(CONFIG)/tcp_client_posix_test bins/$(CONFIG)/tcp_posix_test bins/$(CONFIG)/tcp_server_posix_test bins/$(CONFIG)/time_averaged_stats_test bins/$(CONFIG)/time_test bins/$(CONFIG)/timeout_encoding_test bins/$(CONFIG)/transport_metadata_test bins/$(CONFIG)/chttp2_fake_security_cancel_after_accept_test bins/$(CONFIG)/chttp2_fake_security_cancel_after_accept_and_writes_closed_test bins/$(CONFIG)/chttp2_fake_security_cancel_after_invoke_test bins/$(CONFIG)/chttp2_fake_security_cancel_before_invoke_test bins/$(CONFIG)/chttp2_fake_security_cancel_in_a_vacuum_test bins/$(CONFIG)/chttp2_fake_security_census_simple_request_test bins/$(CONFIG)/chttp2_fake_security_disappearing_server_test bins/$(CONFIG)/chttp2_fake_security_early_server_shutdown_finishes_inflight_calls_test bins/$(CONFIG)/chttp2_fake_security_early_server_shutdown_finishes_tags_test bins/$(CONFIG)/chttp2_fake_security_graceful_server_shutdown_test bins/$(CONFIG)/chttp2_fake_security_invoke_large_request_test bins/$(CONFIG)/chttp2_fake_security_max_concurrent_streams_test bins/$(CONFIG)/chttp2_fake_security_no_op_test bins/$(CONFIG)/chttp2_fake_security_ping_pong_streaming_test bins/$(CONFIG)/chttp2_fake_security_request_response_with_binary_metadata_and_payload_test bins/$(CONFIG)/chttp2_fake_security_request_response_with_metadata_and_payload_test bins/$(CONFIG)/chttp2_fake_security_request_response_with_payload_test bins/$(CONFIG)/chttp2_fake_security_request_response_with_trailing_metadata_and_payload_test bins/$(CONFIG)/chttp2_fake_security_simple_delayed_request_test bins/$(CONFIG)/chttp2_fake_security_simple_request_test bins/$(CONFIG)/chttp2_fake_security_thread_stress_test bins/$(CONFIG)/chttp2_fake_security_writes_done_hangs_with_pending_read_test bins/$(CONFIG)/chttp2_fullstack_cancel_after_accept_test bins/$(CONFIG)/chttp2_fullstack_cancel_after_accept_and_writes_closed_test bins/$(CONFIG)/chttp2_fullstack_cancel_after_invoke_test bins/$(CONFIG)/chttp2_fullstack_cancel_before_invoke_test bins/$(CONFIG)/chttp2_fullstack_cancel_in_a_vacuum_test bins/$(CONFIG)/chttp2_fullstack_census_simple_request_test bins/$(CONFIG)/chttp2_fullstack_disappearing_server_test bins/$(CONFIG)/chttp2_fullstack_early_server_shutdown_finishes_inflight_calls_test bins/$(CONFIG)/chttp2_fullstack_early_server_shutdown_finishes_tags_test bins/$(CONFIG)/chttp2_fullstack_graceful_server_shutdown_test bins/$(CONFIG)/chttp2_fullstack_invoke_large_request_test bins/$(CONFIG)/chttp2_fullstack_max_concurrent_streams_test bins/$(CONFIG)/chttp2_fullstack_no_op_test bins/$(CONFIG)/chttp2_fullstack_ping_pong_streaming_test bins/$(CONFIG)/chttp2_fullstack_request_response_with_binary_metadata_and_payload_test bins/$(CONFIG)/chttp2_fullstack_request_response_with_metadata_and_payload_test bins/$(CONFIG)/chttp2_fullstack_request_response_with_payload_test bins/$(CONFIG)/chttp2_fullstack_request_response_with_trailing_metadata_and_payload_test bins/$(CONFIG)/chttp2_fullstack_simple_delayed_request_test bins/$(CONFIG)/chttp2_fullstack_simple_request_test bins/$(CONFIG)/chttp2_fullstack_thread_stress_test bins/$(CONFIG)/chttp2_fullstack_writes_done_hangs_with_pending_read_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_after_accept_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_after_accept_and_writes_closed_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_after_invoke_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_before_invoke_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_in_a_vacuum_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_census_simple_request_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_disappearing_server_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_early_server_shutdown_finishes_inflight_calls_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_early_server_shutdown_finishes_tags_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_graceful_server_shutdown_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_invoke_large_request_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_max_concurrent_streams_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_no_op_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_ping_pong_streaming_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_binary_metadata_and_payload_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_metadata_and_payload_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_payload_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_trailing_metadata_and_payload_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_simple_delayed_request_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_simple_request_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_thread_stress_test bins/$(CONFIG)/chttp2_simple_ssl_fullstack_writes_done_hangs_with_pending_read_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_after_accept_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_after_accept_and_writes_closed_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_after_invoke_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_before_invoke_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_in_a_vacuum_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_census_simple_request_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_disappearing_server_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_early_server_shutdown_finishes_inflight_calls_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_early_server_shutdown_finishes_tags_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_graceful_server_shutdown_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_invoke_large_request_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_max_concurrent_streams_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_no_op_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_ping_pong_streaming_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_binary_metadata_and_payload_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_metadata_and_payload_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_payload_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_trailing_metadata_and_payload_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_simple_delayed_request_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_simple_request_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_thread_stress_test bins/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_writes_done_hangs_with_pending_read_test bins/$(CONFIG)/chttp2_socket_pair_cancel_after_accept_test bins/$(CONFIG)/chttp2_socket_pair_cancel_after_accept_and_writes_closed_test bins/$(CONFIG)/chttp2_socket_pair_cancel_after_invoke_test bins/$(CONFIG)/chttp2_socket_pair_cancel_before_invoke_test bins/$(CONFIG)/chttp2_socket_pair_cancel_in_a_vacuum_test bins/$(CONFIG)/chttp2_socket_pair_census_simple_request_test bins/$(CONFIG)/chttp2_socket_pair_disappearing_server_test bins/$(CONFIG)/chttp2_socket_pair_early_server_shutdown_finishes_inflight_calls_test bins/$(CONFIG)/chttp2_socket_pair_early_server_shutdown_finishes_tags_test bins/$(CONFIG)/chttp2_socket_pair_graceful_server_shutdown_test bins/$(CONFIG)/chttp2_socket_pair_invoke_large_request_test bins/$(CONFIG)/chttp2_socket_pair_max_concurrent_streams_test bins/$(CONFIG)/chttp2_socket_pair_no_op_test bins/$(CONFIG)/chttp2_socket_pair_ping_pong_streaming_test bins/$(CONFIG)/chttp2_socket_pair_request_response_with_binary_metadata_and_payload_test bins/$(CONFIG)/chttp2_socket_pair_request_response_with_metadata_and_payload_test bins/$(CONFIG)/chttp2_socket_pair_request_response_with_payload_test bins/$(CONFIG)/chttp2_socket_pair_request_response_with_trailing_metadata_and_payload_test bins/$(CONFIG)/chttp2_socket_pair_simple_delayed_request_test bins/$(CONFIG)/chttp2_socket_pair_simple_request_test bins/$(CONFIG)/chttp2_socket_pair_thread_stress_test bins/$(CONFIG)/chttp2_socket_pair_writes_done_hangs_with_pending_read_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_after_accept_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_after_accept_and_writes_closed_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_after_invoke_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_before_invoke_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_in_a_vacuum_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_census_simple_request_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_disappearing_server_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_early_server_shutdown_finishes_inflight_calls_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_early_server_shutdown_finishes_tags_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_graceful_server_shutdown_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_invoke_large_request_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_max_concurrent_streams_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_no_op_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_ping_pong_streaming_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_binary_metadata_and_payload_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_metadata_and_payload_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_payload_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_trailing_metadata_and_payload_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_simple_delayed_request_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_simple_request_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_thread_stress_test bins/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_writes_done_hangs_with_pending_read_test
 
-buildtests_cxx: privatelibs_cxx bins/$(CONFIG)/channel_arguments_test bins/$(CONFIG)/credentials_test bins/$(CONFIG)/end2end_test bins/$(CONFIG)/interop_client bins/$(CONFIG)/interop_server bins/$(CONFIG)/tips_client bins/$(CONFIG)/tips_client_test bins/$(CONFIG)/qps_client bins/$(CONFIG)/qps_server bins/$(CONFIG)/status_test bins/$(CONFIG)/sync_client_async_server_test bins/$(CONFIG)/thread_pool_test
+buildtests_cxx: privatelibs_cxx bins/$(CONFIG)/channel_arguments_test bins/$(CONFIG)/credentials_test bins/$(CONFIG)/end2end_test bins/$(CONFIG)/interop_client bins/$(CONFIG)/interop_server bins/$(CONFIG)/tips_client bins/$(CONFIG)/tips_publisher_test bins/$(CONFIG)/tips_subscriber_test bins/$(CONFIG)/qps_client bins/$(CONFIG)/qps_server bins/$(CONFIG)/status_test bins/$(CONFIG)/sync_client_async_server_test bins/$(CONFIG)/thread_pool_test
 
 test: test_c test_cxx
 
@@ -629,6 +634,10 @@
 	$(Q) ./bins/$(CONFIG)/gpr_host_port_test || ( echo test gpr_host_port_test failed ; exit 1 )
 	$(E) "[RUN]     Testing gpr_log_test"
 	$(Q) ./bins/$(CONFIG)/gpr_log_test || ( echo test gpr_log_test failed ; exit 1 )
+	$(E) "[RUN]     Testing gpr_file_test"
+	$(Q) ./bins/$(CONFIG)/gpr_file_test || ( echo test gpr_file_test failed ; exit 1 )
+	$(E) "[RUN]     Testing gpr_env_test"
+	$(Q) ./bins/$(CONFIG)/gpr_env_test || ( echo test gpr_env_test failed ; exit 1 )
 	$(E) "[RUN]     Testing gpr_slice_buffer_test"
 	$(Q) ./bins/$(CONFIG)/gpr_slice_buffer_test || ( echo test gpr_slice_buffer_test failed ; exit 1 )
 	$(E) "[RUN]     Testing gpr_slice_test"
@@ -667,6 +676,8 @@
 	$(Q) ./bins/$(CONFIG)/httpcli_parser_test || ( echo test httpcli_parser_test failed ; exit 1 )
 	$(E) "[RUN]     Testing httpcli_test"
 	$(Q) ./bins/$(CONFIG)/httpcli_test || ( echo test httpcli_test failed ; exit 1 )
+	$(E) "[RUN]     Testing json_test"
+	$(Q) ./bins/$(CONFIG)/json_test || ( echo test json_test failed ; exit 1 )
 	$(E) "[RUN]     Testing lame_client_test"
 	$(Q) ./bins/$(CONFIG)/lame_client_test || ( echo test lame_client_test failed ; exit 1 )
 	$(E) "[RUN]     Testing message_compress_test"
@@ -699,8 +710,6 @@
 	$(Q) ./bins/$(CONFIG)/timeout_encoding_test || ( echo test timeout_encoding_test failed ; exit 1 )
 	$(E) "[RUN]     Testing transport_metadata_test"
 	$(Q) ./bins/$(CONFIG)/transport_metadata_test || ( echo test transport_metadata_test failed ; exit 1 )
-	$(E) "[RUN]     Testing json_test"
-	$(Q) ./bins/$(CONFIG)/json_test || ( echo test json_test failed ; exit 1 )
 	$(E) "[RUN]     Testing chttp2_fake_security_cancel_after_accept_test"
 	$(Q) ./bins/$(CONFIG)/chttp2_fake_security_cancel_after_accept_test || ( echo test chttp2_fake_security_cancel_after_accept_test failed ; exit 1 )
 	$(E) "[RUN]     Testing chttp2_fake_security_cancel_after_accept_and_writes_closed_test"
@@ -974,8 +983,10 @@
 	$(Q) ./bins/$(CONFIG)/credentials_test || ( echo test credentials_test failed ; exit 1 )
 	$(E) "[RUN]     Testing end2end_test"
 	$(Q) ./bins/$(CONFIG)/end2end_test || ( echo test end2end_test failed ; exit 1 )
-	$(E) "[RUN]     Testing tips_client_test"
-	$(Q) ./bins/$(CONFIG)/tips_client_test || ( echo test tips_client_test failed ; exit 1 )
+	$(E) "[RUN]     Testing tips_publisher_test"
+	$(Q) ./bins/$(CONFIG)/tips_publisher_test || ( echo test tips_publisher_test failed ; exit 1 )
+	$(E) "[RUN]     Testing tips_subscriber_test"
+	$(Q) ./bins/$(CONFIG)/tips_subscriber_test || ( echo test tips_subscriber_test failed ; exit 1 )
 	$(E) "[RUN]     Testing qps_client"
 	$(Q) ./bins/$(CONFIG)/qps_client || ( echo test qps_client failed ; exit 1 )
 	$(E) "[RUN]     Testing qps_server"
@@ -1013,6 +1024,8 @@
 	$(Q) $(STRIP) libs/$(CONFIG)/libgrpc.a
 	$(E) "[STRIP]   Stripping libgrpc_unsecure.a"
 	$(Q) $(STRIP) libs/$(CONFIG)/libgrpc_unsecure.a
+	$(E) "[STRIP]   Stripping libgrpc_csharp_ext.a"
+	$(Q) $(STRIP) libs/$(CONFIG)/libgrpc_csharp_ext.a
 endif
 
 strip-static_cxx: static_cxx
@@ -1029,6 +1042,8 @@
 	$(Q) $(STRIP) libs/$(CONFIG)/libgrpc.$(SHARED_EXT)
 	$(E) "[STRIP]   Stripping libgrpc_unsecure.so"
 	$(Q) $(STRIP) libs/$(CONFIG)/libgrpc_unsecure.$(SHARED_EXT)
+	$(E) "[STRIP]   Stripping libgrpc_csharp_ext.so"
+	$(Q) $(STRIP) libs/$(CONFIG)/libgrpc_csharp_ext.$(SHARED_EXT)
 endif
 
 strip-shared_cxx: shared_cxx
@@ -1134,6 +1149,8 @@
 	$(Q) $(INSTALL) libs/$(CONFIG)/libgrpc.a $(prefix)/lib/libgrpc.a
 	$(E) "[INSTALL] Installing libgrpc_unsecure.a"
 	$(Q) $(INSTALL) libs/$(CONFIG)/libgrpc_unsecure.a $(prefix)/lib/libgrpc_unsecure.a
+	$(E) "[INSTALL] Installing libgrpc_csharp_ext.a"
+	$(Q) $(INSTALL) libs/$(CONFIG)/libgrpc_csharp_ext.a $(prefix)/lib/libgrpc_csharp_ext.a
 
 install-static_cxx: static_cxx strip-static_cxx
 	$(E) "[INSTALL] Installing libgrpc++.a"
@@ -1173,6 +1190,17 @@
 	$(Q) ln -sf libgrpc_unsecure.$(SHARED_EXT) $(prefix)/lib/libgrpc_unsecure.so
 endif
 endif
+ifeq ($(SYSTEM),MINGW32)
+	$(E) "[INSTALL] Installing grpc_csharp_ext.$(SHARED_EXT)"
+	$(Q) $(INSTALL) libs/$(CONFIG)/grpc_csharp_ext.$(SHARED_EXT) $(prefix)/lib/grpc_csharp_ext.$(SHARED_EXT)
+	$(Q) $(INSTALL) libs/$(CONFIG)/libgrpc_csharp_ext-imp.a $(prefix)/lib/libgrpc_csharp_ext-imp.a
+else
+	$(E) "[INSTALL] Installing libgrpc_csharp_ext.$(SHARED_EXT)"
+	$(Q) $(INSTALL) libs/$(CONFIG)/libgrpc_csharp_ext.$(SHARED_EXT) $(prefix)/lib/libgrpc_csharp_ext.$(SHARED_EXT)
+ifneq ($(SYSTEM),Darwin)
+	$(Q) ln -sf libgrpc_csharp_ext.$(SHARED_EXT) $(prefix)/lib/libgrpc_csharp_ext.so
+endif
+endif
 ifneq ($(SYSTEM),MINGW32)
 ifneq ($(SYSTEM),Darwin)
 	$(Q) ldconfig
@@ -1210,6 +1238,12 @@
     src/core/support/cmdline.c \
     src/core/support/cpu_linux.c \
     src/core/support/cpu_posix.c \
+    src/core/support/env_linux.c \
+    src/core/support/env_posix.c \
+    src/core/support/env_win32.c \
+    src/core/support/file.c \
+    src/core/support/file_posix.c \
+    src/core/support/file_win32.c \
     src/core/support/histogram.c \
     src/core/support/host_port.c \
     src/core/support/log.c \
@@ -1251,11 +1285,7 @@
     include/grpc/support/sync_posix.h \
     include/grpc/support/sync_win32.h \
     include/grpc/support/thd.h \
-    include/grpc/support/thd_posix.h \
-    include/grpc/support/thd_win32.h \
     include/grpc/support/time.h \
-    include/grpc/support/time_posix.h \
-    include/grpc/support/time_win32.h \
     include/grpc/support/useful.h \
 
 LIBGPR_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBGPR_SRC))))
@@ -1299,6 +1329,12 @@
 objs/$(CONFIG)/src/core/support/cmdline.o: 
 objs/$(CONFIG)/src/core/support/cpu_linux.o: 
 objs/$(CONFIG)/src/core/support/cpu_posix.o: 
+objs/$(CONFIG)/src/core/support/env_linux.o: 
+objs/$(CONFIG)/src/core/support/env_posix.o: 
+objs/$(CONFIG)/src/core/support/env_win32.o: 
+objs/$(CONFIG)/src/core/support/file.o: 
+objs/$(CONFIG)/src/core/support/file_posix.o: 
+objs/$(CONFIG)/src/core/support/file_win32.o: 
 objs/$(CONFIG)/src/core/support/histogram.o: 
 objs/$(CONFIG)/src/core/support/host_port.o: 
 objs/$(CONFIG)/src/core/support/log.o: 
@@ -1433,6 +1469,7 @@
     src/core/statistics/hash_table.c \
     src/core/statistics/window_stats.c \
     src/core/surface/byte_buffer.c \
+    src/core/surface/byte_buffer_queue.c \
     src/core/surface/byte_buffer_reader.c \
     src/core/surface/call.c \
     src/core/surface/channel.c \
@@ -1559,6 +1596,7 @@
 src/core/statistics/hash_table.c: $(OPENSSL_DEP)
 src/core/statistics/window_stats.c: $(OPENSSL_DEP)
 src/core/surface/byte_buffer.c: $(OPENSSL_DEP)
+src/core/surface/byte_buffer_queue.c: $(OPENSSL_DEP)
 src/core/surface/byte_buffer_reader.c: $(OPENSSL_DEP)
 src/core/surface/call.c: $(OPENSSL_DEP)
 src/core/surface/channel.c: $(OPENSSL_DEP)
@@ -1707,6 +1745,7 @@
 objs/$(CONFIG)/src/core/statistics/hash_table.o: 
 objs/$(CONFIG)/src/core/statistics/window_stats.o: 
 objs/$(CONFIG)/src/core/surface/byte_buffer.o: 
+objs/$(CONFIG)/src/core/surface/byte_buffer_queue.o: 
 objs/$(CONFIG)/src/core/surface/byte_buffer_reader.o: 
 objs/$(CONFIG)/src/core/surface/call.o: 
 objs/$(CONFIG)/src/core/surface/channel.o: 
@@ -1874,6 +1913,7 @@
     src/core/statistics/hash_table.c \
     src/core/statistics/window_stats.c \
     src/core/surface/byte_buffer.c \
+    src/core/surface/byte_buffer_queue.c \
     src/core/surface/byte_buffer_reader.c \
     src/core/surface/call.c \
     src/core/surface/channel.c \
@@ -2005,6 +2045,7 @@
 objs/$(CONFIG)/src/core/statistics/hash_table.o: 
 objs/$(CONFIG)/src/core/statistics/window_stats.o: 
 objs/$(CONFIG)/src/core/surface/byte_buffer.o: 
+objs/$(CONFIG)/src/core/surface/byte_buffer_queue.o: 
 objs/$(CONFIG)/src/core/surface/byte_buffer_reader.o: 
 objs/$(CONFIG)/src/core/surface/call.o: 
 objs/$(CONFIG)/src/core/surface/channel.o: 
@@ -2242,7 +2283,8 @@
     gens/examples/tips/label.pb.cc \
     gens/examples/tips/empty.pb.cc \
     gens/examples/tips/pubsub.pb.cc \
-    examples/tips/client.cc \
+    examples/tips/publisher.cc \
+    examples/tips/subscriber.cc \
 
 
 LIBTIPS_CLIENT_LIB_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBTIPS_CLIENT_LIB_SRC))))
@@ -2260,7 +2302,8 @@
 examples/tips/label.proto: $(OPENSSL_DEP)
 examples/tips/empty.proto: $(OPENSSL_DEP)
 examples/tips/pubsub.proto: $(OPENSSL_DEP)
-examples/tips/client.cc: $(OPENSSL_DEP)
+examples/tips/publisher.cc: $(OPENSSL_DEP)
+examples/tips/subscriber.cc: $(OPENSSL_DEP)
 endif
 
 libs/$(CONFIG)/libtips_client_lib.a: $(ZLIB_DEP) $(OPENSSL_DEP) $(LIBTIPS_CLIENT_LIB_OBJS)
@@ -2287,7 +2330,73 @@
 
 
 
-objs/$(CONFIG)/examples/tips/client.o:     gens/examples/tips/label.pb.cc    gens/examples/tips/empty.pb.cc    gens/examples/tips/pubsub.pb.cc
+objs/$(CONFIG)/examples/tips/publisher.o:     gens/examples/tips/label.pb.cc    gens/examples/tips/empty.pb.cc    gens/examples/tips/pubsub.pb.cc
+objs/$(CONFIG)/examples/tips/subscriber.o:     gens/examples/tips/label.pb.cc    gens/examples/tips/empty.pb.cc    gens/examples/tips/pubsub.pb.cc
+
+
+LIBGRPC_CSHARP_EXT_SRC = \
+    src/csharp/ext/grpc_csharp_ext.c \
+
+
+LIBGRPC_CSHARP_EXT_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBGRPC_CSHARP_EXT_SRC))))
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure libraries if you don't have OpenSSL with ALPN.
+
+libs/$(CONFIG)/libgrpc_csharp_ext.a: openssl_dep_error
+
+ifeq ($(SYSTEM),MINGW32)
+libs/$(CONFIG)/grpc_csharp_ext.$(SHARED_EXT): openssl_dep_error
+else
+libs/$(CONFIG)/libgrpc_csharp_ext.$(SHARED_EXT): openssl_dep_error
+endif
+
+else
+
+ifneq ($(OPENSSL_DEP),)
+src/csharp/ext/grpc_csharp_ext.c: $(OPENSSL_DEP)
+endif
+
+libs/$(CONFIG)/libgrpc_csharp_ext.a: $(ZLIB_DEP) $(OPENSSL_DEP) $(LIBGRPC_CSHARP_EXT_OBJS)
+	$(E) "[AR]      Creating $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) rm -f libs/$(CONFIG)/libgrpc_csharp_ext.a
+	$(Q) $(AR) rcs libs/$(CONFIG)/libgrpc_csharp_ext.a $(LIBGRPC_CSHARP_EXT_OBJS)
+ifeq ($(SYSTEM),Darwin)
+	$(Q) ranlib libs/$(CONFIG)/libgrpc_csharp_ext.a 
+endif
+
+
+
+ifeq ($(SYSTEM),MINGW32)
+libs/$(CONFIG)/grpc_csharp_ext.$(SHARED_EXT): $(LIBGRPC_CSHARP_EXT_OBJS)  $(ZLIB_DEP)libs/$(CONFIG)/gpr.$(SHARED_EXT)libs/$(CONFIG)/grpc.$(SHARED_EXT) $(OPENSSL_DEP)
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) -Llibs/$(CONFIG) -shared -Wl,--output-def=libs/$(CONFIG)/grpc_csharp_ext.def -Wl,--out-implib=libs/$(CONFIG)/libgrpc_csharp_ext-imp.a -o libs/$(CONFIG)/grpc_csharp_ext.$(SHARED_EXT) $(LIBGRPC_CSHARP_EXT_OBJS) $(LDLIBS) $(LDLIBS_SECURE) $(OPENSSL_MERGE_LIBS) -lgpr-imp -lgrpc-imp
+else
+libs/$(CONFIG)/libgrpc_csharp_ext.$(SHARED_EXT): $(LIBGRPC_CSHARP_EXT_OBJS)  $(ZLIB_DEP) libs/$(CONFIG)/libgpr.$(SHARED_EXT) libs/$(CONFIG)/libgrpc.$(SHARED_EXT) $(OPENSSL_DEP)
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+ifeq ($(SYSTEM),Darwin)
+	$(Q) $(LD) $(LDFLAGS) -Llibs/$(CONFIG) -dynamiclib -o libs/$(CONFIG)/libgrpc_csharp_ext.$(SHARED_EXT) $(LIBGRPC_CSHARP_EXT_OBJS) $(LDLIBS) $(LDLIBS_SECURE) $(OPENSSL_MERGE_LIBS) -lgpr -lgrpc
+else
+	$(Q) $(LD) $(LDFLAGS) -Llibs/$(CONFIG) -shared -Wl,-soname,libgrpc_csharp_ext.so.0 -o libs/$(CONFIG)/libgrpc_csharp_ext.$(SHARED_EXT) $(LIBGRPC_CSHARP_EXT_OBJS) $(LDLIBS) $(LDLIBS_SECURE) $(OPENSSL_MERGE_LIBS) -lgpr -lgrpc
+	$(Q) ln -sf libgrpc_csharp_ext.$(SHARED_EXT) libs/$(CONFIG)/libgrpc_csharp_ext.so.0
+	$(Q) ln -sf libgrpc_csharp_ext.$(SHARED_EXT) libs/$(CONFIG)/libgrpc_csharp_ext.so
+endif
+endif
+
+
+endif
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(LIBGRPC_CSHARP_EXT_OBJS:.o=.dep)
+endif
+endif
+
+objs/$(CONFIG)/src/csharp/ext/grpc_csharp_ext.o: 
 
 
 LIBEND2END_FIXTURE_CHTTP2_FAKE_SECURITY_SRC = \
@@ -4230,6 +4339,68 @@
 endif
 
 
+GPR_FILE_TEST_SRC = \
+    test/core/support/file_test.c \
+
+GPR_FILE_TEST_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(GPR_FILE_TEST_SRC))))
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL with ALPN.
+
+bins/$(CONFIG)/gpr_file_test: openssl_dep_error
+
+else
+
+bins/$(CONFIG)/gpr_file_test: $(GPR_FILE_TEST_OBJS) libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(GPR_FILE_TEST_OBJS) libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o bins/$(CONFIG)/gpr_file_test
+
+endif
+
+objs/$(CONFIG)/test/core/support/file_test.o:  libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+
+deps_gpr_file_test: $(GPR_FILE_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(GPR_FILE_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
+GPR_ENV_TEST_SRC = \
+    test/core/support/env_test.c \
+
+GPR_ENV_TEST_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(GPR_ENV_TEST_SRC))))
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL with ALPN.
+
+bins/$(CONFIG)/gpr_env_test: openssl_dep_error
+
+else
+
+bins/$(CONFIG)/gpr_env_test: $(GPR_ENV_TEST_OBJS) libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(GPR_ENV_TEST_OBJS) libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o bins/$(CONFIG)/gpr_env_test
+
+endif
+
+objs/$(CONFIG)/test/core/support/env_test.o:  libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+
+deps_gpr_env_test: $(GPR_ENV_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(GPR_ENV_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 GPR_SLICE_BUFFER_TEST_SRC = \
     test/core/support/slice_buffer_test.c \
 
@@ -4881,6 +5052,99 @@
 endif
 
 
+JSON_REWRITE_SRC = \
+    test/core/json/json_rewrite.c \
+
+JSON_REWRITE_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(JSON_REWRITE_SRC))))
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL with ALPN.
+
+bins/$(CONFIG)/json_rewrite: openssl_dep_error
+
+else
+
+bins/$(CONFIG)/json_rewrite: $(JSON_REWRITE_OBJS) libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(JSON_REWRITE_OBJS) libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o bins/$(CONFIG)/json_rewrite
+
+endif
+
+objs/$(CONFIG)/test/core/json/json_rewrite.o:  libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr.a
+
+deps_json_rewrite: $(JSON_REWRITE_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(JSON_REWRITE_OBJS:.o=.dep)
+endif
+endif
+
+
+JSON_REWRITE_TEST_SRC = \
+    test/core/json/json_rewrite_test.c \
+
+JSON_REWRITE_TEST_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(JSON_REWRITE_TEST_SRC))))
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL with ALPN.
+
+bins/$(CONFIG)/json_rewrite_test: openssl_dep_error
+
+else
+
+bins/$(CONFIG)/json_rewrite_test: $(JSON_REWRITE_TEST_OBJS) libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(JSON_REWRITE_TEST_OBJS) libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o bins/$(CONFIG)/json_rewrite_test
+
+endif
+
+objs/$(CONFIG)/test/core/json/json_rewrite_test.o:  libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+
+deps_json_rewrite_test: $(JSON_REWRITE_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(JSON_REWRITE_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
+JSON_TEST_SRC = \
+    test/core/json/json_test.c \
+
+JSON_TEST_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(JSON_TEST_SRC))))
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL with ALPN.
+
+bins/$(CONFIG)/json_test: openssl_dep_error
+
+else
+
+bins/$(CONFIG)/json_test: $(JSON_TEST_OBJS) libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(JSON_TEST_OBJS) libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o bins/$(CONFIG)/json_test
+
+endif
+
+objs/$(CONFIG)/test/core/json/json_test.o:  libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+
+deps_json_test: $(JSON_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(JSON_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 LAME_CLIENT_TEST_SRC = \
     test/core/surface/lame_client_test.c \
 
@@ -5408,99 +5672,6 @@
 endif
 
 
-JSON_TEST_SRC = \
-    test/core/json/json_test.c \
-
-JSON_TEST_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(JSON_TEST_SRC))))
-
-ifeq ($(NO_SECURE),true)
-
-# You can't build secure targets if you don't have OpenSSL with ALPN.
-
-bins/$(CONFIG)/json_test: openssl_dep_error
-
-else
-
-bins/$(CONFIG)/json_test: $(JSON_TEST_OBJS) libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
-	$(E) "[LD]      Linking $@"
-	$(Q) mkdir -p `dirname $@`
-	$(Q) $(LD) $(LDFLAGS) $(JSON_TEST_OBJS) libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o bins/$(CONFIG)/json_test
-
-endif
-
-objs/$(CONFIG)/test/core/json/json_test.o:  libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
-
-deps_json_test: $(JSON_TEST_OBJS:.o=.dep)
-
-ifneq ($(NO_SECURE),true)
-ifneq ($(NO_DEPS),true)
--include $(JSON_TEST_OBJS:.o=.dep)
-endif
-endif
-
-
-JSON_REWRITE_SRC = \
-    test/core/json/json_rewrite.c \
-
-JSON_REWRITE_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(JSON_REWRITE_SRC))))
-
-ifeq ($(NO_SECURE),true)
-
-# You can't build secure targets if you don't have OpenSSL with ALPN.
-
-bins/$(CONFIG)/json_rewrite: openssl_dep_error
-
-else
-
-bins/$(CONFIG)/json_rewrite: $(JSON_REWRITE_OBJS) libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr.a
-	$(E) "[LD]      Linking $@"
-	$(Q) mkdir -p `dirname $@`
-	$(Q) $(LD) $(LDFLAGS) $(JSON_REWRITE_OBJS) libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o bins/$(CONFIG)/json_rewrite
-
-endif
-
-objs/$(CONFIG)/test/core/json/json_rewrite.o:  libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr.a
-
-deps_json_rewrite: $(JSON_REWRITE_OBJS:.o=.dep)
-
-ifneq ($(NO_SECURE),true)
-ifneq ($(NO_DEPS),true)
--include $(JSON_REWRITE_OBJS:.o=.dep)
-endif
-endif
-
-
-JSON_REWRITE_TEST_SRC = \
-    test/core/json/json_rewrite_test.c \
-
-JSON_REWRITE_TEST_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(JSON_REWRITE_TEST_SRC))))
-
-ifeq ($(NO_SECURE),true)
-
-# You can't build secure targets if you don't have OpenSSL with ALPN.
-
-bins/$(CONFIG)/json_rewrite_test: openssl_dep_error
-
-else
-
-bins/$(CONFIG)/json_rewrite_test: $(JSON_REWRITE_TEST_OBJS) libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
-	$(E) "[LD]      Linking $@"
-	$(Q) mkdir -p `dirname $@`
-	$(Q) $(LD) $(LDFLAGS) $(JSON_REWRITE_TEST_OBJS) libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o bins/$(CONFIG)/json_rewrite_test
-
-endif
-
-objs/$(CONFIG)/test/core/json/json_rewrite_test.o:  libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
-
-deps_json_rewrite_test: $(JSON_REWRITE_TEST_OBJS:.o=.dep)
-
-ifneq ($(NO_SECURE),true)
-ifneq ($(NO_DEPS),true)
--include $(JSON_REWRITE_TEST_OBJS:.o=.dep)
-endif
-endif
-
-
 CHANNEL_ARGUMENTS_TEST_SRC = \
     test/cpp/client/channel_arguments_test.cc \
 
@@ -5690,7 +5861,7 @@
 
 
 TIPS_CLIENT_SRC = \
-    examples/tips/client_main.cc \
+    examples/tips/main.cc \
 
 TIPS_CLIENT_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(TIPS_CLIENT_SRC))))
 
@@ -5709,7 +5880,7 @@
 
 endif
 
-objs/$(CONFIG)/examples/tips/client_main.o:  libs/$(CONFIG)/libtips_client_lib.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+objs/$(CONFIG)/examples/tips/main.o:  libs/$(CONFIG)/libtips_client_lib.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
 
 deps_tips_client: $(TIPS_CLIENT_OBJS:.o=.dep)
 
@@ -5720,33 +5891,64 @@
 endif
 
 
-TIPS_CLIENT_TEST_SRC = \
-    examples/tips/client_test.cc \
+TIPS_PUBLISHER_TEST_SRC = \
+    examples/tips/publisher_test.cc \
 
-TIPS_CLIENT_TEST_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(TIPS_CLIENT_TEST_SRC))))
+TIPS_PUBLISHER_TEST_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(TIPS_PUBLISHER_TEST_SRC))))
 
 ifeq ($(NO_SECURE),true)
 
 # You can't build secure targets if you don't have OpenSSL with ALPN.
 
-bins/$(CONFIG)/tips_client_test: openssl_dep_error
+bins/$(CONFIG)/tips_publisher_test: openssl_dep_error
 
 else
 
-bins/$(CONFIG)/tips_client_test: $(TIPS_CLIENT_TEST_OBJS) libs/$(CONFIG)/libtips_client_lib.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+bins/$(CONFIG)/tips_publisher_test: $(TIPS_PUBLISHER_TEST_OBJS) libs/$(CONFIG)/libtips_client_lib.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
 	$(E) "[LD]      Linking $@"
 	$(Q) mkdir -p `dirname $@`
-	$(Q) $(LDXX) $(LDFLAGS) $(TIPS_CLIENT_TEST_OBJS) $(GTEST_LIB) libs/$(CONFIG)/libtips_client_lib.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS) $(LDLIBS_SECURE) -o bins/$(CONFIG)/tips_client_test
+	$(Q) $(LDXX) $(LDFLAGS) $(TIPS_PUBLISHER_TEST_OBJS) $(GTEST_LIB) libs/$(CONFIG)/libtips_client_lib.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS) $(LDLIBS_SECURE) -o bins/$(CONFIG)/tips_publisher_test
 
 endif
 
-objs/$(CONFIG)/examples/tips/client_test.o:  libs/$(CONFIG)/libtips_client_lib.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+objs/$(CONFIG)/examples/tips/publisher_test.o:  libs/$(CONFIG)/libtips_client_lib.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
 
-deps_tips_client_test: $(TIPS_CLIENT_TEST_OBJS:.o=.dep)
+deps_tips_publisher_test: $(TIPS_PUBLISHER_TEST_OBJS:.o=.dep)
 
 ifneq ($(NO_SECURE),true)
 ifneq ($(NO_DEPS),true)
--include $(TIPS_CLIENT_TEST_OBJS:.o=.dep)
+-include $(TIPS_PUBLISHER_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
+TIPS_SUBSCRIBER_TEST_SRC = \
+    examples/tips/subscriber_test.cc \
+
+TIPS_SUBSCRIBER_TEST_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(TIPS_SUBSCRIBER_TEST_SRC))))
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL with ALPN.
+
+bins/$(CONFIG)/tips_subscriber_test: openssl_dep_error
+
+else
+
+bins/$(CONFIG)/tips_subscriber_test: $(TIPS_SUBSCRIBER_TEST_OBJS) libs/$(CONFIG)/libtips_client_lib.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(TIPS_SUBSCRIBER_TEST_OBJS) $(GTEST_LIB) libs/$(CONFIG)/libtips_client_lib.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS) $(LDLIBS_SECURE) -o bins/$(CONFIG)/tips_subscriber_test
+
+endif
+
+objs/$(CONFIG)/examples/tips/subscriber_test.o:  libs/$(CONFIG)/libtips_client_lib.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++.a libs/$(CONFIG)/libgrpc.a libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgpr.a
+
+deps_tips_subscriber_test: $(TIPS_SUBSCRIBER_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(TIPS_SUBSCRIBER_TEST_OBJS:.o=.dep)
 endif
 endif
 
diff --git a/README.md b/README.md
index fa39d3b..95825f6 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@
 (a collection of methods), and generate client and server side interfaces
 which they use on the client-side and implement on the server side.
 
-By default, gRPC uses [Protocol Buffers](github.com/google/protobuf) as the
+By default, gRPC uses [Protocol Buffers](https://github.com/google/protobuf) as the
 Interface Definition Language (IDL) for describing both the service interface
 and the structure of the payload messages. It is possible to use other 
 alternatives if desired.
@@ -67,7 +67,7 @@
 A gRPC RPC comprises of a bidirectional stream of messages, initiated by the client. In the client-to-server direction, this stream begins with a mandatory `Call Header`, followed by optional `Initial-Metadata`, followed by zero or more `Payload Messages`. The server-to-client direction contains an optional `Initial-Metadata`, followed by zero or more `Payload Messages` terminated with a mandatory `Status` and optional `Status-Metadata` (a.k.a.,`Trailing-Metadata`).
 
 ## Implementation over HTTP/2
-The abstract protocol defined above is implemented over [HTTP/2](https://http2.github.io/). gRPC bidirectional streams are mapped to HTTP/2 streams. The contents of `Call Header` and `Initial Metadata` are sent as HTTP/2 headers and subject to HPAC compression. `Payload Messages` are serialized into a byte stream of length prefixed gRPC frames which are then fragmented into HTTP/2 frames at the sender and reassembled at the receiver. `Status` and `Trailing-Metadata` are sent as HTTP/2 trailing headers (a.k.a., trailers).     
+The abstract protocol defined above is implemented over [HTTP/2](https://http2.github.io/). gRPC bidirectional streams are mapped to HTTP/2 streams. The contents of `Call Header` and `Initial Metadata` are sent as HTTP/2 headers and subject to HPACK compression. `Payload Messages` are serialized into a byte stream of length prefixed gRPC frames which are then fragmented into HTTP/2 frames at the sender and reassembled at the receiver. `Status` and `Trailing-Metadata` are sent as HTTP/2 trailing headers (a.k.a., trailers).     
 
 ## Flow Control
-gRPC inherits the flow control mchanims in HTTP/2 and uses them to enable fine-grained control of the amount of memory used for buffering in-flight messages.
+gRPC inherits the flow control mechanisms in HTTP/2 and uses them to enable fine-grained control of the amount of memory used for buffering in-flight messages.
diff --git a/build.json b/build.json
index 40187b9..170b148 100644
--- a/build.json
+++ b/build.json
@@ -61,8 +61,8 @@
         "src/core/iomgr/tcp_posix.h",
         "src/core/iomgr/tcp_server.h",
         "src/core/iomgr/time_averaged_stats.h",
-        "src/core/iomgr/wakeup_fd_posix.h",
         "src/core/iomgr/wakeup_fd_pipe.h",
+        "src/core/iomgr/wakeup_fd_posix.h",
         "src/core/json/json.h",
         "src/core/json/json_common.h",
         "src/core/json/json_reader.h",
@@ -73,6 +73,7 @@
         "src/core/statistics/census_tracing.h",
         "src/core/statistics/hash_table.h",
         "src/core/statistics/window_stats.h",
+        "src/core/surface/byte_buffer_queue.h",
         "src/core/surface/call.h",
         "src/core/surface/channel.h",
         "src/core/surface/client.h",
@@ -159,6 +160,7 @@
         "src/core/statistics/hash_table.c",
         "src/core/statistics/window_stats.c",
         "src/core/surface/byte_buffer.c",
+        "src/core/surface/byte_buffer_queue.c",
         "src/core/surface/byte_buffer_reader.c",
         "src/core/surface/call.c",
         "src/core/surface/channel.c",
@@ -220,15 +222,13 @@
         "include/grpc/support/sync_posix.h",
         "include/grpc/support/sync_win32.h",
         "include/grpc/support/thd.h",
-        "include/grpc/support/thd_posix.h",
-        "include/grpc/support/thd_win32.h",
         "include/grpc/support/time.h",
-        "include/grpc/support/time_posix.h",
-        "include/grpc/support/time_win32.h",
         "include/grpc/support/useful.h"
       ],
       "headers": [
         "src/core/support/cpu.h",
+        "src/core/support/env.h",
+        "src/core/support/file.h",
         "src/core/support/murmur_hash.h",
         "src/core/support/string.h",
         "src/core/support/thd_internal.h"
@@ -239,6 +239,12 @@
         "src/core/support/cmdline.c",
         "src/core/support/cpu_linux.c",
         "src/core/support/cpu_posix.c",
+        "src/core/support/env_linux.c",
+        "src/core/support/env_posix.c",
+        "src/core/support/env_win32.c",
+        "src/core/support/file.c",
+        "src/core/support/file_posix.c",
+        "src/core/support/file_win32.c",
         "src/core/support/histogram.c",
         "src/core/support/host_port.c",
         "src/core/support/log.c",
@@ -433,13 +439,26 @@
         "examples/tips/label.proto",
         "examples/tips/empty.proto",
         "examples/tips/pubsub.proto",
-        "examples/tips/client.cc"
+        "examples/tips/publisher.cc",
+        "examples/tips/subscriber.cc"
       ],
       "deps": [
         "grpc++",
         "grpc",
         "gpr"
       ]
+    },
+    {
+      "name": "grpc_csharp_ext",
+      "build": "all",
+      "language": "c",
+      "deps": [
+        "gpr",
+        "grpc"
+      ],
+      "src": [
+        "src/csharp/ext/grpc_csharp_ext.c"
+      ]
     }
   ],
   "targets": [
@@ -913,6 +932,30 @@
       ]
     },
     {
+      "name": "gpr_file_test",
+      "build": "test",
+      "language": "c",
+      "src": [
+        "test/core/support/file_test.c"
+      ],
+      "deps": [
+        "gpr_test_util",
+        "gpr"
+      ]
+    },
+    {
+      "name": "gpr_env_test",
+      "build": "test",
+      "language": "c",
+      "src": [
+        "test/core/support/env_test.c"
+      ],
+      "deps": [
+        "gpr_test_util",
+        "gpr"
+      ]
+    },
+    {
       "name": "gpr_slice_buffer_test",
       "build": "test",
       "language": "c",
@@ -1193,6 +1236,48 @@
       ]
     },
     {
+      "name": "json_rewrite",
+      "build": "test",
+      "language": "c",
+      "src": [
+        "test/core/json/json_rewrite.c"
+      ],
+      "deps": [
+        "grpc",
+        "gpr"
+      ],
+      "run": false
+    },
+    {
+      "name": "json_rewrite_test",
+      "build": "test",
+      "language": "c",
+      "src": [
+        "test/core/json/json_rewrite_test.c"
+      ],
+      "deps": [
+        "grpc_test_util",
+        "grpc",
+        "gpr_test_util",
+        "gpr"
+      ],
+      "run": false
+    },
+    {
+      "name": "json_test",
+      "build": "test",
+      "language": "c",
+      "src": [
+        "test/core/json/json_test.c"
+      ],
+      "deps": [
+        "grpc_test_util",
+        "grpc",
+        "gpr_test_util",
+        "gpr"
+      ]
+    },
+    {
       "name": "lame_client_test",
       "build": "test",
       "language": "c",
@@ -1429,48 +1514,6 @@
       ]
     },
     {
-      "name": "json_test",
-      "build": "test",
-      "language": "c",
-      "src": [
-        "test/core/json/json_test.c"
-      ],
-      "deps": [
-        "grpc_test_util",
-        "grpc",
-        "gpr_test_util",
-        "gpr"
-      ]
-    },
-    {
-      "name": "json_rewrite",
-      "build": "test",
-      "language": "c",
-      "src": [
-        "test/core/json/json_rewrite.c"
-      ],
-      "deps": [
-        "grpc",
-        "gpr"
-      ],
-      "run": false
-    },
-    {
-      "name": "json_rewrite_test",
-      "build": "test",
-      "language": "c",
-      "src": [
-        "test/core/json/json_rewrite_test.c"
-      ],
-      "deps": [
-        "grpc_test_util",
-        "grpc",
-        "gpr_test_util",
-        "gpr"
-      ],
-      "run": false
-    },
-    {
       "name": "channel_arguments_test",
       "build": "test",
       "language": "c++",
@@ -1570,10 +1613,27 @@
     {
       "name": "tips_client",
       "build": "test",
-      "run": false,
       "language": "c++",
       "src": [
-        "examples/tips/client_main.cc"
+        "examples/tips/main.cc"
+      ],
+      "deps": [
+        "tips_client_lib",
+        "grpc++_test_util",
+        "grpc_test_util",
+        "grpc++",
+        "grpc",
+        "gpr_test_util",
+        "gpr"
+      ],
+      "run": false
+    },
+    {
+      "name": "tips_publisher_test",
+      "build": "test",
+      "language": "c++",
+      "src": [
+        "examples/tips/publisher_test.cc"
       ],
       "deps": [
         "tips_client_lib",
@@ -1586,11 +1646,11 @@
       ]
     },
     {
-      "name": "tips_client_test",
+      "name": "tips_subscriber_test",
       "build": "test",
       "language": "c++",
       "src": [
-        "examples/tips/client_test.cc"
+        "examples/tips/subscriber_test.cc"
       ],
       "deps": [
         "tips_client_lib",
diff --git a/examples/tips/README b/examples/tips/README
new file mode 100644
index 0000000..ae7d096
--- /dev/null
+++ b/examples/tips/README
@@ -0,0 +1,26 @@
+C++ Client implementation for Cloud Pub/Sub service (TIPS)
+(https://developers.google.com/apis-explorer/#p/pubsub/v1beta1/).
+
+"Google Cloud Pub/Sub" API needs to be enabled at
+https://console.developers.google.com/project to open the access for a client. 
+Select the project name, select the "APIs" under "APIs & auth", and turn
+on "Google Cloud Pub/Sub" API. 
+
+To run the client from Google Compute Engine (GCE), the GCE instance needs to
+be created with scope "https://www.googleapis.com/auth/cloud-platform" as below:
+
+gcloud compute instances create instance-name 
+    --image debian-7 --scopes https://www.googleapis.com/auth/cloud-platform
+   
+To run the client from GCE:
+make tips_client
+bins/opt/tips_client --project_id="your project id"
+    
+A service account credential is required to run the client from other
+environments, which can be generated as a JSON key file from
+https://console.developers.google.com/project/. To run the client with a service 
+account credential:
+
+bins/opt/tips_client
+    --project_id="your project id"
+    --service_account_key_file="absolute path to the JSON key file"
diff --git a/examples/tips/client_main.cc b/examples/tips/client_main.cc
deleted file mode 100644
index 5a3a0da..0000000
--- a/examples/tips/client_main.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- *
- * Copyright 2014, 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 <chrono>
-#include <fstream>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <thread>
-
-#include <grpc/grpc.h>
-#include <grpc/support/log.h>
-#include <google/gflags.h>
-#include <grpc++/channel_interface.h>
-#include <grpc++/create_channel.h>
-#include <grpc++/credentials.h>
-#include <grpc++/status.h>
-
-#include "examples/tips/client.h"
-#include "test/cpp/util/create_test_channel.h"
-
-DEFINE_int32(server_port, 443, "Server port.");
-DEFINE_string(server_host,
-              "pubsub-staging.googleapis.com", "Server host to connect to");
-DEFINE_string(service_account_key_file, "",
-              "Path to service account json key file.");
-DEFINE_string(oauth_scope, "", "Scope for OAuth tokens.");
-
-grpc::string GetServiceAccountJsonKey() {
-  static grpc::string json_key;
-  if (json_key.empty()) {
-    std::ifstream json_key_file(FLAGS_service_account_key_file);
-    std::stringstream key_stream;
-    key_stream << json_key_file.rdbuf();
-    json_key = key_stream.str();
-  }
-  return json_key;
-}
-
-int main(int argc, char** argv) {
-  grpc_init();
-  google::ParseCommandLineFlags(&argc, &argv, true);
-  gpr_log(GPR_INFO, "Start TIPS client");
-
-  const int host_port_buf_size = 1024;
-  char host_port[host_port_buf_size];
-  snprintf(host_port, host_port_buf_size, "%s:%d", FLAGS_server_host.c_str(),
-           FLAGS_server_port);
-
-  std::unique_ptr<grpc::Credentials> creds;
-  if (FLAGS_service_account_key_file != "") {
-    grpc::string json_key = GetServiceAccountJsonKey();
-    creds = grpc::CredentialsFactory::ServiceAccountCredentials(
-        json_key, FLAGS_oauth_scope, std::chrono::hours(1));
-  } else {
-    creds = grpc::CredentialsFactory::ComputeEngineCredentials();
-  }
-
-  std::shared_ptr<grpc::ChannelInterface> channel(
-      grpc::CreateTestChannel(
-          host_port,
-          FLAGS_server_host,
-          true,                // enable SSL
-          true,                // use prod roots
-          creds));
-
-  grpc::examples::tips::Client client(channel);
-
-  grpc::Status s = client.CreateTopic("/topics/stoked-keyword-656/testtopics");
-  gpr_log(GPR_INFO, "return code %d, %s", s.code(), s.details().c_str());
-  GPR_ASSERT(s.IsOk());
-
-  s = client.GetTopic("/topics/stoked-keyword-656/testtopics");
-  gpr_log(GPR_INFO, "return code %d, %s", s.code(), s.details().c_str());
-  GPR_ASSERT(s.IsOk());
-
-  s = client.DeleteTopic("/topics/stoked-keyword-656/testtopics");
-  gpr_log(GPR_INFO, "return code %d, %s", s.code(), s.details().c_str());
-  GPR_ASSERT(s.IsOk());
-
-  channel.reset();
-  grpc_shutdown();
-  return 0;
-}
diff --git a/examples/tips/empty.proto b/examples/tips/empty.proto
index adf66b5..86aaa84 100644
--- a/examples/tips/empty.proto
+++ b/examples/tips/empty.proto
@@ -1,3 +1,5 @@
+// This file will be moved to a new location.
+
 syntax = "proto2";
 
 package proto2;
diff --git a/examples/tips/label.proto b/examples/tips/label.proto
index e93ac9d..6ac786f 100644
--- a/examples/tips/label.proto
+++ b/examples/tips/label.proto
@@ -1,3 +1,5 @@
+// This file will be moved to a new location.
+
 // Labels provide a way to associate user-defined metadata with various
 // objects.  Labels may be used to organize objects into non-hierarchical
 // groups; think metadata tags attached to mp3s.
diff --git a/examples/tips/main.cc b/examples/tips/main.cc
new file mode 100644
index 0000000..df9d984
--- /dev/null
+++ b/examples/tips/main.cc
@@ -0,0 +1,178 @@
+/*
+ *
+ * Copyright 2014, 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 <chrono>
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <thread>
+
+#include <grpc/grpc.h>
+#include <grpc/support/log.h>
+#include <google/gflags.h>
+#include <grpc++/channel_interface.h>
+#include <grpc++/create_channel.h>
+#include <grpc++/credentials.h>
+#include <grpc++/status.h>
+
+#include "examples/tips/publisher.h"
+#include "examples/tips/subscriber.h"
+#include "test/cpp/util/create_test_channel.h"
+
+DEFINE_int32(server_port, 443, "Server port.");
+DEFINE_string(server_host,
+              "pubsub-staging.googleapis.com", "Server host to connect to");
+DEFINE_string(project_id, "", "GCE project id such as stoked-keyword-656");
+DEFINE_string(service_account_key_file, "",
+              "Path to service account json key file.");
+DEFINE_string(oauth_scope,
+              "https://www.googleapis.com/auth/cloud-platform",
+              "Scope for OAuth tokens.");
+
+namespace {
+
+const char kTopic[] = "testtopics";
+const char kSubscriptionName[] = "testsubscription";
+const char kMessageData[] = "Test Data";
+
+}  // namespace
+
+grpc::string GetServiceAccountJsonKey() {
+  grpc::string json_key;
+  if (json_key.empty()) {
+    std::ifstream json_key_file(FLAGS_service_account_key_file);
+    std::stringstream key_stream;
+    key_stream << json_key_file.rdbuf();
+    json_key = key_stream.str();
+  }
+  return json_key;
+}
+
+int main(int argc, char** argv) {
+  grpc_init();
+  google::ParseCommandLineFlags(&argc, &argv, true);
+  gpr_log(GPR_INFO, "Start TIPS client");
+
+  std::ostringstream ss;
+
+  std::unique_ptr<grpc::Credentials> creds;
+  if (FLAGS_service_account_key_file != "") {
+    grpc::string json_key = GetServiceAccountJsonKey();
+    creds = grpc::CredentialsFactory::ServiceAccountCredentials(
+        json_key, FLAGS_oauth_scope, std::chrono::hours(1));
+  } else {
+    creds = grpc::CredentialsFactory::ComputeEngineCredentials();
+  }
+
+  ss << FLAGS_server_host << ":" << FLAGS_server_port;
+  std::shared_ptr<grpc::ChannelInterface> channel(
+      grpc::CreateTestChannel(
+          ss.str(),
+          FLAGS_server_host,
+          true,                // enable SSL
+          true,                // use prod roots
+          creds));
+
+  grpc::examples::tips::Publisher publisher(channel);
+  grpc::examples::tips::Subscriber subscriber(channel);
+
+  GPR_ASSERT(FLAGS_project_id != "");
+  ss.str("");
+  ss << "/topics/" << FLAGS_project_id << "/" << kTopic;
+  grpc::string topic = ss.str();
+
+  ss.str("");
+  ss << FLAGS_project_id << "/"  << kSubscriptionName;
+  grpc::string subscription_name = ss.str();
+
+  // Clean up test topic and subcription if they exist before.
+  grpc::string subscription_topic;
+  if (subscriber.GetSubscription(
+      subscription_name, &subscription_topic).IsOk()) {
+    subscriber.DeleteSubscription(subscription_name);
+  }
+  if (publisher.GetTopic(topic).IsOk()) publisher.DeleteTopic(topic);
+
+  grpc::Status s = publisher.CreateTopic(topic);
+  gpr_log(GPR_INFO, "Create topic returns code %d, %s",
+          s.code(), s.details().c_str());
+  GPR_ASSERT(s.IsOk());
+
+  s = publisher.GetTopic(topic);
+  gpr_log(GPR_INFO, "Get topic returns code %d, %s",
+          s.code(), s.details().c_str());
+  GPR_ASSERT(s.IsOk());
+
+  std::vector<grpc::string> topics;
+  s = publisher.ListTopics(FLAGS_project_id, &topics);
+  gpr_log(GPR_INFO, "List topic returns code %d, %s",
+          s.code(), s.details().c_str());
+  bool topic_found = false;
+  for (unsigned int i = 0; i < topics.size(); i++) {
+    if (topics[i] == topic) topic_found = true;
+    gpr_log(GPR_INFO, "topic: %s", topics[i].c_str());
+  }
+  GPR_ASSERT(s.IsOk());
+  GPR_ASSERT(topic_found);
+
+  s = subscriber.CreateSubscription(topic, subscription_name);
+  gpr_log(GPR_INFO, "create subscrption returns code %d, %s",
+          s.code(), s.details().c_str());
+  GPR_ASSERT(s.IsOk());
+
+  s = publisher.Publish(topic, kMessageData);
+  gpr_log(GPR_INFO, "Publish %s returns code %d, %s",
+          kMessageData, s.code(), s.details().c_str());
+  GPR_ASSERT(s.IsOk());
+
+  grpc::string data;
+  s = subscriber.Pull(subscription_name, &data);
+  gpr_log(GPR_INFO, "Pull %s", data.c_str());
+
+  s =  subscriber.DeleteSubscription(subscription_name);
+  gpr_log(GPR_INFO, "Delete subscription returns code %d, %s",
+          s.code(), s.details().c_str());
+  GPR_ASSERT(s.IsOk());
+
+  s = publisher.DeleteTopic(topic);
+  gpr_log(GPR_INFO, "Delete topic returns code %d, %s",
+          s.code(), s.details().c_str());
+  GPR_ASSERT(s.IsOk());
+
+  subscriber.Shutdown();
+  publisher.Shutdown();
+  channel.reset();
+  grpc_shutdown();
+  return 0;
+}
diff --git a/examples/tips/client.cc b/examples/tips/publisher.cc
similarity index 68%
rename from examples/tips/client.cc
rename to examples/tips/publisher.cc
index f9d5319..eae8731 100644
--- a/examples/tips/client.cc
+++ b/examples/tips/publisher.cc
@@ -31,9 +31,11 @@
  *
  */
 
+#include <sstream>
+
 #include <grpc++/client_context.h>
 
-#include "examples/tips/client.h"
+#include "examples/tips/publisher.h"
 
 using tech::pubsub::Topic;
 using tech::pubsub::DeleteTopicRequest;
@@ -41,16 +43,22 @@
 using tech::pubsub::PublisherService;
 using tech::pubsub::ListTopicsRequest;
 using tech::pubsub::ListTopicsResponse;
+using tech::pubsub::PublishRequest;
+using tech::pubsub::PubsubMessage;
 
 namespace grpc {
 namespace examples {
 namespace tips {
 
-Client::Client(std::shared_ptr<ChannelInterface> channel)
+Publisher::Publisher(std::shared_ptr<ChannelInterface> channel)
     : stub_(PublisherService::NewStub(channel)) {
 }
 
-Status Client::CreateTopic(grpc::string topic) {
+void Publisher::Shutdown() {
+  stub_.reset();
+}
+
+Status Publisher::CreateTopic(const grpc::string& topic) {
   Topic request;
   Topic response;
   request.set_name(topic);
@@ -59,15 +67,28 @@
   return stub_->CreateTopic(&context, request, &response);
 }
 
-Status Client::ListTopics() {
+Status Publisher::ListTopics(const grpc::string& project_id,
+                             std::vector<grpc::string>* topics) {
   ListTopicsRequest request;
   ListTopicsResponse response;
   ClientContext context;
 
-  return stub_->ListTopics(&context, request, &response);
+  std::ostringstream ss;
+  ss << "cloud.googleapis.com/project in (/projects/" << project_id << ")";
+  request.set_query(ss.str());
+
+  Status s = stub_->ListTopics(&context, request, &response);
+
+  tech::pubsub::Topic topic;
+  for (int i = 0; i < response.topic_size(); i++) {
+    topic = response.topic(i);
+    topics->push_back(topic.name());
+  }
+
+  return s;
 }
 
-Status Client::GetTopic(grpc::string topic) {
+Status Publisher::GetTopic(const grpc::string& topic) {
   GetTopicRequest request;
   Topic response;
   ClientContext context;
@@ -77,7 +98,7 @@
   return stub_->GetTopic(&context, request, &response);
 }
 
-Status Client::DeleteTopic(grpc::string topic) {
+Status Publisher::DeleteTopic(const grpc::string& topic) {
   DeleteTopicRequest request;
   proto2::Empty response;
   ClientContext context;
@@ -87,6 +108,17 @@
   return stub_->DeleteTopic(&context, request, &response);
 }
 
+Status Publisher::Publish(const grpc::string& topic, const grpc::string& data) {
+  PublishRequest request;
+  proto2::Empty response;
+  ClientContext context;
+
+  request.mutable_message()->set_data(data);
+  request.set_topic(topic);
+
+  return stub_->Publish(&context, request, &response);
+}
+
 }  // namespace tips
 }  // namespace examples
 }  // namespace grpc
diff --git a/examples/tips/client.h b/examples/tips/publisher.h
similarity index 77%
rename from examples/tips/client.h
rename to examples/tips/publisher.h
index 661ee5c..d8d7353 100644
--- a/examples/tips/client.h
+++ b/examples/tips/publisher.h
@@ -31,8 +31,8 @@
  *
  */
 
-#ifndef __GRPCPP_EXAMPLES_TIPS_CLIENT_H_
-#define __GRPCPP_EXAMPLES_TIPS_CLIENT_H_
+#ifndef __GRPCPP_EXAMPLES_TIPS_PUBLISHER_H_
+#define __GRPCPP_EXAMPLES_TIPS_PUBLISHER_H_
 
 #include <grpc++/channel_interface.h>
 #include <grpc++/status.h>
@@ -43,13 +43,18 @@
 namespace examples {
 namespace tips {
 
-class Client {
+class Publisher {
  public:
-  Client(std::shared_ptr<grpc::ChannelInterface> channel);
-  Status CreateTopic(grpc::string topic);
-  Status GetTopic(grpc::string topic);
-  Status DeleteTopic(grpc::string topic);
-  Status ListTopics();
+  Publisher(std::shared_ptr<ChannelInterface> channel);
+  void Shutdown();
+
+  Status CreateTopic(const grpc::string& topic);
+  Status GetTopic(const grpc::string& topic);
+  Status DeleteTopic(const grpc::string& topic);
+  Status ListTopics(const grpc::string& project_id,
+                    std::vector<grpc::string>* topics);
+
+  Status Publish(const grpc::string& topic, const grpc::string& data);
 
  private:
   std::unique_ptr<tech::pubsub::PublisherService::Stub> stub_;
@@ -59,4 +64,4 @@
 }  // namespace examples
 }  // namespace grpc
 
-#endif  // __GRPCPP_EXAMPLES_TIPS_CLIENT_H_
+#endif  // __GRPCPP_EXAMPLES_TIPS_PUBLISHER_H_
diff --git a/examples/tips/client_test.cc b/examples/tips/publisher_test.cc
similarity index 60%
rename from examples/tips/client_test.cc
rename to examples/tips/publisher_test.cc
index 69238f2..e46576a 100644
--- a/examples/tips/client_test.cc
+++ b/examples/tips/publisher_test.cc
@@ -41,7 +41,7 @@
 #include <grpc++/status.h>
 #include <gtest/gtest.h>
 
-#include "examples/tips/client.h"
+#include "examples/tips/publisher.h"
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
@@ -51,9 +51,11 @@
 namespace testing {
 namespace {
 
+const char kProjectId[] = "project id";
 const char kTopic[] = "test topic";
+const char kMessageData[] = "test message data";
 
-class PublishServiceImpl : public tech::pubsub::PublisherService::Service {
+class PublisherServiceImpl : public tech::pubsub::PublisherService::Service {
  public:
   Status CreateTopic(::grpc::ServerContext* context,
                      const ::tech::pubsub::Topic* request,
@@ -61,34 +63,81 @@
     EXPECT_EQ(request->name(), kTopic);
     return Status::OK;
   }
+
+  Status Publish(ServerContext* context,
+                 const ::tech::pubsub::PublishRequest* request,
+                 ::proto2::Empty* response) override {
+    EXPECT_EQ(request->message().data(), kMessageData);
+    return Status::OK;
+  }
+
+  Status GetTopic(ServerContext* context,
+                  const ::tech::pubsub::GetTopicRequest* request,
+                  ::tech::pubsub::Topic* response) override {
+    EXPECT_EQ(request->topic(), kTopic);
+    return Status::OK;
+  }
+
+ Status ListTopics(ServerContext* context,
+                   const ::tech::pubsub::ListTopicsRequest* request,
+                   ::tech::pubsub::ListTopicsResponse* response) override {
+   std::ostringstream ss;
+   ss << "cloud.googleapis.com/project in (/projects/" << kProjectId << ")";
+   EXPECT_EQ(request->query(), ss.str());
+   response->add_topic()->set_name(kTopic);
+   return Status::OK;
+ }
+
+ Status DeleteTopic(ServerContext* context,
+                    const ::tech::pubsub::DeleteTopicRequest* request,
+                    ::proto2::Empty* response) override {
+    EXPECT_EQ(request->topic(), kTopic);
+    return Status::OK;
+ }
+
 };
 
-class End2endTest : public ::testing::Test {
+class PublisherTest : public ::testing::Test {
  protected:
+  // Setup a server and a client for PublisherService.
   void SetUp() override {
     int port = grpc_pick_unused_port_or_die();
     server_address_ << "localhost:" << port;
-    // Setup server
     ServerBuilder builder;
     builder.AddPort(server_address_.str());
     builder.RegisterService(service_.service());
     server_ = builder.BuildAndStart();
 
     channel_ = CreateChannel(server_address_.str(), ChannelArguments());
+
+    publisher_.reset(new grpc::examples::tips::Publisher(channel_));
   }
 
-  void TearDown() override { server_->Shutdown(); }
+  void TearDown() override {
+    server_->Shutdown();
+    publisher_->Shutdown();
+  }
 
-  std::unique_ptr<Server> server_;
   std::ostringstream server_address_;
-  PublishServiceImpl service_;
+  std::unique_ptr<Server> server_;
+  PublisherServiceImpl service_;
 
   std::shared_ptr<ChannelInterface> channel_;
+
+  std::unique_ptr<grpc::examples::tips::Publisher> publisher_;
 };
 
-TEST_F(End2endTest, CreateTopic) {
-  grpc::examples::tips::Client client(channel_);
-  client.CreateTopic(kTopic);
+TEST_F(PublisherTest, TestPublisher) {
+  EXPECT_TRUE(publisher_->CreateTopic(kTopic).IsOk());
+
+  EXPECT_TRUE(publisher_->Publish(kTopic, kMessageData).IsOk());
+
+  EXPECT_TRUE(publisher_->GetTopic(kTopic).IsOk());
+
+  std::vector<grpc::string> topics;
+  EXPECT_TRUE(publisher_->ListTopics(kProjectId, &topics).IsOk());
+  EXPECT_EQ(topics.size(), 1);
+  EXPECT_EQ(topics[0], kTopic);
 }
 
 }  // namespace
diff --git a/examples/tips/pubsub.proto b/examples/tips/pubsub.proto
index 0b3bd5d..a2dd2f5 100644
--- a/examples/tips/pubsub.proto
+++ b/examples/tips/pubsub.proto
@@ -1,3 +1,5 @@
+// This file will be moved to a new location.
+
 // Specification of the Pubsub API.
 
 syntax = "proto2";
diff --git a/examples/tips/subscriber.cc b/examples/tips/subscriber.cc
new file mode 100644
index 0000000..c067322
--- /dev/null
+++ b/examples/tips/subscriber.cc
@@ -0,0 +1,118 @@
+/*
+ *
+ * Copyright 2014, 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++/client_context.h>
+
+#include "examples/tips/subscriber.h"
+
+using tech::pubsub::Topic;
+using tech::pubsub::DeleteTopicRequest;
+using tech::pubsub::GetTopicRequest;
+using tech::pubsub::SubscriberService;
+using tech::pubsub::ListTopicsRequest;
+using tech::pubsub::ListTopicsResponse;
+using tech::pubsub::PublishRequest;
+using tech::pubsub::PubsubMessage;
+
+namespace grpc {
+namespace examples {
+namespace tips {
+
+Subscriber::Subscriber(std::shared_ptr<ChannelInterface> channel)
+    : stub_(SubscriberService::NewStub(channel)) {
+}
+
+void Subscriber::Shutdown() {
+  stub_.reset();
+}
+
+Status Subscriber::CreateSubscription(const grpc::string& topic,
+                                      const grpc::string& name) {
+  tech::pubsub::Subscription request;
+  tech::pubsub::Subscription response;
+  ClientContext context;
+
+  request.set_topic(topic);
+  request.set_name(name);
+
+  return stub_->CreateSubscription(&context, request, &response);
+}
+
+Status Subscriber::GetSubscription(const grpc::string& name,
+                                   grpc::string* topic) {
+  tech::pubsub::GetSubscriptionRequest request;
+  tech::pubsub::Subscription response;
+  ClientContext context;
+
+  request.set_subscription(name);
+
+  Status s = stub_->GetSubscription(&context, request, &response);
+  *topic = response.topic();
+  return s;
+}
+
+Status Subscriber::DeleteSubscription(const grpc::string& name) {
+  tech::pubsub::DeleteSubscriptionRequest request;
+  proto2::Empty response;
+  ClientContext context;
+
+  request.set_subscription(name);
+
+  return stub_->DeleteSubscription(&context, request, &response);
+}
+
+Status Subscriber::Pull(const grpc::string& name, grpc::string* data) {
+  tech::pubsub::PullRequest request;
+  tech::pubsub::PullResponse response;
+  ClientContext context;
+
+  request.set_subscription(name);
+  Status s = stub_->Pull(&context, request, &response);
+  if (s.IsOk()) {
+    tech::pubsub::PubsubEvent event = response.pubsub_event();
+    if (event.has_message()) {
+      *data = event.message().data();
+    }
+    tech::pubsub::AcknowledgeRequest ack;
+    proto2::Empty empty;
+    ClientContext ack_context;
+    ack.set_subscription(name);
+    ack.add_ack_id(response.ack_id());
+    stub_->Acknowledge(&ack_context, ack, &empty);
+  }
+  return s;
+}
+
+}  // namespace tips
+}  // namespace examples
+}  // namespace grpc
diff --git a/examples/tips/client.h b/examples/tips/subscriber.h
similarity index 75%
copy from examples/tips/client.h
copy to examples/tips/subscriber.h
index 661ee5c..ed706ff 100644
--- a/examples/tips/client.h
+++ b/examples/tips/subscriber.h
@@ -31,8 +31,8 @@
  *
  */
 
-#ifndef __GRPCPP_EXAMPLES_TIPS_CLIENT_H_
-#define __GRPCPP_EXAMPLES_TIPS_CLIENT_H_
+#ifndef __GRPCPP_EXAMPLES_TIPS_SUBSCRIBER_H_
+#define __GRPCPP_EXAMPLES_TIPS_SUBSCRIBER_H_
 
 #include <grpc++/channel_interface.h>
 #include <grpc++/status.h>
@@ -43,20 +43,26 @@
 namespace examples {
 namespace tips {
 
-class Client {
+class Subscriber {
  public:
-  Client(std::shared_ptr<grpc::ChannelInterface> channel);
-  Status CreateTopic(grpc::string topic);
-  Status GetTopic(grpc::string topic);
-  Status DeleteTopic(grpc::string topic);
-  Status ListTopics();
+  Subscriber(std::shared_ptr<ChannelInterface> channel);
+  void Shutdown();
+
+  Status CreateSubscription(const grpc::string& topic,
+                            const grpc::string& name);
+
+  Status GetSubscription(const grpc::string& name, grpc::string* topic);
+
+  Status DeleteSubscription(const grpc::string& name);
+
+  Status Pull(const grpc::string& name, grpc::string* data);
 
  private:
-  std::unique_ptr<tech::pubsub::PublisherService::Stub> stub_;
+  std::unique_ptr<tech::pubsub::SubscriberService::Stub> stub_;
 };
 
 }  // namespace tips
 }  // namespace examples
 }  // namespace grpc
 
-#endif  // __GRPCPP_EXAMPLES_TIPS_CLIENT_H_
+#endif  // __GRPCPP_EXAMPLES_TIPS_SUBSCRIBER_H_
diff --git a/examples/tips/subscriber_test.cc b/examples/tips/subscriber_test.cc
new file mode 100644
index 0000000..595a6a1
--- /dev/null
+++ b/examples/tips/subscriber_test.cc
@@ -0,0 +1,157 @@
+/*
+ *
+ * Copyright 2014, 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++/channel_arguments.h>
+#include <grpc++/channel_interface.h>
+#include <grpc++/client_context.h>
+#include <grpc++/create_channel.h>
+#include <grpc++/server.h>
+#include <grpc++/server_builder.h>
+#include <grpc++/server_context.h>
+#include <grpc++/status.h>
+#include <gtest/gtest.h>
+
+#include "examples/tips/subscriber.h"
+#include "test/core/util/port.h"
+#include "test/core/util/test_config.h"
+
+namespace grpc {
+namespace testing {
+namespace {
+
+const char kTopic[] = "test topic";
+const char kSubscriptionName[] = "subscription name";
+const char kData[] = "Message data";
+
+class SubscriberServiceImpl : public tech::pubsub::SubscriberService::Service {
+ public:
+  Status CreateSubscription(ServerContext* context,
+                            const tech::pubsub::Subscription* request,
+                            tech::pubsub::Subscription* response) override {
+    EXPECT_EQ(request->topic(), kTopic);
+    EXPECT_EQ(request->name(), kSubscriptionName);
+    return Status::OK;
+  }
+
+  Status GetSubscription(ServerContext* context,
+                         const tech::pubsub::GetSubscriptionRequest* request,
+                         tech::pubsub::Subscription* response) override {
+    EXPECT_EQ(request->subscription(), kSubscriptionName);
+    response->set_topic(kTopic);
+    return Status::OK;
+  }
+
+  Status DeleteSubscription(
+      ServerContext* context,
+      const tech::pubsub::DeleteSubscriptionRequest* request,
+      proto2::Empty* response) override {
+    EXPECT_EQ(request->subscription(), kSubscriptionName);
+    return Status::OK;
+  }
+
+  Status Pull(ServerContext* context,
+              const tech::pubsub::PullRequest* request,
+              tech::pubsub::PullResponse* response) override {
+    EXPECT_EQ(request->subscription(), kSubscriptionName);
+    response->set_ack_id("1");
+    response->mutable_pubsub_event()->mutable_message()->set_data(kData);
+    return Status::OK;
+  }
+
+  Status Acknowledge(ServerContext* context,
+                     const tech::pubsub::AcknowledgeRequest* request,
+                     proto2::Empty* response) override {
+    return Status::OK;
+  }
+
+};
+
+class SubscriberTest : public ::testing::Test {
+ protected:
+  // Setup a server and a client for SubscriberService.
+  void SetUp() override {
+    int port = grpc_pick_unused_port_or_die();
+    server_address_ << "localhost:" << port;
+    ServerBuilder builder;
+    builder.AddPort(server_address_.str());
+    builder.RegisterService(service_.service());
+    server_ = builder.BuildAndStart();
+
+    channel_ = CreateChannel(server_address_.str(), ChannelArguments());
+
+    subscriber_.reset(new grpc::examples::tips::Subscriber(channel_));
+  }
+
+  void TearDown() override {
+    server_->Shutdown();
+    subscriber_->Shutdown();
+  }
+
+  std::ostringstream server_address_;
+  std::unique_ptr<Server> server_;
+  SubscriberServiceImpl service_;
+
+  std::shared_ptr<ChannelInterface> channel_;
+
+  std::unique_ptr<grpc::examples::tips::Subscriber> subscriber_;
+};
+
+TEST_F(SubscriberTest, TestSubscriber) {
+  EXPECT_TRUE(subscriber_->CreateSubscription(kTopic,
+                                              kSubscriptionName).IsOk());
+
+  grpc::string topic;
+  EXPECT_TRUE(subscriber_->GetSubscription(kSubscriptionName,
+                                           &topic).IsOk());
+  EXPECT_EQ(topic, kTopic);
+
+  grpc::string data;
+  EXPECT_TRUE(subscriber_->Pull(kSubscriptionName,
+                                &data).IsOk());
+
+  EXPECT_TRUE(subscriber_->DeleteSubscription(kSubscriptionName).IsOk());
+}
+
+}  // namespace
+}  // namespace testing
+}  // namespace grpc
+
+int main(int argc, char** argv) {
+  grpc_test_init(argc, argv);
+  grpc_init();
+  ::testing::InitGoogleTest(&argc, argv);
+  gpr_log(GPR_INFO, "Start test ...");
+  int result = RUN_ALL_TESTS();
+  grpc_shutdown();
+  return result;
+}
diff --git a/include/grpc++/credentials.h b/include/grpc++/credentials.h
index 987d890..52304d7 100644
--- a/include/grpc++/credentials.h
+++ b/include/grpc++/credentials.h
@@ -66,14 +66,13 @@
 
 // Options used to build SslCredentials
 // pem_roots_cert is the buffer containing the PEM encoding of the server root
-// certificates. This parameter cannot be empty.
+// certificates. If this parameter is empty, the default roots will be used.
 // pem_private_key is the buffer containing the PEM encoding of the client's
 // private key. This parameter can be empty if the client does not have a
 // private key.
 // pem_cert_chain is the buffer containing the PEM encoding of the client's
 // certificate chain. This parameter can be empty if the client does not have
 // a certificate chain.
-// TODO(jboeuf) Change it to point to a file.
 struct SslCredentialsOptions {
   grpc::string pem_root_certs;
   grpc::string pem_private_key;
diff --git a/include/grpc/grpc.h b/include/grpc/grpc.h
index af52dd9..c18e47e 100644
--- a/include/grpc/grpc.h
+++ b/include/grpc/grpc.h
@@ -183,17 +183,16 @@
 } grpc_metadata;
 
 typedef enum grpc_completion_type {
-  GRPC_QUEUE_SHUTDOWN,  /* Shutting down */
-  GRPC_READ,            /* A read has completed */
-  GRPC_INVOKE_ACCEPTED, /* An invoke call has been accepted by flow
-                           control */
-  GRPC_WRITE_ACCEPTED, /* A write has been accepted by
-                          flow control */
+  GRPC_QUEUE_SHUTDOWN,       /* Shutting down */
+  GRPC_IOREQ,                /* grpc_call_ioreq completion */
+  GRPC_READ,                 /* A read has completed */
+  GRPC_WRITE_ACCEPTED,       /* A write has been accepted by
+                                flow control */
   GRPC_FINISH_ACCEPTED,      /* writes_done or write_status has been accepted */
   GRPC_CLIENT_METADATA_READ, /* The metadata array sent by server received at
                                 client */
-  GRPC_FINISHED, /* An RPC has finished. The event contains status.
-                    On the server this will be OK or Cancelled. */
+  GRPC_FINISHED,             /* An RPC has finished. The event contains status.
+                                On the server this will be OK or Cancelled. */
   GRPC_SERVER_RPC_NEW,       /* A new RPC has arrived at the server */
   GRPC_SERVER_SHUTDOWN,      /* The server has finished shutting down */
   GRPC_COMPLETION_DO_NOT_USE /* must be last, forces users to include
@@ -213,6 +212,7 @@
     grpc_op_error write_accepted;
     grpc_op_error finish_accepted;
     grpc_op_error invoke_accepted;
+    grpc_op_error ioreq;
     struct {
       size_t count;
       grpc_metadata *elements;
@@ -233,6 +233,57 @@
   } data;
 } grpc_event;
 
+typedef struct {
+  size_t count;
+  size_t capacity;
+  grpc_metadata *metadata;
+} grpc_metadata_array;
+
+typedef struct {
+  const char *method;
+  const char *host;
+  gpr_timespec deadline;
+} grpc_call_details;
+
+typedef enum {
+  GRPC_OP_SEND_INITIAL_METADATA = 0,
+  GRPC_OP_SEND_MESSAGE,
+  GRPC_OP_SEND_CLOSE_FROM_CLIENT,
+  GRPC_OP_SEND_STATUS_FROM_SERVER,
+  GRPC_OP_RECV_INITIAL_METADATA,
+  GRPC_OP_RECV_MESSAGES,
+  GRPC_OP_RECV_STATUS_ON_CLIENT,
+  GRPC_OP_RECV_CLOSE_ON_SERVER
+} grpc_op_type;
+
+typedef struct grpc_op {
+  grpc_op_type op;
+  union {
+    struct {
+      size_t count;
+      const grpc_metadata *metadata;
+    } send_initial_metadata;
+    grpc_byte_buffer *send_message;
+    struct {
+      size_t trailing_metadata_count;
+      grpc_metadata *trailing_metadata;
+      grpc_status_code status;
+      const char *status_details;
+    } send_status_from_server;
+    grpc_metadata_array *recv_initial_metadata;
+    grpc_byte_buffer **recv_message;
+    struct {
+      grpc_metadata_array *trailing_metadata;
+      grpc_status_code *status;
+      char **status_details;
+      size_t *status_details_capacity;
+    } recv_status_on_client;
+    struct {
+      int *cancelled;
+    } recv_close_on_server;
+  } data;
+} grpc_op;
+
 /* Initialize the grpc library */
 void grpc_init(void);
 
@@ -275,8 +326,12 @@
 /* Create a call given a grpc_channel, in order to call 'method'. The request
    is not sent until grpc_call_invoke is called. All completions are sent to
    'completion_queue'. */
-grpc_call *grpc_channel_create_call(grpc_channel *channel, const char *method,
-                                    const char *host, gpr_timespec deadline);
+grpc_call *grpc_channel_create_call_old(grpc_channel *channel,
+                                        const char *method, const char *host,
+                                        gpr_timespec deadline);
+
+grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
+                                      size_t nops, void *tag);
 
 /* Create a client channel */
 grpc_channel *grpc_channel_create(const char *target,
@@ -307,8 +362,9 @@
    REQUIRES: grpc_call_start_invoke/grpc_call_server_end_initial_metadata have
              not been called on this call.
    Produces no events. */
-grpc_call_error grpc_call_add_metadata(grpc_call *call, grpc_metadata *metadata,
-                                       gpr_uint32 flags);
+grpc_call_error grpc_call_add_metadata_old(grpc_call *call,
+                                           grpc_metadata *metadata,
+                                           gpr_uint32 flags);
 
 /* Invoke the RPC. Starts sending metadata and request headers on the wire.
    flags is a bit-field combination of the write flags defined above.
@@ -319,9 +375,9 @@
    Produces a GRPC_FINISHED event with finished_tag when the call has been
        completed (there may be other events for the call pending at this
        time) */
-grpc_call_error grpc_call_invoke(grpc_call *call, grpc_completion_queue *cq,
-                                 void *metadata_read_tag, void *finished_tag,
-                                 gpr_uint32 flags);
+grpc_call_error grpc_call_invoke_old(grpc_call *call, grpc_completion_queue *cq,
+                                     void *metadata_read_tag,
+                                     void *finished_tag, gpr_uint32 flags);
 
 /* Accept an incoming RPC, binding a completion queue to it.
    To be called before sending or receiving messages.
@@ -330,9 +386,9 @@
    Produces a GRPC_FINISHED event with finished_tag when the call has been
        completed (there may be other events for the call pending at this
        time) */
-grpc_call_error grpc_call_server_accept(grpc_call *call,
-                                        grpc_completion_queue *cq,
-                                        void *finished_tag);
+grpc_call_error grpc_call_server_accept_old(grpc_call *call,
+                                            grpc_completion_queue *cq,
+                                            void *finished_tag);
 
 /* Start sending metadata.
    To be called before sending messages.
@@ -340,8 +396,8 @@
    REQUIRES: Can be called at most once per call.
              Can only be called on the server.
              Must be called after grpc_call_server_accept */
-grpc_call_error grpc_call_server_end_initial_metadata(grpc_call *call,
-                                                      gpr_uint32 flags);
+grpc_call_error grpc_call_server_end_initial_metadata_old(grpc_call *call,
+                                                          gpr_uint32 flags);
 
 /* Called by clients to cancel an RPC on the server.
    Can be called multiple times, from any thread. */
@@ -370,9 +426,9 @@
              grpc_call_server_end_of_initial_metadata must have been called
              successfully.
    Produces a GRPC_WRITE_ACCEPTED event. */
-grpc_call_error grpc_call_start_write(grpc_call *call,
-                                      grpc_byte_buffer *byte_buffer, void *tag,
-                                      gpr_uint32 flags);
+grpc_call_error grpc_call_start_write_old(grpc_call *call,
+                                          grpc_byte_buffer *byte_buffer,
+                                          void *tag, gpr_uint32 flags);
 
 /* Queue a status for writing.
    REQUIRES: No other writes are pending on the call.
@@ -380,17 +436,17 @@
              call prior to calling this.
              Only callable on the server.
    Produces a GRPC_FINISH_ACCEPTED event when the status is sent. */
-grpc_call_error grpc_call_start_write_status(grpc_call *call,
-                                             grpc_status_code status_code,
-                                             const char *status_message,
-                                             void *tag);
+grpc_call_error grpc_call_start_write_status_old(grpc_call *call,
+                                                 grpc_status_code status_code,
+                                                 const char *status_message,
+                                                 void *tag);
 
 /* No more messages to send.
    REQUIRES: No other writes are pending on the call.
              Only callable on the client.
    Produces a GRPC_FINISH_ACCEPTED event when all bytes for the call have passed
        outgoing flow control. */
-grpc_call_error grpc_call_writes_done(grpc_call *call, void *tag);
+grpc_call_error grpc_call_writes_done_old(grpc_call *call, void *tag);
 
 /* Initiate a read on a call. Output event contains a byte buffer with the
    result of the read.
@@ -402,7 +458,7 @@
              On the server:
                grpc_call_server_accept must be called before calling this.
    Produces a single GRPC_READ event. */
-grpc_call_error grpc_call_start_read(grpc_call *call, void *tag);
+grpc_call_error grpc_call_start_read_old(grpc_call *call, void *tag);
 
 /* Destroy a call. */
 void grpc_call_destroy(grpc_call *call);
@@ -414,7 +470,8 @@
    tag_cancel.
    REQUIRES: Server must not have been shutdown.
    NOTE: calling this is the only way to obtain GRPC_SERVER_RPC_NEW events. */
-grpc_call_error grpc_server_request_call(grpc_server *server, void *tag_new);
+grpc_call_error grpc_server_request_call_old(grpc_server *server,
+                                             void *tag_new);
 
 /* Create a server */
 grpc_server *grpc_server_create(grpc_completion_queue *cq,
diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h
index 0732a8f..7319590 100644
--- a/include/grpc/grpc_security.h
+++ b/include/grpc/grpc_security.h
@@ -54,6 +54,12 @@
 /* Creates default credentials. */
 grpc_credentials *grpc_default_credentials_create(void);
 
+/* Environment variable that points to the default SSL roots file. This file
+   must be a PEM encoded file with all the roots such as the one that can be
+   downloaded from https://pki.google.com/roots.pem.  */
+#define GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR \
+  "GRPC_DEFAULT_SSL_ROOTS_FILE_PATH"
+
 /* Object that holds a private key / certificate chain pair in PEM format. */
 typedef struct {
   /* private_key is the NULL-terminated string containing the PEM encoding of
diff --git a/include/grpc/support/port_platform.h b/include/grpc/support/port_platform.h
index 58fce64..e99099c 100644
--- a/include/grpc/support/port_platform.h
+++ b/include/grpc/support/port_platform.h
@@ -56,9 +56,13 @@
 #define GPR_CPU_LINUX 1
 #define GPR_GCC_SYNC 1
 #define GPR_POSIX_MULTIPOLL_WITH_POLL 1
+#define GPR_POSIX_WAKEUP_FD 1
+#define GPR_LINUX_EVENTFD 1
 #define GPR_POSIX_SOCKET 1
 #define GPR_POSIX_SOCKETADDR 1
 #define GPR_POSIX_SOCKETUTILS 1
+#define GPR_POSIX_ENV 1
+#define GPR_POSIX_FILE 1
 #define GPR_POSIX_STRING 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
@@ -68,10 +72,12 @@
 #define GPR_GCC_ATOMIC 1
 #define GPR_LINUX 1
 #define GPR_POSIX_MULTIPOLL_WITH_POLL 1
-#define GPR_POSIX_HAS_SPECIAL_WAKEUP_FD 1
+#define GPR_POSIX_WAKEUP_FD 1
 #define GPR_LINUX_EVENTFD 1
 #define GPR_POSIX_SOCKET 1
 #define GPR_POSIX_SOCKETADDR 1
+#define GPR_LINUX_ENV 1
+#define GPR_POSIX_FILE 1
 #define GPR_POSIX_STRING 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
@@ -86,9 +92,13 @@
 #define GPR_GCC_ATOMIC 1
 #define GPR_POSIX_LOG 1
 #define GPR_POSIX_MULTIPOLL_WITH_POLL 1
+#define GPR_POSIX_WAKEUP_FD 1
+#define GPR_POSIX_NO_SPECIAL_WAKEUP_FD 1
 #define GPR_POSIX_SOCKET 1
 #define GPR_POSIX_SOCKETADDR 1
 #define GPR_POSIX_SOCKETUTILS 1
+#define GPR_POSIX_ENV 1
+#define GPR_POSIX_FILE 1
 #define GPR_POSIX_STRING 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
diff --git a/include/grpc/support/thd.h b/include/grpc/support/thd.h
index 91e0c9f..92d40b4 100644
--- a/include/grpc/support/thd.h
+++ b/include/grpc/support/thd.h
@@ -44,18 +44,12 @@
 
 #include <grpc/support/port_platform.h>
 
-#if defined(GPR_POSIX_SYNC)
-#include <grpc/support/thd_posix.h>
-#elif defined(GPR_WIN32)
-#include <grpc/support/thd_win32.h>
-#else
-#error could not determine platform for thd
-#endif
-
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+typedef gpr_uint64 gpr_thd_id;
+
 /* Thread creation options. */
 typedef struct {
   int flags; /* Flags below can be set here.  Default value 0.  */
@@ -72,6 +66,9 @@
 /* Return a gpr_thd_options struct with all fields set to defaults. */
 gpr_thd_options gpr_thd_options_default(void);
 
+/* Returns the identifier of the current thread. */
+gpr_thd_id gpr_thd_currentid(void);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/grpc/support/thd_win32.h b/include/grpc/support/thd_win32.h
deleted file mode 100644
index b4ab3c7..0000000
--- a/include/grpc/support/thd_win32.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- *
- * Copyright 2014, 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_SUPPORT_THD_WIN32_H__
-#define __GRPC_SUPPORT_THD_WIN32_H__
-
-/* Win32 variant of gpr_thd_platform.h */
-
-#include <windows.h>
-#include <grpc/support/atm.h>
-
-typedef int gpr_thd_id;
-
-#endif /* __GRPC_SUPPORT_THD_WIN32_H__ */
diff --git a/include/grpc/support/time.h b/include/grpc/support/time.h
index 6327a2c..9fb1d0b 100644
--- a/include/grpc/support/time.h
+++ b/include/grpc/support/time.h
@@ -34,31 +34,22 @@
 #ifndef __GRPC_SUPPORT_TIME_H__
 #define __GRPC_SUPPORT_TIME_H__
 /* Time support.
-   We use gpr_timespec, which is typedefed to struct timespec on platforms which
-   have it. On some machines, absolute times may be in local time.  */
-
-/* Platform specific header declares gpr_timespec.
-   gpr_timespec contains:
-      time_t tv_sec;  // seconds since start of 1970
-      int tv_nsec;    // nanoseconds;  always in 0..999999999; never negative.
- */
+   We use gpr_timespec, which is analogous to struct timespec.  On some
+   machines, absolute times may be in local time.  */
 
 #include <grpc/support/port_platform.h>
-
-#if defined(GPR_POSIX_TIME)
-#include <grpc/support/time_posix.h>
-#elif defined(GPR_WIN32)
-#include <grpc/support/time_win32.h>
-#else
-#error could not determine platform for time
-#endif
-
 #include <stddef.h>
+#include <time.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+typedef struct gpr_timespec {
+    time_t tv_sec;
+    int tv_nsec;
+} gpr_timespec;
+
 /* Time constants. */
 extern const gpr_timespec gpr_time_0;     /* The zero time interval. */
 extern const gpr_timespec gpr_inf_future; /* The far future */
@@ -103,10 +94,6 @@
 /* Sleep until at least 'until' - an absolute timeout */
 void gpr_sleep_until(gpr_timespec until);
 
-struct timeval gpr_timeval_from_timespec(gpr_timespec t);
-
-gpr_timespec gpr_timespec_from_timeval(struct timeval t);
-
 double gpr_timespec_to_micros(gpr_timespec t);
 
 #ifdef __cplusplus
diff --git a/include/grpc/support/time_win32.h b/include/grpc/support/time_win32.h
deleted file mode 100644
index e62ad64..0000000
--- a/include/grpc/support/time_win32.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- *
- * Copyright 2014, 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_SUPPORT_TIME_WIN32_H__
-#define __GRPC_SUPPORT_TIME_WIN32_H__
-/* Win32 variant of gpr_time_platform.h */
-
-#include <Winsock.h>
-#include <time.h>
-
-typedef struct gpr_timespec {
-  time_t tv_sec;
-  long tv_nsec;
-} gpr_timespec;
-
-#endif /* __GRPC_SUPPORT_TIME_WIN32_H__ */
diff --git a/src/core/channel/channel_stack.c b/src/core/channel/channel_stack.c
index e28bbd7..d9e722c 100644
--- a/src/core/channel/channel_stack.c
+++ b/src/core/channel/channel_stack.c
@@ -210,6 +210,7 @@
   metadata_op.dir = GRPC_CALL_UP;
   metadata_op.done_cb = do_nothing;
   metadata_op.user_data = NULL;
+  metadata_op.flags = 0;
   metadata_op.data.metadata = mdelem;
   grpc_call_next_op(cur_elem, &metadata_op);
 }
@@ -221,6 +222,7 @@
   metadata_op.dir = GRPC_CALL_DOWN;
   metadata_op.done_cb = do_nothing;
   metadata_op.user_data = NULL;
+  metadata_op.flags = 0;
   metadata_op.data.metadata = mdelem;
   grpc_call_next_op(cur_elem, &metadata_op);
 }
@@ -231,14 +233,16 @@
   cancel_op.dir = GRPC_CALL_DOWN;
   cancel_op.done_cb = do_nothing;
   cancel_op.user_data = NULL;
+  cancel_op.flags = 0;
   grpc_call_next_op(cur_elem, &cancel_op);
 }
 
 void grpc_call_element_send_finish(grpc_call_element *cur_elem) {
-  grpc_call_op cancel_op;
-  cancel_op.type = GRPC_SEND_FINISH;
-  cancel_op.dir = GRPC_CALL_DOWN;
-  cancel_op.done_cb = do_nothing;
-  cancel_op.user_data = NULL;
-  grpc_call_next_op(cur_elem, &cancel_op);
+  grpc_call_op finish_op;
+  finish_op.type = GRPC_SEND_FINISH;
+  finish_op.dir = GRPC_CALL_DOWN;
+  finish_op.done_cb = do_nothing;
+  finish_op.user_data = NULL;
+  finish_op.flags = 0;
+  grpc_call_next_op(cur_elem, &finish_op);
 }
diff --git a/src/core/channel/client_channel.c b/src/core/channel/client_channel.c
index bcb024f..507b91b 100644
--- a/src/core/channel/client_channel.c
+++ b/src/core/channel/client_channel.c
@@ -298,6 +298,7 @@
                        grpc_channel_element *from_elem, grpc_channel_op *op) {
   channel_data *chand = elem->channel_data;
   grpc_child_channel *child_channel;
+  grpc_channel_op rop;
   GPR_ASSERT(elem->filter == &grpc_client_channel_filter);
 
   switch (op->type) {
@@ -323,6 +324,10 @@
       if (child_channel) {
         grpc_child_channel_destroy(child_channel, 1);
       }
+      /* fake a transport closed to satisfy the refcounting in client */
+      rop.type = GRPC_TRANSPORT_CLOSED;
+      rop.dir = GRPC_CALL_UP;
+      grpc_channel_next_op(elem, &rop);
       break;
     case GRPC_TRANSPORT_GOAWAY:
       /* receiving goaway: if it's from our active child, drop the active child;
diff --git a/src/core/channel/connected_channel.c b/src/core/channel/connected_channel.c
index d35cede..61a6caf 100644
--- a/src/core/channel/connected_channel.c
+++ b/src/core/channel/connected_channel.c
@@ -298,10 +298,6 @@
 
 static void do_nothing(void *calldata, grpc_op_error error) {}
 
-static void done_message(void *user_data, grpc_op_error error) {
-  grpc_byte_buffer_destroy(user_data);
-}
-
 static void finish_message(channel_data *chand, call_data *calld) {
   grpc_call_element *elem = calld->elem;
   grpc_call_op call_op;
@@ -309,9 +305,9 @@
   call_op.flags = 0;
   /* if we got all the bytes for this message, call up the stack */
   call_op.type = GRPC_RECV_MESSAGE;
-  call_op.done_cb = done_message;
+  call_op.done_cb = do_nothing;
   /* TODO(ctiller): this could be a lot faster if coded directly */
-  call_op.user_data = call_op.data.message = grpc_byte_buffer_create(
+  call_op.data.message = grpc_byte_buffer_create(
       calld->incoming_message.slices, calld->incoming_message.count);
   gpr_slice_buffer_reset_and_unref(&calld->incoming_message);
 
diff --git a/src/core/channel/http_server_filter.c b/src/core/channel/http_server_filter.c
index 2658a6d..b70af43 100644
--- a/src/core/channel/http_server_filter.c
+++ b/src/core/channel/http_server_filter.c
@@ -319,8 +319,8 @@
       if (channeld->gettable_count == gettable_capacity) {
         gettable_capacity =
             GPR_MAX(gettable_capacity * 3 / 2, gettable_capacity + 1);
-        channeld->gettables =
-            gpr_realloc(channeld->gettables, gettable_capacity * sizeof(gettable));
+        channeld->gettables = gpr_realloc(channeld->gettables,
+                                          gettable_capacity * sizeof(gettable));
       }
       g = &channeld->gettables[channeld->gettable_count++];
       g->path = grpc_mdelem_from_strings(mdctx, ":path", p->path);
@@ -328,15 +328,25 @@
           grpc_mdelem_from_strings(mdctx, "content-type", p->content_type);
       slice = gpr_slice_from_copied_string(p->content);
       g->content = grpc_byte_buffer_create(&slice, 1);
+      gpr_slice_unref(slice);
     }
   }
 }
 
 /* Destructor for channel data */
 static void destroy_channel_elem(grpc_channel_element *elem) {
+  size_t i;
+
   /* grab pointers to our data from the channel element */
   channel_data *channeld = elem->channel_data;
 
+  for (i = 0; i < channeld->gettable_count; i++) {
+    grpc_mdelem_unref(channeld->gettables[i].path);
+    grpc_mdelem_unref(channeld->gettables[i].content_type);
+    grpc_byte_buffer_destroy(channeld->gettables[i].content);
+  }
+  gpr_free(channeld->gettables);
+
   grpc_mdelem_unref(channeld->te_trailers);
   grpc_mdelem_unref(channeld->status_ok);
   grpc_mdelem_unref(channeld->status_not_found);
@@ -350,6 +360,6 @@
 }
 
 const grpc_channel_filter grpc_http_server_filter = {
-    call_op,           channel_op,           sizeof(call_data),
-    init_call_elem,    destroy_call_elem,    sizeof(channel_data),
-    init_channel_elem, destroy_channel_elem, "http-server"};
+    call_op, channel_op, sizeof(call_data), init_call_elem, destroy_call_elem,
+    sizeof(channel_data), init_channel_elem, destroy_channel_elem,
+    "http-server"};
diff --git a/src/core/httpcli/format_request.c b/src/core/httpcli/format_request.c
index 58bb7c7..5d1a04e 100644
--- a/src/core/httpcli/format_request.c
+++ b/src/core/httpcli/format_request.c
@@ -105,6 +105,8 @@
   }
   gpr_strvec_add(&out, gpr_strdup("\r\n"));
   tmp = gpr_strvec_flatten(&out, &out_len);
+  gpr_strvec_destroy(&out);
+
   if (body_bytes) {
     tmp = gpr_realloc(tmp, out_len + body_size);
     memcpy(tmp + out_len, body_bytes, body_size);
diff --git a/src/core/iomgr/alarm.c b/src/core/iomgr/alarm.c
index 5b80368..7884b21 100644
--- a/src/core/iomgr/alarm.c
+++ b/src/core/iomgr/alarm.c
@@ -335,10 +335,6 @@
 
     gpr_mu_unlock(&g_mu);
     gpr_mu_unlock(&g_checker_mu);
-  } else if (next) {
-    gpr_mu_lock(&g_mu);
-    *next = gpr_time_min(*next, g_shard_queue[0]->min_deadline);
-    gpr_mu_unlock(&g_mu);
   }
 
   if (n && drop_mu) {
diff --git a/src/core/iomgr/alarm_internal.h b/src/core/iomgr/alarm_internal.h
index 5c6b869..8503292 100644
--- a/src/core/iomgr/alarm_internal.h
+++ b/src/core/iomgr/alarm_internal.h
@@ -39,6 +39,15 @@
 
 /* iomgr internal api for dealing with alarms */
 
+/* Check for alarms to be run, and run them. 
+   Return non zero if alarm callbacks were executed.
+   Drops drop_mu if it is non-null before executing callbacks.
+   If next is non-null, TRY to update *next with the next running alarm
+   IF that alarm occurs before *next current value.
+   *next is never guaranteed to be updated on any given execution; however,
+   with high probability at least one thread in the system will see an update
+   at any time slice. */
+
 int grpc_alarm_check(gpr_mu *drop_mu, gpr_timespec now, gpr_timespec *next);
 
 void grpc_alarm_list_init(gpr_timespec now);
diff --git a/src/core/iomgr/iomgr.c b/src/core/iomgr/iomgr.c
index 8989b49..3d6114c 100644
--- a/src/core/iomgr/iomgr.c
+++ b/src/core/iomgr/iomgr.c
@@ -51,6 +51,7 @@
 
 static gpr_mu g_mu;
 static gpr_cv g_cv;
+static gpr_cv g_rcv;
 static delayed_callback *g_cbs_head = NULL;
 static delayed_callback *g_cbs_tail = NULL;
 static int g_shutdown;
@@ -86,6 +87,7 @@
   gpr_thd_id id;
   gpr_mu_init(&g_mu);
   gpr_cv_init(&g_cv);
+  gpr_cv_init(&g_rcv);
   grpc_alarm_list_init(gpr_now());
   g_refs = 0;
   grpc_iomgr_platform_init();
@@ -115,7 +117,7 @@
       gpr_mu_lock(&g_mu);
     }
     if (g_refs) {
-      if (gpr_cv_wait(&g_cv, &g_mu, shutdown_deadline) && g_cbs_head == NULL) {
+      if (gpr_cv_wait(&g_rcv, &g_mu, shutdown_deadline) && g_cbs_head == NULL) {
         gpr_log(GPR_DEBUG,
                 "Failed to free %d iomgr objects before shutdown deadline: "
                 "memory leaks are likely",
@@ -126,12 +128,14 @@
   }
   gpr_mu_unlock(&g_mu);
 
+  grpc_kick_poller();
   gpr_event_wait(&g_background_callback_executor_done, gpr_inf_future);
 
   grpc_iomgr_platform_shutdown();
   grpc_alarm_list_shutdown();
   gpr_mu_destroy(&g_mu);
   gpr_cv_destroy(&g_cv);
+  gpr_cv_destroy(&g_rcv);
 }
 
 void grpc_iomgr_ref(void) {
@@ -143,7 +147,7 @@
 void grpc_iomgr_unref(void) {
   gpr_mu_lock(&g_mu);
   if (0 == --g_refs) {
-    gpr_cv_signal(&g_cv);
+    gpr_cv_signal(&g_rcv);
   }
   gpr_mu_unlock(&g_mu);
 }
diff --git a/src/core/iomgr/pollset_kick.c b/src/core/iomgr/pollset_kick.c
index 5ee1cef..f0211b8 100644
--- a/src/core/iomgr/pollset_kick.c
+++ b/src/core/iomgr/pollset_kick.c
@@ -48,49 +48,49 @@
 /* This implementation is based on a freelist of wakeup fds, with extra logic to
  * handle kicks while there is no attached fd. */
 
+/* TODO(klempner): Autosize this, and consider providing a way to disable the
+ * cap entirely on systems with large fd limits */
 #define GRPC_MAX_CACHED_WFDS 50
-#define GRPC_WFD_LOW_WATERMARK 25
 
 static grpc_kick_fd_info *fd_freelist = NULL;
 static int fd_freelist_count = 0;
 static gpr_mu fd_freelist_mu;
 
 static grpc_kick_fd_info *allocate_wfd(void) {
-  grpc_kick_fd_info *info;
+  grpc_kick_fd_info *info = NULL;
   gpr_mu_lock(&fd_freelist_mu);
   if (fd_freelist != NULL) {
     info = fd_freelist;
     fd_freelist = fd_freelist->next;
     --fd_freelist_count;
-  } else {
+  }
+  gpr_mu_unlock(&fd_freelist_mu);
+  if (info == NULL) {
     info = gpr_malloc(sizeof(*info));
     grpc_wakeup_fd_create(&info->wakeup_fd);
     info->next = NULL;
   }
-  gpr_mu_unlock(&fd_freelist_mu);
   return info;
 }
 
-static void destroy_wfd(void) {
-  /* assumes fd_freelist_mu is held */
-  grpc_kick_fd_info *current = fd_freelist;
-  fd_freelist = fd_freelist->next;
-  fd_freelist_count--;
-  grpc_wakeup_fd_destroy(&current->wakeup_fd);
-  gpr_free(current);
+static void destroy_wfd(grpc_kick_fd_info* wfd) {
+  grpc_wakeup_fd_destroy(&wfd->wakeup_fd);
+  gpr_free(wfd);
 }
 
 static void free_wfd(grpc_kick_fd_info *fd_info) {
   gpr_mu_lock(&fd_freelist_mu);
-  fd_info->next = fd_freelist;
-  fd_freelist = fd_info;
-  fd_freelist_count++;
-  if (fd_freelist_count > GRPC_MAX_CACHED_WFDS) {
-    while (fd_freelist_count > GRPC_WFD_LOW_WATERMARK) {
-      destroy_wfd();
-    }
+  if (fd_freelist_count < GRPC_MAX_CACHED_WFDS) {
+    fd_info->next = fd_freelist;
+    fd_freelist = fd_info;
+    fd_freelist_count++;
+    fd_info = NULL;
   }
   gpr_mu_unlock(&fd_freelist_mu);
+
+  if (fd_info) {
+    destroy_wfd(fd_info);
+  }
 }
 
 void grpc_pollset_kick_init(grpc_pollset_kick_state *kick_state) {
@@ -138,15 +138,23 @@
 }
 
 void grpc_pollset_kick_global_init_fallback_fd(void) {
+  gpr_mu_init(&fd_freelist_mu);
   grpc_wakeup_fd_global_init_force_fallback();
 }
 
 void grpc_pollset_kick_global_init(void) {
+  gpr_mu_init(&fd_freelist_mu);
   grpc_wakeup_fd_global_init();
 }
 
 void grpc_pollset_kick_global_destroy(void) {
+  while (fd_freelist != NULL) {
+    grpc_kick_fd_info *current = fd_freelist;
+    fd_freelist = fd_freelist->next;
+    destroy_wfd(current);
+  }
   grpc_wakeup_fd_global_destroy();
+  gpr_mu_destroy(&fd_freelist_mu);
 }
 
 
diff --git a/src/core/iomgr/pollset_multipoller_with_poll_posix.c b/src/core/iomgr/pollset_multipoller_with_poll_posix.c
index 7c9a949..e882969 100644
--- a/src/core/iomgr/pollset_multipoller_with_poll_posix.c
+++ b/src/core/iomgr/pollset_multipoller_with_poll_posix.c
@@ -147,8 +147,6 @@
       grpc_fd_unref(h->fds[i]);
     } else {
       h->fds[nf++] = h->fds[i];
-      h->pfds[np].events =
-          grpc_fd_begin_poll(h->fds[i], pollset, POLLIN, POLLOUT);
       h->selfds[np] = h->fds[i];
       h->pfds[np].fd = h->fds[i]->fd;
       h->pfds[np].revents = 0;
@@ -168,6 +166,11 @@
   pollset->counter = 1;
   gpr_mu_unlock(&pollset->mu);
 
+  for (i = 1; i < np; i++) {
+    h->pfds[i].events =
+        grpc_fd_begin_poll(h->selfds[i], pollset, POLLIN, POLLOUT);
+  }
+
   r = poll(h->pfds, h->pfd_count, timeout);
   if (r < 0) {
     if (errno != EINTR) {
diff --git a/src/core/iomgr/pollset_posix.c b/src/core/iomgr/pollset_posix.c
index 39e2dc4..b1c2c64 100644
--- a/src/core/iomgr/pollset_posix.c
+++ b/src/core/iomgr/pollset_posix.c
@@ -75,11 +75,12 @@
 }
 
 void grpc_pollset_kick(grpc_pollset *p) {
-  if (!p->counter) return;
-  grpc_pollset_kick_kick(&p->kick_state);
+  if (p->counter) {
+    grpc_pollset_kick_kick(&p->kick_state);
+  }
 }
 
-void grpc_pollset_force_kick(grpc_pollset *p) { grpc_pollset_kick(p); }
+void grpc_pollset_force_kick(grpc_pollset *p) { grpc_pollset_kick_kick(&p->kick_state); }
 
 /* global state management */
 
@@ -244,11 +245,12 @@
   pfd[0].events = POLLIN;
   pfd[0].revents = 0;
   pfd[1].fd = fd->fd;
-  pfd[1].events = grpc_fd_begin_poll(fd, pollset, POLLIN, POLLOUT);
   pfd[1].revents = 0;
   pollset->counter = 1;
   gpr_mu_unlock(&pollset->mu);
 
+  pfd[1].events = grpc_fd_begin_poll(fd, pollset, POLLIN, POLLOUT);
+
   r = poll(pfd, GPR_ARRAY_SIZE(pfd), timeout);
   if (r < 0) {
     if (errno != EINTR) {
@@ -269,9 +271,9 @@
   }
 
   grpc_pollset_kick_post_poll(&pollset->kick_state);
+  grpc_fd_end_poll(fd, pollset);
 
   gpr_mu_lock(&pollset->mu);
-  grpc_fd_end_poll(fd, pollset);
   pollset->counter = 0;
   gpr_cv_broadcast(&pollset->cv);
   return 1;
diff --git a/src/core/iomgr/pollset_posix.h b/src/core/iomgr/pollset_posix.h
index f624337..cdcb995 100644
--- a/src/core/iomgr/pollset_posix.h
+++ b/src/core/iomgr/pollset_posix.h
@@ -78,7 +78,11 @@
    poll after an fd is orphaned) */
 void grpc_pollset_del_fd(grpc_pollset *pollset, struct grpc_fd *fd);
 
-/* Force any current pollers to break polling */
+/* Force any current pollers to break polling: it's the callers responsibility
+   to ensure that the pollset indeed needs to be kicked - no verification that
+   the pollset is actually performing polling work is done. At worst this will
+   result in spurious wakeups if performed at the wrong moment.
+   Does not touch pollset->mu. */
 void grpc_pollset_force_kick(grpc_pollset *pollset);
 /* Returns the fd to listen on for kicks */
 int grpc_kick_read_fd(grpc_pollset *p);
diff --git a/src/core/iomgr/resolve_address.c b/src/core/iomgr/resolve_address.c
index 0168116..575f884 100644
--- a/src/core/iomgr/resolve_address.c
+++ b/src/core/iomgr/resolve_address.c
@@ -31,7 +31,9 @@
  *
  */
 
+#ifndef _POSIX_SOURCE
 #define _POSIX_SOURCE
+#endif
 
 #include "src/core/iomgr/sockaddr.h"
 #include "src/core/iomgr/resolve_address.h"
diff --git a/src/core/iomgr/socket_utils_linux.c b/src/core/iomgr/socket_utils_linux.c
index f971cb3..7ef5894 100644
--- a/src/core/iomgr/socket_utils_linux.c
+++ b/src/core/iomgr/socket_utils_linux.c
@@ -31,7 +31,9 @@
  *
  */
 
+#ifndef _GNU_SOURCE
 #define _GNU_SOURCE
+#endif
 #include <grpc/support/port_platform.h>
 
 #ifdef GPR_LINUX
diff --git a/src/core/iomgr/socket_utils_posix.c b/src/core/iomgr/socket_utils_posix.c
index 06c5033..9184b2a 100644
--- a/src/core/iomgr/socket_utils_posix.c
+++ b/src/core/iomgr/socket_utils_posix.c
@@ -35,7 +35,6 @@
 
 #ifdef GPR_POSIX_SOCKETUTILS
 
-#define _BSD_SOURCE
 #include "src/core/iomgr/socket_utils_posix.h"
 
 #include <fcntl.h>
diff --git a/src/core/iomgr/tcp_server_posix.c b/src/core/iomgr/tcp_server_posix.c
index d169d23..091f0aa 100644
--- a/src/core/iomgr/tcp_server_posix.c
+++ b/src/core/iomgr/tcp_server_posix.c
@@ -31,11 +31,15 @@
  *
  */
 
+/* FIXME: "posix" files shouldn't be depending on _GNU_SOURCE */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
 #include <grpc/support/port_platform.h>
 
 #ifdef GPR_POSIX_SOCKET
 
-#define _GNU_SOURCE
 #include "src/core/iomgr/tcp_server.h"
 
 #include <limits.h>
diff --git a/src/core/iomgr/wakeup_fd_eventfd.c b/src/core/iomgr/wakeup_fd_eventfd.c
index 3ee7f94..99c32bb 100644
--- a/src/core/iomgr/wakeup_fd_eventfd.c
+++ b/src/core/iomgr/wakeup_fd_eventfd.c
@@ -74,7 +74,7 @@
   return 1;
 }
 
-const grpc_wakeup_fd_vtable specialized_wakeup_fd_vtable = {
+const grpc_wakeup_fd_vtable grpc_specialized_wakeup_fd_vtable = {
   eventfd_create, eventfd_consume, eventfd_wakeup, eventfd_destroy,
   eventfd_check_availability
 };
diff --git a/src/core/iomgr/wakeup_fd_nospecial.c b/src/core/iomgr/wakeup_fd_nospecial.c
index 21e8074..c1038bf 100644
--- a/src/core/iomgr/wakeup_fd_nospecial.c
+++ b/src/core/iomgr/wakeup_fd_nospecial.c
@@ -38,16 +38,17 @@
 
 #include <grpc/support/port_platform.h>
 
-#ifndef GPR_POSIX_HAS_SPECIAL_WAKEUP_FD
+#ifdef GPR_POSIX_NO_SPECIAL_WAKEUP_FD
 
-#include "src/core/iomgr/wakeup_fd.h"
+#include "src/core/iomgr/wakeup_fd_posix.h"
+#include <stddef.h>
 
 static int check_availability_invalid(void) {
   return 0;
 }
 
-const grpc_wakeup_fd_vtable specialized_wakeup_fd_vtable = {
+const grpc_wakeup_fd_vtable grpc_specialized_wakeup_fd_vtable = {
   NULL, NULL, NULL, NULL, check_availability_invalid
 };
 
-#endif /* GPR_POSIX_HAS_SPECIAL_WAKEUP */
+#endif  /* GPR_POSIX_NO_SPECIAL_WAKEUP_FD */
diff --git a/src/core/iomgr/wakeup_fd_pipe.c b/src/core/iomgr/wakeup_fd_pipe.c
index f36e6ee..f895478 100644
--- a/src/core/iomgr/wakeup_fd_pipe.c
+++ b/src/core/iomgr/wakeup_fd_pipe.c
@@ -31,7 +31,10 @@
  *
  */
 
-/* TODO(klempner): Allow this code to be disabled. */
+#include <grpc/support/port_platform.h>
+
+#ifdef GPR_POSIX_WAKEUP_FD
+
 #include "src/core/iomgr/wakeup_fd_posix.h"
 
 #include <errno.h>
@@ -87,7 +90,8 @@
   return 1;
 }
 
-const grpc_wakeup_fd_vtable pipe_wakeup_fd_vtable = {
+const grpc_wakeup_fd_vtable grpc_pipe_wakeup_fd_vtable = {
   pipe_create, pipe_consume, pipe_wakeup, pipe_destroy, pipe_check_availability
 };
 
+#endif  /* GPR_POSIX_WAKUP_FD */
diff --git a/src/core/iomgr/wakeup_fd_pipe.h b/src/core/iomgr/wakeup_fd_pipe.h
index fc2898f..a2fcde5 100644
--- a/src/core/iomgr/wakeup_fd_pipe.h
+++ b/src/core/iomgr/wakeup_fd_pipe.h
@@ -36,6 +36,6 @@
 
 #include "src/core/iomgr/wakeup_fd_posix.h"
 
-extern grpc_wakeup_fd_vtable pipe_wakeup_fd_vtable;
+extern grpc_wakeup_fd_vtable grpc_pipe_wakeup_fd_vtable;
 
 #endif  /* __GRPC_INTERNAL_IOMGR_WAKEUP_FD_PIPE_H_ */
diff --git a/src/core/iomgr/wakeup_fd_posix.c b/src/core/iomgr/wakeup_fd_posix.c
index 9107cf3..d3cc3ec 100644
--- a/src/core/iomgr/wakeup_fd_posix.c
+++ b/src/core/iomgr/wakeup_fd_posix.c
@@ -31,6 +31,10 @@
  *
  */
 
+#include <grpc/support/port_platform.h>
+
+#ifdef GPR_POSIX_WAKEUP_FD
+
 #include "src/core/iomgr/wakeup_fd_posix.h"
 #include "src/core/iomgr/wakeup_fd_pipe.h"
 #include <stddef.h>
@@ -38,15 +42,15 @@
 static const grpc_wakeup_fd_vtable *wakeup_fd_vtable = NULL;
 
 void grpc_wakeup_fd_global_init(void) {
-  if (specialized_wakeup_fd_vtable.check_availability()) {
-    wakeup_fd_vtable = &specialized_wakeup_fd_vtable;
+  if (grpc_specialized_wakeup_fd_vtable.check_availability()) {
+    wakeup_fd_vtable = &grpc_specialized_wakeup_fd_vtable;
   } else {
-    wakeup_fd_vtable = &pipe_wakeup_fd_vtable;
+    wakeup_fd_vtable = &grpc_pipe_wakeup_fd_vtable;
   }
 }
 
 void grpc_wakeup_fd_global_init_force_fallback(void) {
-  wakeup_fd_vtable = &pipe_wakeup_fd_vtable;
+  wakeup_fd_vtable = &grpc_pipe_wakeup_fd_vtable;
 }
 
 void grpc_wakeup_fd_global_destroy(void) {
@@ -68,3 +72,5 @@
 void grpc_wakeup_fd_destroy(grpc_wakeup_fd_info *fd_info) {
   wakeup_fd_vtable->destroy(fd_info);
 }
+
+#endif  /* GPR_POSIX_WAKEUP_FD */
diff --git a/src/core/iomgr/wakeup_fd_posix.h b/src/core/iomgr/wakeup_fd_posix.h
index c2769af..75bb9fc 100644
--- a/src/core/iomgr/wakeup_fd_posix.h
+++ b/src/core/iomgr/wakeup_fd_posix.h
@@ -62,29 +62,14 @@
 #ifndef __GRPC_INTERNAL_IOMGR_WAKEUP_FD_POSIX_H_
 #define __GRPC_INTERNAL_IOMGR_WAKEUP_FD_POSIX_H_
 
-typedef struct grpc_wakeup_fd_info grpc_wakeup_fd_info;
-
 void grpc_wakeup_fd_global_init(void);
 void grpc_wakeup_fd_global_destroy(void);
 
-
-void grpc_wakeup_fd_create(grpc_wakeup_fd_info *fd_info);
-void grpc_wakeup_fd_consume_wakeup(grpc_wakeup_fd_info *fd_info);
-void grpc_wakeup_fd_wakeup(grpc_wakeup_fd_info *fd_info);
-void grpc_wakeup_fd_destroy(grpc_wakeup_fd_info *fd_info);
-
-#define GRPC_WAKEUP_FD_GET_READ_FD(fd_info) ((fd_info)->read_fd)
-
 /* Force using the fallback implementation. This is intended for testing
  * purposes only.*/
 void grpc_wakeup_fd_global_init_force_fallback(void);
 
-/* Private structures; don't access their fields directly outside of wakeup fd
- * code. */
-struct grpc_wakeup_fd_info {
-  int read_fd;
-  int write_fd;
-};
+typedef struct grpc_wakeup_fd_info grpc_wakeup_fd_info;
 
 typedef struct grpc_wakeup_fd_vtable {
   void (*create)(grpc_wakeup_fd_info *fd_info);
@@ -95,8 +80,20 @@
   int (*check_availability)(void);
 } grpc_wakeup_fd_vtable;
 
+struct grpc_wakeup_fd_info {
+  int read_fd;
+  int write_fd;
+};
+
+#define GRPC_WAKEUP_FD_GET_READ_FD(fd_info) ((fd_info)->read_fd)
+
+void grpc_wakeup_fd_create(grpc_wakeup_fd_info *fd_info);
+void grpc_wakeup_fd_consume_wakeup(grpc_wakeup_fd_info *fd_info);
+void grpc_wakeup_fd_wakeup(grpc_wakeup_fd_info *fd_info);
+void grpc_wakeup_fd_destroy(grpc_wakeup_fd_info *fd_info);
+
 /* Defined in some specialized implementation's .c file, or by
  * wakeup_fd_nospecial.c if no such implementation exists. */
-extern const grpc_wakeup_fd_vtable specialized_wakeup_fd_vtable;
+extern const grpc_wakeup_fd_vtable grpc_specialized_wakeup_fd_vtable;
 
 #endif /* __GRPC_INTERNAL_IOMGR_WAKEUP_FD_POSIX_H_ */
diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c
index 7b7d8f3..6f0d72c 100644
--- a/src/core/security/credentials.c
+++ b/src/core/security/credentials.c
@@ -216,14 +216,10 @@
 static void ssl_build_config(const char *pem_root_certs,
                              grpc_ssl_pem_key_cert_pair *pem_key_cert_pair,
                              grpc_ssl_config *config) {
-  if (pem_root_certs == NULL) {
-    /* TODO(jboeuf): Get them from the environment. */
-    gpr_log(GPR_ERROR, "Default SSL roots not yet implemented.");
-  } else {
+  if (pem_root_certs != NULL) {
     ssl_copy_key_material(pem_root_certs, &config->pem_root_certs,
                           &config->pem_root_certs_size);
   }
-
   if (pem_key_cert_pair != NULL) {
     GPR_ASSERT(pem_key_cert_pair->private_key != NULL);
     GPR_ASSERT(pem_key_cert_pair->cert_chain != NULL);
diff --git a/src/core/security/security_context.c b/src/core/security/security_context.c
index 58cd458..1edec29 100644
--- a/src/core/security/security_context.c
+++ b/src/core/security/security_context.c
@@ -39,6 +39,8 @@
 #include "src/core/channel/http_client_filter.h"
 #include "src/core/security/credentials.h"
 #include "src/core/security/secure_endpoint.h"
+#include "src/core/support/env.h"
+#include "src/core/support/file.h"
 #include "src/core/support/string.h"
 #include "src/core/surface/lame_client.h"
 #include "src/core/transport/chttp2/alpn.h"
@@ -319,6 +321,28 @@
 static grpc_security_context_vtable ssl_server_vtable = {
     ssl_server_destroy, ssl_server_create_handshaker, ssl_server_check_peer};
 
+static gpr_slice default_pem_root_certs;
+
+static void init_default_pem_root_certs(void) {
+  char *default_root_certs_path =
+      gpr_getenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR);
+  if (default_root_certs_path == NULL) {
+    default_pem_root_certs = gpr_empty_slice();
+  } else {
+    default_pem_root_certs = gpr_load_file(default_root_certs_path, NULL);
+    gpr_free(default_root_certs_path);
+  }
+}
+
+static size_t get_default_pem_roots(const unsigned char **pem_root_certs) {
+  /* TODO(jboeuf@google.com): Maybe revisit the approach which consists in
+     loading all the roots once for the lifetime of the process. */
+  static gpr_once once = GPR_ONCE_INIT;
+  gpr_once_init(&once, init_default_pem_root_certs);
+  *pem_root_certs = GPR_SLICE_START_PTR(default_pem_root_certs);
+  return GPR_SLICE_LENGTH(default_pem_root_certs);
+}
+
 grpc_security_status grpc_ssl_channel_security_context_create(
     grpc_credentials *request_metadata_creds, const grpc_ssl_config *config,
     const char *secure_peer_name, grpc_channel_security_context **ctx) {
@@ -330,6 +354,8 @@
   tsi_result result = TSI_OK;
   grpc_ssl_channel_security_context *c;
   size_t i;
+  const unsigned char *pem_root_certs;
+  size_t pem_root_certs_size;
 
   for (i = 0; i < num_alpn_protocols; i++) {
     alpn_protocol_strings[i] =
@@ -338,9 +364,8 @@
         strlen(grpc_chttp2_get_alpn_version_index(i));
   }
 
-  if (config == NULL || secure_peer_name == NULL ||
-      config->pem_root_certs == NULL) {
-    gpr_log(GPR_ERROR, "An ssl channel needs a secure name and root certs.");
+  if (config == NULL || secure_peer_name == NULL) {
+    gpr_log(GPR_ERROR, "An ssl channel needs a config and a secure name.");
     goto error;
   }
   if (!check_request_metadata_creds(request_metadata_creds)) {
@@ -357,11 +382,20 @@
   if (secure_peer_name != NULL) {
     c->secure_peer_name = gpr_strdup(secure_peer_name);
   }
+  if (config->pem_root_certs == NULL) {
+    pem_root_certs_size = get_default_pem_roots(&pem_root_certs);
+    if (pem_root_certs == NULL || pem_root_certs_size == 0) {
+      gpr_log(GPR_ERROR, "Could not get default pem root certs.");
+      goto error;
+    }
+  } else {
+    pem_root_certs = config->pem_root_certs;
+    pem_root_certs_size = config->pem_root_certs_size;
+  }
   result = tsi_create_ssl_client_handshaker_factory(
       config->pem_private_key, config->pem_private_key_size,
-      config->pem_cert_chain, config->pem_cert_chain_size,
-      config->pem_root_certs, config->pem_root_certs_size,
-      GRPC_SSL_CIPHER_SUITES, alpn_protocol_strings,
+      config->pem_cert_chain, config->pem_cert_chain_size, pem_root_certs,
+      pem_root_certs_size, GRPC_SSL_CIPHER_SUITES, alpn_protocol_strings,
       alpn_protocol_string_lengths, num_alpn_protocols, &c->handshaker_factory);
   if (result != TSI_OK) {
     gpr_log(GPR_ERROR, "Handshaker factory creation failed with %s.",
diff --git a/src/core/support/cpu_linux.c b/src/core/support/cpu_linux.c
index eab8b7f..ad82174 100644
--- a/src/core/support/cpu_linux.c
+++ b/src/core/support/cpu_linux.c
@@ -31,44 +31,17 @@
  *
  */
 
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif  /* _GNU_SOURCE */
+
 #include <grpc/support/port_platform.h>
 
 #ifdef GPR_CPU_LINUX
 
 #include "src/core/support/cpu.h"
 
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#define GRPC_GNU_SOURCE
-#endif
-
-#ifndef __USE_GNU
-#define __USE_GNU
-#define GRPC_USE_GNU
-#endif
-
-#ifndef __USE_MISC
-#define __USE_MISC
-#define GRPC_USE_MISC
-#endif
-
 #include <sched.h>
-
-#ifdef GRPC_GNU_SOURCE
-#undef _GNU_SOURCE
-#undef GRPC_GNU_SOURCE
-#endif
-
-#ifdef GRPC_USE_GNU
-#undef __USE_GNU
-#undef GRPC_USE_GNU
-#endif
-
-#ifdef GRPC_USE_MISC
-#undef __USE_MISC
-#undef GRPC_USE_MISC
-#endif
-
 #include <errno.h>
 #include <unistd.h>
 #include <string.h>
diff --git a/examples/tips/client.h b/src/core/support/env.h
similarity index 70%
copy from examples/tips/client.h
copy to src/core/support/env.h
index 661ee5c..81dda7d 100644
--- a/examples/tips/client.h
+++ b/src/core/support/env.h
@@ -31,32 +31,30 @@
  *
  */
 
-#ifndef __GRPCPP_EXAMPLES_TIPS_CLIENT_H_
-#define __GRPCPP_EXAMPLES_TIPS_CLIENT_H_
+#ifndef __GRPC_SUPPORT_ENV_H__
+#define __GRPC_SUPPORT_ENV_H__
 
-#include <grpc++/channel_interface.h>
-#include <grpc++/status.h>
+#include <stdio.h>
 
-#include "examples/tips/pubsub.pb.h"
+#include <grpc/support/slice.h>
 
-namespace grpc {
-namespace examples {
-namespace tips {
+#ifdef __cplusplus
+extern "C" {
+#endif
 
-class Client {
- public:
-  Client(std::shared_ptr<grpc::ChannelInterface> channel);
-  Status CreateTopic(grpc::string topic);
-  Status GetTopic(grpc::string topic);
-  Status DeleteTopic(grpc::string topic);
-  Status ListTopics();
+/* Env utility functions */
 
- private:
-  std::unique_ptr<tech::pubsub::PublisherService::Stub> stub_;
-};
+/* Gets the environment variable value with the specified name.
+   Returns a newly allocated string. It is the responsability of the caller to
+   gpr_free the return value if not NULL (which means that the environment
+   variable exists). */
+char *gpr_getenv(const char *name);
 
-}  // namespace tips
-}  // namespace examples
-}  // namespace grpc
+/* Sets the the environment with the specified name to the specified value. */
+void gpr_setenv(const char *name, const char *value);
 
-#endif  // __GRPCPP_EXAMPLES_TIPS_CLIENT_H_
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GRPC_SUPPORT_ENV_H__ */
diff --git a/include/grpc/support/time_posix.h b/src/core/support/env_linux.c
similarity index 73%
copy from include/grpc/support/time_posix.h
copy to src/core/support/env_linux.c
index 9ff6f7f..28e3d14 100644
--- a/include/grpc/support/time_posix.h
+++ b/src/core/support/env_linux.c
@@ -31,13 +31,31 @@
  *
  */
 
-#ifndef __GRPC_SUPPORT_TIME_POSIX_H__
-#define __GRPC_SUPPORT_TIME_POSIX_H__
-/* Posix variant of gpr_time_platform.h */
+/* for secure_getenv. */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
 
-#include <sys/time.h>
-#include <time.h>
+#include <grpc/support/port_platform.h>
 
-typedef struct timespec gpr_timespec;
+#ifdef GPR_LINUX_ENV
 
-#endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */
+#include "src/core/support/env.h"
+
+#include <stdlib.h>
+
+#include <grpc/support/log.h>
+
+#include "src/core/support/string.h"
+
+char *gpr_getenv(const char *name) {
+  char *result = secure_getenv(name);
+  return result == NULL ? result : gpr_strdup(result);
+}
+
+void gpr_setenv(const char *name, const char *value) {
+  int res = setenv(name, value, 1);
+  GPR_ASSERT(res == 0);
+}
+
+#endif /* GPR_LINUX_ENV */
diff --git a/include/grpc/support/time_posix.h b/src/core/support/env_posix.c
similarity index 76%
copy from include/grpc/support/time_posix.h
copy to src/core/support/env_posix.c
index 9ff6f7f..bcbff9a 100644
--- a/include/grpc/support/time_posix.h
+++ b/src/core/support/env_posix.c
@@ -31,13 +31,26 @@
  *
  */
 
-#ifndef __GRPC_SUPPORT_TIME_POSIX_H__
-#define __GRPC_SUPPORT_TIME_POSIX_H__
-/* Posix variant of gpr_time_platform.h */
+#include <grpc/support/port_platform.h>
 
-#include <sys/time.h>
-#include <time.h>
+#ifdef GPR_POSIX_ENV
 
-typedef struct timespec gpr_timespec;
+#include "src/core/support/env.h"
 
-#endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */
+#include <stdlib.h>
+
+#include <grpc/support/log.h>
+
+#include "src/core/support/string.h"
+
+char *gpr_getenv(const char *name) {
+  char *result = getenv(name);
+  return result == NULL ? result : gpr_strdup(result);
+}
+
+void gpr_setenv(const char *name, const char *value) {
+  int res = setenv(name, value, 1);
+  GPR_ASSERT(res == 0);
+}
+
+#endif /* GPR_POSIX_ENV */
diff --git a/include/grpc/support/time_posix.h b/src/core/support/env_win32.c
similarity index 72%
copy from include/grpc/support/time_posix.h
copy to src/core/support/env_win32.c
index 9ff6f7f..a31fa79 100644
--- a/include/grpc/support/time_posix.h
+++ b/src/core/support/env_win32.c
@@ -31,13 +31,30 @@
  *
  */
 
-#ifndef __GRPC_SUPPORT_TIME_POSIX_H__
-#define __GRPC_SUPPORT_TIME_POSIX_H__
-/* Posix variant of gpr_time_platform.h */
+#include <grpc/support/port_platform.h>
 
-#include <sys/time.h>
-#include <time.h>
+#ifdef GPR_WIN32
 
-typedef struct timespec gpr_timespec;
+#include "src/core/support/env.h"
 
-#endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */
+#include <stdlib.h>
+
+#include <grpc/support/log.h>
+
+char *gpr_getenv(const char *name) {
+  size_t required_size;
+  char *result = NULL;
+
+  getenv_s(&required_size, NULL, 0, name);
+  if (required_size == 0) return NULL;
+  result = gpr_malloc(required_size);
+  getenv_s(&required_size, result, required_size, name);
+  return result;
+}
+
+void gpr_setenv(const char *name, const char *value) {
+  errno_t res = _putenv_s(name, value);
+  GPR_ASSERT(res == 0);
+}
+
+#endif /* GPR_WIN32 */
diff --git a/src/core/support/file.c b/src/core/support/file.c
new file mode 100644
index 0000000..c0bb1b6
--- /dev/null
+++ b/src/core/support/file.c
@@ -0,0 +1,89 @@
+/*
+ *
+ * Copyright 2014, 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 "src/core/support/file.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+#include "src/core/support/string.h"
+
+gpr_slice gpr_load_file(const char *filename, int *success) {
+  unsigned char *contents = NULL;
+  size_t contents_size = 0;
+  unsigned char buf[4096];
+  char *error_msg = NULL;
+  gpr_slice result = gpr_empty_slice();
+  FILE *file = fopen(filename, "rb");
+
+  if (file == NULL) {
+    gpr_asprintf(&error_msg, "Could not open file %s (error = %s).", filename,
+                 strerror(errno));
+    GPR_ASSERT(error_msg != NULL);
+    goto end;
+  }
+
+  while (1) {
+    size_t bytes_read = fread(buf, 1, sizeof(buf), file);
+    if (bytes_read > 0) {
+      contents = gpr_realloc(contents, contents_size + bytes_read);
+      memcpy(contents + contents_size, buf, bytes_read);
+      contents_size += bytes_read;
+    }
+    if (bytes_read < sizeof(buf)) {
+      if (ferror(file)) {
+        gpr_asprintf(&error_msg, "Error %s occured while reading file %s.",
+                     strerror(errno), filename);
+        GPR_ASSERT(error_msg != NULL);
+        goto end;
+      } else {
+        GPR_ASSERT(feof(file));
+        break;
+      }
+    }
+  }
+  if (success != NULL) *success = 1;
+  result = gpr_slice_new(contents, contents_size, gpr_free);
+
+end:
+  if (error_msg != NULL) {
+    gpr_log(GPR_ERROR, "%s", error_msg);
+    gpr_free(error_msg);
+    if (success != NULL) *success = 0;
+  }
+  if (file != NULL) fclose(file);
+  return result;
+}
diff --git a/include/grpc/support/time_posix.h b/src/core/support/file.h
similarity index 66%
copy from include/grpc/support/time_posix.h
copy to src/core/support/file.h
index 9ff6f7f..92f420e 100644
--- a/include/grpc/support/time_posix.h
+++ b/src/core/support/file.h
@@ -31,13 +31,31 @@
  *
  */
 
-#ifndef __GRPC_SUPPORT_TIME_POSIX_H__
-#define __GRPC_SUPPORT_TIME_POSIX_H__
-/* Posix variant of gpr_time_platform.h */
+#ifndef __GRPC_SUPPORT_FILE_H__
+#define __GRPC_SUPPORT_FILE_H__
 
-#include <sys/time.h>
-#include <time.h>
+#include <stdio.h>
 
-typedef struct timespec gpr_timespec;
+#include <grpc/support/slice.h>
 
-#endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* File utility functions */
+
+/* Loads the content of a file into a slice. The success parameter, if not NULL,
+   will be set to 1 in case of success and 0 in case of failure. */
+gpr_slice gpr_load_file(const char *filename, int *success);
+
+/* Creates a temporary file from a prefix.
+   If tmp_filename is not NULL, *tmp_filename is assigned the name of the
+   created file and it is the responsibility of the caller to gpr_free it
+   unless an error occurs in which case it will be set to NULL. */
+FILE *gpr_tmpfile(const char *prefix, char **tmp_filename);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GRPC_SUPPORT_FILE_H__ */
diff --git a/src/core/support/file_posix.c b/src/core/support/file_posix.c
new file mode 100644
index 0000000..cb48b3d
--- /dev/null
+++ b/src/core/support/file_posix.c
@@ -0,0 +1,97 @@
+/*
+ *
+ * Copyright 2014, 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.
+ *
+ */
+
+/* Posix code for gpr fdopen and mkstemp support. */
+
+#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 200112L
+#undef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200112L
+#endif
+
+/* Don't know why I have to do this for mkstemp, looks like _POSIX_C_SOURCE
+   should be enough... */
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+
+#include <grpc/support/port_platform.h>
+
+#ifdef GPR_POSIX_FILE
+
+#include "src/core/support/file.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+#include "src/core/support/string.h"
+
+FILE *gpr_tmpfile(const char *prefix, char **tmp_filename) {
+  FILE *result = NULL;
+  char *template;
+  int fd;
+
+  if (tmp_filename != NULL) *tmp_filename = NULL;
+
+  gpr_asprintf(&template, "/tmp/%s_XXXXXX", prefix);
+  GPR_ASSERT(template != NULL);
+
+  fd = mkstemp(template);
+  if (fd == -1) {
+    gpr_log(GPR_ERROR, "mkstemp failed for template %s with error %s.",
+            template, strerror(errno));
+    goto end;
+  }
+  result = fdopen(fd, "w+");
+  if (result == NULL) {
+    gpr_log(GPR_ERROR, "Could not open file %s from fd %d (error = %s).",
+            template, fd, strerror(errno));
+    unlink(template);
+    close(fd);
+    goto end;
+  }
+
+end:
+  if (result != NULL && tmp_filename != NULL) {
+    *tmp_filename = template;
+  } else {
+    gpr_free(template);
+  }
+  return result;
+}
+
+#endif /* GPR_POSIX_FILE */
diff --git a/src/core/support/file_win32.c b/src/core/support/file_win32.c
new file mode 100644
index 0000000..d415281
--- /dev/null
+++ b/src/core/support/file_win32.c
@@ -0,0 +1,78 @@
+/*
+ *
+ * Copyright 2014, 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/support/port_platform.h>
+
+#ifdef GPR_WIN32
+
+#include "src/core/support/file.h"
+
+#include <io.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+FILE *gpr_tmpfile(const char *prefix, char **tmp_filename) {
+  FILE *result = NULL;
+  char *template;
+
+  if (tmp_filename != NULL) *tmp_filename = NULL;
+
+  gpr_asprintf(&template, "%s_XXXXXX", prefix);
+  GPR_ASSERT(template != NULL);
+
+  /* _mktemp_s can only create a maximum of 26 file names for any combination of
+     base and template values which is kind of sad... We may revisit this
+     function later to have something better... */
+  if (_mktemp_s(template, strlen(template) + 1) != 0) {
+    gpr_log(LOG_ERROR, "Could not create tmp file.");
+    goto end;
+  }
+  if (fopen_s(&result, template, "wb+") != 0) {
+    gpr_log(GPR_ERROR, "Could not open file %s", template);
+    result = NULL;
+    goto end;
+  }
+
+end:
+  if (result != NULL && tmp_filename != NULL) {
+    *tmp_filename = template;
+  } else {
+    gpr_free(template);
+  }
+  return result;
+}
+
+#endif /* GPR_WIN32 */
diff --git a/src/core/support/log_linux.c b/src/core/support/log_linux.c
index a0307e1..a64faa9 100644
--- a/src/core/support/log_linux.c
+++ b/src/core/support/log_linux.c
@@ -31,8 +31,14 @@
  *
  */
 
+#ifndef _POSIX_SOURCE
 #define _POSIX_SOURCE
+#endif
+
+#ifndef _GNU_SOURCE
 #define _GNU_SOURCE
+#endif
+
 #include <grpc/support/port_platform.h>
 
 #ifdef GPR_LINUX
diff --git a/src/core/support/log_posix.c b/src/core/support/log_posix.c
index 1292c9e..05f45de 100644
--- a/src/core/support/log_posix.c
+++ b/src/core/support/log_posix.c
@@ -31,11 +31,16 @@
  *
  */
 
-#ifndef _POSIX_C_SOURCE
+#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 200112L
+#undef _POSIX_C_SOURCE
 #define _POSIX_C_SOURCE 200112L
 #endif
 
+/* FIXME: "posix" files probably shouldn't depend on _GNU_SOURCE */
+#ifndef _GNU_SOURCE
 #define _GNU_SOURCE
+#endif
+
 #include <grpc/support/port_platform.h>
 
 #if defined(GPR_POSIX_LOG)
@@ -64,7 +69,7 @@
   va_end(args);
   if (ret < 0) {
     message = NULL;
-  } else if (ret <= sizeof(buf) - 1) {
+  } else if ((size_t)ret <= sizeof(buf) - 1) {
     message = buf;
   } else {
     message = allocated = gpr_malloc(ret + 1);
diff --git a/src/core/support/string_posix.c b/src/core/support/string_posix.c
index 5783281..a6bb805 100644
--- a/src/core/support/string_posix.c
+++ b/src/core/support/string_posix.c
@@ -33,7 +33,8 @@
 
 /* Posix code for gpr snprintf support. */
 
-#ifndef _POSIX_C_SOURCE
+#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 200112L
+#undef _POSIX_C_SOURCE
 #define _POSIX_C_SOURCE 200112L
 #endif
 
diff --git a/src/core/support/sync_posix.c b/src/core/support/sync_posix.c
index 7f0e4a9..a28a4c6 100644
--- a/src/core/support/sync_posix.c
+++ b/src/core/support/sync_posix.c
@@ -33,11 +33,17 @@
 
 /* Posix gpr synchroization support code. */
 
+#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 199309L
+#undef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 199309L
+#endif
+
 #include <grpc/support/port_platform.h>
 
 #ifdef GPR_POSIX_SYNC
 
 #include <errno.h>
+#include <time.h>
 #include <grpc/support/log.h>
 #include <grpc/support/sync.h>
 #include <grpc/support/time.h>
@@ -67,7 +73,10 @@
   if (gpr_time_cmp(abs_deadline, gpr_inf_future) == 0) {
     err = pthread_cond_wait(cv, mu);
   } else {
-    err = pthread_cond_timedwait(cv, mu, &abs_deadline);
+    struct timespec abs_deadline_ts;
+    abs_deadline_ts.tv_sec = abs_deadline.tv_sec;
+    abs_deadline_ts.tv_nsec = abs_deadline.tv_nsec;
+    err = pthread_cond_timedwait(cv, mu, &abs_deadline_ts);
   }
   GPR_ASSERT(err == 0 || err == ETIMEDOUT || err == EAGAIN);
   return err == ETIMEDOUT;
diff --git a/src/core/support/thd_posix.c b/src/core/support/thd_posix.c
index bac1d9c..74ca942 100644
--- a/src/core/support/thd_posix.c
+++ b/src/core/support/thd_posix.c
@@ -62,17 +62,19 @@
                 const gpr_thd_options *options) {
   int thread_started;
   pthread_attr_t attr;
+  pthread_t p;
   struct thd_arg *a = gpr_malloc(sizeof(*a));
   a->body = thd_body;
   a->arg = arg;
 
   GPR_ASSERT(pthread_attr_init(&attr) == 0);
   GPR_ASSERT(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0);
-  thread_started = (pthread_create(t, &attr, &thread_body, a) == 0);
+  thread_started = (pthread_create(&p, &attr, &thread_body, a) == 0);
   GPR_ASSERT(pthread_attr_destroy(&attr) == 0);
   if (!thread_started) {
     gpr_free(a);
   }
+  *t = (gpr_thd_id)p;
   return thread_started;
 }
 
@@ -82,4 +84,8 @@
   return options;
 }
 
+gpr_thd_id gpr_thd_currentid(void) {
+  return (gpr_thd_id)pthread_self();
+}
+
 #endif /* GPR_POSIX_SYNC */
diff --git a/src/core/support/thd_win32.c b/src/core/support/thd_win32.c
index 1762f87..2ee1417 100644
--- a/src/core/support/thd_win32.c
+++ b/src/core/support/thd_win32.c
@@ -58,16 +58,18 @@
 int gpr_thd_new(gpr_thd_id *t, void (*thd_body)(void *arg), void *arg,
                 const gpr_thd_options *options) {
   HANDLE handle;
+  DWORD thread_id;
   struct thd_arg *a = gpr_malloc(sizeof(*a));
   a->body = thd_body;
   a->arg = arg;
   *t = 0;
-  handle = CreateThread(NULL, 64 * 1024, thread_body, a, 0, NULL);
+  handle = CreateThread(NULL, 64 * 1024, thread_body, a, 0, &thread_id);
   if (handle == NULL) {
     gpr_free(a);
   } else {
     CloseHandle(handle); /* threads are "detached" */
   }
+  *t = (gpr_thd_id)thread_id;
   return handle != NULL;
 }
 
@@ -77,4 +79,8 @@
   return options;
 }
 
+gpr_thd_id gpr_thd_currentid(void) {
+  return (gpr_thd_id)GetCurrentThreadId();
+}
+
 #endif /* GPR_WIN32 */
diff --git a/src/core/support/time.c b/src/core/support/time.c
index 9724331..268a43c 100644
--- a/src/core/support/time.c
+++ b/src/core/support/time.c
@@ -234,22 +234,6 @@
   }
 }
 
-struct timeval gpr_timeval_from_timespec(gpr_timespec t) {
-  /* TODO(klempner): Consider whether this should round up, since it is likely
-     to be used for delays */
-  struct timeval tv;
-  tv.tv_sec = t.tv_sec;
-  tv.tv_usec = t.tv_nsec / 1000;
-  return tv;
-}
-
-gpr_timespec gpr_timespec_from_timeval(struct timeval t) {
-  gpr_timespec ts;
-  ts.tv_sec = t.tv_sec;
-  ts.tv_nsec = t.tv_usec * 1000;
-  return ts;
-}
-
 gpr_int32 gpr_time_to_millis(gpr_timespec t) {
   if (t.tv_sec >= 2147483) {
     if (t.tv_sec == 2147483 && t.tv_nsec < 648 * GPR_NS_PER_MS) {
diff --git a/src/core/support/time_posix.c b/src/core/support/time_posix.c
index 9e11f8a..7f0f028 100644
--- a/src/core/support/time_posix.c
+++ b/src/core/support/time_posix.c
@@ -34,7 +34,8 @@
 /* Posix code for gpr time support. */
 
 /* So we get nanosleep and clock_* */
-#ifndef _POSIX_C_SOURCE
+#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 199309L
+#undef _POSIX_C_SOURCE
 #define _POSIX_C_SOURCE 199309L
 #endif
 
@@ -47,11 +48,25 @@
 #include <unistd.h>
 #include <grpc/support/time.h>
 
+static struct timespec timespec_from_gpr(gpr_timespec gts) {
+  struct timespec rv;
+  rv.tv_sec = gts.tv_sec;
+  rv.tv_nsec = gts.tv_nsec;
+  return rv;
+}
+
 #if _POSIX_TIMERS > 0
+static gpr_timespec gpr_from_timespec(struct timespec ts) {
+  gpr_timespec rv;
+  rv.tv_sec = ts.tv_sec;
+  rv.tv_nsec = ts.tv_nsec;
+  return rv;
+}
+
 gpr_timespec gpr_now(void) {
-  gpr_timespec now;
+  struct timespec now;
   clock_gettime(CLOCK_REALTIME, &now);
-  return now;
+  return gpr_from_timespec(now);
 }
 #else
 /* For some reason Apple's OSes haven't implemented clock_gettime. */
@@ -69,6 +84,7 @@
 void gpr_sleep_until(gpr_timespec until) {
   gpr_timespec now;
   gpr_timespec delta;
+  struct timespec delta_ts;
 
   for (;;) {
     /* We could simplify by using clock_nanosleep instead, but it might be
@@ -79,7 +95,8 @@
     }
 
     delta = gpr_time_sub(until, now);
-    if (nanosleep(&delta, NULL) == 0) {
+    delta_ts = timespec_from_gpr(delta);
+    if (nanosleep(&delta_ts, NULL) == 0) {
       break;
     }
   }
diff --git a/src/core/surface/byte_buffer_queue.c b/src/core/surface/byte_buffer_queue.c
new file mode 100644
index 0000000..9709a66
--- /dev/null
+++ b/src/core/surface/byte_buffer_queue.c
@@ -0,0 +1,84 @@
+/*
+ *
+ * Copyright 2014, 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 "src/core/surface/byte_buffer_queue.h"
+#include <grpc/support/alloc.h>
+#include <grpc/support/useful.h>
+
+static void bba_destroy(grpc_bbq_array *array, size_t start_pos) {
+  size_t i;
+  for (i = start_pos; i < array->count; i++) {
+    grpc_byte_buffer_destroy(array->data[i]);
+  }
+  gpr_free(array->data);
+}
+
+/* Append an operation to an array, expanding as needed */
+static void bba_push(grpc_bbq_array *a, grpc_byte_buffer *buffer) {
+  if (a->count == a->capacity) {
+    a->capacity = GPR_MAX(a->capacity * 2, 8);
+    a->data = gpr_realloc(a->data, sizeof(grpc_byte_buffer *) * a->capacity);
+  }
+  a->data[a->count++] = buffer;
+}
+
+void grpc_bbq_destroy(grpc_byte_buffer_queue *q) {
+  bba_destroy(&q->filling, 0);
+  bba_destroy(&q->draining, q->drain_pos);
+}
+
+int grpc_bbq_empty(grpc_byte_buffer_queue *q) {
+  return (q->drain_pos == q->draining.count && q->filling.count == 0);
+}
+
+void grpc_bbq_push(grpc_byte_buffer_queue *q, grpc_byte_buffer *buffer) {
+  bba_push(&q->filling, buffer);
+}
+
+grpc_byte_buffer *grpc_bbq_pop(grpc_byte_buffer_queue *q) {
+  grpc_bbq_array temp_array;
+
+  if (q->drain_pos == q->draining.count) {
+    if (q->filling.count == 0) {
+      return NULL;
+    }
+    q->draining.count = 0;
+    q->drain_pos = 0;
+    /* swap arrays */
+    temp_array = q->filling;
+    q->filling = q->draining;
+    q->draining = temp_array;
+  }
+
+  return q->draining.data[q->drain_pos++];
+}
diff --git a/include/grpc/support/time_posix.h b/src/core/surface/byte_buffer_queue.h
similarity index 65%
copy from include/grpc/support/time_posix.h
copy to src/core/surface/byte_buffer_queue.h
index 9ff6f7f..358a42d 100644
--- a/include/grpc/support/time_posix.h
+++ b/src/core/surface/byte_buffer_queue.h
@@ -31,13 +31,29 @@
  *
  */
 
-#ifndef __GRPC_SUPPORT_TIME_POSIX_H__
-#define __GRPC_SUPPORT_TIME_POSIX_H__
-/* Posix variant of gpr_time_platform.h */
+#ifndef __GRPC_INTERNAL_SURFACE_BYTE_BUFFER_QUEUE_H__
+#define __GRPC_INTERNAL_SURFACE_BYTE_BUFFER_QUEUE_H__
 
-#include <sys/time.h>
-#include <time.h>
+#include <grpc/byte_buffer.h>
 
-typedef struct timespec gpr_timespec;
+/* TODO(ctiller): inline an element or two into this struct to avoid per-call
+                  allocations */
+typedef struct {
+  grpc_byte_buffer **data;
+  size_t count;
+  size_t capacity;
+} grpc_bbq_array;
 
-#endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */
+/* should be initialized by zeroing memory */
+typedef struct {
+  size_t drain_pos;
+  grpc_bbq_array filling;
+  grpc_bbq_array draining;
+} grpc_byte_buffer_queue;
+
+void grpc_bbq_destroy(grpc_byte_buffer_queue *q);
+grpc_byte_buffer *grpc_bbq_pop(grpc_byte_buffer_queue *q);
+int grpc_bbq_empty(grpc_byte_buffer_queue *q);
+void grpc_bbq_push(grpc_byte_buffer_queue *q, grpc_byte_buffer *bb);
+
+#endif  /* __GRPC_INTERNAL_SURFACE_BYTE_BUFFER_QUEUE_H__ */
diff --git a/src/core/surface/call.c b/src/core/surface/call.c
index 8bb0779..c68ce5a 100644
--- a/src/core/surface/call.c
+++ b/src/core/surface/call.c
@@ -36,6 +36,7 @@
 #include "src/core/channel/metadata_buffer.h"
 #include "src/core/iomgr/alarm.h"
 #include "src/core/support/string.h"
+#include "src/core/surface/byte_buffer_queue.h"
 #include "src/core/surface/channel.h"
 #include "src/core/surface/completion_queue.h"
 #include <grpc/support/alloc.h>
@@ -45,251 +46,722 @@
 #include <stdlib.h>
 #include <string.h>
 
-#define INVALID_TAG ((void *)0xdeadbeef)
+typedef struct legacy_state legacy_state;
+static void destroy_legacy_state(legacy_state *ls);
 
-/* Pending read queue
+typedef enum { REQ_INITIAL = 0, REQ_READY, REQ_DONE } req_state;
 
-   This data structure tracks reads that need to be presented to the completion
-   queue but are waiting for the application to ask for them. */
-
-#define INITIAL_PENDING_READ_COUNT 4
-
-typedef struct {
-  grpc_byte_buffer *byte_buffer;
-  void *user_data;
-  void (*on_finish)(void *user_data, grpc_op_error error);
-} pending_read;
-
-/* TODO(ctiller): inline an element or two into this struct to avoid per-call
-                  allocations */
-typedef struct {
-  pending_read *data;
-  size_t count;
-  size_t capacity;
-} pending_read_array;
-
-typedef struct {
-  size_t drain_pos;
-  pending_read_array filling;
-  pending_read_array draining;
-} pending_read_queue;
-
-static void pra_init(pending_read_array *array) {
-  array->data = gpr_malloc(sizeof(pending_read) * INITIAL_PENDING_READ_COUNT);
-  array->count = 0;
-  array->capacity = INITIAL_PENDING_READ_COUNT;
-}
-
-static void pra_destroy(pending_read_array *array,
-                        size_t finish_starting_from) {
-  size_t i;
-  for (i = finish_starting_from; i < array->count; i++) {
-    array->data[i].on_finish(array->data[i].user_data, GRPC_OP_ERROR);
-  }
-  gpr_free(array->data);
-}
-
-/* Append an operation to an array, expanding as needed */
-static void pra_push(pending_read_array *a, grpc_byte_buffer *buffer,
-                     void (*on_finish)(void *user_data, grpc_op_error error),
-                     void *user_data) {
-  if (a->count == a->capacity) {
-    a->capacity *= 2;
-    a->data = gpr_realloc(a->data, sizeof(pending_read) * a->capacity);
-  }
-  a->data[a->count].byte_buffer = buffer;
-  a->data[a->count].user_data = user_data;
-  a->data[a->count].on_finish = on_finish;
-  a->count++;
-}
-
-static void prq_init(pending_read_queue *q) {
-  q->drain_pos = 0;
-  pra_init(&q->filling);
-  pra_init(&q->draining);
-}
-
-static void prq_destroy(pending_read_queue *q) {
-  pra_destroy(&q->filling, 0);
-  pra_destroy(&q->draining, q->drain_pos);
-}
-
-static int prq_is_empty(pending_read_queue *q) {
-  return (q->drain_pos == q->draining.count && q->filling.count == 0);
-}
-
-static void prq_push(pending_read_queue *q, grpc_byte_buffer *buffer,
-                     void (*on_finish)(void *user_data, grpc_op_error error),
-                     void *user_data) {
-  pra_push(&q->filling, buffer, on_finish, user_data);
-}
-
-/* Take the first queue element and move it to the completion queue. Do nothing
-   if q is empty */
-static int prq_pop_to_cq(pending_read_queue *q, void *tag, grpc_call *call,
-                         grpc_completion_queue *cq) {
-  pending_read_array temp_array;
-  pending_read *pr;
-
-  if (q->drain_pos == q->draining.count) {
-    if (q->filling.count == 0) {
-      return 0;
-    }
-    q->draining.count = 0;
-    q->drain_pos = 0;
-    /* swap arrays */
-    temp_array = q->filling;
-    q->filling = q->draining;
-    q->draining = temp_array;
-  }
-
-  pr = q->draining.data + q->drain_pos;
-  q->drain_pos++;
-  grpc_cq_end_read(cq, tag, call, pr->on_finish, pr->user_data,
-                   pr->byte_buffer);
-  return 1;
-}
-
-/* grpc_call proper */
-
-/* the state of a call, based upon which functions have been called against
-   said call */
 typedef enum {
-  CALL_CREATED,
-  CALL_BOUNDCQ,
-  CALL_STARTED,
-  CALL_FINISHED
-} call_state;
+  SEND_NOTHING,
+  SEND_INITIAL_METADATA,
+  SEND_MESSAGE,
+  SEND_TRAILING_METADATA_AND_FINISH,
+  SEND_FINISH
+} send_action;
+
+typedef struct {
+  grpc_ioreq_completion_func on_complete;
+  void *user_data;
+  grpc_op_error status;
+} completed_request;
+
+/* See request_set in grpc_call below for a description */
+#define REQSET_EMPTY 255
+#define REQSET_DONE 254
+
+typedef struct {
+  /* Overall status of the operation: starts OK, may degrade to
+     non-OK */
+  grpc_op_error status;
+  /* Completion function to call at the end of the operation */
+  grpc_ioreq_completion_func on_complete;
+  void *user_data;
+  /* a bit mask of which request ops are needed (1u << opid) */
+  gpr_uint32 need_mask;
+  /* a bit mask of which request ops are now completed */
+  gpr_uint32 complete_mask;
+} reqinfo_master;
+
+/* Status data for a request can come from several sources; this
+   enumerates them all, and acts as a priority sorting for which
+   status to return to the application - earlier entries override
+   later ones */
+typedef enum {
+  /* Status came from the application layer overriding whatever
+     the wire says */
+  STATUS_FROM_API_OVERRIDE = 0,
+  /* Status came from 'the wire' - or somewhere below the surface
+     layer */
+  STATUS_FROM_WIRE,
+  STATUS_SOURCE_COUNT
+} status_source;
+
+typedef struct {
+  gpr_uint8 is_set;
+  grpc_status_code code;
+  grpc_mdstr *details;
+} received_status;
+
+/* How far through the GRPC stream have we read? */
+typedef enum {
+  /* We are still waiting for initial metadata to complete */
+  READ_STATE_INITIAL = 0,
+  /* We have gotten initial metadata, and are reading either
+     messages or trailing metadata */
+  READ_STATE_GOT_INITIAL_METADATA,
+  /* The stream is closed for reading */
+  READ_STATE_READ_CLOSED,
+  /* The stream is closed for reading & writing */
+  READ_STATE_STREAM_CLOSED
+} read_state;
+
+typedef enum {
+  WRITE_STATE_INITIAL = 0,
+  WRITE_STATE_STARTED,
+  WRITE_STATE_WRITE_CLOSED
+} write_state;
 
 struct grpc_call {
   grpc_completion_queue *cq;
   grpc_channel *channel;
   grpc_mdctx *metadata_context;
+  /* TODO(ctiller): share with cq if possible? */
+  gpr_mu mu;
 
-  call_state state;
+  /* how far through the stream have we read? */
+  read_state read_state;
+  /* how far through the stream have we written? */
+  write_state write_state;
+  /* client or server call */
   gpr_uint8 is_client;
-  gpr_uint8 have_write;
-  grpc_metadata_buffer incoming_metadata;
-
-  /* protects variables in this section */
-  gpr_mu read_mu;
-  gpr_uint8 received_start;
-  gpr_uint8 start_ok;
-  gpr_uint8 reads_done;
-  gpr_uint8 received_finish;
-  gpr_uint8 received_metadata;
-  gpr_uint8 have_read;
+  /* is the alarm set */
   gpr_uint8 have_alarm;
-  gpr_uint8 pending_writes_done;
-  gpr_uint8 got_status_code;
-  /* The current outstanding read message tag (only valid if have_read == 1) */
-  void *read_tag;
-  void *metadata_tag;
-  void *finished_tag;
-  pending_read_queue prq;
+  /* are we currently performing a send operation */
+  gpr_uint8 sending;
+  /* pairs with completed_requests */
+  gpr_uint8 num_completed_requests;
+  /* flag that we need to request more data */
+  gpr_uint8 need_more_data;
 
+  /* Active ioreqs.
+     request_set and request_data contain one element per active ioreq
+     operation.
+
+     request_set[op] is an integer specifying a set of operations to which
+     the request belongs:
+       - if it is < GRPC_IOREQ_OP_COUNT, then this operation is pending
+         completion, and the integer represents to which group of operations
+         the ioreq belongs. Each group is represented by one master, and the
+         integer in request_set is an index into masters to find the master
+         data.
+       - if it is REQSET_EMPTY, the ioreq op is inactive and available to be
+         started
+       - finally, if request_set[op] is REQSET_DONE, then the operation is
+         complete and unavailable to be started again
+
+     request_data[op] is the request data as supplied by the initiator of
+     a request, and is valid iff request_set[op] <= GRPC_IOREQ_OP_COUNT.
+     The set fields are as per the request type specified by op.
+
+     Finally, one element of masters is set per active _set_ of ioreq
+     operations. It describes work left outstanding, result status, and
+     what work to perform upon operation completion. As one ioreq of each
+     op type can be active at once, by convention we choose the first element
+     of the group to be the master -- ie the master of in-progress operation
+     op is masters[request_set[op]]. This allows constant time allocation
+     and a strong upper bound of a count of masters to be calculated. */
+  gpr_uint8 request_set[GRPC_IOREQ_OP_COUNT];
+  grpc_ioreq_data request_data[GRPC_IOREQ_OP_COUNT];
+  reqinfo_master masters[GRPC_IOREQ_OP_COUNT];
+
+  /* Dynamic array of ioreq's that have completed: the count of
+     elements is queued in num_completed_requests.
+     This list is built up under lock(), and flushed entirely during
+     unlock().
+     We know the upper bound of the number of elements as we can only
+     have one ioreq of each type active at once. */
+  completed_request completed_requests[GRPC_IOREQ_OP_COUNT];
+  /* Incoming buffer of messages */
+  grpc_byte_buffer_queue incoming_queue;
+  /* Buffered read metadata waiting to be returned to the application.
+     Element 0 is initial metadata, element 1 is trailing metadata. */
+  grpc_metadata_array buffered_metadata[2];
+  /* All metadata received - unreffed at once at the end of the call */
+  grpc_mdelem **owned_metadata;
+  size_t owned_metadata_count;
+  size_t owned_metadata_capacity;
+
+  /* Received call statuses from various sources */
+  received_status status[STATUS_SOURCE_COUNT];
+
+  /* Deadline alarm - if have_alarm is non-zero */
   grpc_alarm alarm;
 
-  /* The current outstanding send message/context/invoke/end tag (only valid if
-     have_write == 1) */
-  void *write_tag;
-  grpc_byte_buffer *pending_write;
-  gpr_uint32 pending_write_flags;
-
-  /* The final status of the call */
-  grpc_status_code status_code;
-  grpc_mdstr *status_details;
-
+  /* Call refcount - to keep the call alive during asynchronous operations */
   gpr_refcount internal_refcount;
+
+  /* Data that the legacy api needs to track. To be deleted at some point
+     soon */
+  legacy_state *legacy_state;
 };
 
-#define CALL_STACK_FROM_CALL(call) ((grpc_call_stack *)((call) + 1))
+#define CALL_STACK_FROM_CALL(call) ((grpc_call_stack *)((call)+1))
 #define CALL_FROM_CALL_STACK(call_stack) (((grpc_call *)(call_stack)) - 1)
 #define CALL_ELEM_FROM_CALL(call, idx) \
   grpc_call_stack_element(CALL_STACK_FROM_CALL(call), idx)
 #define CALL_FROM_TOP_ELEM(top_elem) \
   CALL_FROM_CALL_STACK(grpc_call_stack_from_top_element(top_elem))
 
+#define SWAP(type, x, y) \
+  do {                   \
+    type temp = x;       \
+    x = y;               \
+    y = temp;            \
+  } while (0)
+
 static void do_nothing(void *ignored, grpc_op_error also_ignored) {}
+static send_action choose_send_action(grpc_call *call);
+static void enact_send_action(grpc_call *call, send_action sa);
 
 grpc_call *grpc_call_create(grpc_channel *channel,
                             const void *server_transport_data) {
+  size_t i;
   grpc_channel_stack *channel_stack = grpc_channel_get_channel_stack(channel);
   grpc_call *call =
       gpr_malloc(sizeof(grpc_call) + channel_stack->call_stack_size);
-  call->cq = NULL;
+  memset(call, 0, sizeof(grpc_call));
+  gpr_mu_init(&call->mu);
   call->channel = channel;
+  call->is_client = server_transport_data == NULL;
+  for (i = 0; i < GRPC_IOREQ_OP_COUNT; i++) {
+    call->request_set[i] = REQSET_EMPTY;
+  }
+  if (call->is_client) {
+    call->request_set[GRPC_IOREQ_SEND_TRAILING_METADATA] = REQSET_DONE;
+    call->request_set[GRPC_IOREQ_SEND_STATUS] = REQSET_DONE;
+  }
   grpc_channel_internal_ref(channel);
   call->metadata_context = grpc_channel_get_metadata_context(channel);
-  call->state = CALL_CREATED;
-  call->is_client = (server_transport_data == NULL);
-  call->write_tag = INVALID_TAG;
-  call->read_tag = INVALID_TAG;
-  call->metadata_tag = INVALID_TAG;
-  call->finished_tag = INVALID_TAG;
-  call->have_read = 0;
-  call->have_write = 0;
-  call->have_alarm = 0;
-  call->received_metadata = 0;
-  call->got_status_code = 0;
-  call->start_ok = 0;
-  call->status_code =
-      server_transport_data != NULL ? GRPC_STATUS_OK : GRPC_STATUS_UNKNOWN;
-  call->status_details = NULL;
-  call->received_finish = 0;
-  call->reads_done = 0;
-  call->received_start = 0;
-  call->pending_write = NULL;
-  call->pending_writes_done = 0;
-  grpc_metadata_buffer_init(&call->incoming_metadata);
-  gpr_ref_init(&call->internal_refcount, 1);
+  /* one ref is dropped in response to destroy, the other in
+     stream_closed */
+  gpr_ref_init(&call->internal_refcount, 2);
   grpc_call_stack_init(channel_stack, server_transport_data,
                        CALL_STACK_FROM_CALL(call));
-  prq_init(&call->prq);
-  gpr_mu_init(&call->read_mu);
   return call;
 }
 
 void grpc_call_internal_ref(grpc_call *c) { gpr_ref(&c->internal_refcount); }
 
-void grpc_call_internal_unref(grpc_call *c) {
-  if (gpr_unref(&c->internal_refcount)) {
-    grpc_call_stack_destroy(CALL_STACK_FROM_CALL(c));
-    grpc_metadata_buffer_destroy(&c->incoming_metadata, GRPC_OP_OK);
-    if (c->status_details) {
-      grpc_mdstr_unref(c->status_details);
+static void destroy_call(void *call, int ignored_success) {
+  size_t i;
+  grpc_call *c = call;
+  grpc_call_stack_destroy(CALL_STACK_FROM_CALL(c));
+  grpc_channel_internal_unref(c->channel);
+  gpr_mu_destroy(&c->mu);
+  for (i = 0; i < STATUS_SOURCE_COUNT; i++) {
+    if (c->status[i].details) {
+      grpc_mdstr_unref(c->status[i].details);
     }
-    prq_destroy(&c->prq);
-    gpr_mu_destroy(&c->read_mu);
-    grpc_channel_internal_unref(c->channel);
-    gpr_free(c);
   }
+  for (i = 0; i < c->owned_metadata_count; i++) {
+    grpc_mdelem_unref(c->owned_metadata[i]);
+  }
+  gpr_free(c->owned_metadata);
+  for (i = 0; i < GPR_ARRAY_SIZE(c->buffered_metadata); i++) {
+    gpr_free(c->buffered_metadata[i].metadata);
+  }
+  if (c->legacy_state) {
+    destroy_legacy_state(c->legacy_state);
+  }
+  grpc_bbq_destroy(&c->incoming_queue);
+  gpr_free(c);
+}
+
+void grpc_call_internal_unref(grpc_call *c, int allow_immediate_deletion) {
+  if (gpr_unref(&c->internal_refcount)) {
+    if (allow_immediate_deletion) {
+      destroy_call(c, 1);
+    } else {
+      grpc_iomgr_add_callback(destroy_call, c);
+    }
+  }
+}
+
+static void set_status_code(grpc_call *call, status_source source,
+                            gpr_uint32 status) {
+  call->status[source].is_set = 1;
+  call->status[source].code = status;
+}
+
+static void set_status_details(grpc_call *call, status_source source,
+                               grpc_mdstr *status) {
+  if (call->status[source].details != NULL) {
+    grpc_mdstr_unref(call->status[source].details);
+  }
+  call->status[source].details = status;
+}
+
+static grpc_call_error bind_cq(grpc_call *call, grpc_completion_queue *cq) {
+  if (call->cq) return GRPC_CALL_ERROR_ALREADY_INVOKED;
+  call->cq = cq;
+  return GRPC_CALL_OK;
+}
+
+static void request_more_data(grpc_call *call) {
+  grpc_call_op op;
+
+  /* call down */
+  op.type = GRPC_REQUEST_DATA;
+  op.dir = GRPC_CALL_DOWN;
+  op.flags = 0;
+  op.done_cb = do_nothing;
+  op.user_data = NULL;
+
+  grpc_call_execute_op(call, &op);
+}
+
+static int is_op_live(grpc_call *call, grpc_ioreq_op op) {
+  gpr_uint8 set = call->request_set[op];
+  reqinfo_master *master;
+  if (set >= GRPC_IOREQ_OP_COUNT) return 0;
+  master = &call->masters[set];
+  return (master->complete_mask & (1u << op)) == 0;
+}
+
+static void lock(grpc_call *call) { gpr_mu_lock(&call->mu); }
+
+static void unlock(grpc_call *call) {
+  send_action sa = SEND_NOTHING;
+  completed_request completed_requests[GRPC_IOREQ_OP_COUNT];
+  int num_completed_requests = call->num_completed_requests;
+  int need_more_data =
+      call->need_more_data &&
+      !call->sending &&
+      call->write_state >= WRITE_STATE_STARTED;
+  int i;
+
+  if (need_more_data) {
+    call->need_more_data = 0;
+  }
+
+  if (num_completed_requests != 0) {
+    memcpy(completed_requests, call->completed_requests,
+           sizeof(completed_requests));
+    call->num_completed_requests = 0;
+  }
+
+  if (!call->sending) {
+    sa = choose_send_action(call);
+    if (sa != SEND_NOTHING) {
+      call->sending = 1;
+      grpc_call_internal_ref(call);
+    }
+  }
+
+  gpr_mu_unlock(&call->mu);
+
+  if (need_more_data) {
+    request_more_data(call);
+  }
+
+  if (sa != SEND_NOTHING) {
+    enact_send_action(call, sa);
+  }
+
+  for (i = 0; i < num_completed_requests; i++) {
+    completed_requests[i].on_complete(call, completed_requests[i].status,
+                                      completed_requests[i].user_data);
+  }
+}
+
+static void get_final_status(grpc_call *call, grpc_recv_status_args args) {
+  int i;
+  for (i = 0; i < STATUS_SOURCE_COUNT; i++) {
+    if (call->status[i].is_set) {
+      *args.code = call->status[i].code;
+      if (!args.details) return;
+      if (call->status[i].details) {
+        gpr_slice details = call->status[i].details->slice;
+        size_t len = GPR_SLICE_LENGTH(details);
+        if (len + 1 > *args.details_capacity) {
+          *args.details_capacity =
+              GPR_MAX(len + 1, *args.details_capacity * 3 / 2);
+          *args.details = gpr_realloc(*args.details, *args.details_capacity);
+        }
+        memcpy(*args.details, GPR_SLICE_START_PTR(details), len);
+        (*args.details)[len] = 0;
+      } else {
+        goto no_details;
+      }
+      return;
+    }
+  }
+  *args.code = GRPC_STATUS_UNKNOWN;
+  if (!args.details) return;
+
+no_details:
+  if (0 == *args.details_capacity) {
+    *args.details_capacity = 8;
+    *args.details = gpr_malloc(*args.details_capacity);
+  }
+  **args.details = 0;
+}
+
+static void finish_live_ioreq_op(grpc_call *call, grpc_ioreq_op op,
+                                 grpc_op_error status) {
+  completed_request *cr;
+  gpr_uint8 master_set = call->request_set[op];
+  reqinfo_master *master;
+  size_t i;
+  /* ioreq is live: we need to do something */
+  master = &call->masters[master_set];
+  master->complete_mask |= 1u << op;
+  if (status != GRPC_OP_OK) {
+    master->status = status;
+    master->complete_mask = master->need_mask;
+  }
+  if (master->complete_mask == master->need_mask) {
+    for (i = 0; i < GRPC_IOREQ_OP_COUNT; i++) {
+      if (call->request_set[i] != master_set) {
+        continue;
+      }
+      call->request_set[i] = REQSET_DONE;
+      switch ((grpc_ioreq_op)i) {
+        case GRPC_IOREQ_RECV_MESSAGE:
+        case GRPC_IOREQ_SEND_MESSAGE:
+          if (master->status == GRPC_OP_OK) {
+            call->request_set[i] = REQSET_EMPTY;
+          } else {
+            call->write_state = WRITE_STATE_WRITE_CLOSED;
+          }
+          break;
+        case GRPC_IOREQ_RECV_CLOSE:
+        case GRPC_IOREQ_SEND_INITIAL_METADATA:
+        case GRPC_IOREQ_SEND_TRAILING_METADATA:
+        case GRPC_IOREQ_SEND_STATUS:
+        case GRPC_IOREQ_SEND_CLOSE:
+          break;
+        case GRPC_IOREQ_RECV_STATUS:
+          get_final_status(
+              call, call->request_data[GRPC_IOREQ_RECV_STATUS].recv_status);
+          break;
+        case GRPC_IOREQ_RECV_INITIAL_METADATA:
+          SWAP(grpc_metadata_array, call->buffered_metadata[0],
+               *call->request_data[GRPC_IOREQ_RECV_INITIAL_METADATA]
+                    .recv_metadata);
+          break;
+        case GRPC_IOREQ_RECV_TRAILING_METADATA:
+          SWAP(grpc_metadata_array, call->buffered_metadata[1],
+               *call->request_data[GRPC_IOREQ_RECV_TRAILING_METADATA]
+                    .recv_metadata);
+          break;
+        case GRPC_IOREQ_OP_COUNT:
+          abort();
+          break;
+      }
+    }
+    cr = &call->completed_requests[call->num_completed_requests++];
+    cr->status = master->status;
+    cr->on_complete = master->on_complete;
+    cr->user_data = master->user_data;
+  }
+}
+
+static void finish_ioreq_op(grpc_call *call, grpc_ioreq_op op,
+                            grpc_op_error status) {
+  if (is_op_live(call, op)) {
+    finish_live_ioreq_op(call, op, status);
+  }
+}
+
+static void finish_send_op(grpc_call *call, grpc_ioreq_op op,
+                           grpc_op_error error) {
+  lock(call);
+  finish_ioreq_op(call, op, error);
+  call->sending = 0;
+  unlock(call);
+  grpc_call_internal_unref(call, 0);
+}
+
+static void finish_write_step(void *pc, grpc_op_error error) {
+  finish_send_op(pc, GRPC_IOREQ_SEND_MESSAGE, error);
+}
+
+static void finish_finish_step(void *pc, grpc_op_error error) {
+  finish_send_op(pc, GRPC_IOREQ_SEND_CLOSE, error);
+}
+
+static void finish_start_step(void *pc, grpc_op_error error) {
+  finish_send_op(pc, GRPC_IOREQ_SEND_INITIAL_METADATA, error);
+}
+
+static send_action choose_send_action(grpc_call *call) {
+  switch (call->write_state) {
+    case WRITE_STATE_INITIAL:
+      if (is_op_live(call, GRPC_IOREQ_SEND_INITIAL_METADATA)) {
+        call->write_state = WRITE_STATE_STARTED;
+        return SEND_INITIAL_METADATA;
+      }
+      return SEND_NOTHING;
+    case WRITE_STATE_STARTED:
+      if (is_op_live(call, GRPC_IOREQ_SEND_MESSAGE)) {
+        return SEND_MESSAGE;
+      }
+      if (is_op_live(call, GRPC_IOREQ_SEND_CLOSE)) {
+        call->write_state = WRITE_STATE_WRITE_CLOSED;
+        finish_ioreq_op(call, GRPC_IOREQ_SEND_TRAILING_METADATA, GRPC_OP_OK);
+        finish_ioreq_op(call, GRPC_IOREQ_SEND_STATUS, GRPC_OP_OK);
+        return call->is_client ? SEND_FINISH
+                               : SEND_TRAILING_METADATA_AND_FINISH;
+      }
+      return SEND_NOTHING;
+    case WRITE_STATE_WRITE_CLOSED:
+      return SEND_NOTHING;
+  }
+  gpr_log(GPR_ERROR, "should never reach here");
+  abort();
+  return SEND_NOTHING;
+}
+
+static void send_metadata(grpc_call *call, grpc_mdelem *elem) {
+  grpc_call_op op;
+  op.type = GRPC_SEND_METADATA;
+  op.dir = GRPC_CALL_DOWN;
+  op.flags = 0;
+  op.data.metadata = elem;
+  op.done_cb = do_nothing;
+  op.user_data = NULL;
+  grpc_call_execute_op(call, &op);
+}
+
+static void enact_send_action(grpc_call *call, send_action sa) {
+  grpc_ioreq_data data;
+  grpc_call_op op;
+  size_t i;
+  char status_str[GPR_LTOA_MIN_BUFSIZE];
+
+  switch (sa) {
+    case SEND_NOTHING:
+      abort();
+      break;
+    case SEND_INITIAL_METADATA:
+      data = call->request_data[GRPC_IOREQ_SEND_INITIAL_METADATA];
+      for (i = 0; i < data.send_metadata.count; i++) {
+        const grpc_metadata *md = &data.send_metadata.metadata[i];
+        send_metadata(call,
+                      grpc_mdelem_from_string_and_buffer(
+                          call->metadata_context, md->key,
+                          (const gpr_uint8 *)md->value, md->value_length));
+      }
+      op.type = GRPC_SEND_START;
+      op.dir = GRPC_CALL_DOWN;
+      op.flags = 0;
+      op.data.start.pollset = grpc_cq_pollset(call->cq);
+      op.done_cb = finish_start_step;
+      op.user_data = call;
+      grpc_call_execute_op(call, &op);
+      break;
+    case SEND_MESSAGE:
+      data = call->request_data[GRPC_IOREQ_SEND_MESSAGE];
+      op.type = GRPC_SEND_MESSAGE;
+      op.dir = GRPC_CALL_DOWN;
+      op.flags = 0;
+      op.data.message = data.send_message;
+      op.done_cb = finish_write_step;
+      op.user_data = call;
+      grpc_call_execute_op(call, &op);
+      break;
+    case SEND_TRAILING_METADATA_AND_FINISH:
+      /* send trailing metadata */
+      data = call->request_data[GRPC_IOREQ_SEND_TRAILING_METADATA];
+      for (i = 0; i < data.send_metadata.count; i++) {
+        const grpc_metadata *md = &data.send_metadata.metadata[i];
+        send_metadata(call,
+                      grpc_mdelem_from_string_and_buffer(
+                          call->metadata_context, md->key,
+                          (const gpr_uint8 *)md->value, md->value_length));
+      }
+      /* send status */
+      /* TODO(ctiller): cache common status values */
+      data = call->request_data[GRPC_IOREQ_SEND_STATUS];
+      gpr_ltoa(data.send_status.code, status_str);
+      send_metadata(
+          call,
+          grpc_mdelem_from_metadata_strings(
+              call->metadata_context,
+              grpc_mdstr_ref(grpc_channel_get_status_string(call->channel)),
+              grpc_mdstr_from_string(call->metadata_context, status_str)));
+      if (data.send_status.details) {
+        send_metadata(
+            call,
+            grpc_mdelem_from_metadata_strings(
+                call->metadata_context,
+                grpc_mdstr_ref(grpc_channel_get_message_string(call->channel)),
+                grpc_mdstr_from_string(call->metadata_context,
+                                       data.send_status.details)));
+      }
+    /* fallthrough: see choose_send_action for details */
+    case SEND_FINISH:
+      op.type = GRPC_SEND_FINISH;
+      op.dir = GRPC_CALL_DOWN;
+      op.flags = 0;
+      op.done_cb = finish_finish_step;
+      op.user_data = call;
+      grpc_call_execute_op(call, &op);
+      break;
+  }
+}
+
+static grpc_call_error start_ioreq_error(grpc_call *call,
+                                         gpr_uint32 mutated_ops,
+                                         grpc_call_error ret) {
+  size_t i;
+  for (i = 0; i < GRPC_IOREQ_OP_COUNT; i++) {
+    if (mutated_ops & (1u << i)) {
+      call->request_set[i] = REQSET_EMPTY;
+    }
+  }
+  return ret;
+}
+
+static void finish_read_ops(grpc_call *call) {
+  int empty;
+
+  if (is_op_live(call, GRPC_IOREQ_RECV_MESSAGE)) {
+    empty =
+        (NULL == (*call->request_data[GRPC_IOREQ_RECV_MESSAGE].recv_message =
+                      grpc_bbq_pop(&call->incoming_queue)));
+    if (!empty) {
+      finish_live_ioreq_op(call, GRPC_IOREQ_RECV_MESSAGE, GRPC_OP_OK);
+      empty = grpc_bbq_empty(&call->incoming_queue);
+    }
+  } else {
+    empty = grpc_bbq_empty(&call->incoming_queue);
+  }
+
+  switch (call->read_state) {
+    case READ_STATE_STREAM_CLOSED:
+      if (empty) {
+        finish_ioreq_op(call, GRPC_IOREQ_RECV_CLOSE, GRPC_OP_OK);
+      }
+    /* fallthrough */
+    case READ_STATE_READ_CLOSED:
+      if (empty) {
+        finish_ioreq_op(call, GRPC_IOREQ_RECV_MESSAGE, GRPC_OP_OK);
+      }
+      finish_ioreq_op(call, GRPC_IOREQ_RECV_STATUS, GRPC_OP_OK);
+      finish_ioreq_op(call, GRPC_IOREQ_RECV_TRAILING_METADATA, GRPC_OP_OK);
+    /* fallthrough */
+    case READ_STATE_GOT_INITIAL_METADATA:
+      finish_ioreq_op(call, GRPC_IOREQ_RECV_INITIAL_METADATA, GRPC_OP_OK);
+    /* fallthrough */
+    case READ_STATE_INITIAL:
+      /* do nothing */
+      break;
+  }
+}
+
+static void early_out_write_ops(grpc_call *call) {
+  switch (call->write_state) {
+    case WRITE_STATE_WRITE_CLOSED:
+      finish_ioreq_op(call, GRPC_IOREQ_SEND_MESSAGE, GRPC_OP_ERROR);
+      finish_ioreq_op(call, GRPC_IOREQ_SEND_STATUS, GRPC_OP_ERROR);
+      finish_ioreq_op(call, GRPC_IOREQ_SEND_TRAILING_METADATA, GRPC_OP_ERROR);
+      finish_ioreq_op(call, GRPC_IOREQ_SEND_CLOSE, GRPC_OP_OK);
+    /* fallthrough */
+    case WRITE_STATE_STARTED:
+      finish_ioreq_op(call, GRPC_IOREQ_SEND_INITIAL_METADATA, GRPC_OP_ERROR);
+    /* fallthrough */
+    case WRITE_STATE_INITIAL:
+      /* do nothing */
+      break;
+  }
+}
+
+static grpc_call_error start_ioreq(grpc_call *call, const grpc_ioreq *reqs,
+                                   size_t nreqs,
+                                   grpc_ioreq_completion_func completion,
+                                   void *user_data) {
+  size_t i;
+  gpr_uint32 have_ops = 0;
+  grpc_ioreq_op op;
+  reqinfo_master *master;
+  grpc_ioreq_data data;
+  gpr_uint8 set;
+
+  if (nreqs == 0) {
+    return GRPC_CALL_OK;
+  }
+
+  set = reqs[0].op;
+
+  for (i = 0; i < nreqs; i++) {
+    op = reqs[i].op;
+    if (call->request_set[op] < GRPC_IOREQ_OP_COUNT) {
+      return start_ioreq_error(call, have_ops,
+                               GRPC_CALL_ERROR_TOO_MANY_OPERATIONS);
+    } else if (call->request_set[op] == REQSET_DONE) {
+      return start_ioreq_error(call, have_ops, GRPC_CALL_ERROR_ALREADY_INVOKED);
+    }
+    have_ops |= 1u << op;
+    data = reqs[i].data;
+
+    call->request_data[op] = data;
+    call->request_set[op] = set;
+  }
+
+  master = &call->masters[set];
+  master->status = GRPC_OP_OK;
+  master->need_mask = have_ops;
+  master->complete_mask = 0;
+  master->on_complete = completion;
+  master->user_data = user_data;
+
+  if (have_ops & (1u << GRPC_IOREQ_RECV_MESSAGE)) {
+    call->need_more_data = 1;
+  }
+
+  finish_read_ops(call);
+  early_out_write_ops(call);
+
+  return GRPC_CALL_OK;
+}
+
+static void call_start_ioreq_done(grpc_call *call, grpc_op_error status,
+                                  void *user_data) {
+  grpc_cq_end_ioreq(call->cq, user_data, call, do_nothing, NULL, status);
+}
+
+grpc_call_error grpc_call_start_ioreq(grpc_call *call, const grpc_ioreq *reqs,
+                                      size_t nreqs, void *tag) {
+  grpc_call_error err;
+  lock(call);
+  err = start_ioreq(call, reqs, nreqs, call_start_ioreq_done, tag);
+  unlock(call);
+  return err;
+}
+
+grpc_call_error grpc_call_start_ioreq_and_call_back(
+    grpc_call *call, const grpc_ioreq *reqs, size_t nreqs,
+    grpc_ioreq_completion_func on_complete, void *user_data) {
+  grpc_call_error err;
+  lock(call);
+  err = start_ioreq(call, reqs, nreqs, on_complete, user_data);
+  unlock(call);
+  return err;
 }
 
 void grpc_call_destroy(grpc_call *c) {
   int cancel;
-  gpr_mu_lock(&c->read_mu);
+  lock(c);
   if (c->have_alarm) {
     grpc_alarm_cancel(&c->alarm);
     c->have_alarm = 0;
   }
-  cancel = !c->received_finish;
-  gpr_mu_unlock(&c->read_mu);
+  cancel = c->read_state != READ_STATE_STREAM_CLOSED;
+  unlock(c);
   if (cancel) grpc_call_cancel(c);
-  grpc_call_internal_unref(c);
-}
-
-static void maybe_set_status_code(grpc_call *call, gpr_uint32 status) {
-  if (!call->got_status_code) {
-    call->status_code = status;
-    call->got_status_code = 1;
-  }
-}
-
-static void maybe_set_status_details(grpc_call *call, grpc_mdstr *status) {
-  if (!call->status_details) {
-    call->status_details = grpc_mdstr_ref(status);
-  }
+  grpc_call_internal_unref(c, 1);
 }
 
 grpc_call_error grpc_call_cancel(grpc_call *c) {
@@ -314,12 +786,10 @@
   grpc_mdstr *details =
       description ? grpc_mdstr_from_string(c->metadata_context, description)
                   : NULL;
-  gpr_mu_lock(&c->read_mu);
-  maybe_set_status_code(c, status);
-  if (details) {
-    maybe_set_status_details(c, details);
-  }
-  gpr_mu_unlock(&c->read_mu);
+  lock(c);
+  set_status_code(c, STATUS_FROM_API_OVERRIDE, status);
+  set_status_details(c, STATUS_FROM_API_OVERRIDE, details);
+  unlock(c);
   return grpc_call_cancel(c);
 }
 
@@ -330,633 +800,10 @@
   elem->filter->call_op(elem, NULL, op);
 }
 
-void grpc_call_add_mdelem(grpc_call *call, grpc_mdelem *mdelem,
-                          gpr_uint32 flags) {
-  grpc_call_element *elem;
-  grpc_call_op op;
-
-  GPR_ASSERT(call->state < CALL_FINISHED);
-
-  op.type = GRPC_SEND_METADATA;
-  op.dir = GRPC_CALL_DOWN;
-  op.flags = flags;
-  op.done_cb = do_nothing;
-  op.user_data = NULL;
-  op.data.metadata = mdelem;
-
-  elem = CALL_ELEM_FROM_CALL(call, 0);
-  elem->filter->call_op(elem, NULL, &op);
-}
-
-grpc_call_error grpc_call_add_metadata(grpc_call *call, grpc_metadata *metadata,
-                                       gpr_uint32 flags) {
-  grpc_mdelem *mdelem;
-
-  if (call->is_client) {
-    if (call->state >= CALL_STARTED) {
-      return GRPC_CALL_ERROR_ALREADY_INVOKED;
-    }
-  } else {
-    if (call->state >= CALL_FINISHED) {
-      return GRPC_CALL_ERROR_ALREADY_FINISHED;
-    }
-  }
-
-  mdelem = grpc_mdelem_from_string_and_buffer(
-      call->metadata_context, metadata->key, (gpr_uint8 *)metadata->value,
-      metadata->value_length);
-  grpc_call_add_mdelem(call, mdelem, flags);
-  return GRPC_CALL_OK;
-}
-
-static void finish_call(grpc_call *call) {
-  size_t count;
-  grpc_metadata *elements;
-  count = grpc_metadata_buffer_count(&call->incoming_metadata);
-  elements = grpc_metadata_buffer_extract_elements(&call->incoming_metadata);
-  grpc_cq_end_finished(
-      call->cq, call->finished_tag, call, grpc_metadata_buffer_cleanup_elements,
-      elements, call->status_code,
-      call->status_details
-          ? (char *)grpc_mdstr_as_c_string(call->status_details)
-          : NULL,
-      elements, count);
-}
-
-static void done_write(void *user_data, grpc_op_error error) {
-  grpc_call *call = user_data;
-  void *tag = call->write_tag;
-
-  GPR_ASSERT(call->have_write);
-  call->have_write = 0;
-  call->write_tag = INVALID_TAG;
-  grpc_cq_end_write_accepted(call->cq, tag, call, NULL, NULL, error);
-}
-
-static void done_writes_done(void *user_data, grpc_op_error error) {
-  grpc_call *call = user_data;
-  void *tag = call->write_tag;
-
-  GPR_ASSERT(call->have_write);
-  call->have_write = 0;
-  call->write_tag = INVALID_TAG;
-  grpc_cq_end_finish_accepted(call->cq, tag, call, NULL, NULL, error);
-}
-
-static void call_started(void *user_data, grpc_op_error error) {
-  grpc_call *call = user_data;
-  grpc_call_element *elem;
-  grpc_byte_buffer *pending_write = NULL;
-  gpr_uint32 pending_write_flags = 0;
-  gpr_uint8 pending_writes_done = 0;
-  int ok;
-  grpc_call_op op;
-
-  gpr_mu_lock(&call->read_mu);
-  GPR_ASSERT(!call->received_start);
-  call->received_start = 1;
-  ok = call->start_ok = (error == GRPC_OP_OK);
-  pending_write = call->pending_write;
-  pending_write_flags = call->pending_write_flags;
-  pending_writes_done = call->pending_writes_done;
-  gpr_mu_unlock(&call->read_mu);
-
-  if (pending_write) {
-    if (ok) {
-      op.type = GRPC_SEND_MESSAGE;
-      op.dir = GRPC_CALL_DOWN;
-      op.flags = pending_write_flags;
-      op.done_cb = done_write;
-      op.user_data = call;
-      op.data.message = pending_write;
-
-      elem = CALL_ELEM_FROM_CALL(call, 0);
-      elem->filter->call_op(elem, NULL, &op);
-    } else {
-      done_write(call, error);
-    }
-    grpc_byte_buffer_destroy(pending_write);
-  }
-  if (pending_writes_done) {
-    if (ok) {
-      op.type = GRPC_SEND_FINISH;
-      op.dir = GRPC_CALL_DOWN;
-      op.flags = 0;
-      op.done_cb = done_writes_done;
-      op.user_data = call;
-
-      elem = CALL_ELEM_FROM_CALL(call, 0);
-      elem->filter->call_op(elem, NULL, &op);
-    } else {
-      done_writes_done(call, error);
-    }
-  }
-
-  grpc_call_internal_unref(call);
-}
-
-grpc_call_error grpc_call_invoke(grpc_call *call, grpc_completion_queue *cq,
-                                 void *metadata_read_tag, void *finished_tag,
-                                 gpr_uint32 flags) {
-  grpc_call_element *elem;
-  grpc_call_op op;
-
-  /* validate preconditions */
-  if (!call->is_client) {
-    gpr_log(GPR_ERROR, "can only call %s on clients", __FUNCTION__);
-    return GRPC_CALL_ERROR_NOT_ON_SERVER;
-  }
-
-  if (call->state >= CALL_STARTED || call->cq) {
-    gpr_log(GPR_ERROR, "call is already invoked");
-    return GRPC_CALL_ERROR_ALREADY_INVOKED;
-  }
-
-  if (call->have_write) {
-    gpr_log(GPR_ERROR, "can only have one pending write operation at a time");
-    return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS;
-  }
-
-  if (call->have_read) {
-    gpr_log(GPR_ERROR, "can only have one pending read operation at a time");
-    return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS;
-  }
-
-  if (flags & GRPC_WRITE_NO_COMPRESS) {
-    return GRPC_CALL_ERROR_INVALID_FLAGS;
-  }
-
-  /* inform the completion queue of an incoming operation */
-  grpc_cq_begin_op(cq, call, GRPC_FINISHED);
-  grpc_cq_begin_op(cq, call, GRPC_CLIENT_METADATA_READ);
-
-  gpr_mu_lock(&call->read_mu);
-
-  /* update state */
-  call->cq = cq;
-  call->state = CALL_STARTED;
-  call->finished_tag = finished_tag;
-
-  if (call->received_finish) {
-    /* handle early cancellation */
-    grpc_cq_end_client_metadata_read(call->cq, metadata_read_tag, call, NULL,
-                                     NULL, 0, NULL);
-    finish_call(call);
-
-    /* early out.. unlock & return */
-    gpr_mu_unlock(&call->read_mu);
-    return GRPC_CALL_OK;
-  }
-
-  call->metadata_tag = metadata_read_tag;
-
-  gpr_mu_unlock(&call->read_mu);
-
-  /* call down the filter stack */
-  op.type = GRPC_SEND_START;
-  op.dir = GRPC_CALL_DOWN;
-  op.flags = flags;
-  op.done_cb = call_started;
-  op.data.start.pollset = grpc_cq_pollset(cq);
-  op.user_data = call;
-  grpc_call_internal_ref(call);
-
-  elem = CALL_ELEM_FROM_CALL(call, 0);
-  elem->filter->call_op(elem, NULL, &op);
-
-  return GRPC_CALL_OK;
-}
-
-grpc_call_error grpc_call_server_accept(grpc_call *call,
-                                        grpc_completion_queue *cq,
-                                        void *finished_tag) {
-  /* validate preconditions */
-  if (call->is_client) {
-    gpr_log(GPR_ERROR, "can only call %s on servers", __FUNCTION__);
-    return GRPC_CALL_ERROR_NOT_ON_CLIENT;
-  }
-
-  if (call->state >= CALL_BOUNDCQ) {
-    gpr_log(GPR_ERROR, "call is already accepted");
-    return GRPC_CALL_ERROR_ALREADY_ACCEPTED;
-  }
-
-  /* inform the completion queue of an incoming operation (corresponding to
-     finished_tag) */
-  grpc_cq_begin_op(cq, call, GRPC_FINISHED);
-
-  /* update state */
-  gpr_mu_lock(&call->read_mu);
-  call->state = CALL_BOUNDCQ;
-  call->cq = cq;
-  call->finished_tag = finished_tag;
-  call->received_start = 1;
-  if (prq_is_empty(&call->prq) && call->received_finish) {
-    finish_call(call);
-
-    /* early out.. unlock & return */
-    gpr_mu_unlock(&call->read_mu);
-    return GRPC_CALL_OK;
-  }
-  gpr_mu_unlock(&call->read_mu);
-
-  return GRPC_CALL_OK;
-}
-
-grpc_call_error grpc_call_server_end_initial_metadata(grpc_call *call,
-                                                      gpr_uint32 flags) {
-  grpc_call_element *elem;
-  grpc_call_op op;
-
-  /* validate preconditions */
-  if (call->is_client) {
-    gpr_log(GPR_ERROR, "can only call %s on servers", __FUNCTION__);
-    return GRPC_CALL_ERROR_NOT_ON_CLIENT;
-  }
-
-  if (call->state >= CALL_STARTED) {
-    gpr_log(GPR_ERROR, "call is already started");
-    return GRPC_CALL_ERROR_ALREADY_INVOKED;
-  }
-
-  if (flags & GRPC_WRITE_NO_COMPRESS) {
-    return GRPC_CALL_ERROR_INVALID_FLAGS;
-  }
-
-  /* update state */
-  call->state = CALL_STARTED;
-
-  /* call down */
-  op.type = GRPC_SEND_START;
-  op.dir = GRPC_CALL_DOWN;
-  op.flags = flags;
-  op.done_cb = do_nothing;
-  op.data.start.pollset = grpc_cq_pollset(call->cq);
-  op.user_data = NULL;
-
-  elem = CALL_ELEM_FROM_CALL(call, 0);
-  elem->filter->call_op(elem, NULL, &op);
-
-  return GRPC_CALL_OK;
-}
-
-void grpc_call_client_initial_metadata_complete(
-    grpc_call_element *surface_element) {
-  grpc_call *call = grpc_call_from_top_element(surface_element);
-  size_t count;
-  grpc_metadata *elements;
-
-  gpr_mu_lock(&call->read_mu);
-  count = grpc_metadata_buffer_count(&call->incoming_metadata);
-  elements = grpc_metadata_buffer_extract_elements(&call->incoming_metadata);
-
-  GPR_ASSERT(!call->received_metadata);
-  grpc_cq_end_client_metadata_read(call->cq, call->metadata_tag, call,
-                                   grpc_metadata_buffer_cleanup_elements,
-                                   elements, count, elements);
-  call->received_metadata = 1;
-  call->metadata_tag = INVALID_TAG;
-  gpr_mu_unlock(&call->read_mu);
-}
-
-static void request_more_data(grpc_call *call) {
-  grpc_call_element *elem;
-  grpc_call_op op;
-
-  /* call down */
-  op.type = GRPC_REQUEST_DATA;
-  op.dir = GRPC_CALL_DOWN;
-  op.flags = 0;
-  op.done_cb = do_nothing;
-  op.user_data = NULL;
-
-  elem = CALL_ELEM_FROM_CALL(call, 0);
-  elem->filter->call_op(elem, NULL, &op);
-}
-
-grpc_call_error grpc_call_start_read(grpc_call *call, void *tag) {
-  gpr_uint8 request_more = 0;
-
-  switch (call->state) {
-    case CALL_CREATED:
-      return GRPC_CALL_ERROR_NOT_INVOKED;
-    case CALL_BOUNDCQ:
-    case CALL_STARTED:
-      break;
-    case CALL_FINISHED:
-      return GRPC_CALL_ERROR_ALREADY_FINISHED;
-  }
-
-  gpr_mu_lock(&call->read_mu);
-
-  if (call->have_read) {
-    gpr_mu_unlock(&call->read_mu);
-    return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS;
-  }
-
-  grpc_cq_begin_op(call->cq, call, GRPC_READ);
-
-  if (!prq_pop_to_cq(&call->prq, tag, call, call->cq)) {
-    if (call->reads_done) {
-      grpc_cq_end_read(call->cq, tag, call, do_nothing, NULL, NULL);
-    } else {
-      call->read_tag = tag;
-      call->have_read = 1;
-      request_more = call->received_start;
-    }
-  } else if (prq_is_empty(&call->prq) && call->received_finish) {
-    finish_call(call);
-  }
-
-  gpr_mu_unlock(&call->read_mu);
-
-  if (request_more) {
-    request_more_data(call);
-  }
-
-  return GRPC_CALL_OK;
-}
-
-grpc_call_error grpc_call_start_write(grpc_call *call,
-                                      grpc_byte_buffer *byte_buffer, void *tag,
-                                      gpr_uint32 flags) {
-  grpc_call_element *elem;
-  grpc_call_op op;
-
-  switch (call->state) {
-    case CALL_CREATED:
-    case CALL_BOUNDCQ:
-      return GRPC_CALL_ERROR_NOT_INVOKED;
-    case CALL_STARTED:
-      break;
-    case CALL_FINISHED:
-      return GRPC_CALL_ERROR_ALREADY_FINISHED;
-  }
-
-  if (call->have_write) {
-    return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS;
-  }
-
-  grpc_cq_begin_op(call->cq, call, GRPC_WRITE_ACCEPTED);
-
-  /* TODO(ctiller): if flags & GRPC_WRITE_BUFFER_HINT == 0, this indicates a
-     flush, and that flush should be propogated down from here */
-  if (byte_buffer == NULL) {
-    grpc_cq_end_write_accepted(call->cq, tag, call, NULL, NULL, GRPC_OP_OK);
-    return GRPC_CALL_OK;
-  }
-
-  call->write_tag = tag;
-  call->have_write = 1;
-
-  gpr_mu_lock(&call->read_mu);
-  if (!call->received_start) {
-    call->pending_write = grpc_byte_buffer_copy(byte_buffer);
-    call->pending_write_flags = flags;
-
-    gpr_mu_unlock(&call->read_mu);
-  } else {
-    gpr_mu_unlock(&call->read_mu);
-
-    op.type = GRPC_SEND_MESSAGE;
-    op.dir = GRPC_CALL_DOWN;
-    op.flags = flags;
-    op.done_cb = done_write;
-    op.user_data = call;
-    op.data.message = byte_buffer;
-
-    elem = CALL_ELEM_FROM_CALL(call, 0);
-    elem->filter->call_op(elem, NULL, &op);
-  }
-
-  return GRPC_CALL_OK;
-}
-
-grpc_call_error grpc_call_writes_done(grpc_call *call, void *tag) {
-  grpc_call_element *elem;
-  grpc_call_op op;
-
-  if (!call->is_client) {
-    return GRPC_CALL_ERROR_NOT_ON_SERVER;
-  }
-
-  switch (call->state) {
-    case CALL_CREATED:
-    case CALL_BOUNDCQ:
-      return GRPC_CALL_ERROR_NOT_INVOKED;
-    case CALL_FINISHED:
-      return GRPC_CALL_ERROR_ALREADY_FINISHED;
-    case CALL_STARTED:
-      break;
-  }
-
-  if (call->have_write) {
-    return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS;
-  }
-
-  grpc_cq_begin_op(call->cq, call, GRPC_FINISH_ACCEPTED);
-
-  call->write_tag = tag;
-  call->have_write = 1;
-
-  gpr_mu_lock(&call->read_mu);
-  if (!call->received_start) {
-    call->pending_writes_done = 1;
-
-    gpr_mu_unlock(&call->read_mu);
-  } else {
-    gpr_mu_unlock(&call->read_mu);
-
-    op.type = GRPC_SEND_FINISH;
-    op.dir = GRPC_CALL_DOWN;
-    op.flags = 0;
-    op.done_cb = done_writes_done;
-    op.user_data = call;
-
-    elem = CALL_ELEM_FROM_CALL(call, 0);
-    elem->filter->call_op(elem, NULL, &op);
-  }
-
-  return GRPC_CALL_OK;
-}
-
-grpc_call_error grpc_call_start_write_status(grpc_call *call,
-                                             grpc_status_code status,
-                                             const char *details, void *tag) {
-  grpc_call_element *elem;
-  grpc_call_op op;
-
-  if (call->is_client) {
-    return GRPC_CALL_ERROR_NOT_ON_CLIENT;
-  }
-
-  switch (call->state) {
-    case CALL_CREATED:
-    case CALL_BOUNDCQ:
-      return GRPC_CALL_ERROR_NOT_INVOKED;
-    case CALL_FINISHED:
-      return GRPC_CALL_ERROR_ALREADY_FINISHED;
-    case CALL_STARTED:
-      break;
-  }
-
-  if (call->have_write) {
-    return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS;
-  }
-
-  elem = CALL_ELEM_FROM_CALL(call, 0);
-
-  if (details && details[0]) {
-    grpc_mdelem *md = grpc_mdelem_from_strings(call->metadata_context,
-                                               "grpc-message", details);
-
-    op.type = GRPC_SEND_METADATA;
-    op.dir = GRPC_CALL_DOWN;
-    op.flags = 0;
-    op.done_cb = do_nothing;
-    op.user_data = NULL;
-    op.data.metadata = md;
-    elem->filter->call_op(elem, NULL, &op);
-  }
-
-  /* always send status */
-  {
-    grpc_mdelem *md;
-    char buffer[GPR_LTOA_MIN_BUFSIZE];
-    gpr_ltoa(status, buffer);
-    md =
-        grpc_mdelem_from_strings(call->metadata_context, "grpc-status", buffer);
-
-    op.type = GRPC_SEND_METADATA;
-    op.dir = GRPC_CALL_DOWN;
-    op.flags = 0;
-    op.done_cb = do_nothing;
-    op.user_data = NULL;
-    op.data.metadata = md;
-    elem->filter->call_op(elem, NULL, &op);
-  }
-
-  grpc_cq_begin_op(call->cq, call, GRPC_FINISH_ACCEPTED);
-
-  call->state = CALL_FINISHED;
-  call->write_tag = tag;
-  call->have_write = 1;
-
-  op.type = GRPC_SEND_FINISH;
-  op.dir = GRPC_CALL_DOWN;
-  op.flags = 0;
-  op.done_cb = done_writes_done;
-  op.user_data = call;
-
-  elem->filter->call_op(elem, NULL, &op);
-
-  return GRPC_CALL_OK;
-}
-
-/* we offset status by a small amount when storing it into transport metadata
-   as metadata cannot store a 0 value (which is used as OK for grpc_status_codes
-   */
-#define STATUS_OFFSET 1
-static void destroy_status(void *ignored) {}
-
-static gpr_uint32 decode_status(grpc_mdelem *md) {
-  gpr_uint32 status;
-  void *user_data = grpc_mdelem_get_user_data(md, destroy_status);
-  if (user_data) {
-    status = ((gpr_uint32)(gpr_intptr)user_data) - STATUS_OFFSET;
-  } else {
-    if (!gpr_parse_bytes_to_uint32(grpc_mdstr_as_c_string(md->value),
-                                   GPR_SLICE_LENGTH(md->value->slice),
-                                   &status)) {
-      status = GRPC_STATUS_UNKNOWN; /* could not parse status code */
-    }
-    grpc_mdelem_set_user_data(md, destroy_status,
-                              (void *)(gpr_intptr)(status + STATUS_OFFSET));
-  }
-  return status;
-}
-
-void grpc_call_recv_metadata(grpc_call_element *elem, grpc_call_op *op) {
-  grpc_call *call = CALL_FROM_TOP_ELEM(elem);
-  grpc_mdelem *md = op->data.metadata;
-  grpc_mdstr *key = md->key;
-
-  if (key == grpc_channel_get_status_string(call->channel)) {
-    maybe_set_status_code(call, decode_status(md));
-    grpc_mdelem_unref(md);
-    op->done_cb(op->user_data, GRPC_OP_OK);
-  } else if (key == grpc_channel_get_message_string(call->channel)) {
-    maybe_set_status_details(call, md->value);
-    grpc_mdelem_unref(md);
-    op->done_cb(op->user_data, GRPC_OP_OK);
-  } else {
-    grpc_metadata_buffer_queue(&call->incoming_metadata, op);
-  }
-}
-
-void grpc_call_recv_finish(grpc_call_element *elem, int is_full_close) {
-  grpc_call *call = CALL_FROM_TOP_ELEM(elem);
-
-  gpr_mu_lock(&call->read_mu);
-
-  if (call->have_read) {
-    grpc_cq_end_read(call->cq, call->read_tag, call, do_nothing, NULL, NULL);
-    call->read_tag = INVALID_TAG;
-    call->have_read = 0;
-  }
-  if (call->is_client && !call->received_metadata && call->cq) {
-    size_t count;
-    grpc_metadata *elements;
-
-    call->received_metadata = 1;
-
-    count = grpc_metadata_buffer_count(&call->incoming_metadata);
-    elements = grpc_metadata_buffer_extract_elements(&call->incoming_metadata);
-    grpc_cq_end_client_metadata_read(call->cq, call->metadata_tag, call,
-                                     grpc_metadata_buffer_cleanup_elements,
-                                     elements, count, elements);
-  }
-  if (is_full_close) {
-    if (call->have_alarm) {
-      grpc_alarm_cancel(&call->alarm);
-      call->have_alarm = 0;
-    }
-    call->received_finish = 1;
-    if (prq_is_empty(&call->prq) && call->cq != NULL) {
-      finish_call(call);
-    }
-  } else {
-    call->reads_done = 1;
-  }
-  gpr_mu_unlock(&call->read_mu);
-}
-
-void grpc_call_recv_message(grpc_call_element *elem, grpc_byte_buffer *message,
-                            void (*on_finish)(void *user_data,
-                                              grpc_op_error error),
-                            void *user_data) {
-  grpc_call *call = CALL_FROM_TOP_ELEM(elem);
-
-  gpr_mu_lock(&call->read_mu);
-  if (call->have_read) {
-    grpc_cq_end_read(call->cq, call->read_tag, call, on_finish, user_data,
-                     message);
-    call->read_tag = INVALID_TAG;
-    call->have_read = 0;
-  } else {
-    prq_push(&call->prq, message, on_finish, user_data);
-  }
-  gpr_mu_unlock(&call->read_mu);
-}
-
 grpc_call *grpc_call_from_top_element(grpc_call_element *elem) {
   return CALL_FROM_TOP_ELEM(elem);
 }
 
-grpc_metadata_buffer *grpc_call_get_metadata_buffer(grpc_call *call) {
-  return &call->incoming_metadata;
-}
-
 static void call_alarm(void *arg, int success) {
   grpc_call *call = arg;
   if (success) {
@@ -967,7 +814,7 @@
       grpc_call_cancel(call);
     }
   }
-  grpc_call_internal_unref(call);
+  grpc_call_internal_unref(call, 1);
 }
 
 void grpc_call_set_deadline(grpc_call_element *elem, gpr_timespec deadline) {
@@ -981,7 +828,403 @@
   grpc_alarm_init(&call->alarm, deadline, call_alarm, call, gpr_now());
 }
 
+static void set_read_state(grpc_call *call, read_state state) {
+  lock(call);
+  GPR_ASSERT(call->read_state < state);
+  call->read_state = state;
+  finish_read_ops(call);
+  unlock(call);
+}
+
+void grpc_call_read_closed(grpc_call_element *elem) {
+  set_read_state(CALL_FROM_TOP_ELEM(elem), READ_STATE_READ_CLOSED);
+}
+
+void grpc_call_stream_closed(grpc_call_element *elem) {
+  grpc_call *call = CALL_FROM_TOP_ELEM(elem);
+  set_read_state(call, READ_STATE_STREAM_CLOSED);
+  grpc_call_internal_unref(call, 0);
+}
+
+/* we offset status by a small amount when storing it into transport metadata
+   as metadata cannot store a 0 value (which is used as OK for grpc_status_codes
+   */
+#define STATUS_OFFSET 1
+static void destroy_status(void *ignored) {}
+
+static gpr_uint32 decode_status(grpc_mdelem *md) {
+  gpr_uint32 status;
+  void *user_data = grpc_mdelem_get_user_data(md, destroy_status);
+  if (user_data) {
+    status = ((gpr_uint32)(gpr_intptr) user_data) - STATUS_OFFSET;
+  } else {
+    if (!gpr_parse_bytes_to_uint32(grpc_mdstr_as_c_string(md->value),
+                                   GPR_SLICE_LENGTH(md->value->slice),
+                                   &status)) {
+      status = GRPC_STATUS_UNKNOWN; /* could not parse status code */
+    }
+    grpc_mdelem_set_user_data(md, destroy_status,
+                              (void *)(gpr_intptr)(status + STATUS_OFFSET));
+  }
+  return status;
+}
+
+void grpc_call_recv_message(grpc_call_element *elem,
+                            grpc_byte_buffer *byte_buffer) {
+  grpc_call *call = CALL_FROM_TOP_ELEM(elem);
+  lock(call);
+  grpc_bbq_push(&call->incoming_queue, byte_buffer);
+  finish_read_ops(call);
+  unlock(call);
+}
+
+void grpc_call_recv_metadata(grpc_call_element *elem, grpc_mdelem *md) {
+  grpc_call *call = CALL_FROM_TOP_ELEM(elem);
+  grpc_mdstr *key = md->key;
+  grpc_metadata_array *dest;
+  grpc_metadata *mdusr;
+
+  lock(call);
+  if (key == grpc_channel_get_status_string(call->channel)) {
+    set_status_code(call, STATUS_FROM_WIRE, decode_status(md));
+    grpc_mdelem_unref(md);
+  } else if (key == grpc_channel_get_message_string(call->channel)) {
+    set_status_details(call, STATUS_FROM_WIRE, grpc_mdstr_ref(md->value));
+    grpc_mdelem_unref(md);
+  } else {
+    dest = &call->buffered_metadata[call->read_state >=
+                                    READ_STATE_GOT_INITIAL_METADATA];
+    if (dest->count == dest->capacity) {
+      dest->capacity = GPR_MAX(dest->capacity + 8, dest->capacity * 2);
+      dest->metadata =
+          gpr_realloc(dest->metadata, sizeof(grpc_metadata) * dest->capacity);
+    }
+    mdusr = &dest->metadata[dest->count++];
+    mdusr->key = (char *)grpc_mdstr_as_c_string(md->key);
+    mdusr->value = (char *)grpc_mdstr_as_c_string(md->value);
+    mdusr->value_length = GPR_SLICE_LENGTH(md->value->slice);
+    if (call->owned_metadata_count == call->owned_metadata_capacity) {
+      call->owned_metadata_capacity = GPR_MAX(
+          call->owned_metadata_capacity + 8, call->owned_metadata_capacity * 2);
+      call->owned_metadata =
+          gpr_realloc(call->owned_metadata,
+                      sizeof(grpc_mdelem *) * call->owned_metadata_capacity);
+    }
+    call->owned_metadata[call->owned_metadata_count++] = md;
+  }
+  unlock(call);
+}
+
 grpc_call_stack *grpc_call_get_call_stack(grpc_call *call) {
   return CALL_STACK_FROM_CALL(call);
 }
 
+void grpc_call_initial_metadata_complete(grpc_call_element *surface_element) {
+  grpc_call *call = grpc_call_from_top_element(surface_element);
+  set_read_state(call, READ_STATE_GOT_INITIAL_METADATA);
+}
+
+/*
+ * LEGACY API IMPLEMENTATION
+ * All this code will disappear as soon as wrappings are updated
+ */
+
+struct legacy_state {
+  gpr_uint8 md_out_buffer;
+  size_t md_out_count[2];
+  size_t md_out_capacity[2];
+  grpc_metadata *md_out[2];
+  grpc_byte_buffer *msg_out;
+
+  /* input buffers */
+  grpc_metadata_array initial_md_in;
+  grpc_metadata_array trailing_md_in;
+
+  size_t details_capacity;
+  char *details;
+  grpc_status_code status;
+
+  char *send_details;
+
+  size_t msg_in_read_idx;
+  grpc_byte_buffer *msg_in;
+
+  void *finished_tag;
+};
+
+static legacy_state *get_legacy_state(grpc_call *call) {
+  if (call->legacy_state == NULL) {
+    call->legacy_state = gpr_malloc(sizeof(legacy_state));
+    memset(call->legacy_state, 0, sizeof(legacy_state));
+  }
+  return call->legacy_state;
+}
+
+static void destroy_legacy_state(legacy_state *ls) {
+  size_t i, j;
+  for (i = 0; i < 2; i++) {
+    for (j = 0; j < ls->md_out_count[i]; j++) {
+      gpr_free(ls->md_out[i][j].key);
+      gpr_free(ls->md_out[i][j].value);
+    }
+    gpr_free(ls->md_out[i]);
+  }
+  gpr_free(ls->initial_md_in.metadata);
+  gpr_free(ls->trailing_md_in.metadata);
+  gpr_free(ls->details);
+  gpr_free(ls->send_details);
+  gpr_free(ls);
+}
+
+grpc_call_error grpc_call_add_metadata_old(grpc_call *call,
+                                           grpc_metadata *metadata,
+                                           gpr_uint32 flags) {
+  legacy_state *ls;
+  grpc_metadata *mdout;
+
+  lock(call);
+  ls = get_legacy_state(call);
+
+  if (ls->md_out_count[ls->md_out_buffer] ==
+      ls->md_out_capacity[ls->md_out_buffer]) {
+    ls->md_out_capacity[ls->md_out_buffer] =
+        GPR_MAX(ls->md_out_capacity[ls->md_out_buffer] * 3 / 2,
+                ls->md_out_capacity[ls->md_out_buffer] + 8);
+    ls->md_out[ls->md_out_buffer] = gpr_realloc(
+        ls->md_out[ls->md_out_buffer],
+        sizeof(grpc_metadata) * ls->md_out_capacity[ls->md_out_buffer]);
+  }
+  mdout = &ls->md_out[ls->md_out_buffer][ls->md_out_count[ls->md_out_buffer]++];
+  mdout->key = gpr_strdup(metadata->key);
+  mdout->value = gpr_malloc(metadata->value_length);
+  mdout->value_length = metadata->value_length;
+  memcpy(mdout->value, metadata->value, metadata->value_length);
+
+  unlock(call);
+
+  return GRPC_CALL_OK;
+}
+
+static void finish_status(grpc_call *call, grpc_op_error status,
+                          void *ignored) {
+  legacy_state *ls;
+
+  lock(call);
+  ls = get_legacy_state(call);
+  grpc_cq_end_finished(call->cq, ls->finished_tag, call, do_nothing, NULL,
+                       ls->status, ls->details, ls->trailing_md_in.metadata,
+                       ls->trailing_md_in.count);
+  unlock(call);
+}
+
+static void finish_recv_metadata(grpc_call *call, grpc_op_error status,
+                                 void *tag) {
+  legacy_state *ls;
+
+  lock(call);
+  ls = get_legacy_state(call);
+  if (status == GRPC_OP_OK) {
+    grpc_cq_end_client_metadata_read(call->cq, tag, call, do_nothing, NULL,
+                                     ls->initial_md_in.count,
+                                     ls->initial_md_in.metadata);
+
+  } else {
+    grpc_cq_end_client_metadata_read(call->cq, tag, call, do_nothing, NULL, 0,
+                                     NULL);
+  }
+  unlock(call);
+}
+
+static void finish_send_metadata(grpc_call *call, grpc_op_error status,
+                                 void *tag) {}
+
+grpc_call_error grpc_call_invoke_old(grpc_call *call, grpc_completion_queue *cq,
+                                     void *metadata_read_tag,
+                                     void *finished_tag, gpr_uint32 flags) {
+  grpc_ioreq reqs[3];
+  legacy_state *ls;
+  grpc_call_error err;
+
+  grpc_cq_begin_op(cq, call, GRPC_CLIENT_METADATA_READ);
+  grpc_cq_begin_op(cq, call, GRPC_FINISHED);
+
+  lock(call);
+  ls = get_legacy_state(call);
+  err = bind_cq(call, cq);
+  if (err != GRPC_CALL_OK) goto done;
+
+  ls->finished_tag = finished_tag;
+
+  reqs[0].op = GRPC_IOREQ_SEND_INITIAL_METADATA;
+  reqs[0].data.send_metadata.count = ls->md_out_count[ls->md_out_buffer];
+  reqs[0].data.send_metadata.metadata = ls->md_out[ls->md_out_buffer];
+  ls->md_out_buffer++;
+  err = start_ioreq(call, reqs, 1, finish_send_metadata, NULL);
+  if (err != GRPC_CALL_OK) goto done;
+
+  reqs[0].op = GRPC_IOREQ_RECV_INITIAL_METADATA;
+  reqs[0].data.recv_metadata = &ls->initial_md_in;
+  err = start_ioreq(call, reqs, 1, finish_recv_metadata, metadata_read_tag);
+  if (err != GRPC_CALL_OK) goto done;
+
+  reqs[0].op = GRPC_IOREQ_RECV_TRAILING_METADATA;
+  reqs[0].data.recv_metadata = &ls->trailing_md_in;
+  reqs[1].op = GRPC_IOREQ_RECV_STATUS;
+  reqs[1].data.recv_status.details = &ls->details;
+  reqs[1].data.recv_status.details_capacity = &ls->details_capacity;
+  reqs[1].data.recv_status.code = &ls->status;
+  reqs[2].op = GRPC_IOREQ_RECV_CLOSE;
+  err = start_ioreq(call, reqs, 3, finish_status, NULL);
+  if (err != GRPC_CALL_OK) goto done;
+
+done:
+  unlock(call);
+  return err;
+}
+
+grpc_call_error grpc_call_server_accept_old(grpc_call *call,
+                                            grpc_completion_queue *cq,
+                                            void *finished_tag) {
+  grpc_ioreq reqs[2];
+  grpc_call_error err;
+  legacy_state *ls;
+
+  /* inform the completion queue of an incoming operation (corresponding to
+     finished_tag) */
+  grpc_cq_begin_op(cq, call, GRPC_FINISHED);
+
+  lock(call);
+  ls = get_legacy_state(call);
+
+  err = bind_cq(call, cq);
+  if (err != GRPC_CALL_OK) return err;
+
+  ls->finished_tag = finished_tag;
+
+  reqs[0].op = GRPC_IOREQ_RECV_STATUS;
+  reqs[0].data.recv_status.details = NULL;
+  reqs[0].data.recv_status.details_capacity = 0;
+  reqs[0].data.recv_status.code = &ls->status;
+  reqs[1].op = GRPC_IOREQ_RECV_CLOSE;
+  err = start_ioreq(call, reqs, 2, finish_status, NULL);
+  unlock(call);
+  return err;
+}
+
+static void finish_send_initial_metadata(grpc_call *call, grpc_op_error status,
+                                         void *tag) {}
+
+grpc_call_error grpc_call_server_end_initial_metadata_old(grpc_call *call,
+                                                          gpr_uint32 flags) {
+  grpc_ioreq req;
+  grpc_call_error err;
+  legacy_state *ls;
+
+  lock(call);
+  ls = get_legacy_state(call);
+  req.op = GRPC_IOREQ_SEND_INITIAL_METADATA;
+  req.data.send_metadata.count = ls->md_out_count[ls->md_out_buffer];
+  req.data.send_metadata.metadata = ls->md_out[ls->md_out_buffer];
+  err = start_ioreq(call, &req, 1, finish_send_initial_metadata, NULL);
+  unlock(call);
+
+  return err;
+}
+
+static void finish_read_event(void *p, grpc_op_error error) {
+  if (p) grpc_byte_buffer_destroy(p);
+}
+
+static void finish_read(grpc_call *call, grpc_op_error error, void *tag) {
+  legacy_state *ls;
+  grpc_byte_buffer *msg;
+  lock(call);
+  ls = get_legacy_state(call);
+  msg = ls->msg_in;
+  grpc_cq_end_read(call->cq, tag, call, finish_read_event, msg, msg);
+  unlock(call);
+}
+
+grpc_call_error grpc_call_start_read_old(grpc_call *call, void *tag) {
+  legacy_state *ls;
+  grpc_ioreq req;
+  grpc_call_error err;
+
+  grpc_cq_begin_op(call->cq, call, GRPC_READ);
+
+  lock(call);
+  ls = get_legacy_state(call);
+  req.op = GRPC_IOREQ_RECV_MESSAGE;
+  req.data.recv_message = &ls->msg_in;
+  err = start_ioreq(call, &req, 1, finish_read, tag);
+  unlock(call);
+  return err;
+}
+
+static void finish_write(grpc_call *call, grpc_op_error status, void *tag) {
+  lock(call);
+  grpc_byte_buffer_destroy(get_legacy_state(call)->msg_out);
+  unlock(call);
+  grpc_cq_end_write_accepted(call->cq, tag, call, do_nothing, NULL, status);
+}
+
+grpc_call_error grpc_call_start_write_old(grpc_call *call,
+                                          grpc_byte_buffer *byte_buffer,
+                                          void *tag, gpr_uint32 flags) {
+  grpc_ioreq req;
+  legacy_state *ls;
+  grpc_call_error err;
+
+  grpc_cq_begin_op(call->cq, call, GRPC_WRITE_ACCEPTED);
+
+  lock(call);
+  ls = get_legacy_state(call);
+  ls->msg_out = grpc_byte_buffer_copy(byte_buffer);
+  req.op = GRPC_IOREQ_SEND_MESSAGE;
+  req.data.send_message = ls->msg_out;
+  err = start_ioreq(call, &req, 1, finish_write, tag);
+  unlock(call);
+
+  return err;
+}
+
+static void finish_finish(grpc_call *call, grpc_op_error status, void *tag) {
+  grpc_cq_end_finish_accepted(call->cq, tag, call, do_nothing, NULL, status);
+}
+
+grpc_call_error grpc_call_writes_done_old(grpc_call *call, void *tag) {
+  grpc_ioreq req;
+  grpc_call_error err;
+  grpc_cq_begin_op(call->cq, call, GRPC_FINISH_ACCEPTED);
+
+  lock(call);
+  req.op = GRPC_IOREQ_SEND_CLOSE;
+  err = start_ioreq(call, &req, 1, finish_finish, tag);
+  unlock(call);
+
+  return err;
+}
+
+grpc_call_error grpc_call_start_write_status_old(grpc_call *call,
+                                                 grpc_status_code status,
+                                                 const char *details,
+                                                 void *tag) {
+  grpc_ioreq reqs[3];
+  grpc_call_error err;
+  legacy_state *ls;
+  grpc_cq_begin_op(call->cq, call, GRPC_FINISH_ACCEPTED);
+
+  lock(call);
+  ls = get_legacy_state(call);
+  reqs[0].op = GRPC_IOREQ_SEND_TRAILING_METADATA;
+  reqs[0].data.send_metadata.count = ls->md_out_count[ls->md_out_buffer];
+  reqs[0].data.send_metadata.metadata = ls->md_out[ls->md_out_buffer];
+  reqs[1].op = GRPC_IOREQ_SEND_STATUS;
+  reqs[1].data.send_status.code = status;
+  reqs[1].data.send_status.details = ls->send_details = gpr_strdup(details);
+  reqs[2].op = GRPC_IOREQ_SEND_CLOSE;
+  err = start_ioreq(call, reqs, 3, finish_finish, tag);
+  unlock(call);
+
+  return err;
+}
diff --git a/src/core/surface/call.h b/src/core/surface/call.h
index 804b387..936fb29 100644
--- a/src/core/surface/call.h
+++ b/src/core/surface/call.h
@@ -38,27 +38,73 @@
 #include "src/core/channel/metadata_buffer.h"
 #include <grpc/grpc.h>
 
+/* Primitive operation types - grpc_op's get rewritten into these */
+typedef enum {
+  GRPC_IOREQ_RECV_INITIAL_METADATA,
+  GRPC_IOREQ_RECV_MESSAGE,
+  GRPC_IOREQ_RECV_TRAILING_METADATA,
+  GRPC_IOREQ_RECV_STATUS,
+  GRPC_IOREQ_RECV_CLOSE,
+  GRPC_IOREQ_SEND_INITIAL_METADATA,
+  GRPC_IOREQ_SEND_MESSAGE,
+  GRPC_IOREQ_SEND_TRAILING_METADATA,
+  GRPC_IOREQ_SEND_STATUS,
+  GRPC_IOREQ_SEND_CLOSE,
+  GRPC_IOREQ_OP_COUNT
+} grpc_ioreq_op;
+
+typedef struct {
+  grpc_status_code *code;
+  char **details;
+  size_t *details_capacity;
+} grpc_recv_status_args;
+
+typedef union {
+  grpc_metadata_array *recv_metadata;
+  grpc_byte_buffer **recv_message;
+  grpc_recv_status_args recv_status;
+  struct {
+    size_t count;
+    grpc_metadata *metadata;
+  } send_metadata;
+  grpc_byte_buffer *send_message;
+  struct {
+    grpc_status_code code;
+    char *details;
+  } send_status;
+} grpc_ioreq_data;
+
+typedef struct {
+  grpc_ioreq_op op;
+  grpc_ioreq_data data;
+} grpc_ioreq;
+
+typedef void (*grpc_ioreq_completion_func)(grpc_call *call,
+                                           grpc_op_error status,
+                                           void *user_data);
+
 grpc_call *grpc_call_create(grpc_channel *channel,
                             const void *server_transport_data);
 
 void grpc_call_internal_ref(grpc_call *call);
-void grpc_call_internal_unref(grpc_call *call);
+void grpc_call_internal_unref(grpc_call *call, int allow_immediate_deletion);
 
 /* Helpers for grpc_client, grpc_server filters to publish received data to
    the completion queue/surface layer */
 void grpc_call_recv_metadata(grpc_call_element *surface_element,
-                             grpc_call_op *op);
-void grpc_call_recv_message(
-    grpc_call_element *surface_element, grpc_byte_buffer *message,
-    void (*on_finish)(void *user_data, grpc_op_error error), void *user_data);
-void grpc_call_recv_finish(grpc_call_element *surface_element,
-                           int is_full_close);
+                             grpc_mdelem *md);
+void grpc_call_recv_message(grpc_call_element *surface_element,
+                            grpc_byte_buffer *message);
+void grpc_call_read_closed(grpc_call_element *surface_element);
+void grpc_call_stream_closed(grpc_call_element *surface_element);
 
 void grpc_call_execute_op(grpc_call *call, grpc_call_op *op);
+grpc_call_error grpc_call_start_ioreq_and_call_back(
+    grpc_call *call, const grpc_ioreq *reqs, size_t nreqs,
+    grpc_ioreq_completion_func on_complete, void *user_data);
 
-/* Called when it's known that the initial batch of metadata is complete on the
-   client side (must not be called on the server) */
-void grpc_call_client_initial_metadata_complete(
+/* Called when it's known that the initial batch of metadata is complete */
+void grpc_call_initial_metadata_complete(
     grpc_call_element *surface_element);
 
 void grpc_call_set_deadline(grpc_call_element *surface_element,
@@ -69,10 +115,4 @@
 /* Given the top call_element, get the call object. */
 grpc_call *grpc_call_from_top_element(grpc_call_element *surface_element);
 
-/* Get the metadata buffer. */
-grpc_metadata_buffer *grpc_call_get_metadata_buffer(grpc_call *call);
-
-void grpc_call_add_mdelem(grpc_call *call, grpc_mdelem *mdelem,
-                          gpr_uint32 flags);
-
 #endif /* __GRPC_INTERNAL_SURFACE_CALL_H__ */
diff --git a/src/core/surface/channel.c b/src/core/surface/channel.c
index a1bcea5..b33bd7b 100644
--- a/src/core/surface/channel.c
+++ b/src/core/surface/channel.c
@@ -51,7 +51,10 @@
   grpc_mdstr *authority_string;
 };
 
-#define CHANNEL_STACK_FROM_CHANNEL(c) ((grpc_channel_stack *)((c) + 1))
+#define CHANNEL_STACK_FROM_CHANNEL(c) ((grpc_channel_stack *)((c)+1))
+#define CHANNEL_FROM_CHANNEL_STACK(channel_stack) (((grpc_channel *)(channel_stack)) - 1)
+#define CHANNEL_FROM_TOP_ELEM(top_elem) \
+  CHANNEL_FROM_CHANNEL_STACK(grpc_channel_stack_from_top_element(top_elem))
 
 grpc_channel *grpc_channel_create_from_filters(
     const grpc_channel_filter **filters, size_t num_filters,
@@ -60,8 +63,8 @@
       sizeof(grpc_channel) + grpc_channel_stack_size(filters, num_filters);
   grpc_channel *channel = gpr_malloc(size);
   channel->is_client = is_client;
-  /* decremented by grpc_channel_destroy */
-  gpr_ref_init(&channel->refs, 1);
+  /* decremented by grpc_channel_destroy, and grpc_client_channel_closed if is_client */
+  gpr_ref_init(&channel->refs, 1 + is_client);
   channel->metadata_context = mdctx;
   channel->grpc_status_string = grpc_mdstr_from_string(mdctx, "grpc-status");
   channel->grpc_message_string = grpc_mdstr_from_string(mdctx, "grpc-message");
@@ -74,12 +77,13 @@
 
 static void do_nothing(void *ignored, grpc_op_error error) {}
 
-grpc_call *grpc_channel_create_call(grpc_channel *channel, const char *method,
-                                    const char *host,
-                                    gpr_timespec absolute_deadline) {
+grpc_call *grpc_channel_create_call_old(grpc_channel *channel,
+                                        const char *method, const char *host,
+                                        gpr_timespec absolute_deadline) {
   grpc_call *call;
   grpc_mdelem *path_mdelem;
   grpc_mdelem *authority_mdelem;
+  grpc_call_op op;
 
   if (!channel->is_client) {
     gpr_log(GPR_ERROR, "Cannot create a call on the server.");
@@ -91,20 +95,25 @@
   /* Add :path and :authority headers. */
   /* TODO(klempner): Consider optimizing this by stashing mdelems for common
      values of method and host. */
-  grpc_mdstr_ref(channel->path_string);
   path_mdelem = grpc_mdelem_from_metadata_strings(
-      channel->metadata_context, channel->path_string,
+      channel->metadata_context, grpc_mdstr_ref(channel->path_string),
       grpc_mdstr_from_string(channel->metadata_context, method));
-  grpc_call_add_mdelem(call, path_mdelem, 0);
+  op.type = GRPC_SEND_METADATA;
+  op.dir = GRPC_CALL_DOWN;
+  op.flags = 0;
+  op.data.metadata = path_mdelem;
+  op.done_cb = do_nothing;
+  op.user_data = NULL;
+  grpc_call_execute_op(call, &op);
 
   grpc_mdstr_ref(channel->authority_string);
   authority_mdelem = grpc_mdelem_from_metadata_strings(
       channel->metadata_context, channel->authority_string,
       grpc_mdstr_from_string(channel->metadata_context, host));
-  grpc_call_add_mdelem(call, authority_mdelem, 0);
+  op.data.metadata = authority_mdelem;
+  grpc_call_execute_op(call, &op);
 
   if (0 != gpr_time_cmp(absolute_deadline, gpr_inf_future)) {
-    grpc_call_op op;
     op.type = GRPC_SEND_DEADLINE;
     op.dir = GRPC_CALL_DOWN;
     op.flags = 0;
@@ -152,6 +161,10 @@
   grpc_channel_internal_unref(channel);
 }
 
+void grpc_client_channel_closed(grpc_channel_element *elem) {
+  grpc_channel_internal_unref(CHANNEL_FROM_TOP_ELEM(elem));
+}
+
 grpc_channel_stack *grpc_channel_get_channel_stack(grpc_channel *channel) {
   return CHANNEL_STACK_FROM_CHANNEL(channel);
 }
diff --git a/src/core/surface/channel.h b/src/core/surface/channel.h
index b3ea2ed..ff9bbc2 100644
--- a/src/core/surface/channel.h
+++ b/src/core/surface/channel.h
@@ -45,6 +45,8 @@
 grpc_mdstr *grpc_channel_get_status_string(grpc_channel *channel);
 grpc_mdstr *grpc_channel_get_message_string(grpc_channel *channel);
 
+void grpc_client_channel_closed(grpc_channel_element *elem);
+
 void grpc_channel_internal_ref(grpc_channel *channel);
 void grpc_channel_internal_unref(grpc_channel *channel);
 
diff --git a/src/core/surface/client.c b/src/core/surface/client.c
index a7c9b90..64ee9d5 100644
--- a/src/core/surface/client.c
+++ b/src/core/surface/client.c
@@ -34,6 +34,7 @@
 #include "src/core/surface/client.h"
 
 #include "src/core/surface/call.h"
+#include "src/core/surface/channel.h"
 #include "src/core/support/string.h"
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
@@ -56,23 +57,23 @@
       grpc_call_next_op(elem, op);
       break;
     case GRPC_RECV_METADATA:
-      grpc_call_recv_metadata(elem, op);
+      grpc_call_recv_metadata(elem, op->data.metadata);
       break;
     case GRPC_RECV_DEADLINE:
       gpr_log(GPR_ERROR, "Deadline received by client (ignored)");
       break;
     case GRPC_RECV_MESSAGE:
-      grpc_call_recv_message(elem, op->data.message, op->done_cb,
-                             op->user_data);
+      grpc_call_recv_message(elem, op->data.message);
+      op->done_cb(op->user_data, GRPC_OP_OK);
       break;
     case GRPC_RECV_HALF_CLOSE:
-      grpc_call_recv_finish(elem, 0);
+      grpc_call_read_closed(elem);
       break;
     case GRPC_RECV_FINISH:
-      grpc_call_recv_finish(elem, 1);
+      grpc_call_stream_closed(elem);
       break;
     case GRPC_RECV_END_OF_INITIAL_METADATA:
-      grpc_call_client_initial_metadata_complete(elem);
+      grpc_call_initial_metadata_complete(elem);
       break;
     default:
       GPR_ASSERT(op->dir == GRPC_CALL_DOWN);
@@ -87,7 +88,7 @@
       gpr_log(GPR_ERROR, "Client cannot accept new calls");
       break;
     case GRPC_TRANSPORT_CLOSED:
-      gpr_log(GPR_ERROR, "Transport closed");
+      grpc_client_channel_closed(elem);
       break;
     case GRPC_TRANSPORT_GOAWAY:
       gpr_slice_unref(op->data.goaway.message);
diff --git a/src/core/surface/completion_queue.c b/src/core/surface/completion_queue.c
index 2bf31c5..ae3b960 100644
--- a/src/core/surface/completion_queue.c
+++ b/src/core/surface/completion_queue.c
@@ -173,18 +173,6 @@
   gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset));
 }
 
-void grpc_cq_end_invoke_accepted(grpc_completion_queue *cc, void *tag,
-                                 grpc_call *call,
-                                 grpc_event_finish_func on_finish,
-                                 void *user_data, grpc_op_error error) {
-  event *ev;
-  gpr_mu_lock(GRPC_POLLSET_MU(&cc->pollset));
-  ev = add_locked(cc, GRPC_INVOKE_ACCEPTED, tag, call, on_finish, user_data);
-  ev->base.data.invoke_accepted = error;
-  end_op_locked(cc, GRPC_INVOKE_ACCEPTED);
-  gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset));
-}
-
 void grpc_cq_end_write_accepted(grpc_completion_queue *cc, void *tag,
                                 grpc_call *call,
                                 grpc_event_finish_func on_finish,
@@ -197,6 +185,17 @@
   gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset));
 }
 
+void grpc_cq_end_ioreq(grpc_completion_queue *cc, void *tag, grpc_call *call,
+                       grpc_event_finish_func on_finish, void *user_data,
+                       grpc_op_error error) {
+  event *ev;
+  gpr_mu_lock(GRPC_POLLSET_MU(&cc->pollset));
+  ev = add_locked(cc, GRPC_IOREQ, tag, call, on_finish, user_data);
+  ev->base.data.write_accepted = error;
+  end_op_locked(cc, GRPC_IOREQ);
+  gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset));
+}
+
 void grpc_cq_end_finish_accepted(grpc_completion_queue *cc, void *tag,
                                  grpc_call *call,
                                  grpc_event_finish_func on_finish,
@@ -389,7 +388,7 @@
   event *ev = (event *)base;
   ev->on_finish(ev->on_finish_user_data, GRPC_OP_OK);
   if (ev->base.call) {
-    grpc_call_internal_unref(ev->base.call);
+    grpc_call_internal_unref(ev->base.call, 1);
   }
   gpr_free(ev);
 }
diff --git a/src/core/surface/completion_queue.h b/src/core/surface/completion_queue.h
index 8598407..fea8336 100644
--- a/src/core/surface/completion_queue.h
+++ b/src/core/surface/completion_queue.h
@@ -97,6 +97,10 @@
                          gpr_timespec deadline, size_t metadata_count,
                          grpc_metadata *metadata_elements);
 
+void grpc_cq_end_ioreq(grpc_completion_queue *cc, void *tag, grpc_call *call,
+                       grpc_event_finish_func on_finish, void *user_data,
+                       grpc_op_error error);
+
 void grpc_cq_end_server_shutdown(grpc_completion_queue *cc, void *tag);
 
 /* disable polling for some tests */
diff --git a/src/core/surface/event_string.c b/src/core/surface/event_string.c
index 8975d31..7c76bf9 100644
--- a/src/core/surface/event_string.c
+++ b/src/core/surface/event_string.c
@@ -87,10 +87,10 @@
         gpr_strvec_add(&buf, gpr_strdup(" end-of-stream"));
       }
       break;
-    case GRPC_INVOKE_ACCEPTED:
-      gpr_strvec_add(&buf, gpr_strdup("INVOKE_ACCEPTED: "));
+    case GRPC_IOREQ:
+      gpr_strvec_add(&buf, gpr_strdup("IOREQ: "));
       addhdr(&buf, ev);
-      adderr(&buf, ev->data.invoke_accepted);
+      adderr(&buf, ev->data.ioreq);
       break;
     case GRPC_WRITE_ACCEPTED:
       gpr_strvec_add(&buf, gpr_strdup("WRITE_ACCEPTED: "));
diff --git a/src/core/surface/lame_client.c b/src/core/surface/lame_client.c
index 6098ac7..411dbab 100644
--- a/src/core/surface/lame_client.c
+++ b/src/core/surface/lame_client.c
@@ -50,26 +50,16 @@
   grpc_mdelem *message;
 } channel_data;
 
-static void do_nothing(void *data, grpc_op_error error) {}
-
 static void call_op(grpc_call_element *elem, grpc_call_element *from_elem,
                     grpc_call_op *op) {
   channel_data *channeld = elem->channel_data;
   GRPC_CALL_LOG_OP(GPR_INFO, elem, op);
 
   switch (op->type) {
-    case GRPC_SEND_START: {
-      grpc_call_op set_status_op;
-      grpc_mdelem_ref(channeld->message);
-      memset(&set_status_op, 0, sizeof(grpc_call_op));
-      set_status_op.dir = GRPC_CALL_UP;
-      set_status_op.type = GRPC_RECV_METADATA;
-      set_status_op.done_cb = do_nothing;
-      set_status_op.data.metadata = channeld->message;
-      grpc_call_recv_metadata(elem, &set_status_op);
-      grpc_call_recv_finish(elem, 1);
+    case GRPC_SEND_START:
+      grpc_call_recv_metadata(elem, grpc_mdelem_ref(channeld->message));
+      grpc_call_stream_closed(elem);
       break;
-    }
     case GRPC_SEND_METADATA:
       grpc_mdelem_unref(op->data.metadata);
       break;
@@ -86,6 +76,9 @@
     case GRPC_CHANNEL_GOAWAY:
       gpr_slice_unref(op->data.goaway.message);
       break;
+    case GRPC_CHANNEL_DISCONNECT:
+      grpc_client_channel_closed(elem);
+      break;
     default:
       break;
   }
diff --git a/src/core/surface/server.c b/src/core/surface/server.c
index 9a001f4..455bd43 100644
--- a/src/core/surface/server.c
+++ b/src/core/surface/server.c
@@ -44,6 +44,7 @@
 #include "src/core/surface/call.h"
 #include "src/core/surface/channel.h"
 #include "src/core/surface/completion_queue.h"
+#include "src/core/transport/metadata.h"
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/useful.h>
@@ -63,11 +64,24 @@
 struct channel_data {
   grpc_server *server;
   grpc_channel *channel;
+  grpc_mdstr *path_key;
+  grpc_mdstr *authority_key;
   /* linked list of all channels on a server */
   channel_data *next;
   channel_data *prev;
 };
 
+typedef void (*new_call_cb)(grpc_server *server, grpc_completion_queue *cq,
+                            grpc_metadata_array *initial_metadata,
+                            call_data *calld, void *user_data);
+
+typedef struct {
+  void *user_data;
+  grpc_completion_queue *cq;
+  grpc_metadata_array *initial_metadata;
+  new_call_cb cb;
+} requested_call;
+
 struct grpc_server {
   size_t channel_filter_count;
   const grpc_channel_filter **channel_filters;
@@ -76,9 +90,9 @@
 
   gpr_mu mu;
 
-  void **tags;
-  size_t ntags;
-  size_t tag_cap;
+  requested_call *requested_calls;
+  size_t requested_call_count;
+  size_t requested_call_capacity;
 
   gpr_uint8 shutdown;
   gpr_uint8 have_shutdown_tag;
@@ -107,11 +121,17 @@
   ZOMBIED
 } call_state;
 
+typedef struct legacy_data { grpc_metadata_array *initial_metadata; } legacy_data;
+
 struct call_data {
   grpc_call *call;
 
   call_state state;
   gpr_timespec deadline;
+  grpc_mdstr *path;
+  grpc_mdstr *host;
+
+  legacy_data *legacy;
 
   gpr_uint8 included[CALL_LIST_COUNT];
   call_link links[CALL_LIST_COUNT];
@@ -179,7 +199,7 @@
     grpc_channel_args_destroy(server->channel_args);
     gpr_mu_destroy(&server->mu);
     gpr_free(server->channel_filters);
-    gpr_free(server->tags);
+    gpr_free(server->requested_calls);
     gpr_free(server);
   }
 }
@@ -210,62 +230,36 @@
   grpc_iomgr_add_callback(finish_destroy_channel, chand);
 }
 
-static void queue_new_rpc(grpc_server *server, call_data *calld, void *tag) {
-  grpc_call *call = calld->call;
-  grpc_metadata_buffer *mdbuf = grpc_call_get_metadata_buffer(call);
-  size_t count = grpc_metadata_buffer_count(mdbuf);
-  grpc_metadata *elements = grpc_metadata_buffer_extract_elements(mdbuf);
-  const char *host = NULL;
-  const char *method = NULL;
-  size_t i;
-
-  for (i = 0; i < count; i++) {
-    if (0 == strcmp(elements[i].key, ":authority")) {
-      host = elements[i].value;
-    } else if (0 == strcmp(elements[i].key, ":path")) {
-      method = elements[i].value;
-    }
-  }
-
-  grpc_call_internal_ref(call);
-  grpc_cq_end_new_rpc(server->cq, tag, call,
-                      grpc_metadata_buffer_cleanup_elements, elements, method,
-                      host, calld->deadline, count, elements);
-}
-
 static void start_new_rpc(grpc_call_element *elem) {
   channel_data *chand = elem->channel_data;
   call_data *calld = elem->call_data;
   grpc_server *server = chand->server;
 
   gpr_mu_lock(&server->mu);
-  if (server->ntags) {
+  if (server->requested_call_count > 0) {
+    requested_call rc = server->requested_calls[--server->requested_call_count];
     calld->state = ACTIVATED;
-    queue_new_rpc(server, calld, server->tags[--server->ntags]);
+    gpr_mu_unlock(&server->mu);
+    rc.cb(server, rc.cq, rc.initial_metadata, calld, rc.user_data);
   } else {
     calld->state = PENDING;
     call_list_join(server, calld, PENDING_START);
+    gpr_mu_unlock(&server->mu);
   }
-  gpr_mu_unlock(&server->mu);
 }
 
 static void kill_zombie(void *elem, int success) {
   grpc_call_destroy(grpc_call_from_top_element(elem));
 }
 
-static void finish_rpc(grpc_call_element *elem, int is_full_close) {
+static void stream_closed(grpc_call_element *elem) {
   call_data *calld = elem->call_data;
   channel_data *chand = elem->channel_data;
   gpr_mu_lock(&chand->server->mu);
   switch (calld->state) {
     case ACTIVATED:
-      grpc_call_recv_finish(elem, is_full_close);
       break;
     case PENDING:
-      if (!is_full_close) {
-        grpc_call_recv_finish(elem, is_full_close);
-        break;
-      }
       call_list_remove(chand->server, calld, PENDING_START);
     /* fallthrough intended */
     case NOT_STARTED:
@@ -276,27 +270,60 @@
       break;
   }
   gpr_mu_unlock(&chand->server->mu);
+  grpc_call_stream_closed(elem);
+}
+
+static void read_closed(grpc_call_element *elem) {
+  call_data *calld = elem->call_data;
+  channel_data *chand = elem->channel_data;
+  gpr_mu_lock(&chand->server->mu);
+  switch (calld->state) {
+    case ACTIVATED:
+    case PENDING:
+      grpc_call_read_closed(elem);
+      break;
+    case NOT_STARTED:
+      calld->state = ZOMBIED;
+      grpc_iomgr_add_callback(kill_zombie, elem);
+      break;
+    case ZOMBIED:
+      break;
+  }
+  gpr_mu_unlock(&chand->server->mu);
 }
 
 static void call_op(grpc_call_element *elem, grpc_call_element *from_elemn,
                     grpc_call_op *op) {
+  channel_data *chand = elem->channel_data;
+  call_data *calld = elem->call_data;
+  grpc_mdelem *md;
   GRPC_CALL_LOG_OP(GPR_INFO, elem, op);
   switch (op->type) {
     case GRPC_RECV_METADATA:
-      grpc_call_recv_metadata(elem, op);
+      md = op->data.metadata;
+      if (md->key == chand->path_key) {
+        calld->path = grpc_mdstr_ref(md->value);
+        grpc_mdelem_unref(md);
+      } else if (md->key == chand->authority_key) {
+        calld->host = grpc_mdstr_ref(md->value);
+        grpc_mdelem_unref(md);
+      } else {
+        grpc_call_recv_metadata(elem, md);
+      }
       break;
     case GRPC_RECV_END_OF_INITIAL_METADATA:
       start_new_rpc(elem);
+      grpc_call_initial_metadata_complete(elem);
       break;
     case GRPC_RECV_MESSAGE:
-      grpc_call_recv_message(elem, op->data.message, op->done_cb,
-                             op->user_data);
+      grpc_call_recv_message(elem, op->data.message);
+      op->done_cb(op->user_data, GRPC_OP_OK);
       break;
     case GRPC_RECV_HALF_CLOSE:
-      finish_rpc(elem, 0);
+      read_closed(elem);
       break;
     case GRPC_RECV_FINISH:
-      finish_rpc(elem, 1);
+      stream_closed(elem);
       break;
     case GRPC_RECV_DEADLINE:
       grpc_call_set_deadline(elem, op->data.deadline);
@@ -371,6 +398,7 @@
 
 static void destroy_call_elem(grpc_call_element *elem) {
   channel_data *chand = elem->channel_data;
+  call_data *calld = elem->call_data;
   int i;
 
   gpr_mu_lock(&chand->server->mu);
@@ -383,6 +411,19 @@
   }
   gpr_mu_unlock(&chand->server->mu);
 
+  if (calld->host) {
+    grpc_mdstr_unref(calld->host);
+  }
+  if (calld->path) {
+    grpc_mdstr_unref(calld->path);
+  }
+
+  if (calld->legacy) {
+    gpr_free(calld->legacy->initial_metadata->metadata);
+    gpr_free(calld->legacy->initial_metadata);
+    gpr_free(calld->legacy);
+  }
+
   server_unref(chand->server);
 }
 
@@ -395,6 +436,8 @@
   GPR_ASSERT(!is_last);
   chand->server = NULL;
   chand->channel = NULL;
+  chand->path_key = grpc_mdstr_from_string(metadata_context, ":path");
+  chand->authority_key = grpc_mdstr_from_string(metadata_context, ":authority");
   chand->next = chand->prev = chand;
 }
 
@@ -406,6 +449,8 @@
     chand->prev->next = chand->next;
     chand->next = chand->prev = chand;
     gpr_mu_unlock(&chand->server->mu);
+    grpc_mdstr_unref(chand->path_key);
+    grpc_mdstr_unref(chand->authority_key);
     server_unref(chand->server);
   }
 }
@@ -413,17 +458,8 @@
 static const grpc_channel_filter server_surface_filter = {
     call_op,           channel_op,           sizeof(call_data),
     init_call_elem,    destroy_call_elem,    sizeof(channel_data),
-    init_channel_elem, destroy_channel_elem, "server", };
-
-static void early_terminate_requested_calls(grpc_completion_queue *cq,
-                                            void **tags, size_t ntags) {
-  size_t i;
-
-  for (i = 0; i < ntags; i++) {
-    grpc_cq_end_new_rpc(cq, tags[i], NULL, do_nothing, NULL, NULL, NULL,
-                        gpr_inf_past, 0, NULL);
-  }
-}
+    init_channel_elem, destroy_channel_elem, "server",
+};
 
 grpc_server *grpc_server_create_from_filters(grpc_completion_queue *cq,
                                              grpc_channel_filter **filters,
@@ -517,8 +553,8 @@
 void shutdown_internal(grpc_server *server, gpr_uint8 have_shutdown_tag,
                        void *shutdown_tag) {
   listener *l;
-  void **tags;
-  size_t ntags;
+  requested_call *requested_calls;
+  size_t requested_call_count;
   channel_data **channels;
   channel_data *c;
   size_t nchannels;
@@ -547,10 +583,10 @@
     i++;
   }
 
-  tags = server->tags;
-  ntags = server->ntags;
-  server->tags = NULL;
-  server->ntags = 0;
+  requested_calls = server->requested_calls;
+  requested_call_count = server->requested_call_count;
+  server->requested_calls = NULL;
+  server->requested_call_count = 0;
 
   server->shutdown = 1;
   server->have_shutdown_tag = have_shutdown_tag;
@@ -579,8 +615,12 @@
   gpr_free(channels);
 
   /* terminate all the requested calls */
-  early_terminate_requested_calls(server->cq, tags, ntags);
-  gpr_free(tags);
+  for (i = 0; i < requested_call_count; i++) {
+    requested_calls[i].cb(server, requested_calls[i].cq,
+                          requested_calls[i].initial_metadata, NULL,
+                          requested_calls[i].user_data);
+  }
+  gpr_free(requested_calls);
 
   /* Shutdown listeners */
   for (l = server->listeners; l; l = l->next) {
@@ -625,35 +665,105 @@
   server->listeners = l;
 }
 
-grpc_call_error grpc_server_request_call(grpc_server *server, void *tag_new) {
+static grpc_call_error queue_call_request(grpc_server *server,
+                                          grpc_completion_queue *cq,
+                                          grpc_metadata_array *initial_metadata,
+                                          new_call_cb cb, void *user_data) {
   call_data *calld;
-
-  grpc_cq_begin_op(server->cq, NULL, GRPC_SERVER_RPC_NEW);
-
+  requested_call *rc;
   gpr_mu_lock(&server->mu);
-
   if (server->shutdown) {
     gpr_mu_unlock(&server->mu);
-    early_terminate_requested_calls(server->cq, &tag_new, 1);
+    cb(server, cq, initial_metadata, NULL, user_data);
     return GRPC_CALL_OK;
   }
-
   calld = call_list_remove_head(server, PENDING_START);
   if (calld) {
     GPR_ASSERT(calld->state == PENDING);
     calld->state = ACTIVATED;
-    queue_new_rpc(server, calld, tag_new);
+    gpr_mu_unlock(&server->mu);
+    cb(server, cq, initial_metadata, calld, user_data);
+    return GRPC_CALL_OK;
   } else {
-    if (server->tag_cap == server->ntags) {
-      server->tag_cap = GPR_MAX(3 * server->tag_cap / 2, server->tag_cap + 1);
-      server->tags =
-          gpr_realloc(server->tags, sizeof(void *) * server->tag_cap);
+    if (server->requested_call_count == server->requested_call_capacity) {
+      server->requested_call_capacity =
+          GPR_MAX(server->requested_call_capacity + 8,
+                  server->requested_call_capacity * 2);
+      server->requested_calls =
+          gpr_realloc(server->requested_calls,
+                      sizeof(requested_call) * server->requested_call_capacity);
     }
-    server->tags[server->ntags++] = tag_new;
+    rc = &server->requested_calls[server->requested_call_count++];
+    rc->cb = cb;
+    rc->cq = cq;
+    rc->user_data = user_data;
+    rc->initial_metadata = initial_metadata;
+    gpr_mu_unlock(&server->mu);
+    return GRPC_CALL_OK;
   }
-  gpr_mu_unlock(&server->mu);
+}
 
-  return GRPC_CALL_OK;
+static void begin_request(grpc_server *server, grpc_completion_queue *cq,
+                          grpc_metadata_array *initial_metadata,
+                          call_data *call_data, void *tag) {
+  abort();
+}
+
+grpc_call_error grpc_server_request_call(
+    grpc_server *server, grpc_call_details *details,
+    grpc_metadata_array *initial_metadata, grpc_completion_queue *cq,
+    void *tag) {
+  grpc_cq_begin_op(cq, NULL, GRPC_IOREQ);
+  return queue_call_request(server, cq, initial_metadata, begin_request, tag);
+}
+
+static void publish_legacy_request(grpc_call *call, grpc_op_error status,
+                                   void *tag) {
+  grpc_call_element *elem =
+      grpc_call_stack_element(grpc_call_get_call_stack(call), 0);
+  call_data *calld = elem->call_data;
+  channel_data *chand = elem->channel_data;
+  grpc_server *server = chand->server;
+
+  if (status == GRPC_OP_OK) {
+    grpc_cq_end_new_rpc(server->cq, tag, call, do_nothing, NULL,
+                        grpc_mdstr_as_c_string(calld->path),
+                        grpc_mdstr_as_c_string(calld->host), calld->deadline,
+                        calld->legacy->initial_metadata->count,
+                        calld->legacy->initial_metadata->metadata);
+  } else {
+    abort();
+  }
+}
+
+static void begin_legacy_request(grpc_server *server, grpc_completion_queue *cq,
+                                 grpc_metadata_array *initial_metadata,
+                                 call_data *calld, void *tag) {
+  grpc_ioreq req;
+  if (!calld) {
+    gpr_free(initial_metadata);
+    grpc_cq_end_new_rpc(cq, tag, NULL, do_nothing, NULL, NULL, NULL,
+                        gpr_inf_past, 0, NULL);
+    return;
+  }
+  req.op = GRPC_IOREQ_RECV_INITIAL_METADATA;
+  req.data.recv_metadata = initial_metadata;
+  calld->legacy = gpr_malloc(sizeof(legacy_data));
+  memset(calld->legacy, 0, sizeof(legacy_data));
+  calld->legacy->initial_metadata = initial_metadata;
+  grpc_call_internal_ref(calld->call);
+  grpc_call_start_ioreq_and_call_back(calld->call, &req, 1,
+                                      publish_legacy_request, tag);
+}
+
+grpc_call_error grpc_server_request_call_old(grpc_server *server,
+                                             void *tag_new) {
+  grpc_metadata_array *client_metadata =
+      gpr_malloc(sizeof(grpc_metadata_array));
+  memset(client_metadata, 0, sizeof(*client_metadata));
+  grpc_cq_begin_op(server->cq, NULL, GRPC_SERVER_RPC_NEW);
+  return queue_call_request(server, server->cq, client_metadata,
+                            begin_legacy_request, tag_new);
 }
 
 const grpc_channel_args *grpc_server_get_channel_args(grpc_server *server) {
diff --git a/src/core/transport/chttp2/stream_encoder.c b/src/core/transport/chttp2/stream_encoder.c
index c4e3ca5..2af18c3 100644
--- a/src/core/transport/chttp2/stream_encoder.c
+++ b/src/core/transport/chttp2/stream_encoder.c
@@ -432,7 +432,7 @@
 
 static void deadline_enc(grpc_chttp2_hpack_compressor *c, gpr_timespec deadline,
                          framer_state *st) {
-  char timeout_str[32];
+  char timeout_str[GRPC_CHTTP2_TIMEOUT_ENCODE_MIN_BUFSIZE];
   grpc_chttp2_encode_timeout(gpr_time_sub(deadline, gpr_now()), timeout_str);
   hpack_enc(c, grpc_mdelem_from_metadata_strings(
                    c->mdctx, grpc_mdstr_ref(c->timeout_key_str),
diff --git a/src/core/transport/chttp2_transport.c b/src/core/transport/chttp2_transport.c
index 5465d33..ea579cf 100644
--- a/src/core/transport/chttp2_transport.c
+++ b/src/core/transport/chttp2_transport.c
@@ -184,7 +184,6 @@
   gpr_uint8 is_client;
 
   gpr_mu mu;
-  gpr_cv cv;
 
   /* basic state management - what are we doing at the moment? */
   gpr_uint8 reading;
@@ -237,6 +236,9 @@
   /* state for a stream that's not yet been created */
   grpc_stream_op_buffer new_stream_sopb;
 
+  /* stream ops that need to be destroyed, but outside of the lock */
+  grpc_stream_op_buffer nuke_later_sopb;
+
   /* active parser */
   void *parser_data;
   stream *incoming_stream;
@@ -325,6 +327,9 @@
 
 static void become_skip_parser(transport *t);
 
+static void recv_data(void *tp, gpr_slice *slices, size_t nslices,
+                      grpc_endpoint_cb_status error);
+
 /*
  * CONSTRUCTION/DESTRUCTION/REFCOUNTING
  */
@@ -370,6 +375,8 @@
   }
   gpr_free(t->pending_goaways);
 
+  grpc_sopb_destroy(&t->nuke_later_sopb);
+
   gpr_free(t);
 }
 
@@ -377,8 +384,8 @@
 
 static void init_transport(transport *t, grpc_transport_setup_callback setup,
                            void *arg, const grpc_channel_args *channel_args,
-                           grpc_endpoint *ep, grpc_mdctx *mdctx,
-                           int is_client) {
+                           grpc_endpoint *ep, gpr_slice *slices, size_t nslices,
+                           grpc_mdctx *mdctx, int is_client) {
   size_t i;
   int j;
   grpc_transport_setup_result sr;
@@ -390,7 +397,6 @@
   /* one ref is for destroy, the other for when ep becomes NULL */
   gpr_ref_init(&t->refs, 2);
   gpr_mu_init(&t->mu);
-  gpr_cv_init(&t->cv);
   t->metadata_context = mdctx;
   t->str_grpc_timeout =
       grpc_mdstr_from_string(t->metadata_context, "grpc-timeout");
@@ -416,6 +422,8 @@
   t->cap_pending_goaways = 0;
   gpr_slice_buffer_init(&t->outbuf);
   gpr_slice_buffer_init(&t->qbuf);
+  grpc_sopb_init(&t->nuke_later_sopb);
+  grpc_chttp2_hpack_parser_init(&t->hpack_parser, t->metadata_context);
   if (is_client) {
     gpr_slice_buffer_add(&t->qbuf,
                          gpr_slice_from_copied_string(CLIENT_CONNECT_STRING));
@@ -470,14 +478,15 @@
   ref_transport(t);
   gpr_mu_unlock(&t->mu);
 
+  ref_transport(t);
+  recv_data(t, slices, nslices, GRPC_ENDPOINT_CB_OK);
+
   sr = setup(arg, &t->base, t->metadata_context);
 
   lock(t);
   t->cb = sr.callbacks;
   t->cb_user_data = sr.user_data;
-  grpc_chttp2_hpack_parser_init(&t->hpack_parser, t->metadata_context);
   t->calling_back = 0;
-  gpr_cv_broadcast(&t->cv);
   unlock(t);
   unref_transport(t);
 }
@@ -486,9 +495,6 @@
   transport *t = (transport *)gt;
 
   gpr_mu_lock(&t->mu);
-  while (t->calling_back) {
-    gpr_cv_wait(&t->cv, &t->mu, gpr_inf_future);
-  }
   t->cb = NULL;
   gpr_mu_unlock(&t->mu);
 
@@ -555,6 +561,11 @@
   return 0;
 }
 
+static void schedule_nuke_sopb(transport *t, grpc_stream_op_buffer *sopb) {
+  grpc_sopb_append(&t->nuke_later_sopb, sopb->ops, sopb->nops);
+  sopb->nops = 0;
+}
+
 static void destroy_stream(grpc_transport *gt, grpc_stream *gs) {
   transport *t = (transport *)gt;
   stream *s = (stream *)gs;
@@ -562,13 +573,6 @@
 
   gpr_mu_lock(&t->mu);
 
-  /* await pending callbacks
-     TODO(ctiller): this could be optimized to check if this stream is getting
-     callbacks */
-  while (t->calling_back) {
-    gpr_cv_wait(&t->cv, &t->mu, gpr_inf_future);
-  }
-
   /* stop parsing if we're currently parsing this stream */
   if (t->deframe_state == DTS_FRAME && t->incoming_stream_id == s->id &&
       s->id != 0) {
@@ -580,7 +584,6 @@
   }
   remove_from_stream_map(t, s);
 
-  gpr_cv_broadcast(&t->cv);
   gpr_mu_unlock(&t->mu);
 
   grpc_sopb_destroy(&s->outgoing_sopb);
@@ -681,6 +684,11 @@
   int i;
   pending_goaway *goaways = NULL;
   grpc_endpoint *ep = t->ep;
+  grpc_stream_op_buffer nuke_now = t->nuke_later_sopb;
+
+  if (nuke_now.nops) {
+    memset(&t->nuke_later_sopb, 0, sizeof(t->nuke_later_sopb));
+  }
 
   /* see if we need to trigger a write - and if so, get the data ready */
   if (ep && !t->writing) {
@@ -745,11 +753,14 @@
   if (perform_callbacks || call_closed || num_goaways) {
     lock(t);
     t->calling_back = 0;
-    gpr_cv_broadcast(&t->cv);
     unlock(t);
     unref_transport(t);
   }
 
+  if (nuke_now.nops) {
+    grpc_sopb_destroy(&nuke_now);
+  }
+
   gpr_free(goaways);
 }
 
@@ -872,7 +883,6 @@
   if (!t->reading) {
     grpc_endpoint_destroy(t->ep);
     t->ep = NULL;
-    gpr_cv_broadcast(&t->cv);
     unref_transport(t); /* safe because we'll still have the ref for write */
   }
   unlock(t);
@@ -937,7 +947,7 @@
       stream_list_join(t, s, WRITABLE);
     }
   } else {
-    grpc_stream_ops_unref_owned_objects(ops, ops_count);
+    grpc_sopb_append(&t->nuke_later_sopb, ops, ops_count);
   }
   if (is_last && s->outgoing_sopb.nops == 0 && s->read_closed) {
     stream_list_join(t, s, PENDING_CALLBACKS);
@@ -1006,9 +1016,9 @@
 
   if (s) {
     /* clear out any unreported input & output: nobody cares anymore */
-    grpc_sopb_reset(&s->parser.incoming_sopb);
     had_outgoing = s->outgoing_sopb.nops != 0;
-    grpc_sopb_reset(&s->outgoing_sopb);
+    schedule_nuke_sopb(t, &s->parser.incoming_sopb);
+    schedule_nuke_sopb(t, &s->outgoing_sopb);
     if (s->cancelled) {
       send_rst = 0;
     } else if (!s->read_closed || !s->sent_write_closed || had_outgoing) {
@@ -1518,7 +1528,7 @@
     dts_fh_0:
     case DTS_FH_0:
       GPR_ASSERT(cur < end);
-      t->incoming_frame_size = ((gpr_uint32) * cur) << 16;
+      t->incoming_frame_size = ((gpr_uint32)*cur) << 16;
       if (++cur == end) {
         t->deframe_state = DTS_FH_1;
         return 1;
@@ -1526,7 +1536,7 @@
     /* fallthrough */
     case DTS_FH_1:
       GPR_ASSERT(cur < end);
-      t->incoming_frame_size |= ((gpr_uint32) * cur) << 8;
+      t->incoming_frame_size |= ((gpr_uint32)*cur) << 8;
       if (++cur == end) {
         t->deframe_state = DTS_FH_2;
         return 1;
@@ -1558,7 +1568,7 @@
     /* fallthrough */
     case DTS_FH_5:
       GPR_ASSERT(cur < end);
-      t->incoming_stream_id = (((gpr_uint32) * cur) << 24) & 0x7f;
+      t->incoming_stream_id = (((gpr_uint32)*cur) << 24) & 0x7f;
       if (++cur == end) {
         t->deframe_state = DTS_FH_6;
         return 1;
@@ -1566,7 +1576,7 @@
     /* fallthrough */
     case DTS_FH_6:
       GPR_ASSERT(cur < end);
-      t->incoming_stream_id |= ((gpr_uint32) * cur) << 16;
+      t->incoming_stream_id |= ((gpr_uint32)*cur) << 16;
       if (++cur == end) {
         t->deframe_state = DTS_FH_7;
         return 1;
@@ -1574,7 +1584,7 @@
     /* fallthrough */
     case DTS_FH_7:
       GPR_ASSERT(cur < end);
-      t->incoming_stream_id |= ((gpr_uint32) * cur) << 8;
+      t->incoming_stream_id |= ((gpr_uint32)*cur) << 8;
       if (++cur == end) {
         t->deframe_state = DTS_FH_8;
         return 1;
@@ -1582,7 +1592,7 @@
     /* fallthrough */
     case DTS_FH_8:
       GPR_ASSERT(cur < end);
-      t->incoming_stream_id |= ((gpr_uint32) * cur);
+      t->incoming_stream_id |= ((gpr_uint32)*cur);
       t->deframe_state = DTS_FRAME;
       if (!init_frame_parser(t)) {
         return 0;
@@ -1653,7 +1663,6 @@
       if (!t->writing && t->ep) {
         grpc_endpoint_destroy(t->ep);
         t->ep = NULL;
-        gpr_cv_broadcast(&t->cv);
         unref_transport(t); /* safe as we still have a ref for read */
       }
       unlock(t);
@@ -1738,9 +1747,9 @@
  */
 
 static const grpc_transport_vtable vtable = {
-    sizeof(stream),  init_stream,    send_batch,       set_allow_window_updates,
-    add_to_pollset,  destroy_stream, abort_stream,     goaway,
-    close_transport, send_ping,      destroy_transport};
+    sizeof(stream), init_stream, send_batch, set_allow_window_updates,
+    add_to_pollset, destroy_stream, abort_stream, goaway, close_transport,
+    send_ping, destroy_transport};
 
 void grpc_create_chttp2_transport(grpc_transport_setup_callback setup,
                                   void *arg,
@@ -1749,7 +1758,6 @@
                                   size_t nslices, grpc_mdctx *mdctx,
                                   int is_client) {
   transport *t = gpr_malloc(sizeof(transport));
-  init_transport(t, setup, arg, channel_args, ep, mdctx, is_client);
-  ref_transport(t);
-  recv_data(t, slices, nslices, GRPC_ENDPOINT_CB_OK);
+  init_transport(t, setup, arg, channel_args, ep, slices, nslices, mdctx,
+                 is_client);
 }
diff --git a/src/core/tsi/ssl_transport_security.c b/src/core/tsi/ssl_transport_security.c
index 0f8cbcc..e23421f 100644
--- a/src/core/tsi/ssl_transport_security.c
+++ b/src/core/tsi/ssl_transport_security.c
@@ -37,6 +37,7 @@
 
 #include <grpc/support/log.h>
 #include <grpc/support/sync.h>
+#include <grpc/support/thd.h>
 #include <grpc/support/useful.h>
 #include "src/core/tsi/transport_security.h"
 
@@ -103,11 +104,32 @@
 /* --- Library Initialization. ---*/
 
 static gpr_once init_openssl_once = GPR_ONCE_INIT;
+static gpr_mu *openssl_mutexes = NULL;
+
+static void openssl_locking_cb(int mode, int type, const char* file, int line) {
+  if (mode & CRYPTO_LOCK) {
+    gpr_mu_lock(&openssl_mutexes[type]);
+  } else {
+    gpr_mu_unlock(&openssl_mutexes[type]);
+  }
+}
+
+static unsigned long openssl_thread_id_cb(void) {
+  return (unsigned long)gpr_thd_currentid();
+}
 
 static void init_openssl(void) {
+  int i;
   SSL_library_init();
   SSL_load_error_strings();
   OpenSSL_add_all_algorithms();
+  openssl_mutexes = malloc(CRYPTO_num_locks() * sizeof(gpr_mu));
+  GPR_ASSERT(openssl_mutexes != NULL);
+  for (i = 0; i < CRYPTO_num_locks(); i++) {
+    gpr_mu_init(&openssl_mutexes[i]);
+  }
+  CRYPTO_set_locking_callback(openssl_locking_cb);
+  CRYPTO_set_id_callback(openssl_thread_id_cb);
 }
 
 /* --- Ssl utils. ---*/
diff --git a/src/cpp/client/channel.cc b/src/cpp/client/channel.cc
index c8b2bb2..3f39364 100644
--- a/src/cpp/client/channel.cc
+++ b/src/cpp/client/channel.cc
@@ -99,9 +99,10 @@
                                  const google::protobuf::Message &request,
                                  google::protobuf::Message *result) {
   Status status;
-  grpc_call *call = grpc_channel_create_call(
+  grpc_call *call = grpc_channel_create_call_old(
       c_channel_, method.name(), target_.c_str(), context->RawDeadline());
   context->set_call(call);
+
   grpc_event *ev;
   void *finished_tag = reinterpret_cast<char *>(call);
   void *metadata_read_tag = reinterpret_cast<char *>(call) + 2;
@@ -114,8 +115,8 @@
   // add_metadata from context
   //
   // invoke
-  GPR_ASSERT(grpc_call_invoke(call, cq, metadata_read_tag, finished_tag,
-                              GRPC_WRITE_BUFFER_HINT) == GRPC_CALL_OK);
+  GPR_ASSERT(grpc_call_invoke_old(call, cq, metadata_read_tag, finished_tag,
+                                  GRPC_WRITE_BUFFER_HINT) == GRPC_CALL_OK);
   // write request
   grpc_byte_buffer *write_buffer = nullptr;
   bool success = SerializeProto(request, &write_buffer);
@@ -126,8 +127,8 @@
     GetFinalStatus(cq, finished_tag, nullptr);
     return status;
   }
-  GPR_ASSERT(grpc_call_start_write(call, write_buffer, write_tag,
-                                   GRPC_WRITE_BUFFER_HINT) == GRPC_CALL_OK);
+  GPR_ASSERT(grpc_call_start_write_old(call, write_buffer, write_tag,
+                                       GRPC_WRITE_BUFFER_HINT) == GRPC_CALL_OK);
   grpc_byte_buffer_destroy(write_buffer);
   ev = grpc_completion_queue_pluck(cq, write_tag, gpr_inf_future);
 
@@ -138,7 +139,7 @@
     return status;
   }
   // writes done
-  GPR_ASSERT(grpc_call_writes_done(call, halfclose_tag) == GRPC_CALL_OK);
+  GPR_ASSERT(grpc_call_writes_done_old(call, halfclose_tag) == GRPC_CALL_OK);
   ev = grpc_completion_queue_pluck(cq, halfclose_tag, gpr_inf_future);
   grpc_event_finish(ev);
   // start read metadata
@@ -146,7 +147,7 @@
   ev = grpc_completion_queue_pluck(cq, metadata_read_tag, gpr_inf_future);
   grpc_event_finish(ev);
   // start read
-  GPR_ASSERT(grpc_call_start_read(call, read_tag) == GRPC_CALL_OK);
+  GPR_ASSERT(grpc_call_start_read_old(call, read_tag) == GRPC_CALL_OK);
   ev = grpc_completion_queue_pluck(cq, read_tag, gpr_inf_future);
   if (ev->data.read) {
     if (!DeserializeProto(ev->data.read, result)) {
@@ -167,7 +168,7 @@
     const RpcMethod &method, ClientContext *context,
     const google::protobuf::Message *request,
     google::protobuf::Message *result) {
-  grpc_call *call = grpc_channel_create_call(
+  grpc_call *call = grpc_channel_create_call_old(
       c_channel_, method.name(), target_.c_str(), context->RawDeadline());
   context->set_call(call);
   grpc_completion_queue *cq = grpc_completion_queue_create();
diff --git a/src/cpp/server/async_server.cc b/src/cpp/server/async_server.cc
index d576201..86faa07 100644
--- a/src/cpp/server/async_server.cc
+++ b/src/cpp/server/async_server.cc
@@ -72,7 +72,7 @@
     return;
   }
   lock.unlock();
-  grpc_call_error err = grpc_server_request_call(server_, nullptr);
+  grpc_call_error err = grpc_server_request_call_old(server_, nullptr);
   GPR_ASSERT(err == GRPC_CALL_OK);
 }
 
diff --git a/src/cpp/server/async_server_context.cc b/src/cpp/server/async_server_context.cc
index 9295811..886e794 100644
--- a/src/cpp/server/async_server_context.cc
+++ b/src/cpp/server/async_server_context.cc
@@ -53,14 +53,15 @@
 AsyncServerContext::~AsyncServerContext() { grpc_call_destroy(call_); }
 
 void AsyncServerContext::Accept(grpc_completion_queue *cq) {
-  GPR_ASSERT(grpc_call_server_accept(call_, cq, this) == GRPC_CALL_OK);
-  GPR_ASSERT(grpc_call_server_end_initial_metadata(call_, 0) == GRPC_CALL_OK);
+  GPR_ASSERT(grpc_call_server_accept_old(call_, cq, this) == GRPC_CALL_OK);
+  GPR_ASSERT(grpc_call_server_end_initial_metadata_old(call_, GRPC_WRITE_BUFFER_HINT) ==
+             GRPC_CALL_OK);
 }
 
 bool AsyncServerContext::StartRead(google::protobuf::Message *request) {
   GPR_ASSERT(request);
   request_ = request;
-  grpc_call_error err = grpc_call_start_read(call_, this);
+  grpc_call_error err = grpc_call_start_read_old(call_, this);
   return err == GRPC_CALL_OK;
 }
 
@@ -70,13 +71,13 @@
   if (!SerializeProto(response, &buffer)) {
     return false;
   }
-  grpc_call_error err = grpc_call_start_write(call_, buffer, this, flags);
+  grpc_call_error err = grpc_call_start_write_old(call_, buffer, this, flags);
   grpc_byte_buffer_destroy(buffer);
   return err == GRPC_CALL_OK;
 }
 
 bool AsyncServerContext::StartWriteStatus(const Status &status) {
-  grpc_call_error err = grpc_call_start_write_status(
+  grpc_call_error err = grpc_call_start_write_status_old(
       call_, static_cast<grpc_status_code>(status.code()),
       status.details().empty() ? nullptr
                                : const_cast<char *>(status.details().c_str()),
diff --git a/src/cpp/server/server.cc b/src/cpp/server/server.cc
index 193688e..1abdf70 100644
--- a/src/cpp/server/server.cc
+++ b/src/cpp/server/server.cc
@@ -111,7 +111,7 @@
 
 void Server::AllowOneRpc() {
   GPR_ASSERT(started_);
-  grpc_call_error err = grpc_server_request_call(server_, nullptr);
+  grpc_call_error err = grpc_server_request_call_old(server_, nullptr);
   GPR_ASSERT(err == GRPC_CALL_OK);
 }
 
diff --git a/src/cpp/server/server_rpc_handler.cc b/src/cpp/server/server_rpc_handler.cc
index 061ac1c..bf02de8 100644
--- a/src/cpp/server/server_rpc_handler.cc
+++ b/src/cpp/server/server_rpc_handler.cc
@@ -77,7 +77,7 @@
 
     if (status.IsOk()) {
       // Send the response if we get an ok status.
-      async_server_context_->StartWrite(*response, 0);
+      async_server_context_->StartWrite(*response, GRPC_WRITE_BUFFER_HINT);
       type = WaitForNextEvent();
       if (type != CompletionQueue::SERVER_WRITE_OK) {
         status = Status(StatusCode::INTERNAL, "Error writing response.");
diff --git a/src/cpp/server/thread_pool.cc b/src/cpp/server/thread_pool.cc
index a46d4c6..2027959 100644
--- a/src/cpp/server/thread_pool.cc
+++ b/src/cpp/server/thread_pool.cc
@@ -41,7 +41,10 @@
       for (;;) {
         std::unique_lock<std::mutex> lock(mu_);
         // Wait until work is available or we are shutting down.
-        cv_.wait(lock, [=]() { return shutdown_ || !callbacks_.empty(); });
+        auto have_work = [=]() { return shutdown_ || !callbacks_.empty(); };
+        if (!have_work()) {
+          cv_.wait(lock, have_work);
+        }
         // Drain callbacks before considering shutdown to ensure all work
         // gets completed.
         if (!callbacks_.empty()) {
@@ -71,7 +74,7 @@
 void ThreadPool::ScheduleCallback(const std::function<void()> &callback) {
   std::lock_guard<std::mutex> lock(mu_);
   callbacks_.push(callback);
-  cv_.notify_all();
+  cv_.notify_one();
 }
 
 }  // namespace grpc
diff --git a/src/cpp/stream/stream_context.cc b/src/cpp/stream/stream_context.cc
index edb2fc5..e4f344d 100644
--- a/src/cpp/stream/stream_context.cc
+++ b/src/cpp/stream/stream_context.cc
@@ -80,22 +80,22 @@
   if (is_client_) {
     // TODO(yangg) handle metadata send path
     int flag = buffered ? GRPC_WRITE_BUFFER_HINT : 0;
-    grpc_call_error error = grpc_call_invoke(
+    grpc_call_error error = grpc_call_invoke_old(
         call(), cq(), client_metadata_read_tag(), finished_tag(), flag);
     GPR_ASSERT(GRPC_CALL_OK == error);
   } else {
     // TODO(yangg) metadata needs to be added before accept
     // TODO(yangg) correctly set flag to accept
-    GPR_ASSERT(grpc_call_server_accept(call(), cq(), finished_tag()) ==
+    GPR_ASSERT(grpc_call_server_accept_old(call(), cq(), finished_tag()) ==
                GRPC_CALL_OK);
-    GPR_ASSERT(grpc_call_server_end_initial_metadata(call(), 0) ==
+    GPR_ASSERT(grpc_call_server_end_initial_metadata_old(call(), 0) ==
                GRPC_CALL_OK);
   }
 }
 
 bool StreamContext::Read(google::protobuf::Message *msg) {
   // TODO(yangg) check peer_halfclosed_ here for possible early return.
-  grpc_call_error err = grpc_call_start_read(call(), read_tag());
+  grpc_call_error err = grpc_call_start_read_old(call(), read_tag());
   GPR_ASSERT(err == GRPC_CALL_OK);
   grpc_event *read_ev =
       grpc_completion_queue_pluck(cq(), read_tag(), gpr_inf_future);
@@ -129,7 +129,7 @@
     }
     int flag = is_last ? GRPC_WRITE_BUFFER_HINT : 0;
     grpc_call_error err =
-        grpc_call_start_write(call(), out_buf, write_tag(), flag);
+        grpc_call_start_write_old(call(), out_buf, write_tag(), flag);
     grpc_byte_buffer_destroy(out_buf);
     GPR_ASSERT(err == GRPC_CALL_OK);
 
@@ -140,7 +140,7 @@
     grpc_event_finish(ev);
   }
   if (ret && is_last) {
-    grpc_call_error err = grpc_call_writes_done(call(), halfclose_tag());
+    grpc_call_error err = grpc_call_writes_done_old(call(), halfclose_tag());
     GPR_ASSERT(err == GRPC_CALL_OK);
     ev = grpc_completion_queue_pluck(cq(), halfclose_tag(), gpr_inf_future);
     GPR_ASSERT(ev->type == GRPC_FINISH_ACCEPTED);
diff --git a/src/csharp/.gitignore b/src/csharp/.gitignore
new file mode 100644
index 0000000..dbf38f3
--- /dev/null
+++ b/src/csharp/.gitignore
@@ -0,0 +1,2 @@
+*.userprefs
+test-results
diff --git a/src/csharp/Grpc.sln b/src/csharp/Grpc.sln
new file mode 100644
index 0000000..5890617
--- /dev/null
+++ b/src/csharp/Grpc.sln
@@ -0,0 +1,38 @@
+

+Microsoft Visual Studio Solution File, Format Version 11.00

+# Visual Studio 2010

+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcDemo", "GrpcDemo\GrpcDemo.csproj", "{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}"

+EndProject

+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcApi", "GrpcApi\GrpcApi.csproj", "{7DC1433E-3225-42C7-B7EA-546D56E27A4B}"

+EndProject

+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcCore", "GrpcCore\GrpcCore.csproj", "{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}"

+EndProject

+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcCoreTests", "GrpcCoreTests\GrpcCoreTests.csproj", "{86EC5CB4-4EA2-40A2-8057-86542A0353BB}"

+EndProject

+Global

+	GlobalSection(SolutionConfigurationPlatforms) = preSolution

+		Debug|x86 = Debug|x86

+		Release|x86 = Release|x86

+	EndGlobalSection

+	GlobalSection(ProjectConfigurationPlatforms) = postSolution

+		{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Debug|x86.ActiveCfg = Debug|x86

+		{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Debug|x86.Build.0 = Debug|x86

+		{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Release|x86.ActiveCfg = Release|x86

+		{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Release|x86.Build.0 = Release|x86

+		{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Debug|x86.ActiveCfg = Debug|Any CPU

+		{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Debug|x86.Build.0 = Debug|Any CPU

+		{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Release|x86.ActiveCfg = Release|Any CPU

+		{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Release|x86.Build.0 = Release|Any CPU

+		{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Debug|x86.ActiveCfg = Debug|Any CPU

+		{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Debug|x86.Build.0 = Debug|Any CPU

+		{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Release|x86.ActiveCfg = Release|Any CPU

+		{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Release|x86.Build.0 = Release|Any CPU

+		{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Debug|x86.ActiveCfg = Debug|Any CPU

+		{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Debug|x86.Build.0 = Debug|Any CPU

+		{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Release|x86.ActiveCfg = Release|Any CPU

+		{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Release|x86.Build.0 = Release|Any CPU

+	EndGlobalSection

+	GlobalSection(MonoDevelopProperties) = preSolution

+		StartupItem = GrpcDemo\GrpcDemo.csproj

+	EndGlobalSection

+EndGlobal

diff --git a/src/csharp/GrpcApi/.gitignore b/src/csharp/GrpcApi/.gitignore
new file mode 100644
index 0000000..2cc8cca
--- /dev/null
+++ b/src/csharp/GrpcApi/.gitignore
@@ -0,0 +1,2 @@
+test-results
+bin
diff --git a/src/csharp/GrpcApi/DummyMathServiceClient.cs b/src/csharp/GrpcApi/DummyMathServiceClient.cs
new file mode 100644
index 0000000..6799109
--- /dev/null
+++ b/src/csharp/GrpcApi/DummyMathServiceClient.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+
+namespace math
+{
+//	/// <summary>
+//	/// Dummy local implementation of math service.
+//	/// </summary>
+//	public class DummyMathServiceClient : IMathServiceClient
+//	{
+//		public DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken))
+//		{
+//			// TODO: cancellation...
+//			return DivInternal(args);
+//		}
+//		
+//		public Task<DivReply> DivAsync(DivArgs args, CancellationToken token = default(CancellationToken))
+//		{
+//			return Task.Factory.StartNew(() => DivInternal(args), token);
+//		}
+//
+//		public IObservable<Num> Fib(FibArgs args, CancellationToken token = default(CancellationToken))
+//		{
+//			if (args.Limit > 0)
+//			{
+//				// TODO: cancellation
+//				return FibInternal(args.Limit).ToObservable();
+//			}
+//
+//			throw new NotImplementedException("Not implemented yet");
+//		}
+//
+//		public Task<Num> Sum(IObservable<Num> inputs, CancellationToken token = default(CancellationToken))
+//		{
+//			// TODO: implement
+//			inputs = null;
+//			return Task.Factory.StartNew(() => Num.CreateBuilder().Build(), token);
+//		}
+//
+//		public IObservable<DivReply> DivMany(IObservable<DivArgs> inputs, CancellationToken token = default(CancellationToken))
+//		{
+//			// TODO: implement
+//			inputs = null;
+//			return new List<DivReply> { }.ToObservable ();
+//		}
+//
+//
+//		DivReply DivInternal(DivArgs args)
+//		{
+//			long quotient = args.Dividend / args.Divisor;
+//			long remainder = args.Dividend % args.Divisor;
+//			return new DivReply.Builder{ Quotient = quotient, Remainder = remainder }.Build();
+//		}
+//
+//		IEnumerable<Num> FibInternal(long n)
+//		{
+//			long a = 0;
+//			yield return new Num.Builder{Num_=a}.Build();
+//
+//			long b = 1;
+//			for (long i = 0; i < n - 1; i++)
+//			{
+//				long temp = a;
+//				a = b;
+//				b = temp + b;
+//				yield return new Num.Builder{Num_=a}.Build();
+//			}
+//		}
+//	}
+}
+
diff --git a/src/csharp/GrpcApi/Examples.cs b/src/csharp/GrpcApi/Examples.cs
new file mode 100644
index 0000000..d45b702
--- /dev/null
+++ b/src/csharp/GrpcApi/Examples.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+
+namespace math
+{
+	public class Examples
+	{
+		public static void DivExample(IMathServiceClient stub)
+		{
+			DivReply result = stub.Div(new DivArgs.Builder { Dividend = 10, Divisor = 3 }.Build());
+			Console.WriteLine("Div Result: " + result);
+		}
+
+		public static void DivAsyncExample(IMathServiceClient stub)
+		{
+			Task<DivReply> call = stub.DivAsync(new DivArgs.Builder { Dividend = 4, Divisor = 5 }.Build());
+			DivReply result = call.Result;
+			Console.WriteLine(result);
+		}
+
+		public static void DivAsyncWithCancellationExample(IMathServiceClient stub)
+		{
+			Task<DivReply> call = stub.DivAsync(new DivArgs.Builder { Dividend = 4, Divisor = 5 }.Build());
+			DivReply result = call.Result;
+			Console.WriteLine(result);
+		}
+
+		public static void FibExample(IMathServiceClient stub)
+		{
+            var recorder = new RecordingObserver<Num>();
+            stub.Fib(new FibArgs.Builder { Limit = 5 }.Build(), recorder);
+
+			List<Num> numbers = recorder.ToList().Result;
+            Console.WriteLine("Fib Result: " + string.Join("|", recorder.ToList().Result));
+		}
+
+		public static void SumExample(IMathServiceClient stub)
+		{
+			List<Num> numbers = new List<Num>{new Num.Builder { Num_ = 1 }.Build(), 
+				new Num.Builder { Num_ = 2 }.Build(),
+				new Num.Builder { Num_ = 3 }.Build()};
+			
+            var res = stub.Sum();
+            foreach (var num in numbers) {
+                res.Inputs.OnNext(num);
+            }
+            res.Inputs.OnCompleted();
+
+			Console.WriteLine("Sum Result: " + res.Task.Result);
+		}
+
+		public static void DivManyExample(IMathServiceClient stub)
+		{
+			List<DivArgs> divArgsList = new List<DivArgs>{
+				new DivArgs.Builder { Dividend = 10, Divisor = 3 }.Build(),
+				new DivArgs.Builder { Dividend = 100, Divisor = 21 }.Build(),
+				new DivArgs.Builder { Dividend = 7, Divisor = 2 }.Build()
+			};
+
+            var recorder = new RecordingObserver<DivReply>();
+			
+            var inputs = stub.DivMany(recorder);
+            foreach (var input in divArgsList)
+            {
+                inputs.OnNext(input);
+            }
+            inputs.OnCompleted();
+
+			Console.WriteLine("DivMany Result: " + string.Join("|", recorder.ToList().Result));
+		}
+
+		public static void DependendRequestsExample(IMathServiceClient stub)
+		{
+			var numberList = new List<Num>
+			{ new Num.Builder{ Num_ = 1 }.Build(), 
+				new Num.Builder{ Num_ = 2 }.Build(), new Num.Builder{ Num_ = 3 }.Build()
+			};
+
+			numberList.ToObservable();
+
+			//IObserver<Num> numbers;
+			//Task<Num> call = stub.Sum(out numbers);            
+			//foreach (var num in numberList)
+			//{
+			//	numbers.OnNext(num);
+			//}
+			//numbers.OnCompleted();
+
+			//Num sum = call.Result;
+
+			//DivReply result = stub.Div(new DivArgs.Builder { Dividend = sum.Num_, Divisor = numberList.Count }.Build());
+		}
+	}
+}
+
diff --git a/src/csharp/GrpcApi/GrpcApi.csproj b/src/csharp/GrpcApi/GrpcApi.csproj
new file mode 100644
index 0000000..d037782
--- /dev/null
+++ b/src/csharp/GrpcApi/GrpcApi.csproj
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{7DC1433E-3225-42C7-B7EA-546D56E27A4B}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <RootNamespace>GrpcApi</RootNamespace>
+    <AssemblyName>GrpcApi</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Reactive.Linq, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Data.Linq" />
+    <Reference Include="System.Reactive.Interfaces, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Reactive.Core, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Google.ProtocolBuffers">
+      <HintPath>..\lib\Google.ProtocolBuffers.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Examples.cs" />
+    <Compile Include="IMathServiceClient.cs" />
+    <Compile Include="Math.cs" />
+    <Compile Include="DummyMathServiceClient.cs" />
+    <Compile Include="MathServiceClientStub.cs" />
+    <Compile Include="RecordingObserver.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <ProjectReference Include="..\GrpcCore\GrpcCore.csproj">
+      <Project>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</Project>
+      <Name>GrpcCore</Name>
+    </ProjectReference>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/csharp/GrpcApi/IMathServiceClient.cs b/src/csharp/GrpcApi/IMathServiceClient.cs
new file mode 100644
index 0000000..51385a3
--- /dev/null
+++ b/src/csharp/GrpcApi/IMathServiceClient.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using Google.GRPC.Core;
+
+namespace math
+{
+	/// <summary>
+	/// Hand-written stub for MathService defined in math.proto.
+	/// This code will be generated by gRPC codegen in the future.
+	/// </summary>
+	public interface IMathServiceClient
+	{
+		DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken));
+
+		Task<DivReply> DivAsync(DivArgs args, CancellationToken token = default(CancellationToken));
+
+		Task Fib(FibArgs args, IObserver<Num> outputs, CancellationToken token = default(CancellationToken));
+
+		ClientStreamingAsyncResult<Num, Num> Sum(CancellationToken token = default(CancellationToken));
+
+		IObserver<DivArgs> DivMany(IObserver<DivReply> outputs, CancellationToken token = default(CancellationToken));
+	}
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcApi/Math.cs b/src/csharp/GrpcApi/Math.cs
new file mode 100644
index 0000000..2d70033
--- /dev/null
+++ b/src/csharp/GrpcApi/Math.cs
@@ -0,0 +1,1531 @@
+// Generated by ProtoGen, Version=2.4.1.521, Culture=neutral, PublicKeyToken=17b3b1f090c3ea48.  DO NOT EDIT!
+#pragma warning disable 1591, 0612, 3021
+#region Designer generated code
+
+using pb = global::Google.ProtocolBuffers;
+using pbc = global::Google.ProtocolBuffers.Collections;
+using pbd = global::Google.ProtocolBuffers.Descriptors;
+using scg = global::System.Collections.Generic;
+namespace math {
+  
+  namespace Proto {
+    
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    public static partial class Math {
+    
+      #region Extension registration
+      public static void RegisterAllExtensions(pb::ExtensionRegistry registry) {
+      }
+      #endregion
+      #region Static variables
+      internal static pbd::MessageDescriptor internal__static_math_DivArgs__Descriptor;
+      internal static pb::FieldAccess.FieldAccessorTable<global::math.DivArgs, global::math.DivArgs.Builder> internal__static_math_DivArgs__FieldAccessorTable;
+      internal static pbd::MessageDescriptor internal__static_math_DivReply__Descriptor;
+      internal static pb::FieldAccess.FieldAccessorTable<global::math.DivReply, global::math.DivReply.Builder> internal__static_math_DivReply__FieldAccessorTable;
+      internal static pbd::MessageDescriptor internal__static_math_FibArgs__Descriptor;
+      internal static pb::FieldAccess.FieldAccessorTable<global::math.FibArgs, global::math.FibArgs.Builder> internal__static_math_FibArgs__FieldAccessorTable;
+      internal static pbd::MessageDescriptor internal__static_math_Num__Descriptor;
+      internal static pb::FieldAccess.FieldAccessorTable<global::math.Num, global::math.Num.Builder> internal__static_math_Num__FieldAccessorTable;
+      internal static pbd::MessageDescriptor internal__static_math_FibReply__Descriptor;
+      internal static pb::FieldAccess.FieldAccessorTable<global::math.FibReply, global::math.FibReply.Builder> internal__static_math_FibReply__FieldAccessorTable;
+      #endregion
+      #region Descriptor
+      public static pbd::FileDescriptor Descriptor {
+        get { return descriptor; }
+      }
+      private static pbd::FileDescriptor descriptor;
+      
+      static Math() {
+        byte[] descriptorData = global::System.Convert.FromBase64String(
+            string.Concat(
+              "CgptYXRoLnByb3RvEgRtYXRoIiwKB0RpdkFyZ3MSEAoIZGl2aWRlbmQYASAB", 
+              "KAMSDwoHZGl2aXNvchgCIAEoAyIvCghEaXZSZXBseRIQCghxdW90aWVudBgB", 
+              "IAEoAxIRCglyZW1haW5kZXIYAiABKAMiGAoHRmliQXJncxINCgVsaW1pdBgB", 
+              "IAEoAyISCgNOdW0SCwoDbnVtGAEgASgDIhkKCEZpYlJlcGx5Eg0KBWNvdW50", 
+              "GAEgASgDMqQBCgRNYXRoEiYKA0RpdhINLm1hdGguRGl2QXJncxoOLm1hdGgu", 
+              "RGl2UmVwbHkiABIuCgdEaXZNYW55Eg0ubWF0aC5EaXZBcmdzGg4ubWF0aC5E", 
+              "aXZSZXBseSIAKAEwARIjCgNGaWISDS5tYXRoLkZpYkFyZ3MaCS5tYXRoLk51", 
+            "bSIAMAESHwoDU3VtEgkubWF0aC5OdW0aCS5tYXRoLk51bSIAKAE="));
+        pbd::FileDescriptor.InternalDescriptorAssigner assigner = delegate(pbd::FileDescriptor root) {
+          descriptor = root;
+          internal__static_math_DivArgs__Descriptor = Descriptor.MessageTypes[0];
+          internal__static_math_DivArgs__FieldAccessorTable = 
+              new pb::FieldAccess.FieldAccessorTable<global::math.DivArgs, global::math.DivArgs.Builder>(internal__static_math_DivArgs__Descriptor,
+                  new string[] { "Dividend", "Divisor", });
+          internal__static_math_DivReply__Descriptor = Descriptor.MessageTypes[1];
+          internal__static_math_DivReply__FieldAccessorTable = 
+              new pb::FieldAccess.FieldAccessorTable<global::math.DivReply, global::math.DivReply.Builder>(internal__static_math_DivReply__Descriptor,
+                  new string[] { "Quotient", "Remainder", });
+          internal__static_math_FibArgs__Descriptor = Descriptor.MessageTypes[2];
+          internal__static_math_FibArgs__FieldAccessorTable = 
+              new pb::FieldAccess.FieldAccessorTable<global::math.FibArgs, global::math.FibArgs.Builder>(internal__static_math_FibArgs__Descriptor,
+                  new string[] { "Limit", });
+          internal__static_math_Num__Descriptor = Descriptor.MessageTypes[3];
+          internal__static_math_Num__FieldAccessorTable = 
+              new pb::FieldAccess.FieldAccessorTable<global::math.Num, global::math.Num.Builder>(internal__static_math_Num__Descriptor,
+                  new string[] { "Num_", });
+          internal__static_math_FibReply__Descriptor = Descriptor.MessageTypes[4];
+          internal__static_math_FibReply__FieldAccessorTable = 
+              new pb::FieldAccess.FieldAccessorTable<global::math.FibReply, global::math.FibReply.Builder>(internal__static_math_FibReply__Descriptor,
+                  new string[] { "Count", });
+          pb::ExtensionRegistry registry = pb::ExtensionRegistry.CreateInstance();
+          RegisterAllExtensions(registry);
+          return registry;
+        };
+        pbd::FileDescriptor.InternalBuildGeneratedFileFrom(descriptorData,
+            new pbd::FileDescriptor[] {
+            }, assigner);
+      }
+      #endregion
+      
+    }
+  }
+  #region Messages
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class DivArgs : pb::GeneratedMessage<DivArgs, DivArgs.Builder> {
+    private DivArgs() { }
+    private static readonly DivArgs defaultInstance = new DivArgs().MakeReadOnly();
+    private static readonly string[] _divArgsFieldNames = new string[] { "dividend", "divisor" };
+    private static readonly uint[] _divArgsFieldTags = new uint[] { 8, 16 };
+    public static DivArgs DefaultInstance {
+      get { return defaultInstance; }
+    }
+    
+    public override DivArgs DefaultInstanceForType {
+      get { return DefaultInstance; }
+    }
+    
+    protected override DivArgs ThisMessage {
+      get { return this; }
+    }
+    
+    public static pbd::MessageDescriptor Descriptor {
+      get { return global::math.Proto.Math.internal__static_math_DivArgs__Descriptor; }
+    }
+    
+    protected override pb::FieldAccess.FieldAccessorTable<DivArgs, DivArgs.Builder> InternalFieldAccessors {
+      get { return global::math.Proto.Math.internal__static_math_DivArgs__FieldAccessorTable; }
+    }
+    
+    public const int DividendFieldNumber = 1;
+    private bool hasDividend;
+    private long dividend_;
+    public bool HasDividend {
+      get { return hasDividend; }
+    }
+    public long Dividend {
+      get { return dividend_; }
+    }
+    
+    public const int DivisorFieldNumber = 2;
+    private bool hasDivisor;
+    private long divisor_;
+    public bool HasDivisor {
+      get { return hasDivisor; }
+    }
+    public long Divisor {
+      get { return divisor_; }
+    }
+    
+    public override bool IsInitialized {
+      get {
+        return true;
+      }
+    }
+    
+    public override void WriteTo(pb::ICodedOutputStream output) {
+      int size = SerializedSize;
+      string[] field_names = _divArgsFieldNames;
+      if (hasDividend) {
+        output.WriteInt64(1, field_names[0], Dividend);
+      }
+      if (hasDivisor) {
+        output.WriteInt64(2, field_names[1], Divisor);
+      }
+      UnknownFields.WriteTo(output);
+    }
+    
+    private int memoizedSerializedSize = -1;
+    public override int SerializedSize {
+      get {
+        int size = memoizedSerializedSize;
+        if (size != -1) return size;
+        
+        size = 0;
+        if (hasDividend) {
+          size += pb::CodedOutputStream.ComputeInt64Size(1, Dividend);
+        }
+        if (hasDivisor) {
+          size += pb::CodedOutputStream.ComputeInt64Size(2, Divisor);
+        }
+        size += UnknownFields.SerializedSize;
+        memoizedSerializedSize = size;
+        return size;
+      }
+    }
+    
+    public static DivArgs ParseFrom(pb::ByteString data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static DivArgs ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static DivArgs ParseFrom(byte[] data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static DivArgs ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static DivArgs ParseFrom(global::System.IO.Stream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static DivArgs ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    public static DivArgs ParseDelimitedFrom(global::System.IO.Stream input) {
+      return CreateBuilder().MergeDelimitedFrom(input).BuildParsed();
+    }
+    public static DivArgs ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed();
+    }
+    public static DivArgs ParseFrom(pb::ICodedInputStream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static DivArgs ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    private DivArgs MakeReadOnly() {
+      return this;
+    }
+    
+    public static Builder CreateBuilder() { return new Builder(); }
+    public override Builder ToBuilder() { return CreateBuilder(this); }
+    public override Builder CreateBuilderForType() { return new Builder(); }
+    public static Builder CreateBuilder(DivArgs prototype) {
+      return new Builder(prototype);
+    }
+    
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    public sealed partial class Builder : pb::GeneratedBuilder<DivArgs, Builder> {
+      protected override Builder ThisBuilder {
+        get { return this; }
+      }
+      public Builder() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+      }
+      internal Builder(DivArgs cloneFrom) {
+        result = cloneFrom;
+        resultIsReadOnly = true;
+      }
+      
+      private bool resultIsReadOnly;
+      private DivArgs result;
+      
+      private DivArgs PrepareBuilder() {
+        if (resultIsReadOnly) {
+          DivArgs original = result;
+          result = new DivArgs();
+          resultIsReadOnly = false;
+          MergeFrom(original);
+        }
+        return result;
+      }
+      
+      public override bool IsInitialized {
+        get { return result.IsInitialized; }
+      }
+      
+      protected override DivArgs MessageBeingBuilt {
+        get { return PrepareBuilder(); }
+      }
+      
+      public override Builder Clear() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+        return this;
+      }
+      
+      public override Builder Clone() {
+        if (resultIsReadOnly) {
+          return new Builder(result);
+        } else {
+          return new Builder().MergeFrom(result);
+        }
+      }
+      
+      public override pbd::MessageDescriptor DescriptorForType {
+        get { return global::math.DivArgs.Descriptor; }
+      }
+      
+      public override DivArgs DefaultInstanceForType {
+        get { return global::math.DivArgs.DefaultInstance; }
+      }
+      
+      public override DivArgs BuildPartial() {
+        if (resultIsReadOnly) {
+          return result;
+        }
+        resultIsReadOnly = true;
+        return result.MakeReadOnly();
+      }
+      
+      public override Builder MergeFrom(pb::IMessage other) {
+        if (other is DivArgs) {
+          return MergeFrom((DivArgs) other);
+        } else {
+          base.MergeFrom(other);
+          return this;
+        }
+      }
+      
+      public override Builder MergeFrom(DivArgs other) {
+        if (other == global::math.DivArgs.DefaultInstance) return this;
+        PrepareBuilder();
+        if (other.HasDividend) {
+          Dividend = other.Dividend;
+        }
+        if (other.HasDivisor) {
+          Divisor = other.Divisor;
+        }
+        this.MergeUnknownFields(other.UnknownFields);
+        return this;
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input) {
+        return MergeFrom(input, pb::ExtensionRegistry.Empty);
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+        PrepareBuilder();
+        pb::UnknownFieldSet.Builder unknownFields = null;
+        uint tag;
+        string field_name;
+        while (input.ReadTag(out tag, out field_name)) {
+          if(tag == 0 && field_name != null) {
+            int field_ordinal = global::System.Array.BinarySearch(_divArgsFieldNames, field_name, global::System.StringComparer.Ordinal);
+            if(field_ordinal >= 0)
+              tag = _divArgsFieldTags[field_ordinal];
+            else {
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              continue;
+            }
+          }
+          switch (tag) {
+            case 0: {
+              throw pb::InvalidProtocolBufferException.InvalidTag();
+            }
+            default: {
+              if (pb::WireFormat.IsEndGroupTag(tag)) {
+                if (unknownFields != null) {
+                  this.UnknownFields = unknownFields.Build();
+                }
+                return this;
+              }
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              break;
+            }
+            case 8: {
+              result.hasDividend = input.ReadInt64(ref result.dividend_);
+              break;
+            }
+            case 16: {
+              result.hasDivisor = input.ReadInt64(ref result.divisor_);
+              break;
+            }
+          }
+        }
+        
+        if (unknownFields != null) {
+          this.UnknownFields = unknownFields.Build();
+        }
+        return this;
+      }
+      
+      
+      public bool HasDividend {
+        get { return result.hasDividend; }
+      }
+      public long Dividend {
+        get { return result.Dividend; }
+        set { SetDividend(value); }
+      }
+      public Builder SetDividend(long value) {
+        PrepareBuilder();
+        result.hasDividend = true;
+        result.dividend_ = value;
+        return this;
+      }
+      public Builder ClearDividend() {
+        PrepareBuilder();
+        result.hasDividend = false;
+        result.dividend_ = 0L;
+        return this;
+      }
+      
+      public bool HasDivisor {
+        get { return result.hasDivisor; }
+      }
+      public long Divisor {
+        get { return result.Divisor; }
+        set { SetDivisor(value); }
+      }
+      public Builder SetDivisor(long value) {
+        PrepareBuilder();
+        result.hasDivisor = true;
+        result.divisor_ = value;
+        return this;
+      }
+      public Builder ClearDivisor() {
+        PrepareBuilder();
+        result.hasDivisor = false;
+        result.divisor_ = 0L;
+        return this;
+      }
+    }
+    static DivArgs() {
+      object.ReferenceEquals(global::math.Proto.Math.Descriptor, null);
+    }
+  }
+  
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class DivReply : pb::GeneratedMessage<DivReply, DivReply.Builder> {
+    private DivReply() { }
+    private static readonly DivReply defaultInstance = new DivReply().MakeReadOnly();
+    private static readonly string[] _divReplyFieldNames = new string[] { "quotient", "remainder" };
+    private static readonly uint[] _divReplyFieldTags = new uint[] { 8, 16 };
+    public static DivReply DefaultInstance {
+      get { return defaultInstance; }
+    }
+    
+    public override DivReply DefaultInstanceForType {
+      get { return DefaultInstance; }
+    }
+    
+    protected override DivReply ThisMessage {
+      get { return this; }
+    }
+    
+    public static pbd::MessageDescriptor Descriptor {
+      get { return global::math.Proto.Math.internal__static_math_DivReply__Descriptor; }
+    }
+    
+    protected override pb::FieldAccess.FieldAccessorTable<DivReply, DivReply.Builder> InternalFieldAccessors {
+      get { return global::math.Proto.Math.internal__static_math_DivReply__FieldAccessorTable; }
+    }
+    
+    public const int QuotientFieldNumber = 1;
+    private bool hasQuotient;
+    private long quotient_;
+    public bool HasQuotient {
+      get { return hasQuotient; }
+    }
+    public long Quotient {
+      get { return quotient_; }
+    }
+    
+    public const int RemainderFieldNumber = 2;
+    private bool hasRemainder;
+    private long remainder_;
+    public bool HasRemainder {
+      get { return hasRemainder; }
+    }
+    public long Remainder {
+      get { return remainder_; }
+    }
+    
+    public override bool IsInitialized {
+      get {
+        return true;
+      }
+    }
+    
+    public override void WriteTo(pb::ICodedOutputStream output) {
+      int size = SerializedSize;
+      string[] field_names = _divReplyFieldNames;
+      if (hasQuotient) {
+        output.WriteInt64(1, field_names[0], Quotient);
+      }
+      if (hasRemainder) {
+        output.WriteInt64(2, field_names[1], Remainder);
+      }
+      UnknownFields.WriteTo(output);
+    }
+    
+    private int memoizedSerializedSize = -1;
+    public override int SerializedSize {
+      get {
+        int size = memoizedSerializedSize;
+        if (size != -1) return size;
+        
+        size = 0;
+        if (hasQuotient) {
+          size += pb::CodedOutputStream.ComputeInt64Size(1, Quotient);
+        }
+        if (hasRemainder) {
+          size += pb::CodedOutputStream.ComputeInt64Size(2, Remainder);
+        }
+        size += UnknownFields.SerializedSize;
+        memoizedSerializedSize = size;
+        return size;
+      }
+    }
+    
+    public static DivReply ParseFrom(pb::ByteString data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static DivReply ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static DivReply ParseFrom(byte[] data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static DivReply ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static DivReply ParseFrom(global::System.IO.Stream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static DivReply ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    public static DivReply ParseDelimitedFrom(global::System.IO.Stream input) {
+      return CreateBuilder().MergeDelimitedFrom(input).BuildParsed();
+    }
+    public static DivReply ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed();
+    }
+    public static DivReply ParseFrom(pb::ICodedInputStream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static DivReply ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    private DivReply MakeReadOnly() {
+      return this;
+    }
+    
+    public static Builder CreateBuilder() { return new Builder(); }
+    public override Builder ToBuilder() { return CreateBuilder(this); }
+    public override Builder CreateBuilderForType() { return new Builder(); }
+    public static Builder CreateBuilder(DivReply prototype) {
+      return new Builder(prototype);
+    }
+    
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    public sealed partial class Builder : pb::GeneratedBuilder<DivReply, Builder> {
+      protected override Builder ThisBuilder {
+        get { return this; }
+      }
+      public Builder() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+      }
+      internal Builder(DivReply cloneFrom) {
+        result = cloneFrom;
+        resultIsReadOnly = true;
+      }
+      
+      private bool resultIsReadOnly;
+      private DivReply result;
+      
+      private DivReply PrepareBuilder() {
+        if (resultIsReadOnly) {
+          DivReply original = result;
+          result = new DivReply();
+          resultIsReadOnly = false;
+          MergeFrom(original);
+        }
+        return result;
+      }
+      
+      public override bool IsInitialized {
+        get { return result.IsInitialized; }
+      }
+      
+      protected override DivReply MessageBeingBuilt {
+        get { return PrepareBuilder(); }
+      }
+      
+      public override Builder Clear() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+        return this;
+      }
+      
+      public override Builder Clone() {
+        if (resultIsReadOnly) {
+          return new Builder(result);
+        } else {
+          return new Builder().MergeFrom(result);
+        }
+      }
+      
+      public override pbd::MessageDescriptor DescriptorForType {
+        get { return global::math.DivReply.Descriptor; }
+      }
+      
+      public override DivReply DefaultInstanceForType {
+        get { return global::math.DivReply.DefaultInstance; }
+      }
+      
+      public override DivReply BuildPartial() {
+        if (resultIsReadOnly) {
+          return result;
+        }
+        resultIsReadOnly = true;
+        return result.MakeReadOnly();
+      }
+      
+      public override Builder MergeFrom(pb::IMessage other) {
+        if (other is DivReply) {
+          return MergeFrom((DivReply) other);
+        } else {
+          base.MergeFrom(other);
+          return this;
+        }
+      }
+      
+      public override Builder MergeFrom(DivReply other) {
+        if (other == global::math.DivReply.DefaultInstance) return this;
+        PrepareBuilder();
+        if (other.HasQuotient) {
+          Quotient = other.Quotient;
+        }
+        if (other.HasRemainder) {
+          Remainder = other.Remainder;
+        }
+        this.MergeUnknownFields(other.UnknownFields);
+        return this;
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input) {
+        return MergeFrom(input, pb::ExtensionRegistry.Empty);
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+        PrepareBuilder();
+        pb::UnknownFieldSet.Builder unknownFields = null;
+        uint tag;
+        string field_name;
+        while (input.ReadTag(out tag, out field_name)) {
+          if(tag == 0 && field_name != null) {
+            int field_ordinal = global::System.Array.BinarySearch(_divReplyFieldNames, field_name, global::System.StringComparer.Ordinal);
+            if(field_ordinal >= 0)
+              tag = _divReplyFieldTags[field_ordinal];
+            else {
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              continue;
+            }
+          }
+          switch (tag) {
+            case 0: {
+              throw pb::InvalidProtocolBufferException.InvalidTag();
+            }
+            default: {
+              if (pb::WireFormat.IsEndGroupTag(tag)) {
+                if (unknownFields != null) {
+                  this.UnknownFields = unknownFields.Build();
+                }
+                return this;
+              }
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              break;
+            }
+            case 8: {
+              result.hasQuotient = input.ReadInt64(ref result.quotient_);
+              break;
+            }
+            case 16: {
+              result.hasRemainder = input.ReadInt64(ref result.remainder_);
+              break;
+            }
+          }
+        }
+        
+        if (unknownFields != null) {
+          this.UnknownFields = unknownFields.Build();
+        }
+        return this;
+      }
+      
+      
+      public bool HasQuotient {
+        get { return result.hasQuotient; }
+      }
+      public long Quotient {
+        get { return result.Quotient; }
+        set { SetQuotient(value); }
+      }
+      public Builder SetQuotient(long value) {
+        PrepareBuilder();
+        result.hasQuotient = true;
+        result.quotient_ = value;
+        return this;
+      }
+      public Builder ClearQuotient() {
+        PrepareBuilder();
+        result.hasQuotient = false;
+        result.quotient_ = 0L;
+        return this;
+      }
+      
+      public bool HasRemainder {
+        get { return result.hasRemainder; }
+      }
+      public long Remainder {
+        get { return result.Remainder; }
+        set { SetRemainder(value); }
+      }
+      public Builder SetRemainder(long value) {
+        PrepareBuilder();
+        result.hasRemainder = true;
+        result.remainder_ = value;
+        return this;
+      }
+      public Builder ClearRemainder() {
+        PrepareBuilder();
+        result.hasRemainder = false;
+        result.remainder_ = 0L;
+        return this;
+      }
+    }
+    static DivReply() {
+      object.ReferenceEquals(global::math.Proto.Math.Descriptor, null);
+    }
+  }
+  
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class FibArgs : pb::GeneratedMessage<FibArgs, FibArgs.Builder> {
+    private FibArgs() { }
+    private static readonly FibArgs defaultInstance = new FibArgs().MakeReadOnly();
+    private static readonly string[] _fibArgsFieldNames = new string[] { "limit" };
+    private static readonly uint[] _fibArgsFieldTags = new uint[] { 8 };
+    public static FibArgs DefaultInstance {
+      get { return defaultInstance; }
+    }
+    
+    public override FibArgs DefaultInstanceForType {
+      get { return DefaultInstance; }
+    }
+    
+    protected override FibArgs ThisMessage {
+      get { return this; }
+    }
+    
+    public static pbd::MessageDescriptor Descriptor {
+      get { return global::math.Proto.Math.internal__static_math_FibArgs__Descriptor; }
+    }
+    
+    protected override pb::FieldAccess.FieldAccessorTable<FibArgs, FibArgs.Builder> InternalFieldAccessors {
+      get { return global::math.Proto.Math.internal__static_math_FibArgs__FieldAccessorTable; }
+    }
+    
+    public const int LimitFieldNumber = 1;
+    private bool hasLimit;
+    private long limit_;
+    public bool HasLimit {
+      get { return hasLimit; }
+    }
+    public long Limit {
+      get { return limit_; }
+    }
+    
+    public override bool IsInitialized {
+      get {
+        return true;
+      }
+    }
+    
+    public override void WriteTo(pb::ICodedOutputStream output) {
+      int size = SerializedSize;
+      string[] field_names = _fibArgsFieldNames;
+      if (hasLimit) {
+        output.WriteInt64(1, field_names[0], Limit);
+      }
+      UnknownFields.WriteTo(output);
+    }
+    
+    private int memoizedSerializedSize = -1;
+    public override int SerializedSize {
+      get {
+        int size = memoizedSerializedSize;
+        if (size != -1) return size;
+        
+        size = 0;
+        if (hasLimit) {
+          size += pb::CodedOutputStream.ComputeInt64Size(1, Limit);
+        }
+        size += UnknownFields.SerializedSize;
+        memoizedSerializedSize = size;
+        return size;
+      }
+    }
+    
+    public static FibArgs ParseFrom(pb::ByteString data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static FibArgs ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static FibArgs ParseFrom(byte[] data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static FibArgs ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static FibArgs ParseFrom(global::System.IO.Stream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static FibArgs ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    public static FibArgs ParseDelimitedFrom(global::System.IO.Stream input) {
+      return CreateBuilder().MergeDelimitedFrom(input).BuildParsed();
+    }
+    public static FibArgs ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed();
+    }
+    public static FibArgs ParseFrom(pb::ICodedInputStream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static FibArgs ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    private FibArgs MakeReadOnly() {
+      return this;
+    }
+    
+    public static Builder CreateBuilder() { return new Builder(); }
+    public override Builder ToBuilder() { return CreateBuilder(this); }
+    public override Builder CreateBuilderForType() { return new Builder(); }
+    public static Builder CreateBuilder(FibArgs prototype) {
+      return new Builder(prototype);
+    }
+    
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    public sealed partial class Builder : pb::GeneratedBuilder<FibArgs, Builder> {
+      protected override Builder ThisBuilder {
+        get { return this; }
+      }
+      public Builder() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+      }
+      internal Builder(FibArgs cloneFrom) {
+        result = cloneFrom;
+        resultIsReadOnly = true;
+      }
+      
+      private bool resultIsReadOnly;
+      private FibArgs result;
+      
+      private FibArgs PrepareBuilder() {
+        if (resultIsReadOnly) {
+          FibArgs original = result;
+          result = new FibArgs();
+          resultIsReadOnly = false;
+          MergeFrom(original);
+        }
+        return result;
+      }
+      
+      public override bool IsInitialized {
+        get { return result.IsInitialized; }
+      }
+      
+      protected override FibArgs MessageBeingBuilt {
+        get { return PrepareBuilder(); }
+      }
+      
+      public override Builder Clear() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+        return this;
+      }
+      
+      public override Builder Clone() {
+        if (resultIsReadOnly) {
+          return new Builder(result);
+        } else {
+          return new Builder().MergeFrom(result);
+        }
+      }
+      
+      public override pbd::MessageDescriptor DescriptorForType {
+        get { return global::math.FibArgs.Descriptor; }
+      }
+      
+      public override FibArgs DefaultInstanceForType {
+        get { return global::math.FibArgs.DefaultInstance; }
+      }
+      
+      public override FibArgs BuildPartial() {
+        if (resultIsReadOnly) {
+          return result;
+        }
+        resultIsReadOnly = true;
+        return result.MakeReadOnly();
+      }
+      
+      public override Builder MergeFrom(pb::IMessage other) {
+        if (other is FibArgs) {
+          return MergeFrom((FibArgs) other);
+        } else {
+          base.MergeFrom(other);
+          return this;
+        }
+      }
+      
+      public override Builder MergeFrom(FibArgs other) {
+        if (other == global::math.FibArgs.DefaultInstance) return this;
+        PrepareBuilder();
+        if (other.HasLimit) {
+          Limit = other.Limit;
+        }
+        this.MergeUnknownFields(other.UnknownFields);
+        return this;
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input) {
+        return MergeFrom(input, pb::ExtensionRegistry.Empty);
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+        PrepareBuilder();
+        pb::UnknownFieldSet.Builder unknownFields = null;
+        uint tag;
+        string field_name;
+        while (input.ReadTag(out tag, out field_name)) {
+          if(tag == 0 && field_name != null) {
+            int field_ordinal = global::System.Array.BinarySearch(_fibArgsFieldNames, field_name, global::System.StringComparer.Ordinal);
+            if(field_ordinal >= 0)
+              tag = _fibArgsFieldTags[field_ordinal];
+            else {
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              continue;
+            }
+          }
+          switch (tag) {
+            case 0: {
+              throw pb::InvalidProtocolBufferException.InvalidTag();
+            }
+            default: {
+              if (pb::WireFormat.IsEndGroupTag(tag)) {
+                if (unknownFields != null) {
+                  this.UnknownFields = unknownFields.Build();
+                }
+                return this;
+              }
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              break;
+            }
+            case 8: {
+              result.hasLimit = input.ReadInt64(ref result.limit_);
+              break;
+            }
+          }
+        }
+        
+        if (unknownFields != null) {
+          this.UnknownFields = unknownFields.Build();
+        }
+        return this;
+      }
+      
+      
+      public bool HasLimit {
+        get { return result.hasLimit; }
+      }
+      public long Limit {
+        get { return result.Limit; }
+        set { SetLimit(value); }
+      }
+      public Builder SetLimit(long value) {
+        PrepareBuilder();
+        result.hasLimit = true;
+        result.limit_ = value;
+        return this;
+      }
+      public Builder ClearLimit() {
+        PrepareBuilder();
+        result.hasLimit = false;
+        result.limit_ = 0L;
+        return this;
+      }
+    }
+    static FibArgs() {
+      object.ReferenceEquals(global::math.Proto.Math.Descriptor, null);
+    }
+  }
+  
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class Num : pb::GeneratedMessage<Num, Num.Builder> {
+    private Num() { }
+    private static readonly Num defaultInstance = new Num().MakeReadOnly();
+    private static readonly string[] _numFieldNames = new string[] { "num" };
+    private static readonly uint[] _numFieldTags = new uint[] { 8 };
+    public static Num DefaultInstance {
+      get { return defaultInstance; }
+    }
+    
+    public override Num DefaultInstanceForType {
+      get { return DefaultInstance; }
+    }
+    
+    protected override Num ThisMessage {
+      get { return this; }
+    }
+    
+    public static pbd::MessageDescriptor Descriptor {
+      get { return global::math.Proto.Math.internal__static_math_Num__Descriptor; }
+    }
+    
+    protected override pb::FieldAccess.FieldAccessorTable<Num, Num.Builder> InternalFieldAccessors {
+      get { return global::math.Proto.Math.internal__static_math_Num__FieldAccessorTable; }
+    }
+    
+    public const int Num_FieldNumber = 1;
+    private bool hasNum_;
+    private long num_;
+    public bool HasNum_ {
+      get { return hasNum_; }
+    }
+    public long Num_ {
+      get { return num_; }
+    }
+    
+    public override bool IsInitialized {
+      get {
+        return true;
+      }
+    }
+    
+    public override void WriteTo(pb::ICodedOutputStream output) {
+      int size = SerializedSize;
+      string[] field_names = _numFieldNames;
+      if (hasNum_) {
+        output.WriteInt64(1, field_names[0], Num_);
+      }
+      UnknownFields.WriteTo(output);
+    }
+    
+    private int memoizedSerializedSize = -1;
+    public override int SerializedSize {
+      get {
+        int size = memoizedSerializedSize;
+        if (size != -1) return size;
+        
+        size = 0;
+        if (hasNum_) {
+          size += pb::CodedOutputStream.ComputeInt64Size(1, Num_);
+        }
+        size += UnknownFields.SerializedSize;
+        memoizedSerializedSize = size;
+        return size;
+      }
+    }
+    
+    public static Num ParseFrom(pb::ByteString data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static Num ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static Num ParseFrom(byte[] data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static Num ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static Num ParseFrom(global::System.IO.Stream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static Num ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    public static Num ParseDelimitedFrom(global::System.IO.Stream input) {
+      return CreateBuilder().MergeDelimitedFrom(input).BuildParsed();
+    }
+    public static Num ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed();
+    }
+    public static Num ParseFrom(pb::ICodedInputStream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static Num ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    private Num MakeReadOnly() {
+      return this;
+    }
+    
+    public static Builder CreateBuilder() { return new Builder(); }
+    public override Builder ToBuilder() { return CreateBuilder(this); }
+    public override Builder CreateBuilderForType() { return new Builder(); }
+    public static Builder CreateBuilder(Num prototype) {
+      return new Builder(prototype);
+    }
+    
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    public sealed partial class Builder : pb::GeneratedBuilder<Num, Builder> {
+      protected override Builder ThisBuilder {
+        get { return this; }
+      }
+      public Builder() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+      }
+      internal Builder(Num cloneFrom) {
+        result = cloneFrom;
+        resultIsReadOnly = true;
+      }
+      
+      private bool resultIsReadOnly;
+      private Num result;
+      
+      private Num PrepareBuilder() {
+        if (resultIsReadOnly) {
+          Num original = result;
+          result = new Num();
+          resultIsReadOnly = false;
+          MergeFrom(original);
+        }
+        return result;
+      }
+      
+      public override bool IsInitialized {
+        get { return result.IsInitialized; }
+      }
+      
+      protected override Num MessageBeingBuilt {
+        get { return PrepareBuilder(); }
+      }
+      
+      public override Builder Clear() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+        return this;
+      }
+      
+      public override Builder Clone() {
+        if (resultIsReadOnly) {
+          return new Builder(result);
+        } else {
+          return new Builder().MergeFrom(result);
+        }
+      }
+      
+      public override pbd::MessageDescriptor DescriptorForType {
+        get { return global::math.Num.Descriptor; }
+      }
+      
+      public override Num DefaultInstanceForType {
+        get { return global::math.Num.DefaultInstance; }
+      }
+      
+      public override Num BuildPartial() {
+        if (resultIsReadOnly) {
+          return result;
+        }
+        resultIsReadOnly = true;
+        return result.MakeReadOnly();
+      }
+      
+      public override Builder MergeFrom(pb::IMessage other) {
+        if (other is Num) {
+          return MergeFrom((Num) other);
+        } else {
+          base.MergeFrom(other);
+          return this;
+        }
+      }
+      
+      public override Builder MergeFrom(Num other) {
+        if (other == global::math.Num.DefaultInstance) return this;
+        PrepareBuilder();
+        if (other.HasNum_) {
+          Num_ = other.Num_;
+        }
+        this.MergeUnknownFields(other.UnknownFields);
+        return this;
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input) {
+        return MergeFrom(input, pb::ExtensionRegistry.Empty);
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+        PrepareBuilder();
+        pb::UnknownFieldSet.Builder unknownFields = null;
+        uint tag;
+        string field_name;
+        while (input.ReadTag(out tag, out field_name)) {
+          if(tag == 0 && field_name != null) {
+            int field_ordinal = global::System.Array.BinarySearch(_numFieldNames, field_name, global::System.StringComparer.Ordinal);
+            if(field_ordinal >= 0)
+              tag = _numFieldTags[field_ordinal];
+            else {
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              continue;
+            }
+          }
+          switch (tag) {
+            case 0: {
+              throw pb::InvalidProtocolBufferException.InvalidTag();
+            }
+            default: {
+              if (pb::WireFormat.IsEndGroupTag(tag)) {
+                if (unknownFields != null) {
+                  this.UnknownFields = unknownFields.Build();
+                }
+                return this;
+              }
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              break;
+            }
+            case 8: {
+              result.hasNum_ = input.ReadInt64(ref result.num_);
+              break;
+            }
+          }
+        }
+        
+        if (unknownFields != null) {
+          this.UnknownFields = unknownFields.Build();
+        }
+        return this;
+      }
+      
+      
+      public bool HasNum_ {
+        get { return result.hasNum_; }
+      }
+      public long Num_ {
+        get { return result.Num_; }
+        set { SetNum_(value); }
+      }
+      public Builder SetNum_(long value) {
+        PrepareBuilder();
+        result.hasNum_ = true;
+        result.num_ = value;
+        return this;
+      }
+      public Builder ClearNum_() {
+        PrepareBuilder();
+        result.hasNum_ = false;
+        result.num_ = 0L;
+        return this;
+      }
+    }
+    static Num() {
+      object.ReferenceEquals(global::math.Proto.Math.Descriptor, null);
+    }
+  }
+  
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class FibReply : pb::GeneratedMessage<FibReply, FibReply.Builder> {
+    private FibReply() { }
+    private static readonly FibReply defaultInstance = new FibReply().MakeReadOnly();
+    private static readonly string[] _fibReplyFieldNames = new string[] { "count" };
+    private static readonly uint[] _fibReplyFieldTags = new uint[] { 8 };
+    public static FibReply DefaultInstance {
+      get { return defaultInstance; }
+    }
+    
+    public override FibReply DefaultInstanceForType {
+      get { return DefaultInstance; }
+    }
+    
+    protected override FibReply ThisMessage {
+      get { return this; }
+    }
+    
+    public static pbd::MessageDescriptor Descriptor {
+      get { return global::math.Proto.Math.internal__static_math_FibReply__Descriptor; }
+    }
+    
+    protected override pb::FieldAccess.FieldAccessorTable<FibReply, FibReply.Builder> InternalFieldAccessors {
+      get { return global::math.Proto.Math.internal__static_math_FibReply__FieldAccessorTable; }
+    }
+    
+    public const int CountFieldNumber = 1;
+    private bool hasCount;
+    private long count_;
+    public bool HasCount {
+      get { return hasCount; }
+    }
+    public long Count {
+      get { return count_; }
+    }
+    
+    public override bool IsInitialized {
+      get {
+        return true;
+      }
+    }
+    
+    public override void WriteTo(pb::ICodedOutputStream output) {
+      int size = SerializedSize;
+      string[] field_names = _fibReplyFieldNames;
+      if (hasCount) {
+        output.WriteInt64(1, field_names[0], Count);
+      }
+      UnknownFields.WriteTo(output);
+    }
+    
+    private int memoizedSerializedSize = -1;
+    public override int SerializedSize {
+      get {
+        int size = memoizedSerializedSize;
+        if (size != -1) return size;
+        
+        size = 0;
+        if (hasCount) {
+          size += pb::CodedOutputStream.ComputeInt64Size(1, Count);
+        }
+        size += UnknownFields.SerializedSize;
+        memoizedSerializedSize = size;
+        return size;
+      }
+    }
+    
+    public static FibReply ParseFrom(pb::ByteString data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static FibReply ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static FibReply ParseFrom(byte[] data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static FibReply ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static FibReply ParseFrom(global::System.IO.Stream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static FibReply ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    public static FibReply ParseDelimitedFrom(global::System.IO.Stream input) {
+      return CreateBuilder().MergeDelimitedFrom(input).BuildParsed();
+    }
+    public static FibReply ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed();
+    }
+    public static FibReply ParseFrom(pb::ICodedInputStream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static FibReply ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    private FibReply MakeReadOnly() {
+      return this;
+    }
+    
+    public static Builder CreateBuilder() { return new Builder(); }
+    public override Builder ToBuilder() { return CreateBuilder(this); }
+    public override Builder CreateBuilderForType() { return new Builder(); }
+    public static Builder CreateBuilder(FibReply prototype) {
+      return new Builder(prototype);
+    }
+    
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    public sealed partial class Builder : pb::GeneratedBuilder<FibReply, Builder> {
+      protected override Builder ThisBuilder {
+        get { return this; }
+      }
+      public Builder() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+      }
+      internal Builder(FibReply cloneFrom) {
+        result = cloneFrom;
+        resultIsReadOnly = true;
+      }
+      
+      private bool resultIsReadOnly;
+      private FibReply result;
+      
+      private FibReply PrepareBuilder() {
+        if (resultIsReadOnly) {
+          FibReply original = result;
+          result = new FibReply();
+          resultIsReadOnly = false;
+          MergeFrom(original);
+        }
+        return result;
+      }
+      
+      public override bool IsInitialized {
+        get { return result.IsInitialized; }
+      }
+      
+      protected override FibReply MessageBeingBuilt {
+        get { return PrepareBuilder(); }
+      }
+      
+      public override Builder Clear() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+        return this;
+      }
+      
+      public override Builder Clone() {
+        if (resultIsReadOnly) {
+          return new Builder(result);
+        } else {
+          return new Builder().MergeFrom(result);
+        }
+      }
+      
+      public override pbd::MessageDescriptor DescriptorForType {
+        get { return global::math.FibReply.Descriptor; }
+      }
+      
+      public override FibReply DefaultInstanceForType {
+        get { return global::math.FibReply.DefaultInstance; }
+      }
+      
+      public override FibReply BuildPartial() {
+        if (resultIsReadOnly) {
+          return result;
+        }
+        resultIsReadOnly = true;
+        return result.MakeReadOnly();
+      }
+      
+      public override Builder MergeFrom(pb::IMessage other) {
+        if (other is FibReply) {
+          return MergeFrom((FibReply) other);
+        } else {
+          base.MergeFrom(other);
+          return this;
+        }
+      }
+      
+      public override Builder MergeFrom(FibReply other) {
+        if (other == global::math.FibReply.DefaultInstance) return this;
+        PrepareBuilder();
+        if (other.HasCount) {
+          Count = other.Count;
+        }
+        this.MergeUnknownFields(other.UnknownFields);
+        return this;
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input) {
+        return MergeFrom(input, pb::ExtensionRegistry.Empty);
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+        PrepareBuilder();
+        pb::UnknownFieldSet.Builder unknownFields = null;
+        uint tag;
+        string field_name;
+        while (input.ReadTag(out tag, out field_name)) {
+          if(tag == 0 && field_name != null) {
+            int field_ordinal = global::System.Array.BinarySearch(_fibReplyFieldNames, field_name, global::System.StringComparer.Ordinal);
+            if(field_ordinal >= 0)
+              tag = _fibReplyFieldTags[field_ordinal];
+            else {
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              continue;
+            }
+          }
+          switch (tag) {
+            case 0: {
+              throw pb::InvalidProtocolBufferException.InvalidTag();
+            }
+            default: {
+              if (pb::WireFormat.IsEndGroupTag(tag)) {
+                if (unknownFields != null) {
+                  this.UnknownFields = unknownFields.Build();
+                }
+                return this;
+              }
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              break;
+            }
+            case 8: {
+              result.hasCount = input.ReadInt64(ref result.count_);
+              break;
+            }
+          }
+        }
+        
+        if (unknownFields != null) {
+          this.UnknownFields = unknownFields.Build();
+        }
+        return this;
+      }
+      
+      
+      public bool HasCount {
+        get { return result.hasCount; }
+      }
+      public long Count {
+        get { return result.Count; }
+        set { SetCount(value); }
+      }
+      public Builder SetCount(long value) {
+        PrepareBuilder();
+        result.hasCount = true;
+        result.count_ = value;
+        return this;
+      }
+      public Builder ClearCount() {
+        PrepareBuilder();
+        result.hasCount = false;
+        result.count_ = 0L;
+        return this;
+      }
+    }
+    static FibReply() {
+      object.ReferenceEquals(global::math.Proto.Math.Descriptor, null);
+    }
+  }
+  
+  #endregion
+  
+  #region Services
+  /*
+  * Service generation is now disabled by default, use the following option to enable:
+  * option (google.protobuf.csharp_file_options).service_generator_type = GENERIC;
+  */
+  #endregion
+  
+}
+
+#endregion Designer generated code
diff --git a/src/csharp/GrpcApi/MathServiceClientStub.cs b/src/csharp/GrpcApi/MathServiceClientStub.cs
new file mode 100644
index 0000000..493c186
--- /dev/null
+++ b/src/csharp/GrpcApi/MathServiceClientStub.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using Google.GRPC.Core;
+
+namespace math
+{
+	/// <summary>
+	/// Implementation of math service stub (this is handwritten version of code 
+	/// that will normally be generated).
+	/// </summary>
+	public class MathServiceClientStub : IMathServiceClient
+	{
+		readonly Channel channel;
+        readonly TimeSpan methodTimeout;
+
+		public MathServiceClientStub(Channel channel, TimeSpan methodTimeout)
+		{
+			this.channel = channel;
+            this.methodTimeout = methodTimeout;
+		}
+
+		public DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken))
+		{
+            var call = new Google.GRPC.Core.Call<DivArgs, DivReply>("/math.Math/Div", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel);
+            return Calls.BlockingUnaryCall(call, args, token);
+		}
+
+		public Task<DivReply> DivAsync(DivArgs args, CancellationToken token = default(CancellationToken))
+		{
+            var call = new Google.GRPC.Core.Call<DivArgs, DivReply>("/math.Math/Div", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel);
+            return Calls.AsyncUnaryCall(call, args, token);
+		}
+
+        public Task Fib(FibArgs args, IObserver<Num> outputs, CancellationToken token = default(CancellationToken))
+		{
+            var call = new Google.GRPC.Core.Call<FibArgs, Num>("/math.Math/Fib", Serialize_FibArgs, Deserialize_Num, methodTimeout, channel);
+            return Calls.AsyncServerStreamingCall(call, args, outputs, token);
+		}
+
+        public ClientStreamingAsyncResult<Num, Num> Sum(CancellationToken token = default(CancellationToken)) 
+		{
+            var call = new Google.GRPC.Core.Call<Num, Num>("/math.Math/Sum", Serialize_Num, Deserialize_Num, methodTimeout, channel);
+            return Calls.AsyncClientStreamingCall(call, token);
+		}
+
+        public IObserver<DivArgs> DivMany(IObserver<DivReply> outputs, CancellationToken token = default(CancellationToken))
+		{
+            var call = new Google.GRPC.Core.Call<DivArgs, DivReply>("/math.Math/DivMany", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel);
+            return Calls.DuplexStreamingCall(call, outputs, token);
+		}
+
+        private static byte[] Serialize_DivArgs(DivArgs arg) {
+            return arg.ToByteArray();
+        }
+
+        private static byte[] Serialize_FibArgs(FibArgs arg) {
+            return arg.ToByteArray();
+        }
+
+        private static byte[] Serialize_Num(Num arg) {
+            return arg.ToByteArray();
+        }
+
+        private static DivReply Deserialize_DivReply(byte[] payload) {
+            return DivReply.CreateBuilder().MergeFrom(payload).Build();
+        }
+
+        private static Num Deserialize_Num(byte[] payload) {
+            return Num.CreateBuilder().MergeFrom(payload).Build();
+        }
+	}
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcApi/Messages.cs b/src/csharp/GrpcApi/Messages.cs
new file mode 100644
index 0000000..b08816b
--- /dev/null
+++ b/src/csharp/GrpcApi/Messages.cs
@@ -0,0 +1,35 @@
+//using System;
+
+//namespace Google.GRPC.Examples.Math
+//{
+//	// Messages in this file are placeholders for actual protobuf message classes
+//	// that will be generated from math.proto file.
+//
+//	public class DivArgs
+//	{
+//		public long Dividend{ get; set; }
+//		public long Divisor { get; set; }
+//	}
+//	
+//	public class DivReply
+//	{
+//		public long Quotient { get; set; }
+//		public long Remainder { get; set; }
+//	}
+//	
+//	public class FibArgs
+//	{
+//		public long Limit { get; set; }
+//	}
+//	
+//	public class Number
+//	{
+//		public long Num { get; set; }
+//	}
+//	
+//	public class FibReply
+//	{
+//		public long Count { get; set; }
+//	}
+//}
+
diff --git a/src/csharp/GrpcApi/Properties/AssemblyInfo.cs b/src/csharp/GrpcApi/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..725f12c
--- /dev/null
+++ b/src/csharp/GrpcApi/Properties/AssemblyInfo.cs
@@ -0,0 +1,22 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes. 
+// Change them to the values specific to your project.
+[assembly: AssemblyTitle ("GrpcApi")]
+[assembly: AssemblyDescription ("")]
+[assembly: AssemblyConfiguration ("")]
+[assembly: AssemblyCompany ("")]
+[assembly: AssemblyProduct ("")]
+[assembly: AssemblyCopyright ("jtattermusch")]
+[assembly: AssemblyTrademark ("")]
+[assembly: AssemblyCulture ("")]
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+[assembly: AssemblyVersion ("1.0.*")]
+// The following attributes are used to specify the signing key for the assembly, 
+// if desired. See the Mono documentation for more information about signing.
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
diff --git a/src/csharp/GrpcApi/RecordingObserver.cs b/src/csharp/GrpcApi/RecordingObserver.cs
new file mode 100644
index 0000000..8ba3787
--- /dev/null
+++ b/src/csharp/GrpcApi/RecordingObserver.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+namespace math
+{
+    public class RecordingObserver<T> : IObserver<T>
+    {
+        TaskCompletionSource<List<T>> tcs = new TaskCompletionSource<List<T>>();
+        List<T> data = new List<T>();
+
+        public void OnCompleted()
+        {
+            tcs.SetResult(data);
+        }
+
+        public void OnError(Exception error)
+        {
+            tcs.SetException(error);
+        }
+
+        public void OnNext(T value)
+        {
+            data.Add(value);
+        }
+
+        public Task<List<T>> ToList() {
+            return tcs.Task;
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcApi/math.proto b/src/csharp/GrpcApi/math.proto
new file mode 100755
index 0000000..e98b99e
--- /dev/null
+++ b/src/csharp/GrpcApi/math.proto
@@ -0,0 +1,50 @@
+syntax = "proto2";
+
+package math;
+
+message DivArgs {
+  optional int64 dividend = 1;
+  optional int64 divisor = 2;
+}
+
+message DivReply {
+  optional int64 quotient = 1;
+  optional int64 remainder = 2;
+}
+
+message FibArgs {
+  optional int64 limit = 1;
+}
+
+message Num {
+  optional int64 num = 1;
+}
+
+message FibReply {
+  optional int64 count = 1;
+}
+
+service Math {
+  // Div divides args.dividend by args.divisor and returns the quotient and
+  // remainder.
+  rpc Div (DivArgs) returns (DivReply) {
+  }
+
+  // DivMany accepts an arbitrary number of division args from the client stream
+  // and sends back the results in the reply stream.  The stream continues until
+  // the client closes its end; the server does the same after sending all the
+  // replies.  The stream ends immediately if either end aborts.
+  rpc DivMany (stream DivArgs) returns (stream DivReply) {
+  }
+
+  // Fib generates numbers in the Fibonacci sequence.  If args.limit > 0, Fib
+  // generates up to limit numbers; otherwise it continues until the call is
+  // canceled.  Unlike Fib above, Fib has no final FibReply.
+  rpc Fib (FibArgs) returns (stream Num) {
+  }
+
+  // Sum sums a stream of numbers, returning the final result once the stream
+  // is closed.
+  rpc Sum (stream Num) returns (Num) {
+  }
+}
diff --git a/src/csharp/GrpcCore/.gitignore b/src/csharp/GrpcCore/.gitignore
new file mode 100644
index 0000000..ba077a4
--- /dev/null
+++ b/src/csharp/GrpcCore/.gitignore
@@ -0,0 +1 @@
+bin
diff --git a/src/csharp/GrpcCore/Call.cs b/src/csharp/GrpcCore/Call.cs
new file mode 100644
index 0000000..d3847a8
--- /dev/null
+++ b/src/csharp/GrpcCore/Call.cs
@@ -0,0 +1,65 @@
+using System;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core
+{
+    public class Call<TRequest, TResponse>
+    {
+        readonly string methodName;
+        readonly Func<TRequest, byte[]> requestSerializer;
+        readonly Func<byte[], TResponse> responseDeserializer;
+        readonly Channel channel;
+
+        public Call(string methodName, 
+                    Func<TRequest, byte[]> requestSerializer,
+                    Func<byte[], TResponse> responseDeserializer,
+                    TimeSpan timeout,
+                    Channel channel) {
+            this.methodName = methodName;
+            this.requestSerializer = requestSerializer;
+            this.responseDeserializer = responseDeserializer;
+            this.channel = channel;
+        }
+
+        public Call(Method<TRequest, TResponse> method, Channel channel)
+        {
+            this.methodName = method.Name;
+            this.requestSerializer = method.RequestMarshaller.Serialize;
+            this.responseDeserializer = method.ResponseMarshaller.Deserialize;
+            this.channel = channel;
+        }
+
+        public Channel Channel
+        {
+            get
+            {
+                return this.channel;
+            }
+        }
+
+        public string MethodName
+        {
+            get
+            {
+                return this.methodName;
+            }
+        }
+
+        public Func<TRequest, byte[]> RequestSerializer
+        {
+            get
+            {
+                return this.requestSerializer;
+            }
+        }
+
+        public Func<byte[], TResponse> ResponseDeserializer
+        {
+            get
+            {
+                return this.responseDeserializer;
+            }
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Calls.cs b/src/csharp/GrpcCore/Calls.cs
new file mode 100644
index 0000000..c3e51cb
--- /dev/null
+++ b/src/csharp/GrpcCore/Calls.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core
+{
+    // NOTE: this class is work-in-progress
+
+    /// <summary>
+    /// Helper methods for generated stubs to make RPC calls.
+    /// </summary>
+    public static class Calls
+    {
+        public static TResponse BlockingUnaryCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, CancellationToken token)
+        {
+            //TODO: implement this in real synchronous style once new GRPC C core API is available.
+            return AsyncUnaryCall(call, req, token).Result;
+        }
+
+        public static async Task<TResponse> AsyncUnaryCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, CancellationToken token)
+        {
+            var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestSerializer, call.ResponseDeserializer);
+            asyncCall.Initialize(call.Channel, call.MethodName);
+            asyncCall.Start(false, GetCompletionQueue());
+
+            await asyncCall.WriteAsync(req);
+            await asyncCall.WritesCompletedAsync();
+
+            TResponse response = await asyncCall.ReadAsync();
+
+            Status status = await asyncCall.Finished;
+
+            if (status.StatusCode != StatusCode.GRPC_STATUS_OK)
+            {
+                throw new RpcException(status);
+            }
+            return response;
+        }
+
+        public static async Task AsyncServerStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, IObserver<TResponse> outputs, CancellationToken token)
+        {
+            var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestSerializer, call.ResponseDeserializer);
+            asyncCall.Initialize(call.Channel, call.MethodName);
+            asyncCall.Start(false, GetCompletionQueue());
+
+            asyncCall.StartReadingToStream(outputs);
+
+            await asyncCall.WriteAsync(req);
+            await asyncCall.WritesCompletedAsync();
+        }
+
+        public static ClientStreamingAsyncResult<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, CancellationToken token)
+        {
+            var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestSerializer, call.ResponseDeserializer);
+            asyncCall.Initialize(call.Channel, call.MethodName);
+            asyncCall.Start(false, GetCompletionQueue());
+
+            var task = asyncCall.ReadAsync();
+            var inputs = new StreamingInputObserver<TRequest, TResponse>(asyncCall);
+            return new ClientStreamingAsyncResult<TRequest, TResponse>(task, inputs);
+        }
+
+        public static TResponse BlockingClientStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, IObservable<TRequest> inputs, CancellationToken token)
+        {
+            throw new NotImplementedException();
+        }
+
+        public static IObserver<TRequest> DuplexStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, IObserver<TResponse> outputs, CancellationToken token)
+        {
+            var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestSerializer, call.ResponseDeserializer);
+            asyncCall.Initialize(call.Channel, call.MethodName);
+            asyncCall.Start(false, GetCompletionQueue());
+
+            asyncCall.StartReadingToStream(outputs);
+            var inputs = new StreamingInputObserver<TRequest, TResponse>(asyncCall);
+            return inputs;
+        }
+
+        private static CompletionQueueSafeHandle GetCompletionQueue() {
+            return GrpcEnvironment.ThreadPool.CompletionQueue;
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Channel.cs b/src/csharp/GrpcCore/Channel.cs
new file mode 100644
index 0000000..b0d8bee
--- /dev/null
+++ b/src/csharp/GrpcCore/Channel.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core
+{
+	public class Channel : IDisposable
+	{
+        /// <summary>
+        /// Make sure GPRC environment is initialized before any channels get used.
+        /// </summary>
+        static Channel() {
+            GrpcEnvironment.EnsureInitialized();
+        }
+       
+        readonly ChannelSafeHandle handle;
+        readonly String target;
+
+        // TODO: add way how to create grpc_secure_channel....
+		// TODO: add support for channel args...
+		public Channel(string target)
+		{
+            this.handle = ChannelSafeHandle.Create(target, IntPtr.Zero);
+			this.target = target;
+		}
+
+        internal ChannelSafeHandle Handle
+        {
+            get
+            {
+                return this.handle;
+            }
+        }
+
+        public string Target
+        {
+            get
+            {
+                return this.target;
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (handle != null && !handle.IsInvalid)
+            {
+                handle.Dispose();
+            }
+        }
+	}
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/ClientStreamingAsyncResult.cs b/src/csharp/GrpcCore/ClientStreamingAsyncResult.cs
new file mode 100644
index 0000000..9e7312c
--- /dev/null
+++ b/src/csharp/GrpcCore/ClientStreamingAsyncResult.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Google.GRPC.Core
+{
+    /// <summary>
+    /// Return type for client streaming async method.
+    /// </summary>
+    public struct ClientStreamingAsyncResult<TRequest, TResponse>
+    {
+        readonly Task<TResponse> task;
+        readonly IObserver<TRequest> inputs;
+
+        public ClientStreamingAsyncResult(Task<TResponse> task, IObserver<TRequest> inputs)
+        {
+            this.task = task;
+            this.inputs = inputs;
+        }
+
+        public Task<TResponse> Task
+        {
+            get
+            {
+                return this.task;
+            }
+        }
+
+        public IObserver<TRequest> Inputs
+        {
+            get
+            {
+                return this.inputs;
+            }
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/GrpcCore.csproj b/src/csharp/GrpcCore/GrpcCore.csproj
new file mode 100644
index 0000000..2ad0f91
--- /dev/null
+++ b/src/csharp/GrpcCore/GrpcCore.csproj
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <RootNamespace>GrpcCore</RootNamespace>
+    <AssemblyName>GrpcCore</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="RpcException.cs" />
+    <Compile Include="Calls.cs" />
+    <Compile Include="Call.cs" />
+    <Compile Include="ClientStreamingAsyncResult.cs" />
+    <Compile Include="GrpcEnvironment.cs" />
+    <Compile Include="Status.cs" />
+    <Compile Include="StatusCode.cs" />
+    <Compile Include="Server.cs" />
+    <Compile Include="Channel.cs" />
+    <Compile Include="Internal\CallSafeHandle.cs" />
+    <Compile Include="Internal\ChannelSafeHandle.cs" />
+    <Compile Include="Internal\CompletionQueueSafeHandle.cs" />
+    <Compile Include="Internal\Enums.cs" />
+    <Compile Include="Internal\Event.cs" />
+    <Compile Include="Internal\SafeHandleZeroIsInvalid.cs" />
+    <Compile Include="Internal\Timespec.cs" />
+    <Compile Include="Internal\GrpcThreadPool.cs" />
+    <Compile Include="Internal\AsyncCall.cs" />
+    <Compile Include="Internal\ServerSafeHandle.cs" />
+    <Compile Include="Internal\StreamingInputObserver.cs" />
+    <Compile Include="Method.cs" />
+    <Compile Include="IMarshaller.cs" />
+    <Compile Include="ServerCalls.cs" />
+    <Compile Include="ServerCallHandler.cs" />
+    <Compile Include="Internal\ServerWritingObserver.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <Folder Include="Internal\" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/GrpcEnvironment.cs b/src/csharp/GrpcCore/GrpcEnvironment.cs
new file mode 100644
index 0000000..7a644f49
--- /dev/null
+++ b/src/csharp/GrpcCore/GrpcEnvironment.cs
@@ -0,0 +1,91 @@
+using System;
+using Google.GRPC.Core.Internal;
+using System.Runtime.InteropServices;
+
+namespace Google.GRPC.Core
+{
+    /// <summary>
+    /// Encapsulates initialization and shutdown of GRPC C core library.
+    /// You should not need to initialize it manually, as static constructors
+    /// should load the library when needed.
+    /// </summary>
+    public static class GrpcEnvironment
+    {
+        const int THREAD_POOL_SIZE = 1;
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_init();
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_shutdown();
+
+        static object staticLock = new object();
+        static bool initCalled = false;
+        static bool shutdownCalled = false;
+
+        static GrpcThreadPool threadPool = new GrpcThreadPool(THREAD_POOL_SIZE);
+
+        /// <summary>
+        /// Makes sure GRPC environment is initialized.
+        /// </summary>
+        public static void EnsureInitialized() {
+            lock(staticLock)
+            {
+                if (!initCalled)
+                {
+                    initCalled = true;
+                    GrpcInit();       
+                }
+            }
+        }
+
+        /// <summary>
+        /// Shuts down the GRPC environment if it was initialized before.
+        /// Repeated invocations have no effect.
+        /// </summary>
+        public static void Shutdown()
+        {
+            lock(staticLock)
+            {
+                if (initCalled && !shutdownCalled)
+                {
+                    shutdownCalled = true;
+                    GrpcShutdown();
+                }
+            }
+
+        }
+
+        /// <summary>
+        /// Initializes GRPC C Core library.
+        /// </summary>
+        private static void GrpcInit()
+        {
+            grpc_init();
+            threadPool.Start();
+            // TODO: use proper logging here
+            Console.WriteLine("GRPC initialized.");
+        }
+
+        /// <summary>
+        /// Shutdown GRPC C Core library.
+        /// </summary>
+        private static void GrpcShutdown()
+        {
+            threadPool.Stop();
+            grpc_shutdown();
+
+            // TODO: use proper logging here
+            Console.WriteLine("GRPC shutdown.");
+        }
+
+        internal static GrpcThreadPool ThreadPool
+        {
+            get
+            {
+                return threadPool;
+            }
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/IMarshaller.cs b/src/csharp/GrpcCore/IMarshaller.cs
new file mode 100644
index 0000000..eb08d8d
--- /dev/null
+++ b/src/csharp/GrpcCore/IMarshaller.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace Google.GRPC.Core
+{
+    /// <summary>
+    /// For serializing and deserializing messages.
+    /// </summary>
+    public interface IMarshaller<T>
+    {
+        byte[] Serialize(T value);
+
+        T Deserialize(byte[] payload);
+    }
+
+    /// <summary>
+    /// UTF-8 Marshalling for string. Useful for testing.
+    /// </summary>
+    internal class StringMarshaller : IMarshaller<string> {
+
+        public byte[] Serialize(string value)
+        {
+            return System.Text.Encoding.UTF8.GetBytes(value);
+        }
+
+        public string Deserialize(byte[] payload)
+        {
+            return System.Text.Encoding.UTF8.GetString(payload);
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Internal/AsyncCall.cs b/src/csharp/GrpcCore/Internal/AsyncCall.cs
new file mode 100644
index 0000000..c38363b
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/AsyncCall.cs
@@ -0,0 +1,493 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// Listener for call events that can be delivered from a completion queue.
+    /// </summary>
+    internal interface ICallEventListener {
+    
+        void OnClientMetadata();
+
+        void OnRead(byte[] payload);
+
+        void OnWriteAccepted(GRPCOpError error);
+
+        void OnFinishAccepted(GRPCOpError error);
+
+        // ignore the status on server
+        void OnFinished(Status status);
+    }
+
+    /// <summary>
+    /// Handle native call lifecycle and provides convenience methods.
+    /// </summary>
+    internal class AsyncCall<TWrite, TRead>: ICallEventListener, IDisposable
+    {
+        readonly Func<TWrite, byte[]> serializer;
+        readonly Func<byte[], TRead> deserializer;
+
+        // TODO: make sure the delegate doesn't get garbage collected while 
+        // native callbacks are in the completion queue.
+        readonly EventCallbackDelegate callbackHandler;
+
+        object myLock = new object();
+        bool disposed;
+        CallSafeHandle call;
+
+        bool started;
+        bool errorOccured;
+
+        bool cancelRequested;
+        bool halfcloseRequested;
+        bool halfclosed;
+        bool doneWithReading;
+        Nullable<Status> finishedStatus;
+
+        TaskCompletionSource<object> writeTcs;
+        TaskCompletionSource<TRead> readTcs;
+        TaskCompletionSource<object> halfcloseTcs = new TaskCompletionSource<object>();
+        TaskCompletionSource<Status> finishedTcs = new TaskCompletionSource<Status>();
+
+        IObserver<TRead> readObserver;
+
+        public AsyncCall(Func<TWrite, byte[]> serializer, Func<byte[], TRead> deserializer)
+        {
+            this.serializer = serializer;
+            this.deserializer = deserializer;
+            this.callbackHandler = HandleEvent;
+        }
+
+        public Task WriteAsync(TWrite msg)
+        {
+            return StartWrite(msg, false).Task;
+        }
+
+        public Task WritesCompletedAsync()
+        {
+            WritesDone();
+            return halfcloseTcs.Task;
+        }
+
+        public Task WriteStatusAsync(Status status)
+        {
+            WriteStatus(status);
+            return halfcloseTcs.Task;
+        }
+
+        public Task<TRead> ReadAsync()
+        {
+            return StartRead().Task;
+        }
+
+        public Task Halfclosed
+        {
+            get
+            {
+                return halfcloseTcs.Task;
+            }
+        }
+
+        public Task<Status> Finished
+        {
+            get
+            {
+                return finishedTcs.Task;
+            }
+        }
+
+        /// <summary>
+        /// Initiates reading to given observer.
+        /// </summary>
+        public void StartReadingToStream(IObserver<TRead> readObserver) {
+            lock (myLock)
+            {
+                CheckStarted();
+                if (this.readObserver != null)
+                {
+                    throw new InvalidOperationException("Already registered an observer.");
+                }
+                this.readObserver = readObserver;
+                StartRead();
+            }
+        }
+
+        public void Initialize(Channel channel, String methodName) {
+            lock (myLock)
+            {
+               this.call = CallSafeHandle.Create(channel.Handle, methodName, channel.Target, Timespec.InfFuture);
+            }
+        }
+
+        public void InitializeServer(CallSafeHandle call)
+        {
+            lock(myLock)
+            {
+                this.call = call;
+            }
+        }
+
+        // Client only
+        public void Start(bool buffered, CompletionQueueSafeHandle cq)
+        {
+            lock (myLock)
+            {
+                if (started)
+                {
+                    throw new InvalidOperationException("Already started.");
+                }
+
+                call.Invoke(cq, buffered, callbackHandler, callbackHandler);
+                started = true;
+            }
+        }
+
+        // Server only
+        public void Accept(CompletionQueueSafeHandle cq)
+        {
+            lock (myLock)
+            {
+                if (started)
+                {
+                    throw new InvalidOperationException("Already started.");
+                }
+
+                call.ServerAccept(cq, callbackHandler);
+                call.ServerEndInitialMetadata(0);
+                started = true;
+            }
+        }
+
+        public TaskCompletionSource<object> StartWrite(TWrite msg, bool buffered)
+        {
+            lock (myLock)
+            {
+                CheckStarted();
+                CheckNotFinished();
+                CheckNoError();
+                CheckCancelNotRequested();
+               
+                if (halfcloseRequested || halfclosed)
+                {
+                    throw new InvalidOperationException("Already halfclosed.");
+                }
+
+                if (writeTcs != null)
+                {
+                    throw new InvalidOperationException("Only one write can be pending at a time");
+                }
+
+                // TODO: wrap serialization...
+                byte[] payload = serializer(msg);
+               
+                call.StartWrite(payload, buffered, callbackHandler);
+                writeTcs = new TaskCompletionSource<object>();
+                return writeTcs;
+            }
+        }
+
+        // client only
+        public void WritesDone()
+        {
+            lock (myLock)
+            {
+                CheckStarted();
+                CheckNotFinished();
+                CheckNoError();
+                CheckCancelNotRequested();
+
+                if (halfcloseRequested || halfclosed)
+                {
+                    throw new InvalidOperationException("Already halfclosed.");
+                }
+
+                call.WritesDone(callbackHandler);
+                halfcloseRequested = true;
+            }
+        }
+
+        // server only
+        public void WriteStatus(Status status)
+        {
+            lock (myLock)
+            {
+                CheckStarted();
+                CheckNotFinished();
+                CheckNoError();
+                CheckCancelNotRequested();
+
+                if (halfcloseRequested || halfclosed)
+                {
+                    throw new InvalidOperationException("Already halfclosed.");
+                }
+
+                call.StartWriteStatus(status, callbackHandler);
+                halfcloseRequested = true;
+            }
+        }
+
+        public TaskCompletionSource<TRead> StartRead()
+        {
+            lock (myLock)
+            {
+                CheckStarted();
+                CheckNotFinished();
+                CheckNoError();
+
+                // TODO: add check for not cancelled?
+
+                if (doneWithReading)
+                {
+                    throw new InvalidOperationException("Already read the last message.");
+                }
+
+                if (readTcs != null)
+                {
+                    throw new InvalidOperationException("Only one read can be pending at a time");
+                }
+
+                call.StartRead(callbackHandler);
+
+                readTcs = new TaskCompletionSource<TRead>();
+                return readTcs;
+            }
+        }
+
+        public void Cancel()
+        {
+            lock (myLock)
+            {
+                CheckStarted();
+                CheckNotFinished();
+
+                cancelRequested = true;
+            }
+            // grpc_call_cancel is threadsafe
+            call.Cancel();
+        }
+
+        public void CancelWithStatus(Status status)
+        {
+            lock (myLock)
+            {
+                CheckStarted();
+                CheckNotFinished();
+
+                cancelRequested = true;
+            }
+            // grpc_call_cancel_with_status is threadsafe
+            call.CancelWithStatus(status);
+        }
+       
+        public void OnClientMetadata()
+        {
+            // TODO: implement....
+        }
+
+        public void OnRead(byte[] payload)
+        {
+            TaskCompletionSource<TRead> oldTcs = null;
+            IObserver<TRead> observer = null;
+            lock (myLock)
+            {
+                oldTcs = readTcs;
+                readTcs = null;
+                if (payload == null)
+                {
+                    doneWithReading = true;
+                }
+                observer = readObserver;
+            }
+
+            // TODO: wrap deserialization...
+            TRead msg = payload != null ? deserializer(payload) : default(TRead);
+
+            oldTcs.SetResult(msg);
+
+            // TODO: make sure we deliver reads in the right order.
+
+            if (observer != null)
+            {
+                if (payload != null)
+                {
+                    // TODO: wrap to handle exceptions
+                    observer.OnNext(msg);
+
+                    // start a new read
+                    StartRead();
+                }
+                else
+                {
+                    // TODO: wrap to handle exceptions;
+                    observer.OnCompleted();
+                }
+
+            }
+        }
+
+        public void OnWriteAccepted(GRPCOpError error)
+        {
+            TaskCompletionSource<object> oldTcs = null;
+            lock (myLock)
+            {
+                UpdateErrorOccured(error);
+                oldTcs = writeTcs;
+                writeTcs = null;
+            }
+
+            if (errorOccured)
+            {
+                // TODO: use the right type of exception...
+                oldTcs.SetException(new Exception("Write failed"));
+            }
+            else
+            {
+                // TODO: where does the continuation run?
+                oldTcs.SetResult(null);
+            }
+        }
+
+        public void OnFinishAccepted(GRPCOpError error)
+        {
+            lock (myLock)
+            {
+                UpdateErrorOccured(error);
+                halfclosed = true;
+            }
+
+            if (errorOccured)
+            {
+                halfcloseTcs.SetException(new Exception("Halfclose failed"));
+
+            }
+            else
+            {
+                halfcloseTcs.SetResult(null);
+            }
+
+        }
+
+        public void OnFinished(Status status)
+        {
+            lock (myLock)
+            {
+                finishedStatus = status;
+
+                DisposeResourcesIfNeeded();
+            }
+            finishedTcs.SetResult(status);
+
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (!disposed)
+            {
+                if (disposing)
+                {
+                    if (call != null)
+                    {
+                        call.Dispose();
+                    }
+                } 
+                disposed = true;
+            }
+        }
+
+        private void UpdateErrorOccured(GRPCOpError error)
+        {
+            if (error == GRPCOpError.GRPC_OP_ERROR)
+            {
+                errorOccured = true;
+            }
+        }
+
+        private void CheckStarted()
+        {
+            if (!started)
+            {
+                throw new InvalidOperationException("Call not started");
+            }
+        }
+
+        private void CheckNoError()
+        {
+            if (errorOccured)
+            {
+                throw new InvalidOperationException("Error occured when processing call.");
+            }
+        }
+
+        private void CheckNotFinished()
+        {
+            if (finishedStatus.HasValue)
+            {
+                throw new InvalidOperationException("Already finished.");
+            }
+        }
+
+        private void CheckCancelNotRequested()
+        {
+            if (cancelRequested)
+            {
+                throw new InvalidOperationException("Cancel has been requested.");
+            }
+        }
+
+        private void DisposeResourcesIfNeeded()
+        {
+            if (call != null && started && finishedStatus.HasValue)
+            {
+                // TODO: should we also wait for all the pending events to finish?
+
+                call.Dispose();
+            }
+        }
+
+        private void HandleEvent(IntPtr eventPtr) {
+            try {
+                var ev = new EventSafeHandleNotOwned(eventPtr);
+                switch (ev.GetCompletionType())
+                {
+                case GRPCCompletionType.GRPC_CLIENT_METADATA_READ:
+                    OnClientMetadata();
+                    break;
+
+                case GRPCCompletionType.GRPC_READ:
+                    byte[] payload = ev.GetReadData();
+                    OnRead(payload);
+                    break;
+
+                case GRPCCompletionType.GRPC_WRITE_ACCEPTED:
+                    OnWriteAccepted(ev.GetWriteAccepted());
+                    break;
+
+                case GRPCCompletionType.GRPC_FINISH_ACCEPTED:
+                    OnFinishAccepted(ev.GetFinishAccepted());
+                    break;
+
+                case GRPCCompletionType.GRPC_FINISHED:
+                    OnFinished(ev.GetFinished());
+                    break;
+
+                default:
+                    throw new ArgumentException("Unexpected completion type");
+                }
+            } catch(Exception e) {
+                Console.WriteLine("Caught exception in a native handler: " + e);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/Internal/CallSafeHandle.cs b/src/csharp/GrpcCore/Internal/CallSafeHandle.cs
new file mode 100644
index 0000000..6c9c58a
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/CallSafeHandle.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+using Google.GRPC.Core;
+
+namespace Google.GRPC.Core.Internal
+{
+    // TODO: we need to make sure that the delegates are not collected before invoked.
+    internal delegate void EventCallbackDelegate(IntPtr eventPtr);
+
+    /// <summary>
+    /// grpc_call from <grpc/grpc.h>
+    /// </summary>
+	internal class CallSafeHandle : SafeHandleZeroIsInvalid
+	{
+        const UInt32 GRPC_WRITE_BUFFER_HINT = 1;
+
+        [DllImport("libgrpc.so")]
+        static extern CallSafeHandle grpc_channel_create_call_old(ChannelSafeHandle channel, string method, string host, Timespec deadline);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_add_metadata(CallSafeHandle call, IntPtr metadata, UInt32 flags);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_invoke_old(CallSafeHandle call, CompletionQueueSafeHandle cq, IntPtr metadataReadTag, IntPtr finishedTag, UInt32 flags);
+
+        [DllImport("libgrpc.so", EntryPoint = "grpc_call_invoke_old")]
+        static extern GRPCCallError grpc_call_invoke_old_CALLBACK(CallSafeHandle call, CompletionQueueSafeHandle cq,
+                                                              [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate metadataReadCallback, 
+                                                              [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate finishedCallback, 
+                                                              UInt32 flags);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_server_accept_old(CallSafeHandle call, CompletionQueueSafeHandle completionQueue, IntPtr finishedTag);
+
+        [DllImport("libgrpc.so", EntryPoint = "grpc_call_server_accept_old")]
+        static extern GRPCCallError grpc_call_server_accept_old_CALLBACK(CallSafeHandle call, CompletionQueueSafeHandle completionQueue, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate finishedCallback);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_server_end_initial_metadata_old(CallSafeHandle call, UInt32 flags);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_cancel(CallSafeHandle call);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_cancel_with_status(CallSafeHandle call, StatusCode status, string description);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_start_write_status_old(CallSafeHandle call, StatusCode statusCode, string statusMessage, IntPtr tag);
+
+        [DllImport("libgrpc.so", EntryPoint = "grpc_call_start_write_status_old")]
+        static extern GRPCCallError grpc_call_start_write_status_old_CALLBACK(CallSafeHandle call, StatusCode statusCode, string statusMessage, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_writes_done_old(CallSafeHandle call, IntPtr tag);
+
+        [DllImport("libgrpc.so", EntryPoint = "grpc_call_writes_done_old")]
+        static extern GRPCCallError grpc_call_writes_done_old_CALLBACK(CallSafeHandle call, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_start_read_old(CallSafeHandle call, IntPtr tag);
+
+        [DllImport("libgrpc.so", EntryPoint = "grpc_call_start_read_old")]
+        static extern GRPCCallError grpc_call_start_read_old_CALLBACK(CallSafeHandle call, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern void grpc_call_start_write_from_copied_buffer(CallSafeHandle call,
+                                                                    byte[] buffer, UIntPtr length,
+                                                                    IntPtr tag, UInt32 flags);
+
+        [DllImport("libgrpc_csharp_ext.so", EntryPoint = "grpc_call_start_write_from_copied_buffer")]
+        static extern void grpc_call_start_write_from_copied_buffer_CALLBACK(CallSafeHandle call,
+                                                                             byte[] buffer, UIntPtr length,
+                                                                             [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback,
+                                                                             UInt32 flags);
+
+		[DllImport("libgrpc.so")]
+		static extern void grpc_call_destroy(IntPtr call);
+
+        private CallSafeHandle()
+        {
+        }
+
+        /// <summary>
+        /// Creates a client call.
+        /// </summary>
+        public static CallSafeHandle Create(ChannelSafeHandle channel, string method, string host, Timespec deadline)
+        {
+            return grpc_channel_create_call_old(channel, method, host, deadline);
+        }
+
+        public void Invoke(CompletionQueueSafeHandle cq, IntPtr metadataReadTag, IntPtr finishedTag, bool buffered)
+        {   
+            AssertCallOk(grpc_call_invoke_old(this, cq, metadataReadTag, finishedTag, GetFlags(buffered)));
+        }
+
+        public void Invoke(CompletionQueueSafeHandle cq, bool buffered, EventCallbackDelegate metadataReadCallback, EventCallbackDelegate finishedCallback)
+        {   
+            AssertCallOk(grpc_call_invoke_old_CALLBACK(this, cq, metadataReadCallback, finishedCallback, GetFlags(buffered)));
+        }
+
+        public void ServerAccept(CompletionQueueSafeHandle cq, IntPtr finishedTag)
+        {
+            AssertCallOk(grpc_call_server_accept_old(this, cq, finishedTag));
+        }
+
+        public void ServerAccept(CompletionQueueSafeHandle cq, EventCallbackDelegate callback)
+        {
+            AssertCallOk(grpc_call_server_accept_old_CALLBACK(this, cq, callback));
+        }
+
+        public void ServerEndInitialMetadata(UInt32 flags)
+        {
+            AssertCallOk(grpc_call_server_end_initial_metadata_old(this, flags));
+        }
+
+        public void StartWrite(byte[] payload, IntPtr tag, bool buffered)
+        {
+            grpc_call_start_write_from_copied_buffer(this, payload, new UIntPtr((ulong) payload.Length), tag, GetFlags(buffered));
+        }
+
+        public void StartWrite(byte[] payload, bool buffered, EventCallbackDelegate callback)
+        {
+            grpc_call_start_write_from_copied_buffer_CALLBACK(this, payload, new UIntPtr((ulong) payload.Length), callback, GetFlags(buffered));
+        }
+
+        public void StartWriteStatus(Status status, IntPtr tag)
+        {
+            AssertCallOk(grpc_call_start_write_status_old(this, status.StatusCode, status.Detail, tag));
+        }
+
+        public void StartWriteStatus(Status status, EventCallbackDelegate callback)
+        {
+            AssertCallOk(grpc_call_start_write_status_old_CALLBACK(this, status.StatusCode, status.Detail, callback));
+        }
+
+        public void WritesDone(IntPtr tag)
+        {
+            AssertCallOk(grpc_call_writes_done_old(this, tag));
+        }
+
+        public void WritesDone(EventCallbackDelegate callback)
+        {
+            AssertCallOk(grpc_call_writes_done_old_CALLBACK(this, callback));
+        }
+
+        public void StartRead(IntPtr tag)
+        {
+            AssertCallOk(grpc_call_start_read_old(this, tag));
+        }
+
+        public void StartRead(EventCallbackDelegate callback)
+        {
+            AssertCallOk(grpc_call_start_read_old_CALLBACK(this, callback));
+        }
+
+        public void Cancel()
+        {
+            AssertCallOk(grpc_call_cancel(this));
+        }
+
+        public void CancelWithStatus(Status status)
+        {
+            AssertCallOk(grpc_call_cancel_with_status(this, status.StatusCode, status.Detail));
+        }
+
+		protected override bool ReleaseHandle()
+		{
+			grpc_call_destroy(handle);
+			return true;
+		}
+
+        private static void AssertCallOk(GRPCCallError callError)
+        {
+            Trace.Assert(callError == GRPCCallError.GRPC_CALL_OK, "Status not GRPC_CALL_OK");
+        }
+
+        private static UInt32 GetFlags(bool buffered) {
+            return buffered ? 0 : GRPC_WRITE_BUFFER_HINT;
+        }
+	}
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/Internal/ChannelSafeHandle.cs b/src/csharp/GrpcCore/Internal/ChannelSafeHandle.cs
new file mode 100644
index 0000000..3a09d8b
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/ChannelSafeHandle.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// grpc_channel from <grpc/grpc.h>
+    /// </summary>
+	internal class ChannelSafeHandle : SafeHandleZeroIsInvalid
+	{
+        [DllImport("libgrpc.so")]
+        static extern ChannelSafeHandle grpc_channel_create(string target, IntPtr channelArgs);
+
+		[DllImport("libgrpc.so")]
+		static extern void grpc_channel_destroy(IntPtr channel);
+
+        private ChannelSafeHandle()
+        {
+        }
+
+        public static ChannelSafeHandle Create(string target, IntPtr channelArgs)
+        {
+            return grpc_channel_create(target, channelArgs);
+        }
+
+		protected override bool ReleaseHandle()
+		{
+			grpc_channel_destroy(handle);
+			return true;
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/Internal/CompletionQueueSafeHandle.cs b/src/csharp/GrpcCore/Internal/CompletionQueueSafeHandle.cs
new file mode 100644
index 0000000..73dd3ed
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/CompletionQueueSafeHandle.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// grpc_completion_queue from <grpc/grpc.h>
+    /// </summary>
+	internal class CompletionQueueSafeHandle : SafeHandleZeroIsInvalid
+	{
+        [DllImport("libgrpc.so")]
+        static extern CompletionQueueSafeHandle grpc_completion_queue_create();
+
+        [DllImport("libgrpc.so")]
+        static extern EventSafeHandle grpc_completion_queue_pluck(CompletionQueueSafeHandle cq, IntPtr tag, Timespec deadline);
+
+        [DllImport("libgrpc.so")]
+        static extern EventSafeHandle grpc_completion_queue_next(CompletionQueueSafeHandle cq, Timespec deadline);
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_completion_queue_shutdown(CompletionQueueSafeHandle cq);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCCompletionType grpc_completion_queue_next_with_callback(CompletionQueueSafeHandle cq);
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_completion_queue_destroy(IntPtr cq);
+
+        private CompletionQueueSafeHandle()
+        {
+        }
+
+        public static CompletionQueueSafeHandle Create()
+        {
+            return grpc_completion_queue_create();
+        }
+
+        public EventSafeHandle Next(Timespec deadline)
+        {
+            return grpc_completion_queue_next(this, deadline);
+        }
+
+        public GRPCCompletionType NextWithCallback()
+        {
+            return grpc_completion_queue_next_with_callback(this);
+        }
+
+        public EventSafeHandle Pluck(IntPtr tag, Timespec deadline)
+        {
+            return grpc_completion_queue_pluck(this, tag, deadline);
+        }
+
+        public void Shutdown()
+        {
+            grpc_completion_queue_shutdown(this);
+        }
+
+		protected override bool ReleaseHandle()
+        {
+            grpc_completion_queue_destroy(handle);
+			return true;
+		}
+	}
+}
+
diff --git a/src/csharp/GrpcCore/Internal/Enums.cs b/src/csharp/GrpcCore/Internal/Enums.cs
new file mode 100644
index 0000000..46e3bca
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/Enums.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// from grpc/grpc.h
+    /// </summary>
+    internal enum GRPCCallError
+    {
+        /* everything went ok */
+        GRPC_CALL_OK = 0,
+        /* something failed, we don't know what */
+        GRPC_CALL_ERROR,
+        /* this method is not available on the server */
+        GRPC_CALL_ERROR_NOT_ON_SERVER,
+        /* this method is not available on the client */
+        GRPC_CALL_ERROR_NOT_ON_CLIENT,
+        /* this method must be called before server_accept */
+        GRPC_CALL_ERROR_ALREADY_ACCEPTED,
+        /* this method must be called before invoke */
+        GRPC_CALL_ERROR_ALREADY_INVOKED,
+        /* this method must be called after invoke */
+        GRPC_CALL_ERROR_NOT_INVOKED,
+        /* this call is already finished
+     (writes_done or write_status has already been called) */
+        GRPC_CALL_ERROR_ALREADY_FINISHED,
+        /* there is already an outstanding read/write operation on the call */
+        GRPC_CALL_ERROR_TOO_MANY_OPERATIONS,
+        /* the flags value was illegal for this call */
+        GRPC_CALL_ERROR_INVALID_FLAGS
+    }
+
+    /// <summary>
+    /// grpc_completion_type from grpc/grpc.h
+    /// </summary>
+    internal enum GRPCCompletionType
+    {
+        GRPC_QUEUE_SHUTDOWN,
+        /* Shutting down */
+        GRPC_READ,
+        /* A read has completed */
+        GRPC_INVOKE_ACCEPTED,
+        /* An invoke call has been accepted by flow
+                                control */
+        GRPC_WRITE_ACCEPTED,
+        /* A write has been accepted by
+                                flow control */
+        GRPC_FINISH_ACCEPTED,
+        /* writes_done or write_status has been accepted */
+        GRPC_CLIENT_METADATA_READ,
+        /* The metadata array sent by server received at
+                                client */
+        GRPC_FINISHED,
+        /* An RPC has finished. The event contains status.
+                                On the server this will be OK or Cancelled. */
+        GRPC_SERVER_RPC_NEW,
+        /* A new RPC has arrived at the server */
+        GRPC_COMPLETION_DO_NOT_USE
+        /* must be last, forces users to include
+                                a default: case */
+    }
+
+    /// <summary>
+    /// grpc_op_error from grpc/grpc.h
+    /// </summary>
+    internal enum GRPCOpError
+    {
+        /* everything went ok */
+        GRPC_OP_OK = 0,
+        /* something failed, we don't know what */
+        GRPC_OP_ERROR
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Internal/Event.cs b/src/csharp/GrpcCore/Internal/Event.cs
new file mode 100644
index 0000000..7056005
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/Event.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Runtime.InteropServices;
+using Google.GRPC.Core;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// grpc_event from grpc/grpc.h
+    /// </summary>
+    internal class EventSafeHandle : SafeHandleZeroIsInvalid
+    {
+        [DllImport("libgrpc.so")]
+        static extern void grpc_event_finish(IntPtr ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCCompletionType grpc_event_type(EventSafeHandle ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern CallSafeHandle grpc_event_call(EventSafeHandle ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCOpError grpc_event_write_accepted(EventSafeHandle ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCOpError grpc_event_finish_accepted(EventSafeHandle ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern StatusCode grpc_event_finished_status(EventSafeHandle ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern IntPtr grpc_event_finished_details(EventSafeHandle ev);  // returns const char*
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern IntPtr grpc_event_read_length(EventSafeHandle ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern void grpc_event_read_copy_to_buffer(EventSafeHandle ev, byte[] buffer, UIntPtr bufferLen);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern IntPtr grpc_event_server_rpc_new_method(EventSafeHandle ev); // returns const char*
+
+        public GRPCCompletionType GetCompletionType()
+        {
+            return grpc_event_type(this);
+        }
+
+        public GRPCOpError GetWriteAccepted()
+        {
+            return grpc_event_write_accepted(this);
+        }
+
+        public GRPCOpError GetFinishAccepted()
+        {
+            return grpc_event_finish_accepted(this);
+        }
+
+        public Status GetFinished()
+        {
+            // TODO: can the native method return string directly?
+            string details = Marshal.PtrToStringAnsi(grpc_event_finished_details(this));
+            return new Status(grpc_event_finished_status(this), details);
+        }
+
+        public byte[] GetReadData()
+        {
+            IntPtr len = grpc_event_read_length(this);
+            if (len == new IntPtr(-1))
+            {
+                return null;
+            }
+            byte[] data = new byte[(int) len];
+            grpc_event_read_copy_to_buffer(this, data, new UIntPtr((ulong)data.Length));
+            return data;
+        }
+
+        public CallSafeHandle GetCall() {
+            return grpc_event_call(this);
+        }
+
+        public string GetServerRpcNewMethod() {
+            // TODO: can the native method return string directly?
+            return Marshal.PtrToStringAnsi(grpc_event_server_rpc_new_method(this));
+        }
+
+        //TODO: client_metadata_read event type
+
+        protected override bool ReleaseHandle()
+        {
+            grpc_event_finish(handle);
+            return true;
+        }
+    }
+
+    // TODO: this is basically c&p of EventSafeHandle. Unify!
+    /// <summary>
+    /// Not owned version of 
+    /// grpc_event from grpc/grpc.h
+    /// </summary>
+    internal class EventSafeHandleNotOwned : SafeHandleZeroIsInvalid
+    {
+        [DllImport("libgrpc.so")]
+        static extern void grpc_event_finish(IntPtr ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCCompletionType grpc_event_type(EventSafeHandleNotOwned ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern CallSafeHandle grpc_event_call(EventSafeHandleNotOwned ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCOpError grpc_event_write_accepted(EventSafeHandleNotOwned ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCOpError grpc_event_finish_accepted(EventSafeHandleNotOwned ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern StatusCode grpc_event_finished_status(EventSafeHandleNotOwned ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern IntPtr grpc_event_finished_details(EventSafeHandleNotOwned ev);  // returns const char*
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern IntPtr grpc_event_read_length(EventSafeHandleNotOwned ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern void grpc_event_read_copy_to_buffer(EventSafeHandleNotOwned ev, byte[] buffer, UIntPtr bufferLen);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern IntPtr grpc_event_server_rpc_new_method(EventSafeHandleNotOwned ev); // returns const char*
+
+        public EventSafeHandleNotOwned() : base(false)
+        {
+        }
+
+        public EventSafeHandleNotOwned(IntPtr handle) : base(false)
+        {
+            SetHandle(handle);
+        }
+
+        public GRPCCompletionType GetCompletionType()
+        {
+            return grpc_event_type(this);
+        }
+
+        public GRPCOpError GetWriteAccepted()
+        {
+            return grpc_event_write_accepted(this);
+        }
+
+        public GRPCOpError GetFinishAccepted()
+        {
+            return grpc_event_finish_accepted(this);
+        }
+
+        public Status GetFinished()
+        {
+            // TODO: can the native method return string directly?
+            string details = Marshal.PtrToStringAnsi(grpc_event_finished_details(this));
+            return new Status(grpc_event_finished_status(this), details);
+        }
+
+        public byte[] GetReadData()
+        {
+            IntPtr len = grpc_event_read_length(this);
+            if (len == new IntPtr(-1))
+            {
+                return null;
+            }
+            byte[] data = new byte[(int) len];
+            grpc_event_read_copy_to_buffer(this, data, new UIntPtr((ulong)data.Length));
+            return data;
+        }
+
+        public CallSafeHandle GetCall() {
+            return grpc_event_call(this);
+        }
+
+        public string GetServerRpcNewMethod() {
+            // TODO: can the native method return string directly?
+            return Marshal.PtrToStringAnsi(grpc_event_server_rpc_new_method(this));
+        }
+
+        //TODO: client_metadata_read event type
+
+        protected override bool ReleaseHandle()
+        {
+            grpc_event_finish(handle);
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/Internal/GrpcThreadPool.cs b/src/csharp/GrpcCore/Internal/GrpcThreadPool.cs
new file mode 100644
index 0000000..1139e54
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/GrpcThreadPool.cs
@@ -0,0 +1,129 @@
+using System;
+using Google.GRPC.Core.Internal;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// Pool of threads polling on the same completion queue.
+    /// </summary>
+    internal class GrpcThreadPool
+    {
+        readonly object myLock = new object();
+        readonly List<Thread> threads = new List<Thread>();
+        readonly int poolSize;
+        readonly Action<EventSafeHandle> eventHandler;
+
+        CompletionQueueSafeHandle cq;
+
+        public GrpcThreadPool(int poolSize) {
+            this.poolSize = poolSize;
+        }
+
+        internal GrpcThreadPool(int poolSize, Action<EventSafeHandle> eventHandler) {
+            this.poolSize = poolSize;
+            this.eventHandler = eventHandler;
+        }
+
+        public void Start() {
+
+            lock (myLock)
+            {
+                if (cq != null)
+                {
+                    throw new InvalidOperationException("Already started.");
+                }
+
+                cq = CompletionQueueSafeHandle.Create();
+
+                for (int i = 0; i < poolSize; i++)
+                {
+                    threads.Add(CreateAndStartThread(i));
+                }
+            }
+        }
+
+        public void Stop() {
+
+            lock (myLock)
+            {
+                cq.Shutdown();
+
+                Console.WriteLine("Waiting for GPRC threads to finish.");
+                foreach (var thread in threads)
+                {
+                    thread.Join();
+                }
+
+                cq.Dispose();
+
+            }
+        }
+
+        internal CompletionQueueSafeHandle CompletionQueue
+        {
+            get
+            {
+                return cq;
+            }
+        }
+
+        private Thread CreateAndStartThread(int i) {
+            Action body;
+            if (eventHandler != null)
+            {
+                body = ThreadBodyWithHandler;
+            }
+            else
+            {
+                body = ThreadBodyNoHandler;
+            }
+            var thread = new Thread(new ThreadStart(body));
+            thread.IsBackground = false;
+            thread.Start();
+            if (eventHandler != null)
+            {
+                thread.Name = "grpc_server_newrpc " + i;
+            }
+            else
+            {
+                thread.Name = "grpc " + i;
+            }
+            return thread;
+        }
+
+        /// <summary>
+        /// Body of the polling thread.
+        /// </summary>
+        private void ThreadBodyNoHandler()
+        {
+            GRPCCompletionType completionType;
+            do
+            {
+                completionType = cq.NextWithCallback();
+            } while(completionType != GRPCCompletionType.GRPC_QUEUE_SHUTDOWN);
+            Console.WriteLine("Completion queue has shutdown successfully, thread " + Thread.CurrentThread.Name + " exiting.");
+        }
+
+        /// <summary>
+        /// Body of the polling thread.
+        /// </summary>
+        private void ThreadBodyWithHandler()
+        {
+            GRPCCompletionType completionType;
+            do
+            {
+                using (EventSafeHandle ev = cq.Next(Timespec.InfFuture)) {
+                    completionType = ev.GetCompletionType();
+                    eventHandler(ev);
+                }
+            } while(completionType != GRPCCompletionType.GRPC_QUEUE_SHUTDOWN);
+            Console.WriteLine("Completion queue has shutdown successfully, thread " + Thread.CurrentThread.Name + " exiting.");
+        }
+    }
+
+}
+
diff --git a/src/csharp/GrpcCore/Internal/SafeHandleZeroIsInvalid.cs b/src/csharp/GrpcCore/Internal/SafeHandleZeroIsInvalid.cs
new file mode 100644
index 0000000..5a1252b
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/SafeHandleZeroIsInvalid.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// Safe handle to wrap native objects.
+    /// </summary>
+    internal abstract class SafeHandleZeroIsInvalid : SafeHandle
+    {
+        public SafeHandleZeroIsInvalid() : base(IntPtr.Zero, true)
+        {
+        }
+
+        public SafeHandleZeroIsInvalid(bool ownsHandle) : base(IntPtr.Zero, ownsHandle)
+        {
+        }
+
+        public override bool IsInvalid
+        {
+            get
+            {
+                return handle == IntPtr.Zero;
+            }
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs b/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs
new file mode 100644
index 0000000..08d4cf0
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+using System.Collections.Concurrent;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// grpc_server from grpc/grpc.h
+    /// </summary>
+    internal sealed class ServerSafeHandle : SafeHandleZeroIsInvalid
+    {
+        [DllImport("libgrpc.so", EntryPoint = "grpc_server_request_call_old")]
+        static extern GRPCCallError grpc_server_request_call_old_CALLBACK(ServerSafeHandle server, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback);
+
+        [DllImport("libgrpc.so")]
+        static extern ServerSafeHandle grpc_server_create(CompletionQueueSafeHandle cq, IntPtr args);
+
+        // TODO: check int representation size
+        [DllImport("libgrpc.so")]
+        static extern int grpc_server_add_http2_port(ServerSafeHandle server, string addr);
+
+        // TODO: check int representation size
+        [DllImport("libgrpc.so")]
+        static extern int grpc_server_add_secure_http2_port(ServerSafeHandle server, string addr);
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_server_start(ServerSafeHandle server);
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_server_shutdown(ServerSafeHandle server);
+
+        [DllImport("libgrpc.so", EntryPoint = "grpc_server_shutdown_and_notify")]
+        static extern void grpc_server_shutdown_and_notify_CALLBACK(ServerSafeHandle server, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback);
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_server_destroy(IntPtr server);
+
+        private ServerSafeHandle()
+        {
+        }
+
+        public static ServerSafeHandle NewServer(CompletionQueueSafeHandle cq, IntPtr args)
+        {
+            // TODO: also grpc_secure_server_create...
+            return grpc_server_create(cq, args);
+        }
+
+        public int AddPort(string addr)
+        {
+            // TODO: also grpc_server_add_secure_http2_port...
+            return grpc_server_add_http2_port(this, addr);
+        }
+
+        public void Start()
+        {
+            grpc_server_start(this);
+        }
+
+        public void Shutdown()
+        {
+            grpc_server_shutdown(this);
+        }
+
+        public void ShutdownAndNotify(EventCallbackDelegate callback)
+        {
+            grpc_server_shutdown_and_notify_CALLBACK(this, callback);
+        }
+
+        public GRPCCallError RequestCall(EventCallbackDelegate callback)
+        {
+            return grpc_server_request_call_old_CALLBACK(this, callback);
+        }
+
+        protected override bool ReleaseHandle()
+        {
+            grpc_server_destroy(handle);
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/Internal/ServerWritingObserver.cs b/src/csharp/GrpcCore/Internal/ServerWritingObserver.cs
new file mode 100644
index 0000000..2b46e9c
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/ServerWritingObserver.cs
@@ -0,0 +1,38 @@
+using System;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// Observer that writes all arriving messages to a call abstraction (in blocking fashion)
+    /// and then halfcloses the call. Used for server-side call handling.
+    /// </summary>
+    internal class ServerWritingObserver<TWrite, TRead> : IObserver<TWrite>
+	{
+        readonly AsyncCall<TWrite, TRead> call;
+
+        public ServerWritingObserver(AsyncCall<TWrite, TRead> call)
+		{
+            this.call = call;
+		}
+
+		public void OnCompleted()
+		{
+            // TODO: how bad is the Wait here?
+            call.WriteStatusAsync(new Status(StatusCode.GRPC_STATUS_OK, "")).Wait();
+		}
+
+		public void OnError(Exception error)
+		{
+            // TODO: handle this...
+			throw new InvalidOperationException("This should never be called.");
+		}
+
+		public void OnNext(TWrite value)
+		{
+            // TODO: how bad is the Wait here?
+            call.WriteAsync(value).Wait();
+		}
+	}
+}
+
diff --git a/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs b/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs
new file mode 100644
index 0000000..c5de979
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs
@@ -0,0 +1,33 @@
+using System;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core.Internal
+{
+    internal class StreamingInputObserver<TWrite, TRead> : IObserver<TWrite>
+	{
+        readonly AsyncCall<TWrite, TRead> call;
+
+        public StreamingInputObserver(AsyncCall<TWrite, TRead> call)
+		{
+            this.call = call;
+		}
+
+		public void OnCompleted()
+		{
+            // TODO: how bad is the Wait here?
+            call.WritesCompletedAsync().Wait();
+		}
+
+		public void OnError(Exception error)
+		{
+			throw new InvalidOperationException("This should never be called.");
+		}
+
+		public void OnNext(TWrite value)
+		{
+            // TODO: how bad is the Wait here?
+            call.WriteAsync(value).Wait();
+		}
+	}
+}
+
diff --git a/src/csharp/GrpcCore/Internal/Timespec.cs b/src/csharp/GrpcCore/Internal/Timespec.cs
new file mode 100644
index 0000000..8ffaf70
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/Timespec.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Google.GRPC.Core.Internal
+{
+	/// <summary>
+	/// gpr_timespec from grpc/support/time.h
+	/// </summary>
+	[StructLayout(LayoutKind.Sequential)]
+	internal struct Timespec
+	{
+        const int nanosPerSecond = 1000 * 1000 * 1000;
+        const int nanosPerTick = 100;
+
+        [DllImport("libgpr.so")]
+        static extern Timespec gpr_now();
+
+		// TODO: this only works on 64bit linux, can we autoselect the right size of ints?
+		// perhaps using IntPtr would work.
+		public System.Int64 tv_sec;
+		public System.Int64 tv_nsec;
+
+		/// <summary>
+		/// Timespec a long time in the future.
+		/// </summary>
+		public static Timespec InfFuture
+		{
+			get
+			{
+				// TODO: set correct value based on the length of the struct
+				return new Timespec { tv_sec = Int32.MaxValue, tv_nsec = 0 };
+			}
+		}
+
+        public static Timespec Now
+        {
+            get
+            {
+                return gpr_now();
+            }
+        }
+
+        /// <summary>
+        /// Creates a GPR deadline from current instant and given timeout.
+        /// </summary>
+        /// <returns>The from timeout.</returns>
+        public static Timespec DeadlineFromTimeout(TimeSpan timeout) {
+            if (timeout == Timeout.InfiniteTimeSpan)
+            {
+                return Timespec.InfFuture;
+            }
+            return Timespec.Now.Add(timeout);
+        }
+
+        public Timespec Add(TimeSpan timeSpan) {
+            long nanos = tv_nsec + (timeSpan.Ticks % TimeSpan.TicksPerSecond) * nanosPerTick;
+            long overflow_sec = (nanos > nanosPerSecond) ? 1 : 0;
+
+            Timespec result;
+            result.tv_nsec = nanos % nanosPerSecond;
+            result.tv_sec = tv_sec + (timeSpan.Ticks / TimeSpan.TicksPerSecond) + overflow_sec; 
+            return result;
+        }
+	}
+}
+
diff --git a/src/csharp/GrpcCore/Method.cs b/src/csharp/GrpcCore/Method.cs
new file mode 100644
index 0000000..2790115
--- /dev/null
+++ b/src/csharp/GrpcCore/Method.cs
@@ -0,0 +1,64 @@
+using System;
+
+namespace Google.GRPC.Core
+{
+    public enum MethodType
+    {
+        Unary,
+        ClientStreaming,
+        ServerStreaming,
+        DuplexStreaming
+    }
+
+    /// <summary>
+    /// A description of a service method.
+    /// </summary>
+    public class Method<TRequest, TResponse>
+    {
+        readonly MethodType type;
+        readonly string name;
+        readonly IMarshaller<TRequest> requestMarshaller;
+        readonly IMarshaller<TResponse> responseMarshaller;
+
+        public Method(MethodType type, string name, IMarshaller<TRequest> requestMarshaller, IMarshaller<TResponse> responseMarshaller)
+        {
+            this.type = type;
+            this.name = name;
+            this.requestMarshaller = requestMarshaller;
+            this.responseMarshaller = responseMarshaller;
+        }
+
+        public MethodType Type
+        {
+            get
+            {
+                return this.type;
+            }
+        }
+
+        public string Name
+        {
+            get
+            {
+                return this.name;
+            }
+        }
+
+        public IMarshaller<TRequest> RequestMarshaller
+        {
+            get
+            {
+                return this.requestMarshaller;
+            }
+        }
+
+        public IMarshaller<TResponse> ResponseMarshaller
+        {
+            get
+            {
+                return this.responseMarshaller;
+            }
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Properties/AssemblyInfo.cs b/src/csharp/GrpcCore/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..74aba25
--- /dev/null
+++ b/src/csharp/GrpcCore/Properties/AssemblyInfo.cs
@@ -0,0 +1,24 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes. 
+// Change them to the values specific to your project.
+[assembly: AssemblyTitle ("GrpcCore")]
+[assembly: AssemblyDescription ("")]
+[assembly: AssemblyConfiguration ("")]
+[assembly: AssemblyCompany ("")]
+[assembly: AssemblyProduct ("")]
+[assembly: AssemblyCopyright ("jtattermusch")]
+[assembly: AssemblyTrademark ("")]
+[assembly: AssemblyCulture ("")]
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+[assembly: AssemblyVersion ("1.0.*")]
+// The following attributes are used to specify the signing key for the assembly, 
+// if desired. See the Mono documentation for more information about signing.
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
+[assembly: InternalsVisibleTo("GrpcCoreTests")]
+
diff --git a/src/csharp/GrpcCore/RpcException.cs b/src/csharp/GrpcCore/RpcException.cs
new file mode 100644
index 0000000..8811c3a
--- /dev/null
+++ b/src/csharp/GrpcCore/RpcException.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Google.GRPC.Core
+{
+    public class RpcException : Exception
+    {
+        private readonly Status status;
+
+        public RpcException(Status status)
+        {
+            this.status = status;
+        }
+
+        public RpcException(Status status, string message) : base(message)
+        {
+            this.status = status;
+        }
+
+        public Status Status {
+            get
+            {
+                return status;
+            }
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Server.cs b/src/csharp/GrpcCore/Server.cs
new file mode 100644
index 0000000..4e9d114
--- /dev/null
+++ b/src/csharp/GrpcCore/Server.cs
@@ -0,0 +1,174 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core
+{
+    /// <summary>
+    /// Server is implemented only to be able to do
+    /// in-process testing.
+    /// </summary>
+    public class Server
+    {
+        // TODO: make sure the delegate doesn't get garbage collected while 
+        // native callbacks are in the completion queue.
+        readonly EventCallbackDelegate newRpcHandler;
+        readonly EventCallbackDelegate serverShutdownHandler;
+
+        readonly BlockingCollection<NewRpcInfo> newRpcQueue = new BlockingCollection<NewRpcInfo>();
+        readonly ServerSafeHandle handle;
+
+        readonly Dictionary<string, IServerCallHandler> callHandlers = new Dictionary<string, IServerCallHandler>();
+
+        readonly TaskCompletionSource<object> shutdownTcs = new TaskCompletionSource<object>();
+
+        static Server() {
+            GrpcEnvironment.EnsureInitialized();
+        }
+
+        public Server()
+        {
+            // TODO: what is the tag for server shutdown?
+            this.handle = ServerSafeHandle.NewServer(GetCompletionQueue(), IntPtr.Zero);
+            this.newRpcHandler = HandleNewRpc;
+            this.serverShutdownHandler = HandleServerShutdown;
+        }
+
+        // only call before Start(), this will be in server builder in the future.
+        internal void AddCallHandler(string methodName, IServerCallHandler handler) {
+            callHandlers.Add(methodName, handler);
+        }
+        // only call before Start()
+        public int AddPort(string addr) {
+            return handle.AddPort(addr);
+        }
+
+        public void Start()
+        {
+            handle.Start();
+
+            // TODO: this basically means the server is single threaded....
+            StartHandlingRpcs();
+        }
+
+        /// <summary>
+        /// Requests and handles single RPC call.
+        /// </summary>
+        internal void RunRpc()
+        {
+            AllowOneRpc();
+         
+            try
+            {
+                var rpcInfo = newRpcQueue.Take();
+
+                Console.WriteLine("Server received RPC " + rpcInfo.Method);
+
+                IServerCallHandler callHandler;
+                if (!callHandlers.TryGetValue(rpcInfo.Method, out callHandler))
+                {
+                    callHandler = new NoSuchMethodCallHandler();
+                } 
+                callHandler.StartCall(rpcInfo.Method, rpcInfo.Call, GetCompletionQueue());
+            }
+            catch(Exception e)
+            {
+                Console.WriteLine("Exception while handling RPC: " + e);
+            }
+        }
+
+        /// <summary>
+        /// Requests server shutdown and when there are no more calls being serviced,
+        /// cleans up used resources.
+        /// </summary>
+        /// <returns>The async.</returns>
+        public async Task ShutdownAsync() {
+            handle.ShutdownAndNotify(serverShutdownHandler);
+            await shutdownTcs.Task;
+            handle.Dispose();
+        }
+
+        public void Kill() {
+            handle.Dispose();
+        }
+
+        private async Task StartHandlingRpcs() {
+            while (true)
+            {
+                await Task.Factory.StartNew(RunRpc);
+            }
+        }
+
+        private void AllowOneRpc()
+        {
+            AssertCallOk(handle.RequestCall(newRpcHandler));
+        }
+
+        private void HandleNewRpc(IntPtr eventPtr)
+        {
+            try
+            {
+                var ev = new EventSafeHandleNotOwned(eventPtr);
+                newRpcQueue.Add(new NewRpcInfo(ev.GetCall(), ev.GetServerRpcNewMethod()));
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine("Caught exception in a native handler: " + e);
+            }
+        }
+
+        private void HandleServerShutdown(IntPtr eventPtr)
+        {
+            try
+            {
+                shutdownTcs.SetResult(null);
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine("Caught exception in a native handler: " + e);
+            }
+        }
+
+        private static void AssertCallOk(GRPCCallError callError)
+        {
+            Trace.Assert(callError == GRPCCallError.GRPC_CALL_OK, "Status not GRPC_CALL_OK");
+        }
+
+        private static CompletionQueueSafeHandle GetCompletionQueue()
+        {
+            return GrpcEnvironment.ThreadPool.CompletionQueue;
+        }
+
+        private struct NewRpcInfo
+        {
+            private CallSafeHandle call;
+            private string method;
+
+            public NewRpcInfo(CallSafeHandle call, string method)
+            {
+                this.call = call;
+                this.method = method;
+            }
+
+            public CallSafeHandle Call
+            {
+                get
+                {
+                    return this.call;
+                }
+            }
+
+            public string Method
+            {
+                get
+                {
+                    return this.method;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/ServerCallHandler.cs b/src/csharp/GrpcCore/ServerCallHandler.cs
new file mode 100644
index 0000000..08d527a
--- /dev/null
+++ b/src/csharp/GrpcCore/ServerCallHandler.cs
@@ -0,0 +1,93 @@
+using System;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core
+{
+    internal interface IServerCallHandler
+    {
+        void StartCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq);
+    }
+
+    internal class UnaryRequestServerCallHandler<TRequest, TResponse> : IServerCallHandler
+    {
+        readonly Method<TRequest, TResponse> method;
+        readonly UnaryRequestServerMethod<TRequest, TResponse> handler;
+
+        public UnaryRequestServerCallHandler(Method<TRequest, TResponse> method, UnaryRequestServerMethod<TRequest, TResponse> handler)
+        {
+            this.method = method;
+            this.handler = handler;
+        }
+
+        public void StartCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq)
+        {
+            var asyncCall = new AsyncCall<TResponse, TRequest>(
+                (msg) => method.ResponseMarshaller.Serialize(msg),
+                (payload) => method.RequestMarshaller.Deserialize(payload));
+
+            asyncCall.InitializeServer(call);
+            asyncCall.Accept(cq);
+           
+            var request = asyncCall.ReadAsync().Result;
+
+            var responseObserver = new ServerWritingObserver<TResponse, TRequest>(asyncCall);
+            handler(request, responseObserver);
+
+            asyncCall.Halfclosed.Wait();
+            // TODO: wait until writing is finished
+
+            asyncCall.WriteStatusAsync(new Status(StatusCode.GRPC_STATUS_OK, "")).Wait();
+            asyncCall.Finished.Wait();
+        }
+    }
+
+    internal class StreamingRequestServerCallHandler<TRequest, TResponse> : IServerCallHandler
+    {
+        readonly Method<TRequest, TResponse> method;
+        readonly StreamingRequestServerMethod<TRequest, TResponse> handler;
+
+        public StreamingRequestServerCallHandler(Method<TRequest, TResponse> method, StreamingRequestServerMethod<TRequest, TResponse> handler)
+        {
+            this.method = method;
+            this.handler = handler;
+        }
+
+        public void StartCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq)
+        {
+            var asyncCall = new AsyncCall<TResponse, TRequest>(
+                (msg) => method.ResponseMarshaller.Serialize(msg),
+                (payload) => method.RequestMarshaller.Deserialize(payload));
+
+            asyncCall.InitializeServer(call);
+            asyncCall.Accept(cq);
+
+            var responseObserver = new ServerWritingObserver<TResponse, TRequest>(asyncCall);
+            var requestObserver = handler(responseObserver);
+
+            // feed the requests
+            asyncCall.StartReadingToStream(requestObserver);
+
+            asyncCall.Halfclosed.Wait();
+
+            asyncCall.WriteStatusAsync(new Status(StatusCode.GRPC_STATUS_OK, "")).Wait();
+            asyncCall.Finished.Wait();
+        }
+    }
+
+    internal class NoSuchMethodCallHandler : IServerCallHandler
+    {
+        public void StartCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq)
+        {
+            // We don't care about the payload type here.
+            AsyncCall<byte[], byte[]> asyncCall = new AsyncCall<byte[], byte[]>(
+                (payload) => payload, (payload) => payload);
+
+            asyncCall.InitializeServer(call);
+            asyncCall.Accept(cq);
+            asyncCall.WriteStatusAsync(new Status(StatusCode.GRPC_STATUS_UNIMPLEMENTED, "No such method.")).Wait();
+
+            asyncCall.Finished.Wait();
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/ServerCalls.cs b/src/csharp/GrpcCore/ServerCalls.cs
new file mode 100644
index 0000000..86c4718
--- /dev/null
+++ b/src/csharp/GrpcCore/ServerCalls.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Google.GRPC.Core
+{
+    // TODO: perhaps add also serverSideStreaming and clientSideStreaming
+
+    public delegate void UnaryRequestServerMethod<TRequest, TResponse> (TRequest request, IObserver<TResponse> responseObserver);
+
+    public delegate IObserver<TRequest> StreamingRequestServerMethod<TRequest, TResponse> (IObserver<TResponse> responseObserver);
+
+    internal static class ServerCalls {
+
+        public static IServerCallHandler UnaryRequestCall<TRequest, TResponse>(Method<TRequest, TResponse> method, UnaryRequestServerMethod<TRequest, TResponse> handler)
+        {
+            return new UnaryRequestServerCallHandler<TRequest, TResponse>(method, handler);
+        }
+
+        public static IServerCallHandler StreamingRequestCall<TRequest, TResponse>(Method<TRequest, TResponse> method, StreamingRequestServerMethod<TRequest, TResponse> handler)
+        {
+            return new StreamingRequestServerCallHandler<TRequest, TResponse>(method, handler);
+        }
+
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Status.cs b/src/csharp/GrpcCore/Status.cs
new file mode 100644
index 0000000..f1212f8
--- /dev/null
+++ b/src/csharp/GrpcCore/Status.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Google.GRPC.Core
+{
+	/// <summary>
+	/// Represents RPC result.
+	/// </summary>
+	public struct Status
+	{
+		readonly StatusCode statusCode;
+		readonly string detail;
+
+		public Status(StatusCode statusCode, string detail)
+		{
+			this.statusCode = statusCode;
+			this.detail = detail;
+		}
+
+		public StatusCode StatusCode
+		{
+			get
+			{
+				return statusCode;
+			}
+		}
+
+		public string Detail
+		{
+			get
+			{
+				return detail;
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/StatusCode.cs b/src/csharp/GrpcCore/StatusCode.cs
new file mode 100644
index 0000000..80fc8bd
--- /dev/null
+++ b/src/csharp/GrpcCore/StatusCode.cs
@@ -0,0 +1,150 @@
+using System;
+
+namespace Google.GRPC.Core
+{
+    // TODO: element names should changed to comply with C# naming conventions.
+    /// <summary>
+    /// grpc_status_code from grpc/status.h
+    /// </summary>
+    public enum StatusCode
+    {
+        /* Not an error; returned on success
+
+     HTTP Mapping: 200 OK */
+        GRPC_STATUS_OK = 0,
+        /* The operation was cancelled (typically by the caller).
+
+     HTTP Mapping: 499 Client Closed Request */
+        GRPC_STATUS_CANCELLED = 1,
+        /* Unknown error.  An example of where this error may be returned is
+     if a Status value received from another address space belongs to
+     an error-space that is not known in this address space.  Also
+     errors raised by APIs that do not return enough error information
+     may be converted to this error.
+
+     HTTP Mapping: 500 Internal Server Error */
+        GRPC_STATUS_UNKNOWN = 2,
+        /* Client specified an invalid argument.  Note that this differs
+     from FAILED_PRECONDITION.  INVALID_ARGUMENT indicates arguments
+     that are problematic regardless of the state of the system
+     (e.g., a malformed file name).
+
+     HTTP Mapping: 400 Bad Request */
+        GRPC_STATUS_INVALID_ARGUMENT = 3,
+        /* Deadline expired before operation could complete.  For operations
+     that change the state of the system, this error may be returned
+     even if the operation has completed successfully.  For example, a
+     successful response from a server could have been delayed long
+     enough for the deadline to expire.
+
+     HTTP Mapping: 504 Gateway Timeout */
+        GRPC_STATUS_DEADLINE_EXCEEDED = 4,
+        /* Some requested entity (e.g., file or directory) was not found.
+
+     HTTP Mapping: 404 Not Found */
+        GRPC_STATUS_NOT_FOUND = 5,
+        /* Some entity that we attempted to create (e.g., file or directory)
+     already exists.
+
+     HTTP Mapping: 409 Conflict */
+        GRPC_STATUS_ALREADY_EXISTS = 6,
+        /* The caller does not have permission to execute the specified
+     operation.  PERMISSION_DENIED must not be used for rejections
+     caused by exhausting some resource (use RESOURCE_EXHAUSTED
+     instead for those errors).  PERMISSION_DENIED must not be
+     used if the caller can not be identified (use UNAUTHENTICATED
+     instead for those errors).
+
+     HTTP Mapping: 403 Forbidden */
+        GRPC_STATUS_PERMISSION_DENIED = 7,
+        /* The request does not have valid authentication credentials for the
+     operation.
+
+     HTTP Mapping: 401 Unauthorized */
+        GRPC_STATUS_UNAUTHENTICATED = 16,
+        /* Some resource has been exhausted, perhaps a per-user quota, or
+     perhaps the entire file system is out of space.
+
+     HTTP Mapping: 429 Too Many Requests */
+        GRPC_STATUS_RESOURCE_EXHAUSTED = 8,
+        /* Operation was rejected because the system is not in a state
+     required for the operation's execution.  For example, directory
+     to be deleted may be non-empty, an rmdir operation is applied to
+     a non-directory, etc.
+
+     A litmus test that may help a service implementor in deciding
+     between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
+      (a) Use UNAVAILABLE if the client can retry just the failing call.
+      (b) Use ABORTED if the client should retry at a higher-level
+          (e.g., restarting a read-modify-write sequence).
+      (c) Use FAILED_PRECONDITION if the client should not retry until
+          the system state has been explicitly fixed.  E.g., if an "rmdir"
+          fails because the directory is non-empty, FAILED_PRECONDITION
+          should be returned since the client should not retry unless
+          they have first fixed up the directory by deleting files from it.
+      (d) Use FAILED_PRECONDITION if the client performs conditional
+          REST Get/Update/Delete on a resource and the resource on the
+          server does not match the condition. E.g., conflicting
+          read-modify-write on the same resource.
+
+     HTTP Mapping: 400 Bad Request
+
+     NOTE: HTTP spec says 412 Precondition Failed should only be used if
+     the request contains Etag related headers. So if the server does see
+     Etag related headers in the request, it may choose to return 412
+     instead of 400 for this error code. */
+        GRPC_STATUS_FAILED_PRECONDITION = 9,
+        /* The operation was aborted, typically due to a concurrency issue
+     like sequencer check failures, transaction aborts, etc.
+
+     See litmus test above for deciding between FAILED_PRECONDITION,
+     ABORTED, and UNAVAILABLE.
+
+     HTTP Mapping: 409 Conflict */
+        GRPC_STATUS_ABORTED = 10,
+        /* Operation was attempted past the valid range.  E.g., seeking or
+     reading past end of file.
+
+     Unlike INVALID_ARGUMENT, this error indicates a problem that may
+     be fixed if the system state changes. For example, a 32-bit file
+     system will generate INVALID_ARGUMENT if asked to read at an
+     offset that is not in the range [0,2^32-1], but it will generate
+     OUT_OF_RANGE if asked to read from an offset past the current
+     file size.
+
+     There is a fair bit of overlap between FAILED_PRECONDITION and
+     OUT_OF_RANGE.  We recommend using OUT_OF_RANGE (the more specific
+     error) when it applies so that callers who are iterating through
+     a space can easily look for an OUT_OF_RANGE error to detect when
+     they are done.
+
+     HTTP Mapping: 400 Bad Request */
+        GRPC_STATUS_OUT_OF_RANGE = 11,
+        /* Operation is not implemented or not supported/enabled in this service.
+
+     HTTP Mapping: 501 Not Implemented */
+        GRPC_STATUS_UNIMPLEMENTED = 12,
+        /* Internal errors.  Means some invariants expected by underlying
+     system has been broken.  If you see one of these errors,
+     something is very broken.
+
+     HTTP Mapping: 500 Internal Server Error */
+        GRPC_STATUS_INTERNAL = 13,
+        /* The service is currently unavailable.  This is a most likely a
+     transient condition and may be corrected by retrying with
+     a backoff.
+
+     See litmus test above for deciding between FAILED_PRECONDITION,
+     ABORTED, and UNAVAILABLE.
+
+     HTTP Mapping: 503 Service Unavailable */
+        GRPC_STATUS_UNAVAILABLE = 14,
+        /* Unrecoverable data loss or corruption.
+
+     HTTP Mapping: 500 Internal Server Error */
+        GRPC_STATUS_DATA_LOSS = 15,
+        /* Force users to include a default branch: */
+        GRPC_STATUS__DO_NOT_USE = -1
+    }
+}
+
diff --git a/src/csharp/GrpcCoreTests/.gitignore b/src/csharp/GrpcCoreTests/.gitignore
new file mode 100644
index 0000000..2cc8cca
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/.gitignore
@@ -0,0 +1,2 @@
+test-results
+bin
diff --git a/src/csharp/GrpcCoreTests/ClientServerTest.cs b/src/csharp/GrpcCoreTests/ClientServerTest.cs
new file mode 100644
index 0000000..511683b
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/ClientServerTest.cs
@@ -0,0 +1,55 @@
+using System;
+using NUnit.Framework;
+using Google.GRPC.Core.Internal;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Google.GRPC.Core.Tests
+{
+    public class ClientServerTest
+    {
+        string serverAddr = "localhost:" + Utils.PickUnusedPort();
+
+        private Method<string, string> unaryEchoStringMethod = new Method<string, string>(
+            MethodType.Unary,
+            "/tests.Test/UnaryEchoString",
+            new StringMarshaller(),
+            new StringMarshaller());
+
+        [Test]
+        public void EmptyCall()
+        {
+            Server server = new Server();
+
+            server.AddCallHandler(unaryEchoStringMethod.Name, 
+                                  ServerCalls.UnaryRequestCall(unaryEchoStringMethod, HandleUnaryEchoString));
+
+            server.AddPort(serverAddr);
+            server.Start();
+
+            using (Channel channel = new Channel(serverAddr))
+            {
+                var call = CreateUnaryEchoStringCall(channel);
+
+                Assert.AreEqual("ABC", Calls.BlockingUnaryCall(call, "ABC", default(CancellationToken)));
+                Assert.AreEqual("abcdef", Calls.BlockingUnaryCall(call, "abcdef", default(CancellationToken)));
+            }
+         
+            server.ShutdownAsync().Wait();
+
+            GrpcEnvironment.Shutdown();
+        }
+
+        private Call<string, string> CreateUnaryEchoStringCall(Channel channel)
+        {
+            return new Call<string, string>(unaryEchoStringMethod, channel);
+        }
+
+        private void HandleUnaryEchoString(string request, IObserver<string> responseObserver) {
+            responseObserver.OnNext(request);
+            responseObserver.OnCompleted();
+        }
+
+    }
+}
+
diff --git a/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj b/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj
new file mode 100644
index 0000000..3de0f58
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{86EC5CB4-4EA2-40A2-8057-86542A0353BB}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <RootNamespace>GrpcCoreTests</RootNamespace>
+    <AssemblyName>GrpcCoreTests</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="nunit.framework, Version=2.6.0.0, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77">
+      <Private>False</Private>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ClientServerTest.cs" />
+    <Compile Include="ServerTest.cs" />
+    <Compile Include="Utils.cs" />
+    <Compile Include="GrpcEnvironmentTest.cs" />
+    <Compile Include="TimespecTest.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <ProjectReference Include="..\GrpcCore\GrpcCore.csproj">
+      <Project>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</Project>
+      <Name>GrpcCore</Name>
+    </ProjectReference>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/csharp/GrpcCoreTests/GrpcEnvironmentTest.cs b/src/csharp/GrpcCoreTests/GrpcEnvironmentTest.cs
new file mode 100644
index 0000000..136878d
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/GrpcEnvironmentTest.cs
@@ -0,0 +1,18 @@
+using System;
+using NUnit.Framework;
+using Google.GRPC.Core;
+using System.Threading;
+
+namespace Google.GRPC.Core.Tests
+{
+    public class GrpcEnvironmentTest
+    {
+        [Test]
+        public void InitializeAndShutdownGrpcEnvironment() {
+            GrpcEnvironment.EnsureInitialized();
+            Thread.Sleep(500);
+            Assert.IsNotNull(GrpcEnvironment.ThreadPool.CompletionQueue);
+            GrpcEnvironment.Shutdown();
+        }
+    }
+}
diff --git a/src/csharp/GrpcCoreTests/Properties/AssemblyInfo.cs b/src/csharp/GrpcCoreTests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..565b1e2
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,22 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes. 
+// Change them to the values specific to your project.
+[assembly: AssemblyTitle("GrpcCoreTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("jtattermusch")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+[assembly: AssemblyVersion("1.0.*")]
+// The following attributes are used to specify the signing key for the assembly, 
+// if desired. See the Mono documentation for more information about signing.
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
diff --git a/src/csharp/GrpcCoreTests/ServerTest.cs b/src/csharp/GrpcCoreTests/ServerTest.cs
new file mode 100644
index 0000000..e6de95c
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/ServerTest.cs
@@ -0,0 +1,21 @@
+using System;
+using NUnit.Framework;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core.Tests
+{
+    public class ServerTest
+    {
+        [Test]
+        public void StartAndShutdownServer() {
+
+            Server server = new Server();
+            server.AddPort("localhost:" + Utils.PickUnusedPort());
+            server.Start();
+            server.ShutdownAsync().Wait();
+
+            GrpcEnvironment.Shutdown();
+        }
+
+    }
+}
diff --git a/src/csharp/GrpcCoreTests/TestResult.xml b/src/csharp/GrpcCoreTests/TestResult.xml
new file mode 100644
index 0000000..a5a6abd
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/TestResult.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<!--This file represents the results of running a test suite-->
+<test-results name="/usr/local/google/home/jtattermusch/github/grpc/src/csharp/GrpcCoreTests/bin/Debug/GrpcCoreTests.dll" total="3" errors="0" failures="0" not-run="0" inconclusive="0" ignored="0" skipped="0" invalid="0" date="2015-01-29" time="19:40:47">
+  <environment nunit-version="2.6.0.0" clr-version="4.0.30319.17020" os-version="Unix 3.13.0.43" platform="Unix" cwd="/usr/local/google/home/jtattermusch/github/grpc/src/csharp/GrpcCoreTests" machine-name="jtattermusch.mtv.corp.google.com" user="jtattermusch" user-domain="jtattermusch.mtv.corp.google.com" />
+  <culture-info current-culture="en-US" current-uiculture="en-US" />
+  <test-suite type="Assembly" name="/usr/local/google/home/jtattermusch/github/grpc/src/csharp/GrpcCoreTests/bin/Debug/GrpcCoreTests.dll" executed="True" result="Success" success="True" time="0.172" asserts="0">
+    <results>
+      <test-suite type="Namespace" name="Google" executed="True" result="Success" success="True" time="0.166" asserts="0">
+        <results>
+          <test-suite type="Namespace" name="GRPC" executed="True" result="Success" success="True" time="0.166" asserts="0">
+            <results>
+              <test-suite type="Namespace" name="Core" executed="True" result="Success" success="True" time="0.166" asserts="0">
+                <results>
+                  <test-suite type="Namespace" name="Tests" executed="True" result="Success" success="True" time="0.166" asserts="0">
+                    <results>
+                      <test-suite type="TestFixture" name="CallsTest" executed="True" result="Success" success="True" time="0.009" asserts="0">
+                        <results>
+                          <test-case name="Google.GRPC.Core.Tests.CallsTest.Test1" executed="True" result="Success" success="True" time="0.004" asserts="0" />
+                        </results>
+                      </test-suite>
+                      <test-suite type="TestFixture" name="ClientServerTest" executed="True" result="Success" success="True" time="0.149" asserts="0">
+                        <results>
+                          <test-case name="Google.GRPC.Core.Tests.ClientServerTest.EmptyCall" executed="True" result="Success" success="True" time="0.111" asserts="0" />
+                        </results>
+                      </test-suite>
+                      <test-suite type="TestFixture" name="ServerTest" executed="True" result="Success" success="True" time="0.001" asserts="0">
+                        <results>
+                          <test-case name="Google.GRPC.Core.Tests.ServerTest.StartAndShutdownServer" executed="True" result="Success" success="True" time="0.001" asserts="0" />
+                        </results>
+                      </test-suite>
+                    </results>
+                  </test-suite>
+                </results>
+              </test-suite>
+            </results>
+          </test-suite>
+        </results>
+      </test-suite>
+    </results>
+  </test-suite>
+</test-results>
\ No newline at end of file
diff --git a/src/csharp/GrpcCoreTests/TimespecTest.cs b/src/csharp/GrpcCoreTests/TimespecTest.cs
new file mode 100644
index 0000000..484bad7
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/TimespecTest.cs
@@ -0,0 +1,43 @@
+using System;
+using NUnit.Framework;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core.Internal.Tests
+{
+    public class TimespecTest
+    {
+        [Test]
+        public void Now()
+        {
+            var timespec = Timespec.Now;
+        }
+
+        [Test]
+        public void Add()
+        {
+            var t = new Timespec { tv_sec = 12345, tv_nsec = 123456789 };
+            var result = t.Add(TimeSpan.FromTicks(TimeSpan.TicksPerSecond * 10));
+            Assert.AreEqual(result.tv_sec, 12355);
+            Assert.AreEqual(result.tv_nsec, 123456789);
+        }
+
+        [Test]
+        public void Add_Nanos()
+        {
+            var t = new Timespec { tv_sec = 12345, tv_nsec = 123456789 };
+            var result = t.Add(TimeSpan.FromTicks(10));
+            Assert.AreEqual(result.tv_sec, 12345);
+            Assert.AreEqual(result.tv_nsec, 123456789 + 1000);
+        }
+
+        [Test]
+        public void Add_NanosOverflow()
+        {
+            var t = new Timespec { tv_sec = 12345, tv_nsec = 999999999 };
+            var result = t.Add(TimeSpan.FromTicks(TimeSpan.TicksPerSecond * 10 + 10));
+            Assert.AreEqual(result.tv_sec, 12356);
+            Assert.AreEqual(result.tv_nsec, 999);
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCoreTests/Utils.cs b/src/csharp/GrpcCoreTests/Utils.cs
new file mode 100644
index 0000000..b0c0a7b
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/Utils.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Google.GRPC.Core.Tests
+{
+    /// <summary>
+    /// Testing utils.
+    /// </summary>
+    public class Utils
+    {
+        static Random random = new Random();
+        // TODO: cleanup this code a bit
+        public static int PickUnusedPort()
+        {
+            int port;
+            do
+            {
+                port = random.Next(2000, 50000);
+
+            } while(!IsPortAvailable(port));
+            return port;
+        }
+        // TODO: cleanup this code a bit
+        public static bool IsPortAvailable(int port)
+        {
+            bool available = true;
+
+            TcpListener server = null;
+            try
+            {
+                IPAddress ipAddress = Dns.GetHostEntry("localhost").AddressList[0];
+                server = new TcpListener(ipAddress, port);
+                server.Start();
+            }
+            catch (Exception ex)
+            {
+                available = false;
+            }
+            finally
+            {
+                if (server != null)
+                {
+                    server.Stop();
+                }
+            }
+            return available;
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcDemo/.gitignore b/src/csharp/GrpcDemo/.gitignore
new file mode 100644
index 0000000..ba077a4
--- /dev/null
+++ b/src/csharp/GrpcDemo/.gitignore
@@ -0,0 +1 @@
+bin
diff --git a/src/csharp/GrpcDemo/GrpcDemo.csproj b/src/csharp/GrpcDemo/GrpcDemo.csproj
new file mode 100644
index 0000000..31ce7f1
--- /dev/null
+++ b/src/csharp/GrpcDemo/GrpcDemo.csproj
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>GrpcDemo</RootNamespace>
+    <AssemblyName>GrpcDemo</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Externalconsole>true</Externalconsole>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Externalconsole>true</Externalconsole>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <ProjectReference Include="..\GrpcApi\GrpcApi.csproj">
+      <Project>{7DC1433E-3225-42C7-B7EA-546D56E27A4B}</Project>
+      <Name>GrpcApi</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\GrpcCore\GrpcCore.csproj">
+      <Project>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</Project>
+      <Name>GrpcCore</Name>
+    </ProjectReference>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/csharp/GrpcDemo/Program.cs b/src/csharp/GrpcDemo/Program.cs
new file mode 100644
index 0000000..258762d
--- /dev/null
+++ b/src/csharp/GrpcDemo/Program.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.InteropServices;
+using Google.GRPC.Core;
+using System.Threading;
+using math;
+
+namespace Google.GRPC.Demo
+{
+	class MainClass
+    {
+		public static void Main (string[] args)
+		{
+			using (Channel channel = new Channel("127.0.0.1:23456"))
+			{
+				IMathServiceClient stub = new MathServiceClientStub(channel, Timeout.InfiniteTimeSpan);
+				Examples.DivExample(stub);
+
+                Examples.FibExample(stub);
+
+				Examples.SumExample(stub);
+
+				Examples.DivManyExample(stub);
+			}
+           
+            GrpcEnvironment.Shutdown();
+		}
+	}
+}
diff --git a/src/csharp/GrpcDemo/Properties/AssemblyInfo.cs b/src/csharp/GrpcDemo/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..b8e1406
--- /dev/null
+++ b/src/csharp/GrpcDemo/Properties/AssemblyInfo.cs
@@ -0,0 +1,22 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes. 
+// Change them to the values specific to your project.
+[assembly: AssemblyTitle ("GrpcDemo")]
+[assembly: AssemblyDescription ("")]
+[assembly: AssemblyConfiguration ("")]
+[assembly: AssemblyCompany ("")]
+[assembly: AssemblyProduct ("")]
+[assembly: AssemblyCopyright ("jtattermusch")]
+[assembly: AssemblyTrademark ("")]
+[assembly: AssemblyCulture ("")]
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+[assembly: AssemblyVersion ("1.0.*")]
+// The following attributes are used to specify the signing key for the assembly, 
+// if desired. See the Mono documentation for more information about signing.
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
diff --git a/src/csharp/README.md b/src/csharp/README.md
new file mode 100755
index 0000000..c2f988c
--- /dev/null
+++ b/src/csharp/README.md
@@ -0,0 +1,54 @@
+gRPC C#
+=======
+
+A C# implementation of gRPC, Google's RPC library.
+
+EXPERIMENTAL ONLY
+-----------------
+
+**This gRPC C# implementation is work-in-progress and is not expected to work yet.**
+
+- The implementation is a wrapper around gRPC C core library
+- Code only runs under mono currently, building gGRPC C core library under Windows
+  is in progress.
+- It is very possible that some parts of the code will be heavily refactored or
+  completely rewritten.
+
+
+INSTALLATION AND USAGE
+----------------------
+
+- Compile and install the gRPC C Core library
+```
+make shared_c
+sudo make install
+```
+
+- Prerequisites for development: Mono framework, MonoDevelop (IDE)
+```
+sudo apt-get install mono-devel
+sudo apt-get install monodevelop monodevelop-nunit
+sudo apt-get install nunit nunit-console
+```
+
+- Use MonoDevelop to open the solution Grpc.sln (you can also run unit tests
+  from there).
+
+- After building the solution with MonoDevelop, you can use
+  nunit-console to run the unit tests (currently only running one by
+  one will make them pass.
+
+```
+nunit-console GrpcCoreTests.dll
+```
+
+CONTENTS
+--------
+
+- ext:
+  The extension library that wraps C API to be more digestible by C#.
+- GrpcCore:
+  The main gRPC C# library.
+- GrpcApi:
+  API examples for math.proto.
+
diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c
new file mode 100644
index 0000000..74d11c6
--- /dev/null
+++ b/src/csharp/ext/grpc_csharp_ext.c
@@ -0,0 +1,113 @@
+#include <grpc/grpc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/slice.h>
+
+#include <string.h>
+
+grpc_byte_buffer *string_to_byte_buffer(const char *buffer, size_t len) {
+  gpr_slice slice = gpr_slice_from_copied_buffer(buffer, len);
+  grpc_byte_buffer *bb = grpc_byte_buffer_create(&slice, 1);
+  gpr_slice_unref(slice);
+  return bb;
+}
+
+void grpc_call_start_write_from_copied_buffer(grpc_call *call,
+                                              const char *buffer, size_t len,
+                                              void *tag, gpr_uint32 flags) {
+  grpc_byte_buffer *byte_buffer = string_to_byte_buffer(buffer, len);
+  GPR_ASSERT(grpc_call_start_write_old(call, byte_buffer, tag, flags) ==
+             GRPC_CALL_OK);
+  grpc_byte_buffer_destroy(byte_buffer);
+}
+
+grpc_completion_type grpc_event_type(const grpc_event *event) {
+  return event->type;
+}
+
+grpc_op_error grpc_event_write_accepted(const grpc_event *event) {
+  GPR_ASSERT(event->type == GRPC_WRITE_ACCEPTED);
+  return event->data.invoke_accepted;
+}
+
+grpc_op_error grpc_event_finish_accepted(const grpc_event *event) {
+  GPR_ASSERT(event->type == GRPC_FINISH_ACCEPTED);
+  return event->data.finish_accepted;
+}
+
+grpc_status_code grpc_event_finished_status(const grpc_event *event) {
+  GPR_ASSERT(event->type == GRPC_FINISHED);
+  return event->data.finished.status;
+}
+
+const char *grpc_event_finished_details(const grpc_event *event) {
+  GPR_ASSERT(event->type == GRPC_FINISHED);
+  return event->data.finished.details;
+}
+
+gpr_intptr grpc_event_read_length(const grpc_event *event) {
+  GPR_ASSERT(event->type == GRPC_READ);
+  if (!event->data.read) {
+    return -1;
+  }
+  return grpc_byte_buffer_length(event->data.read);
+}
+
+/*
+ * Copies data from read event to a buffer. Fatal error occurs if
+ * buffer is too small.
+ */
+void grpc_event_read_copy_to_buffer(const grpc_event *event, char *buffer,
+                                    size_t buffer_len) {
+  grpc_byte_buffer_reader *reader;
+  gpr_slice slice;
+  size_t offset = 0;
+
+  GPR_ASSERT(event->type == GRPC_READ);
+  reader = grpc_byte_buffer_reader_create(event->data.read);
+
+  GPR_ASSERT(event->data.read);
+  while (grpc_byte_buffer_reader_next(reader, &slice)) {
+    size_t len = GPR_SLICE_LENGTH(slice);
+    GPR_ASSERT(offset + len <= buffer_len);
+    memcpy(buffer + offset, GPR_SLICE_START_PTR(slice),
+           GPR_SLICE_LENGTH(slice));
+    offset += len;
+    gpr_slice_unref(slice);
+  }
+  grpc_byte_buffer_reader_destroy(reader);
+}
+
+grpc_call *grpc_event_call(const grpc_event *event) {
+  /* we only allow this for newly incoming server calls. */
+  GPR_ASSERT(event->type == GRPC_SERVER_RPC_NEW);
+  return event->call;
+}
+
+const char *grpc_event_server_rpc_new_method(const grpc_event *event) {
+  GPR_ASSERT(event->type == GRPC_SERVER_RPC_NEW);
+  return event->data.server_rpc_new.method;
+}
+
+grpc_completion_type grpc_completion_queue_next_with_callback(
+    grpc_completion_queue *cq) {
+  grpc_event *ev;
+  grpc_completion_type t;
+  void (*callback)(grpc_event *);
+
+  ev = grpc_completion_queue_next(cq, gpr_inf_future);
+  t = ev->type;
+  if (ev->tag) {
+    /* call the callback in ev->tag */
+    /* C forbids to cast object pointers to function pointers, so
+     * we cast to intptr first.
+     */
+    callback = (void (*)(grpc_event *))(gpr_intptr)ev->tag;
+    (*callback)(ev);
+  }
+  grpc_event_finish(ev);
+
+  /* return completion type to allow some handling for events that have no
+   * tag - such as GRPC_QUEUE_SHUTDOWN
+   */
+  return t;
+}
diff --git a/src/csharp/lib/Google.ProtocolBuffers.dll b/src/csharp/lib/Google.ProtocolBuffers.dll
new file mode 100755
index 0000000..ce2f466
--- /dev/null
+++ b/src/csharp/lib/Google.ProtocolBuffers.dll
Binary files differ
diff --git a/src/node/.gitignore b/src/node/.gitignore
new file mode 100644
index 0000000..e3fbd98
--- /dev/null
+++ b/src/node/.gitignore
@@ -0,0 +1,2 @@
+build
+node_modules
diff --git a/src/node/README.md b/src/node/README.md
index 55329d8..c342b7c 100644
--- a/src/node/README.md
+++ b/src/node/README.md
@@ -1,12 +1,70 @@
-# Node.js GRPC extension
+# Node.js gRPC Library
 
-The package is built with
+## Installation
 
-    node-gyp configure
-    node-gyp build
+First, clone this repository (NPM package coming soon). Then follow the instructions in the `INSTALL` file in the root of the repository to install the C core library that this package depends on.
 
-or, for brevity
+Then, simply run `npm install` in or referencing this directory.
 
-    node-gyp configure build
+## Tests
 
-The tests can be run with `npm test` on a dev install.
\ No newline at end of file
+To run the test suite, simply run `npm test` in the install location.
+
+## API
+
+This library internally uses [ProtoBuf.js](https://github.com/dcodeIO/ProtoBuf.js), and some structures it exports match those exported by that library
+
+If you require this module, you will get an object with the following members
+
+```javascript
+function load(filename)
+```
+
+Takes a filename of a [Protocol Buffer](https://developers.google.com/protocol-buffers/) file, and returns an object representing the structure of the protocol buffer in the following way:
+
+ - Namespaces become maps from the names of their direct members to those member objects
+ - Service definitions become client constructors for clients for that service. They also have a `service` member that can be used for constructing servers.
+ - Message definitions become Message constructors like those that ProtoBuf.js would create
+ - Enum definitions become Enum objects like those that ProtoBuf.js would create
+ - Anything else becomes the relevant reflection object that ProtoBuf.js would create
+
+
+```javascript
+function loadObject(reflectionObject)
+```
+
+Returns the same structure that `load` returns, but takes a reflection object from `ProtoBuf.js` instead of a file name.
+
+```javascript
+function buildServer(serviceArray)
+```
+
+Takes an array of service objects and returns a constructor for a server that handles requests to all of those services.
+
+
+```javascript
+status
+```
+
+An object mapping status names to status code numbers.
+
+
+```javascript
+callError
+```
+
+An object mapping call error names to codes. This is primarily useful for tracking down certain kinds of internal errors.
+
+
+```javascript
+Credentials
+```
+
+An object with factory methods for creating credential objects for clients.
+
+
+```javascript
+ServerCredentials
+```
+
+An object with factory methods fro creating credential objects for servers.
diff --git a/src/node/examples/stock.proto b/src/node/examples/stock.proto
new file mode 100644
index 0000000..efe98d8
--- /dev/null
+++ b/src/node/examples/stock.proto
@@ -0,0 +1,62 @@
+// 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.
+
+syntax = "proto3";
+
+package examples;
+
+// Protocol type definitions
+message StockRequest {
+  optional string symbol = 1;
+  optional int32 num_trades_to_watch = 2 [default=0];
+};
+
+message StockReply {
+  optional float price = 1;
+  optional string symbol = 2;
+};
+
+
+// Interface exported by the server
+service Stock {
+  // Simple blocking RPC
+  rpc GetLastTradePrice(StockRequest) returns (StockReply) {
+  };
+  // Bidirectional streaming RPC
+  rpc GetLastTradePriceMultiple(stream StockRequest) returns
+    (stream StockReply) {
+  };
+  // Unidirectional server-to-client streaming RPC
+  rpc WatchFutureTrades(StockRequest) returns (stream StockReply) {
+  };
+  // Unidirectional client-to-server streaming RPC
+  rpc GetHighestTradePrice(stream StockRequest) returns (StockReply) {
+  };
+
+};
\ No newline at end of file
diff --git a/include/grpc/support/time_posix.h b/src/node/examples/stock_client.js
similarity index 80%
copy from include/grpc/support/time_posix.h
copy to src/node/examples/stock_client.js
index 9ff6f7f..8e99090 100644
--- a/include/grpc/support/time_posix.h
+++ b/src/node/examples/stock_client.js
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2014, Google Inc.
+ * Copyright 2015, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,13 +31,13 @@
  *
  */
 
-#ifndef __GRPC_SUPPORT_TIME_POSIX_H__
-#define __GRPC_SUPPORT_TIME_POSIX_H__
-/* Posix variant of gpr_time_platform.h */
+var grpc = require('..');
+var examples = grpc.load(__dirname + '/stock.proto').examples;
 
-#include <sys/time.h>
-#include <time.h>
-
-typedef struct timespec gpr_timespec;
-
-#endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */
+/**
+ * This exports a client constructor for the Stock service. The usage looks like
+ *
+ * var StockClient = require('stock_client.js');
+ * var stockClient = new StockClient(server_address);
+ */
+module.exports = examples.Stock;
diff --git a/src/node/examples/stock_server.js b/src/node/examples/stock_server.js
new file mode 100644
index 0000000..c188181
--- /dev/null
+++ b/src/node/examples/stock_server.js
@@ -0,0 +1,83 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var _ = require('underscore');
+var grpc = require('..');
+var examples = grpc.load(__dirname + '/stock.proto').examples;
+
+var StockServer = grpc.makeServerConstructor([examples.Stock.service]);
+
+function getLastTradePrice(call, callback) {
+  callback(null, {price: 88});
+}
+
+function watchFutureTrades(call) {
+  for (var i = 0; i < call.request.num_trades_to_watch; i++) {
+    call.write({price: 88.00 + i * 10.00});
+  }
+  call.end();
+}
+
+function getHighestTradePrice(call, callback) {
+  var trades = [];
+  call.on('data', function(data) {
+    trades.push({symbol: data.symbol, price: _.random(0, 100)});
+  });
+  call.on('end', function() {
+    if(_.isEmpty(trades)) {
+      callback(null, {});
+    } else {
+      callback(null, _.max(trades, function(trade){return trade.price;}));
+    }
+  });
+}
+
+function getLastTradePriceMultiple(call) {
+  call.on('data', function(data) {
+    call.write({price: 88});
+  });
+  call.on('end', function() {
+    call.end();
+  });
+}
+
+var stockServer = new StockServer({
+  'examples.Stock' : {
+    getLastTradePrice: getLastTradePrice,
+    getLastTradePriceMultiple: getLastTradePriceMultiple,
+    watchFutureTrades: watchFutureTrades,
+    getHighestTradePrice: getHighestTradePrice
+  }
+});
+
+exports.module = stockServer;
diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc
index 3261b78..23aead0 100644
--- a/src/node/ext/call.cc
+++ b/src/node/ext/call.cc
@@ -152,9 +152,9 @@
       NanUtf8String method(args[1]);
       double deadline = args[2]->NumberValue();
       grpc_channel *wrapped_channel = channel->GetWrappedChannel();
-      grpc_call *wrapped_call =
-          grpc_channel_create_call(wrapped_channel, *method, channel->GetHost(),
-                                   MillisecondsToTimespec(deadline));
+      grpc_call *wrapped_call = grpc_channel_create_call_old(
+          wrapped_channel, *method, channel->GetHost(),
+          MillisecondsToTimespec(deadline));
       call = new Call(wrapped_call);
       args.This()->SetHiddenValue(String::NewSymbol("channel_"),
                                   channel_object);
@@ -195,7 +195,7 @@
       if (Buffer::HasInstance(value)) {
         metadata.value = Buffer::Data(value);
         metadata.value_length = Buffer::Length(value);
-        error = grpc_call_add_metadata(call->wrapped_call, &metadata, 0);
+        error = grpc_call_add_metadata_old(call->wrapped_call, &metadata, 0);
       } else if (value->IsString()) {
         Handle<String> string_value = value->ToString();
         NanUtf8String utf8_value(string_value);
@@ -203,7 +203,7 @@
         metadata.value_length = string_value->Length();
         gpr_log(GPR_DEBUG, "adding metadata: %s, %s, %d", metadata.key,
                 metadata.value, metadata.value_length);
-        error = grpc_call_add_metadata(call->wrapped_call, &metadata, 0);
+        error = grpc_call_add_metadata_old(call->wrapped_call, &metadata, 0);
       } else {
         return NanThrowTypeError(
             "addMetadata values must be strings or buffers");
@@ -232,7 +232,7 @@
   }
   Call *call = ObjectWrap::Unwrap<Call>(args.This());
   unsigned int flags = args[3]->Uint32Value();
-  grpc_call_error error = grpc_call_invoke(
+  grpc_call_error error = grpc_call_invoke_old(
       call->wrapped_call, CompletionQueueAsyncWorker::GetQueue(),
       CreateTag(args[0], args.This()), CreateTag(args[1], args.This()), flags);
   if (error == GRPC_CALL_OK) {
@@ -253,7 +253,7 @@
     return NanThrowTypeError("accept's first argument must be a function");
   }
   Call *call = ObjectWrap::Unwrap<Call>(args.This());
-  grpc_call_error error = grpc_call_server_accept(
+  grpc_call_error error = grpc_call_server_accept_old(
       call->wrapped_call, CompletionQueueAsyncWorker::GetQueue(),
       CreateTag(args[0], args.This()));
   if (error == GRPC_CALL_OK) {
@@ -277,7 +277,7 @@
   Call *call = ObjectWrap::Unwrap<Call>(args.This());
   unsigned int flags = args[1]->Uint32Value();
   grpc_call_error error =
-      grpc_call_server_end_initial_metadata(call->wrapped_call, flags);
+      grpc_call_server_end_initial_metadata_old(call->wrapped_call, flags);
   if (error != GRPC_CALL_OK) {
     return NanThrowError("serverEndInitialMetadata failed", error);
   }
@@ -315,7 +315,7 @@
   Call *call = ObjectWrap::Unwrap<Call>(args.This());
   grpc_byte_buffer *buffer = BufferToByteBuffer(args[0]);
   unsigned int flags = args[2]->Uint32Value();
-  grpc_call_error error = grpc_call_start_write(
+  grpc_call_error error = grpc_call_start_write_old(
       call->wrapped_call, buffer, CreateTag(args[1], args.This()), flags);
   if (error == GRPC_CALL_OK) {
     CompletionQueueAsyncWorker::Next();
@@ -345,7 +345,7 @@
   }
   Call *call = ObjectWrap::Unwrap<Call>(args.This());
   NanUtf8String details(args[1]);
-  grpc_call_error error = grpc_call_start_write_status(
+  grpc_call_error error = grpc_call_start_write_status_old(
       call->wrapped_call, (grpc_status_code)args[0]->Uint32Value(), *details,
       CreateTag(args[2], args.This()));
   if (error == GRPC_CALL_OK) {
@@ -365,7 +365,7 @@
     return NanThrowTypeError("writesDone's first argument must be a function");
   }
   Call *call = ObjectWrap::Unwrap<Call>(args.This());
-  grpc_call_error error = grpc_call_writes_done(
+  grpc_call_error error = grpc_call_writes_done_old(
       call->wrapped_call, CreateTag(args[0], args.This()));
   if (error == GRPC_CALL_OK) {
     CompletionQueueAsyncWorker::Next();
@@ -384,8 +384,8 @@
     return NanThrowTypeError("startRead's first argument must be a function");
   }
   Call *call = ObjectWrap::Unwrap<Call>(args.This());
-  grpc_call_error error =
-      grpc_call_start_read(call->wrapped_call, CreateTag(args[0], args.This()));
+  grpc_call_error error = grpc_call_start_read_old(
+      call->wrapped_call, CreateTag(args[0], args.This()));
   if (error == GRPC_CALL_OK) {
     CompletionQueueAsyncWorker::Next();
   } else {
diff --git a/src/node/ext/server.cc b/src/node/ext/server.cc
index b102775..6b8ccef 100644
--- a/src/node/ext/server.cc
+++ b/src/node/ext/server.cc
@@ -175,7 +175,7 @@
     return NanThrowTypeError("requestCall can only be called on a Server");
   }
   Server *server = ObjectWrap::Unwrap<Server>(args.This());
-  grpc_call_error error = grpc_server_request_call(
+  grpc_call_error error = grpc_server_request_call_old(
       server->wrapped_server, CreateTag(args[0], NanNull()));
   if (error == GRPC_CALL_OK) {
     CompletionQueueAsyncWorker::Next();
diff --git a/src/node/ext/timeval.cc b/src/node/ext/timeval.cc
index 687e335..20d52f0 100644
--- a/src/node/ext/timeval.cc
+++ b/src/node/ext/timeval.cc
@@ -56,9 +56,8 @@
   } else if (gpr_time_cmp(timespec, gpr_inf_past) == 0) {
     return -std::numeric_limits<double>::infinity();
   } else {
-    struct timeval time = gpr_timeval_from_timespec(timespec);
-    return (static_cast<double>(time.tv_sec) * 1000 +
-            static_cast<double>(time.tv_usec) / 1000);
+    return (static_cast<double>(timespec.tv_sec) * 1000 +
+            static_cast<double>(timespec.tv_nsec) / 1000000);
   }
 }
 
diff --git a/src/node/src/server.js b/src/node/src/server.js
index a5d737c..e4f71ff 100644
--- a/src/node/src/server.js
+++ b/src/node/src/server.js
@@ -243,15 +243,24 @@
       var handler = undefined;
       var deadline = data.absolute_deadline;
       var cancelled = false;
-      if (handlers.hasOwnProperty(data.method)) {
-        handler = handlers[data.method];
-      }
       call.serverAccept(function(event) {
         if (event.data.code === grpc.status.CANCELLED) {
           cancelled = true;
-          stream.emit('cancelled');
+          if (stream) {
+            stream.emit('cancelled');
+          }
         }
       }, 0);
+      if (handlers.hasOwnProperty(data.method)) {
+        handler = handlers[data.method];
+      } else {
+        call.serverEndInitialMetadata(0);
+        call.startWriteStatus(
+            grpc.status.UNIMPLEMENTED,
+            "This method is not available on this server.",
+            function() {});
+        return;
+      }
       if (getMetadata) {
         call.addMetadata(getMetadata(data.method, data.metadata));
       }
diff --git a/src/node/test/client_server_test.js b/src/node/test/client_server_test.js
index 059dd13..1db9f69 100644
--- a/src/node/test/client_server_test.js
+++ b/src/node/test/client_server_test.js
@@ -185,6 +185,14 @@
       done();
     });
   });
+  it('should get correct status for unimplemented method', function(done) {
+    var stream = client.makeRequest(channel, 'unimplemented_method');
+    stream.end();
+    stream.on('status', function(status) {
+      assert.equal(status.code, grpc.status.UNIMPLEMENTED);
+      done();
+    });
+  });
 });
 /* TODO(mlumish): explore options for reducing duplication between this test
  * and the insecure echo client test */
diff --git a/src/php/README.md b/src/php/README.md
index 176bcfa..620c68f 100755
--- a/src/php/README.md
+++ b/src/php/README.md
@@ -7,31 +7,25 @@
 
 ## ENVIRONMENT
 
-To build a PHP environment that works with this extension, download and extract
-PHP 5.5 (5.6 may also work), configure it, and install it:
+Install `php5` and `php5-dev`.
 
-```bash
-apt-get install libxml2 libxml2-dev
-curl http://php.net/get/php-5.5.16.tar.gz
-tar -xf php-5.5.16.tar.gz
-cd php-5.5.16
-./configure --with-zlib=/usr --with-libxml-dir=ext/libxml --with-openssl=/usr/local/ssl
-make
-make install
-```
+To run the tests, additionally install `php5-readline` and `phpunit`.
 
-To also download and install the patched protoc and PHP code generator:
+Alternatively, build and install PHP 5.5 or later from source with standard
+configuration options.
+
+To also download and install protoc and the PHP code generator.
 
 ```bash
 apt-get install -y procps
 curl -sSL https://get.rvm.io | sudo bash -s stable --ruby
-git clone sso://team/one-platform-grpc-team/protobuf
+git clone git@github.com:google/protobuf.git
 cd protobuf
 ./configure
 make
 make install
-git clone sso://team/one-platform-grpc-team/grpc-php-protobuf-php
-cd grpc-php-protobuf-php
+git clone git@github.com:murgatroid99/Protobuf-PHP.git
+cd Protobuf-PHP
 rake pear:package version=1.0
 pear install Protobuf-1.0.tgz
 ```
@@ -52,5 +46,4 @@
 There is also a generated code test (`./bin/run_gen_code_test.sh`), which tests
 the stub `./tests/generated_code/math.php` against a running localhost server
 serving the math service. That stub is generated from
-`./tests/generated_code/math.proto` with the head of the repo
-`sso://team/one-platform-grpc-team/grpc-php-protobuf-php`.
+`./tests/generated_code/math.proto`.
diff --git a/src/php/ext/grpc/call.c b/src/php/ext/grpc/call.c
index b171c9c..3bc9ce2 100644
--- a/src/php/ext/grpc/call.c
+++ b/src/php/ext/grpc/call.c
@@ -85,73 +85,25 @@
     memcpy(str_val, elem->value, elem->value_length);
     if (zend_hash_find(array_hash, str_key, key_len, (void **)data) ==
         SUCCESS) {
-      switch (Z_TYPE_P(*data)) {
-        case IS_STRING:
-          MAKE_STD_ZVAL(inner_array);
-          array_init(inner_array);
-          add_next_index_zval(inner_array, *data);
-          add_assoc_zval(array, str_key, inner_array);
-          break;
-        case IS_ARRAY:
-          inner_array = *data;
-          break;
-        default:
-          zend_throw_exception(zend_exception_get_default(),
-                               "Metadata hash somehow contains wrong types.",
-                               1 TSRMLS_CC);
+      if (Z_TYPE_P(*data) != IS_ARRAY) {
+        zend_throw_exception(zend_exception_get_default(),
+                             "Metadata hash somehow contains wrong types.",
+                             1 TSRMLS_CC);
           efree(str_key);
           efree(str_val);
           return NULL;
       }
-      add_next_index_stringl(inner_array, str_val, elem->value_length, false);
+      add_next_index_stringl(*data, str_val, elem->value_length, false);
     } else {
-      add_assoc_stringl(array, str_key, str_val, elem->value_length, false);
+      MAKE_STD_ZVAL(inner_array);
+      array_init(inner_array);
+      add_next_index_stringl(inner_array, str_val, elem->value_length, false);
+      add_assoc_zval(array, str_key, inner_array);
     }
   }
   return array;
 }
 
-int php_grpc_call_add_metadata_array_walk(void *elem TSRMLS_DC, int num_args,
-                                          va_list args,
-                                          zend_hash_key *hash_key) {
-  grpc_call_error error_code;
-  zval **data = (zval **)elem;
-  grpc_metadata metadata;
-  grpc_call *call = va_arg(args, grpc_call *);
-  gpr_uint32 flags = va_arg(args, gpr_uint32);
-  const char *key;
-  HashTable *inner_hash;
-  /* We assume that either two args were passed, and we are in the recursive
-     case (and the second argument is the key), or one arg was passed and
-     hash_key is the string key. */
-  if (num_args > 2) {
-    key = va_arg(args, const char *);
-  } else {
-    /* TODO(mlumish): If possible, check that hash_key is a string */
-    key = hash_key->arKey;
-  }
-  switch (Z_TYPE_P(*data)) {
-    case IS_STRING:
-      metadata.key = (char *)key;
-      metadata.value = Z_STRVAL_P(*data);
-      metadata.value_length = Z_STRLEN_P(*data);
-      error_code = grpc_call_add_metadata(call, &metadata, 0u);
-      MAYBE_THROW_CALL_ERROR(add_metadata, error_code);
-      break;
-    case IS_ARRAY:
-      inner_hash = Z_ARRVAL_P(*data);
-      zend_hash_apply_with_arguments(inner_hash TSRMLS_CC,
-                                     php_grpc_call_add_metadata_array_walk, 3,
-                                     call, flags, key);
-      break;
-    default:
-      zend_throw_exception(zend_exception_get_default(),
-                           "Metadata hash somehow contains wrong types.",
-                           1 TSRMLS_CC);
-  }
-  return ZEND_HASH_APPLY_KEEP;
-}
-
 /**
  * Constructs a new instance of the Call class.
  * @param Channel $channel The channel to associate the call with. Must not be
@@ -188,8 +140,8 @@
   wrapped_grpc_timeval *deadline =
       (wrapped_grpc_timeval *)zend_object_store_get_object(
           deadline_obj TSRMLS_CC);
-  call->wrapped = grpc_channel_create_call(channel->wrapped, method,
-                                           channel->target, deadline->wrapped);
+  call->wrapped = grpc_channel_create_call_old(
+      channel->wrapped, method, channel->target, deadline->wrapped);
 }
 
 /**
@@ -204,8 +156,18 @@
 PHP_METHOD(Call, add_metadata) {
   wrapped_grpc_call *call =
       (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
+  grpc_metadata metadata;
+  grpc_call_error error_code;
   zval *array;
+  zval **inner_array;
+  zval **value;
   HashTable *array_hash;
+  HashPosition array_pointer;
+  HashTable *inner_array_hash;
+  HashPosition inner_array_pointer;
+  char *key;
+  uint key_len;
+  ulong index;
   long flags = 0;
   /* "a|l" == 1 array, 1 optional long */
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|l", &array, &flags) ==
@@ -216,9 +178,41 @@
     return;
   }
   array_hash = Z_ARRVAL_P(array);
-  zend_hash_apply_with_arguments(array_hash TSRMLS_CC,
-                                 php_grpc_call_add_metadata_array_walk, 2,
-                                 call->wrapped, (gpr_uint32)flags);
+  for (zend_hash_internal_pointer_reset_ex(array_hash, &array_pointer);
+       zend_hash_get_current_data_ex(array_hash, (void**)&inner_array,
+                                     &array_pointer) == SUCCESS;
+       zend_hash_move_forward_ex(array_hash, &array_pointer)) {
+    if (zend_hash_get_current_key_ex(array_hash, &key, &key_len, &index, 0,
+                                     &array_pointer) != HASH_KEY_IS_STRING) {
+      zend_throw_exception(spl_ce_InvalidArgumentException,
+                           "metadata keys must be strings", 1 TSRMLS_CC);
+      return;
+    }
+    if (Z_TYPE_P(*inner_array) != IS_ARRAY) {
+      zend_throw_exception(spl_ce_InvalidArgumentException,
+                           "metadata values must be arrays",
+                           1 TSRMLS_CC);
+      return;
+    }
+    inner_array_hash = Z_ARRVAL_P(*inner_array);
+    for (zend_hash_internal_pointer_reset_ex(inner_array_hash,
+                                             &inner_array_pointer);
+         zend_hash_get_current_data_ex(inner_array_hash, (void**)&value,
+                                       &inner_array_pointer) == SUCCESS;
+         zend_hash_move_forward_ex(inner_array_hash, &inner_array_pointer)) {
+      if (Z_TYPE_P(*value) != IS_STRING) {
+        zend_throw_exception(spl_ce_InvalidArgumentException,
+                             "metadata values must be arrays of strings",
+                             1 TSRMLS_CC);
+        return;
+      }
+      metadata.key = key;
+      metadata.value = Z_STRVAL_P(*value);
+      metadata.value_length = Z_STRLEN_P(*value);
+      error_code = grpc_call_add_metadata_old(call->wrapped, &metadata, 0u);
+      MAYBE_THROW_CALL_ERROR(add_metadata, error_code);
+    }
+  }
 }
 
 /**
@@ -252,8 +246,8 @@
   wrapped_grpc_completion_queue *queue =
       (wrapped_grpc_completion_queue *)zend_object_store_get_object(
           queue_obj TSRMLS_CC);
-  error_code = grpc_call_invoke(call->wrapped, queue->wrapped, (void *)tag1,
-                                (void *)tag2, (gpr_uint32)flags);
+  error_code = grpc_call_invoke_old(call->wrapped, queue->wrapped, (void *)tag1,
+                                    (void *)tag2, (gpr_uint32)flags);
   MAYBE_THROW_CALL_ERROR(invoke, error_code);
 }
 
@@ -287,7 +281,7 @@
       (wrapped_grpc_completion_queue *)zend_object_store_get_object(
           queue_obj TSRMLS_CC);
   error_code =
-      grpc_call_server_accept(call->wrapped, queue->wrapped, (void *)tag);
+      grpc_call_server_accept_old(call->wrapped, queue->wrapped, (void *)tag);
   MAYBE_THROW_CALL_ERROR(server_accept, error_code);
 }
 
@@ -303,7 +297,7 @@
   }
   wrapped_grpc_call *call =
       (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  error_code = grpc_call_server_end_initial_metadata(call->wrapped, flags);
+  error_code = grpc_call_server_end_initial_metadata_old(call->wrapped, flags);
   MAYBE_THROW_CALL_ERROR(server_end_initial_metadata, error_code);
 }
 
@@ -342,9 +336,9 @@
                          1 TSRMLS_CC);
     return;
   }
-  error_code = grpc_call_start_write(call->wrapped,
-                                     string_to_byte_buffer(buffer, buffer_len),
-                                     (void *)tag, (gpr_uint32)flags);
+  error_code = grpc_call_start_write_old(
+      call->wrapped, string_to_byte_buffer(buffer, buffer_len), (void *)tag,
+      (gpr_uint32)flags);
   MAYBE_THROW_CALL_ERROR(start_write, error_code);
 }
 
@@ -372,9 +366,9 @@
         "start_write_status expects a long, a string, and a long", 1 TSRMLS_CC);
     return;
   }
-  error_code =
-      grpc_call_start_write_status(call->wrapped, (grpc_status_code)status_code,
-                                   status_details, (void *)tag);
+  error_code = grpc_call_start_write_status_old(call->wrapped,
+                                                (grpc_status_code)status_code,
+                                                status_details, (void *)tag);
   MAYBE_THROW_CALL_ERROR(start_write_status, error_code);
 }
 
@@ -393,7 +387,7 @@
                          "writes_done expects a long", 1 TSRMLS_CC);
     return;
   }
-  error_code = grpc_call_writes_done(call->wrapped, (void *)tag);
+  error_code = grpc_call_writes_done_old(call->wrapped, (void *)tag);
   MAYBE_THROW_CALL_ERROR(writes_done, error_code);
 }
 
@@ -414,7 +408,7 @@
                          "start_read expects a long", 1 TSRMLS_CC);
     return;
   }
-  error_code = grpc_call_start_read(call->wrapped, (void *)tag);
+  error_code = grpc_call_start_read_old(call->wrapped, (void *)tag);
   MAYBE_THROW_CALL_ERROR(start_read, error_code);
 }
 
diff --git a/src/php/ext/grpc/call.h b/src/php/ext/grpc/call.h
index 232c5d7..ba1f1e7 100644
--- a/src/php/ext/grpc/call.h
+++ b/src/php/ext/grpc/call.h
@@ -19,6 +19,7 @@
       zend_throw_exception(spl_ce_LogicException,                \
                            #func_name " was called incorrectly", \
                            (long)error_code TSRMLS_CC);          \
+      return;                                                    \
     }                                                            \
   } while (0)
 
diff --git a/src/php/ext/grpc/server.c b/src/php/ext/grpc/server.c
index 38777f3..47ea38d 100644
--- a/src/php/ext/grpc/server.c
+++ b/src/php/ext/grpc/server.c
@@ -125,7 +125,7 @@
                          "request_call expects a long", 1 TSRMLS_CC);
     return;
   }
-  error_code = grpc_server_request_call(server->wrapped, (void *)tag_new);
+  error_code = grpc_server_request_call_old(server->wrapped, (void *)tag_new);
   MAYBE_THROW_CALL_ERROR(request_call, error_code);
 }
 
@@ -146,7 +146,7 @@
                          "add_http2_port expects a string", 1 TSRMLS_CC);
     return;
   }
-  RETURN_BOOL(grpc_server_add_http2_port(server->wrapped, addr));
+  RETURN_LONG(grpc_server_add_http2_port(server->wrapped, addr));
 }
 
 PHP_METHOD(Server, add_secure_http2_port) {
@@ -161,7 +161,7 @@
                          "add_http2_port expects a string", 1 TSRMLS_CC);
     return;
   }
-  RETURN_BOOL(grpc_server_add_secure_http2_port(server->wrapped, addr));
+  RETURN_LONG(grpc_server_add_secure_http2_port(server->wrapped, addr));
 }
 
 /**
diff --git a/src/php/lib/Grpc/AbstractSurfaceActiveCall.php b/src/php/lib/Grpc/AbstractSurfaceActiveCall.php
index 53c7d4c..83e4719 100755
--- a/src/php/lib/Grpc/AbstractSurfaceActiveCall.php
+++ b/src/php/lib/Grpc/AbstractSurfaceActiveCall.php
@@ -44,7 +44,7 @@
 
   protected function _read() {
     $response = $this->active_call->read();
-    if ($response == null) {
+    if ($response === null) {
       return null;
     }
     return call_user_func($this->deserialize, $response);
diff --git a/src/php/lib/Grpc/ActiveCall.php b/src/php/lib/Grpc/ActiveCall.php
index e0ea43a..847cfee 100755
--- a/src/php/lib/Grpc/ActiveCall.php
+++ b/src/php/lib/Grpc/ActiveCall.php
@@ -66,12 +66,7 @@
    * @param ByteBuffer $data The data to write
    */
   public function write($data) {
-    if($this->call->start_write($data,
-                                WRITE_ACCEPTED,
-                                $this->flags) != OP_OK) {
-      // TODO(mlumish): more useful error
-      throw new \Exception("Cannot call write after writesDone");
-    }
+    $this->call->start_write($data, WRITE_ACCEPTED, $this->flags);
     $this->completion_queue->pluck(WRITE_ACCEPTED, Timeval::inf_future());
   }
 
diff --git a/src/php/lib/Grpc/BaseStub.php b/src/php/lib/Grpc/BaseStub.php
index e745ec3..ff293c0 100755
--- a/src/php/lib/Grpc/BaseStub.php
+++ b/src/php/lib/Grpc/BaseStub.php
@@ -33,10 +33,10 @@
    * @param array $metadata A metadata map to send to the server
    * @return SimpleSurfaceActiveCall The active call object
    */
-  protected function _simpleRequest($method,
-                                    $argument,
-                                    callable $deserialize,
-                                    $metadata = array()) {
+  public function _simpleRequest($method,
+                                 $argument,
+                                 callable $deserialize,
+                                 $metadata = array()) {
     return new SimpleSurfaceActiveCall($this->channel,
                                        $method,
                                        $deserialize,
@@ -55,10 +55,10 @@
    * @param array $metadata A metadata map to send to the server
    * @return ClientStreamingSurfaceActiveCall The active call object
    */
-  protected function _clientStreamRequest($method,
-                                          $arguments,
-                                          callable $deserialize,
-                                          $metadata = array()) {
+  public function _clientStreamRequest($method,
+                                       $arguments,
+                                       callable $deserialize,
+                                       $metadata = array()) {
     return new ClientStreamingSurfaceActiveCall($this->channel,
                                                 $method,
                                                 $deserialize,
@@ -76,10 +76,10 @@
    * @param array $metadata A metadata map to send to the server
    * @return ServerStreamingSurfaceActiveCall The active call object
    */
-  protected function _serverStreamRequest($method,
-                                          $argument,
-                                          callable $deserialize,
-                                          $metadata = array()) {
+  public function _serverStreamRequest($method,
+                                       $argument,
+                                       callable $deserialize,
+                                       $metadata = array()) {
     return new ServerStreamingSurfaceActiveCall($this->channel,
                                                 $method,
                                                 $deserialize,
@@ -95,9 +95,9 @@
    * @param array $metadata A metadata map to send to the server
    * @return BidiStreamingSurfaceActiveCall The active call object
    */
-  protected function _bidiRequest($method,
-                                  callable $deserialize,
-                                  $metadata = array()) {
+  public function _bidiRequest($method,
+                               callable $deserialize,
+                               $metadata = array()) {
     return new BidiStreamingSurfaceActiveCall($this->channel,
                                               $method,
                                               $deserialize,
diff --git a/src/php/lib/Grpc/ServerStreamingSurfaceActiveCall.php b/src/php/lib/Grpc/ServerStreamingSurfaceActiveCall.php
index 082f995..f131d6b 100755
--- a/src/php/lib/Grpc/ServerStreamingSurfaceActiveCall.php
+++ b/src/php/lib/Grpc/ServerStreamingSurfaceActiveCall.php
@@ -31,7 +31,7 @@
    * @return An iterator of response values
    */
   public function responses() {
-    while(($response = $this->_read()) != null) {
+    while(($response = $this->_read()) !== null) {
       yield $response;
     }
   }
diff --git a/src/php/tests/generated_code/GeneratedCodeTest.php b/src/php/tests/generated_code/GeneratedCodeTest.php
index 42d25e4..ee7b871 100755
--- a/src/php/tests/generated_code/GeneratedCodeTest.php
+++ b/src/php/tests/generated_code/GeneratedCodeTest.php
@@ -17,9 +17,9 @@
     $div_arg->setDividend(7);
     $div_arg->setDivisor(4);
     list($response, $status) = self::$client->Div($div_arg)->wait();
-    $this->assertEquals(1, $response->getQuotient());
-    $this->assertEquals(3, $response->getRemainder());
-    $this->assertEquals(\Grpc\STATUS_OK, $status->code);
+    $this->assertSame(1, $response->getQuotient());
+    $this->assertSame(3, $response->getRemainder());
+    $this->assertSame(\Grpc\STATUS_OK, $status->code);
   }
 
   public function testServerStreaming() {
@@ -31,9 +31,9 @@
       return $num->getNum();
     };
     $values = array_map($extract_num, $result_array);
-    $this->assertEquals([1, 1, 2, 3, 5, 8, 13], $values);
+    $this->assertSame([1, 1, 2, 3, 5, 8, 13], $values);
     $status = $call->getStatus();
-    $this->assertEquals(\Grpc\STATUS_OK, $status->code);
+    $this->assertSame(\Grpc\STATUS_OK, $status->code);
   }
 
   public function testClientStreaming() {
@@ -46,8 +46,8 @@
     };
     $call = self::$client->Sum($num_iter());
     list($response, $status) = $call->wait();
-    $this->assertEquals(21, $response->getNum());
-    $this->assertEquals(\Grpc\STATUS_OK, $status->code);
+    $this->assertSame(21, $response->getNum());
+    $this->assertSame(\Grpc\STATUS_OK, $status->code);
   }
 
   public function testBidiStreaming() {
@@ -58,11 +58,11 @@
       $div_arg->setDivisor(2);
       $call->write($div_arg);
       $response = $call->read();
-      $this->assertEquals($i, $response->getQuotient());
-      $this->assertEquals(1, $response->getRemainder());
+      $this->assertSame($i, $response->getQuotient());
+      $this->assertSame(1, $response->getRemainder());
     }
     $call->writesDone();
     $status = $call->getStatus();
-    $this->assertEquals(\Grpc\STATUS_OK, $status->code);
+    $this->assertSame(\Grpc\STATUS_OK, $status->code);
   }
 }
\ No newline at end of file
diff --git a/src/php/tests/interop/empty.php b/src/php/tests/interop/empty.php
index 3e4968f..22b1180 100755
--- a/src/php/tests/interop/empty.php
+++ b/src/php/tests/interop/empty.php
@@ -1,7 +1,7 @@
 <?php
 // DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0
 // Source: test/cpp/interop/empty.proto
-//   Date: 2015-01-30 21:44:54
+//   Date: 2015-01-30 23:30:46
 
 namespace grpc\testing {
 
diff --git a/src/php/tests/interop/interop_client.php b/src/php/tests/interop/interop_client.php
index c837506..5266e9a 100755
--- a/src/php/tests/interop/interop_client.php
+++ b/src/php/tests/interop/interop_client.php
@@ -26,8 +26,8 @@
  */
 function emptyUnary($stub) {
   list($result, $status) = $stub->EmptyCall(new grpc\testing\EmptyMessage())->wait();
-  hardAssert($status->code == Grpc\STATUS_OK, 'Call did not complete successfully');
-  hardAssert($result != null, 'Call completed with a null response');
+  hardAssert($status->code === Grpc\STATUS_OK, 'Call did not complete successfully');
+  hardAssert($result !== null, 'Call completed with a null response');
 }
 
 /**
@@ -49,14 +49,14 @@
   $request->setPayload($payload);
 
   list($result, $status) = $stub->UnaryCall($request)->wait();
-  hardAssert($status->code == Grpc\STATUS_OK, 'Call did not complete successfully');
-  hardAssert($result != null, 'Call returned a null response');
+  hardAssert($status->code === Grpc\STATUS_OK, 'Call did not complete successfully');
+  hardAssert($result !== null, 'Call returned a null response');
   $payload = $result->getPayload();
-  hardAssert($payload->getType() == grpc\testing\PayloadType::COMPRESSABLE,
+  hardAssert($payload->getType() === grpc\testing\PayloadType::COMPRESSABLE,
          'Payload had the wrong type');
-  hardAssert(strlen($payload->getBody()) == $response_len,
+  hardAssert(strlen($payload->getBody()) === $response_len,
          'Payload had the wrong length');
-  hardAssert($payload->getBody() == str_repeat("\0", $response_len),
+  hardAssert($payload->getBody() === str_repeat("\0", $response_len),
          'Payload had the wrong content');
 }
 
@@ -78,8 +78,8 @@
       }, $request_lengths);
 
   list($result, $status) = $stub->StreamingInputCall($requests)->wait();
-  hardAssert($status->code == Grpc\STATUS_OK, 'Call did not complete successfully');
-  hardAssert($result->getAggregatedPayloadSize() == 74922,
+  hardAssert($status->code === Grpc\STATUS_OK, 'Call did not complete successfully');
+  hardAssert($result->getAggregatedPayloadSize() === 74922,
               'aggregated_payload_size was incorrect');
 }
 
@@ -100,15 +100,15 @@
   }
 
   $call = $stub->StreamingOutputCall($request);
-  hardAssert($call->getStatus()->code == Grpc\STATUS_OK,
+  hardAssert($call->getStatus()->code === Grpc\STATUS_OK,
               'Call did not complete successfully');
   $i = 0;
   foreach($call->responses() as $value) {
     hardAssert($i < 4, 'Too many responses');
     $payload = $value->getPayload();
-    hardAssert($payload->getType() == grpc\testing\PayloadType::COMPRESSABLE,
+    hardAssert($payload->getType() === grpc\testing\PayloadType::COMPRESSABLE,
                 'Payload ' . $i . ' had the wrong type');
-    hardAssert(strlen($payload->getBody()) == $sizes[$i],
+    hardAssert(strlen($payload->getBody()) === $sizes[$i],
                 'Response ' . $i . ' had the wrong length');
   }
 }
@@ -136,19 +136,38 @@
     $call->write($request);
     $response = $call->read();
 
-    hardAssert($response != null, 'Server returned too few responses');
+    hardAssert($response !== null, 'Server returned too few responses');
     $payload = $response->getPayload();
-    hardAssert($payload->getType() == grpc\testing\PayloadType::COMPRESSABLE,
+    hardAssert($payload->getType() === grpc\testing\PayloadType::COMPRESSABLE,
                 'Payload ' . $i . ' had the wrong type');
-    hardAssert(strlen($payload->getBody()) == $response_lengths[$i],
+    hardAssert(strlen($payload->getBody()) === $response_lengths[$i],
                 'Payload ' . $i . ' had the wrong length');
   }
   $call->writesDone();
-  hardAssert($call->read() == null, 'Server returned too many responses');
-  hardAssert($call->getStatus()->code == Grpc\STATUS_OK,
+  hardAssert($call->read() === null, 'Server returned too many responses');
+  hardAssert($call->getStatus()->code === Grpc\STATUS_OK,
               'Call did not complete successfully');
 }
 
+function cancelAfterFirstResponse($stub) {
+  $call = $stub->FullDuplexCall();
+  $request = new grpc\testing\StreamingOutputCallRequest();
+  $request->setResponseType(grpc\testing\PayloadType::COMPRESSABLE);
+  $response_parameters = new grpc\testing\ResponseParameters();
+  $response_parameters->setSize(31415);
+  $request->addResponseParameters($response_parameters);
+  $payload = new grpc\testing\Payload();
+  $payload->setBody(str_repeat("\0", 27182));
+  $request->setPayload($payload);
+
+  $call->write($request);
+  $response = $call->read();
+
+  $call->cancel();
+  hardAssert($call->getStatus()->code === Grpc\STATUS_CANCELLED,
+             'Call status was not CANCELLED');
+}
+
 $args = getopt('', array('server_host:', 'server_port:', 'test_case:'));
 if (!array_key_exists('server_host', $args) ||
     !array_key_exists('server_port', $args) ||
@@ -161,11 +180,12 @@
 $credentials = Grpc\Credentials::createSsl(
     file_get_contents(dirname(__FILE__) . '/../data/ca.pem'));
 $stub = new grpc\testing\TestServiceClient(
-    $server_address,
-    [
-        'grpc.ssl_target_name_override' => 'foo.test.google.com',
-        'credentials' => $credentials
-     ]);
+    new Grpc\BaseStub(
+        $server_address,
+        [
+            'grpc.ssl_target_name_override' => 'foo.test.google.com',
+            'credentials' => $credentials
+         ]));
 
 echo "Connecting to $server_address\n";
 echo "Running test case $args[test_case]\n";
@@ -186,4 +206,6 @@
   case 'ping_pong':
     pingPong($stub);
     break;
+  case 'cancel_after_first_response':
+    cancelAfterFirstResponse($stub);
 }
\ No newline at end of file
diff --git a/src/php/tests/interop/messages.php b/src/php/tests/interop/messages.php
index bbdd296..129c96f 100755
--- a/src/php/tests/interop/messages.php
+++ b/src/php/tests/interop/messages.php
@@ -1,7 +1,7 @@
 <?php
 // DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0
 // Source: test/cpp/interop/messages.proto
-//   Date: 2015-01-30 21:44:54
+//   Date: 2015-01-30 23:30:46
 
 namespace grpc\testing {
 
diff --git a/src/php/tests/interop/test.php b/src/php/tests/interop/test.php
index 060a21d..014bbc9 100755
--- a/src/php/tests/interop/test.php
+++ b/src/php/tests/interop/test.php
@@ -1,52 +1,52 @@
 <?php
 // DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0
 // Source: test/cpp/interop/test.proto
-//   Date: 2015-01-30 21:44:54
+//   Date: 2015-01-30 23:30:46
 
 namespace grpc\testing {
 
-  class TestServiceClient extends \Grpc\BaseStub {
+  class TestServiceClient{
+
+    private $rpc_impl;
+
+    public function __construct($rpc_impl) {
+      $this->rpc_impl = $rpc_impl;
+    }
     /**
      * @param grpc\testing\EmptyMessage $input
-     * @return grpc\testing\EmptyMessage
      */
     public function EmptyCall(\grpc\testing\EmptyMessage $argument, $metadata = array()) {
-      return $this->_simpleRequest('/grpc.testing.TestService/EmptyCall', $argument, '\grpc\testing\EmptyMessage::deserialize', $metadata);
+      return $this->rpc_impl->_simpleRequest('/grpc.testing.TestService/EmptyCall', $argument, '\grpc\testing\EmptyMessage::deserialize', $metadata);
     }
     /**
      * @param grpc\testing\SimpleRequest $input
-     * @return grpc\testing\SimpleResponse
      */
     public function UnaryCall(\grpc\testing\SimpleRequest $argument, $metadata = array()) {
-      return $this->_simpleRequest('/grpc.testing.TestService/UnaryCall', $argument, '\grpc\testing\SimpleResponse::deserialize', $metadata);
+      return $this->rpc_impl->_simpleRequest('/grpc.testing.TestService/UnaryCall', $argument, '\grpc\testing\SimpleResponse::deserialize', $metadata);
     }
     /**
      * @param grpc\testing\StreamingOutputCallRequest $input
-     * @return grpc\testing\StreamingOutputCallResponse
      */
     public function StreamingOutputCall($argument, $metadata = array()) {
-      return $this->_serverStreamRequest('/grpc.testing.TestService/StreamingOutputCall', $argument, '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata);
+      return $this->rpc_impl->_serverStreamRequest('/grpc.testing.TestService/StreamingOutputCall', $argument, '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata);
     }
     /**
      * @param grpc\testing\StreamingInputCallRequest $input
-     * @return grpc\testing\StreamingInputCallResponse
      */
     public function StreamingInputCall($arguments, $metadata = array()) {
-      return $this->_clientStreamRequest('/grpc.testing.TestService/StreamingInputCall', $arguments, '\grpc\testing\StreamingInputCallResponse::deserialize', $metadata);
+      return $this->rpc_impl->_clientStreamRequest('/grpc.testing.TestService/StreamingInputCall', $arguments, '\grpc\testing\StreamingInputCallResponse::deserialize', $metadata);
     }
     /**
      * @param grpc\testing\StreamingOutputCallRequest $input
-     * @return grpc\testing\StreamingOutputCallResponse
      */
     public function FullDuplexCall($metadata = array()) {
-      return $this->_bidiRequest('/grpc.testing.TestService/FullDuplexCall', '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata);
+      return $this->rpc_impl->_bidiRequest('/grpc.testing.TestService/FullDuplexCall', '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata);
     }
     /**
      * @param grpc\testing\StreamingOutputCallRequest $input
-     * @return grpc\testing\StreamingOutputCallResponse
      */
     public function HalfDuplexCall($metadata = array()) {
-      return $this->_bidiRequest('/grpc.testing.TestService/HalfDuplexCall', '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata);
+      return $this->rpc_impl->_bidiRequest('/grpc.testing.TestService/HalfDuplexCall', '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata);
     }
   }
 }
diff --git a/src/php/tests/unit_tests/CallTest.php b/src/php/tests/unit_tests/CallTest.php
index 795831c..8f709b7 100755
--- a/src/php/tests/unit_tests/CallTest.php
+++ b/src/php/tests/unit_tests/CallTest.php
@@ -1,16 +1,17 @@
 <?php
 class CallTest extends PHPUnit_Framework_TestCase{
   static $server;
+  static $port;
 
   public static function setUpBeforeClass() {
     $cq = new Grpc\CompletionQueue();
     self::$server = new Grpc\Server($cq, []);
-    self::$server->add_http2_port('localhost:9001');
+    self::$port = self::$server->add_http2_port('0.0.0.0:0');
   }
 
   public function setUp() {
     $this->cq = new Grpc\CompletionQueue();
-    $this->channel = new Grpc\Channel('localhost:9001', []);
+    $this->channel = new Grpc\Channel('localhost:' . self::$port, []);
     $this->call = new Grpc\Call($this->channel,
                                 '/foo',
                                 Grpc\Timeval::inf_future());
@@ -46,7 +47,7 @@
   }
 
   public function testAddSingleMetadata() {
-    $this->call->add_metadata(['key' => 'value'], 0);
+    $this->call->add_metadata(['key' => ['value']], 0);
     /* Dummy assert: Checks that the previous call completed without error */
     $this->assertTrue(true);
   }
@@ -59,7 +60,7 @@
 
   public function testAddSingleAndMultiValueMetadata() {
     $this->call->add_metadata(
-        ['key1' => 'value1',
+        ['key1' => ['value1'],
          'key2' => ['value2', 'value3']], 0);
     /* Dummy assert: Checks that the previous call completed without error */
     $this->assertTrue(true);
diff --git a/src/php/tests/unit_tests/EndToEndTest.php b/src/php/tests/unit_tests/EndToEndTest.php
index 78c5e9f..05104c0 100755
--- a/src/php/tests/unit_tests/EndToEndTest.php
+++ b/src/php/tests/unit_tests/EndToEndTest.php
@@ -1,13 +1,11 @@
 <?php
-require __DIR__ . '/../util/port_picker.php';
 class EndToEndTest extends PHPUnit_Framework_TestCase{
   public function setUp() {
     $this->client_queue = new Grpc\CompletionQueue();
     $this->server_queue = new Grpc\CompletionQueue();
     $this->server = new Grpc\Server($this->server_queue, []);
-    $address = '127.0.0.1:' . getNewPort();
-    $this->server->add_http2_port($address);
-    $this->channel = new Grpc\Channel($address, []);
+    $port = $this->server->add_http2_port('0.0.0.0:0');
+    $this->channel = new Grpc\Channel('localhost:' . $port, []);
   }
 
   public function tearDown() {
@@ -24,62 +22,52 @@
                           'dummy_method',
                           $deadline);
     $tag = 1;
-    $this->assertEquals(Grpc\CALL_OK,
-                        $call->invoke($this->client_queue,
-                                      $tag,
-                                      $tag));
-
+    $call->invoke($this->client_queue, $tag, $tag);
     $server_tag = 2;
 
     $call->writes_done($tag);
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertEquals(Grpc\OP_OK, $event->data);
+    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
+    $this->assertSame(Grpc\OP_OK, $event->data);
 
     // check that a server rpc new was received
     $this->server->start();
     $this->server->request_call($server_tag);
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\SERVER_RPC_NEW, $event->type);
+    $this->assertSame(Grpc\SERVER_RPC_NEW, $event->type);
     $server_call = $event->call;
     $this->assertNotNull($server_call);
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->server_accept($this->server_queue,
-                                                    $server_tag));
+    $server_call->server_accept($this->server_queue, $server_tag);
 
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->server_end_initial_metadata());
+    $server_call->server_end_initial_metadata();
 
 
     // the server sends the status
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->start_write_status(Grpc\STATUS_OK,
-                                                         $status_text,
-                                                         $server_tag));
+    $server_call->start_write_status(Grpc\STATUS_OK, $status_text, $server_tag);
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertEquals(Grpc\OP_OK, $event->data);
+    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
+    $this->assertSame(Grpc\OP_OK, $event->data);
 
     // the client gets CLIENT_METADATA_READ
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\CLIENT_METADATA_READ, $event->type);
+    $this->assertSame(Grpc\CLIENT_METADATA_READ, $event->type);
 
     // the client gets FINISHED
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\FINISHED, $event->type);
+    $this->assertSame(Grpc\FINISHED, $event->type);
     $status = $event->data;
-    $this->assertEquals(Grpc\STATUS_OK, $status->code);
-    $this->assertEquals($status_text, $status->details);
+    $this->assertSame(Grpc\STATUS_OK, $status->code);
+    $this->assertSame($status_text, $status->details);
 
     // and the server gets FINISHED
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\FINISHED, $event->type);
+    $this->assertSame(Grpc\FINISHED, $event->type);
     $status = $event->data;
 
     unset($call);
@@ -96,10 +84,7 @@
                           'dummy_method',
                           $deadline);
     $tag = 1;
-    $this->assertEquals(Grpc\CALL_OK,
-                        $call->invoke($this->client_queue,
-                                      $tag,
-                                      $tag));
+    $call->invoke($this->client_queue, $tag, $tag);
 
     $server_tag = 2;
 
@@ -107,76 +92,69 @@
     $call->start_write($req_text, $tag);
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\WRITE_ACCEPTED, $event->type);
+    $this->assertSame(Grpc\WRITE_ACCEPTED, $event->type);
 
     // check that a server rpc new was received
     $this->server->start();
     $this->server->request_call($server_tag);
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\SERVER_RPC_NEW, $event->type);
+    $this->assertSame(Grpc\SERVER_RPC_NEW, $event->type);
     $server_call = $event->call;
     $this->assertNotNull($server_call);
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->server_accept($this->server_queue,
-                                                    $server_tag));
+    $server_call->server_accept($this->server_queue, $server_tag);
 
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->server_end_initial_metadata());
+    $server_call->server_end_initial_metadata();
 
     // start the server read
     $server_call->start_read($server_tag);
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\READ, $event->type);
-    $this->assertEquals($req_text, $event->data);
+    $this->assertSame(Grpc\READ, $event->type);
+    $this->assertSame($req_text, $event->data);
 
     // the server replies
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->start_write($reply_text, $server_tag));
+    $server_call->start_write($reply_text, $server_tag);
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\WRITE_ACCEPTED, $event->type);
+    $this->assertSame(Grpc\WRITE_ACCEPTED, $event->type);
 
     // the client reads the metadata
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\CLIENT_METADATA_READ, $event->type);
+    $this->assertSame(Grpc\CLIENT_METADATA_READ, $event->type);
 
     // the client reads the reply
     $call->start_read($tag);
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\READ, $event->type);
-    $this->assertEquals($reply_text, $event->data);
+    $this->assertSame(Grpc\READ, $event->type);
+    $this->assertSame($reply_text, $event->data);
 
     // the client sends writes done
     $call->writes_done($tag);
     $event = $this->client_queue->next($deadline);
-    $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertEquals(Grpc\OP_OK, $event->data);
+    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
+    $this->assertSame(Grpc\OP_OK, $event->data);
 
     // the server sends the status
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->start_write_status(GRPC\STATUS_OK,
-                                                         $status_text,
-                                                         $server_tag));
+    $server_call->start_write_status(GRPC\STATUS_OK, $status_text, $server_tag);
     $event = $this->server_queue->next($deadline);
-    $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertEquals(Grpc\OP_OK, $event->data);
+    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
+    $this->assertSame(Grpc\OP_OK, $event->data);
 
     // the client gets FINISHED
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\FINISHED, $event->type);
+    $this->assertSame(Grpc\FINISHED, $event->type);
     $status = $event->data;
-    $this->assertEquals(Grpc\STATUS_OK, $status->code);
-    $this->assertEquals($status_text, $status->details);
+    $this->assertSame(Grpc\STATUS_OK, $status->code);
+    $this->assertSame($status_text, $status->details);
 
     // and the server gets FINISHED
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\FINISHED, $event->type);
+    $this->assertSame(Grpc\FINISHED, $event->type);
 
     unset($call);
     unset($server_call);
diff --git a/src/php/tests/unit_tests/SecureEndToEndTest.php b/src/php/tests/unit_tests/SecureEndToEndTest.php
index 7c3ad8a..5e95b11 100755
--- a/src/php/tests/unit_tests/SecureEndToEndTest.php
+++ b/src/php/tests/unit_tests/SecureEndToEndTest.php
@@ -11,10 +11,9 @@
         file_get_contents(dirname(__FILE__) . '/../data/server1.pem'));
     $this->server = new Grpc\Server($this->server_queue,
                                     ['credentials' => $server_credentials]);
-    $address = '127.0.0.1:' . getNewPort();
-    $this->server->add_secure_http2_port($address);
+    $port = $this->server->add_secure_http2_port('0.0.0.0:0');
     $this->channel = new Grpc\Channel(
-        $address,
+        'localhost:' . $port,
         [
             'grpc.ssl_target_name_override' => 'foo.test.google.com',
             'credentials' => $credentials
@@ -36,59 +35,50 @@
                           'dummy_method',
                           $deadline);
     $tag = 1;
-    $this->assertEquals(Grpc\CALL_OK,
-                        $call->invoke($this->client_queue,
-                                      $tag,
-                                      $tag));
+    $call->invoke($this->client_queue, $tag, $tag);
     $server_tag = 2;
 
     $call->writes_done($tag);
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertEquals(Grpc\OP_OK, $event->data);
+    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
+    $this->assertSame(Grpc\OP_OK, $event->data);
 
     // check that a server rpc new was received
     $this->server->request_call($server_tag);
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\SERVER_RPC_NEW, $event->type);
+    $this->assertSame(Grpc\SERVER_RPC_NEW, $event->type);
     $server_call = $event->call;
     $this->assertNotNull($server_call);
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->server_accept($this->server_queue,
-                                                    $server_tag));
+    $server_call->server_accept($this->server_queue, $server_tag);
 
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->server_end_initial_metadata());
+    $server_call->server_end_initial_metadata();
 
     // the server sends the status
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->start_write_status(Grpc\STATUS_OK,
-                                                         $status_text,
-                                                         $server_tag));
+    $server_call->start_write_status(Grpc\STATUS_OK, $status_text, $server_tag);
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertEquals(Grpc\OP_OK, $event->data);
+    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
+    $this->assertSame(Grpc\OP_OK, $event->data);
 
     // the client gets CLIENT_METADATA_READ
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\CLIENT_METADATA_READ, $event->type);
+    $this->assertSame(Grpc\CLIENT_METADATA_READ, $event->type);
 
     // the client gets FINISHED
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\FINISHED, $event->type);
+    $this->assertSame(Grpc\FINISHED, $event->type);
     $status = $event->data;
-    $this->assertEquals(Grpc\STATUS_OK, $status->code);
-    $this->assertEquals($status_text, $status->details);
+    $this->assertSame(Grpc\STATUS_OK, $status->code);
+    $this->assertSame($status_text, $status->details);
 
     // and the server gets FINISHED
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\FINISHED, $event->type);
+    $this->assertSame(Grpc\FINISHED, $event->type);
     $status = $event->data;
 
     unset($call);
@@ -106,10 +96,7 @@
                           'dummy_method',
                           $deadline);
     $tag = 1;
-    $this->assertEquals(Grpc\CALL_OK,
-                        $call->invoke($this->client_queue,
-                                      $tag,
-                                      $tag));
+    $call->invoke($this->client_queue, $tag, $tag);
 
     $server_tag = 2;
 
@@ -117,75 +104,68 @@
     $call->start_write($req_text, $tag);
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\WRITE_ACCEPTED, $event->type);
+    $this->assertSame(Grpc\WRITE_ACCEPTED, $event->type);
 
     // check that a server rpc new was received
     $this->server->request_call($server_tag);
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\SERVER_RPC_NEW, $event->type);
+    $this->assertSame(Grpc\SERVER_RPC_NEW, $event->type);
     $server_call = $event->call;
     $this->assertNotNull($server_call);
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->server_accept($this->server_queue,
-                                                    $server_tag));
+    $server_call->server_accept($this->server_queue, $server_tag);
 
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->server_end_initial_metadata());
+    $server_call->server_end_initial_metadata();
 
     // start the server read
     $server_call->start_read($server_tag);
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\READ, $event->type);
-    $this->assertEquals($req_text, $event->data);
+    $this->assertSame(Grpc\READ, $event->type);
+    $this->assertSame($req_text, $event->data);
 
     // the server replies
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->start_write($reply_text, $server_tag));
+    $server_call->start_write($reply_text, $server_tag);
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\WRITE_ACCEPTED, $event->type);
+    $this->assertSame(Grpc\WRITE_ACCEPTED, $event->type);
 
     // the client reads the metadata
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\CLIENT_METADATA_READ, $event->type);
+    $this->assertSame(Grpc\CLIENT_METADATA_READ, $event->type);
 
     // the client reads the reply
     $call->start_read($tag);
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\READ, $event->type);
-    $this->assertEquals($reply_text, $event->data);
+    $this->assertSame(Grpc\READ, $event->type);
+    $this->assertSame($reply_text, $event->data);
 
     // the client sends writes done
     $call->writes_done($tag);
     $event = $this->client_queue->next($deadline);
-    $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertEquals(Grpc\OP_OK, $event->data);
+    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
+    $this->assertSame(Grpc\OP_OK, $event->data);
 
     // the server sends the status
-    $this->assertEquals(Grpc\CALL_OK,
-                        $server_call->start_write_status(GRPC\STATUS_OK,
-                                                         $status_text,
-                                                         $server_tag));
+    $server_call->start_write_status(GRPC\STATUS_OK, $status_text, $server_tag);
     $event = $this->server_queue->next($deadline);
-    $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertEquals(Grpc\OP_OK, $event->data);
+    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
+    $this->assertSame(Grpc\OP_OK, $event->data);
 
     // the client gets FINISHED
     $event = $this->client_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\FINISHED, $event->type);
+    $this->assertSame(Grpc\FINISHED, $event->type);
     $status = $event->data;
-    $this->assertEquals(Grpc\STATUS_OK, $status->code);
-    $this->assertEquals($status_text, $status->details);
+    $this->assertSame(Grpc\STATUS_OK, $status->code);
+    $this->assertSame($status_text, $status->details);
 
     // and the server gets FINISHED
     $event = $this->server_queue->next($deadline);
     $this->assertNotNull($event);
-    $this->assertEquals(Grpc\FINISHED, $event->type);
+    $this->assertSame(Grpc\FINISHED, $event->type);
 
     unset($call);
     unset($server_call);
diff --git a/src/php/tests/unit_tests/TimevalTest.php b/src/php/tests/unit_tests/TimevalTest.php
index 6af9fba..067254b 100755
--- a/src/php/tests/unit_tests/TimevalTest.php
+++ b/src/php/tests/unit_tests/TimevalTest.php
@@ -2,7 +2,7 @@
 class TimevalTest extends PHPUnit_Framework_TestCase{
   public function testCompareSame() {
     $zero = Grpc\Timeval::zero();
-    $this->assertEquals(0, Grpc\Timeval::compare($zero, $zero));
+    $this->assertSame(0, Grpc\Timeval::compare($zero, $zero));
   }
 
   public function testPastIsLessThanZero() {
diff --git a/src/php/tests/util/port_picker.php b/src/php/tests/util/port_picker.php
deleted file mode 100755
index d869d8b..0000000
--- a/src/php/tests/util/port_picker.php
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-function getNewPort() {
-  static $port = 10000;
-  $port += 1;
-  return $port;
-}
\ No newline at end of file
diff --git a/src/python/setup.py b/src/python/setup.py
new file mode 100644
index 0000000..58dc3b1
--- /dev/null
+++ b/src/python/setup.py
@@ -0,0 +1,86 @@
+# 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.
+
+"""A setup module for the GRPC Python package."""
+
+from distutils import core as _core
+
+_EXTENSION_SOURCES = (
+    'src/_adapter/_c.c',
+    'src/_adapter/_call.c',
+    'src/_adapter/_channel.c',
+    'src/_adapter/_completion_queue.c',
+    'src/_adapter/_error.c',
+    'src/_adapter/_server.c',
+)
+
+_EXTENSION_INCLUDE_DIRECTORIES = (
+    'src',
+    # TODO(nathaniel): Can this path specification be made to work?
+    #'../../include',
+)
+
+_EXTENSION_LIBRARIES = (
+    'gpr',
+    'grpc',
+)
+
+_EXTENSION_LIBRARY_DIRECTORIES = (
+    # TODO(nathaniel): Can this path specification be made to work?
+    #'../../libs/dbg',
+)
+
+_EXTENSION_MODULE = _core.Extension(
+    '_adapter._c', sources=list(_EXTENSION_SOURCES),
+    include_dirs=_EXTENSION_INCLUDE_DIRECTORIES,
+    libraries=_EXTENSION_LIBRARIES,
+    library_dirs=_EXTENSION_LIBRARY_DIRECTORIES)
+
+_PACKAGES=(
+    '_adapter',
+    '_framework',
+    '_framework.base',
+    '_framework.base.packets',
+    '_framework.common',
+    '_framework.face',
+    '_framework.face.testing',
+    '_framework.foundation',
+    '_junkdrawer',
+)
+
+_PACKAGE_DIRECTORIES = {
+    '_adapter': 'src/_adapter',
+    '_framework': 'src/_framework',
+    '_junkdrawer': 'src/_junkdrawer',
+}
+
+_core.setup(
+    name='grpc', version='0.0.1',
+    ext_modules=[_EXTENSION_MODULE], packages=_PACKAGES,
+    package_dir=_PACKAGE_DIRECTORIES)
diff --git a/src/python/__init__.py b/src/python/src/__init__.py
similarity index 100%
rename from src/python/__init__.py
rename to src/python/src/__init__.py
diff --git a/src/python/_junkdrawer/__init__.py b/src/python/src/_adapter/__init__.py
similarity index 100%
copy from src/python/_junkdrawer/__init__.py
copy to src/python/src/_adapter/__init__.py
diff --git a/src/python/src/_adapter/_blocking_invocation_inline_service_test.py b/src/python/src/_adapter/_blocking_invocation_inline_service_test.py
new file mode 100644
index 0000000..873ce9a
--- /dev/null
+++ b/src/python/src/_adapter/_blocking_invocation_inline_service_test.py
@@ -0,0 +1,17 @@
+"""One of the tests of the Face layer of RPC Framework."""
+
+import unittest
+
+from _adapter import _face_test_case
+from _framework.face.testing import blocking_invocation_inline_service_test_case as test_case
+
+
+class BlockingInvocationInlineServiceTest(
+    _face_test_case.FaceTestCase,
+    test_case.BlockingInvocationInlineServiceTestCase,
+    unittest.TestCase):
+  pass
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/src/python/src/_adapter/_c.c b/src/python/src/_adapter/_c.c
new file mode 100644
index 0000000..d1f7fbb
--- /dev/null
+++ b/src/python/src/_adapter/_c.c
@@ -0,0 +1,77 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+#include <Python.h>
+#include <grpc/grpc.h>
+
+#include "_adapter/_completion_queue.h"
+#include "_adapter/_channel.h"
+#include "_adapter/_call.h"
+#include "_adapter/_server.h"
+
+static PyObject *init(PyObject *self, PyObject *args) {
+  grpc_init();
+  Py_RETURN_NONE;
+}
+
+static PyObject *shutdown(PyObject *self, PyObject *args) {
+  grpc_shutdown();
+  Py_RETURN_NONE;
+}
+
+static PyMethodDef _c_methods[] = {
+    {"init", init, METH_VARARGS, "Initialize the module's static state."},
+    {"shut_down", shutdown, METH_VARARGS,
+     "Shut down the module's static state."},
+    {NULL},
+};
+
+PyMODINIT_FUNC init_c(void) {
+  PyObject *module;
+
+  module = Py_InitModule3("_c", _c_methods,
+                          "Wrappings of C structures and functions.");
+
+  if (pygrpc_add_completion_queue(module) == -1) {
+    return;
+  }
+  if (pygrpc_add_channel(module) == -1) {
+    return;
+  }
+  if (pygrpc_add_call(module) == -1) {
+    return;
+  }
+  if (pygrpc_add_server(module) == -1) {
+    return;
+  }
+}
diff --git a/src/python/src/_adapter/_c_test.py b/src/python/src/_adapter/_c_test.py
new file mode 100644
index 0000000..bc0a622
--- /dev/null
+++ b/src/python/src/_adapter/_c_test.py
@@ -0,0 +1,141 @@
+# 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.
+
+"""Tests for _adapter._c."""
+
+import threading
+import time
+import unittest
+
+from _adapter import _c
+from _adapter import _datatypes
+
+_TIMEOUT = 3
+_FUTURE = time.time() + 60 * 60 * 24
+_IDEMPOTENCE_DEMONSTRATION = 7
+
+
+class _CTest(unittest.TestCase):
+
+  def testUpAndDown(self):
+    _c.init()
+    _c.shut_down()
+
+  def testCompletionQueue(self):
+    _c.init()
+
+    completion_queue = _c.CompletionQueue()
+    event = completion_queue.get(0)
+    self.assertIsNone(event)
+    event = completion_queue.get(time.time())
+    self.assertIsNone(event)
+    event = completion_queue.get(time.time() + _TIMEOUT)
+    self.assertIsNone(event)
+    completion_queue.stop()
+    for _ in range(_IDEMPOTENCE_DEMONSTRATION):
+      event = completion_queue.get(time.time() + _TIMEOUT)
+      self.assertIs(event.kind, _datatypes.Event.Kind.STOP)
+
+    del completion_queue
+    del event
+
+    _c.shut_down()
+
+  def testChannel(self):
+    _c.init()
+
+    channel = _c.Channel('test host:12345')
+    del channel
+
+    _c.shut_down()
+
+  def testCall(self):
+    method = 'test method'
+    host = 'test host'
+
+    _c.init()
+
+    channel = _c.Channel('%s:%d' % (host, 12345))
+    call = _c.Call(channel, method, host, time.time() + _TIMEOUT)
+    del call
+    del channel
+
+    _c.shut_down()
+
+  def testServer(self):
+    _c.init()
+
+    completion_queue = _c.CompletionQueue()
+    server = _c.Server(completion_queue)
+    server.add_http2_addr('[::]:0')
+    server.start()
+    server.stop()
+    completion_queue.stop()
+    del server
+    del completion_queue
+
+    service_tag = object()
+    completion_queue = _c.CompletionQueue()
+    server = _c.Server(completion_queue)
+    server.add_http2_addr('[::]:0')
+    server.start()
+    server.service(service_tag)
+    server.stop()
+    completion_queue.stop()
+    event = completion_queue.get(time.time() + _TIMEOUT)
+    self.assertIs(event.kind, _datatypes.Event.Kind.SERVICE_ACCEPTED)
+    self.assertIs(event.tag, service_tag)
+    self.assertIsNone(event.service_acceptance)
+    for _ in range(_IDEMPOTENCE_DEMONSTRATION):
+      event = completion_queue.get(time.time() + _TIMEOUT)
+      self.assertIs(event.kind, _datatypes.Event.Kind.STOP)
+    del server
+    del completion_queue
+
+    completion_queue = _c.CompletionQueue()
+    server = _c.Server(completion_queue)
+    server.add_http2_addr('[::]:0')
+    server.start()
+    thread = threading.Thread(target=completion_queue.get, args=(_FUTURE,))
+    thread.start()
+    time.sleep(1)
+    server.stop()
+    completion_queue.stop()
+    for _ in range(_IDEMPOTENCE_DEMONSTRATION):
+      event = completion_queue.get(time.time() + _TIMEOUT)
+      self.assertIs(event.kind, _datatypes.Event.Kind.STOP)
+    thread.join()
+    del server
+    del completion_queue
+
+    _c.shut_down()
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/src/python/src/_adapter/_call.c b/src/python/src/_adapter/_call.c
new file mode 100644
index 0000000..3bc35be
--- /dev/null
+++ b/src/python/src/_adapter/_call.c
@@ -0,0 +1,293 @@
+/*
+ *
+ * Copyright 2014, 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 "_adapter/_call.h"
+
+#include <math.h>
+#include <Python.h>
+#include <grpc/grpc.h>
+
+#include "_adapter/_channel.h"
+#include "_adapter/_completion_queue.h"
+#include "_adapter/_error.h"
+
+static int pygrpc_call_init(Call *self, PyObject *args, PyObject *kwds) {
+  const PyObject *channel;
+  const char *method;
+  const char *host;
+  const double deadline;
+
+  if (!PyArg_ParseTuple(args, "O!ssd", &pygrpc_ChannelType, &channel, &method,
+                        &host, &deadline)) {
+    self->c_call = NULL;
+    return -1;
+  }
+
+  /* TODO(nathaniel): Hoist the gpr_timespec <-> PyFloat arithmetic into its own
+   * function with its own test coverage.
+   */
+  self->c_call = grpc_channel_create_call_old(
+      ((Channel *)channel)->c_channel, method, host,
+      gpr_time_from_nanos(deadline * GPR_NS_PER_SEC));
+
+  return 0;
+}
+
+static void pygrpc_call_dealloc(Call *self) {
+  if (self->c_call != NULL) {
+    grpc_call_destroy(self->c_call);
+  }
+  self->ob_type->tp_free((PyObject *)self);
+}
+
+static const PyObject *pygrpc_call_invoke(Call *self, PyObject *args) {
+  const PyObject *completion_queue;
+  const PyObject *metadata_tag;
+  const PyObject *finish_tag;
+  grpc_call_error call_error;
+  const PyObject *result;
+
+  if (!(PyArg_ParseTuple(args, "O!OO", &pygrpc_CompletionQueueType,
+                         &completion_queue, &metadata_tag, &finish_tag))) {
+    return NULL;
+  }
+
+  call_error = grpc_call_invoke_old(
+      self->c_call, ((CompletionQueue *)completion_queue)->c_completion_queue,
+      (void *)metadata_tag, (void *)finish_tag, 0);
+
+  result = pygrpc_translate_call_error(call_error);
+  if (result != NULL) {
+    Py_INCREF(metadata_tag);
+    Py_INCREF(finish_tag);
+  }
+  return result;
+}
+
+static const PyObject *pygrpc_call_write(Call *self, PyObject *args) {
+  const char *bytes;
+  int length;
+  const PyObject *tag;
+  gpr_slice slice;
+  grpc_byte_buffer *byte_buffer;
+  grpc_call_error call_error;
+  const PyObject *result;
+
+  if (!(PyArg_ParseTuple(args, "s#O", &bytes, &length, &tag))) {
+    return NULL;
+  }
+
+  slice = gpr_slice_from_copied_buffer(bytes, length);
+  byte_buffer = grpc_byte_buffer_create(&slice, 1);
+  gpr_slice_unref(slice);
+
+  call_error =
+      grpc_call_start_write_old(self->c_call, byte_buffer, (void *)tag, 0);
+
+  grpc_byte_buffer_destroy(byte_buffer);
+
+  result = pygrpc_translate_call_error(call_error);
+  if (result != NULL) {
+    Py_INCREF(tag);
+  }
+  return result;
+}
+
+static const PyObject *pygrpc_call_complete(Call *self, PyObject *args) {
+  const PyObject *tag;
+  grpc_call_error call_error;
+  const PyObject *result;
+
+  if (!(PyArg_ParseTuple(args, "O", &tag))) {
+    return NULL;
+  }
+
+  call_error = grpc_call_writes_done_old(self->c_call, (void *)tag);
+
+  result = pygrpc_translate_call_error(call_error);
+  if (result != NULL) {
+    Py_INCREF(tag);
+  }
+  return result;
+}
+
+static const PyObject *pygrpc_call_accept(Call *self, PyObject *args) {
+  const PyObject *completion_queue;
+  const PyObject *tag;
+  grpc_call_error call_error;
+  const PyObject *result;
+
+  if (!(PyArg_ParseTuple(args, "O!O", &pygrpc_CompletionQueueType,
+                         &completion_queue, &tag))) {
+    return NULL;
+  }
+
+  call_error = grpc_call_server_accept_old(
+      self->c_call, ((CompletionQueue *)completion_queue)->c_completion_queue,
+      (void *)tag);
+  result = pygrpc_translate_call_error(call_error);
+
+  if (result != NULL) {
+    Py_INCREF(tag);
+  }
+
+  return result;
+}
+
+static const PyObject *pygrpc_call_premetadata(Call *self, PyObject *args) {
+  /* TODO(b/18702680): Actually support metadata. */
+  return pygrpc_translate_call_error(
+      grpc_call_server_end_initial_metadata_old(self->c_call, 0));
+}
+
+static const PyObject *pygrpc_call_read(Call *self, PyObject *args) {
+  const PyObject *tag;
+  grpc_call_error call_error;
+  const PyObject *result;
+
+  if (!(PyArg_ParseTuple(args, "O", &tag))) {
+    return NULL;
+  }
+
+  call_error = grpc_call_start_read_old(self->c_call, (void *)tag);
+
+  result = pygrpc_translate_call_error(call_error);
+  if (result != NULL) {
+    Py_INCREF(tag);
+  }
+  return result;
+}
+
+static const PyObject *pygrpc_call_status(Call *self, PyObject *args) {
+  PyObject *status;
+  PyObject *code;
+  PyObject *details;
+  const PyObject *tag;
+  grpc_status_code c_code;
+  char *c_message;
+  grpc_call_error call_error;
+  const PyObject *result;
+
+  if (!(PyArg_ParseTuple(args, "OO", &status, &tag))) {
+    return NULL;
+  }
+
+  code = PyObject_GetAttrString(status, "code");
+  details = PyObject_GetAttrString(status, "details");
+  c_code = PyInt_AsLong(code);
+  c_message = PyBytes_AsString(details);
+  Py_DECREF(code);
+  Py_DECREF(details);
+
+  call_error = grpc_call_start_write_status_old(self->c_call, c_code, c_message,
+                                                (void *)tag);
+
+  result = pygrpc_translate_call_error(call_error);
+  if (result != NULL) {
+    Py_INCREF(tag);
+  }
+  return result;
+}
+
+static const PyObject *pygrpc_call_cancel(Call *self) {
+  return pygrpc_translate_call_error(grpc_call_cancel(self->c_call));
+}
+
+static PyMethodDef methods[] = {
+    {"invoke", (PyCFunction)pygrpc_call_invoke, METH_VARARGS,
+     "Invoke this call."},
+    {"write", (PyCFunction)pygrpc_call_write, METH_VARARGS,
+     "Write bytes to this call."},
+    {"complete", (PyCFunction)pygrpc_call_complete, METH_VARARGS,
+     "Complete writes to this call."},
+    {"accept", (PyCFunction)pygrpc_call_accept, METH_VARARGS, "Accept an RPC."},
+    {"premetadata", (PyCFunction)pygrpc_call_premetadata, METH_VARARGS,
+     "Indicate the end of leading metadata in the response."},
+    {"read", (PyCFunction)pygrpc_call_read, METH_VARARGS,
+     "Read bytes from this call."},
+    {"status", (PyCFunction)pygrpc_call_status, METH_VARARGS,
+     "Report this call's status."},
+    {"cancel", (PyCFunction)pygrpc_call_cancel, METH_NOARGS,
+     "Cancel this call."},
+    {NULL}};
+
+PyTypeObject pygrpc_CallType = {
+    PyObject_HEAD_INIT(NULL)0,       /*ob_size*/
+    "_grpc.Call",                    /*tp_name*/
+    sizeof(Call),                    /*tp_basicsize*/
+    0,                               /*tp_itemsize*/
+    (destructor)pygrpc_call_dealloc, /*tp_dealloc*/
+    0,                               /*tp_print*/
+    0,                               /*tp_getattr*/
+    0,                               /*tp_setattr*/
+    0,                               /*tp_compare*/
+    0,                               /*tp_repr*/
+    0,                               /*tp_as_number*/
+    0,                               /*tp_as_sequence*/
+    0,                               /*tp_as_mapping*/
+    0,                               /*tp_hash */
+    0,                               /*tp_call*/
+    0,                               /*tp_str*/
+    0,                               /*tp_getattro*/
+    0,                               /*tp_setattro*/
+    0,                               /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT,              /*tp_flags*/
+    "Wrapping of grpc_call.",        /* tp_doc */
+    0,                               /* tp_traverse */
+    0,                               /* tp_clear */
+    0,                               /* tp_richcompare */
+    0,                               /* tp_weaklistoffset */
+    0,                               /* tp_iter */
+    0,                               /* tp_iternext */
+    methods,                         /* tp_methods */
+    0,                               /* tp_members */
+    0,                               /* tp_getset */
+    0,                               /* tp_base */
+    0,                               /* tp_dict */
+    0,                               /* tp_descr_get */
+    0,                               /* tp_descr_set */
+    0,                               /* tp_dictoffset */
+    (initproc)pygrpc_call_init,      /* tp_init */
+};
+
+int pygrpc_add_call(PyObject *module) {
+  pygrpc_CallType.tp_new = PyType_GenericNew;
+  if (PyType_Ready(&pygrpc_CallType) < 0) {
+    PyErr_SetString(PyExc_RuntimeError, "Error defining pygrpc_CallType!");
+    return -1;
+  }
+  if (PyModule_AddObject(module, "Call", (PyObject *)&pygrpc_CallType) == -1) {
+    PyErr_SetString(PyExc_ImportError, "Couldn't add Call type to module!");
+  }
+  return 0;
+}
diff --git a/include/grpc/support/time_posix.h b/src/python/src/_adapter/_call.h
similarity index 83%
copy from include/grpc/support/time_posix.h
copy to src/python/src/_adapter/_call.h
index 9ff6f7f..a936e23 100644
--- a/include/grpc/support/time_posix.h
+++ b/src/python/src/_adapter/_call.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2014, Google Inc.
+ * Copyright 2015, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,13 +31,16 @@
  *
  */
 
-#ifndef __GRPC_SUPPORT_TIME_POSIX_H__
-#define __GRPC_SUPPORT_TIME_POSIX_H__
-/* Posix variant of gpr_time_platform.h */
+#ifndef _ADAPTER__CALL_H_
+#define _ADAPTER__CALL_H_
 
-#include <sys/time.h>
-#include <time.h>
+#include <Python.h>
+#include <grpc/grpc.h>
 
-typedef struct timespec gpr_timespec;
+typedef struct { PyObject_HEAD grpc_call *c_call; } Call;
 
-#endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */
+PyTypeObject pygrpc_CallType;
+
+int pygrpc_add_call(PyObject *module);
+
+#endif /* _ADAPTER__CALL_H_ */
diff --git a/src/python/src/_adapter/_channel.c b/src/python/src/_adapter/_channel.c
new file mode 100644
index 0000000..d41ebd4
--- /dev/null
+++ b/src/python/src/_adapter/_channel.c
@@ -0,0 +1,109 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+#include "_adapter/_channel.h"
+
+#include <Python.h>
+#include <grpc/grpc.h>
+
+static int pygrpc_channel_init(Channel *self, PyObject *args, PyObject *kwds) {
+  const char *hostport;
+
+  if (!(PyArg_ParseTuple(args, "s", &hostport))) {
+    self->c_channel = NULL;
+    return -1;
+  }
+
+  self->c_channel = grpc_channel_create(hostport, NULL);
+  return 0;
+}
+
+static void pygrpc_channel_dealloc(Channel *self) {
+  if (self->c_channel != NULL) {
+    grpc_channel_destroy(self->c_channel);
+  }
+  self->ob_type->tp_free((PyObject *)self);
+}
+
+PyTypeObject pygrpc_ChannelType = {
+    PyObject_HEAD_INIT(NULL)0,          /*ob_size*/
+    "_grpc.Channel",                    /*tp_name*/
+    sizeof(Channel),                    /*tp_basicsize*/
+    0,                                  /*tp_itemsize*/
+    (destructor)pygrpc_channel_dealloc, /*tp_dealloc*/
+    0,                                  /*tp_print*/
+    0,                                  /*tp_getattr*/
+    0,                                  /*tp_setattr*/
+    0,                                  /*tp_compare*/
+    0,                                  /*tp_repr*/
+    0,                                  /*tp_as_number*/
+    0,                                  /*tp_as_sequence*/
+    0,                                  /*tp_as_mapping*/
+    0,                                  /*tp_hash */
+    0,                                  /*tp_call*/
+    0,                                  /*tp_str*/
+    0,                                  /*tp_getattro*/
+    0,                                  /*tp_setattro*/
+    0,                                  /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT,                 /*tp_flags*/
+    "Wrapping of grpc_channel.",        /* tp_doc */
+    0,                                  /* tp_traverse */
+    0,                                  /* tp_clear */
+    0,                                  /* tp_richcompare */
+    0,                                  /* tp_weaklistoffset */
+    0,                                  /* tp_iter */
+    0,                                  /* tp_iternext */
+    0,                                  /* tp_methods */
+    0,                                  /* tp_members */
+    0,                                  /* tp_getset */
+    0,                                  /* tp_base */
+    0,                                  /* tp_dict */
+    0,                                  /* tp_descr_get */
+    0,                                  /* tp_descr_set */
+    0,                                  /* tp_dictoffset */
+    (initproc)pygrpc_channel_init,      /* tp_init */
+};
+
+int pygrpc_add_channel(PyObject *module) {
+  pygrpc_ChannelType.tp_new = PyType_GenericNew;
+  if (PyType_Ready(&pygrpc_ChannelType) < 0) {
+    PyErr_SetString(PyExc_RuntimeError, "Error defining pygrpc_ChannelType!");
+    return -1;
+  }
+  if (PyModule_AddObject(module, "Channel", (PyObject *)&pygrpc_ChannelType) ==
+      -1) {
+    PyErr_SetString(PyExc_ImportError, "Couldn't add Channel type to module!");
+    return -1;
+  }
+  return 0;
+}
diff --git a/include/grpc/support/time_posix.h b/src/python/src/_adapter/_channel.h
similarity index 82%
copy from include/grpc/support/time_posix.h
copy to src/python/src/_adapter/_channel.h
index 9ff6f7f..6241ccd 100644
--- a/include/grpc/support/time_posix.h
+++ b/src/python/src/_adapter/_channel.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2014, Google Inc.
+ * Copyright 2015, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,13 +31,16 @@
  *
  */
 
-#ifndef __GRPC_SUPPORT_TIME_POSIX_H__
-#define __GRPC_SUPPORT_TIME_POSIX_H__
-/* Posix variant of gpr_time_platform.h */
+#ifndef _ADAPTER__CHANNEL_H_
+#define _ADAPTER__CHANNEL_H_
 
-#include <sys/time.h>
-#include <time.h>
+#include <Python.h>
+#include <grpc/grpc.h>
 
-typedef struct timespec gpr_timespec;
+typedef struct { PyObject_HEAD grpc_channel *c_channel; } Channel;
 
-#endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */
+PyTypeObject pygrpc_ChannelType;
+
+int pygrpc_add_channel(PyObject *module);
+
+#endif /* _ADAPTER__CHANNEL_H_ */
diff --git a/src/python/src/_adapter/_common.py b/src/python/src/_adapter/_common.py
new file mode 100644
index 0000000..492849f
--- /dev/null
+++ b/src/python/src/_adapter/_common.py
@@ -0,0 +1,76 @@
+# 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.
+
+"""State used by both invocation-side and service-side code."""
+
+import enum
+
+
+@enum.unique
+class HighWrite(enum.Enum):
+  """The possible categories of high-level write state."""
+
+  OPEN = 'OPEN'
+  CLOSED = 'CLOSED'
+
+
+class WriteState(object):
+  """A description of the state of writing to an RPC.
+
+  Attributes:
+    low: A side-specific value describing the low-level state of writing.
+    high: A HighWrite value describing the high-level state of writing.
+    pending: A list of bytestrings for the RPC waiting to be written to the
+      other side of the RPC.
+  """
+
+  def __init__(self, low, high, pending):
+    self.low = low
+    self.high = high
+    self.pending = pending
+
+
+class CommonRPCState(object):
+  """A description of an RPC's state.
+
+  Attributes:
+    write: A WriteState describing the state of writing to the RPC.
+    sequence_number: The lowest-unused sequence number for use in generating
+      tickets locally describing the progress of the RPC.
+    deserializer: The behavior to be used to deserialize payload bytestreams
+      taken off the wire.
+    serializer: The behavior to be used to serialize payloads to be sent on the
+      wire.
+  """
+
+  def __init__(self, write, sequence_number, deserializer, serializer):
+    self.write = write
+    self.sequence_number = sequence_number
+    self.deserializer = deserializer
+    self.serializer = serializer
diff --git a/src/python/src/_adapter/_completion_queue.c b/src/python/src/_adapter/_completion_queue.c
new file mode 100644
index 0000000..7c951d2
--- /dev/null
+++ b/src/python/src/_adapter/_completion_queue.c
@@ -0,0 +1,541 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+#include "_adapter/_completion_queue.h"
+
+#include <Python.h>
+#include <grpc/grpc.h>
+#include <grpc/support/alloc.h>
+
+#include "_adapter/_call.h"
+
+static PyObject *status_class;
+static PyObject *service_acceptance_class;
+static PyObject *event_class;
+
+static PyObject *ok_status_code;
+static PyObject *cancelled_status_code;
+static PyObject *unknown_status_code;
+static PyObject *invalid_argument_status_code;
+static PyObject *expired_status_code;
+static PyObject *not_found_status_code;
+static PyObject *already_exists_status_code;
+static PyObject *permission_denied_status_code;
+static PyObject *unauthenticated_status_code;
+static PyObject *resource_exhausted_status_code;
+static PyObject *failed_precondition_status_code;
+static PyObject *aborted_status_code;
+static PyObject *out_of_range_status_code;
+static PyObject *unimplemented_status_code;
+static PyObject *internal_error_status_code;
+static PyObject *unavailable_status_code;
+static PyObject *data_loss_status_code;
+
+static PyObject *stop_event_kind;
+static PyObject *write_event_kind;
+static PyObject *complete_event_kind;
+static PyObject *service_event_kind;
+static PyObject *read_event_kind;
+static PyObject *metadata_event_kind;
+static PyObject *finish_event_kind;
+
+static PyObject *pygrpc_as_py_time(gpr_timespec *timespec) {
+  return Py_BuildValue("f",
+                       timespec->tv_sec + ((double)timespec->tv_nsec) / 1.0E9);
+}
+
+static PyObject *pygrpc_status_code(grpc_status_code c_status_code) {
+  switch (c_status_code) {
+    case GRPC_STATUS_OK:
+      return ok_status_code;
+    case GRPC_STATUS_CANCELLED:
+      return cancelled_status_code;
+    case GRPC_STATUS_UNKNOWN:
+      return unknown_status_code;
+    case GRPC_STATUS_INVALID_ARGUMENT:
+      return invalid_argument_status_code;
+    case GRPC_STATUS_DEADLINE_EXCEEDED:
+      return expired_status_code;
+    case GRPC_STATUS_NOT_FOUND:
+      return not_found_status_code;
+    case GRPC_STATUS_ALREADY_EXISTS:
+      return already_exists_status_code;
+    case GRPC_STATUS_PERMISSION_DENIED:
+      return permission_denied_status_code;
+    case GRPC_STATUS_UNAUTHENTICATED:
+      return unauthenticated_status_code;
+    case GRPC_STATUS_RESOURCE_EXHAUSTED:
+      return resource_exhausted_status_code;
+    case GRPC_STATUS_FAILED_PRECONDITION:
+      return failed_precondition_status_code;
+    case GRPC_STATUS_ABORTED:
+      return aborted_status_code;
+    case GRPC_STATUS_OUT_OF_RANGE:
+      return out_of_range_status_code;
+    case GRPC_STATUS_UNIMPLEMENTED:
+      return unimplemented_status_code;
+    case GRPC_STATUS_INTERNAL:
+      return internal_error_status_code;
+    case GRPC_STATUS_UNAVAILABLE:
+      return unavailable_status_code;
+    case GRPC_STATUS_DATA_LOSS:
+      return data_loss_status_code;
+    default:
+      return NULL;
+  }
+}
+
+static PyObject *pygrpc_stop_event_args(grpc_event *c_event) {
+  return Py_BuildValue("(OOOOOOO)", stop_event_kind, Py_None, Py_None, Py_None,
+                       Py_None, Py_None, Py_None);
+}
+
+static PyObject *pygrpc_write_event_args(grpc_event *c_event) {
+  PyObject *write_accepted =
+      c_event->data.write_accepted == GRPC_OP_OK ? Py_True : Py_False;
+  return Py_BuildValue("(OOOOOOO)", write_event_kind, (PyObject *)c_event->tag,
+                       write_accepted, Py_None, Py_None, Py_None, Py_None);
+}
+
+static PyObject *pygrpc_complete_event_args(grpc_event *c_event) {
+  PyObject *complete_accepted =
+      c_event->data.finish_accepted == GRPC_OP_OK ? Py_True : Py_False;
+  return Py_BuildValue("(OOOOOOO)", complete_event_kind,
+                       (PyObject *)c_event->tag, Py_None, complete_accepted,
+                       Py_None, Py_None, Py_None);
+}
+
+static PyObject *pygrpc_service_event_args(grpc_event *c_event) {
+  if (c_event->data.server_rpc_new.method == NULL) {
+    return Py_BuildValue("(OOOOOOO)", service_event_kind, c_event->tag,
+                         Py_None, Py_None, Py_None, Py_None, Py_None);
+  } else {
+    PyObject *method = PyBytes_FromString(c_event->data.server_rpc_new.method);
+    PyObject *host = PyBytes_FromString(c_event->data.server_rpc_new.host);
+    PyObject *service_deadline =
+        pygrpc_as_py_time(&c_event->data.server_rpc_new.deadline);
+
+    Call *call;
+    PyObject *service_acceptance_args;
+    PyObject *service_acceptance;
+    PyObject *event_args;
+
+    call = PyObject_New(Call, &pygrpc_CallType);
+    call->c_call = c_event->call;
+
+    service_acceptance_args =
+        Py_BuildValue("(OOOO)", call, method, host, service_deadline);
+    Py_DECREF(call);
+    Py_DECREF(method);
+    Py_DECREF(host);
+    Py_DECREF(service_deadline);
+
+    service_acceptance =
+        PyObject_CallObject(service_acceptance_class, service_acceptance_args);
+    Py_DECREF(service_acceptance_args);
+
+    event_args = Py_BuildValue("(OOOOOOO)", service_event_kind,
+                               (PyObject *)c_event->tag, Py_None, Py_None,
+                               service_acceptance, Py_None, Py_None);
+    Py_DECREF(service_acceptance);
+    return event_args;
+  }
+}
+
+static PyObject *pygrpc_read_event_args(grpc_event *c_event) {
+  if (c_event->data.read == NULL) {
+    return Py_BuildValue("(OOOOOOO)", read_event_kind,
+                         (PyObject *)c_event->tag, Py_None, Py_None, Py_None,
+                         Py_None, Py_None);
+  } else {
+    size_t length;
+    size_t offset;
+    grpc_byte_buffer_reader *reader;
+    gpr_slice slice;
+    char *c_bytes;
+    PyObject *bytes;
+    PyObject *event_args;
+
+    length = grpc_byte_buffer_length(c_event->data.read);
+    reader = grpc_byte_buffer_reader_create(c_event->data.read);
+    c_bytes = gpr_malloc(length);
+    offset = 0;
+    while (grpc_byte_buffer_reader_next(reader, &slice)) {
+      memcpy(c_bytes + offset, GPR_SLICE_START_PTR(slice),
+             GPR_SLICE_LENGTH(slice));
+      offset += GPR_SLICE_LENGTH(slice);
+    }
+    grpc_byte_buffer_reader_destroy(reader);
+    bytes = PyBytes_FromStringAndSize(c_bytes, length);
+    gpr_free(c_bytes);
+    event_args =
+        Py_BuildValue("(OOOOOOO)", read_event_kind, (PyObject *)c_event->tag,
+                      Py_None, Py_None, Py_None, bytes, Py_None);
+    Py_DECREF(bytes);
+    return event_args;
+  }
+}
+
+static PyObject *pygrpc_metadata_event_args(grpc_event *c_event) {
+  /* TODO(nathaniel): Actual transmission of metadata. */
+  return Py_BuildValue("(OOOOOOO)", metadata_event_kind,
+                       (PyObject *)c_event->tag, Py_None, Py_None, Py_None,
+                       Py_None, Py_None);
+}
+
+static PyObject *pygrpc_finished_event_args(grpc_event *c_event) {
+  PyObject *code;
+  PyObject *details;
+  PyObject *status_args;
+  PyObject *status;
+  PyObject *event_args;
+
+  code = pygrpc_status_code(c_event->data.finished.status);
+  if (code == NULL) {
+    PyErr_SetString(PyExc_RuntimeError, "Unrecognized status code!");
+    return NULL;
+  }
+  if (c_event->data.finished.details == NULL) {
+    details = PyBytes_FromString("");
+  } else {
+    details = PyBytes_FromString(c_event->data.finished.details);
+  }
+  status_args = Py_BuildValue("(OO)", code, details);
+  Py_DECREF(details);
+  status = PyObject_CallObject(status_class, status_args);
+  Py_DECREF(status_args);
+  event_args =
+      Py_BuildValue("(OOOOOOO)", finish_event_kind, (PyObject *)c_event->tag,
+                    Py_None, Py_None, Py_None, Py_None, status);
+  Py_DECREF(status);
+  return event_args;
+}
+
+static int pygrpc_completion_queue_init(CompletionQueue *self, PyObject *args,
+                                        PyObject *kwds) {
+  self->c_completion_queue = grpc_completion_queue_create();
+  return 0;
+}
+
+static void pygrpc_completion_queue_dealloc(CompletionQueue *self) {
+  grpc_completion_queue_destroy(self->c_completion_queue);
+  self->ob_type->tp_free((PyObject *)self);
+}
+
+static PyObject *pygrpc_completion_queue_get(CompletionQueue *self,
+                                             PyObject *args) {
+  PyObject *deadline;
+  double double_deadline;
+  gpr_timespec deadline_timespec;
+  grpc_event *c_event;
+
+  PyObject *event_args;
+  PyObject *event;
+
+  if (!(PyArg_ParseTuple(args, "O", &deadline))) {
+    return NULL;
+  }
+
+  if (deadline == Py_None) {
+    deadline_timespec = gpr_inf_future;
+  } else {
+    double_deadline = PyFloat_AsDouble(deadline);
+    deadline_timespec = gpr_time_from_nanos((long)(double_deadline * 1.0E9));
+  }
+
+  /* TODO(nathaniel): Suppress clang-format in this block and remove the
+     unnecessary and unPythonic semicolons trailing the _ALLOW_THREADS macros.
+     (Right now clang-format only understands //-demarcated suppressions.) */
+  Py_BEGIN_ALLOW_THREADS;
+  c_event =
+      grpc_completion_queue_next(self->c_completion_queue, deadline_timespec);
+  Py_END_ALLOW_THREADS;
+
+  if (c_event == NULL) {
+    Py_RETURN_NONE;
+  }
+
+  switch (c_event->type) {
+    case GRPC_QUEUE_SHUTDOWN:
+      event_args = pygrpc_stop_event_args(c_event);
+      break;
+    case GRPC_WRITE_ACCEPTED:
+      event_args = pygrpc_write_event_args(c_event);
+      break;
+    case GRPC_FINISH_ACCEPTED:
+      event_args = pygrpc_complete_event_args(c_event);
+      break;
+    case GRPC_SERVER_RPC_NEW:
+      event_args = pygrpc_service_event_args(c_event);
+      break;
+    case GRPC_READ:
+      event_args = pygrpc_read_event_args(c_event);
+      break;
+    case GRPC_CLIENT_METADATA_READ:
+      event_args = pygrpc_metadata_event_args(c_event);
+      break;
+    case GRPC_FINISHED:
+      event_args = pygrpc_finished_event_args(c_event);
+      break;
+    default:
+      PyErr_SetString(PyExc_Exception, "Unrecognized event type!");
+      return NULL;
+  }
+
+  if (event_args == NULL) {
+    return NULL;
+  }
+
+  event = PyObject_CallObject(event_class, event_args);
+
+  Py_DECREF(event_args);
+  Py_XDECREF((PyObject *)c_event->tag);
+  grpc_event_finish(c_event);
+
+  return event;
+}
+
+static PyObject *pygrpc_completion_queue_stop(CompletionQueue *self) {
+  grpc_completion_queue_shutdown(self->c_completion_queue);
+
+  Py_RETURN_NONE;
+}
+
+static PyMethodDef methods[] = {
+    {"get", (PyCFunction)pygrpc_completion_queue_get, METH_VARARGS,
+     "Get the next event."},
+    {"stop", (PyCFunction)pygrpc_completion_queue_stop, METH_NOARGS,
+     "Stop this completion queue."},
+    {NULL}};
+
+PyTypeObject pygrpc_CompletionQueueType = {
+    PyObject_HEAD_INIT(NULL)0,                   /*ob_size*/
+    "_gprc.CompletionQueue",                     /*tp_name*/
+    sizeof(CompletionQueue),                     /*tp_basicsize*/
+    0,                                           /*tp_itemsize*/
+    (destructor)pygrpc_completion_queue_dealloc, /*tp_dealloc*/
+    0,                                           /*tp_print*/
+    0,                                           /*tp_getattr*/
+    0,                                           /*tp_setattr*/
+    0,                                           /*tp_compare*/
+    0,                                           /*tp_repr*/
+    0,                                           /*tp_as_number*/
+    0,                                           /*tp_as_sequence*/
+    0,                                           /*tp_as_mapping*/
+    0,                                           /*tp_hash */
+    0,                                           /*tp_call*/
+    0,                                           /*tp_str*/
+    0,                                           /*tp_getattro*/
+    0,                                           /*tp_setattro*/
+    0,                                           /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT,                          /*tp_flags*/
+    "Wrapping of grpc_completion_queue.",        /* tp_doc */
+    0,                                           /* tp_traverse */
+    0,                                           /* tp_clear */
+    0,                                           /* tp_richcompare */
+    0,                                           /* tp_weaklistoffset */
+    0,                                           /* tp_iter */
+    0,                                           /* tp_iternext */
+    methods,                                     /* tp_methods */
+    0,                                           /* tp_members */
+    0,                                           /* tp_getset */
+    0,                                           /* tp_base */
+    0,                                           /* tp_dict */
+    0,                                           /* tp_descr_get */
+    0,                                           /* tp_descr_set */
+    0,                                           /* tp_dictoffset */
+    (initproc)pygrpc_completion_queue_init,      /* tp_init */
+};
+
+static int pygrpc_get_status_codes(PyObject *datatypes_module) {
+  PyObject *code_class = PyObject_GetAttrString(datatypes_module, "Code");
+  if (code_class == NULL) {
+    return -1;
+  }
+  ok_status_code = PyObject_GetAttrString(code_class, "OK");
+  if (ok_status_code == NULL) {
+    return -1;
+  }
+  cancelled_status_code = PyObject_GetAttrString(code_class, "CANCELLED");
+  if (cancelled_status_code == NULL) {
+    return -1;
+  }
+  unknown_status_code = PyObject_GetAttrString(code_class, "UNKNOWN");
+  if (unknown_status_code == NULL) {
+    return -1;
+  }
+  invalid_argument_status_code =
+      PyObject_GetAttrString(code_class, "INVALID_ARGUMENT");
+  if (invalid_argument_status_code == NULL) {
+    return -1;
+  }
+  expired_status_code = PyObject_GetAttrString(code_class, "EXPIRED");
+  if (expired_status_code == NULL) {
+    return -1;
+  }
+  not_found_status_code = PyObject_GetAttrString(code_class, "NOT_FOUND");
+  if (not_found_status_code == NULL) {
+    return -1;
+  }
+  already_exists_status_code =
+      PyObject_GetAttrString(code_class, "ALREADY_EXISTS");
+  if (already_exists_status_code == NULL) {
+    return -1;
+  }
+  permission_denied_status_code =
+      PyObject_GetAttrString(code_class, "PERMISSION_DENIED");
+  if (permission_denied_status_code == NULL) {
+    return -1;
+  }
+  unauthenticated_status_code =
+      PyObject_GetAttrString(code_class, "UNAUTHENTICATED");
+  if (unauthenticated_status_code == NULL) {
+    return -1;
+  }
+  resource_exhausted_status_code =
+      PyObject_GetAttrString(code_class, "RESOURCE_EXHAUSTED");
+  if (resource_exhausted_status_code == NULL) {
+    return -1;
+  }
+  failed_precondition_status_code =
+      PyObject_GetAttrString(code_class, "FAILED_PRECONDITION");
+  if (failed_precondition_status_code == NULL) {
+    return -1;
+  }
+  aborted_status_code = PyObject_GetAttrString(code_class, "ABORTED");
+  if (aborted_status_code == NULL) {
+    return -1;
+  }
+  out_of_range_status_code = PyObject_GetAttrString(code_class, "OUT_OF_RANGE");
+  if (out_of_range_status_code == NULL) {
+    return -1;
+  }
+  unimplemented_status_code =
+      PyObject_GetAttrString(code_class, "UNIMPLEMENTED");
+  if (unimplemented_status_code == NULL) {
+    return -1;
+  }
+  internal_error_status_code =
+      PyObject_GetAttrString(code_class, "INTERNAL_ERROR");
+  if (internal_error_status_code == NULL) {
+    return -1;
+  }
+  unavailable_status_code = PyObject_GetAttrString(code_class, "UNAVAILABLE");
+  if (unavailable_status_code == NULL) {
+    return -1;
+  }
+  data_loss_status_code = PyObject_GetAttrString(code_class, "DATA_LOSS");
+  if (data_loss_status_code == NULL) {
+    return -1;
+  }
+  Py_DECREF(code_class);
+  return 0;
+}
+
+static int pygrpc_get_event_kinds(PyObject *event_class) {
+  PyObject *kind_class = PyObject_GetAttrString(event_class, "Kind");
+  if (kind_class == NULL) {
+    return -1;
+  }
+  stop_event_kind = PyObject_GetAttrString(kind_class, "STOP");
+  if (stop_event_kind == NULL) {
+    return -1;
+  }
+  write_event_kind = PyObject_GetAttrString(kind_class, "WRITE_ACCEPTED");
+  if (write_event_kind == NULL) {
+    return -1;
+  }
+  complete_event_kind = PyObject_GetAttrString(kind_class, "COMPLETE_ACCEPTED");
+  if (complete_event_kind == NULL) {
+    return -1;
+  }
+  service_event_kind = PyObject_GetAttrString(kind_class, "SERVICE_ACCEPTED");
+  if (service_event_kind == NULL) {
+    return -1;
+  }
+  read_event_kind = PyObject_GetAttrString(kind_class, "READ_ACCEPTED");
+  if (read_event_kind == NULL) {
+    return -1;
+  }
+  metadata_event_kind = PyObject_GetAttrString(kind_class, "METADATA_ACCEPTED");
+  if (metadata_event_kind == NULL) {
+    return -1;
+  }
+  finish_event_kind = PyObject_GetAttrString(kind_class, "FINISH");
+  if (finish_event_kind == NULL) {
+    return -1;
+  }
+  Py_DECREF(kind_class);
+  return 0;
+}
+
+int pygrpc_add_completion_queue(PyObject *module) {
+  char *datatypes_module_path = "_adapter._datatypes";
+  PyObject *datatypes_module = PyImport_ImportModule(datatypes_module_path);
+  if (datatypes_module == NULL) {
+    PyErr_SetString(PyExc_ImportError, datatypes_module_path);
+    return -1;
+  }
+  status_class = PyObject_GetAttrString(datatypes_module, "Status");
+  service_acceptance_class =
+      PyObject_GetAttrString(datatypes_module, "ServiceAcceptance");
+  event_class = PyObject_GetAttrString(datatypes_module, "Event");
+  if (status_class == NULL || service_acceptance_class == NULL ||
+      event_class == NULL) {
+    PyErr_SetString(PyExc_ImportError, "Missing classes in _datatypes module!");
+    return -1;
+  }
+  if (pygrpc_get_status_codes(datatypes_module) == -1) {
+    PyErr_SetString(PyExc_ImportError, "Status codes import broken!");
+    return -1;
+  }
+  if (pygrpc_get_event_kinds(event_class) == -1) {
+    PyErr_SetString(PyExc_ImportError, "Event kinds import broken!");
+    return -1;
+  }
+  Py_DECREF(datatypes_module);
+
+  pygrpc_CompletionQueueType.tp_new = PyType_GenericNew;
+  if (PyType_Ready(&pygrpc_CompletionQueueType) < 0) {
+    PyErr_SetString(PyExc_RuntimeError,
+                    "Error defining pygrpc_CompletionQueueType!");
+    return -1;
+  }
+  if (PyModule_AddObject(module, "CompletionQueue",
+                         (PyObject *)&pygrpc_CompletionQueueType) == -1) {
+    PyErr_SetString(PyExc_ImportError,
+                    "Couldn't add CompletionQueue type to module!");
+    return -1;
+  }
+  return 0;
+}
diff --git a/include/grpc/support/time_posix.h b/src/python/src/_adapter/_completion_queue.h
similarity index 79%
copy from include/grpc/support/time_posix.h
copy to src/python/src/_adapter/_completion_queue.h
index 9ff6f7f..8e5ee9f 100644
--- a/include/grpc/support/time_posix.h
+++ b/src/python/src/_adapter/_completion_queue.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2014, Google Inc.
+ * Copyright 2015, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,13 +31,18 @@
  *
  */
 
-#ifndef __GRPC_SUPPORT_TIME_POSIX_H__
-#define __GRPC_SUPPORT_TIME_POSIX_H__
-/* Posix variant of gpr_time_platform.h */
+#ifndef _ADAPTER__COMPLETION_QUEUE_H_
+#define _ADAPTER__COMPLETION_QUEUE_H_
 
-#include <sys/time.h>
-#include <time.h>
+#include <Python.h>
+#include <grpc/grpc.h>
 
-typedef struct timespec gpr_timespec;
+typedef struct {
+  PyObject_HEAD grpc_completion_queue *c_completion_queue;
+} CompletionQueue;
 
-#endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */
+PyTypeObject pygrpc_CompletionQueueType;
+
+int pygrpc_add_completion_queue(PyObject *module);
+
+#endif /* _ADAPTER__COMPLETION_QUEUE_H_ */
diff --git a/src/python/src/_adapter/_datatypes.py b/src/python/src/_adapter/_datatypes.py
new file mode 100644
index 0000000..e271ec8
--- /dev/null
+++ b/src/python/src/_adapter/_datatypes.py
@@ -0,0 +1,86 @@
+# 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.
+
+"""Datatypes passed between Python and C code."""
+
+import collections
+import enum
+
+
+@enum.unique
+class Code(enum.IntEnum):
+  """One Platform error codes (see status.h and codes.proto)."""
+
+  OK = 0
+  CANCELLED = 1
+  UNKNOWN = 2
+  INVALID_ARGUMENT = 3
+  EXPIRED = 4
+  NOT_FOUND = 5
+  ALREADY_EXISTS = 6
+  PERMISSION_DENIED = 7
+  UNAUTHENTICATED = 16
+  RESOURCE_EXHAUSTED = 8
+  FAILED_PRECONDITION = 9
+  ABORTED = 10
+  OUT_OF_RANGE = 11
+  UNIMPLEMENTED = 12
+  INTERNAL_ERROR = 13
+  UNAVAILABLE = 14
+  DATA_LOSS = 15
+
+
+class Status(collections.namedtuple('Status', ['code', 'details'])):
+  """Describes an RPC's overall status."""
+
+
+class ServiceAcceptance(
+    collections.namedtuple(
+        'ServiceAcceptance', ['call', 'method', 'host', 'deadline'])):
+  """Describes an RPC on the service side at the start of service."""
+
+
+class Event(
+    collections.namedtuple(
+        'Event',
+        ['kind', 'tag', 'write_accepted', 'complete_accepted',
+         'service_acceptance', 'bytes', 'status'])):
+  """Describes an event emitted from a completion queue."""
+
+  @enum.unique
+  class Kind(enum.Enum):
+    """Describes the kind of an event."""
+
+    STOP = object()
+    WRITE_ACCEPTED = object()
+    COMPLETE_ACCEPTED = object()
+    SERVICE_ACCEPTED = object()
+    READ_ACCEPTED = object()
+    METADATA_ACCEPTED = object()
+    FINISH = object()
diff --git a/src/python/src/_adapter/_error.c b/src/python/src/_adapter/_error.c
new file mode 100644
index 0000000..8c04f4b
--- /dev/null
+++ b/src/python/src/_adapter/_error.c
@@ -0,0 +1,79 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+#include "_adapter/_error.h"
+
+#include <Python.h>
+#include <grpc/grpc.h>
+
+const PyObject *pygrpc_translate_call_error(grpc_call_error call_error) {
+  switch (call_error) {
+    case GRPC_CALL_OK:
+      Py_RETURN_NONE;
+    case GRPC_CALL_ERROR:
+      PyErr_SetString(PyExc_Exception, "Defect: unknown defect!");
+      return NULL;
+    case GRPC_CALL_ERROR_NOT_ON_SERVER:
+      PyErr_SetString(PyExc_Exception,
+                      "Defect: client-only method called on server!");
+      return NULL;
+    case GRPC_CALL_ERROR_NOT_ON_CLIENT:
+      PyErr_SetString(PyExc_Exception,
+                      "Defect: server-only method called on client!");
+      return NULL;
+    case GRPC_CALL_ERROR_ALREADY_ACCEPTED:
+      PyErr_SetString(PyExc_Exception,
+                      "Defect: attempted to accept already-accepted call!");
+      return NULL;
+    case GRPC_CALL_ERROR_ALREADY_INVOKED:
+      PyErr_SetString(PyExc_Exception,
+                      "Defect: attempted to invoke already-invoked call!");
+      return NULL;
+    case GRPC_CALL_ERROR_NOT_INVOKED:
+      PyErr_SetString(PyExc_Exception, "Defect: Call not yet invoked!");
+      return NULL;
+    case GRPC_CALL_ERROR_ALREADY_FINISHED:
+      PyErr_SetString(PyExc_Exception, "Defect: Call already finished!");
+      return NULL;
+    case GRPC_CALL_ERROR_TOO_MANY_OPERATIONS:
+      PyErr_SetString(PyExc_Exception,
+                      "Defect: Attempted extra read or extra write on call!");
+      return NULL;
+    case GRPC_CALL_ERROR_INVALID_FLAGS:
+      PyErr_SetString(PyExc_Exception, "Defect: invalid flags!");
+      return NULL;
+    default:
+      PyErr_SetString(PyExc_Exception, "Defect: Unknown call error!");
+      return NULL;
+  }
+}
diff --git a/include/grpc/support/thd_posix.h b/src/python/src/_adapter/_error.h
similarity index 85%
rename from include/grpc/support/thd_posix.h
rename to src/python/src/_adapter/_error.h
index b688e45..6988b1c 100644
--- a/include/grpc/support/thd_posix.h
+++ b/src/python/src/_adapter/_error.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2014, Google Inc.
+ * Copyright 2015, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,12 +31,12 @@
  *
  */
 
-#ifndef __GRPC_SUPPORT_THD_POSIX_H__
-#define __GRPC_SUPPORT_THD_POSIX_H__
-/* Posix variant of gpr_thd_platform.h. */
+#ifndef _ADAPTER__ERROR_H_
+#define _ADAPTER__ERROR_H_
 
-#include <pthread.h>
+#include <Python.h>
+#include <grpc/grpc.h>
 
-typedef pthread_t gpr_thd_id;
+const PyObject *pygrpc_translate_call_error(grpc_call_error call_error);
 
-#endif /* __GRPC_SUPPORT_THD_POSIX_H__ */
+#endif /* _ADAPTER__ERROR_H_ */
diff --git a/src/python/src/_adapter/_event_invocation_synchronous_event_service_test.py b/src/python/src/_adapter/_event_invocation_synchronous_event_service_test.py
new file mode 100644
index 0000000..69d91ec
--- /dev/null
+++ b/src/python/src/_adapter/_event_invocation_synchronous_event_service_test.py
@@ -0,0 +1,46 @@
+# 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.
+
+"""One of the tests of the Face layer of RPC Framework."""
+
+import unittest
+
+from _adapter import _face_test_case
+from _framework.face.testing import event_invocation_synchronous_event_service_test_case as test_case
+
+
+class EventInvocationSynchronousEventServiceTest(
+    _face_test_case.FaceTestCase,
+    test_case.EventInvocationSynchronousEventServiceTestCase,
+    unittest.TestCase):
+  pass
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/src/python/src/_adapter/_face_test_case.py b/src/python/src/_adapter/_face_test_case.py
new file mode 100644
index 0000000..112dcfb
--- /dev/null
+++ b/src/python/src/_adapter/_face_test_case.py
@@ -0,0 +1,124 @@
+# 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.
+
+"""Common construction and destruction for GRPC-backed Face-layer tests."""
+
+import unittest
+
+from _adapter import fore
+from _adapter import rear
+from _framework.base import util
+from _framework.base.packets import implementations as tickets_implementations
+from _framework.face import implementations as face_implementations
+from _framework.face.testing import coverage
+from _framework.face.testing import serial
+from _framework.face.testing import test_case
+from _framework.foundation import logging_pool
+
+_TIMEOUT = 3
+_MAXIMUM_TIMEOUT = 90
+_MAXIMUM_POOL_SIZE = 400
+
+
+class FaceTestCase(test_case.FaceTestCase, coverage.BlockingCoverage):
+  """Provides abstract Face-layer tests a GRPC-backed implementation."""
+
+  def set_up_implementation(
+      self,
+      name,
+      methods,
+      inline_value_in_value_out_methods,
+      inline_value_in_stream_out_methods,
+      inline_stream_in_value_out_methods,
+      inline_stream_in_stream_out_methods,
+      event_value_in_value_out_methods,
+      event_value_in_stream_out_methods,
+      event_stream_in_value_out_methods,
+      event_stream_in_stream_out_methods,
+      multi_method):
+    pool = logging_pool.pool(_MAXIMUM_POOL_SIZE)
+
+    servicer = face_implementations.servicer(
+        pool,
+        inline_value_in_value_out_methods=inline_value_in_value_out_methods,
+        inline_value_in_stream_out_methods=inline_value_in_stream_out_methods,
+        inline_stream_in_value_out_methods=inline_stream_in_value_out_methods,
+        inline_stream_in_stream_out_methods=inline_stream_in_stream_out_methods,
+        event_value_in_value_out_methods=event_value_in_value_out_methods,
+        event_value_in_stream_out_methods=event_value_in_stream_out_methods,
+        event_stream_in_value_out_methods=event_stream_in_value_out_methods,
+        event_stream_in_stream_out_methods=event_stream_in_stream_out_methods,
+        multi_method=multi_method)
+
+    serialization = serial.serialization(methods)
+
+    fore_link = fore.ForeLink(
+        pool, serialization.request_deserializers,
+        serialization.response_serializers)
+    port = fore_link.start()
+    rear_link = rear.RearLink(
+        'localhost', port, pool,
+        serialization.request_serializers, serialization.response_deserializers)
+    rear_link.start()
+    front = tickets_implementations.front(pool, pool, pool)
+    back = tickets_implementations.back(
+        servicer, pool, pool, pool, _TIMEOUT, _MAXIMUM_TIMEOUT)
+    fore_link.join_rear_link(back)
+    back.join_fore_link(fore_link)
+    rear_link.join_fore_link(front)
+    front.join_rear_link(rear_link)
+
+    server = face_implementations.server()
+    stub = face_implementations.stub(front, pool)
+    return server, stub, (rear_link, fore_link, front, back)
+
+  def tear_down_implementation(self, memo):
+    rear_link, fore_link, front, back = memo
+    # TODO(nathaniel): Waiting for the front and back to idle possibly should
+    # not be necessary - investigate as part of graceful shutdown work.
+    util.wait_for_idle(front)
+    util.wait_for_idle(back)
+    rear_link.stop()
+    fore_link.stop()
+
+  @unittest.skip('Service-side failure not transmitted by GRPC.')
+  def testFailedUnaryRequestUnaryResponse(self):
+    raise NotImplementedError()
+
+  @unittest.skip('Service-side failure not transmitted by GRPC.')
+  def testFailedUnaryRequestStreamResponse(self):
+    raise NotImplementedError()
+
+  @unittest.skip('Service-side failure not transmitted by GRPC.')
+  def testFailedStreamRequestUnaryResponse(self):
+    raise NotImplementedError()
+
+  @unittest.skip('Service-side failure not transmitted by GRPC.')
+  def testFailedStreamRequestStreamResponse(self):
+    raise NotImplementedError()
diff --git a/src/python/src/_adapter/_future_invocation_asynchronous_event_service_test.py b/src/python/src/_adapter/_future_invocation_asynchronous_event_service_test.py
new file mode 100644
index 0000000..3db39dd
--- /dev/null
+++ b/src/python/src/_adapter/_future_invocation_asynchronous_event_service_test.py
@@ -0,0 +1,46 @@
+# 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.
+
+"""One of the tests of the Face layer of RPC Framework."""
+
+import unittest
+
+from _adapter import _face_test_case
+from _framework.face.testing import future_invocation_asynchronous_event_service_test_case as test_case
+
+
+class FutureInvocationAsynchronousEventServiceTest(
+    _face_test_case.FaceTestCase,
+    test_case.FutureInvocationAsynchronousEventServiceTestCase,
+    unittest.TestCase):
+  pass
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/src/python/src/_adapter/_links_test.py b/src/python/src/_adapter/_links_test.py
new file mode 100644
index 0000000..8341460
--- /dev/null
+++ b/src/python/src/_adapter/_links_test.py
@@ -0,0 +1,246 @@
+# 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.
+
+"""Test of the GRPC-backed ForeLink and RearLink."""
+
+import threading
+import unittest
+
+from _adapter import _proto_scenarios
+from _adapter import _test_links
+from _adapter import fore
+from _adapter import rear
+from _framework.base import interfaces
+from _framework.base.packets import packets as tickets
+from _framework.foundation import logging_pool
+
+_IDENTITY = lambda x: x
+_TIMEOUT = 2
+
+
+class RoundTripTest(unittest.TestCase):
+
+  def setUp(self):
+    self.fore_link_pool = logging_pool.pool(80)
+    self.rear_link_pool = logging_pool.pool(80)
+
+  def tearDown(self):
+    self.rear_link_pool.shutdown(wait=True)
+    self.fore_link_pool.shutdown(wait=True)
+
+  def testZeroMessageRoundTrip(self):
+    test_operation_id = object()
+    test_method = 'test method'
+    test_fore_link = _test_links.ForeLink(None, None)
+    def rear_action(front_to_back_ticket, fore_link):
+      if front_to_back_ticket.kind in (
+          tickets.Kind.COMPLETION, tickets.Kind.ENTIRE):
+        back_to_front_ticket = tickets.BackToFrontPacket(
+            front_to_back_ticket.operation_id, 0, tickets.Kind.COMPLETION, None)
+        fore_link.accept_back_to_front_ticket(back_to_front_ticket)
+    test_rear_link = _test_links.RearLink(rear_action, None)
+
+    fore_link = fore.ForeLink(
+        self.fore_link_pool, {test_method: None}, {test_method: None})
+    fore_link.join_rear_link(test_rear_link)
+    test_rear_link.join_fore_link(fore_link)
+    port = fore_link.start()
+
+    rear_link = rear.RearLink(
+        'localhost', port, self.rear_link_pool, {test_method: None},
+        {test_method: None})
+    rear_link.join_fore_link(test_fore_link)
+    test_fore_link.join_rear_link(rear_link)
+    rear_link.start()
+
+    front_to_back_ticket = tickets.FrontToBackPacket(
+        test_operation_id, 0, tickets.Kind.ENTIRE, test_method,
+        interfaces.ServicedSubscription.Kind.FULL, None, None, _TIMEOUT)
+    rear_link.accept_front_to_back_ticket(front_to_back_ticket)
+
+    with test_fore_link.condition:
+      while (not test_fore_link.tickets or
+             test_fore_link.tickets[-1].kind is tickets.Kind.CONTINUATION):
+        test_fore_link.condition.wait()
+
+    rear_link.stop()
+    fore_link.stop()
+
+    with test_fore_link.condition:
+      self.assertIs(test_fore_link.tickets[-1].kind, tickets.Kind.COMPLETION)
+
+  def testEntireRoundTrip(self):
+    test_operation_id = object()
+    test_method = 'test method'
+    test_front_to_back_datum = b'\x07'
+    test_back_to_front_datum = b'\x08'
+    test_fore_link = _test_links.ForeLink(None, None)
+    rear_sequence_number = [0]
+    def rear_action(front_to_back_ticket, fore_link):
+      if front_to_back_ticket.payload is None:
+        payload = None
+      else:
+        payload = test_back_to_front_datum
+      terminal = front_to_back_ticket.kind in (
+          tickets.Kind.COMPLETION, tickets.Kind.ENTIRE)
+      if payload is not None or terminal:
+        back_to_front_ticket = tickets.BackToFrontPacket(
+            front_to_back_ticket.operation_id, rear_sequence_number[0],
+            tickets.Kind.COMPLETION if terminal else tickets.Kind.CONTINUATION,
+            payload)
+        rear_sequence_number[0] += 1
+        fore_link.accept_back_to_front_ticket(back_to_front_ticket)
+    test_rear_link = _test_links.RearLink(rear_action, None)
+
+    fore_link = fore.ForeLink(
+        self.fore_link_pool, {test_method: _IDENTITY},
+        {test_method: _IDENTITY})
+    fore_link.join_rear_link(test_rear_link)
+    test_rear_link.join_fore_link(fore_link)
+    port = fore_link.start()
+
+    rear_link = rear.RearLink(
+        'localhost', port, self.rear_link_pool, {test_method: _IDENTITY},
+        {test_method: _IDENTITY})
+    rear_link.join_fore_link(test_fore_link)
+    test_fore_link.join_rear_link(rear_link)
+    rear_link.start()
+
+    front_to_back_ticket = tickets.FrontToBackPacket(
+        test_operation_id, 0, tickets.Kind.ENTIRE, test_method,
+        interfaces.ServicedSubscription.Kind.FULL, None,
+        test_front_to_back_datum, _TIMEOUT)
+    rear_link.accept_front_to_back_ticket(front_to_back_ticket)
+
+    with test_fore_link.condition:
+      while (not test_fore_link.tickets or
+             test_fore_link.tickets[-1].kind is not tickets.Kind.COMPLETION):
+        test_fore_link.condition.wait()
+
+    rear_link.stop()
+    fore_link.stop()
+
+    with test_rear_link.condition:
+      front_to_back_payloads = tuple(
+          ticket.payload for ticket in test_rear_link.tickets
+          if ticket.payload is not None)
+    with test_fore_link.condition:
+      back_to_front_payloads = tuple(
+          ticket.payload for ticket in test_fore_link.tickets
+          if ticket.payload is not None)
+    self.assertTupleEqual((test_front_to_back_datum,), front_to_back_payloads)
+    self.assertTupleEqual((test_back_to_front_datum,), back_to_front_payloads)
+
+  def _perform_scenario_test(self, scenario):
+    test_operation_id = object()
+    test_method = scenario.method()
+    test_fore_link = _test_links.ForeLink(None, None)
+    rear_lock = threading.Lock()
+    rear_sequence_number = [0]
+    def rear_action(front_to_back_ticket, fore_link):
+      with rear_lock:
+        if front_to_back_ticket.payload is not None:
+          response = scenario.response_for_request(front_to_back_ticket.payload)
+        else:
+          response = None
+      terminal = front_to_back_ticket.kind in (
+          tickets.Kind.COMPLETION, tickets.Kind.ENTIRE)
+      if response is not None or terminal:
+        back_to_front_ticket = tickets.BackToFrontPacket(
+            front_to_back_ticket.operation_id, rear_sequence_number[0],
+            tickets.Kind.COMPLETION if terminal else tickets.Kind.CONTINUATION,
+            response)
+        rear_sequence_number[0] += 1
+        fore_link.accept_back_to_front_ticket(back_to_front_ticket)
+    test_rear_link = _test_links.RearLink(rear_action, None)
+
+    fore_link = fore.ForeLink(
+        self.fore_link_pool, {test_method: scenario.deserialize_request},
+        {test_method: scenario.serialize_response})
+    fore_link.join_rear_link(test_rear_link)
+    test_rear_link.join_fore_link(fore_link)
+    port = fore_link.start()
+
+    rear_link = rear.RearLink(
+        'localhost', port, self.rear_link_pool,
+        {test_method: scenario.serialize_request},
+        {test_method: scenario.deserialize_response})
+    rear_link.join_fore_link(test_fore_link)
+    test_fore_link.join_rear_link(rear_link)
+    rear_link.start()
+
+    commencement_ticket = tickets.FrontToBackPacket(
+        test_operation_id, 0, tickets.Kind.COMMENCEMENT, test_method,
+        interfaces.ServicedSubscription.Kind.FULL, None, None, _TIMEOUT)
+    fore_sequence_number = 1
+    rear_link.accept_front_to_back_ticket(commencement_ticket)
+    for request in scenario.requests():
+      continuation_ticket = tickets.FrontToBackPacket(
+          test_operation_id, fore_sequence_number, tickets.Kind.CONTINUATION,
+          None, None, None, request, None)
+      fore_sequence_number += 1
+      rear_link.accept_front_to_back_ticket(continuation_ticket)
+    completion_ticket = tickets.FrontToBackPacket(
+        test_operation_id, fore_sequence_number, tickets.Kind.COMPLETION, None,
+        None, None, None, None)
+    fore_sequence_number += 1
+    rear_link.accept_front_to_back_ticket(completion_ticket)
+
+    with test_fore_link.condition:
+      while (not test_fore_link.tickets or
+             test_fore_link.tickets[-1].kind is not tickets.Kind.COMPLETION):
+        test_fore_link.condition.wait()
+
+    rear_link.stop()
+    fore_link.stop()
+
+    with test_rear_link.condition:
+      requests = tuple(
+          ticket.payload for ticket in test_rear_link.tickets
+          if ticket.payload is not None)
+    with test_fore_link.condition:
+      responses = tuple(
+          ticket.payload for ticket in test_fore_link.tickets
+          if ticket.payload is not None)
+    self.assertTrue(scenario.verify_requests(requests))
+    self.assertTrue(scenario.verify_responses(responses))
+
+  def testEmptyScenario(self):
+    self._perform_scenario_test(_proto_scenarios.EmptyScenario())
+
+  def testBidirectionallyUnaryScenario(self):
+    self._perform_scenario_test(_proto_scenarios.BidirectionallyUnaryScenario())
+
+  def testBidirectionallyStreamingScenario(self):
+    self._perform_scenario_test(
+        _proto_scenarios.BidirectionallyStreamingScenario())
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/src/python/src/_adapter/_lonely_rear_link_test.py b/src/python/src/_adapter/_lonely_rear_link_test.py
new file mode 100644
index 0000000..7ccdb0b
--- /dev/null
+++ b/src/python/src/_adapter/_lonely_rear_link_test.py
@@ -0,0 +1,97 @@
+# 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.
+
+"""A test of invocation-side code unconnected to an RPC server."""
+
+import unittest
+
+from _adapter import _test_links
+from _adapter import rear
+from _framework.base import interfaces
+from _framework.base.packets import packets
+from _framework.foundation import logging_pool
+
+_IDENTITY = lambda x: x
+_TIMEOUT = 2
+
+
+class LonelyRearLinkTest(unittest.TestCase):
+
+  def setUp(self):
+    self.pool = logging_pool.pool(80)
+
+  def tearDown(self):
+    self.pool.shutdown(wait=True)
+
+  def testUpAndDown(self):
+    rear_link = rear.RearLink('nonexistent', 54321, self.pool, {}, {})
+
+    rear_link.start()
+    rear_link.stop()
+
+  def _perform_lonely_client_test_with_ticket_kind(
+      self, front_to_back_ticket_kind):
+    test_operation_id = object()
+    test_method = 'test method'
+    fore_link = _test_links.ForeLink(None, None)
+
+    rear_link = rear.RearLink(
+        'nonexistent', 54321, self.pool, {test_method: None},
+        {test_method: None})
+    rear_link.join_fore_link(fore_link)
+    rear_link.start()
+
+    front_to_back_ticket = packets.FrontToBackPacket(
+        test_operation_id, 0, front_to_back_ticket_kind, test_method,
+        interfaces.ServicedSubscription.Kind.FULL, None, None, _TIMEOUT)
+    rear_link.accept_front_to_back_ticket(front_to_back_ticket)
+
+    with fore_link.condition:
+      while True:
+        if (fore_link.tickets and
+            fore_link.tickets[-1].kind is not packets.Kind.CONTINUATION):
+          break
+        fore_link.condition.wait()
+
+    rear_link.stop()
+
+    with fore_link.condition:
+      self.assertIsNot(fore_link.tickets[-1].kind, packets.Kind.COMPLETION)
+      
+  @unittest.skip('TODO(nathaniel): This seems to have broken in the last few weeks; fix it.')
+  def testLonelyClientCommencementPacket(self):
+    self._perform_lonely_client_test_with_ticket_kind(
+        packets.Kind.COMMENCEMENT)
+
+  def testLonelyClientEntirePacket(self):
+    self._perform_lonely_client_test_with_ticket_kind(packets.Kind.ENTIRE)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/src/python/src/_adapter/_low.py b/src/python/src/_adapter/_low.py
new file mode 100644
index 0000000..6c24087
--- /dev/null
+++ b/src/python/src/_adapter/_low.py
@@ -0,0 +1,55 @@
+# 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.
+
+"""A Python interface for GRPC C core structures and behaviors."""
+
+import atexit
+import gc
+
+from _adapter import _c
+from _adapter import _datatypes
+
+def _shut_down():
+  # force garbage collection before shutting down grpc, to ensure all grpc
+  # objects are cleaned up
+  gc.collect()
+  _c.shut_down()
+
+_c.init()
+atexit.register(_shut_down)
+
+# pylint: disable=invalid-name
+Code = _datatypes.Code
+Status = _datatypes.Status
+Event = _datatypes.Event
+Call = _c.Call
+Channel = _c.Channel
+CompletionQueue = _c.CompletionQueue
+Server = _c.Server
+# pylint: enable=invalid-name
diff --git a/src/python/src/_adapter/_low_test.py b/src/python/src/_adapter/_low_test.py
new file mode 100644
index 0000000..57b3be6
--- /dev/null
+++ b/src/python/src/_adapter/_low_test.py
@@ -0,0 +1,371 @@
+# 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.
+
+"""Tests for _adapter._low."""
+
+import time
+import unittest
+
+from _adapter import _low
+
+_STREAM_LENGTH = 300
+_TIMEOUT = 5
+_AFTER_DELAY = 2
+_FUTURE = time.time() + 60 * 60 * 24
+_BYTE_SEQUENCE = b'\abcdefghijklmnopqrstuvwxyz0123456789' * 200
+_BYTE_SEQUENCE_SEQUENCE = tuple(
+    bytes(bytearray((row + column) % 256 for column in range(row)))
+    for row in range(_STREAM_LENGTH))
+
+
+class LonelyClientTest(unittest.TestCase):
+
+  def testLonelyClient(self):
+    host = 'nosuchhostexists'
+    port = 54321
+    method = 'test method'
+    deadline = time.time() + _TIMEOUT
+    after_deadline = deadline + _AFTER_DELAY
+    metadata_tag = object()
+    finish_tag = object()
+
+    completion_queue = _low.CompletionQueue()
+    channel = _low.Channel('%s:%d' % (host, port))
+    client_call = _low.Call(channel, method, host, deadline)
+
+    client_call.invoke(completion_queue, metadata_tag, finish_tag)
+    first_event = completion_queue.get(after_deadline)
+    self.assertIsNotNone(first_event)
+    second_event = completion_queue.get(after_deadline)
+    self.assertIsNotNone(second_event)
+    kinds = [event.kind for event in (first_event, second_event)]
+    self.assertItemsEqual(
+        (_low.Event.Kind.METADATA_ACCEPTED, _low.Event.Kind.FINISH),
+        kinds)
+
+    self.assertIsNone(completion_queue.get(after_deadline))
+
+    completion_queue.stop()
+    stop_event = completion_queue.get(_FUTURE)
+    self.assertEqual(_low.Event.Kind.STOP, stop_event.kind)
+
+
+class EchoTest(unittest.TestCase):
+
+  def setUp(self):
+    self.host = 'localhost'
+
+    self.server_completion_queue = _low.CompletionQueue()
+    self.server = _low.Server(self.server_completion_queue)
+    port = self.server.add_http2_addr('[::]:0')
+    self.server.start()
+
+    self.client_completion_queue = _low.CompletionQueue()
+    self.channel = _low.Channel('%s:%d' % (self.host, port))
+
+  def tearDown(self):
+    self.server.stop()
+    # NOTE(nathaniel): Yep, this is weird; it's a consequence of
+    # grpc_server_destroy's being what has the effect of telling the server's
+    # completion queue to pump out all pending events/tags immediately rather
+    # than gracefully completing all outstanding RPCs while accepting no new
+    # ones.
+    # TODO(nathaniel): Deallocation of a Python object shouldn't have this kind
+    # of observable side effect let alone such an important one.
+    del self.server
+    self.server_completion_queue.stop()
+    self.client_completion_queue.stop()
+    while True:
+      event = self.server_completion_queue.get(_FUTURE)
+      if event is not None and event.kind is _low.Event.Kind.STOP:
+        break
+    while True:
+      event = self.client_completion_queue.get(_FUTURE)
+      if event is not None and event.kind is _low.Event.Kind.STOP:
+        break
+    self.server_completion_queue = None
+    self.client_completion_queue = None
+
+  def _perform_echo_test(self, test_data):
+    method = 'test method'
+    details = 'test details'
+    deadline = _FUTURE
+    metadata_tag = object()
+    finish_tag = object()
+    write_tag = object()
+    complete_tag = object()
+    service_tag = object()
+    read_tag = object()
+    status_tag = object()
+
+    server_data = []
+    client_data = []
+
+    client_call = _low.Call(self.channel, method, self.host, deadline)
+
+    client_call.invoke(self.client_completion_queue, metadata_tag, finish_tag)
+
+    self.server.service(service_tag)
+    service_accepted = self.server_completion_queue.get(_FUTURE)
+    self.assertIsNotNone(service_accepted)
+    self.assertIs(service_accepted.kind, _low.Event.Kind.SERVICE_ACCEPTED)
+    self.assertIs(service_accepted.tag, service_tag)
+    self.assertEqual(method, service_accepted.service_acceptance.method)
+    self.assertEqual(self.host, service_accepted.service_acceptance.host)
+    self.assertIsNotNone(service_accepted.service_acceptance.call)
+    server_call = service_accepted.service_acceptance.call
+    server_call.accept(self.server_completion_queue, finish_tag)
+    server_call.premetadata()
+
+    metadata_accepted = self.client_completion_queue.get(_FUTURE)
+    self.assertIsNotNone(metadata_accepted)
+    self.assertEqual(_low.Event.Kind.METADATA_ACCEPTED, metadata_accepted.kind)
+    self.assertEqual(metadata_tag, metadata_accepted.tag)
+    # TODO(nathaniel): Test transmission and reception of metadata.
+
+    for datum in test_data:
+      client_call.write(datum, write_tag)
+      write_accepted = self.client_completion_queue.get(_FUTURE)
+      self.assertIsNotNone(write_accepted)
+      self.assertIs(write_accepted.kind, _low.Event.Kind.WRITE_ACCEPTED)
+      self.assertIs(write_accepted.tag, write_tag)
+      self.assertIs(write_accepted.write_accepted, True)
+
+      server_call.read(read_tag)
+      read_accepted = self.server_completion_queue.get(_FUTURE)
+      self.assertIsNotNone(read_accepted)
+      self.assertEqual(_low.Event.Kind.READ_ACCEPTED, read_accepted.kind)
+      self.assertEqual(read_tag, read_accepted.tag)
+      self.assertIsNotNone(read_accepted.bytes)
+      server_data.append(read_accepted.bytes)
+
+      server_call.write(read_accepted.bytes, write_tag)
+      write_accepted = self.server_completion_queue.get(_FUTURE)
+      self.assertIsNotNone(write_accepted)
+      self.assertEqual(_low.Event.Kind.WRITE_ACCEPTED, write_accepted.kind)
+      self.assertEqual(write_tag, write_accepted.tag)
+      self.assertTrue(write_accepted.write_accepted)
+
+      client_call.read(read_tag)
+      read_accepted = self.client_completion_queue.get(_FUTURE)
+      self.assertIsNotNone(read_accepted)
+      self.assertEqual(_low.Event.Kind.READ_ACCEPTED, read_accepted.kind)
+      self.assertEqual(read_tag, read_accepted.tag)
+      self.assertIsNotNone(read_accepted.bytes)
+      client_data.append(read_accepted.bytes)
+
+    client_call.complete(complete_tag)
+    complete_accepted = self.client_completion_queue.get(_FUTURE)
+    self.assertIsNotNone(complete_accepted)
+    self.assertIs(complete_accepted.kind, _low.Event.Kind.COMPLETE_ACCEPTED)
+    self.assertIs(complete_accepted.tag, complete_tag)
+    self.assertIs(complete_accepted.complete_accepted, True)
+
+    server_call.read(read_tag)
+    read_accepted = self.server_completion_queue.get(_FUTURE)
+    self.assertIsNotNone(read_accepted)
+    self.assertEqual(_low.Event.Kind.READ_ACCEPTED, read_accepted.kind)
+    self.assertEqual(read_tag, read_accepted.tag)
+    self.assertIsNone(read_accepted.bytes)
+
+    server_call.status(_low.Status(_low.Code.OK, details), status_tag)
+    server_terminal_event_one = self.server_completion_queue.get(_FUTURE)
+    server_terminal_event_two = self.server_completion_queue.get(_FUTURE)
+    if server_terminal_event_one.kind == _low.Event.Kind.COMPLETE_ACCEPTED:
+      status_accepted = server_terminal_event_one
+      rpc_accepted = server_terminal_event_two
+    else:
+      status_accepted = server_terminal_event_two
+      rpc_accepted = server_terminal_event_one
+    self.assertIsNotNone(status_accepted)
+    self.assertIsNotNone(rpc_accepted)
+    self.assertEqual(_low.Event.Kind.COMPLETE_ACCEPTED, status_accepted.kind)
+    self.assertEqual(status_tag, status_accepted.tag)
+    self.assertTrue(status_accepted.complete_accepted)
+    self.assertEqual(_low.Event.Kind.FINISH, rpc_accepted.kind)
+    self.assertEqual(finish_tag, rpc_accepted.tag)
+    self.assertEqual(_low.Status(_low.Code.OK, ''), rpc_accepted.status)
+
+    client_call.read(read_tag)
+    client_terminal_event_one = self.client_completion_queue.get(_FUTURE)
+    client_terminal_event_two = self.client_completion_queue.get(_FUTURE)
+    if client_terminal_event_one.kind == _low.Event.Kind.READ_ACCEPTED:
+      read_accepted = client_terminal_event_one
+      finish_accepted = client_terminal_event_two
+    else:
+      read_accepted = client_terminal_event_two
+      finish_accepted = client_terminal_event_one
+    self.assertIsNotNone(read_accepted)
+    self.assertIsNotNone(finish_accepted)
+    self.assertEqual(_low.Event.Kind.READ_ACCEPTED, read_accepted.kind)
+    self.assertEqual(read_tag, read_accepted.tag)
+    self.assertIsNone(read_accepted.bytes)
+    self.assertEqual(_low.Event.Kind.FINISH, finish_accepted.kind)
+    self.assertEqual(finish_tag, finish_accepted.tag)
+    self.assertEqual(_low.Status(_low.Code.OK, details), finish_accepted.status)
+
+    server_timeout_none_event = self.server_completion_queue.get(0)
+    self.assertIsNone(server_timeout_none_event)
+    client_timeout_none_event = self.client_completion_queue.get(0)
+    self.assertIsNone(client_timeout_none_event)
+
+    self.assertSequenceEqual(test_data, server_data)
+    self.assertSequenceEqual(test_data, client_data)
+
+  def testNoEcho(self):
+    self._perform_echo_test(())
+
+  def testOneByteEcho(self):
+    self._perform_echo_test([b'\x07'])
+
+  def testOneManyByteEcho(self):
+    self._perform_echo_test([_BYTE_SEQUENCE])
+
+  def testManyOneByteEchoes(self):
+    self._perform_echo_test(_BYTE_SEQUENCE)
+
+  def testManyManyByteEchoes(self):
+    self._perform_echo_test(_BYTE_SEQUENCE_SEQUENCE)
+
+
+class CancellationTest(unittest.TestCase):
+
+  def setUp(self):
+    self.host = 'localhost'
+
+    self.server_completion_queue = _low.CompletionQueue()
+    self.server = _low.Server(self.server_completion_queue)
+    port = self.server.add_http2_addr('[::]:0')
+    self.server.start()
+
+    self.client_completion_queue = _low.CompletionQueue()
+    self.channel = _low.Channel('%s:%d' % (self.host, port))
+
+  def tearDown(self):
+    self.server.stop()
+    del self.server
+    self.server_completion_queue.stop()
+    self.client_completion_queue.stop()
+    while True:
+      event = self.server_completion_queue.get(0)
+      if event is not None and event.kind is _low.Event.Kind.STOP:
+        break
+    while True:
+      event = self.client_completion_queue.get(0)
+      if event is not None and event.kind is _low.Event.Kind.STOP:
+        break
+
+  def testCancellation(self):
+    method = 'test method'
+    deadline = _FUTURE
+    metadata_tag = object()
+    finish_tag = object()
+    write_tag = object()
+    service_tag = object()
+    read_tag = object()
+    test_data = _BYTE_SEQUENCE_SEQUENCE
+
+    server_data = []
+    client_data = []
+
+    client_call = _low.Call(self.channel, method, self.host, deadline)
+
+    client_call.invoke(self.client_completion_queue, metadata_tag, finish_tag)
+
+    self.server.service(service_tag)
+    service_accepted = self.server_completion_queue.get(_FUTURE)
+    server_call = service_accepted.service_acceptance.call
+
+    server_call.accept(self.server_completion_queue, finish_tag)
+    server_call.premetadata()
+
+    metadata_accepted = self.client_completion_queue.get(_FUTURE)
+    self.assertIsNotNone(metadata_accepted)
+
+    for datum in test_data:
+      client_call.write(datum, write_tag)
+      write_accepted = self.client_completion_queue.get(_FUTURE)
+
+      server_call.read(read_tag)
+      read_accepted = self.server_completion_queue.get(_FUTURE)
+      server_data.append(read_accepted.bytes)
+
+      server_call.write(read_accepted.bytes, write_tag)
+      write_accepted = self.server_completion_queue.get(_FUTURE)
+      self.assertIsNotNone(write_accepted)
+
+      client_call.read(read_tag)
+      read_accepted = self.client_completion_queue.get(_FUTURE)
+      client_data.append(read_accepted.bytes)
+
+    client_call.cancel()
+    # cancel() is idempotent.
+    client_call.cancel()
+    client_call.cancel()
+    client_call.cancel()
+
+    server_call.read(read_tag)
+
+    server_terminal_event_one = self.server_completion_queue.get(_FUTURE)
+    server_terminal_event_two = self.server_completion_queue.get(_FUTURE)
+    if server_terminal_event_one.kind == _low.Event.Kind.READ_ACCEPTED:
+      read_accepted = server_terminal_event_one
+      rpc_accepted = server_terminal_event_two
+    else:
+      read_accepted = server_terminal_event_two
+      rpc_accepted = server_terminal_event_one
+    self.assertIsNotNone(read_accepted)
+    self.assertIsNotNone(rpc_accepted)
+    self.assertEqual(_low.Event.Kind.READ_ACCEPTED, read_accepted.kind)
+    self.assertIsNone(read_accepted.bytes)
+    self.assertEqual(_low.Event.Kind.FINISH, rpc_accepted.kind)
+    self.assertEqual(_low.Status(_low.Code.CANCELLED, ''), rpc_accepted.status)
+
+    finish_event = self.client_completion_queue.get(_FUTURE)
+    self.assertEqual(_low.Event.Kind.FINISH, finish_event.kind)
+    self.assertEqual(_low.Status(_low.Code.CANCELLED, ''), finish_event.status)
+
+    server_timeout_none_event = self.server_completion_queue.get(0)
+    self.assertIsNone(server_timeout_none_event)
+    client_timeout_none_event = self.client_completion_queue.get(0)
+    self.assertIsNone(client_timeout_none_event)
+
+    self.assertSequenceEqual(test_data, server_data)
+    self.assertSequenceEqual(test_data, client_data)
+
+
+class ExpirationTest(unittest.TestCase):
+
+  @unittest.skip('TODO(nathaniel): Expiration test!')
+  def testExpiration(self):
+    pass
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/src/python/src/_adapter/_proto_scenarios.py b/src/python/src/_adapter/_proto_scenarios.py
new file mode 100644
index 0000000..c452fb5
--- /dev/null
+++ b/src/python/src/_adapter/_proto_scenarios.py
@@ -0,0 +1,261 @@
+# 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.
+
+"""Test scenarios using protocol buffers."""
+
+import abc
+import threading
+
+from _junkdrawer import math_pb2
+
+
+class ProtoScenario(object):
+  """An RPC test scenario using protocol buffers."""
+  __metaclass__ = abc.ABCMeta
+
+  @abc.abstractmethod
+  def method(self):
+    """Access the test method name.
+
+    Returns:
+      The test method name.
+    """
+    raise NotImplementedError()
+
+  @abc.abstractmethod
+  def serialize_request(self, request):
+    """Serialize a request protocol buffer.
+
+    Args:
+      request: A request protocol buffer.
+
+    Returns:
+      The bytestring serialization of the given request protocol buffer.
+    """
+    raise NotImplementedError()
+
+  @abc.abstractmethod
+  def deserialize_request(self, request_bytestring):
+    """Deserialize a request protocol buffer.
+
+    Args:
+      request_bytestring: The bytestring serialization of a request protocol
+        buffer.
+
+    Returns:
+      The request protocol buffer deserialized from the given byte string.
+    """
+    raise NotImplementedError()
+
+  @abc.abstractmethod
+  def serialize_response(self, response):
+    """Serialize a response protocol buffer.
+
+    Args:
+      response: A response protocol buffer.
+
+    Returns:
+      The bytestring serialization of the given response protocol buffer.
+    """
+    raise NotImplementedError()
+
+  @abc.abstractmethod
+  def deserialize_response(self, response_bytestring):
+    """Deserialize a response protocol buffer.
+
+    Args:
+      response_bytestring: The bytestring serialization of a response protocol
+        buffer.
+
+    Returns:
+      The response protocol buffer deserialized from the given byte string.
+    """
+    raise NotImplementedError()
+
+  @abc.abstractmethod
+  def requests(self):
+    """Access the sequence of requests for this scenario.
+
+    Returns:
+      A sequence of request protocol buffers.
+    """
+    raise NotImplementedError()
+
+  @abc.abstractmethod
+  def response_for_request(self, request):
+    """Access the response for a particular request.
+
+    Args:
+      request: A request protocol buffer.
+
+    Returns:
+      The response protocol buffer appropriate for the given request.
+    """
+    raise NotImplementedError()
+
+  @abc.abstractmethod
+  def verify_requests(self, experimental_requests):
+    """Verify the requests transmitted through the system under test.
+
+    Args:
+      experimental_requests: The request protocol buffers transmitted through
+        the system under test.
+
+    Returns:
+      True if the requests satisfy this test scenario; False otherwise.
+    """
+    raise NotImplementedError()
+
+  @abc.abstractmethod
+  def verify_responses(self, experimental_responses):
+    """Verify the responses transmitted through the system under test.
+
+    Args:
+      experimental_responses: The response protocol buffers transmitted through
+        the system under test.
+
+    Returns:
+      True if the responses satisfy this test scenario; False otherwise.
+    """
+    raise NotImplementedError()
+
+
+class EmptyScenario(ProtoScenario):
+  """A scenario that transmits no protocol buffers in either direction."""
+
+  def method(self):
+    return 'DivMany'
+
+  def serialize_request(self, request):
+    raise ValueError('This should not be necessary to call!')
+
+  def deserialize_request(self, request_bytestring):
+    raise ValueError('This should not be necessary to call!')
+
+  def serialize_response(self, response):
+    raise ValueError('This should not be necessary to call!')
+
+  def deserialize_response(self, response_bytestring):
+    raise ValueError('This should not be necessary to call!')
+
+  def requests(self):
+    return ()
+
+  def response_for_request(self, request):
+    raise ValueError('This should not be necessary to call!')
+
+  def verify_requests(self, experimental_requests):
+    return not experimental_requests
+
+  def verify_responses(self, experimental_responses):
+    return not experimental_responses
+
+
+class BidirectionallyUnaryScenario(ProtoScenario):
+  """A scenario that transmits no protocol buffers in either direction."""
+
+  _DIVIDEND = 59
+  _DIVISOR = 7
+  _QUOTIENT = 8
+  _REMAINDER = 3
+
+  _REQUEST = math_pb2.DivArgs(dividend=_DIVIDEND, divisor=_DIVISOR)
+  _RESPONSE = math_pb2.DivReply(quotient=_QUOTIENT, remainder=_REMAINDER)
+
+  def method(self):
+    return 'Div'
+
+  def serialize_request(self, request):
+    return request.SerializeToString()
+
+  def deserialize_request(self, request_bytestring):
+    return math_pb2.DivArgs.FromString(request_bytestring)
+
+  def serialize_response(self, response):
+    return response.SerializeToString()
+
+  def deserialize_response(self, response_bytestring):
+    return math_pb2.DivReply.FromString(response_bytestring)
+
+  def requests(self):
+    return [self._REQUEST]
+
+  def response_for_request(self, request):
+    return self._RESPONSE
+
+  def verify_requests(self, experimental_requests):
+    return tuple(experimental_requests) == (self._REQUEST,)
+
+  def verify_responses(self, experimental_responses):
+    return tuple(experimental_responses) == (self._RESPONSE,)
+
+
+class BidirectionallyStreamingScenario(ProtoScenario):
+  """A scenario that transmits no protocol buffers in either direction."""
+
+  _STREAM_LENGTH = 200
+  _REQUESTS = tuple(
+      math_pb2.DivArgs(dividend=59 + index, divisor=7 + index)
+      for index in range(_STREAM_LENGTH))
+
+  def __init__(self):
+    self._lock = threading.Lock()
+    self._responses = []
+
+  def method(self):
+    return 'DivMany'
+
+  def serialize_request(self, request):
+    return request.SerializeToString()
+
+  def deserialize_request(self, request_bytestring):
+    return math_pb2.DivArgs.FromString(request_bytestring)
+
+  def serialize_response(self, response):
+    return response.SerializeToString()
+
+  def deserialize_response(self, response_bytestring):
+    return math_pb2.DivReply.FromString(response_bytestring)
+
+  def requests(self):
+    return self._REQUESTS
+
+  def response_for_request(self, request):
+    quotient, remainder = divmod(request.dividend, request.divisor)
+    response = math_pb2.DivReply(quotient=quotient, remainder=remainder)
+    with self._lock:
+      self._responses.append(response)
+    return response
+
+  def verify_requests(self, experimental_requests):
+    return tuple(experimental_requests) == self._REQUESTS
+
+  def verify_responses(self, experimental_responses):
+    with self._lock:
+      return tuple(experimental_responses) == tuple(self._responses)
diff --git a/src/python/src/_adapter/_server.c b/src/python/src/_adapter/_server.c
new file mode 100644
index 0000000..d2730d9
--- /dev/null
+++ b/src/python/src/_adapter/_server.c
@@ -0,0 +1,167 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+#include "_adapter/_server.h"
+
+#include <Python.h>
+#include <grpc/grpc.h>
+
+#include "_adapter/_completion_queue.h"
+#include "_adapter/_error.h"
+
+static int pygrpc_server_init(Server *self, PyObject *args, PyObject *kwds) {
+  const PyObject *completion_queue;
+  if (!(PyArg_ParseTuple(args, "O!", &pygrpc_CompletionQueueType,
+                         &completion_queue))) {
+    self->c_server = NULL;
+    return -1;
+  }
+
+  self->c_server = grpc_server_create(
+      ((CompletionQueue *)completion_queue)->c_completion_queue, NULL);
+  return 0;
+}
+
+static void pygrpc_server_dealloc(Server *self) {
+  if (self->c_server != NULL) {
+    grpc_server_destroy(self->c_server);
+  }
+  self->ob_type->tp_free((PyObject *)self);
+}
+
+static PyObject *pygrpc_server_add_http2_addr(Server *self, PyObject *args) {
+  const char *addr;
+  int port;
+  PyArg_ParseTuple(args, "s", &addr);
+
+  port = grpc_server_add_http2_port(self->c_server, addr);
+  if (port == 0) {
+    PyErr_SetString(PyExc_RuntimeError, "Couldn't add port to server!");
+    return NULL;
+  }
+
+  return PyInt_FromLong(port);
+}
+
+static PyObject *pygrpc_server_start(Server *self) {
+  grpc_server_start(self->c_server);
+
+  Py_RETURN_NONE;
+}
+
+static const PyObject *pygrpc_server_service(Server *self, PyObject *args) {
+  const PyObject *tag;
+  grpc_call_error call_error;
+  const PyObject *result;
+
+  if (!(PyArg_ParseTuple(args, "O", &tag))) {
+    return NULL;
+  }
+
+  call_error = grpc_server_request_call_old(self->c_server, (void *)tag);
+
+  result = pygrpc_translate_call_error(call_error);
+  if (result != NULL) {
+    Py_INCREF(tag);
+  }
+  return result;
+}
+
+static PyObject *pygrpc_server_stop(Server *self) {
+  grpc_server_shutdown(self->c_server);
+
+  Py_RETURN_NONE;
+}
+
+static PyMethodDef methods[] = {
+    {"add_http2_addr", (PyCFunction)pygrpc_server_add_http2_addr, METH_VARARGS,
+     "Add an HTTP2 address."},
+    {"start", (PyCFunction)pygrpc_server_start, METH_NOARGS,
+     "Starts the server."},
+    {"service", (PyCFunction)pygrpc_server_service, METH_VARARGS,
+     "Services a call."},
+    {"stop", (PyCFunction)pygrpc_server_stop, METH_NOARGS, "Stops the server."},
+    {NULL}};
+
+static PyTypeObject pygrpc_ServerType = {
+    PyObject_HEAD_INIT(NULL)0,         /*ob_size*/
+    "_gprc.Server",                    /*tp_name*/
+    sizeof(Server),                    /*tp_basicsize*/
+    0,                                 /*tp_itemsize*/
+    (destructor)pygrpc_server_dealloc, /*tp_dealloc*/
+    0,                                 /*tp_print*/
+    0,                                 /*tp_getattr*/
+    0,                                 /*tp_setattr*/
+    0,                                 /*tp_compare*/
+    0,                                 /*tp_repr*/
+    0,                                 /*tp_as_number*/
+    0,                                 /*tp_as_sequence*/
+    0,                                 /*tp_as_mapping*/
+    0,                                 /*tp_hash */
+    0,                                 /*tp_call*/
+    0,                                 /*tp_str*/
+    0,                                 /*tp_getattro*/
+    0,                                 /*tp_setattro*/
+    0,                                 /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT,                /*tp_flags*/
+    "Wrapping of grpc_server.",        /* tp_doc */
+    0,                                 /* tp_traverse */
+    0,                                 /* tp_clear */
+    0,                                 /* tp_richcompare */
+    0,                                 /* tp_weaklistoffset */
+    0,                                 /* tp_iter */
+    0,                                 /* tp_iternext */
+    methods,                           /* tp_methods */
+    0,                                 /* tp_members */
+    0,                                 /* tp_getset */
+    0,                                 /* tp_base */
+    0,                                 /* tp_dict */
+    0,                                 /* tp_descr_get */
+    0,                                 /* tp_descr_set */
+    0,                                 /* tp_dictoffset */
+    (initproc)pygrpc_server_init,      /* tp_init */
+};
+
+int pygrpc_add_server(PyObject *module) {
+  pygrpc_ServerType.tp_new = PyType_GenericNew;
+  if (PyType_Ready(&pygrpc_ServerType) < 0) {
+    PyErr_SetString(PyExc_RuntimeError, "Error defining pygrpc_ServerType!");
+    return -1;
+  }
+  if (PyModule_AddObject(module, "Server", (PyObject *)&pygrpc_ServerType) ==
+      -1) {
+    PyErr_SetString(PyExc_ImportError, "Couldn't add Server type to module!");
+    return -1;
+  }
+  return 0;
+}
diff --git a/include/grpc/support/time_posix.h b/src/python/src/_adapter/_server.h
similarity index 84%
rename from include/grpc/support/time_posix.h
rename to src/python/src/_adapter/_server.h
index 9ff6f7f..0c517e3 100644
--- a/include/grpc/support/time_posix.h
+++ b/src/python/src/_adapter/_server.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2014, Google Inc.
+ * Copyright 2015, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,13 +31,14 @@
  *
  */
 
-#ifndef __GRPC_SUPPORT_TIME_POSIX_H__
-#define __GRPC_SUPPORT_TIME_POSIX_H__
-/* Posix variant of gpr_time_platform.h */
+#ifndef _ADAPTER__SERVER_H_
+#define _ADAPTER__SERVER_H_
 
-#include <sys/time.h>
-#include <time.h>
+#include <Python.h>
+#include <grpc/grpc.h>
 
-typedef struct timespec gpr_timespec;
+typedef struct { PyObject_HEAD grpc_server *c_server; } Server;
 
-#endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */
+int pygrpc_add_server(PyObject *module);
+
+#endif /* _ADAPTER__SERVER_H_ */
diff --git a/src/python/src/_adapter/_test_links.py b/src/python/src/_adapter/_test_links.py
new file mode 100644
index 0000000..77d1b00
--- /dev/null
+++ b/src/python/src/_adapter/_test_links.py
@@ -0,0 +1,80 @@
+# 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.
+
+"""Links suitable for use in tests."""
+
+import threading
+
+from _framework.base.packets import interfaces
+
+
+class ForeLink(interfaces.ForeLink):
+  """A ForeLink suitable for use in tests of RearLinks."""
+
+  def __init__(self, action, rear_link):
+    self.condition = threading.Condition()
+    self.tickets = []
+    self.action = action
+    self.rear_link = rear_link
+
+  def accept_back_to_front_ticket(self, ticket):
+    with self.condition:
+      self.tickets.append(ticket)
+      self.condition.notify_all()
+      action, rear_link = self.action, self.rear_link
+
+    if action is not None:
+      action(ticket, rear_link)
+
+  def join_rear_link(self, rear_link):
+    with self.condition:
+      self.rear_link = rear_link
+
+
+class RearLink(interfaces.RearLink):
+  """A RearLink suitable for use in tests of ForeLinks."""
+
+  def __init__(self, action, fore_link):
+    self.condition = threading.Condition()
+    self.tickets = []
+    self.action = action
+    self.fore_link = fore_link
+
+  def accept_front_to_back_ticket(self, ticket):
+    with self.condition:
+      self.tickets.append(ticket)
+      self.condition.notify_all()
+      action, fore_link = self.action, self.fore_link
+
+    if action is not None:
+      action(ticket, fore_link)
+
+  def join_fore_link(self, fore_link):
+    with self.condition:
+      self.fore_link = fore_link
diff --git a/src/python/src/_adapter/fore.py b/src/python/src/_adapter/fore.py
new file mode 100644
index 0000000..c307e7c
--- /dev/null
+++ b/src/python/src/_adapter/fore.py
@@ -0,0 +1,310 @@
+# 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.
+
+"""The RPC-service-side bridge between RPC Framework and GRPC-on-the-wire."""
+
+import enum
+import logging
+import threading
+import time
+
+from _adapter import _common
+from _adapter import _low
+from _framework.base import interfaces
+from _framework.base.packets import interfaces as ticket_interfaces
+from _framework.base.packets import null
+from _framework.base.packets import packets as tickets
+
+
+@enum.unique
+class _LowWrite(enum.Enum):
+  """The possible categories of low-level write state."""
+
+  OPEN = 'OPEN'
+  ACTIVE = 'ACTIVE'
+  CLOSED = 'CLOSED'
+
+
+def _write(call, rpc_state, payload):
+  serialized_payload = rpc_state.serializer(payload)
+  if rpc_state.write.low is _LowWrite.OPEN:
+    call.write(serialized_payload, call)
+    rpc_state.write.low = _LowWrite.ACTIVE
+  else:
+    rpc_state.write.pending.append(serialized_payload)
+
+
+def _status(call, rpc_state):
+  call.status(_low.Status(_low.Code.OK, ''), call)
+  rpc_state.write.low = _LowWrite.CLOSED
+
+
+class ForeLink(ticket_interfaces.ForeLink):
+  """A service-side bridge between RPC Framework and the C-ish _low code."""
+
+  def __init__(
+      self, pool, request_deserializers, response_serializers, port=None):
+    """Constructor.
+
+    Args:
+      pool: A thread pool.
+      request_deserializers: A dict from RPC method names to request object
+        deserializer behaviors.
+      response_serializers: A dict from RPC method names to response object
+        serializer behaviors.
+      port: The port on which to serve, or None to have a port selected
+        automatically.
+    """
+    self._condition = threading.Condition()
+    self._pool = pool
+    self._request_deserializers = request_deserializers
+    self._response_serializers = response_serializers
+    self._port = port
+
+    self._rear_link = null.NULL_REAR_LINK
+    self._completion_queue = None
+    self._server = None
+    self._rpc_states = {}
+    self._spinning = False
+
+  def _on_stop_event(self):
+    self._spinning = False
+    self._condition.notify_all()
+
+  def _on_service_acceptance_event(self, event, server):
+    """Handle a service invocation event."""
+    service_acceptance = event.service_acceptance
+    if service_acceptance is None:
+      return
+
+    call = service_acceptance.call
+    call.accept(self._completion_queue, call)
+    # TODO(nathaniel): Metadata support.
+    call.premetadata()
+    call.read(call)
+    method = service_acceptance.method
+
+    self._rpc_states[call] = _common.CommonRPCState(
+        _common.WriteState(_LowWrite.OPEN, _common.HighWrite.OPEN, []), 1,
+        self._request_deserializers[method],
+        self._response_serializers[method])
+
+    ticket = tickets.FrontToBackPacket(
+        call, 0, tickets.Kind.COMMENCEMENT, method,
+        interfaces.ServicedSubscription.Kind.FULL, None, None,
+        service_acceptance.deadline - time.time())
+    self._rear_link.accept_front_to_back_ticket(ticket)
+
+    server.service(None)
+
+  def _on_read_event(self, event):
+    """Handle data arriving during an RPC."""
+    call = event.tag
+    rpc_state = self._rpc_states.get(call, None)
+    if rpc_state is None:
+      return
+
+    sequence_number = rpc_state.sequence_number
+    rpc_state.sequence_number += 1
+    if event.bytes is None:
+      ticket = tickets.FrontToBackPacket(
+          call, sequence_number, tickets.Kind.COMPLETION, None, None, None,
+          None, None)
+    else:
+      call.read(call)
+      ticket = tickets.FrontToBackPacket(
+          call, sequence_number, tickets.Kind.CONTINUATION, None, None, None,
+          rpc_state.deserializer(event.bytes), None)
+
+    self._rear_link.accept_front_to_back_ticket(ticket)
+
+  def _on_write_event(self, event):
+    call = event.tag
+    rpc_state = self._rpc_states.get(call, None)
+    if rpc_state is None:
+      return
+
+    if rpc_state.write.pending:
+      serialized_payload = rpc_state.write.pending.pop(0)
+      call.write(serialized_payload, call)
+    elif rpc_state.write.high is _common.HighWrite.CLOSED:
+      _status(call, rpc_state)
+    else:
+      rpc_state.write.low = _LowWrite.OPEN
+
+  def _on_complete_event(self, event):
+    if not event.complete_accepted:
+      logging.error('Complete not accepted! %s', (event,))
+      call = event.tag
+      rpc_state = self._rpc_states.pop(call, None)
+      if rpc_state is None:
+        return
+
+      sequence_number = rpc_state.sequence_number
+      rpc_state.sequence_number += 1
+      ticket = tickets.FrontToBackPacket(
+          call, sequence_number, tickets.Kind.TRANSMISSION_FAILURE, None, None,
+          None, None, None)
+      self._rear_link.accept_front_to_back_ticket(ticket)
+
+  def _on_finish_event(self, event):
+    """Handle termination of an RPC."""
+    call = event.tag
+    rpc_state = self._rpc_states.pop(call, None)
+    if rpc_state is None:
+      return
+
+    code = event.status.code
+    if code is _low.Code.OK:
+      return
+
+    sequence_number = rpc_state.sequence_number
+    rpc_state.sequence_number += 1
+    if code is _low.Code.CANCELLED:
+      ticket = tickets.FrontToBackPacket(
+          call, sequence_number, tickets.Kind.CANCELLATION, None, None, None,
+          None, None)
+    elif code is _low.Code.EXPIRED:
+      ticket = tickets.FrontToBackPacket(
+          call, sequence_number, tickets.Kind.EXPIRATION, None, None, None,
+          None, None)
+    else:
+      # TODO(nathaniel): Better mapping of codes to ticket-categories
+      ticket = tickets.FrontToBackPacket(
+          call, sequence_number, tickets.Kind.TRANSMISSION_FAILURE, None, None,
+          None, None, None)
+    self._rear_link.accept_front_to_back_ticket(ticket)
+
+  def _spin(self, completion_queue, server):
+    while True:
+      event = completion_queue.get(None)
+
+      with self._condition:
+        if event.kind is _low.Event.Kind.STOP:
+          self._on_stop_event()
+          return
+        elif self._server is None:
+          continue
+        elif event.kind is _low.Event.Kind.SERVICE_ACCEPTED:
+          self._on_service_acceptance_event(event, server)
+        elif event.kind is _low.Event.Kind.READ_ACCEPTED:
+          self._on_read_event(event)
+        elif event.kind is _low.Event.Kind.WRITE_ACCEPTED:
+          self._on_write_event(event)
+        elif event.kind is _low.Event.Kind.COMPLETE_ACCEPTED:
+          self._on_complete_event(event)
+        elif event.kind is _low.Event.Kind.FINISH:
+          self._on_finish_event(event)
+        else:
+          logging.error('Illegal event! %s', (event,))
+
+  def _continue(self, call, payload):
+    rpc_state = self._rpc_states.get(call, None)
+    if rpc_state is None:
+      return
+
+    _write(call, rpc_state, payload)
+
+  def _complete(self, call, payload):
+    """Handle completion of the writes of an RPC."""
+    rpc_state = self._rpc_states.get(call, None)
+    if rpc_state is None:
+      return
+
+    if rpc_state.write.low is _LowWrite.OPEN:
+      if payload is None:
+        _status(call, rpc_state)
+      else:
+        _write(call, rpc_state, payload)
+    elif rpc_state.write.low is _LowWrite.ACTIVE:
+      if payload is not None:
+        rpc_state.write.pending.append(rpc_state.serializer(payload))
+    else:
+      raise ValueError('Called to complete after having already completed!')
+    rpc_state.write.high = _common.HighWrite.CLOSED
+
+  def _cancel(self, call):
+    call.cancel()
+    self._rpc_states.pop(call, None)
+
+  def join_rear_link(self, rear_link):
+    """See ticket_interfaces.ForeLink.join_rear_link for specification."""
+    self._rear_link = null.NULL_REAR_LINK if rear_link is None else rear_link
+
+  def start(self):
+    """Starts this ForeLink.
+
+    This method must be called before attempting to exchange tickets with this
+    object.
+    """
+    with self._condition:
+      self._completion_queue = _low.CompletionQueue()
+      self._server = _low.Server(self._completion_queue)
+      port = self._server.add_http2_addr(
+          '[::]:%d' % (0 if self._port is None else self._port))
+      self._server.start()
+
+      self._server.service(None)
+
+      self._pool.submit(self._spin, self._completion_queue, self._server)
+      self._spinning = True
+
+      return port
+
+  # TODO(nathaniel): Expose graceful-shutdown semantics in which this object
+  # enters a state in which it finishes ongoing RPCs but refuses new ones.
+  def stop(self):
+    """Stops this ForeLink.
+
+    This method must be called for proper termination of this object, and no
+    attempts to exchange tickets with this object may be made after this method
+    has been called.
+    """
+    with self._condition:
+      self._server.stop()
+      # TODO(b/18904187): Yep, this is weird. Deleting a server shouldn't have a
+      # behaviorally significant side-effect.
+      self._server = None
+      self._completion_queue.stop()
+
+      while self._spinning:
+        self._condition.wait()
+
+  def accept_back_to_front_ticket(self, ticket):
+    """See ticket_interfaces.ForeLink.accept_back_to_front_ticket for spec."""
+    with self._condition:
+      if self._server is None:
+        return
+
+      if ticket.kind is tickets.Kind.CONTINUATION:
+        self._continue(ticket.operation_id, ticket.payload)
+      elif ticket.kind is tickets.Kind.COMPLETION:
+        self._complete(ticket.operation_id, ticket.payload)
+      else:
+        self._cancel(ticket.operation_id)
diff --git a/src/python/src/_adapter/rear.py b/src/python/src/_adapter/rear.py
new file mode 100644
index 0000000..5e0975a
--- /dev/null
+++ b/src/python/src/_adapter/rear.py
@@ -0,0 +1,344 @@
+# 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.
+
+"""The RPC-invocation-side bridge between RPC Framework and GRPC-on-the-wire."""
+
+import enum
+import logging
+import threading
+import time
+
+from _adapter import _common
+from _adapter import _low
+from _framework.base.packets import interfaces as ticket_interfaces
+from _framework.base.packets import null
+from _framework.base.packets import packets as tickets
+
+_INVOCATION_EVENT_KINDS = (
+    _low.Event.Kind.METADATA_ACCEPTED,
+    _low.Event.Kind.FINISH
+)
+
+
+@enum.unique
+class _LowWrite(enum.Enum):
+  """The possible categories of low-level write state."""
+
+  OPEN = 'OPEN'
+  ACTIVE = 'ACTIVE'
+  CLOSED = 'CLOSED'
+
+
+class _RPCState(object):
+  """The full state of any tracked RPC.
+
+  Attributes:
+    call: The _low.Call object for the RPC.
+    outstanding: The set of Event.Kind values describing expected future events
+      for the RPC.
+    active: A boolean indicating whether or not the RPC is active.
+    common: An _common.RPCState describing additional state for the RPC.
+  """
+
+  def __init__(self, call, outstanding, active, common):
+    self.call = call
+    self.outstanding = outstanding
+    self.active = active
+    self.common = common
+
+
+def _write(operation_id, call, outstanding, write_state, serialized_payload):
+  if write_state.low is _LowWrite.OPEN:
+    call.write(serialized_payload, operation_id)
+    outstanding.add(_low.Event.Kind.WRITE_ACCEPTED)
+    write_state.low = _LowWrite.ACTIVE
+  elif write_state.low is _LowWrite.ACTIVE:
+    write_state.pending.append(serialized_payload)
+  else:
+    raise ValueError('Write attempted after writes completed!')
+
+
+class RearLink(ticket_interfaces.RearLink):
+  """An invocation-side bridge between RPC Framework and the C-ish _low code."""
+
+  def __init__(
+      self, host, port, pool, request_serializers, response_deserializers):
+    """Constructor.
+
+    Args:
+      host: The host to which to connect for RPC service.
+      port: The port to which to connect for RPC service.
+      pool: A thread pool.
+      request_serializers: A dict from RPC method names to request object
+        serializer behaviors.
+      response_deserializers: A dict from RPC method names to response object
+        deserializer behaviors.
+    """
+    self._condition = threading.Condition()
+    self._host = host
+    self._port = port
+    self._pool = pool
+    self._request_serializers = request_serializers
+    self._response_deserializers = response_deserializers
+
+    self._fore_link = null.NULL_FORE_LINK
+    self._completion_queue = None
+    self._channel = None
+    self._rpc_states = {}
+    self._spinning = False
+
+  def _on_write_event(self, operation_id, event, rpc_state):
+    if event.write_accepted:
+      if rpc_state.common.write.pending:
+        rpc_state.call.write(
+            rpc_state.common.write.pending.pop(0), operation_id)
+        rpc_state.outstanding.add(_low.Event.Kind.WRITE_ACCEPTED)
+      elif rpc_state.common.write.high is _common.HighWrite.CLOSED:
+        rpc_state.call.complete(operation_id)
+        rpc_state.outstanding.add(_low.Event.Kind.COMPLETE_ACCEPTED)
+        rpc_state.common.write.low = _LowWrite.CLOSED
+      else:
+        rpc_state.common.write.low = _LowWrite.OPEN
+    else:
+      logging.error('RPC write not accepted! Event: %s', (event,))
+      rpc_state.active = False
+      ticket = tickets.BackToFrontPacket(
+          operation_id, rpc_state.common.sequence_number,
+          tickets.Kind.TRANSMISSION_FAILURE, None)
+      rpc_state.common.sequence_number += 1
+      self._fore_link.accept_back_to_front_ticket(ticket)
+
+  def _on_read_event(self, operation_id, event, rpc_state):
+    if event.bytes is not None:
+      rpc_state.call.read(operation_id)
+      rpc_state.outstanding.add(_low.Event.Kind.READ_ACCEPTED)
+
+      ticket = tickets.BackToFrontPacket(
+          operation_id, rpc_state.common.sequence_number,
+          tickets.Kind.CONTINUATION, rpc_state.common.deserializer(event.bytes))
+      rpc_state.common.sequence_number += 1
+      self._fore_link.accept_back_to_front_ticket(ticket)
+
+  def _on_complete_event(self, operation_id, event, rpc_state):
+    if not event.complete_accepted:
+      logging.error('RPC complete not accepted! Event: %s', (event,))
+      rpc_state.active = False
+      ticket = tickets.BackToFrontPacket(
+          operation_id, rpc_state.common.sequence_number,
+          tickets.Kind.TRANSMISSION_FAILURE, None)
+      rpc_state.common.sequence_number += 1
+      self._fore_link.accept_back_to_front_ticket(ticket)
+
+  # TODO(nathaniel): Metadata support.
+  def _on_metadata_event(self, operation_id, event, rpc_state):  # pylint: disable=unused-argument
+    rpc_state.call.read(operation_id)
+    rpc_state.outstanding.add(_low.Event.Kind.READ_ACCEPTED)
+
+  def _on_finish_event(self, operation_id, event, rpc_state):
+    """Handle termination of an RPC."""
+    # TODO(nathaniel): Cover all statuses.
+    if event.status.code is _low.Code.OK:
+      category = tickets.Kind.COMPLETION
+    elif event.status.code is _low.Code.CANCELLED:
+      category = tickets.Kind.CANCELLATION
+    elif event.status.code is _low.Code.EXPIRED:
+      category = tickets.Kind.EXPIRATION
+    else:
+      category = tickets.Kind.TRANSMISSION_FAILURE
+    ticket = tickets.BackToFrontPacket(
+        operation_id, rpc_state.common.sequence_number, category,
+        None)
+    rpc_state.common.sequence_number += 1
+    self._fore_link.accept_back_to_front_ticket(ticket)
+
+  def _spin(self, completion_queue):
+    while True:
+      event = completion_queue.get(None)
+      operation_id = event.tag
+
+      with self._condition:
+        rpc_state = self._rpc_states[operation_id]
+        rpc_state.outstanding.remove(event.kind)
+        if rpc_state.active and self._completion_queue is not None:
+          if event.kind is _low.Event.Kind.WRITE_ACCEPTED:
+            self._on_write_event(operation_id, event, rpc_state)
+          elif event.kind is _low.Event.Kind.METADATA_ACCEPTED:
+            self._on_metadata_event(operation_id, event, rpc_state)
+          elif event.kind is _low.Event.Kind.READ_ACCEPTED:
+            self._on_read_event(operation_id, event, rpc_state)
+          elif event.kind is _low.Event.Kind.COMPLETE_ACCEPTED:
+            self._on_complete_event(operation_id, event, rpc_state)
+          elif event.kind is _low.Event.Kind.FINISH:
+            self._on_finish_event(operation_id, event, rpc_state)
+          else:
+            logging.error('Illegal RPC event! %s', (event,))
+
+        if not rpc_state.outstanding:
+          self._rpc_states.pop(operation_id)
+        if not self._rpc_states:
+          self._spinning = False
+          self._condition.notify_all()
+          return
+
+  def _invoke(self, operation_id, name, high_state, payload, timeout):
+    """Invoke an RPC.
+
+    Args:
+      operation_id: Any object to be used as an operation ID for the RPC.
+      name: The RPC method name.
+      high_state: A _common.HighWrite value representing the "high write state"
+        of the RPC.
+      payload: A payload object for the RPC or None if no payload was given at
+        invocation-time.
+      timeout: A duration of time in seconds to allow for the RPC.
+    """
+    request_serializer = self._request_serializers[name]
+    call = _low.Call(self._channel, name, self._host, time.time() + timeout)
+    call.invoke(self._completion_queue, operation_id, operation_id)
+    outstanding = set(_INVOCATION_EVENT_KINDS)
+
+    if payload is None:
+      if high_state is _common.HighWrite.CLOSED:
+        call.complete(operation_id)
+        low_state = _LowWrite.CLOSED
+        outstanding.add(_low.Event.Kind.COMPLETE_ACCEPTED)
+      else:
+        low_state = _LowWrite.OPEN
+    else:
+      serialized_payload = request_serializer(payload)
+      call.write(serialized_payload, operation_id)
+      outstanding.add(_low.Event.Kind.WRITE_ACCEPTED)
+      low_state = _LowWrite.ACTIVE
+
+    write_state = _common.WriteState(low_state, high_state, [])
+    common_state = _common.CommonRPCState(
+        write_state, 0, self._response_deserializers[name], request_serializer)
+    self._rpc_states[operation_id] = _RPCState(
+        call, outstanding, True, common_state)
+
+    if not self._spinning:
+      self._pool.submit(self._spin, self._completion_queue)
+      self._spinning = True
+
+  def _commence(self, operation_id, name, payload, timeout):
+    self._invoke(operation_id, name, _common.HighWrite.OPEN, payload, timeout)
+
+  def _continue(self, operation_id, payload):
+    rpc_state = self._rpc_states.get(operation_id, None)
+    if rpc_state is None or not rpc_state.active:
+      return
+
+    _write(
+        operation_id, rpc_state.call, rpc_state.outstanding,
+        rpc_state.common.write, rpc_state.common.serializer(payload))
+
+  def _complete(self, operation_id, payload):
+    """Close writes associated with an ongoing RPC.
+
+    Args:
+      operation_id: Any object being use as an operation ID for the RPC.
+      payload: A payload object for the RPC (and thus the last payload object
+        for the RPC) or None if no payload was given along with the instruction
+        to indicate the end of writes for the RPC.
+    """
+    rpc_state = self._rpc_states.get(operation_id, None)
+    if rpc_state is None or not rpc_state.active:
+      return
+
+    write_state = rpc_state.common.write
+    if payload is None:
+      if write_state.low is _LowWrite.OPEN:
+        rpc_state.call.complete(operation_id)
+        rpc_state.outstanding.add(_low.Event.Kind.COMPLETE_ACCEPTED)
+        write_state.low = _LowWrite.CLOSED
+    else:
+      _write(
+          operation_id, rpc_state.call, rpc_state.outstanding, write_state,
+          rpc_state.common.serializer(payload))
+    write_state.high = _common.HighWrite.CLOSED
+
+  def _entire(self, operation_id, name, payload, timeout):
+    self._invoke(operation_id, name, _common.HighWrite.CLOSED, payload, timeout)
+
+  def _cancel(self, operation_id):
+    rpc_state = self._rpc_states.get(operation_id, None)
+    if rpc_state is not None and rpc_state.active:
+      rpc_state.call.cancel()
+      rpc_state.active = False
+
+  def join_fore_link(self, fore_link):
+    """See ticket_interfaces.RearLink.join_fore_link for specification."""
+    with self._condition:
+      self._fore_link = null.NULL_FORE_LINK if fore_link is None else fore_link
+
+  def start(self):
+    """Starts this RearLink.
+
+    This method must be called before attempting to exchange tickets with this
+    object.
+    """
+    with self._condition:
+      self._completion_queue = _low.CompletionQueue()
+      self._channel = _low.Channel('%s:%d' % (self._host, self._port))
+
+  def stop(self):
+    """Stops this RearLink.
+
+    This method must be called for proper termination of this object, and no
+    attempts to exchange tickets with this object may be made after this method
+    has been called.
+    """
+    with self._condition:
+      self._completion_queue.stop()
+      self._completion_queue = None
+
+      while self._spinning:
+        self._condition.wait()
+
+  def accept_front_to_back_ticket(self, ticket):
+    """See ticket_interfaces.RearLink.accept_front_to_back_ticket for spec."""
+    with self._condition:
+      if self._completion_queue is None:
+        return
+
+      if ticket.kind is tickets.Kind.COMMENCEMENT:
+        self._commence(
+            ticket.operation_id, ticket.name, ticket.payload, ticket.timeout)
+      elif ticket.kind is tickets.Kind.CONTINUATION:
+        self._continue(ticket.operation_id, ticket.payload)
+      elif ticket.kind is tickets.Kind.COMPLETION:
+        self._complete(ticket.operation_id, ticket.payload)
+      elif ticket.kind is tickets.Kind.ENTIRE:
+        self._entire(
+            ticket.operation_id, ticket.name, ticket.payload, ticket.timeout)
+      elif ticket.kind is tickets.Kind.CANCELLATION:
+        self._cancel(ticket.operation_id)
+      else:
+        # NOTE(nathaniel): All other categories are treated as cancellation.
+        self._cancel(ticket.operation_id)
diff --git a/src/python/_framework/__init__.py b/src/python/src/_framework/__init__.py
similarity index 100%
rename from src/python/_framework/__init__.py
rename to src/python/src/_framework/__init__.py
diff --git a/src/python/_framework/base/__init__.py b/src/python/src/_framework/base/__init__.py
similarity index 100%
rename from src/python/_framework/base/__init__.py
rename to src/python/src/_framework/base/__init__.py
diff --git a/src/python/_framework/base/exceptions.py b/src/python/src/_framework/base/exceptions.py
similarity index 100%
rename from src/python/_framework/base/exceptions.py
rename to src/python/src/_framework/base/exceptions.py
diff --git a/src/python/_framework/base/interfaces.py b/src/python/src/_framework/base/interfaces.py
similarity index 86%
rename from src/python/_framework/base/interfaces.py
rename to src/python/src/_framework/base/interfaces.py
index de7137c..70030e5 100644
--- a/src/python/_framework/base/interfaces.py
+++ b/src/python/src/_framework/base/interfaces.py
@@ -29,27 +29,24 @@
 
 """Interfaces defined and used by the base layer of RPC Framework."""
 
-# TODO(nathaniel): Use Python's new enum library for enumerated types rather
-# than constants merely placed close together.
-
 import abc
+import enum
 
 # stream is referenced from specification in this module.
 from _framework.foundation import stream  # pylint: disable=unused-import
 
-# Operation outcomes.
-COMPLETED = 'completed'
-CANCELLED = 'cancelled'
-EXPIRED = 'expired'
-RECEPTION_FAILURE = 'reception failure'
-TRANSMISSION_FAILURE = 'transmission failure'
-SERVICER_FAILURE = 'servicer failure'
-SERVICED_FAILURE = 'serviced failure'
 
-# Subscription categories.
-FULL = 'full'
-TERMINATION_ONLY = 'termination only'
-NONE = 'none'
+@enum.unique
+class Outcome(enum.Enum):
+  """Operation outcomes."""
+
+  COMPLETED = 'completed'
+  CANCELLED = 'cancelled'
+  EXPIRED = 'expired'
+  RECEPTION_FAILURE = 'reception failure'
+  TRANSMISSION_FAILURE = 'transmission failure'
+  SERVICER_FAILURE = 'servicer failure'
+  SERVICED_FAILURE = 'serviced failure'
 
 
 class OperationContext(object):
@@ -70,9 +67,7 @@
     """Adds a function to be called upon operation termination.
 
     Args:
-      callback: A callable that will be passed one of COMPLETED, CANCELLED,
-        EXPIRED, RECEPTION_FAILURE, TRANSMISSION_FAILURE, SERVICER_FAILURE, or
-        SERVICED_FAILURE.
+      callback: A callable that will be passed an Outcome value.
     """
     raise NotImplementedError()
 
@@ -167,11 +162,20 @@
   """A sum type representing a serviced's interest in an operation.
 
   Attributes:
-    category: One of FULL, TERMINATION_ONLY, or NONE.
-    ingestor: A ServicedIngestor. Must be present if category is FULL.
+    kind: A Kind value.
+    ingestor: A ServicedIngestor. Must be present if kind is Kind.FULL. Must
+      be None if kind is Kind.TERMINATION_ONLY or Kind.NONE.
   """
   __metaclass__ = abc.ABCMeta
 
+  @enum.unique
+  class Kind(enum.Enum):
+    """Kinds of subscription."""
+
+    FULL = 'full'
+    TERMINATION_ONLY = 'termination only'
+    NONE = 'none'
+
 
 class End(object):
   """Common type for entry-point objects on both sides of an operation."""
@@ -182,9 +186,8 @@
     """Reports the number of terminated operations broken down by outcome.
 
     Returns:
-      A dictionary from operation outcome constant (COMPLETED, CANCELLED,
-        EXPIRED, and so on) to an integer representing the number of operations
-        that terminated with that outcome.
+      A dictionary from Outcome value to an integer identifying the number
+        of operations that terminated with that outcome.
     """
     raise NotImplementedError()
 
diff --git a/src/python/_framework/base/interfaces_test.py b/src/python/src/_framework/base/interfaces_test.py
similarity index 89%
rename from src/python/_framework/base/interfaces_test.py
rename to src/python/src/_framework/base/interfaces_test.py
index 6eb07ea..8e26d88 100644
--- a/src/python/_framework/base/interfaces_test.py
+++ b/src/python/src/_framework/base/interfaces_test.py
@@ -49,13 +49,13 @@
 WAIT_ON_CONDITION = 'wait on condition'
 
 EMPTY_OUTCOME_DICT = {
-    interfaces.COMPLETED: 0,
-    interfaces.CANCELLED: 0,
-    interfaces.EXPIRED: 0,
-    interfaces.RECEPTION_FAILURE: 0,
-    interfaces.TRANSMISSION_FAILURE: 0,
-    interfaces.SERVICER_FAILURE: 0,
-    interfaces.SERVICED_FAILURE: 0,
+    interfaces.Outcome.COMPLETED: 0,
+    interfaces.Outcome.CANCELLED: 0,
+    interfaces.Outcome.EXPIRED: 0,
+    interfaces.Outcome.RECEPTION_FAILURE: 0,
+    interfaces.Outcome.TRANSMISSION_FAILURE: 0,
+    interfaces.Outcome.SERVICER_FAILURE: 0,
+    interfaces.Outcome.SERVICED_FAILURE: 0,
     }
 
 
@@ -169,7 +169,8 @@
         SYNCHRONOUS_ECHO, None, True, SMALL_TIMEOUT,
         util.none_serviced_subscription(), 'test trace ID')
     util.wait_for_idle(self.front)
-    self.assertEqual(1, self.front.operation_stats()[interfaces.COMPLETED])
+    self.assertEqual(
+        1, self.front.operation_stats()[interfaces.Outcome.COMPLETED])
 
     # Assuming nothing really pathological (such as pauses on the order of
     # SMALL_TIMEOUT interfering with this test) there are a two different ways
@@ -183,7 +184,7 @@
     first_back_possibility = EMPTY_OUTCOME_DICT
     # (2) The packet arrived at the back and the back completed the operation.
     second_back_possibility = dict(EMPTY_OUTCOME_DICT)
-    second_back_possibility[interfaces.COMPLETED] = 1
+    second_back_possibility[interfaces.Outcome.COMPLETED] = 1
     self.assertIn(
         back_operation_stats, (first_back_possibility, second_back_possibility))
     # It's true that if the packet had arrived at the back and the back had
@@ -204,8 +205,10 @@
 
     util.wait_for_idle(self.front)
     util.wait_for_idle(self.back)
-    self.assertEqual(1, self.front.operation_stats()[interfaces.COMPLETED])
-    self.assertEqual(1, self.back.operation_stats()[interfaces.COMPLETED])
+    self.assertEqual(
+        1, self.front.operation_stats()[interfaces.Outcome.COMPLETED])
+    self.assertEqual(
+        1, self.back.operation_stats()[interfaces.Outcome.COMPLETED])
     self.assertListEqual([(test_payload, True)], test_consumer.calls)
 
   def testBidirectionalStreamingEcho(self):
@@ -226,8 +229,10 @@
 
     util.wait_for_idle(self.front)
     util.wait_for_idle(self.back)
-    self.assertEqual(1, self.front.operation_stats()[interfaces.COMPLETED])
-    self.assertEqual(1, self.back.operation_stats()[interfaces.COMPLETED])
+    self.assertEqual(
+        1, self.front.operation_stats()[interfaces.Outcome.COMPLETED])
+    self.assertEqual(
+        1, self.back.operation_stats()[interfaces.Outcome.COMPLETED])
     self.assertListEqual(test_payloads, test_consumer.values())
 
   def testCancellation(self):
@@ -242,7 +247,8 @@
     operation.cancel()
 
     util.wait_for_idle(self.front)
-    self.assertEqual(1, self.front.operation_stats()[interfaces.CANCELLED])
+    self.assertEqual(
+        1, self.front.operation_stats()[interfaces.Outcome.CANCELLED])
     util.wait_for_idle(self.back)
     self.assertListEqual([], test_consumer.calls)
 
@@ -260,7 +266,7 @@
     # The back started processing based on the first packet and then stopped
     # upon receiving the cancellation packet.
     second_back_possibility = dict(EMPTY_OUTCOME_DICT)
-    second_back_possibility[interfaces.CANCELLED] = 1
+    second_back_possibility[interfaces.Outcome.CANCELLED] = 1
     self.assertIn(
         back_operation_stats, (first_back_possibility, second_back_possibility))
 
@@ -292,8 +298,10 @@
     duration = termination_time_cell[0] - start_time
     self.assertLessEqual(timeout, duration)
     self.assertLess(duration, timeout + allowance)
-    self.assertEqual(interfaces.EXPIRED, outcome_cell[0])
+    self.assertEqual(interfaces.Outcome.EXPIRED, outcome_cell[0])
     util.wait_for_idle(self.front)
-    self.assertEqual(1, self.front.operation_stats()[interfaces.EXPIRED])
+    self.assertEqual(
+        1, self.front.operation_stats()[interfaces.Outcome.EXPIRED])
     util.wait_for_idle(self.back)
-    self.assertLessEqual(1, self.back.operation_stats()[interfaces.EXPIRED])
+    self.assertLessEqual(
+        1, self.back.operation_stats()[interfaces.Outcome.EXPIRED])
diff --git a/src/python/_framework/base/packets/__init__.py b/src/python/src/_framework/base/packets/__init__.py
similarity index 100%
rename from src/python/_framework/base/packets/__init__.py
rename to src/python/src/_framework/base/packets/__init__.py
diff --git a/src/python/_framework/base/packets/_cancellation.py b/src/python/src/_framework/base/packets/_cancellation.py
similarity index 100%
rename from src/python/_framework/base/packets/_cancellation.py
rename to src/python/src/_framework/base/packets/_cancellation.py
diff --git a/src/python/_framework/base/packets/_constants.py b/src/python/src/_framework/base/packets/_constants.py
similarity index 100%
rename from src/python/_framework/base/packets/_constants.py
rename to src/python/src/_framework/base/packets/_constants.py
diff --git a/src/python/_framework/base/packets/_context.py b/src/python/src/_framework/base/packets/_context.py
similarity index 100%
rename from src/python/_framework/base/packets/_context.py
rename to src/python/src/_framework/base/packets/_context.py
diff --git a/src/python/_framework/base/packets/_emission.py b/src/python/src/_framework/base/packets/_emission.py
similarity index 100%
rename from src/python/_framework/base/packets/_emission.py
rename to src/python/src/_framework/base/packets/_emission.py
diff --git a/src/python/_framework/base/packets/_ends.py b/src/python/src/_framework/base/packets/_ends.py
similarity index 96%
rename from src/python/_framework/base/packets/_ends.py
rename to src/python/src/_framework/base/packets/_ends.py
index baaf5ca..b1d1645 100644
--- a/src/python/_framework/base/packets/_ends.py
+++ b/src/python/src/_framework/base/packets/_ends.py
@@ -51,13 +51,13 @@
 _IDLE_ACTION_EXCEPTION_LOG_MESSAGE = 'Exception calling idle action!'
 
 _OPERATION_OUTCOMES = (
-    base_interfaces.COMPLETED,
-    base_interfaces.CANCELLED,
-    base_interfaces.EXPIRED,
-    base_interfaces.RECEPTION_FAILURE,
-    base_interfaces.TRANSMISSION_FAILURE,
-    base_interfaces.SERVICER_FAILURE,
-    base_interfaces.SERVICED_FAILURE,
+    base_interfaces.Outcome.COMPLETED,
+    base_interfaces.Outcome.CANCELLED,
+    base_interfaces.Outcome.EXPIRED,
+    base_interfaces.Outcome.RECEPTION_FAILURE,
+    base_interfaces.Outcome.TRANSMISSION_FAILURE,
+    base_interfaces.Outcome.SERVICER_FAILURE,
+    base_interfaces.Outcome.SERVICED_FAILURE,
     )
 
 
@@ -193,10 +193,10 @@
   lock = threading.Lock()
   with lock:
     termination_manager = _termination.front_termination_manager(
-        work_pool, utility_pool, termination_action, subscription.category)
+        work_pool, utility_pool, termination_action, subscription.kind)
     transmission_manager = _transmission.front_transmission_manager(
         lock, transmission_pool, callback, operation_id, name,
-        subscription.category, trace_id, timeout, termination_manager)
+        subscription.kind, trace_id, timeout, termination_manager)
     operation_context = _context.OperationContext(
         lock, operation_id, packets.Kind.SERVICED_FAILURE,
         termination_manager, transmission_manager)
@@ -225,9 +225,10 @@
 
     transmission_manager.inmit(payload, complete)
 
-    returned_reception_manager = (
-        None if subscription.category == base_interfaces.NONE
-        else reception_manager)
+    if subscription.kind is base_interfaces.ServicedSubscription.Kind.NONE:
+      returned_reception_manager = None
+    else:
+      returned_reception_manager = reception_manager
 
     return _FrontManagement(
         returned_reception_manager, emission_manager, operation_context,
diff --git a/src/python/_framework/base/packets/_expiration.py b/src/python/src/_framework/base/packets/_expiration.py
similarity index 100%
rename from src/python/_framework/base/packets/_expiration.py
rename to src/python/src/_framework/base/packets/_expiration.py
diff --git a/src/python/_framework/base/packets/_ingestion.py b/src/python/src/_framework/base/packets/_ingestion.py
similarity index 99%
rename from src/python/_framework/base/packets/_ingestion.py
rename to src/python/src/_framework/base/packets/_ingestion.py
index ad5ed4c..abc1e7a 100644
--- a/src/python/_framework/base/packets/_ingestion.py
+++ b/src/python/src/_framework/base/packets/_ingestion.py
@@ -111,7 +111,7 @@
 
   def create_consumer(self, requirement):
     """See _ConsumerCreator.create_consumer for specification."""
-    if self._subscription.category == interfaces.FULL:
+    if self._subscription.kind is interfaces.ServicedSubscription.Kind.FULL:
       try:
         return _ConsumerCreation(
             self._subscription.ingestor.consumer(self._operation_context),
diff --git a/src/python/_framework/base/packets/_interfaces.py b/src/python/src/_framework/base/packets/_interfaces.py
similarity index 96%
rename from src/python/_framework/base/packets/_interfaces.py
rename to src/python/src/_framework/base/packets/_interfaces.py
index 5f6c059..d1bda95 100644
--- a/src/python/_framework/base/packets/_interfaces.py
+++ b/src/python/src/_framework/base/packets/_interfaces.py
@@ -58,10 +58,7 @@
     immediately.
 
     Args:
-      callback: A callable that will be passed one of base_interfaces.COMPLETED,
-        base_interfaces.CANCELLED, base_interfaces.EXPIRED,
-        base_interfaces.RECEPTION_FAILURE, base_interfaces.TRANSMISSION_FAILURE,
-        base_interfaces.SERVICER_FAILURE, or base_interfaces.SERVICED_FAILURE.
+      callback: A callable that will be passed a base_interfaces.Outcome value.
     """
     raise NotImplementedError()
 
diff --git a/src/python/_framework/base/packets/_reception.py b/src/python/src/_framework/base/packets/_reception.py
similarity index 100%
rename from src/python/_framework/base/packets/_reception.py
rename to src/python/src/_framework/base/packets/_reception.py
diff --git a/src/python/_framework/base/packets/_termination.py b/src/python/src/_framework/base/packets/_termination.py
similarity index 75%
rename from src/python/_framework/base/packets/_termination.py
rename to src/python/src/_framework/base/packets/_termination.py
index d586c21..ae3ba1c 100644
--- a/src/python/_framework/base/packets/_termination.py
+++ b/src/python/src/_framework/base/packets/_termination.py
@@ -29,6 +29,8 @@
 
 """State and behavior for operation termination."""
 
+import enum
+
 from _framework.base import interfaces
 from _framework.base.packets import _constants
 from _framework.base.packets import _interfaces
@@ -37,26 +39,32 @@
 
 _CALLBACK_EXCEPTION_LOG_MESSAGE = 'Exception calling termination callback!'
 
-# TODO(nathaniel): enum module.
-_EMISSION = 'emission'
-_TRANSMISSION = 'transmission'
-_INGESTION = 'ingestion'
-
-_FRONT_NOT_LISTENING_REQUIREMENTS = (_TRANSMISSION,)
-_BACK_NOT_LISTENING_REQUIREMENTS = (_EMISSION, _INGESTION,)
-_LISTENING_REQUIREMENTS = (_TRANSMISSION, _INGESTION,)
-
 _KINDS_TO_OUTCOMES = {
-    packets.Kind.COMPLETION: interfaces.COMPLETED,
-    packets.Kind.CANCELLATION: interfaces.CANCELLED,
-    packets.Kind.EXPIRATION: interfaces.EXPIRED,
-    packets.Kind.RECEPTION_FAILURE: interfaces.RECEPTION_FAILURE,
-    packets.Kind.TRANSMISSION_FAILURE: interfaces.TRANSMISSION_FAILURE,
-    packets.Kind.SERVICER_FAILURE: interfaces.SERVICER_FAILURE,
-    packets.Kind.SERVICED_FAILURE: interfaces.SERVICED_FAILURE,
+    packets.Kind.COMPLETION: interfaces.Outcome.COMPLETED,
+    packets.Kind.CANCELLATION: interfaces.Outcome.CANCELLED,
+    packets.Kind.EXPIRATION: interfaces.Outcome.EXPIRED,
+    packets.Kind.RECEPTION_FAILURE: interfaces.Outcome.RECEPTION_FAILURE,
+    packets.Kind.TRANSMISSION_FAILURE: interfaces.Outcome.TRANSMISSION_FAILURE,
+    packets.Kind.SERVICER_FAILURE: interfaces.Outcome.SERVICER_FAILURE,
+    packets.Kind.SERVICED_FAILURE: interfaces.Outcome.SERVICED_FAILURE,
     }
 
 
+@enum.unique
+class _Requirement(enum.Enum):
+  """Symbols indicating events required for termination."""
+
+  EMISSION = 'emission'
+  TRANSMISSION = 'transmission'
+  INGESTION = 'ingestion'
+
+_FRONT_NOT_LISTENING_REQUIREMENTS = (_Requirement.TRANSMISSION,)
+_BACK_NOT_LISTENING_REQUIREMENTS = (
+    _Requirement.EMISSION, _Requirement.INGESTION,)
+_LISTENING_REQUIREMENTS = (
+    _Requirement.TRANSMISSION, _Requirement.INGESTION,)
+
+
 class _TerminationManager(_interfaces.TerminationManager):
   """An implementation of _interfaces.TerminationManager."""
 
@@ -68,9 +76,8 @@
       work_pool: A thread pool in which customer work will be done.
       utility_pool: A thread pool in which work utility work will be done.
       action: An action to call on operation termination.
-      requirements: A combination of _EMISSION, _TRANSMISSION, and _INGESTION
-        identifying what must finish for the operation to be considered
-        completed.
+      requirements: A combination of _Requirement values identifying what
+        must finish for the operation to be considered completed.
       local_failure: A packets.Kind specifying what constitutes local failure of
         customer work.
     """
@@ -137,21 +144,21 @@
   def emission_complete(self):
     """See superclass method for specification."""
     if self._outstanding_requirements is not None:
-      self._outstanding_requirements.discard(_EMISSION)
+      self._outstanding_requirements.discard(_Requirement.EMISSION)
       if not self._outstanding_requirements:
         self._terminate(packets.Kind.COMPLETION)
 
   def transmission_complete(self):
     """See superclass method for specification."""
     if self._outstanding_requirements is not None:
-      self._outstanding_requirements.discard(_TRANSMISSION)
+      self._outstanding_requirements.discard(_Requirement.TRANSMISSION)
       if not self._outstanding_requirements:
         self._terminate(packets.Kind.COMPLETION)
 
   def ingestion_complete(self):
     """See superclass method for specification."""
     if self._outstanding_requirements is not None:
-      self._outstanding_requirements.discard(_INGESTION)
+      self._outstanding_requirements.discard(_Requirement.INGESTION)
       if not self._outstanding_requirements:
         self._terminate(packets.Kind.COMPLETION)
 
@@ -163,39 +170,46 @@
       self._terminate(kind)
 
 
-def front_termination_manager(work_pool, utility_pool, action, subscription):
+def front_termination_manager(
+    work_pool, utility_pool, action, subscription_kind):
   """Creates a TerminationManager appropriate for front-side use.
 
   Args:
     work_pool: A thread pool in which customer work will be done.
     utility_pool: A thread pool in which work utility work will be done.
     action: An action to call on operation termination.
-    subscription: One of interfaces.FULL, interfaces.termination_only, or
-      interfaces.NONE.
+    subscription_kind: An interfaces.ServicedSubscription.Kind value.
 
   Returns:
     A TerminationManager appropriate for front-side use.
   """
+  if subscription_kind is interfaces.ServicedSubscription.Kind.NONE:
+    requirements = _FRONT_NOT_LISTENING_REQUIREMENTS
+  else:
+    requirements = _LISTENING_REQUIREMENTS
+
   return _TerminationManager(
-      work_pool, utility_pool, action,
-      _FRONT_NOT_LISTENING_REQUIREMENTS if subscription == interfaces.NONE else
-      _LISTENING_REQUIREMENTS, packets.Kind.SERVICED_FAILURE)
+      work_pool, utility_pool, action, requirements,
+      packets.Kind.SERVICED_FAILURE)
 
 
-def back_termination_manager(work_pool, utility_pool, action, subscription):
+def back_termination_manager(work_pool, utility_pool, action, subscription_kind):
   """Creates a TerminationManager appropriate for back-side use.
 
   Args:
     work_pool: A thread pool in which customer work will be done.
     utility_pool: A thread pool in which work utility work will be done.
     action: An action to call on operation termination.
-    subscription: One of interfaces.FULL, interfaces.termination_only, or
-      interfaces.NONE.
+    subscription_kind: An interfaces.ServicedSubscription.Kind value.
 
   Returns:
     A TerminationManager appropriate for back-side use.
   """
+  if subscription_kind is interfaces.ServicedSubscription.Kind.NONE:
+    requirements = _BACK_NOT_LISTENING_REQUIREMENTS
+  else:
+    requirements = _LISTENING_REQUIREMENTS
+
   return _TerminationManager(
-      work_pool, utility_pool, action,
-      _BACK_NOT_LISTENING_REQUIREMENTS if subscription == interfaces.NONE else
-      _LISTENING_REQUIREMENTS, packets.Kind.SERVICER_FAILURE)
+      work_pool, utility_pool, action, requirements,
+      packets.Kind.SERVICER_FAILURE)
diff --git a/src/python/_framework/base/packets/_transmission.py b/src/python/src/_framework/base/packets/_transmission.py
similarity index 92%
rename from src/python/_framework/base/packets/_transmission.py
rename to src/python/src/_framework/base/packets/_transmission.py
index 0061287..24fe6e6 100644
--- a/src/python/_framework/base/packets/_transmission.py
+++ b/src/python/src/_framework/base/packets/_transmission.py
@@ -91,20 +91,19 @@
 class _FrontPacketizer(_Packetizer):
   """Front-side packet-creating behavior."""
 
-  def __init__(self, name, subscription, trace_id, timeout):
+  def __init__(self, name, subscription_kind, trace_id, timeout):
     """Constructor.
 
     Args:
       name: The name of the operation.
-      subscription: One of interfaces.FULL, interfaces.TERMINATION_ONLY, or
-        interfaces.NONE describing the interest the front has in packets sent
-        from the back.
+      subscription_kind: An interfaces.ServicedSubscription.Kind value
+        describing the interest the front has in packets sent from the back.
       trace_id: A uuid.UUID identifying a set of related operations to which
         this operation belongs.
       timeout: A length of time in seconds to allow for the entire operation.
     """
     self._name = name
-    self._subscription = subscription
+    self._subscription_kind = subscription_kind
     self._trace_id = trace_id
     self._timeout = timeout
 
@@ -114,13 +113,13 @@
       return packets.FrontToBackPacket(
           operation_id, sequence_number,
           packets.Kind.COMPLETION if complete else packets.Kind.CONTINUATION,
-          self._name, self._subscription, self._trace_id, payload,
+          self._name, self._subscription_kind, self._trace_id, payload,
           self._timeout)
     else:
       return packets.FrontToBackPacket(
           operation_id, 0,
           packets.Kind.ENTIRE if complete else packets.Kind.COMMENCEMENT,
-          self._name, self._subscription, self._trace_id, payload,
+          self._name, self._subscription_kind, self._trace_id, payload,
           self._timeout)
 
   def packetize_abortion(self, operation_id, sequence_number, kind):
@@ -335,8 +334,8 @@
 
 
 def front_transmission_manager(
-    lock, pool, callback, operation_id, name, subscription, trace_id, timeout,
-    termination_manager):
+    lock, pool, callback, operation_id, name, subscription_kind, trace_id,
+    timeout, termination_manager):
   """Creates a TransmissionManager appropriate for front-side use.
 
   Args:
@@ -347,9 +346,8 @@
       of the operation.
     operation_id: The operation's ID.
     name: The name of the operation.
-    subscription: One of interfaces.FULL, interfaces.TERMINATION_ONLY, or
-      interfaces.NONE describing the interest the front has in packets sent
-      from the back.
+    subscription_kind: An interfaces.ServicedSubscription.Kind value
+      describing the interest the front has in packets sent from the back.
     trace_id: A uuid.UUID identifying a set of related operations to which
       this operation belongs.
     timeout: A length of time in seconds to allow for the entire operation.
@@ -361,12 +359,13 @@
   """
   return _TransmittingTransmissionManager(
       lock, pool, callback, operation_id, _FrontPacketizer(
-          name, subscription, trace_id, timeout),
+          name, subscription_kind, trace_id, timeout),
       termination_manager)
 
 
 def back_transmission_manager(
-    lock, pool, callback, operation_id, termination_manager, subscription):
+    lock, pool, callback, operation_id, termination_manager,
+    subscription_kind):
   """Creates a TransmissionManager appropriate for back-side use.
 
   Args:
@@ -378,14 +377,13 @@
     operation_id: The operation's ID.
     termination_manager: The _interfaces.TerminationManager associated with
       this operation.
-    subscription: One of interfaces.FULL, interfaces.TERMINATION_ONLY, or
-      interfaces.NONE describing the interest the front has in packets sent from
-      the back.
+    subscription_kind: An interfaces.ServicedSubscription.Kind value
+      describing the interest the front has in packets sent from the back.
 
   Returns:
     A TransmissionManager appropriate for back-side use.
   """
-  if subscription == interfaces.NONE:
+  if subscription_kind is interfaces.ServicedSubscription.Kind.NONE:
     return _EmptyTransmissionManager()
   else:
     return _TransmittingTransmissionManager(
diff --git a/src/python/_framework/base/packets/implementations.py b/src/python/src/_framework/base/packets/implementations.py
similarity index 100%
rename from src/python/_framework/base/packets/implementations.py
rename to src/python/src/_framework/base/packets/implementations.py
diff --git a/src/python/_framework/base/packets/implementations_test.py b/src/python/src/_framework/base/packets/implementations_test.py
similarity index 100%
rename from src/python/_framework/base/packets/implementations_test.py
rename to src/python/src/_framework/base/packets/implementations_test.py
diff --git a/src/python/_framework/base/packets/in_memory.py b/src/python/src/_framework/base/packets/in_memory.py
similarity index 100%
rename from src/python/_framework/base/packets/in_memory.py
rename to src/python/src/_framework/base/packets/in_memory.py
diff --git a/src/python/_framework/base/packets/interfaces.py b/src/python/src/_framework/base/packets/interfaces.py
similarity index 100%
rename from src/python/_framework/base/packets/interfaces.py
rename to src/python/src/_framework/base/packets/interfaces.py
diff --git a/src/python/_framework/base/packets/null.py b/src/python/src/_framework/base/packets/null.py
similarity index 100%
rename from src/python/_framework/base/packets/null.py
rename to src/python/src/_framework/base/packets/null.py
diff --git a/src/python/_framework/base/packets/packets.py b/src/python/src/_framework/base/packets/packets.py
similarity index 94%
rename from src/python/_framework/base/packets/packets.py
rename to src/python/src/_framework/base/packets/packets.py
index 1315ca6..f7503bd 100644
--- a/src/python/_framework/base/packets/packets.py
+++ b/src/python/src/_framework/base/packets/packets.py
@@ -71,10 +71,9 @@
       Kind.RECEPTION_FAILURE, or Kind.TRANSMISSION_FAILURE.
     name: The name of an operation. Must be present if kind is Kind.COMMENCEMENT
       or Kind.ENTIRE. Must be None for any other kind.
-    subscription: One of interfaces.FULL, interfaces.TERMINATION_ONLY, or
-      interfaces.NONE describing the interest the front has in packets sent from
-      the back. Must be present if kind is Kind.COMMENCEMENT or Kind.ENTIRE.
-      Must be None for any other kind.
+    subscription: An interfaces.ServicedSubscription.Kind value describing the
+      interest the front has in packets sent from the back. Must be present if
+      kind is Kind.COMMENCEMENT or Kind.ENTIRE. Must be None for any other kind.
     trace_id: A uuid.UUID identifying a set of related operations to which this
       operation belongs. May be None.
     payload: A customer payload object. Must be present if kind is
diff --git a/src/python/_framework/base/util.py b/src/python/src/_framework/base/util.py
similarity index 86%
rename from src/python/_framework/base/util.py
rename to src/python/src/_framework/base/util.py
index 6bbd18a..35ce044 100644
--- a/src/python/_framework/base/util.py
+++ b/src/python/src/_framework/base/util.py
@@ -36,13 +36,14 @@
 
 
 class _ServicedSubscription(
-    collections.namedtuple('_ServicedSubscription', ['category', 'ingestor']),
+    collections.namedtuple('_ServicedSubscription', ['kind', 'ingestor']),
     interfaces.ServicedSubscription):
   """See interfaces.ServicedSubscription for specification."""
 
-_NONE_SUBSCRIPTION = _ServicedSubscription(interfaces.NONE, None)
+_NONE_SUBSCRIPTION = _ServicedSubscription(
+    interfaces.ServicedSubscription.Kind.NONE, None)
 _TERMINATION_ONLY_SUBSCRIPTION = _ServicedSubscription(
-    interfaces.TERMINATION_ONLY, None)
+    interfaces.ServicedSubscription.Kind.TERMINATION_ONLY, None)
 
 
 def none_serviced_subscription():
@@ -72,12 +73,14 @@
   """Creates a "full" interfaces.ServicedSubscription object.
 
   Args:
-    ingestor: A ServicedIngestor.
+    ingestor: An interfaces.ServicedIngestor.
 
   Returns:
-    A ServicedSubscription object indicating a full subscription.
+    An interfaces.ServicedSubscription object indicating a full
+      subscription.
   """
-  return _ServicedSubscription(interfaces.FULL, ingestor)
+  return _ServicedSubscription(
+      interfaces.ServicedSubscription.Kind.FULL, ingestor)
 
 
 def wait_for_idle(end):
diff --git a/src/python/_framework/common/__init__.py b/src/python/src/_framework/common/__init__.py
similarity index 100%
rename from src/python/_framework/common/__init__.py
rename to src/python/src/_framework/common/__init__.py
diff --git a/src/python/_framework/common/cardinality.py b/src/python/src/_framework/common/cardinality.py
similarity index 100%
rename from src/python/_framework/common/cardinality.py
rename to src/python/src/_framework/common/cardinality.py
diff --git a/src/python/_framework/face/__init__.py b/src/python/src/_framework/face/__init__.py
similarity index 100%
rename from src/python/_framework/face/__init__.py
rename to src/python/src/_framework/face/__init__.py
diff --git a/src/python/_framework/face/_calls.py b/src/python/src/_framework/face/_calls.py
similarity index 95%
rename from src/python/_framework/face/_calls.py
rename to src/python/src/_framework/face/_calls.py
index ab58e63..9128aef 100644
--- a/src/python/_framework/face/_calls.py
+++ b/src/python/src/_framework/face/_calls.py
@@ -94,7 +94,7 @@
 
   def cancel(self):
     self._operation.cancel()
-    self._rendezvous.set_outcome(base_interfaces.CANCELLED)
+    self._rendezvous.set_outcome(base_interfaces.Outcome.CANCELLED)
 
 
 class _OperationFuture(future.Future):
@@ -150,15 +150,12 @@
     """Indicates to this object that the operation has terminated.
 
     Args:
-      operation_outcome: One of base_interfaces.COMPLETED,
-        base_interfaces.CANCELLED, base_interfaces.EXPIRED,
-        base_interfaces.RECEPTION_FAILURE, base_interfaces.TRANSMISSION_FAILURE,
-        base_interfaces.SERVICED_FAILURE, or base_interfaces.SERVICER_FAILURE
-        indicating the categorical outcome of the operation.
+      operation_outcome: A base_interfaces.Outcome value indicating the
+        outcome of the operation.
     """
     with self._condition:
       if (self._outcome is None and
-          operation_outcome != base_interfaces.COMPLETED):
+          operation_outcome is not base_interfaces.Outcome.COMPLETED):
         self._outcome = future.raised(
             _control.abortion_outcome_to_exception(operation_outcome))
         self._condition.notify_all()
diff --git a/src/python/_framework/face/_control.py b/src/python/src/_framework/face/_control.py
similarity index 88%
rename from src/python/_framework/face/_control.py
rename to src/python/src/_framework/face/_control.py
index 2c22132..9f1bf6d 100644
--- a/src/python/_framework/face/_control.py
+++ b/src/python/src/_framework/face/_control.py
@@ -40,13 +40,17 @@
 INTERNAL_ERROR_LOG_MESSAGE = ':-( RPC Framework (Face) Internal Error! :-('
 
 _OPERATION_OUTCOME_TO_RPC_ABORTION = {
-    base_interfaces.CANCELLED: interfaces.CANCELLED,
-    base_interfaces.EXPIRED: interfaces.EXPIRED,
-    base_interfaces.RECEPTION_FAILURE: interfaces.NETWORK_FAILURE,
-    base_interfaces.TRANSMISSION_FAILURE: interfaces.NETWORK_FAILURE,
-    base_interfaces.SERVICED_FAILURE: interfaces.SERVICED_FAILURE,
-    base_interfaces.SERVICER_FAILURE: interfaces.SERVICER_FAILURE,
-    }
+    base_interfaces.Outcome.CANCELLED: interfaces.Abortion.CANCELLED,
+    base_interfaces.Outcome.EXPIRED: interfaces.Abortion.EXPIRED,
+    base_interfaces.Outcome.RECEPTION_FAILURE:
+        interfaces.Abortion.NETWORK_FAILURE,
+    base_interfaces.Outcome.TRANSMISSION_FAILURE:
+        interfaces.Abortion.NETWORK_FAILURE,
+    base_interfaces.Outcome.SERVICED_FAILURE:
+        interfaces.Abortion.SERVICED_FAILURE,
+    base_interfaces.Outcome.SERVICER_FAILURE:
+        interfaces.Abortion.SERVICER_FAILURE,
+}
 
 
 def _as_operation_termination_callback(rpc_abortion_callback):
@@ -59,13 +63,13 @@
 
 
 def _abortion_outcome_to_exception(abortion_outcome):
-  if abortion_outcome == base_interfaces.CANCELLED:
+  if abortion_outcome == base_interfaces.Outcome.CANCELLED:
     return exceptions.CancellationError()
-  elif abortion_outcome == base_interfaces.EXPIRED:
+  elif abortion_outcome == base_interfaces.Outcome.EXPIRED:
     return exceptions.ExpirationError()
-  elif abortion_outcome == base_interfaces.SERVICER_FAILURE:
+  elif abortion_outcome == base_interfaces.Outcome.SERVICER_FAILURE:
     return exceptions.ServicerError()
-  elif abortion_outcome == base_interfaces.SERVICED_FAILURE:
+  elif abortion_outcome == base_interfaces.Outcome.SERVICED_FAILURE:
     return exceptions.ServicedError()
   else:
     return exceptions.NetworkError()
@@ -133,7 +137,7 @@
 
   def set_outcome(self, outcome):
     with self._condition:
-      if outcome != base_interfaces.COMPLETED:
+      if outcome is not base_interfaces.Outcome.COMPLETED:
         self._abortion = outcome
         self._condition.notify()
 
diff --git a/src/python/_framework/face/_service.py b/src/python/src/_framework/face/_service.py
similarity index 100%
rename from src/python/_framework/face/_service.py
rename to src/python/src/_framework/face/_service.py
diff --git a/src/python/_framework/face/_test_case.py b/src/python/src/_framework/face/_test_case.py
similarity index 100%
rename from src/python/_framework/face/_test_case.py
rename to src/python/src/_framework/face/_test_case.py
diff --git a/src/python/_framework/face/blocking_invocation_inline_service_test.py b/src/python/src/_framework/face/blocking_invocation_inline_service_test.py
similarity index 100%
rename from src/python/_framework/face/blocking_invocation_inline_service_test.py
rename to src/python/src/_framework/face/blocking_invocation_inline_service_test.py
diff --git a/src/python/_framework/face/demonstration.py b/src/python/src/_framework/face/demonstration.py
similarity index 100%
rename from src/python/_framework/face/demonstration.py
rename to src/python/src/_framework/face/demonstration.py
diff --git a/src/python/_framework/face/event_invocation_synchronous_event_service_test.py b/src/python/src/_framework/face/event_invocation_synchronous_event_service_test.py
similarity index 100%
rename from src/python/_framework/face/event_invocation_synchronous_event_service_test.py
rename to src/python/src/_framework/face/event_invocation_synchronous_event_service_test.py
diff --git a/src/python/_framework/face/exceptions.py b/src/python/src/_framework/face/exceptions.py
similarity index 100%
rename from src/python/_framework/face/exceptions.py
rename to src/python/src/_framework/face/exceptions.py
diff --git a/src/python/_framework/face/future_invocation_asynchronous_event_service_test.py b/src/python/src/_framework/face/future_invocation_asynchronous_event_service_test.py
similarity index 100%
rename from src/python/_framework/face/future_invocation_asynchronous_event_service_test.py
rename to src/python/src/_framework/face/future_invocation_asynchronous_event_service_test.py
diff --git a/src/python/_framework/face/implementations.py b/src/python/src/_framework/face/implementations.py
similarity index 100%
rename from src/python/_framework/face/implementations.py
rename to src/python/src/_framework/face/implementations.py
diff --git a/src/python/_framework/face/interfaces.py b/src/python/src/_framework/face/interfaces.py
similarity index 93%
rename from src/python/_framework/face/interfaces.py
rename to src/python/src/_framework/face/interfaces.py
index 0cc7c70..2480454 100644
--- a/src/python/_framework/face/interfaces.py
+++ b/src/python/src/_framework/face/interfaces.py
@@ -30,6 +30,7 @@
 """Interfaces for the face layer of RPC Framework."""
 
 import abc
+import enum
 
 # exceptions, abandonment, and future are referenced from specification in this
 # module.
@@ -58,14 +59,15 @@
     raise NotImplementedError()
 
 
-# Constants that categorize RPC abortion.
-# TODO(nathaniel): Learn and use Python's enum library for this de facto
-# enumerated type
-CANCELLED = 'abortion: cancelled'
-EXPIRED = 'abortion: expired'
-NETWORK_FAILURE = 'abortion: network failure'
-SERVICED_FAILURE = 'abortion: serviced failure'
-SERVICER_FAILURE = 'abortion: servicer failure'
+@enum.unique
+class Abortion(enum.Enum):
+  """Categories of RPC abortion."""
+
+  CANCELLED = 'cancelled'
+  EXPIRED = 'expired'
+  NETWORK_FAILURE = 'network failure'
+  SERVICED_FAILURE = 'serviced failure'
+  SERVICER_FAILURE = 'servicer failure'
 
 
 class RpcContext(object):
@@ -93,9 +95,8 @@
     """Registers a callback to be called if the RPC is aborted.
 
     Args:
-      abortion_callback: A callable to be called and passed one of CANCELLED,
-        EXPIRED, NETWORK_FAILURE, SERVICED_FAILURE, or SERVICER_FAILURE in the
-        event of RPC abortion.
+      abortion_callback: A callable to be called and passed an Abortion value
+        in the event of RPC abortion.
     """
     raise NotImplementedError()
 
@@ -474,9 +475,8 @@
       request: The request value for the RPC.
       response_callback: A callback to be called to accept the response value
         of the RPC.
-      abortion_callback: A callback to be called to accept one of CANCELLED,
-        EXPIRED, NETWORK_FAILURE, or SERVICER_FAILURE in the event of RPC
-        abortion.
+      abortion_callback: A callback to be called and passed an Abortion value
+        in the event of RPC abortion.
       timeout: A duration of time in seconds to allow for the RPC.
 
     Returns:
@@ -494,9 +494,8 @@
       request: The request value for the RPC.
       response_consumer: A stream.Consumer to be called to accept the response
         values of the RPC.
-      abortion_callback: A callback to be called to accept one of CANCELLED,
-        EXPIRED, NETWORK_FAILURE, or SERVICER_FAILURE in the event of RPC
-        abortion.
+      abortion_callback: A callback to be called and passed an Abortion value
+        in the event of RPC abortion.
       timeout: A duration of time in seconds to allow for the RPC.
 
     Returns:
@@ -513,9 +512,8 @@
       name: The RPC method name.
       response_callback: A callback to be called to accept the response value
         of the RPC.
-      abortion_callback: A callback to be called to accept one of CANCELLED,
-        EXPIRED, NETWORK_FAILURE, or SERVICER_FAILURE in the event of RPC
-        abortion.
+      abortion_callback: A callback to be called and passed an Abortion value
+        in the event of RPC abortion.
       timeout: A duration of time in seconds to allow for the RPC.
 
     Returns:
@@ -533,9 +531,8 @@
       name: The RPC method name.
       response_consumer: A stream.Consumer to be called to accept the response
         values of the RPC.
-      abortion_callback: A callback to be called to accept one of CANCELLED,
-        EXPIRED, NETWORK_FAILURE, or SERVICER_FAILURE in the event of RPC
-        abortion.
+      abortion_callback: A callback to be called and passed an Abortion value
+        in the event of RPC abortion.
       timeout: A duration of time in seconds to allow for the RPC.
 
     Returns:
diff --git a/src/python/_framework/face/testing/__init__.py b/src/python/src/_framework/face/testing/__init__.py
similarity index 100%
rename from src/python/_framework/face/testing/__init__.py
rename to src/python/src/_framework/face/testing/__init__.py
diff --git a/src/python/_framework/face/testing/base_util.py b/src/python/src/_framework/face/testing/base_util.py
similarity index 100%
rename from src/python/_framework/face/testing/base_util.py
rename to src/python/src/_framework/face/testing/base_util.py
diff --git a/src/python/_framework/face/testing/blocking_invocation_inline_service_test_case.py b/src/python/src/_framework/face/testing/blocking_invocation_inline_service_test_case.py
similarity index 100%
rename from src/python/_framework/face/testing/blocking_invocation_inline_service_test_case.py
rename to src/python/src/_framework/face/testing/blocking_invocation_inline_service_test_case.py
diff --git a/src/python/_framework/face/testing/callback.py b/src/python/src/_framework/face/testing/callback.py
similarity index 100%
rename from src/python/_framework/face/testing/callback.py
rename to src/python/src/_framework/face/testing/callback.py
diff --git a/src/python/_framework/face/testing/control.py b/src/python/src/_framework/face/testing/control.py
similarity index 100%
rename from src/python/_framework/face/testing/control.py
rename to src/python/src/_framework/face/testing/control.py
diff --git a/src/python/_framework/face/testing/coverage.py b/src/python/src/_framework/face/testing/coverage.py
similarity index 100%
rename from src/python/_framework/face/testing/coverage.py
rename to src/python/src/_framework/face/testing/coverage.py
diff --git a/src/python/_framework/face/testing/digest.py b/src/python/src/_framework/face/testing/digest.py
similarity index 100%
rename from src/python/_framework/face/testing/digest.py
rename to src/python/src/_framework/face/testing/digest.py
diff --git a/src/python/_framework/face/testing/event_invocation_synchronous_event_service_test_case.py b/src/python/src/_framework/face/testing/event_invocation_synchronous_event_service_test_case.py
similarity index 93%
rename from src/python/_framework/face/testing/event_invocation_synchronous_event_service_test_case.py
rename to src/python/src/_framework/face/testing/event_invocation_synchronous_event_service_test_case.py
index dba73a9..cb786f5 100644
--- a/src/python/_framework/face/testing/event_invocation_synchronous_event_service_test_case.py
+++ b/src/python/src/_framework/face/testing/event_invocation_synchronous_event_service_test_case.py
@@ -176,7 +176,7 @@
               name, request, callback.complete, callback.abort, _TIMEOUT)
           callback.block_until_terminated()
 
-        self.assertEqual(interfaces.EXPIRED, callback.abortion())
+        self.assertEqual(interfaces.Abortion.EXPIRED, callback.abortion())
 
   def testExpiredUnaryRequestStreamResponse(self):
     for name, test_messages_sequence in (
@@ -190,7 +190,7 @@
               name, request, callback, callback.abort, _TIMEOUT)
           callback.block_until_terminated()
 
-        self.assertEqual(interfaces.EXPIRED, callback.abortion())
+        self.assertEqual(interfaces.Abortion.EXPIRED, callback.abortion())
 
   def testExpiredStreamRequestUnaryResponse(self):
     for name, test_messages_sequence in (
@@ -202,7 +202,7 @@
             name, callback.complete, callback.abort, _TIMEOUT)
         callback.block_until_terminated()
 
-        self.assertEqual(interfaces.EXPIRED, callback.abortion())
+        self.assertEqual(interfaces.Abortion.EXPIRED, callback.abortion())
 
   def testExpiredStreamRequestStreamResponse(self):
     for name, test_messages_sequence in (
@@ -217,7 +217,7 @@
           request_consumer.consume(request)
         callback.block_until_terminated()
 
-        self.assertEqual(interfaces.EXPIRED, callback.abortion())
+        self.assertEqual(interfaces.Abortion.EXPIRED, callback.abortion())
 
   def testFailedUnaryRequestUnaryResponse(self):
     for name, test_messages_sequence in (
@@ -231,7 +231,7 @@
               name, request, callback.complete, callback.abort, _TIMEOUT)
           callback.block_until_terminated()
 
-        self.assertEqual(interfaces.SERVICER_FAILURE, callback.abortion())
+        self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, callback.abortion())
 
   def testFailedUnaryRequestStreamResponse(self):
     for name, test_messages_sequence in (
@@ -245,7 +245,7 @@
               name, request, callback, callback.abort, _TIMEOUT)
           callback.block_until_terminated()
 
-        self.assertEqual(interfaces.SERVICER_FAILURE, callback.abortion())
+        self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, callback.abortion())
 
   def testFailedStreamRequestUnaryResponse(self):
     for name, test_messages_sequence in (
@@ -262,7 +262,7 @@
           request_consumer.terminate()
           callback.block_until_terminated()
 
-        self.assertEqual(interfaces.SERVICER_FAILURE, callback.abortion())
+        self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, callback.abortion())
 
   def testFailedStreamRequestStreamResponse(self):
     for name, test_messages_sequence in (
@@ -279,7 +279,7 @@
           request_consumer.terminate()
           callback.block_until_terminated()
 
-        self.assertEqual(interfaces.SERVICER_FAILURE, callback.abortion())
+        self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, callback.abortion())
 
   def testParallelInvocations(self):
     for name, test_messages_sequence in (
@@ -321,7 +321,7 @@
           call.cancel()
           callback.block_until_terminated()
 
-        self.assertEqual(interfaces.CANCELLED, callback.abortion())
+        self.assertEqual(interfaces.Abortion.CANCELLED, callback.abortion())
 
   def testCancelledUnaryRequestStreamResponse(self):
     for name, test_messages_sequence in (
@@ -335,7 +335,7 @@
         call.cancel()
         callback.block_until_terminated()
 
-        self.assertEqual(interfaces.CANCELLED, callback.abortion())
+        self.assertEqual(interfaces.Abortion.CANCELLED, callback.abortion())
 
   def testCancelledStreamRequestUnaryResponse(self):
     for name, test_messages_sequence in (
@@ -351,7 +351,7 @@
         call.cancel()
         callback.block_until_terminated()
 
-        self.assertEqual(interfaces.CANCELLED, callback.abortion())
+        self.assertEqual(interfaces.Abortion.CANCELLED, callback.abortion())
 
   def testCancelledStreamRequestStreamResponse(self):
     for name, test_messages_sequence in (
@@ -364,4 +364,4 @@
         call.cancel()
         callback.block_until_terminated()
 
-        self.assertEqual(interfaces.CANCELLED, callback.abortion())
+        self.assertEqual(interfaces.Abortion.CANCELLED, callback.abortion())
diff --git a/src/python/_framework/face/testing/future_invocation_asynchronous_event_service_test_case.py b/src/python/src/_framework/face/testing/future_invocation_asynchronous_event_service_test_case.py
similarity index 100%
rename from src/python/_framework/face/testing/future_invocation_asynchronous_event_service_test_case.py
rename to src/python/src/_framework/face/testing/future_invocation_asynchronous_event_service_test_case.py
diff --git a/src/python/_framework/face/testing/interfaces.py b/src/python/src/_framework/face/testing/interfaces.py
similarity index 100%
rename from src/python/_framework/face/testing/interfaces.py
rename to src/python/src/_framework/face/testing/interfaces.py
diff --git a/src/python/_framework/face/testing/serial.py b/src/python/src/_framework/face/testing/serial.py
similarity index 100%
rename from src/python/_framework/face/testing/serial.py
rename to src/python/src/_framework/face/testing/serial.py
diff --git a/src/python/_framework/face/testing/service.py b/src/python/src/_framework/face/testing/service.py
similarity index 100%
rename from src/python/_framework/face/testing/service.py
rename to src/python/src/_framework/face/testing/service.py
diff --git a/src/python/_framework/face/testing/stock_service.py b/src/python/src/_framework/face/testing/stock_service.py
similarity index 100%
rename from src/python/_framework/face/testing/stock_service.py
rename to src/python/src/_framework/face/testing/stock_service.py
diff --git a/src/python/_framework/face/testing/test_case.py b/src/python/src/_framework/face/testing/test_case.py
similarity index 100%
rename from src/python/_framework/face/testing/test_case.py
rename to src/python/src/_framework/face/testing/test_case.py
diff --git a/src/python/_framework/foundation/__init__.py b/src/python/src/_framework/foundation/__init__.py
similarity index 100%
rename from src/python/_framework/foundation/__init__.py
rename to src/python/src/_framework/foundation/__init__.py
diff --git a/src/python/_framework/foundation/_later_test.py b/src/python/src/_framework/foundation/_later_test.py
similarity index 100%
rename from src/python/_framework/foundation/_later_test.py
rename to src/python/src/_framework/foundation/_later_test.py
diff --git a/src/python/_framework/foundation/_logging_pool_test.py b/src/python/src/_framework/foundation/_logging_pool_test.py
similarity index 100%
rename from src/python/_framework/foundation/_logging_pool_test.py
rename to src/python/src/_framework/foundation/_logging_pool_test.py
diff --git a/src/python/_framework/foundation/_timer_future.py b/src/python/src/_framework/foundation/_timer_future.py
similarity index 100%
rename from src/python/_framework/foundation/_timer_future.py
rename to src/python/src/_framework/foundation/_timer_future.py
diff --git a/src/python/_framework/foundation/abandonment.py b/src/python/src/_framework/foundation/abandonment.py
similarity index 100%
rename from src/python/_framework/foundation/abandonment.py
rename to src/python/src/_framework/foundation/abandonment.py
diff --git a/src/python/_framework/foundation/callable_util.py b/src/python/src/_framework/foundation/callable_util.py
similarity index 100%
rename from src/python/_framework/foundation/callable_util.py
rename to src/python/src/_framework/foundation/callable_util.py
diff --git a/src/python/_framework/foundation/future.py b/src/python/src/_framework/foundation/future.py
similarity index 100%
rename from src/python/_framework/foundation/future.py
rename to src/python/src/_framework/foundation/future.py
diff --git a/src/python/_framework/foundation/later.py b/src/python/src/_framework/foundation/later.py
similarity index 100%
rename from src/python/_framework/foundation/later.py
rename to src/python/src/_framework/foundation/later.py
diff --git a/src/python/_framework/foundation/logging_pool.py b/src/python/src/_framework/foundation/logging_pool.py
similarity index 100%
rename from src/python/_framework/foundation/logging_pool.py
rename to src/python/src/_framework/foundation/logging_pool.py
diff --git a/src/python/_framework/foundation/stream.py b/src/python/src/_framework/foundation/stream.py
similarity index 100%
rename from src/python/_framework/foundation/stream.py
rename to src/python/src/_framework/foundation/stream.py
diff --git a/src/python/_framework/foundation/stream_testing.py b/src/python/src/_framework/foundation/stream_testing.py
similarity index 100%
rename from src/python/_framework/foundation/stream_testing.py
rename to src/python/src/_framework/foundation/stream_testing.py
diff --git a/src/python/_framework/foundation/stream_util.py b/src/python/src/_framework/foundation/stream_util.py
similarity index 100%
rename from src/python/_framework/foundation/stream_util.py
rename to src/python/src/_framework/foundation/stream_util.py
diff --git a/src/python/_junkdrawer/__init__.py b/src/python/src/_junkdrawer/__init__.py
similarity index 100%
rename from src/python/_junkdrawer/__init__.py
rename to src/python/src/_junkdrawer/__init__.py
diff --git a/src/python/src/_junkdrawer/math_pb2.py b/src/python/src/_junkdrawer/math_pb2.py
new file mode 100644
index 0000000..2016595
--- /dev/null
+++ b/src/python/src/_junkdrawer/math_pb2.py
@@ -0,0 +1,266 @@
+# 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.
+
+# TODO(nathaniel): Remove this from source control after having made
+# generation from the math.proto source part of GRPC's build-and-test
+# process.
+
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: math.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
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='math.proto',
+  package='math',
+  serialized_pb=_b('\n\nmath.proto\x12\x04math\",\n\x07\x44ivArgs\x12\x10\n\x08\x64ividend\x18\x01 \x02(\x03\x12\x0f\n\x07\x64ivisor\x18\x02 \x02(\x03\"/\n\x08\x44ivReply\x12\x10\n\x08quotient\x18\x01 \x02(\x03\x12\x11\n\tremainder\x18\x02 \x02(\x03\"\x18\n\x07\x46ibArgs\x12\r\n\x05limit\x18\x01 \x01(\x03\"\x12\n\x03Num\x12\x0b\n\x03num\x18\x01 \x02(\x03\"\x19\n\x08\x46ibReply\x12\r\n\x05\x63ount\x18\x01 \x02(\x03\x32\xa4\x01\n\x04Math\x12&\n\x03\x44iv\x12\r.math.DivArgs\x1a\x0e.math.DivReply\"\x00\x12.\n\x07\x44ivMany\x12\r.math.DivArgs\x1a\x0e.math.DivReply\"\x00(\x01\x30\x01\x12#\n\x03\x46ib\x12\r.math.FibArgs\x1a\t.math.Num\"\x00\x30\x01\x12\x1f\n\x03Sum\x12\t.math.Num\x1a\t.math.Num\"\x00(\x01')
+)
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+
+
+
+_DIVARGS = _descriptor.Descriptor(
+  name='DivArgs',
+  full_name='math.DivArgs',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='dividend', full_name='math.DivArgs.dividend', index=0,
+      number=1, type=3, cpp_type=2, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='divisor', full_name='math.DivArgs.divisor', index=1,
+      number=2, type=3, cpp_type=2, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=20,
+  serialized_end=64,
+)
+
+
+_DIVREPLY = _descriptor.Descriptor(
+  name='DivReply',
+  full_name='math.DivReply',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='quotient', full_name='math.DivReply.quotient', index=0,
+      number=1, type=3, cpp_type=2, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='remainder', full_name='math.DivReply.remainder', index=1,
+      number=2, type=3, cpp_type=2, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=66,
+  serialized_end=113,
+)
+
+
+_FIBARGS = _descriptor.Descriptor(
+  name='FibArgs',
+  full_name='math.FibArgs',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='limit', full_name='math.FibArgs.limit', index=0,
+      number=1, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=115,
+  serialized_end=139,
+)
+
+
+_NUM = _descriptor.Descriptor(
+  name='Num',
+  full_name='math.Num',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='num', full_name='math.Num.num', index=0,
+      number=1, type=3, cpp_type=2, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=141,
+  serialized_end=159,
+)
+
+
+_FIBREPLY = _descriptor.Descriptor(
+  name='FibReply',
+  full_name='math.FibReply',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='count', full_name='math.FibReply.count', index=0,
+      number=1, type=3, cpp_type=2, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=161,
+  serialized_end=186,
+)
+
+DESCRIPTOR.message_types_by_name['DivArgs'] = _DIVARGS
+DESCRIPTOR.message_types_by_name['DivReply'] = _DIVREPLY
+DESCRIPTOR.message_types_by_name['FibArgs'] = _FIBARGS
+DESCRIPTOR.message_types_by_name['Num'] = _NUM
+DESCRIPTOR.message_types_by_name['FibReply'] = _FIBREPLY
+
+DivArgs = _reflection.GeneratedProtocolMessageType('DivArgs', (_message.Message,), dict(
+  DESCRIPTOR = _DIVARGS,
+  __module__ = 'math_pb2'
+  # @@protoc_insertion_point(class_scope:math.DivArgs)
+  ))
+_sym_db.RegisterMessage(DivArgs)
+
+DivReply = _reflection.GeneratedProtocolMessageType('DivReply', (_message.Message,), dict(
+  DESCRIPTOR = _DIVREPLY,
+  __module__ = 'math_pb2'
+  # @@protoc_insertion_point(class_scope:math.DivReply)
+  ))
+_sym_db.RegisterMessage(DivReply)
+
+FibArgs = _reflection.GeneratedProtocolMessageType('FibArgs', (_message.Message,), dict(
+  DESCRIPTOR = _FIBARGS,
+  __module__ = 'math_pb2'
+  # @@protoc_insertion_point(class_scope:math.FibArgs)
+  ))
+_sym_db.RegisterMessage(FibArgs)
+
+Num = _reflection.GeneratedProtocolMessageType('Num', (_message.Message,), dict(
+  DESCRIPTOR = _NUM,
+  __module__ = 'math_pb2'
+  # @@protoc_insertion_point(class_scope:math.Num)
+  ))
+_sym_db.RegisterMessage(Num)
+
+FibReply = _reflection.GeneratedProtocolMessageType('FibReply', (_message.Message,), dict(
+  DESCRIPTOR = _FIBREPLY,
+  __module__ = 'math_pb2'
+  # @@protoc_insertion_point(class_scope:math.FibReply)
+  ))
+_sym_db.RegisterMessage(FibReply)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/src/python/_junkdrawer/stock_pb2.py b/src/python/src/_junkdrawer/stock_pb2.py
similarity index 100%
rename from src/python/_junkdrawer/stock_pb2.py
rename to src/python/src/_junkdrawer/stock_pb2.py
diff --git a/src/ruby/Rakefile b/src/ruby/Rakefile
index 5fc325e..b27305d 100755
--- a/src/ruby/Rakefile
+++ b/src/ruby/Rakefile
@@ -35,18 +35,20 @@
 
         t.pattern = spec_files
         t.rspec_opts = "--tag #{suite[:tag]}" if suite[:tag]
-        t.rspec_opts = suite[:tags].map{ |t| "--tag #{t}" }.join(' ') if suite[:tags]
+        if suite[:tags]
+          t.rspec_opts = suite[:tags].map { |x| "--tag #{x}" }.join(' ')
+        end
       end
     end
   end
 end
 
-desc 'Run compiles the extension, runs all the tests'
+desc 'Compiles the extension then runs all the tests'
 task :all
 
 task default: :all
-task 'spec:suite:wrapper' => :compile
+task 'spec:suite:wrapper' => [:compile, :rubocop]
 task 'spec:suite:idiomatic' => 'spec:suite:wrapper'
 task 'spec:suite:bidi' => 'spec:suite:wrapper'
 task 'spec:suite:server' => 'spec:suite:wrapper'
-task :all => ['spec:suite:idiomatic', 'spec:suite:bidi', 'spec:suite:server']
+task all: ['spec:suite:idiomatic', 'spec:suite:bidi', 'spec:suite:server']
diff --git a/src/ruby/bin/apis/google/protobuf/empty.rb b/src/ruby/bin/apis/google/protobuf/empty.rb
new file mode 100644
index 0000000..33e8a92
--- /dev/null
+++ b/src/ruby/bin/apis/google/protobuf/empty.rb
@@ -0,0 +1,44 @@
+# Copyright 2014, 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.
+
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: google/protobuf/empty.proto
+
+require 'google/protobuf'
+
+Google::Protobuf::DescriptorPool.generated_pool.build do
+  add_message "google.protobuf.Empty" do
+  end
+end
+
+module Google
+  module Protobuf
+    Empty = Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Empty").msgclass
+  end
+end
diff --git a/src/ruby/bin/apis/pubsub_demo.rb b/src/ruby/bin/apis/pubsub_demo.rb
new file mode 100755
index 0000000..8ebac19
--- /dev/null
+++ b/src/ruby/bin/apis/pubsub_demo.rb
@@ -0,0 +1,278 @@
+#!/usr/bin/env ruby
+
+# Copyright 2014, 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.
+
+# pubsub_demo demos accesses the Google PubSub API via its gRPC interface
+#
+# TODO: update the Usage once the usable auth gem is available
+# $ SSL_CERT_FILE=<path/to/ssl/certs> \
+#   path/to/pubsub_demo.rb \
+#   --service_account_key_file=<path_to_service_account> \
+#   [--action=<chosen_demo_action> ]
+#
+# There are options related to the chosen action, see #parse_args below.
+# - the possible actions are given by the method names of NamedAction class
+# - the default action is list_some_topics
+
+this_dir = File.expand_path(File.dirname(__FILE__))
+lib_dir = File.join(File.dirname(File.dirname(this_dir)), 'lib')
+$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
+$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir)
+
+require 'optparse'
+
+require 'grpc'
+require 'google/protobuf'
+
+require 'google/protobuf/empty'
+require 'tech/pubsub/proto/pubsub'
+require 'tech/pubsub/proto/pubsub_services'
+
+# loads the certificates used to access the test server securely.
+def load_prod_cert
+  fail 'could not find a production cert' if ENV['SSL_CERT_FILE'].nil?
+  p "loading prod certs from #{ENV['SSL_CERT_FILE']}"
+  File.open(ENV['SSL_CERT_FILE']).read
+end
+
+# creates a SSL Credentials from the production certificates.
+def ssl_creds
+  GRPC::Core::Credentials.new(load_prod_cert)
+end
+
+# Builds the metadata authentication update proc.
+#
+# TODO: replace this once the ruby usable auth repo is available.
+def auth_proc(opts)
+  if GRPC::Auth::GCECredentials.on_gce?
+    return GRPC::Auth::GCECredentials.new.updater_proc
+  end
+  fd = StringIO.new(File.read(opts.oauth_key_file))
+  GRPC::Auth::ServiceAccountCredentials.new(opts.oauth_scope, fd).updater_proc
+end
+
+# Creates a stub for accessing the publisher service.
+def publisher_stub(opts)
+  address = "#{opts.host}:#{opts.port}"
+  stub_clz = Tech::Pubsub::PublisherService::Stub # shorter
+  logger.info("... access PublisherService at #{address}")
+  stub_clz.new(address,
+               creds: ssl_creds, update_metadata: auth_proc(opts),
+               GRPC::Core::Channel::SSL_TARGET => opts.host)
+end
+
+# Creates a stub for accessing the subscriber service.
+def subscriber_stub(opts)
+  address = "#{opts.host}:#{opts.port}"
+  stub_clz = Tech::Pubsub::SubscriberService::Stub # shorter
+  logger.info("... access SubscriberService at #{address}")
+  stub_clz.new(address,
+               creds: ssl_creds, update_metadata: auth_proc(opts),
+               GRPC::Core::Channel::SSL_TARGET => opts.host)
+end
+
+# defines methods corresponding to each interop test case.
+class NamedActions
+  include Tech::Pubsub
+
+  # Initializes NamedActions
+  #
+  # @param pub [Stub] a stub for accessing the publisher service
+  # @param sub [Stub] a stub for accessing the publisher service
+  # @param args [Args] provides access to the command line
+  def initialize(pub, sub, args)
+    @pub = pub
+    @sub = sub
+    @args = args
+  end
+
+  # Removes the test topic if it exists
+  def remove_topic
+    name = test_topic_name
+    p "... removing Topic #{name}"
+    @pub.delete_topic(DeleteTopicRequest.new(topic: name))
+    p "removed Topic: #{name} OK"
+  rescue GRPC::BadStatus => e
+    p "Could not delete a topics: rpc failed with '#{e}'"
+  end
+
+  # Creates a test topic
+  def create_topic
+    name = test_topic_name
+    p "... creating Topic #{name}"
+    resp = @pub.create_topic(Topic.new(name: name))
+    p "created Topic: #{resp.name} OK"
+  rescue GRPC::BadStatus => e
+    p "Could not create a topics: rpc failed with '#{e}'"
+  end
+
+  # Lists topics in the project
+  def list_some_topics
+    p 'Listing topics'
+    p '-------------_'
+    list_project_topics.topic.each { |t| p t.name }
+  rescue GRPC::BadStatus => e
+    p "Could not list topics: rpc failed with '#{e}'"
+  end
+
+  # Checks if a topics exists in a project
+  def check_exists
+    name = test_topic_name
+    p "... checking for topic #{name}"
+    exists = topic_exists?(name)
+    p "#{name} is a topic" if exists
+    p "#{name} is not a topic" unless exists
+  rescue GRPC::BadStatus => e
+    p "Could not check for a topics: rpc failed with '#{e}'"
+  end
+
+  # Publishes some messages
+  def random_pub_sub
+    topic_name, sub_name = test_topic_name, test_sub_name
+    create_topic_if_needed(topic_name)
+    @sub.create_subscription(Subscription.new(name: sub_name,
+                                              topic: topic_name))
+    msg_count = rand(10..30)
+    msg_count.times do |x|
+      msg = PubsubMessage.new(data: "message #{x}")
+      @pub.publish(PublishRequest.new(topic: topic_name, message: msg))
+    end
+    p "Sent #{msg_count} messages to #{topic_name}, checking for them now."
+    batch = @sub.pull_batch(PullBatchRequest.new(subscription: sub_name,
+                                                 max_events: msg_count))
+    ack_ids = batch.pull_responses.map { |x| x.ack_id }
+    p "Got #{ack_ids.size} messages; acknowledging them.."
+    @sub.acknowledge(AcknowledgeRequest.new(subscription: sub_name,
+                                            ack_id: ack_ids))
+    p "Test messages were acknowledged OK, deleting the subscription"
+    del_req = DeleteSubscriptionRequest.new(subscription: sub_name)
+    @sub.delete_subscription(del_req)
+  rescue GRPC::BadStatus => e
+    p "Could not do random pub sub: rpc failed with '#{e}'"
+  end
+
+  private
+
+  # test_topic_name is the topic name to use in this test.
+  def test_topic_name
+    unless @args.topic_name.nil?
+      return "/topics/#{@args.project_id}/#{@args.topic_name}"
+    end
+    now_text = Time.now.utc.strftime('%Y%m%d%H%M%S%L')
+    "/topics/#{@args.project_id}/#{ENV['USER']}-#{now_text}"
+  end
+
+  # test_sub_name is the subscription name to use in this test.
+  def test_sub_name
+    unless @args.sub_name.nil?
+      return "/subscriptions/#{@args.project_id}/#{@args.sub_name}"
+    end
+    now_text = Time.now.utc.strftime('%Y%m%d%H%M%S%L')
+    "/subscriptions/#{@args.project_id}/#{ENV['USER']}-#{now_text}"
+  end
+
+  # determines if the topic name exists
+  def topic_exists?(name)
+    topics = list_project_topics.topic.map { |t| t.name }
+    topics.include?(name)
+  end
+
+  def create_topic_if_needed(name)
+    return if topic_exists?(name)
+    @pub.create_topic(Topic.new(name: name))
+  end
+
+  def list_project_topics
+    q = "cloud.googleapis.com/project in (/projects/#{@args.project_id})"
+    @pub.list_topics(ListTopicsRequest.new(query: q))
+  end
+end
+
+# Args is used to hold the command line info.
+Args = Struct.new(:host, :oauth_scope, :oauth_key_file, :port, :action,
+                  :project_id, :topic_name, :sub_name)
+
+# validates the the command line options, returning them as an Arg.
+def parse_args
+  args = Args.new('pubsub-staging.googleapis.com',
+                  'https://www.googleapis.com/auth/pubsub',
+                  nil, 443, 'list_some_topics', 'stoked-keyword-656')
+  OptionParser.new do |opts|
+    opts.on('--oauth_scope scope',
+            'Scope for OAuth tokens') { |v| args['oauth_scope'] = v }
+    opts.on('--server_host SERVER_HOST', 'server hostname') do |v|
+      args.host = v
+    end
+    opts.on('--server_port SERVER_PORT', 'server port') do |v|
+      args.port = v
+    end
+    opts.on('--service_account_key_file PATH',
+            'Path to the service account json key file') do |v|
+      args.oauth_key_file = v
+    end
+
+    # instance_methods(false) gives only the methods defined in that class.
+    scenes = NamedActions.instance_methods(false).map { |t| t.to_s }
+    scene_list = scenes.join(',')
+    opts.on("--action CODE", scenes, {}, 'pick a demo action',
+            "  (#{scene_list})") do |v|
+      args.action = v
+    end
+
+    # Set the remaining values.
+    %w(project_id topic_name sub_name).each do |o|
+      opts.on("--#{o} VALUE", "#{o}") do |v|
+        args[o] = v
+      end
+    end
+  end.parse!
+  _check_args(args)
+end
+
+def _check_args(args)
+  %w(host port action).each do |a|
+    if args[a].nil?
+      raise OptionParser::MissingArgument.new("please specify --#{a}")
+    end
+  end
+  if args['oauth_key_file'].nil? || args['oauth_scope'].nil?
+    fail(OptionParser::MissingArgument,
+         'please specify both of --service_account_key_file and --oauth_scope')
+  end
+  args
+end
+
+def main
+  args = parse_args
+  pub, sub = publisher_stub(args), subscriber_stub(args)
+  NamedActions.new(pub, sub, args).method(args.action).call
+end
+
+main
diff --git a/src/ruby/bin/apis/tech/pubsub/proto/pubsub.rb b/src/ruby/bin/apis/tech/pubsub/proto/pubsub.rb
new file mode 100644
index 0000000..aa7893d
--- /dev/null
+++ b/src/ruby/bin/apis/tech/pubsub/proto/pubsub.rb
@@ -0,0 +1,174 @@
+# Copyright 2014, 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.
+
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: tech/pubsub/proto/pubsub.proto
+
+require 'google/protobuf'
+
+require 'google/protobuf/empty'
+Google::Protobuf::DescriptorPool.generated_pool.build do
+  add_message "tech.pubsub.Topic" do
+    optional :name, :string, 1
+  end
+  add_message "tech.pubsub.PubsubMessage" do
+    optional :data, :string, 1
+    optional :message_id, :string, 3
+  end
+  add_message "tech.pubsub.GetTopicRequest" do
+    optional :topic, :string, 1
+  end
+  add_message "tech.pubsub.PublishRequest" do
+    optional :topic, :string, 1
+    optional :message, :message, 2, "tech.pubsub.PubsubMessage"
+  end
+  add_message "tech.pubsub.PublishBatchRequest" do
+    optional :topic, :string, 1
+    repeated :messages, :message, 2, "tech.pubsub.PubsubMessage"
+  end
+  add_message "tech.pubsub.PublishBatchResponse" do
+    repeated :message_ids, :string, 1
+  end
+  add_message "tech.pubsub.ListTopicsRequest" do
+    optional :query, :string, 1
+    optional :max_results, :int32, 2
+    optional :page_token, :string, 3
+  end
+  add_message "tech.pubsub.ListTopicsResponse" do
+    repeated :topic, :message, 1, "tech.pubsub.Topic"
+    optional :next_page_token, :string, 2
+  end
+  add_message "tech.pubsub.DeleteTopicRequest" do
+    optional :topic, :string, 1
+  end
+  add_message "tech.pubsub.Subscription" do
+    optional :name, :string, 1
+    optional :topic, :string, 2
+    optional :query, :string, 3
+    optional :truncation_policy, :message, 4, "tech.pubsub.Subscription.TruncationPolicy"
+    optional :push_config, :message, 5, "tech.pubsub.PushConfig"
+    optional :ack_deadline_seconds, :int32, 6
+    optional :garbage_collect_seconds, :int64, 7
+  end
+  add_message "tech.pubsub.Subscription.TruncationPolicy" do
+    optional :max_bytes, :int64, 1
+    optional :max_age_seconds, :int64, 2
+  end
+  add_message "tech.pubsub.PushConfig" do
+    optional :push_endpoint, :string, 1
+  end
+  add_message "tech.pubsub.PubsubEvent" do
+    optional :subscription, :string, 1
+    optional :message, :message, 2, "tech.pubsub.PubsubMessage"
+    optional :truncated, :bool, 3
+    optional :deleted, :bool, 4
+  end
+  add_message "tech.pubsub.GetSubscriptionRequest" do
+    optional :subscription, :string, 1
+  end
+  add_message "tech.pubsub.ListSubscriptionsRequest" do
+    optional :query, :string, 1
+    optional :max_results, :int32, 3
+    optional :page_token, :string, 4
+  end
+  add_message "tech.pubsub.ListSubscriptionsResponse" do
+    repeated :subscription, :message, 1, "tech.pubsub.Subscription"
+    optional :next_page_token, :string, 2
+  end
+  add_message "tech.pubsub.TruncateSubscriptionRequest" do
+    optional :subscription, :string, 1
+  end
+  add_message "tech.pubsub.DeleteSubscriptionRequest" do
+    optional :subscription, :string, 1
+  end
+  add_message "tech.pubsub.ModifyPushConfigRequest" do
+    optional :subscription, :string, 1
+    optional :push_config, :message, 2, "tech.pubsub.PushConfig"
+  end
+  add_message "tech.pubsub.PullRequest" do
+    optional :subscription, :string, 1
+    optional :return_immediately, :bool, 2
+  end
+  add_message "tech.pubsub.PullResponse" do
+    optional :ack_id, :string, 1
+    optional :pubsub_event, :message, 2, "tech.pubsub.PubsubEvent"
+  end
+  add_message "tech.pubsub.PullBatchRequest" do
+    optional :subscription, :string, 1
+    optional :return_immediately, :bool, 2
+    optional :max_events, :int32, 3
+  end
+  add_message "tech.pubsub.PullBatchResponse" do
+    repeated :pull_responses, :message, 2, "tech.pubsub.PullResponse"
+  end
+  add_message "tech.pubsub.ModifyAckDeadlineRequest" do
+    optional :subscription, :string, 1
+    optional :ack_id, :string, 2
+    optional :ack_deadline_seconds, :int32, 3
+  end
+  add_message "tech.pubsub.AcknowledgeRequest" do
+    optional :subscription, :string, 1
+    repeated :ack_id, :string, 2
+  end
+  add_message "tech.pubsub.NackRequest" do
+    optional :subscription, :string, 1
+    repeated :ack_id, :string, 2
+  end
+end
+
+module Tech
+  module Pubsub
+    Topic = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.Topic").msgclass
+    PubsubMessage = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.PubsubMessage").msgclass
+    GetTopicRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.GetTopicRequest").msgclass
+    PublishRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.PublishRequest").msgclass
+    PublishBatchRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.PublishBatchRequest").msgclass
+    PublishBatchResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.PublishBatchResponse").msgclass
+    ListTopicsRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.ListTopicsRequest").msgclass
+    ListTopicsResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.ListTopicsResponse").msgclass
+    DeleteTopicRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.DeleteTopicRequest").msgclass
+    Subscription = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.Subscription").msgclass
+    Subscription::TruncationPolicy = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.Subscription.TruncationPolicy").msgclass
+    PushConfig = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.PushConfig").msgclass
+    PubsubEvent = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.PubsubEvent").msgclass
+    GetSubscriptionRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.GetSubscriptionRequest").msgclass
+    ListSubscriptionsRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.ListSubscriptionsRequest").msgclass
+    ListSubscriptionsResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.ListSubscriptionsResponse").msgclass
+    TruncateSubscriptionRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.TruncateSubscriptionRequest").msgclass
+    DeleteSubscriptionRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.DeleteSubscriptionRequest").msgclass
+    ModifyPushConfigRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.ModifyPushConfigRequest").msgclass
+    PullRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.PullRequest").msgclass
+    PullResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.PullResponse").msgclass
+    PullBatchRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.PullBatchRequest").msgclass
+    PullBatchResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.PullBatchResponse").msgclass
+    ModifyAckDeadlineRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.ModifyAckDeadlineRequest").msgclass
+    AcknowledgeRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.AcknowledgeRequest").msgclass
+    NackRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tech.pubsub.NackRequest").msgclass
+  end
+end
diff --git a/src/ruby/bin/apis/tech/pubsub/proto/pubsub_services.rb b/src/ruby/bin/apis/tech/pubsub/proto/pubsub_services.rb
new file mode 100644
index 0000000..0023f48
--- /dev/null
+++ b/src/ruby/bin/apis/tech/pubsub/proto/pubsub_services.rb
@@ -0,0 +1,103 @@
+# Copyright 2014, 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.
+
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# Source: tech/pubsub/proto/pubsub.proto for package 'tech.pubsub'
+
+require 'grpc'
+require 'google/protobuf/empty'
+require 'tech/pubsub/proto/pubsub'
+
+module Tech
+  module Pubsub
+    module PublisherService
+
+      # TODO: add proto service documentation here
+      class Service
+
+        include GRPC::GenericService
+
+        self.marshal_class_method = :encode
+        self.unmarshal_class_method = :decode
+        self.service_name = 'tech.pubsub.PublisherService'
+
+        rpc :CreateTopic, Topic, Topic
+        rpc :Publish, PublishRequest, Google::Protobuf::Empty
+        rpc :PublishBatch, PublishBatchRequest, PublishBatchResponse
+        rpc :GetTopic, GetTopicRequest, Topic
+        rpc :ListTopics, ListTopicsRequest, ListTopicsResponse
+        rpc :DeleteTopic, DeleteTopicRequest, Google::Protobuf::Empty
+      end
+
+      Stub = Service.rpc_stub_class
+    end
+    module SubscriberService
+
+      # TODO: add proto service documentation here
+      class Service
+
+        include GRPC::GenericService
+
+        self.marshal_class_method = :encode
+        self.unmarshal_class_method = :decode
+        self.service_name = 'tech.pubsub.SubscriberService'
+
+        rpc :CreateSubscription, Subscription, Subscription
+        rpc :GetSubscription, GetSubscriptionRequest, Subscription
+        rpc :ListSubscriptions, ListSubscriptionsRequest, ListSubscriptionsResponse
+        rpc :DeleteSubscription, DeleteSubscriptionRequest, Google::Protobuf::Empty
+        rpc :TruncateSubscription, TruncateSubscriptionRequest, Google::Protobuf::Empty
+        rpc :ModifyPushConfig, ModifyPushConfigRequest, Google::Protobuf::Empty
+        rpc :Pull, PullRequest, PullResponse
+        rpc :PullBatch, PullBatchRequest, PullBatchResponse
+        rpc :ModifyAckDeadline, ModifyAckDeadlineRequest, Google::Protobuf::Empty
+        rpc :Acknowledge, AcknowledgeRequest, Google::Protobuf::Empty
+        rpc :Nack, NackRequest, Google::Protobuf::Empty
+      end
+
+      Stub = Service.rpc_stub_class
+    end
+    module PushEndpointService
+
+      # TODO: add proto service documentation here
+      class Service
+
+        include GRPC::GenericService
+
+        self.marshal_class_method = :encode
+        self.unmarshal_class_method = :decode
+        self.service_name = 'tech.pubsub.PushEndpointService'
+
+        rpc :HandlePubsubEvent, PubsubEvent, Google::Protobuf::Empty
+      end
+
+      Stub = Service.rpc_stub_class
+    end
+  end
+end
diff --git a/src/ruby/bin/interop/interop_client.rb b/src/ruby/bin/interop/interop_client.rb
index 86739b7..e29e22b 100755
--- a/src/ruby/bin/interop/interop_client.rb
+++ b/src/ruby/bin/interop/interop_client.rb
@@ -56,6 +56,8 @@
 
 require 'signet/ssl_config'
 
+include Google::RPC::Auth
+
 # loads the certificates used to access the test server securely.
 def load_test_certs
   this_dir = File.expand_path(File.dirname(__FILE__))
@@ -67,40 +69,54 @@
 # loads the certificates used to access the test server securely.
 def load_prod_cert
   fail 'could not find a production cert' if ENV['SSL_CERT_FILE'].nil?
-  p "loading prod certs from #{ENV['SSL_CERT_FILE']}"
+  logger.info("loading prod certs from #{ENV['SSL_CERT_FILE']}")
   File.open(ENV['SSL_CERT_FILE']).read
 end
 
-# creates a Credentials from the test certificates.
+# creates SSL Credentials from the test certificates.
 def test_creds
   certs = load_test_certs
   GRPC::Core::Credentials.new(certs[0])
 end
 
-RX_CERT = /-----BEGIN CERTIFICATE-----\n.*?-----END CERTIFICATE-----\n/m
-
-
-# creates a Credentials from the production certificates.
+# creates SSL Credentials from the production certificates.
 def prod_creds
   cert_text = load_prod_cert
   GRPC::Core::Credentials.new(cert_text)
 end
 
+# creates the SSL Credentials.
+def ssl_creds(use_test_ca)
+  return test_creds if use_test_ca
+  prod_creds
+end
+
 # creates a test stub that accesses host:port securely.
-def create_stub(host, port, is_secure, host_override, use_test_ca)
-  address = "#{host}:#{port}"
-  if is_secure
-    creds = nil
-    if use_test_ca
-      creds = test_creds
-    else
-      creds = prod_creds
+def create_stub(opts)
+  address = "#{opts.host}:#{opts.port}"
+  if opts.secure
+    stub_opts = {
+      :creds => ssl_creds(opts.use_test_ca),
+      GRPC::Core::Channel::SSL_TARGET => opts.host_override
+    }
+
+    # Add service account creds if specified
+    if %w(all service_account_creds).include?(opts.test_case)
+      unless opts.oauth_scope.nil?
+        fd = StringIO.new(File.read(opts.oauth_key_file))
+        logger.info("loading oauth certs from #{opts.oauth_key_file}")
+        auth_creds = ServiceAccountCredentials.new(opts.oauth_scope, fd)
+        stub_opts[:update_metadata] = auth_creds.updater_proc
+      end
     end
 
-    stub_opts = {
-      :creds => creds,
-      GRPC::Core::Channel::SSL_TARGET => host_override
-    }
+    # Add compute engine creds if specified
+    if %w(all compute_engine_creds).include?(opts.test_case)
+      unless opts.oauth_scope.nil?
+        stub_opts[:update_metadata] = GCECredentials.new.update_proc
+      end
+    end
+
     logger.info("... connecting securely to #{address}")
     Grpc::Testing::TestService::Stub.new(address, **stub_opts)
   else
@@ -158,9 +174,10 @@
   include Grpc::Testing::PayloadType
   attr_accessor :assertions # required by Minitest::Assertions
 
-  def initialize(stub)
+  def initialize(stub, args)
     @assertions = 0  # required by Minitest::Assertions
     @stub = stub
+    @args = args
   end
 
   def empty_unary
@@ -170,21 +187,37 @@
   end
 
   def large_unary
-    req_size, wanted_response_size = 271_828, 314_159
-    payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size))
-    req = SimpleRequest.new(response_type: :COMPRESSABLE,
-                            response_size: wanted_response_size,
-                            payload: payload)
-    resp = @stub.unary_call(req)
-    assert_equal(:COMPRESSABLE, resp.payload.type,
-                 'large_unary: payload had the wrong type')
-    assert_equal(wanted_response_size, resp.payload.body.length,
-                 'large_unary: payload had the wrong length')
-    assert_equal(nulls(wanted_response_size), resp.payload.body,
-                 'large_unary: payload content is invalid')
+    perform_large_unary
     p 'OK: large_unary'
   end
 
+  def service_account_creds
+    # ignore this test if the oauth options are not set
+    if @args.oauth_scope.nil? || @args.oauth_key_file.nil?
+      p 'NOT RUN: service_account_creds; no service_account settings'
+      return
+    end
+    json_key = File.read(@args.oauth_key_file)
+    wanted_email = MultiJson.load(json_key)['client_email']
+    resp = perform_large_unary(fill_username: true,
+                               fill_oauth_scope: true)
+    assert_equal(wanted_email, resp.username,
+                 'service_account_creds: incorrect username')
+    assert(@args.oauth_scope.include?(resp.oauth_scope),
+           'service_account_creds: incorrect oauth_scope')
+    p 'OK: service_account_creds'
+  end
+
+  def compute_engine_creds
+    resp = perform_large_unary(fill_username: true,
+                               fill_oauth_scope: true)
+    assert(@args.oauth_scope.include?(resp.oauth_scope),
+           'service_account_creds: incorrect oauth_scope')
+    assert_equal(@args.default_service_account, resp.username,
+                 'service_account_creds: incorrect username')
+    p 'OK: compute_engine_creds'
+  end
+
   def client_streaming
     msg_sizes = [27_182, 8, 1828, 45_904]
     wanted_aggregate_size = 74_922
@@ -230,64 +263,89 @@
       method(m).call
     end
   end
+
+  private
+
+  def perform_large_unary(fill_username: false, fill_oauth_scope: false)
+    req_size, wanted_response_size = 271_828, 314_159
+    payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size))
+    req = SimpleRequest.new(response_type: :COMPRESSABLE,
+                            response_size: wanted_response_size,
+                            payload: payload)
+    req.fill_username = fill_username
+    req.fill_oauth_scope = fill_oauth_scope
+    resp = @stub.unary_call(req)
+    assert_equal(:COMPRESSABLE, resp.payload.type,
+                 'large_unary: payload had the wrong type')
+    assert_equal(wanted_response_size, resp.payload.body.length,
+                 'large_unary: payload had the wrong length')
+    assert_equal(nulls(wanted_response_size), resp.payload.body,
+                 'large_unary: payload content is invalid')
+    resp
+  end
 end
 
+# Args is used to hold the command line info.
+Args = Struct.new(:default_service_account, :host, :host_override,
+                  :oauth_scope, :oauth_key_file, :port, :secure, :test_case,
+                  :use_test_ca)
+
 # validates the the command line options, returning them as a Hash.
-def parse_options
-  options = {
-    'secure' => false,
-    'server_host' => nil,
-    'server_host_override' => nil,
-    'server_port' => nil,
-    'test_case' => nil
-  }
+def parse_args
+  args = Args.new
+  args.host_override = 'foo.test.google.com'
   OptionParser.new do |opts|
-    opts.banner = 'Usage: --server_host <server_host> --server_port server_port'
+    opts.on('--oauth_scope scope',
+            'Scope for OAuth tokens') { |v| args['oauth_scope'] = v }
     opts.on('--server_host SERVER_HOST', 'server hostname') do |v|
-      options['server_host'] = v
+      args['host'] = v
+    end
+    opts.on('--default_service_account email_address',
+            'email address of the default service account') do |v|
+      args['default_service_account'] = v
+    end
+    opts.on('--service_account_key_file PATH',
+            'Path to the service account json key file') do |v|
+      args['oauth_key_file'] = v
     end
     opts.on('--server_host_override HOST_OVERRIDE',
             'override host via a HTTP header') do |v|
-      options['server_host_override'] = v
+      args['host_override'] = v
     end
-    opts.on('--server_port SERVER_PORT', 'server port') do |v|
-      options['server_port'] = v
-    end
+    opts.on('--server_port SERVER_PORT', 'server port') { |v| args['port'] = v }
     # instance_methods(false) gives only the methods defined in that class
     test_cases = NamedTests.instance_methods(false).map(&:to_s)
     test_case_list = test_cases.join(',')
     opts.on('--test_case CODE', test_cases, {}, 'select a test_case',
-            "  (#{test_case_list})") do |v|
-      options['test_case'] = v
-    end
+            "  (#{test_case_list})") { |v| args['test_case'] = v }
     opts.on('-s', '--use_tls', 'require a secure connection?') do |v|
-      options['secure'] = v
+      args['secure'] = v
     end
     opts.on('-t', '--use_test_ca',
             'if secure, use the test certificate?') do |v|
-      options['use_test_ca'] = v
+      args['use_test_ca'] = v
     end
   end.parse!
-  _check_options(options)
+  _check_args(args)
 end
 
-def _check_options(opts)
-  %w(server_host server_port test_case).each do |arg|
-    if opts[arg].nil?
+def _check_args(args)
+  %w(host port test_case).each do |a|
+    if args[a].nil?
       fail(OptionParser::MissingArgument, "please specify --#{arg}")
     end
   end
-  if opts['server_host_override'].nil?
-    opts['server_host_override'] = opts['server_host']
+  if args['oauth_key_file'].nil? ^ args['oauth_scope'].nil?
+    fail(OptionParser::MissingArgument,
+         'please specify both of --service_account_key_file and --oauth_scope')
   end
-  opts
+  args
 end
 
 def main
-  opts = parse_options
-  stub = create_stub(opts['server_host'], opts['server_port'], opts['secure'],
-                     opts['server_host_override'], opts['use_test_ca'])
-  NamedTests.new(stub).method(opts['test_case']).call
+  opts = parse_args
+  stub = create_stub(opts)
+  NamedTests.new(stub, opts).method(opts['test_case']).call
 end
 
 main
diff --git a/src/ruby/bin/interop/test/cpp/interop/messages.rb b/src/ruby/bin/interop/test/cpp/interop/messages.rb
index 491608b..b86cd39 100644
--- a/src/ruby/bin/interop/test/cpp/interop/messages.rb
+++ b/src/ruby/bin/interop/test/cpp/interop/messages.rb
@@ -41,10 +41,13 @@
     optional :response_type, :enum, 1, "grpc.testing.PayloadType"
     optional :response_size, :int32, 2
     optional :payload, :message, 3, "grpc.testing.Payload"
+    optional :fill_username, :bool, 4
+    optional :fill_oauth_scope, :bool, 5
   end
   add_message "grpc.testing.SimpleResponse" do
     optional :payload, :message, 1, "grpc.testing.Payload"
-    optional :effective_gaia_user_id, :int64, 2
+    optional :username, :string, 2
+    optional :oauth_scope, :string, 3
   end
   add_message "grpc.testing.StreamingInputCallRequest" do
     optional :payload, :message, 1, "grpc.testing.Payload"
diff --git a/src/ruby/bin/math.rb b/src/ruby/bin/math.rb
old mode 100644
new mode 100755
diff --git a/src/ruby/bin/math_services.rb b/src/ruby/bin/math_services.rb
old mode 100644
new mode 100755
diff --git a/src/ruby/ext/grpc/rb_call.c b/src/ruby/ext/grpc/rb_call.c
index 1b6565f..5d72307 100644
--- a/src/ruby/ext/grpc/rb_call.c
+++ b/src/ruby/ext/grpc/rb_call.c
@@ -125,7 +125,7 @@
       md_obj_args[1] = rb_ary_entry(val, i);
       md_obj = rb_class_new_instance(2, md_obj_args, rb_cMetadata);
       md = grpc_rb_get_wrapped_metadata(md_obj);
-      err = grpc_call_add_metadata(call, md, NUM2UINT(flags));
+      err = grpc_call_add_metadata_old(call, md, NUM2UINT(flags));
       if (err != GRPC_CALL_OK) {
         rb_raise(rb_eCallError, "add metadata failed: %s (code=%d)",
                  grpc_call_error_detail_of(err), err);
@@ -136,7 +136,7 @@
     md_obj_args[1] = val;
     md_obj = rb_class_new_instance(2, md_obj_args, rb_cMetadata);
     md = grpc_rb_get_wrapped_metadata(md_obj);
-    err = grpc_call_add_metadata(call, md, NUM2UINT(flags));
+    err = grpc_call_add_metadata_old(call, md, NUM2UINT(flags));
     if (err != GRPC_CALL_OK) {
       rb_raise(rb_eCallError, "add metadata failed: %s (code=%d)",
                grpc_call_error_detail_of(err), err);
@@ -220,8 +220,8 @@
   }
   cq = grpc_rb_get_wrapped_completion_queue(cqueue);
   Data_Get_Struct(self, grpc_call, call);
-  err = grpc_call_invoke(call, cq, ROBJECT(metadata_read_tag),
-                         ROBJECT(finished_tag), NUM2UINT(flags));
+  err = grpc_call_invoke_old(call, cq, ROBJECT(metadata_read_tag),
+                             ROBJECT(finished_tag), NUM2UINT(flags));
   if (err != GRPC_CALL_OK) {
     rb_raise(rb_eCallError, "invoke failed: %s (code=%d)",
              grpc_call_error_detail_of(err), err);
@@ -242,7 +242,7 @@
   grpc_call *call = NULL;
   grpc_call_error err;
   Data_Get_Struct(self, grpc_call, call);
-  err = grpc_call_start_read(call, ROBJECT(tag));
+  err = grpc_call_start_read_old(call, ROBJECT(tag));
   if (err != GRPC_CALL_OK) {
     rb_raise(rb_eCallError, "start read failed: %s (code=%d)",
              grpc_call_error_detail_of(err), err);
@@ -330,7 +330,7 @@
   }
   bfr = grpc_rb_get_wrapped_byte_buffer(byte_buffer);
   Data_Get_Struct(self, grpc_call, call);
-  err = grpc_call_start_write(call, bfr, ROBJECT(tag), NUM2UINT(flags));
+  err = grpc_call_start_write_old(call, bfr, ROBJECT(tag), NUM2UINT(flags));
   if (err != GRPC_CALL_OK) {
     rb_raise(rb_eCallError, "start write failed: %s (code=%d)",
              grpc_call_error_detail_of(err), err);
@@ -358,8 +358,8 @@
   grpc_call *call = NULL;
   grpc_call_error err;
   Data_Get_Struct(self, grpc_call, call);
-  err = grpc_call_start_write_status(call, NUM2UINT(code),
-                                     StringValueCStr(status), ROBJECT(tag));
+  err = grpc_call_start_write_status_old(call, NUM2UINT(code),
+                                         StringValueCStr(status), ROBJECT(tag));
   if (err != GRPC_CALL_OK) {
     rb_raise(rb_eCallError, "start write status: %s (code=%d)",
              grpc_call_error_detail_of(err), err);
@@ -374,7 +374,7 @@
   grpc_call *call = NULL;
   grpc_call_error err;
   Data_Get_Struct(self, grpc_call, call);
-  err = grpc_call_writes_done(call, ROBJECT(tag));
+  err = grpc_call_writes_done_old(call, ROBJECT(tag));
   if (err != GRPC_CALL_OK) {
     rb_raise(rb_eCallError, "writes done: %s (code=%d)",
              grpc_call_error_detail_of(err), err);
@@ -405,7 +405,7 @@
     flags = UINT2NUM(0); /* Default to no flags */
   }
   Data_Get_Struct(self, grpc_call, call);
-  err = grpc_call_server_end_initial_metadata(call, NUM2UINT(flags));
+  err = grpc_call_server_end_initial_metadata_old(call, NUM2UINT(flags));
   if (err != GRPC_CALL_OK) {
     rb_raise(rb_eCallError, "end_initial_metadata failed: %s (code=%d)",
              grpc_call_error_detail_of(err), err);
@@ -430,7 +430,7 @@
   grpc_completion_queue *cq = grpc_rb_get_wrapped_completion_queue(cqueue);
   grpc_call_error err;
   Data_Get_Struct(self, grpc_call, call);
-  err = grpc_call_server_accept(call, cq, ROBJECT(finished_tag));
+  err = grpc_call_server_accept_old(call, cq, ROBJECT(finished_tag));
   if (err != GRPC_CALL_OK) {
     rb_raise(rb_eCallError, "server_accept failed: %s (code=%d)",
              grpc_call_error_detail_of(err), err);
diff --git a/src/ruby/ext/grpc/rb_channel.c b/src/ruby/ext/grpc/rb_channel.c
index c0187d2..7c98e66 100644
--- a/src/ruby/ext/grpc/rb_channel.c
+++ b/src/ruby/ext/grpc/rb_channel.c
@@ -192,9 +192,10 @@
     rb_raise(rb_eRuntimeError, "closed!");
   }
 
-  call = grpc_channel_create_call(ch, method_chars, host_chars,
-                                  grpc_rb_time_timeval(deadline,
-                                                       /* absolute time */ 0));
+  call =
+      grpc_channel_create_call_old(ch, method_chars, host_chars,
+                                   grpc_rb_time_timeval(deadline,
+                                                        /* absolute time */ 0));
   if (call == NULL) {
     rb_raise(rb_eRuntimeError, "cannot create call with method %s",
              method_chars);
diff --git a/src/ruby/ext/grpc/rb_server.c b/src/ruby/ext/grpc/rb_server.c
index 436d006..e68843e 100644
--- a/src/ruby/ext/grpc/rb_server.c
+++ b/src/ruby/ext/grpc/rb_server.c
@@ -175,7 +175,7 @@
   if (s->wrapped == NULL) {
     rb_raise(rb_eRuntimeError, "closed!");
   } else {
-    err = grpc_server_request_call(s->wrapped, ROBJECT(tag_new));
+    err = grpc_server_request_call_old(s->wrapped, ROBJECT(tag_new));
     if (err != GRPC_CALL_OK) {
       rb_raise(rb_eCallError, "server request failed: %s (code=%d)",
                grpc_call_error_detail_of(err), err);
diff --git a/src/ruby/grpc.gemspec b/src/ruby/grpc.gemspec
index ffd084d..c479cc9 100755
--- a/src/ruby/grpc.gemspec
+++ b/src/ruby/grpc.gemspec
@@ -1,3 +1,4 @@
+# -*- ruby -*-
 # encoding: utf-8
 $LOAD_PATH.push File.expand_path('../lib', __FILE__)
 require 'grpc/version'
@@ -19,11 +20,14 @@
   s.require_paths = ['lib']
   s.platform      = Gem::Platform::RUBY
 
-  s.add_dependency 'xray'
-  s.add_dependency 'logging', '~> 1.8'
+  s.add_dependency 'faraday', '~> 0.9'
   s.add_dependency 'google-protobuf', '~> 3.0.0alpha.1.1'
-  s.add_dependency 'signet', '~> 0.5.1'
+  s.add_dependency 'logging', '~> 1.8'
+  s.add_dependency 'jwt', '~> 1.2.1'
   s.add_dependency 'minitest', '~> 5.4'  # reqd for interop tests
+  s.add_dependency 'multi_json', '1.10.1'
+  s.add_dependency 'signet', '~> 0.6.0'
+  s.add_dependency 'xray', '~> 1.1'
 
   s.add_development_dependency 'bundler', '~> 1.7'
   s.add_development_dependency 'rake', '~> 10.0'
diff --git a/src/ruby/lib/grpc.rb b/src/ruby/lib/grpc.rb
index 81c67ec..758ac0c 100644
--- a/src/ruby/lib/grpc.rb
+++ b/src/ruby/lib/grpc.rb
@@ -27,6 +27,8 @@
 # (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 'grpc/auth/compute_engine.rb'
+require 'grpc/auth/service_account.rb'
 require 'grpc/errors'
 require 'grpc/grpc'
 require 'grpc/logconfig'
diff --git a/src/ruby/lib/grpc/auth/compute_engine.rb b/src/ruby/lib/grpc/auth/compute_engine.rb
new file mode 100644
index 0000000..9004bef
--- /dev/null
+++ b/src/ruby/lib/grpc/auth/compute_engine.rb
@@ -0,0 +1,69 @@
+# 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.
+
+require 'faraday'
+require 'grpc/auth/signet'
+
+module Google
+  module RPC
+    # Module Auth provides classes that provide Google-specific authentication
+    # used to access Google gRPC services.
+    module Auth
+      # Extends Signet::OAuth2::Client so that the auth token is obtained from
+      # the GCE metadata server.
+      class GCECredentials < Signet::OAuth2::Client
+        COMPUTE_AUTH_TOKEN_URI = 'http://metadata/computeMetadata/v1/'\
+                                 'instance/service-accounts/default/token'
+        COMPUTE_CHECK_URI = 'http://metadata.google.internal'
+
+        # Detect if this appear to be a GCE instance, by checking if metadata
+        # is available
+        def self.on_gce?(options = {})
+          c = options[:connection] || Faraday.default_connection
+          resp = c.get(COMPUTE_CHECK_URI)
+          return false unless resp.status == 200
+          return false unless resp.headers.key?('Metadata-Flavor')
+          return resp.headers['Metadata-Flavor'] == 'Google'
+        rescue Faraday::ConnectionFailed
+          return false
+        end
+
+        # Overrides the super class method to change how access tokens are
+        # fetched.
+        def fetch_access_token(options = {})
+          c = options[:connection] || Faraday.default_connection
+          c.headers = { 'Metadata-Flavor' => 'Google' }
+          resp = c.get(COMPUTE_AUTH_TOKEN_URI)
+          Signet::OAuth2.parse_credentials(resp.body,
+                                           resp.headers['content-type'])
+        end
+      end
+    end
+  end
+end
diff --git a/src/ruby/lib/grpc/auth/service_account.rb b/src/ruby/lib/grpc/auth/service_account.rb
new file mode 100644
index 0000000..35b5cbf
--- /dev/null
+++ b/src/ruby/lib/grpc/auth/service_account.rb
@@ -0,0 +1,68 @@
+# 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.
+
+require 'grpc/auth/signet'
+require 'multi_json'
+require 'openssl'
+
+# Reads the private key and client email fields from service account JSON key.
+def read_json_key(json_key_io)
+  json_key = MultiJson.load(json_key_io.read)
+  fail 'missing client_email' unless json_key.key?('client_email')
+  fail 'missing private_key' unless json_key.key?('private_key')
+  [json_key['private_key'], json_key['client_email']]
+end
+
+module Google
+  module RPC
+    # Module Auth provides classes that provide Google-specific authentication
+    # used to access Google gRPC services.
+    module Auth
+      # Authenticates requests using Google's Service Account credentials.
+      # (cf https://developers.google.com/accounts/docs/OAuth2ServiceAccount)
+      class ServiceAccountCredentials < Signet::OAuth2::Client
+        TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v3/token'
+        AUDIENCE = TOKEN_CRED_URI
+
+        # Initializes a ServiceAccountCredentials.
+        #
+        # @param scope [string|array] the scope(s) to access
+        # @param json_key_io [IO] an IO from which the JSON key can be read
+        def initialize(scope, json_key_io)
+          private_key, client_email = read_json_key(json_key_io)
+          super(token_credential_uri: TOKEN_CRED_URI,
+                audience: AUDIENCE,
+                scope: scope,
+                issuer: client_email,
+                signing_key: OpenSSL::PKey::RSA.new(private_key))
+        end
+      end
+    end
+  end
+end
diff --git a/src/ruby/lib/grpc/auth/signet.rb b/src/ruby/lib/grpc/auth/signet.rb
new file mode 100644
index 0000000..a8bce12
--- /dev/null
+++ b/src/ruby/lib/grpc/auth/signet.rb
@@ -0,0 +1,67 @@
+# 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.
+
+require 'signet/oauth_2/client'
+
+module Signet
+  # Signet::OAuth2 supports OAuth2 authentication.
+  module OAuth2
+    AUTH_METADATA_KEY = :Authorization
+    # Signet::OAuth2::Client creates an OAuth2 client
+    #
+    # Here client is re-opened to add the #apply and #apply! methods which
+    # update a hash map with the fetched authentication token
+    #
+    # Eventually, this change may be merged into signet itself, or some other
+    # package that provides Google-specific auth via signet, and this extension
+    # will be unnecessary.
+    class Client
+      # Updates a_hash updated with the authentication token
+      def apply!(a_hash, opts = {})
+        # fetch the access token there is currently not one, or if the client
+        # has expired
+        fetch_access_token!(opts) if access_token.nil? || expired?
+        a_hash[AUTH_METADATA_KEY] = "Bearer #{access_token}"
+      end
+
+      # Returns a clone of a_hash updated with the authentication token
+      def apply(a_hash, opts = {})
+        a_copy = a_hash.clone
+        apply!(a_copy, opts)
+        a_copy
+      end
+
+      # Returns a reference to the #apply method, suitable for passing as
+      # a closure
+      def updater_proc
+        lambda(&method(:apply))
+      end
+    end
+  end
+end
diff --git a/src/ruby/spec/auth/apply_auth_examples.rb b/src/ruby/spec/auth/apply_auth_examples.rb
new file mode 100644
index 0000000..09b3930
--- /dev/null
+++ b/src/ruby/spec/auth/apply_auth_examples.rb
@@ -0,0 +1,163 @@
+# 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.
+
+spec_dir = File.expand_path(File.join(File.dirname(__FILE__)))
+$LOAD_PATH.unshift(spec_dir)
+$LOAD_PATH.uniq!
+
+require 'faraday'
+require 'spec_helper'
+
+def build_json_response(payload)
+  [200,
+   { 'Content-Type' => 'application/json; charset=utf-8' },
+   MultiJson.dump(payload)]
+end
+
+WANTED_AUTH_KEY = :Authorization
+
+shared_examples 'apply/apply! are OK' do
+  # tests that use these examples need to define
+  #
+  # @client which should be an auth client
+  #
+  # @make_auth_stubs, which should stub out the expected http behaviour of the
+  # auth client
+  describe '#fetch_access_token' do
+    it 'should set access_token to the fetched value' do
+      token = '1/abcdef1234567890'
+      stubs = make_auth_stubs with_access_token: token
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+
+      @client.fetch_access_token!(connection: c)
+      expect(@client.access_token).to eq(token)
+      stubs.verify_stubbed_calls
+    end
+  end
+
+  describe '#apply!' do
+    it 'should update the target hash with fetched access token' do
+      token = '1/abcdef1234567890'
+      stubs = make_auth_stubs with_access_token: token
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+
+      md = { foo: 'bar' }
+      @client.apply!(md, connection: c)
+      want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
+      expect(md).to eq(want)
+      stubs.verify_stubbed_calls
+    end
+  end
+
+  describe 'updater_proc' do
+    it 'should provide a proc that updates a hash with the access token' do
+      token = '1/abcdef1234567890'
+      stubs = make_auth_stubs with_access_token: token
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+
+      md = { foo: 'bar' }
+      the_proc = @client.updater_proc
+      got = the_proc.call(md, connection: c)
+      want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
+      expect(got).to eq(want)
+      stubs.verify_stubbed_calls
+    end
+  end
+
+  describe '#apply' do
+    it 'should not update the original hash with the access token' do
+      token = '1/abcdef1234567890'
+      stubs = make_auth_stubs with_access_token: token
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+
+      md = { foo: 'bar' }
+      @client.apply(md, connection: c)
+      want = { foo: 'bar' }
+      expect(md).to eq(want)
+      stubs.verify_stubbed_calls
+    end
+
+    it 'should add the token to the returned hash' do
+      token = '1/abcdef1234567890'
+      stubs = make_auth_stubs with_access_token: token
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+
+      md = { foo: 'bar' }
+      got = @client.apply(md, connection: c)
+      want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
+      expect(got).to eq(want)
+      stubs.verify_stubbed_calls
+    end
+
+    it 'should not fetch a new token if the current is not expired' do
+      token = '1/abcdef1234567890'
+      stubs = make_auth_stubs with_access_token: token
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+
+      n = 5 # arbitrary
+      n.times do |_t|
+        md = { foo: 'bar' }
+        got = @client.apply(md, connection: c)
+        want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
+        expect(got).to eq(want)
+      end
+      stubs.verify_stubbed_calls
+    end
+
+    it 'should fetch a new token if the current one is expired' do
+      token_1 = '1/abcdef1234567890'
+      token_2 = '2/abcdef1234567890'
+
+      [token_1, token_2].each do |t|
+        stubs = make_auth_stubs with_access_token: t
+        c = Faraday.new do |b|
+          b.adapter(:test, stubs)
+        end
+        md = { foo: 'bar' }
+        got = @client.apply(md, connection: c)
+        want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{t}" }
+        expect(got).to eq(want)
+        stubs.verify_stubbed_calls
+        @client.expires_at -= 3601 # default is to expire in 1hr
+      end
+    end
+  end
+end
diff --git a/src/ruby/spec/auth/compute_engine_spec.rb b/src/ruby/spec/auth/compute_engine_spec.rb
new file mode 100644
index 0000000..9e0b466
--- /dev/null
+++ b/src/ruby/spec/auth/compute_engine_spec.rb
@@ -0,0 +1,108 @@
+# 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.
+
+spec_dir = File.expand_path(File.join(File.dirname(__FILE__)))
+$LOAD_PATH.unshift(spec_dir)
+$LOAD_PATH.uniq!
+
+require 'apply_auth_examples'
+require 'faraday'
+require 'grpc/auth/compute_engine'
+require 'spec_helper'
+
+describe Google::RPC::Auth::GCECredentials do
+  MD_URI = '/computeMetadata/v1/instance/service-accounts/default/token'
+  GCECredentials = Google::RPC::Auth::GCECredentials
+
+  before(:example) do
+    @client = GCECredentials.new
+  end
+
+  def make_auth_stubs(with_access_token: '')
+    Faraday::Adapter::Test::Stubs.new do |stub|
+      stub.get(MD_URI) do |env|
+        headers = env[:request_headers]
+        expect(headers['Metadata-Flavor']).to eq('Google')
+        build_json_response(
+            'access_token' => with_access_token,
+            'token_type' => 'Bearer',
+            'expires_in' => 3600)
+      end
+    end
+  end
+
+  it_behaves_like 'apply/apply! are OK'
+
+  describe '#on_gce?' do
+    it 'should be true when Metadata-Flavor is Google' do
+      stubs = Faraday::Adapter::Test::Stubs.new do |stub|
+        stub.get('/') do |_env|
+          [200,
+           { 'Metadata-Flavor' => 'Google' },
+           '']
+        end
+      end
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+      expect(GCECredentials.on_gce?(connection: c)).to eq(true)
+      stubs.verify_stubbed_calls
+    end
+
+    it 'should be false when Metadata-Flavor is not Google' do
+      stubs = Faraday::Adapter::Test::Stubs.new do |stub|
+        stub.get('/') do |_env|
+          [200,
+           { 'Metadata-Flavor' => 'NotGoogle' },
+           '']
+        end
+      end
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+      expect(GCECredentials.on_gce?(connection: c)).to eq(false)
+      stubs.verify_stubbed_calls
+    end
+
+    it 'should be false if the response is not 200' do
+      stubs = Faraday::Adapter::Test::Stubs.new do |stub|
+        stub.get('/') do |_env|
+          [404,
+           { 'Metadata-Flavor' => 'Google' },
+           '']
+        end
+      end
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+      expect(GCECredentials.on_gce?(connection: c)).to eq(false)
+      stubs.verify_stubbed_calls
+    end
+  end
+end
diff --git a/src/ruby/spec/auth/service_account_spec.rb b/src/ruby/spec/auth/service_account_spec.rb
new file mode 100644
index 0000000..cbc6a73
--- /dev/null
+++ b/src/ruby/spec/auth/service_account_spec.rb
@@ -0,0 +1,75 @@
+# 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.
+
+spec_dir = File.expand_path(File.join(File.dirname(__FILE__)))
+$LOAD_PATH.unshift(spec_dir)
+$LOAD_PATH.uniq!
+
+require 'apply_auth_examples'
+require 'grpc/auth/service_account'
+require 'jwt'
+require 'multi_json'
+require 'openssl'
+require 'spec_helper'
+
+describe Google::RPC::Auth::ServiceAccountCredentials do
+  before(:example) do
+    @key = OpenSSL::PKey::RSA.new(2048)
+    cred_json = {
+      private_key_id: 'a_private_key_id',
+      private_key: @key.to_pem,
+      client_email: 'app@developer.gserviceaccount.com',
+      client_id: 'app.apps.googleusercontent.com',
+      type: 'service_account'
+    }
+    cred_json_text = MultiJson.dump(cred_json)
+    @client = Google::RPC::Auth::ServiceAccountCredentials.new(
+        'https://www.googleapis.com/auth/userinfo.profile',
+        StringIO.new(cred_json_text))
+  end
+
+  def make_auth_stubs(with_access_token: '')
+    Faraday::Adapter::Test::Stubs.new do |stub|
+      stub.post('/oauth2/v3/token') do |env|
+        params = Addressable::URI.form_unencode(env[:body])
+        _claim, _header = JWT.decode(params.assoc('assertion').last,
+                                     @key.public_key)
+        want = ['grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer']
+        expect(params.assoc('grant_type')).to eq(want)
+        build_json_response(
+          'access_token' => with_access_token,
+          'token_type' => 'Bearer',
+          'expires_in' => 3600
+        )
+      end
+    end
+  end
+
+  it_behaves_like 'apply/apply! are OK'
+end
diff --git a/src/ruby/spec/auth/signet_spec.rb b/src/ruby/spec/auth/signet_spec.rb
new file mode 100644
index 0000000..1712edf
--- /dev/null
+++ b/src/ruby/spec/auth/signet_spec.rb
@@ -0,0 +1,70 @@
+# 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.
+
+spec_dir = File.expand_path(File.join(File.dirname(__FILE__)))
+$LOAD_PATH.unshift(spec_dir)
+$LOAD_PATH.uniq!
+
+require 'apply_auth_examples'
+require 'grpc/auth/signet'
+require 'jwt'
+require 'openssl'
+require 'spec_helper'
+
+describe Signet::OAuth2::Client do
+  before(:example) do
+    @key = OpenSSL::PKey::RSA.new(2048)
+    @client = Signet::OAuth2::Client.new(
+        token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
+        scope: 'https://www.googleapis.com/auth/userinfo.profile',
+        issuer: 'app@example.com',
+        audience: 'https://accounts.google.com/o/oauth2/token',
+        signing_key: @key
+      )
+  end
+
+  def make_auth_stubs(with_access_token: '')
+    Faraday::Adapter::Test::Stubs.new do |stub|
+      stub.post('/o/oauth2/token') do |env|
+        params = Addressable::URI.form_unencode(env[:body])
+        _claim, _header = JWT.decode(params.assoc('assertion').last,
+                                     @key.public_key)
+        want = ['grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer']
+        expect(params.assoc('grant_type')).to eq(want)
+        build_json_response(
+          'access_token' => with_access_token,
+          'token_type' => 'Bearer',
+          'expires_in' => 3600
+        )
+      end
+    end
+  end
+
+  it_behaves_like 'apply/apply! are OK'
+end
diff --git a/src/ruby/spec/channel_spec.rb b/src/ruby/spec/channel_spec.rb
index 189d1c6..82c7915 100644
--- a/src/ruby/spec/channel_spec.rb
+++ b/src/ruby/spec/channel_spec.rb
@@ -29,8 +29,6 @@
 
 require 'grpc'
 
-FAKE_HOST='localhost:0'
-
 def load_test_certs
   test_root = File.join(File.dirname(__FILE__), 'testdata')
   files = ['ca.pem', 'server1.key', 'server1.pem']
@@ -38,6 +36,8 @@
 end
 
 describe GRPC::Core::Channel do
+  FAKE_HOST = 'localhost:0'
+
   def create_test_cert
     GRPC::Core::Credentials.new(load_test_certs[0])
   end
diff --git a/src/ruby/spec/generic/active_call_spec.rb b/src/ruby/spec/generic/active_call_spec.rb
index e81b216..599e68b 100644
--- a/src/ruby/spec/generic/active_call_spec.rb
+++ b/src/ruby/spec/generic/active_call_spec.rb
@@ -371,6 +371,6 @@
   end
 
   def deadline
-    Time.now + 0.25  # in 0.25 seconds; arbitrary
+    Time.now + 1  # in 1 second; arbitrary
   end
 end
diff --git a/src/ruby/spec/spec_helper.rb b/src/ruby/spec/spec_helper.rb
index 3322674..ea0a256 100644
--- a/src/ruby/spec/spec_helper.rb
+++ b/src/ruby/spec/spec_helper.rb
@@ -27,10 +27,22 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+spec_dir = File.expand_path(File.dirname(__FILE__))
+root_dir = File.expand_path(File.join(spec_dir, '..'))
+lib_dir = File.expand_path(File.join(root_dir, 'lib'))
+
+$LOAD_PATH.unshift(spec_dir)
+$LOAD_PATH.unshift(lib_dir)
+$LOAD_PATH.uniq!
+
+require 'faraday'
 require 'rspec'
 require 'logging'
 require 'rspec/logging_helper'
 
+# Allow Faraday to support test stubs
+Faraday::Adapter.load_middleware(:test)
+
 # Configure RSpec to capture log messages for each test. The output from the
 # logs will be stored in the @log_output variable. It is a StringIO instance.
 RSpec.configure do |config|
diff --git a/templates/Makefile.template b/templates/Makefile.template
index 142d188..c34949c 100644
--- a/templates/Makefile.template
+++ b/templates/Makefile.template
@@ -206,11 +206,13 @@
 ZLIB_CHECK_CMD = $(CC) $(CFLAGS) $(CPPFLAGS) -o /dev/null test/build/zlib.c -lz $(LDFLAGS)
 PERFTOOLS_CHECK_CMD = $(CC) $(CFLAGS) $(CPPFLAGS) -o /dev/null test/build/perftools.c -lprofiler $(LDFLAGS)
 
+ifndef REQUIRE_CUSTOM_LIBRARIES_$(CONFIG)
 HAS_SYSTEM_PERFTOOLS = $(shell $(PERFTOOLS_CHECK_CMD) 2> /dev/null && echo true || echo false)
 ifeq ($(HAS_SYSTEM_PERFTOOLS),true)
 DEFINES += GRPC_HAVE_PERFTOOLS
 LIBS += profiler
 endif
+endif
 
 ifndef REQUIRE_CUSTOM_LIBRARIES_$(CONFIG)
 HAS_SYSTEM_OPENSSL_ALPN = $(shell $(OPENSSL_ALPN_CHECK_CMD) 2> /dev/null && echo true || echo false)
diff --git a/test/core/echo/client.c b/test/core/echo/client.c
index bb478c4..5652fd9 100644
--- a/test/core/echo/client.c
+++ b/test/core/echo/client.c
@@ -52,7 +52,7 @@
   for (i = 0; i < length; i++)
     GPR_SLICE_START_PTR(slice)[i] = (first + i) % 256;
   byte_buffer = grpc_byte_buffer_create(&slice, 1);
-  GPR_ASSERT(grpc_call_start_write(call, byte_buffer, (void *)1, 0) ==
+  GPR_ASSERT(grpc_call_start_write_old(call, byte_buffer, (void *)1, 0) ==
              GRPC_CALL_OK);
   gpr_slice_unref(slice);
   grpc_byte_buffer_destroy(byte_buffer);
@@ -78,15 +78,15 @@
 
   GPR_ASSERT(argc == 2);
   channel = grpc_channel_create(argv[1], NULL);
-  call = grpc_channel_create_call(channel, "/foo", "localhost",
-                                  gpr_time_add(gpr_time_from_seconds(5),
-                                               gpr_now()));
-  GPR_ASSERT(grpc_call_invoke(call, cq, (void *)1, (void *)1, 0) ==
+  call = grpc_channel_create_call_old(
+      channel, "/foo", "localhost",
+      gpr_time_add(gpr_time_from_seconds(5), gpr_now()));
+  GPR_ASSERT(grpc_call_invoke_old(call, cq, (void *)1, (void *)1, 0) ==
              GRPC_CALL_OK);
 
   start_write_next_slice(call, bytes_written, WRITE_SLICE_LENGTH);
   bytes_written += WRITE_SLICE_LENGTH;
-  GPR_ASSERT(grpc_call_start_read(call, (void *)1) == GRPC_CALL_OK);
+  GPR_ASSERT(grpc_call_start_read_old(call, (void *)1) == GRPC_CALL_OK);
   waiting_finishes = 2;
   while (waiting_finishes) {
     ev = grpc_completion_queue_next(cq, gpr_inf_future);
@@ -96,7 +96,8 @@
           start_write_next_slice(call, bytes_written, WRITE_SLICE_LENGTH);
           bytes_written += WRITE_SLICE_LENGTH;
         } else {
-          GPR_ASSERT(grpc_call_writes_done(call, (void *)1) == GRPC_CALL_OK);
+          GPR_ASSERT(grpc_call_writes_done_old(call, (void *)1) ==
+                     GRPC_CALL_OK);
         }
         break;
       case GRPC_CLIENT_METADATA_READ:
@@ -112,7 +113,7 @@
         }
         grpc_byte_buffer_reader_destroy(bb_reader);
         if (bytes_read < TOTAL_BYTES) {
-          GPR_ASSERT(grpc_call_start_read(call, (void *)1) == GRPC_CALL_OK);
+          GPR_ASSERT(grpc_call_start_read_old(call, (void *)1) == GRPC_CALL_OK);
         }
         break;
       case GRPC_FINISHED:
diff --git a/test/core/echo/echo_test.c b/test/core/echo/echo_test.c
index 83b83ab..5450dfb 100644
--- a/test/core/echo/echo_test.c
+++ b/test/core/echo/echo_test.c
@@ -31,7 +31,10 @@
  *
  */
 
+#ifndef _POSIX_SOURCE
 #define _POSIX_SOURCE
+#endif
+
 #include <unistd.h>
 #include <assert.h>
 #include <stdio.h>
diff --git a/test/core/echo/server.c b/test/core/echo/server.c
index 2764a9e..6e494d5 100644
--- a/test/core/echo/server.c
+++ b/test/core/echo/server.c
@@ -64,7 +64,7 @@
   call_state *tag = gpr_malloc(sizeof(*tag));
   gpr_ref_init(&tag->pending_ops, 2);
   tag->bytes_read = 0;
-  grpc_server_request_call(server, tag);
+  grpc_server_request_call_old(server, tag);
 }
 
 static void assert_read_ok(call_state *s, grpc_byte_buffer *b) {
@@ -173,10 +173,10 @@
       case GRPC_SERVER_RPC_NEW:
         if (ev->call != NULL) {
           /* initial ops are already started in request_call */
-          grpc_call_server_accept(ev->call, cq, s);
-          grpc_call_server_end_initial_metadata(ev->call,
-                                                GRPC_WRITE_BUFFER_HINT);
-          GPR_ASSERT(grpc_call_start_read(ev->call, s) == GRPC_CALL_OK);
+          grpc_call_server_accept_old(ev->call, cq, s);
+          grpc_call_server_end_initial_metadata_old(ev->call,
+                                                    GRPC_WRITE_BUFFER_HINT);
+          GPR_ASSERT(grpc_call_start_read_old(ev->call, s) == GRPC_CALL_OK);
           request_call();
         } else {
           GPR_ASSERT(shutdown_started);
@@ -185,17 +185,17 @@
         break;
       case GRPC_WRITE_ACCEPTED:
         GPR_ASSERT(ev->data.write_accepted == GRPC_OP_OK);
-        GPR_ASSERT(grpc_call_start_read(ev->call, s) == GRPC_CALL_OK);
+        GPR_ASSERT(grpc_call_start_read_old(ev->call, s) == GRPC_CALL_OK);
         break;
       case GRPC_READ:
         if (ev->data.read) {
           assert_read_ok(ev->tag, ev->data.read);
-          GPR_ASSERT(grpc_call_start_write(ev->call, ev->data.read, s,
-                                           GRPC_WRITE_BUFFER_HINT) ==
+          GPR_ASSERT(grpc_call_start_write_old(ev->call, ev->data.read, s,
+                                               GRPC_WRITE_BUFFER_HINT) ==
                      GRPC_CALL_OK);
         } else {
-          GPR_ASSERT(grpc_call_start_write_status(ev->call, GRPC_STATUS_OK,
-                                                  NULL, s) == GRPC_CALL_OK);
+          GPR_ASSERT(grpc_call_start_write_status_old(ev->call, GRPC_STATUS_OK,
+                                                      NULL, s) == GRPC_CALL_OK);
         }
         break;
       case GRPC_FINISH_ACCEPTED:
diff --git a/test/core/end2end/README b/test/core/end2end/README
index 31598b6..59daec4 100644
--- a/test/core/end2end/README
+++ b/test/core/end2end/README
@@ -5,6 +5,3 @@
 - add the code to the relevant directory
 - update gen_build_json.py to reflect the change
 - regenerate projects
-// MOE:begin_strip
-- update net/grpc/c/BUILD to reflect the change
-// MOE:end_strip
\ No newline at end of file
diff --git a/test/core/end2end/cq_verifier.c b/test/core/end2end/cq_verifier.c
index 287f83e..904ed77 100644
--- a/test/core/end2end/cq_verifier.c
+++ b/test/core/end2end/cq_verifier.c
@@ -70,6 +70,7 @@
   union {
     grpc_op_error finish_accepted;
     grpc_op_error write_accepted;
+    grpc_op_error ioreq;
     struct {
       const char *method;
       const char *host;
@@ -180,9 +181,6 @@
     case GRPC_WRITE_ACCEPTED:
       GPR_ASSERT(e->data.write_accepted == ev->data.write_accepted);
       break;
-    case GRPC_INVOKE_ACCEPTED:
-      abort();
-      break;
     case GRPC_SERVER_RPC_NEW:
       GPR_ASSERT(string_equivalent(e->data.server_rpc_new.method,
                                    ev->data.server_rpc_new.method));
@@ -222,6 +220,9 @@
         GPR_ASSERT(ev->data.read == NULL);
       }
       break;
+    case GRPC_IOREQ:
+      GPR_ASSERT(e->data.ioreq == ev->data.ioreq);
+      break;
     case GRPC_SERVER_SHUTDOWN:
       break;
     case GRPC_COMPLETION_DO_NOT_USE:
@@ -242,7 +243,9 @@
       gpr_asprintf(&tmp, "%c%s:%s", i ? ',' : '{', md->keys[i], md->values[i]);
       gpr_strvec_add(buf, tmp);
     }
-    gpr_strvec_add(buf, gpr_strdup("}"));
+    if (md->count) {
+      gpr_strvec_add(buf, gpr_strdup("}"));
+    }
   }
 }
 
@@ -261,8 +264,9 @@
                      e->data.write_accepted);
       gpr_strvec_add(buf, tmp);
       break;
-    case GRPC_INVOKE_ACCEPTED:
-      gpr_strvec_add(buf, gpr_strdup("GRPC_INVOKE_ACCEPTED"));
+    case GRPC_IOREQ:
+      gpr_asprintf(&tmp, "GRPC_IOREQ result=%d", e->data.ioreq);
+      gpr_strvec_add(buf, tmp);
       break;
     case GRPC_SERVER_RPC_NEW:
       timeout = gpr_time_sub(e->data.server_rpc_new.deadline, gpr_now());
diff --git a/test/core/end2end/dualstack_socket_test.c b/test/core/end2end/dualstack_socket_test.c
index 6219f57..9d893f6 100644
--- a/test/core/end2end/dualstack_socket_test.c
+++ b/test/core/end2end/dualstack_socket_test.c
@@ -112,35 +112,36 @@
   }
 
   /* Send a trivial request. */
-  c = grpc_channel_create_call(client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(client, "/foo", "test.google.com", deadline);
   GPR_ASSERT(c);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_invoke(c, client_cq, tag(2), tag(3), 0));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(4)));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_invoke_old(c, client_cq, tag(2), tag(3), 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(4)));
   if (expect_ok) {
     /* Check for a successful request. */
     cq_expect_finish_accepted(v_client, tag(4), GRPC_OP_OK);
     cq_verify(v_client);
 
-    GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(server, tag(100)));
+    GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(server, tag(100)));
     cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                              deadline, NULL);
     cq_verify(v_server);
 
-    GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, server_cq, tag(102)));
-    GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+    GPR_ASSERT(GRPC_CALL_OK ==
+               grpc_call_server_accept_old(s, server_cq, tag(102)));
+    GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
     cq_expect_client_metadata_read(v_client, tag(2), NULL);
     cq_verify(v_client);
 
     GPR_ASSERT(GRPC_CALL_OK ==
-               grpc_call_start_write_status(s, GRPC_STATUS_UNIMPLEMENTED, "xyz",
-                                            tag(5)));
+               grpc_call_start_write_status_old(s, GRPC_STATUS_UNIMPLEMENTED,
+                                                "xyz", tag(5)));
     cq_expect_finished_with_status(v_client, tag(3), GRPC_STATUS_UNIMPLEMENTED,
                                    "xyz", NULL);
     cq_verify(v_client);
 
     cq_expect_finish_accepted(v_server, tag(5), GRPC_OP_OK);
-    cq_verify(v_server);
     cq_expect_finished(v_server, tag(102), NULL);
     cq_verify(v_server);
 
diff --git a/test/core/end2end/fixtures/chttp2_simple_ssl_fullstack.c b/test/core/end2end/fixtures/chttp2_simple_ssl_fullstack.c
index a70819e..149ac8c 100644
--- a/test/core/end2end/fixtures/chttp2_simple_ssl_fullstack.c
+++ b/test/core/end2end/fixtures/chttp2_simple_ssl_fullstack.c
@@ -39,6 +39,9 @@
 #include "src/core/channel/channel_args.h"
 #include "src/core/security/credentials.h"
 #include "src/core/security/security_context.h"
+#include "src/core/support/env.h"
+#include "src/core/support/file.h"
+#include "src/core/support/string.h"
 #include <grpc/support/alloc.h>
 #include <grpc/support/host_port.h>
 #include <grpc/support/log.h>
@@ -99,7 +102,7 @@
 static void chttp2_init_client_simple_ssl_secure_fullstack(
     grpc_end2end_test_fixture *f, grpc_channel_args *client_args) {
   grpc_credentials *ssl_creds =
-      grpc_ssl_credentials_create(test_root_cert, NULL);
+      grpc_ssl_credentials_create(NULL, NULL);
   grpc_arg ssl_name_override = {GRPC_ARG_STRING,
                                 GRPC_SSL_TARGET_NAME_OVERRIDE_ARG,
                                 {"foo.test.google.com"}};
@@ -129,8 +132,20 @@
 
 int main(int argc, char **argv) {
   size_t i;
+  FILE *roots_file;
+  size_t roots_size = strlen(test_root_cert);
+  char *roots_filename;
+
   grpc_test_init(argc, argv);
 
+  /* Set the SSL roots env var. */
+  roots_file = gpr_tmpfile("chttp2_simple_ssl_fullstack_test", &roots_filename);
+  GPR_ASSERT(roots_filename != NULL);
+  GPR_ASSERT(roots_file != NULL);
+  GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size);
+  fclose(roots_file);
+  gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_filename);
+
   grpc_init();
 
   for (i = 0; i < sizeof(configs) / sizeof(*configs); i++) {
@@ -139,5 +154,9 @@
 
   grpc_shutdown();
 
+  /* Cleanup. */
+  remove(roots_filename);
+  gpr_free(roots_filename);
+
   return 0;
 }
diff --git a/test/core/end2end/gen_build_json.py b/test/core/end2end/gen_build_json.py
index 2c4368f..e28dbdb 100755
--- a/test/core/end2end/gen_build_json.py
+++ b/test/core/end2end/gen_build_json.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python2.7
 
 """Generates the appropriate build.json data for all the end2end tests."""
 
diff --git a/test/core/end2end/no_server_test.c b/test/core/end2end/no_server_test.c
index 389a642..85d9533 100644
--- a/test/core/end2end/no_server_test.c
+++ b/test/core/end2end/no_server_test.c
@@ -56,8 +56,8 @@
 
   /* create a call, channel to a non existant server */
   chan = grpc_channel_create("nonexistant:54321", NULL);
-  call = grpc_channel_create_call(chan, "/foo", "nonexistant", deadline);
-  GPR_ASSERT(grpc_call_invoke(call, cq, tag(2), tag(3), 0) == GRPC_CALL_OK);
+  call = grpc_channel_create_call_old(chan, "/foo", "nonexistant", deadline);
+  GPR_ASSERT(grpc_call_invoke_old(call, cq, tag(2), tag(3), 0) == GRPC_CALL_OK);
   /* verify that all tags get completed */
   cq_expect_client_metadata_read(cqv, tag(2), NULL);
   cq_expect_finished_with_status(cqv, tag(3), GRPC_STATUS_DEADLINE_EXCEEDED,
diff --git a/test/core/end2end/tests/cancel_after_accept.c b/test/core/end2end/tests/cancel_after_accept.c
index 05a2dc8..f9bf9fab 100644
--- a/test/core/end2end/tests/cancel_after_accept.c
+++ b/test/core/end2end/tests/cancel_after_accept.c
@@ -113,19 +113,21 @@
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, NULL);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_server_accept_old(s, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
diff --git a/test/core/end2end/tests/cancel_after_accept_and_writes_closed.c b/test/core/end2end/tests/cancel_after_accept_and_writes_closed.c
index db245a3..b8a1438 100644
--- a/test/core/end2end/tests/cancel_after_accept_and_writes_closed.c
+++ b/test/core/end2end/tests/cancel_after_accept_and_writes_closed.c
@@ -113,27 +113,29 @@
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, NULL);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_server_accept_old(s, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(4)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(4)));
   cq_expect_finish_accepted(v_client, tag(4), GRPC_OP_OK);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(s, tag(101)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(s, tag(101)));
   cq_expect_empty_read(v_server, tag(101));
   cq_verify(v_server);
 
diff --git a/test/core/end2end/tests/cancel_after_invoke.c b/test/core/end2end/tests/cancel_after_invoke.c
index 5dfb3f7..8b28223 100644
--- a/test/core/end2end/tests/cancel_after_invoke.c
+++ b/test/core/end2end/tests/cancel_after_invoke.c
@@ -111,11 +111,12 @@
   gpr_timespec deadline = five_seconds_time();
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
   GPR_ASSERT(GRPC_CALL_OK == mode.initiate_cancel(c));
 
diff --git a/test/core/end2end/tests/cancel_before_invoke.c b/test/core/end2end/tests/cancel_before_invoke.c
index ac81648..5851277 100644
--- a/test/core/end2end/tests/cancel_before_invoke.c
+++ b/test/core/end2end/tests/cancel_before_invoke.c
@@ -109,13 +109,14 @@
   gpr_timespec deadline = five_seconds_time();
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK == grpc_call_cancel(c));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_expect_finished_with_status(v_client, tag(3), GRPC_STATUS_CANCELLED, NULL,
                                  NULL);
diff --git a/test/core/end2end/tests/cancel_in_a_vacuum.c b/test/core/end2end/tests/cancel_in_a_vacuum.c
index 5257ece..6b5194f 100644
--- a/test/core/end2end/tests/cancel_in_a_vacuum.c
+++ b/test/core/end2end/tests/cancel_in_a_vacuum.c
@@ -109,7 +109,8 @@
   gpr_timespec deadline = five_seconds_time();
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK == mode.initiate_cancel(c));
diff --git a/test/core/end2end/tests/census_simple_request.c b/test/core/end2end/tests/census_simple_request.c
index 86cef43..4cbaa65 100644
--- a/test/core/end2end/tests/census_simple_request.c
+++ b/test/core/end2end/tests/census_simple_request.c
@@ -106,34 +106,35 @@
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
   tag(1);
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(4)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(4)));
   cq_expect_finish_accepted(v_client, tag(4), GRPC_OP_OK);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, NULL);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_server_accept_old(s, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(5)));
   cq_expect_finished_with_status(v_client, tag(3), GRPC_STATUS_UNIMPLEMENTED,
                                  "xyz", NULL);
   cq_verify(v_client);
 
   cq_expect_finish_accepted(v_server, tag(5), GRPC_OP_OK);
-  cq_verify(v_server);
   cq_expect_finished(v_server, tag(102), NULL);
   cq_verify(v_server);
   grpc_call_destroy(c);
diff --git a/test/core/end2end/tests/disappearing_server.c b/test/core/end2end/tests/disappearing_server.c
index 036fdc2..9b2f168 100644
--- a/test/core/end2end/tests/disappearing_server.c
+++ b/test/core/end2end/tests/disappearing_server.c
@@ -97,24 +97,25 @@
   grpc_call *s;
   gpr_timespec deadline = five_seconds_time();
 
-  c = grpc_channel_create_call(f->client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f->client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f->client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f->client_cq, tag(2), tag(3), 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(4)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(4)));
   cq_expect_finish_accepted(v_client, tag(4), GRPC_OP_OK);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f->server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f->server, tag(100)));
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, NULL);
   cq_verify(v_server);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_server_accept(s, f->server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+             grpc_call_server_accept_old(s, f->server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
@@ -122,7 +123,7 @@
      - and still complete the request */
   grpc_server_shutdown(f->server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(5)));
   cq_expect_finished_with_status(v_client, tag(3), GRPC_STATUS_UNIMPLEMENTED,
                                  "xyz", NULL);
diff --git a/test/core/end2end/tests/early_server_shutdown_finishes_inflight_calls.c b/test/core/end2end/tests/early_server_shutdown_finishes_inflight_calls.c
index 66e3c44..a9d34e2 100644
--- a/test/core/end2end/tests/early_server_shutdown_finishes_inflight_calls.c
+++ b/test/core/end2end/tests/early_server_shutdown_finishes_inflight_calls.c
@@ -111,23 +111,25 @@
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(4)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(4)));
   cq_expect_finish_accepted(v_client, tag(4), GRPC_OP_OK);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, NULL);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_server_accept_old(s, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
diff --git a/test/core/end2end/tests/early_server_shutdown_finishes_tags.c b/test/core/end2end/tests/early_server_shutdown_finishes_tags.c
index 88f735c..123c8bc 100644
--- a/test/core/end2end/tests/early_server_shutdown_finishes_tags.c
+++ b/test/core/end2end/tests/early_server_shutdown_finishes_tags.c
@@ -110,7 +110,7 @@
 
   /* upon shutdown, the server should finish all requested calls indicating
      no new call */
-  grpc_server_request_call(f.server, tag(1000));
+  grpc_server_request_call_old(f.server, tag(1000));
   grpc_server_shutdown(f.server);
   cq_expect_server_rpc_new(v_server, &s, tag(1000), NULL, NULL, gpr_inf_past,
                            NULL);
diff --git a/test/core/end2end/tests/graceful_server_shutdown.c b/test/core/end2end/tests/graceful_server_shutdown.c
index d9c9dbb..dcd6192 100644
--- a/test/core/end2end/tests/graceful_server_shutdown.c
+++ b/test/core/end2end/tests/graceful_server_shutdown.c
@@ -110,23 +110,25 @@
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(4)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(4)));
   cq_expect_finish_accepted(v_client, tag(4), GRPC_OP_OK);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, NULL);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_server_accept_old(s, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
@@ -134,7 +136,7 @@
   grpc_server_shutdown_and_notify(f.server, tag(0xdead));
   cq_verify_empty(v_server);
 
-  grpc_call_start_write_status(s, GRPC_STATUS_OK, NULL, tag(103));
+  grpc_call_start_write_status_old(s, GRPC_STATUS_OK, NULL, tag(103));
   grpc_call_destroy(s);
   cq_expect_finish_accepted(v_server, tag(103), GRPC_OP_OK);
   cq_expect_finished(v_server, tag(102), NULL);
diff --git a/test/core/end2end/tests/invoke_large_request.c b/test/core/end2end/tests/invoke_large_request.c
index f187ece..7774fe4 100644
--- a/test/core/end2end/tests/invoke_large_request.c
+++ b/test/core/end2end/tests/invoke_large_request.c
@@ -120,16 +120,17 @@
   /* byte buffer holds the slice, we can unref it already */
   gpr_slice_unref(request_payload_slice);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write(c, request_payload, tag(4), 0));
+             grpc_call_start_write_old(c, request_payload, tag(4), 0));
   /* destroy byte buffer early to ensure async code keeps track of its contents
      correctly */
   grpc_byte_buffer_destroy(request_payload);
@@ -141,20 +142,21 @@
                            deadline, NULL);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_server_accept_old(s, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(s, tag(5)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(s, tag(5)));
   /* now the write can be accepted */
   cq_expect_write_accepted(v_client, tag(4), GRPC_OP_OK);
   cq_verify(v_client);
   cq_expect_read(v_server, tag(5), large_slice());
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(8)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(8)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(9)));
 
   cq_expect_finish_accepted(v_client, tag(8), GRPC_OP_OK);
diff --git a/test/core/end2end/tests/max_concurrent_streams.c b/test/core/end2end/tests/max_concurrent_streams.c
index a177a7b..4830b85 100644
--- a/test/core/end2end/tests/max_concurrent_streams.c
+++ b/test/core/end2end/tests/max_concurrent_streams.c
@@ -109,34 +109,35 @@
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(4)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(4)));
   cq_expect_finish_accepted(v_client, tag(4), GRPC_OP_OK);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, NULL);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_server_accept_old(s, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(5)));
   cq_expect_finished_with_status(v_client, tag(3), GRPC_STATUS_UNIMPLEMENTED,
                                  "xyz", NULL);
   cq_verify(v_client);
 
   cq_expect_finish_accepted(v_server, tag(5), GRPC_OP_OK);
-  cq_verify(v_server);
   cq_expect_finished(v_server, tag(102), NULL);
   cq_verify(v_server);
 
@@ -181,20 +182,21 @@
   /* start two requests - ensuring that the second is not accepted until
      the first completes */
   deadline = five_seconds_time();
-  c1 =
-      grpc_channel_create_call(f.client, "/alpha", "test.google.com", deadline);
+  c1 = grpc_channel_create_call_old(f.client, "/alpha", "test.google.com",
+                                    deadline);
   GPR_ASSERT(c1);
-  c2 = grpc_channel_create_call(f.client, "/beta", "test.google.com", deadline);
+  c2 = grpc_channel_create_call_old(f.client, "/beta", "test.google.com",
+                                    deadline);
   GPR_ASSERT(c1);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c1, f.client_cq, tag(301), tag(302), 0));
+             grpc_call_invoke_old(c1, f.client_cq, tag(301), tag(302), 0));
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c2, f.client_cq, tag(401), tag(402), 0));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c1, tag(303)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c2, tag(303)));
+             grpc_call_invoke_old(c2, f.client_cq, tag(401), tag(402), 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c1, tag(303)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c2, tag(303)));
 
   ev = grpc_completion_queue_next(
       f.client_cq, gpr_time_add(gpr_now(), gpr_time_from_seconds(10)));
@@ -204,7 +206,7 @@
   /* The /alpha or /beta calls started above could be invoked (but NOT both);
    * check this here */
   /* We'll get tag 303 or 403, we want 300, 400 */
-  live_call = ((int)(gpr_intptr)ev->tag) - 3;
+  live_call = ((int)(gpr_intptr) ev->tag) - 3;
   grpc_event_finish(ev);
 
   cq_expect_server_rpc_new(v_server, &s1, tag(100),
@@ -213,14 +215,14 @@
   cq_verify(v_server);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_server_accept(s1, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s1, 0));
+             grpc_call_server_accept_old(s1, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s1, 0));
   cq_expect_client_metadata_read(v_client, tag(live_call + 1), NULL);
   cq_verify(v_client);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write_status(s1, GRPC_STATUS_UNIMPLEMENTED, "xyz",
-                                          tag(103)));
+             grpc_call_start_write_status_old(s1, GRPC_STATUS_UNIMPLEMENTED,
+                                              "xyz", tag(103)));
   cq_expect_finish_accepted(v_server, tag(103), GRPC_OP_OK);
   cq_expect_finished(v_server, tag(102), NULL);
   cq_verify(v_server);
@@ -232,21 +234,21 @@
   live_call = (live_call == 300) ? 400 : 300;
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(200)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(200)));
   cq_expect_server_rpc_new(v_server, &s2, tag(200),
                            live_call == 300 ? "/alpha" : "/beta",
                            "test.google.com", deadline, NULL);
   cq_verify(v_server);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_server_accept(s2, f.server_cq, tag(202)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s2, 0));
+             grpc_call_server_accept_old(s2, f.server_cq, tag(202)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s2, 0));
   cq_expect_client_metadata_read(v_client, tag(live_call + 1), NULL);
   cq_verify(v_client);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write_status(s2, GRPC_STATUS_UNIMPLEMENTED, "xyz",
-                                          tag(203)));
+             grpc_call_start_write_status_old(s2, GRPC_STATUS_UNIMPLEMENTED,
+                                              "xyz", tag(203)));
   cq_expect_finish_accepted(v_server, tag(203), GRPC_OP_OK);
   cq_expect_finished(v_server, tag(202), NULL);
   cq_verify(v_server);
diff --git a/test/core/end2end/tests/ping_pong_streaming.c b/test/core/end2end/tests/ping_pong_streaming.c
index 6768bd8..0c034a1 100644
--- a/test/core/end2end/tests/ping_pong_streaming.c
+++ b/test/core/end2end/tests/ping_pong_streaming.c
@@ -118,19 +118,21 @@
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
 
   gpr_log(GPR_INFO, "testing with %d message pairs.", messages);
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
 
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, NULL);
   cq_verify(v_server);
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_server_accept_old(s, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
 
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
@@ -138,7 +140,7 @@
   for (i = 0; i < messages; i++) {
     request_payload = grpc_byte_buffer_create(&request_payload_slice, 1);
     GPR_ASSERT(GRPC_CALL_OK ==
-               grpc_call_start_write(c, request_payload, tag(2), 0));
+               grpc_call_start_write_old(c, request_payload, tag(2), 0));
     /* destroy byte buffer early to ensure async code keeps track of its
        contents
        correctly */
@@ -146,14 +148,14 @@
     cq_expect_write_accepted(v_client, tag(2), GRPC_OP_OK);
     cq_verify(v_client);
 
-    GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(s, tag(3)));
+    GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(s, tag(3)));
     cq_expect_read(v_server, tag(3),
                    gpr_slice_from_copied_string("hello world"));
     cq_verify(v_server);
 
     response_payload = grpc_byte_buffer_create(&response_payload_slice, 1);
     GPR_ASSERT(GRPC_CALL_OK ==
-               grpc_call_start_write(s, response_payload, tag(4), 0));
+               grpc_call_start_write_old(s, response_payload, tag(4), 0));
     /* destroy byte buffer early to ensure async code keeps track of its
        contents
        correctly */
@@ -161,7 +163,7 @@
     cq_expect_write_accepted(v_server, tag(4), GRPC_OP_OK);
     cq_verify(v_server);
 
-    GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(c, tag(5)));
+    GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(c, tag(5)));
     cq_expect_read(v_client, tag(5), gpr_slice_from_copied_string("hello you"));
     cq_verify(v_client);
   }
@@ -169,8 +171,8 @@
   gpr_slice_unref(request_payload_slice);
   gpr_slice_unref(response_payload_slice);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(6)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(6)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(7)));
 
   cq_expect_finish_accepted(v_client, tag(6), GRPC_OP_OK);
diff --git a/test/core/end2end/tests/request_response_with_binary_metadata_and_payload.c b/test/core/end2end/tests/request_response_with_binary_metadata_and_payload.c
index 1dd798d..daadcf6 100644
--- a/test/core/end2end/tests/request_response_with_binary_metadata_and_payload.c
+++ b/test/core/end2end/tests/request_response_with_binary_metadata_and_payload.c
@@ -131,24 +131,25 @@
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
 
   /* byte buffer holds the slice, we can unref it already */
   gpr_slice_unref(request_payload_slice);
   gpr_slice_unref(response_payload_slice);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   /* add multiple metadata */
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(c, &meta1, 0));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(c, &meta2, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(c, &meta1, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(c, &meta2, 0));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write(c, request_payload, tag(4), 0));
+             grpc_call_start_write_old(c, request_payload, tag(4), 0));
   /* destroy byte buffer early to ensure async code keeps track of its contents
      correctly */
   grpc_byte_buffer_destroy(request_payload);
@@ -161,20 +162,20 @@
       "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d", NULL);
   cq_verify(v_server);
 
-  grpc_call_server_accept(s, f.server_cq, tag(102));
+  grpc_call_server_accept_old(s, f.server_cq, tag(102));
 
   /* add multiple metadata */
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(s, &meta3, 0));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(s, &meta4, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(s, &meta3, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(s, &meta4, 0));
 
-  grpc_call_server_end_initial_metadata(s, 0);
+  grpc_call_server_end_initial_metadata_old(s, 0);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(s, tag(5)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(s, tag(5)));
   cq_expect_read(v_server, tag(5), gpr_slice_from_copied_string("hello world"));
   cq_verify(v_server);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write(s, response_payload, tag(6), 0));
+             grpc_call_start_write_old(s, response_payload, tag(6), 0));
   /* destroy byte buffer early to ensure async code keeps track of its contents
      correctly */
   grpc_byte_buffer_destroy(response_payload);
@@ -189,12 +190,12 @@
       "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(c, tag(7)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(c, tag(7)));
   cq_expect_read(v_client, tag(7), gpr_slice_from_copied_string("hello you"));
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(8)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(8)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(9)));
 
   cq_expect_finish_accepted(v_client, tag(8), GRPC_OP_OK);
diff --git a/test/core/end2end/tests/request_response_with_metadata_and_payload.c b/test/core/end2end/tests/request_response_with_metadata_and_payload.c
index cfc9b61..0a58398 100644
--- a/test/core/end2end/tests/request_response_with_metadata_and_payload.c
+++ b/test/core/end2end/tests/request_response_with_metadata_and_payload.c
@@ -122,24 +122,25 @@
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
 
   /* byte buffer holds the slice, we can unref it already */
   gpr_slice_unref(request_payload_slice);
   gpr_slice_unref(response_payload_slice);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   /* add multiple metadata */
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(c, &meta1, 0));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(c, &meta2, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(c, &meta1, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(c, &meta2, 0));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write(c, request_payload, tag(4), 0));
+             grpc_call_start_write_old(c, request_payload, tag(4), 0));
   /* destroy byte buffer early to ensure async code keeps track of its contents
      correctly */
   grpc_byte_buffer_destroy(request_payload);
@@ -150,20 +151,20 @@
                            deadline, "key1", "val1", "key2", "val2", NULL);
   cq_verify(v_server);
 
-  grpc_call_server_accept(s, f.server_cq, tag(102));
+  grpc_call_server_accept_old(s, f.server_cq, tag(102));
 
   /* add multiple metadata */
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(s, &meta3, 0));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(s, &meta4, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(s, &meta3, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(s, &meta4, 0));
 
-  grpc_call_server_end_initial_metadata(s, 0);
+  grpc_call_server_end_initial_metadata_old(s, 0);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(s, tag(5)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(s, tag(5)));
   cq_expect_read(v_server, tag(5), gpr_slice_from_copied_string("hello world"));
   cq_verify(v_server);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write(s, response_payload, tag(6), 0));
+             grpc_call_start_write_old(s, response_payload, tag(6), 0));
   /* destroy byte buffer early to ensure async code keeps track of its contents
      correctly */
   grpc_byte_buffer_destroy(response_payload);
@@ -175,12 +176,12 @@
                                  "val4", NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(c, tag(7)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(c, tag(7)));
   cq_expect_read(v_client, tag(7), gpr_slice_from_copied_string("hello you"));
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(8)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(8)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(9)));
 
   cq_expect_finish_accepted(v_client, tag(8), GRPC_OP_OK);
diff --git a/test/core/end2end/tests/request_response_with_payload.c b/test/core/end2end/tests/request_response_with_payload.c
index 32bf512..d3b237b 100644
--- a/test/core/end2end/tests/request_response_with_payload.c
+++ b/test/core/end2end/tests/request_response_with_payload.c
@@ -119,16 +119,17 @@
   gpr_slice_unref(request_payload_slice);
   gpr_slice_unref(response_payload_slice);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write(c, request_payload, tag(4), 0));
+             grpc_call_start_write_old(c, request_payload, tag(4), 0));
   /* destroy byte buffer early to ensure async code keeps track of its contents
      correctly */
   grpc_byte_buffer_destroy(request_payload);
@@ -139,28 +140,29 @@
                            deadline, NULL);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_server_accept_old(s, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(s, tag(5)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(s, tag(5)));
   cq_expect_read(v_server, tag(5), gpr_slice_from_copied_string("hello world"));
   cq_verify(v_server);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write(s, response_payload, tag(6), 0));
+             grpc_call_start_write_old(s, response_payload, tag(6), 0));
   /* destroy byte buffer early to ensure async code keeps track of its contents
      correctly */
   grpc_byte_buffer_destroy(response_payload);
   cq_expect_write_accepted(v_server, tag(6), GRPC_OP_OK);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(c, tag(7)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(c, tag(7)));
   cq_expect_read(v_client, tag(7), gpr_slice_from_copied_string("hello you"));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(8)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(8)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(9)));
 
   cq_expect_finish_accepted(v_client, tag(8), GRPC_OP_OK);
diff --git a/test/core/end2end/tests/request_response_with_trailing_metadata_and_payload.c b/test/core/end2end/tests/request_response_with_trailing_metadata_and_payload.c
index 4f1de8b..f5f0e64 100644
--- a/test/core/end2end/tests/request_response_with_trailing_metadata_and_payload.c
+++ b/test/core/end2end/tests/request_response_with_trailing_metadata_and_payload.c
@@ -124,24 +124,25 @@
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
 
   /* byte buffer holds the slice, we can unref it already */
   gpr_slice_unref(request_payload_slice);
   gpr_slice_unref(response_payload_slice);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   /* add multiple metadata */
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(c, &meta1, 0));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(c, &meta2, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(c, &meta1, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(c, &meta2, 0));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write(c, request_payload, tag(4), 0));
+             grpc_call_start_write_old(c, request_payload, tag(4), 0));
   /* destroy byte buffer early to ensure async code keeps track of its contents
      correctly */
   grpc_byte_buffer_destroy(request_payload);
@@ -152,23 +153,23 @@
                            deadline, "key1", "val1", "key2", "val2", NULL);
   cq_verify(v_server);
 
-  grpc_call_server_accept(s, f.server_cq, tag(102));
+  grpc_call_server_accept_old(s, f.server_cq, tag(102));
 
   /* add multiple metadata */
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(s, &meta3, 0));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(s, &meta4, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(s, &meta3, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(s, &meta4, 0));
 
-  grpc_call_server_end_initial_metadata(s, 0);
+  grpc_call_server_end_initial_metadata_old(s, 0);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(s, &meta5, 0));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(s, &meta6, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(s, &meta5, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(s, &meta6, 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(s, tag(5)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(s, tag(5)));
   cq_expect_read(v_server, tag(5), gpr_slice_from_copied_string("hello world"));
   cq_verify(v_server);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write(s, response_payload, tag(6), 0));
+             grpc_call_start_write_old(s, response_payload, tag(6), 0));
   /* destroy byte buffer early to ensure async code keeps track of its contents
      correctly */
   grpc_byte_buffer_destroy(response_payload);
@@ -180,12 +181,12 @@
                                  "val4", NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(c, tag(7)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(c, tag(7)));
   cq_expect_read(v_client, tag(7), gpr_slice_from_copied_string("hello you"));
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(8)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(8)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(9)));
 
   cq_expect_finish_accepted(v_client, tag(8), GRPC_OP_OK);
diff --git a/test/core/end2end/tests/request_with_large_metadata.c b/test/core/end2end/tests/request_with_large_metadata.c
index 8362844..f6c892c 100644
--- a/test/core/end2end/tests/request_with_large_metadata.c
+++ b/test/core/end2end/tests/request_with_large_metadata.c
@@ -113,7 +113,7 @@
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
   const int large_size = 64 * 1024;
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
 
   meta.key = "key";
   meta.value = gpr_malloc(large_size + 1);
@@ -121,14 +121,15 @@
   meta.value[large_size] = 0;
   meta.value_length = large_size;
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   /* add the metadata */
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(c, &meta, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(c, &meta, 0));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, "key", meta.value, NULL);
@@ -140,9 +141,9 @@
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(8)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(8)));
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write_status(s, GRPC_STATUS_OK, NULL, tag(9)));
+             grpc_call_start_write_status_old(s, GRPC_STATUS_OK, NULL, tag(9)));
 
   cq_expect_finish_accepted(v_client, tag(8), GRPC_OP_OK);
   cq_expect_finished_with_status(v_client, tag(3), GRPC_STATUS_OK, NULL, NULL);
diff --git a/test/core/end2end/tests/request_with_payload.c b/test/core/end2end/tests/request_with_payload.c
index a352783..caf5d0e 100644
--- a/test/core/end2end/tests/request_with_payload.c
+++ b/test/core/end2end/tests/request_with_payload.c
@@ -116,15 +116,16 @@
   /* byte buffer holds the slice, we can unref it already */
   gpr_slice_unref(payload_slice);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write(c, payload, tag(4), 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_old(c, payload, tag(4), 0));
   /* destroy byte buffer early to ensure async code keeps track of its contents
      correctly */
   grpc_byte_buffer_destroy(payload);
@@ -139,11 +140,11 @@
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(s, tag(4)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(s, tag(4)));
   cq_expect_read(v_server, tag(4), gpr_slice_from_copied_string("hello world"));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(5)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(5)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(6)));
   cq_expect_finish_accepted(v_client, tag(5), GRPC_OP_OK);
   cq_expect_finished_with_status(v_client, tag(3), GRPC_STATUS_UNIMPLEMENTED,
diff --git a/test/core/end2end/tests/simple_delayed_request.c b/test/core/end2end/tests/simple_delayed_request.c
index 1e15eaa..a982bb5 100644
--- a/test/core/end2end/tests/simple_delayed_request.c
+++ b/test/core/end2end/tests/simple_delayed_request.c
@@ -103,32 +103,33 @@
 
   config.init_client(f, client_args);
 
-  c = grpc_channel_create_call(f->client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f->client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f->client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f->client_cq, tag(2), tag(3), 0));
 
   config.init_server(f, server_args);
 
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(4)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(4)));
   cq_expect_finish_accepted(v_client, tag(4), GRPC_OP_OK);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f->server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f->server, tag(100)));
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, NULL);
   cq_verify(v_server);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_server_accept(s, f->server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+             grpc_call_server_accept_old(s, f->server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(5)));
   cq_expect_finished_with_status(v_client, tag(3), GRPC_STATUS_UNIMPLEMENTED,
                                  "xyz", NULL);
diff --git a/test/core/end2end/tests/simple_request.c b/test/core/end2end/tests/simple_request.c
index f8894a8..db0d6d8 100644
--- a/test/core/end2end/tests/simple_request.c
+++ b/test/core/end2end/tests/simple_request.c
@@ -110,34 +110,35 @@
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(4)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(4)));
   cq_expect_finish_accepted(v_client, tag(4), GRPC_OP_OK);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, NULL);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_server_accept_old(s, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(5)));
   cq_expect_finished_with_status(v_client, tag(3), GRPC_STATUS_UNIMPLEMENTED,
                                  "xyz", NULL);
   cq_verify(v_client);
 
   cq_expect_finish_accepted(v_server, tag(5), GRPC_OP_OK);
-  cq_verify(v_server);
   cq_expect_finished(v_server, tag(102), NULL);
   cq_verify(v_server);
 
@@ -156,36 +157,36 @@
   cq_verifier *v_client = cq_verifier_create(f.client_cq);
   cq_verifier *v_server = cq_verifier_create(f.server_cq);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(4)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(4)));
   cq_expect_finish_accepted(v_client, tag(4), GRPC_OP_OK);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, NULL);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_server_accept_old(s, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(5)));
-  cq_expect_finish_accepted(v_server, tag(5), GRPC_OP_OK);
   cq_verify(v_server);
 
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
-  cq_verify(v_client);
-
   cq_expect_finished_with_status(v_client, tag(3), GRPC_STATUS_UNIMPLEMENTED,
                                  "xyz", NULL);
   cq_verify(v_client);
 
+  cq_expect_finish_accepted(v_server, tag(5), GRPC_OP_OK);
   cq_expect_finished(v_server, tag(102), NULL);
   cq_verify(v_server);
 
diff --git a/test/core/end2end/tests/thread_stress.c b/test/core/end2end/tests/thread_stress.c
index 8fdb765..e950a98 100644
--- a/test/core/end2end/tests/thread_stress.c
+++ b/test/core/end2end/tests/thread_stress.c
@@ -108,7 +108,7 @@
 static void start_request(void) {
   gpr_slice slice = gpr_slice_malloc(100);
   grpc_byte_buffer *buf;
-  grpc_call *call = grpc_channel_create_call(
+  grpc_call *call = grpc_channel_create_call_old(
       g_fixture.client, "/Foo", "test.google.com", g_test_end_time);
 
   memset(GPR_SLICE_START_PTR(slice), 1, GPR_SLICE_LENGTH(slice));
@@ -117,9 +117,9 @@
 
   g_active_requests++;
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(call, g_fixture.client_cq, NULL, NULL, 0));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(call, NULL));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write(call, buf, NULL, 0));
+             grpc_call_invoke_old(call, g_fixture.client_cq, NULL, NULL, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(call, NULL));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_old(call, buf, NULL, 0));
 
   grpc_byte_buffer_destroy(buf);
 }
@@ -143,7 +143,7 @@
         case GRPC_READ:
           break;
         case GRPC_WRITE_ACCEPTED:
-          GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(ev->call, NULL));
+          GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(ev->call, NULL));
           break;
         case GRPC_FINISH_ACCEPTED:
           break;
@@ -179,13 +179,13 @@
 static void request_server_call(void) {
   gpr_refcount *rc = gpr_malloc(sizeof(gpr_refcount));
   gpr_ref_init(rc, 2);
-  grpc_server_request_call(g_fixture.server, rc);
+  grpc_server_request_call_old(g_fixture.server, rc);
 }
 
 static void maybe_end_server_call(grpc_call *call, gpr_refcount *rc) {
   if (gpr_unref(rc)) {
-    GPR_ASSERT(GRPC_CALL_OK ==
-               grpc_call_start_write_status(call, GRPC_STATUS_OK, NULL, NULL));
+    GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
+                                   call, GRPC_STATUS_OK, NULL, NULL));
     gpr_free(rc);
   }
 }
@@ -215,20 +215,22 @@
         case GRPC_SERVER_RPC_NEW:
           if (ev->call) {
             GPR_ASSERT(GRPC_CALL_OK ==
-                       grpc_call_server_accept(ev->call, g_fixture.server_cq,
-                                               ev->tag));
+                       grpc_call_server_accept_old(
+                           ev->call, g_fixture.server_cq, ev->tag));
             GPR_ASSERT(GRPC_CALL_OK ==
-                       grpc_call_server_end_initial_metadata(ev->call, 0));
-            GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(ev->call, ev->tag));
+                       grpc_call_server_end_initial_metadata_old(ev->call, 0));
             GPR_ASSERT(GRPC_CALL_OK ==
-                       grpc_call_start_write(ev->call, buf, ev->tag, 0));
+                       grpc_call_start_read_old(ev->call, ev->tag));
+            GPR_ASSERT(GRPC_CALL_OK ==
+                       grpc_call_start_write_old(ev->call, buf, ev->tag, 0));
           } else {
             gpr_free(ev->tag);
           }
           break;
         case GRPC_READ:
           if (ev->data.read) {
-            GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(ev->call, ev->tag));
+            GPR_ASSERT(GRPC_CALL_OK ==
+                       grpc_call_start_read_old(ev->call, ev->tag));
           } else {
             maybe_end_server_call(ev->call, ev->tag);
           }
diff --git a/test/core/end2end/tests/writes_done_hangs_with_pending_read.c b/test/core/end2end/tests/writes_done_hangs_with_pending_read.c
index eea4594..0c77aa2 100644
--- a/test/core/end2end/tests/writes_done_hangs_with_pending_read.c
+++ b/test/core/end2end/tests/writes_done_hangs_with_pending_read.c
@@ -124,44 +124,46 @@
   gpr_slice_unref(request_payload_slice);
   gpr_slice_unref(response_payload_slice);
 
-  c = grpc_channel_create_call(f.client, "/foo", "test.google.com", deadline);
+  c = grpc_channel_create_call_old(f.client, "/foo", "test.google.com",
+                                   deadline);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_invoke(c, f.client_cq, tag(2), tag(3), 0));
+             grpc_call_invoke_old(c, f.client_cq, tag(2), tag(3), 0));
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write(c, request_payload, tag(4), 0));
+             grpc_call_start_write_old(c, request_payload, tag(4), 0));
   /* destroy byte buffer early to ensure async code keeps track of its contents
      correctly */
   grpc_byte_buffer_destroy(request_payload);
   cq_expect_write_accepted(v_client, tag(4), GRPC_OP_OK);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, tag(100)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call_old(f.server, tag(100)));
   cq_expect_server_rpc_new(v_server, &s, tag(100), "/foo", "test.google.com",
                            deadline, NULL);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_accept(s, f.server_cq, tag(102)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata(s, 0));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_server_accept_old(s, f.server_cq, tag(102)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_server_end_initial_metadata_old(s, 0));
   cq_expect_client_metadata_read(v_client, tag(2), NULL);
   cq_verify(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(s, tag(5)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(s, tag(5)));
   cq_expect_read(v_server, tag(5), gpr_slice_from_copied_string("hello world"));
   cq_verify(v_server);
 
   GPR_ASSERT(GRPC_CALL_OK ==
-             grpc_call_start_write(s, response_payload, tag(6), 0));
+             grpc_call_start_write_old(s, response_payload, tag(6), 0));
   /* destroy byte buffer early to ensure async code keeps track of its contents
      correctly */
   grpc_byte_buffer_destroy(response_payload);
   cq_expect_write_accepted(v_server, tag(6), GRPC_OP_OK);
   cq_verify(v_server);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done(c, tag(6)));
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status(
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_writes_done_old(c, tag(6)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_write_status_old(
                                  s, GRPC_STATUS_UNIMPLEMENTED, "xyz", tag(7)));
 
   cq_expect_finish_accepted(v_client, tag(6), GRPC_OP_OK);
@@ -170,7 +172,7 @@
   /* does not return status because there is a pending message to be read */
   cq_verify_empty(v_client);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read(c, tag(8)));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_read_old(c, tag(8)));
   cq_expect_read(v_client, tag(8), gpr_slice_from_copied_string("hello you"));
   cq_verify(v_client);
 
diff --git a/test/core/fling/client.c b/test/core/fling/client.c
index d6561e9..a91dfba 100644
--- a/test/core/fling/client.c
+++ b/test/core/fling/client.c
@@ -53,15 +53,16 @@
 static void init_ping_pong_request(void) {}
 
 static void step_ping_pong_request(void) {
-  call = grpc_channel_create_call(channel, "/Reflector/reflectUnary",
-                                  "localhost", gpr_inf_future);
-  GPR_ASSERT(grpc_call_invoke(call, cq, (void *)1, (void *)1,
-                              GRPC_WRITE_BUFFER_HINT) == GRPC_CALL_OK);
-  GPR_ASSERT(grpc_call_start_write(call, the_buffer, (void *)1,
-                                   GRPC_WRITE_BUFFER_HINT) == GRPC_CALL_OK);
+  call = grpc_channel_create_call_old(channel, "/Reflector/reflectUnary",
+                                      "localhost", gpr_inf_future);
+  GPR_ASSERT(grpc_call_invoke_old(call, cq, (void *)1, (void *)1,
+                                  GRPC_WRITE_BUFFER_HINT) == GRPC_CALL_OK);
+  GPR_ASSERT(grpc_call_start_write_old(call, the_buffer, (void *)1,
+                                       GRPC_WRITE_BUFFER_HINT) == GRPC_CALL_OK);
   grpc_event_finish(grpc_completion_queue_next(cq, gpr_inf_future));
-  GPR_ASSERT(grpc_call_start_read(call, (void *)1) == GRPC_CALL_OK);
-  GPR_ASSERT(grpc_call_writes_done(call, (void *)1) == GRPC_CALL_OK);
+  GPR_ASSERT(grpc_call_start_read_old(call, (void *)1) == GRPC_CALL_OK);
+  GPR_ASSERT(grpc_call_writes_done_old(call, (void *)1) == GRPC_CALL_OK);
+  grpc_event_finish(grpc_completion_queue_next(cq, gpr_inf_future));
   grpc_event_finish(grpc_completion_queue_next(cq, gpr_inf_future));
   grpc_event_finish(grpc_completion_queue_next(cq, gpr_inf_future));
   grpc_event_finish(grpc_completion_queue_next(cq, gpr_inf_future));
@@ -70,17 +71,17 @@
 }
 
 static void init_ping_pong_stream(void) {
-  call = grpc_channel_create_call(channel, "/Reflector/reflectStream",
-                                  "localhost", gpr_inf_future);
-  GPR_ASSERT(grpc_call_invoke(call, cq, (void *)1, (void *)1, 0) ==
+  call = grpc_channel_create_call_old(channel, "/Reflector/reflectStream",
+                                      "localhost", gpr_inf_future);
+  GPR_ASSERT(grpc_call_invoke_old(call, cq, (void *)1, (void *)1, 0) ==
              GRPC_CALL_OK);
   grpc_event_finish(grpc_completion_queue_next(cq, gpr_inf_future));
 }
 
 static void step_ping_pong_stream(void) {
-  GPR_ASSERT(grpc_call_start_write(call, the_buffer, (void *)1, 0) ==
+  GPR_ASSERT(grpc_call_start_write_old(call, the_buffer, (void *)1, 0) ==
              GRPC_CALL_OK);
-  GPR_ASSERT(grpc_call_start_read(call, (void *)1) == GRPC_CALL_OK);
+  GPR_ASSERT(grpc_call_start_read_old(call, (void *)1) == GRPC_CALL_OK);
   grpc_event_finish(grpc_completion_queue_next(cq, gpr_inf_future));
   grpc_event_finish(grpc_completion_queue_next(cq, gpr_inf_future));
 }
diff --git a/test/core/fling/fling_stream_test.c b/test/core/fling/fling_stream_test.c
index 7f52fb1..1db2f1a 100644
--- a/test/core/fling/fling_stream_test.c
+++ b/test/core/fling/fling_stream_test.c
@@ -31,7 +31,10 @@
  *
  */
 
+#ifndef _POSIX_SOURCE
 #define _POSIX_SOURCE
+#endif
+
 #include <unistd.h>
 #include <assert.h>
 #include <stdio.h>
diff --git a/test/core/fling/fling_test.c b/test/core/fling/fling_test.c
index b2272f2..4f41a21 100644
--- a/test/core/fling/fling_test.c
+++ b/test/core/fling/fling_test.c
@@ -31,7 +31,10 @@
  *
  */
 
+#ifndef _POSIX_SOURCE
 #define _POSIX_SOURCE
+#endif
+
 #include <unistd.h>
 #include <assert.h>
 #include <stdio.h>
diff --git a/test/core/fling/server.c b/test/core/fling/server.c
index f811aac..ba5e96d 100644
--- a/test/core/fling/server.c
+++ b/test/core/fling/server.c
@@ -62,7 +62,7 @@
 static void request_call(void) {
   call_state *s = gpr_malloc(sizeof(call_state));
   gpr_ref_init(&s->pending_ops, 2);
-  grpc_server_request_call(server, s);
+  grpc_server_request_call_old(server, s);
 }
 
 static void sigint_handler(int x) { got_sigint = 1; }
@@ -142,9 +142,9 @@
           } else {
             s->flags = GRPC_WRITE_BUFFER_HINT;
           }
-          grpc_call_server_accept(ev->call, cq, s);
-          grpc_call_server_end_initial_metadata(ev->call, s->flags);
-          GPR_ASSERT(grpc_call_start_read(ev->call, s) == GRPC_CALL_OK);
+          grpc_call_server_accept_old(ev->call, cq, s);
+          grpc_call_server_end_initial_metadata_old(ev->call, s->flags);
+          GPR_ASSERT(grpc_call_start_read_old(ev->call, s) == GRPC_CALL_OK);
           request_call();
         } else {
           GPR_ASSERT(shutdown_started);
@@ -153,15 +153,15 @@
         break;
       case GRPC_WRITE_ACCEPTED:
         GPR_ASSERT(ev->data.write_accepted == GRPC_OP_OK);
-        GPR_ASSERT(grpc_call_start_read(ev->call, s) == GRPC_CALL_OK);
+        GPR_ASSERT(grpc_call_start_read_old(ev->call, s) == GRPC_CALL_OK);
         break;
       case GRPC_READ:
         if (ev->data.read) {
-          GPR_ASSERT(grpc_call_start_write(ev->call, ev->data.read, s,
-                                           s->flags) == GRPC_CALL_OK);
+          GPR_ASSERT(grpc_call_start_write_old(ev->call, ev->data.read, s,
+                                               s->flags) == GRPC_CALL_OK);
         } else {
-          GPR_ASSERT(grpc_call_start_write_status(ev->call, GRPC_STATUS_OK,
-                                                  NULL, s) == GRPC_CALL_OK);
+          GPR_ASSERT(grpc_call_start_write_status_old(ev->call, GRPC_STATUS_OK,
+                                                      NULL, s) == GRPC_CALL_OK);
         }
         break;
       case GRPC_FINISH_ACCEPTED:
diff --git a/test/core/iomgr/poll_kick_posix_test.c b/test/core/iomgr/poll_kick_posix_test.c
index 3c6d815..2c5b444 100644
--- a/test/core/iomgr/poll_kick_posix_test.c
+++ b/test/core/iomgr/poll_kick_posix_test.c
@@ -105,6 +105,7 @@
     grpc_pollset_kick_post_poll(&kick_state[i]);
     grpc_pollset_kick_destroy(&kick_state[i]);
   }
+  gpr_free(kick_state);
 }
 
 static void run_tests(void) {
diff --git a/test/core/json/json_test.c b/test/core/json/json_test.c
index 11659a5..6d0227a 100644
--- a/test/core/json/json_test.c
+++ b/test/core/json/json_test.c
@@ -151,7 +151,7 @@
       GPR_ASSERT(!json);
     }
 
-    free(scratchpad);
+    gpr_free(scratchpad);
   }
 }
 
@@ -166,6 +166,7 @@
   grpc_json_destroy(json->child);
   json->child = brother;
   grpc_json_destroy(json);
+  gpr_free(scratchpad);
 }
 
 int main(int argc, char **argv) {
diff --git a/include/grpc/support/time_posix.h b/test/core/support/env_test.c
similarity index 68%
copy from include/grpc/support/time_posix.h
copy to test/core/support/env_test.c
index 9ff6f7f..36d7adf 100644
--- a/include/grpc/support/time_posix.h
+++ b/test/core/support/env_test.c
@@ -31,13 +31,34 @@
  *
  */
 
-#ifndef __GRPC_SUPPORT_TIME_POSIX_H__
-#define __GRPC_SUPPORT_TIME_POSIX_H__
-/* Posix variant of gpr_time_platform.h */
+#include <stdio.h>
+#include <string.h>
 
-#include <sys/time.h>
-#include <time.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
 
-typedef struct timespec gpr_timespec;
+#include "src/core/support/env.h"
+#include "src/core/support/string.h"
+#include "test/core/util/test_config.h"
 
-#endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */
+#define LOG_TEST_NAME() gpr_log(GPR_INFO, "%s", __FUNCTION__)
+
+static void test_setenv_getenv(void) {
+  const char *name = "FOO";
+  const char *value = "BAR";
+  char *retrieved_value;
+
+  LOG_TEST_NAME();
+
+  gpr_setenv(name, value);
+  retrieved_value = gpr_getenv(name);
+  GPR_ASSERT(retrieved_value != NULL);
+  GPR_ASSERT(!strcmp(value, retrieved_value));
+  gpr_free(retrieved_value);
+}
+
+int main(int argc, char **argv) {
+  grpc_test_init(argc, argv);
+  test_setenv_getenv();
+  return 0;
+}
diff --git a/test/core/support/file_test.c b/test/core/support/file_test.c
new file mode 100644
index 0000000..b089954
--- /dev/null
+++ b/test/core/support/file_test.c
@@ -0,0 +1,159 @@
+/*
+ *
+ * Copyright 2014, 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 <stdio.h>
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/slice.h>
+
+#include "src/core/support/file.h"
+#include "src/core/support/string.h"
+#include "test/core/util/test_config.h"
+
+#define LOG_TEST_NAME() gpr_log(GPR_INFO, "%s", __FUNCTION__)
+
+static const char prefix[] = "file_test";
+
+static void test_load_empty_file(void) {
+  FILE *tmp = NULL;
+  gpr_slice slice;
+  int success;
+  char *tmp_name;
+
+  LOG_TEST_NAME();
+
+  tmp = gpr_tmpfile(prefix, &tmp_name);
+  GPR_ASSERT(tmp_name != NULL);
+  GPR_ASSERT(tmp != NULL);
+  fclose(tmp);
+
+  slice = gpr_load_file(tmp_name, &success);
+  GPR_ASSERT(success == 1);
+  GPR_ASSERT(GPR_SLICE_LENGTH(slice) == 0);
+
+  remove(tmp_name);
+  gpr_free(tmp_name);
+  gpr_slice_unref(slice);
+}
+
+static void test_load_failure(void) {
+  FILE *tmp = NULL;
+  gpr_slice slice;
+  int success;
+  char *tmp_name;
+
+  LOG_TEST_NAME();
+
+  tmp = gpr_tmpfile(prefix, &tmp_name);
+  GPR_ASSERT(tmp_name != NULL);
+  GPR_ASSERT(tmp != NULL);
+  fclose(tmp);
+  remove(tmp_name);
+
+  slice = gpr_load_file(tmp_name, &success);
+  GPR_ASSERT(success == 0);
+  GPR_ASSERT(GPR_SLICE_LENGTH(slice) == 0);
+  gpr_free(tmp_name);
+  gpr_slice_unref(slice);
+}
+
+static void test_load_small_file(void) {
+  FILE *tmp = NULL;
+  gpr_slice slice;
+  int success;
+  char *tmp_name;
+  const char *blah = "blah";
+
+  LOG_TEST_NAME();
+
+  tmp = gpr_tmpfile(prefix, &tmp_name);
+  GPR_ASSERT(tmp_name != NULL);
+  GPR_ASSERT(tmp != NULL);
+  GPR_ASSERT(fwrite(blah, 1, strlen(blah), tmp) == strlen(blah));
+  fclose(tmp);
+
+  slice = gpr_load_file(tmp_name, &success);
+  GPR_ASSERT(success == 1);
+  GPR_ASSERT(GPR_SLICE_LENGTH(slice) == strlen(blah));
+  GPR_ASSERT(!memcmp(GPR_SLICE_START_PTR(slice), blah, strlen(blah)));
+
+  remove(tmp_name);
+  gpr_free(tmp_name);
+  gpr_slice_unref(slice);
+}
+
+static void test_load_big_file(void) {
+  FILE *tmp = NULL;
+  gpr_slice slice;
+  int success;
+  char *tmp_name;
+  unsigned char buffer[124631];
+  unsigned char *current;
+  size_t i;
+
+  LOG_TEST_NAME();
+
+  for (i = 0; i < sizeof(buffer); i++) {
+    buffer[i] = 42;
+  }
+
+  tmp = gpr_tmpfile(prefix, &tmp_name);
+  GPR_ASSERT(tmp != NULL);
+  GPR_ASSERT(tmp_name != NULL);
+  GPR_ASSERT(fwrite(buffer, 1, sizeof(buffer), tmp) == sizeof(buffer));
+  fclose(tmp);
+
+  slice = gpr_load_file(tmp_name, &success);
+  GPR_ASSERT(success == 1);
+  GPR_ASSERT(GPR_SLICE_LENGTH(slice) == sizeof(buffer));
+  current = GPR_SLICE_START_PTR(slice);
+  for (i = 0; i < sizeof(buffer); i++) {
+    GPR_ASSERT(current[i] == 42);
+  }
+
+  remove(tmp_name);
+  gpr_free(tmp_name);
+  gpr_slice_unref(slice);
+}
+
+
+int main(int argc, char **argv) {
+  grpc_test_init(argc, argv);
+  test_load_empty_file();
+  test_load_failure();
+  test_load_small_file();
+  test_load_big_file();
+  return 0;
+}
diff --git a/test/core/surface/completion_queue_test.c b/test/core/surface/completion_queue_test.c
index dc459d6..875cf3e 100644
--- a/test/core/surface/completion_queue_test.c
+++ b/test/core/surface/completion_queue_test.c
@@ -105,32 +105,6 @@
   shutdown_and_destroy(cc);
 }
 
-static void test_cq_end_invoke_accepted(void) {
-  grpc_event *ev;
-  grpc_completion_queue *cc;
-  int on_finish_called = 0;
-  void *tag = create_test_tag();
-
-  LOG_TEST();
-
-  cc = grpc_completion_queue_create();
-
-  grpc_cq_begin_op(cc, NULL, GRPC_INVOKE_ACCEPTED);
-  grpc_cq_end_invoke_accepted(cc, tag, NULL, increment_int_on_finish,
-                              &on_finish_called, GRPC_OP_OK);
-
-  ev = grpc_completion_queue_next(cc, gpr_inf_past);
-  GPR_ASSERT(ev != NULL);
-  GPR_ASSERT(ev->type == GRPC_INVOKE_ACCEPTED);
-  GPR_ASSERT(ev->tag == tag);
-  GPR_ASSERT(ev->data.invoke_accepted == GRPC_OP_OK);
-  GPR_ASSERT(on_finish_called == 0);
-  grpc_event_finish(ev);
-  GPR_ASSERT(on_finish_called == 1);
-
-  shutdown_and_destroy(cc);
-}
-
 static void test_cq_end_write_accepted(void) {
   grpc_event *ev;
   grpc_completion_queue *cc;
@@ -421,7 +395,6 @@
   test_no_op();
   test_wait_empty();
   test_cq_end_read();
-  test_cq_end_invoke_accepted();
   test_cq_end_write_accepted();
   test_cq_end_finish_accepted();
   test_cq_end_client_metadata_read();
diff --git a/test/core/surface/lame_client_test.c b/test/core/surface/lame_client_test.c
index 9b9f020..0a6edc1 100644
--- a/test/core/surface/lame_client_test.c
+++ b/test/core/surface/lame_client_test.c
@@ -51,7 +51,7 @@
 
   chan = grpc_lame_client_channel_create();
   GPR_ASSERT(chan);
-  call = grpc_channel_create_call(
+  call = grpc_channel_create_call_old(
       chan, "/Foo", "anywhere",
       gpr_time_add(gpr_now(), gpr_time_from_seconds(100)));
   GPR_ASSERT(call);
@@ -59,10 +59,10 @@
   cqv = cq_verifier_create(cq);
 
   /* we should be able to add metadata */
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata(call, &md, 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_add_metadata_old(call, &md, 0));
 
   /* and invoke the call */
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_invoke(call, cq, tag(2), tag(3), 0));
+  GPR_ASSERT(GRPC_CALL_OK == grpc_call_invoke_old(call, cq, tag(2), tag(3), 0));
 
   /* the call should immediately fail */
   cq_expect_client_metadata_read(cqv, tag(2), NULL);
diff --git a/test/cpp/qps/client.cc b/test/cpp/qps/client.cc
index affc492..d2c83aa 100644
--- a/test/cpp/qps/client.cc
+++ b/test/cpp/qps/client.cc
@@ -44,6 +44,7 @@
 #include <google/gflags.h>
 #include <grpc++/client_context.h>
 #include <grpc++/status.h>
+#include "test/core/util/grpc_profiler.h"
 #include "test/cpp/util/create_test_channel.h"
 #include "test/cpp/qps/qpstest.pb.h"
 
@@ -129,6 +130,8 @@
   grpc::Status status_beg = stub_stats->CollectServerStats(
       &context_stats_begin, stats_request, &server_stats_begin);
 
+  grpc_profiler_start("qps_client.prof");
+
   for (int i = 0; i < client_threads; i++) {
     gpr_histogram *hist = gpr_histogram_create(0.01, 60e9);
     GPR_ASSERT(hist != NULL);
@@ -172,6 +175,9 @@
   for (auto &t : threads) {
     t.join();
   }
+
+  grpc_profiler_stop();
+
   for (int i = 0; i < client_threads; i++) {
     gpr_histogram *h = thread_stats[i];
     gpr_log(GPR_INFO, "latency at thread %d (50/90/95/99/99.9): %f/%f/%f/%f/%f",
diff --git a/test/cpp/qps/server.cc b/test/cpp/qps/server.cc
index eb810b8..c35d9eb 100644
--- a/test/cpp/qps/server.cc
+++ b/test/cpp/qps/server.cc
@@ -33,6 +33,7 @@
 
 #include <sys/time.h>
 #include <sys/resource.h>
+#include <sys/signal.h>
 #include <thread>
 
 #include <google/gflags.h>
@@ -43,6 +44,7 @@
 #include <grpc++/server_builder.h>
 #include <grpc++/server_context.h>
 #include <grpc++/status.h>
+#include "test/core/util/grpc_profiler.h"
 #include "test/cpp/qps/qpstest.pb.h"
 
 #include <grpc/grpc.h>
@@ -63,11 +65,15 @@
 using grpc::testing::TestService;
 using grpc::Status;
 
+static bool got_sigint = false;
+
+static void sigint_handler(int x) { got_sigint = 1; }
+
 static double time_double(struct timeval* tv) {
   return tv->tv_sec + 1e-6 * tv->tv_usec;
 }
 
-bool SetPayload(PayloadType type, int size, Payload* payload) {
+static bool SetPayload(PayloadType type, int size, Payload* payload) {
   PayloadType response_type = type;
   // TODO(yangg): Support UNCOMPRESSABLE payload.
   if (type != PayloadType::COMPRESSABLE) {
@@ -79,7 +85,9 @@
   return true;
 }
 
-class TestServiceImpl : public TestService::Service {
+namespace {
+
+class TestServiceImpl final : public TestService::Service {
  public:
   Status CollectServerStats(ServerContext* context, const StatsRequest*,
                             ServerStats* response) {
@@ -104,7 +112,9 @@
   }
 };
 
-void RunServer() {
+}  // namespace
+
+static void RunServer() {
   char* server_address = NULL;
   gpr_join_host_port(&server_address, "::", FLAGS_port);
 
@@ -118,10 +128,15 @@
   builder.RegisterService(service.service());
   std::unique_ptr<Server> server(builder.BuildAndStart());
   gpr_log(GPR_INFO, "Server listening on %s\n", server_address);
-  while (true) {
+
+  grpc_profiler_start("qps_server.prof");
+
+  while (!got_sigint) {
     std::this_thread::sleep_for(std::chrono::seconds(5));
   }
 
+  grpc_profiler_stop();
+
   gpr_free(server_address);
 }
 
@@ -129,6 +144,8 @@
   grpc_init();
   google::ParseCommandLineFlags(&argc, &argv, true);
 
+  signal(SIGINT, sigint_handler);
+  
   GPR_ASSERT(FLAGS_port != 0);
   GPR_ASSERT(!FLAGS_enable_ssl);
   RunServer();
@@ -136,3 +153,4 @@
   grpc_shutdown();
   return 0;
 }
+
diff --git a/test/cpp/util/create_test_channel.cc b/test/cpp/util/create_test_channel.cc
index a521162..301e9a3 100644
--- a/test/cpp/util/create_test_channel.cc
+++ b/test/cpp/util/create_test_channel.cc
@@ -45,6 +45,8 @@
 // override_hostname is provided.
 // When ssl is not enabled, override_hostname is ignored.
 // Set use_prod_root to true to use the SSL root for connecting to google.
+// In this case, path to the roots pem file must be set via environment variable
+// GRPC_DEFAULT_SSL_ROOTS_FILE_PATH.
 // Otherwise, root for test SSL cert will be used.
 // creds will be used to create a channel when enable_ssl is true.
 // Use examples:
@@ -60,7 +62,7 @@
   ChannelArguments channel_args;
   if (enable_ssl) {
     const char* roots_certs =
-        use_prod_roots ? prod_roots_certs : test_root_cert;
+        use_prod_roots ? "" : test_root_cert;
     SslCredentialsOptions ssl_opts = {roots_certs, "", ""};
 
     std::unique_ptr<Credentials> channel_creds =
diff --git a/tools/buildgen/build-cleaner.py b/tools/buildgen/build-cleaner.py
index f930736..4992beb 100755
--- a/tools/buildgen/build-cleaner.py
+++ b/tools/buildgen/build-cleaner.py
@@ -33,9 +33,9 @@
   for name in ['public_headers', 'headers', 'src']:
     if name not in indict: continue
     inlist = indict[name]
-    protos = set(x for x in inlist if os.path.splitext(x)[1] == '.proto')
+    protos = list(x for x in inlist if os.path.splitext(x)[1] == '.proto')
     others = set(x for x in inlist if x not in protos)
-    indict[name] = sorted(protos) + sorted(others)
+    indict[name] = protos + sorted(others)
   return rebuild_as_ordered_dict(indict, _ELEM_KEYS)
 
 for filename in sys.argv[1:]:
diff --git a/tools/buildgen/mako_renderer.py b/tools/buildgen/mako_renderer.py
index 29c7cf0..18f6eea 100755
--- a/tools/buildgen/mako_renderer.py
+++ b/tools/buildgen/mako_renderer.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python2.7
 
 """Simple Mako renderer.
 
diff --git a/tools/dockerfile/grpc_base/README.md b/tools/dockerfile/grpc_base/README.md
index 4745141..e3b5f2e 100644
--- a/tools/dockerfile/grpc_base/README.md
+++ b/tools/dockerfile/grpc_base/README.md
@@ -4,8 +4,7 @@
 Dockerfile for creating the base gRPC development Docker instance.
 For now, this assumes that the development will be done on GCE instances, with source code on Git-on-Borg.
 
-As of 2014/09/29, it includes
+As of 2015/02/02, it includes
 - git
 - some useful tools like curl, emacs, strace, telnet etc
-- downloads the gerrit-compute-tools and installs the script that allows access to gerrit when on git-on-borg
 - a patched version of protoc, to allow protos with stream tags to work
diff --git a/tools/dockerfile/grpc_cxx/Dockerfile b/tools/dockerfile/grpc_cxx/Dockerfile
index 43da9fe..9b20e7a 100644
--- a/tools/dockerfile/grpc_cxx/Dockerfile
+++ b/tools/dockerfile/grpc_cxx/Dockerfile
@@ -22,5 +22,7 @@
   && make interop_server
 
 ADD service_account service_account
+ADD cacerts cacerts
+ENV GRPC_DEFAULT_SSL_ROOTS_FILE_PATH /cacerts/roots.pem
 
 CMD ["/var/local/git/grpc/bins/opt/interop_server", "--enable_ssl", "--port=8010"]
diff --git a/tools/dockerfile/grpc_java/README.md b/tools/dockerfile/grpc_java/README.md
index 2da2393..808f0fc 100644
--- a/tools/dockerfile/grpc_java/README.md
+++ b/tools/dockerfile/grpc_java/README.md
@@ -5,5 +5,5 @@
 
 As of 2014/12 this
  - is based on the gRPC Java base
- - pulls from gRPC Java on git-on-borg
+ - pulls from gRPC Java on GitHub
  - installs it and runs the tests
\ No newline at end of file
diff --git a/tools/dockerfile/grpc_node/Dockerfile b/tools/dockerfile/grpc_node/Dockerfile
index baec0e2..ce582d2 100644
--- a/tools/dockerfile/grpc_node/Dockerfile
+++ b/tools/dockerfile/grpc_node/Dockerfile
@@ -11,4 +11,4 @@
 
 RUN cd /var/local/git/grpc/src/node && npm install && node-gyp rebuild
 
-CMD ["/usr/bin/nodejs", "/var/local/git/grpc/src/node/interop/interop_server.js", "--use_tls=true", "--port 8040"]
\ No newline at end of file
+CMD ["/usr/bin/nodejs", "/var/local/git/grpc/src/node/interop/interop_server.js", "--use_tls=true", "--port=8040"]
\ No newline at end of file
diff --git a/tools/dockerfile/grpc_php/README.md b/tools/dockerfile/grpc_php/README.md
index a37389f..f3c332b 100644
--- a/tools/dockerfile/grpc_php/README.md
+++ b/tools/dockerfile/grpc_php/README.md
@@ -5,6 +5,6 @@
 
 As of 2014/10 this
 - is based on the GRPC PHP base
-- adds a pull of the HEAD GRPC PHP source from git-on-borg
+- adds a pull of the HEAD GRPC PHP source from GitHub
 - it builds it
 - runs the tests, i.e, the image won't be created if the tests don't pass
diff --git a/tools/dockerfile/grpc_php_base/Dockerfile b/tools/dockerfile/grpc_php_base/Dockerfile
index 47266a3..ef58f3a 100644
--- a/tools/dockerfile/grpc_php_base/Dockerfile
+++ b/tools/dockerfile/grpc_php_base/Dockerfile
@@ -45,13 +45,9 @@
   && ./configure --with-zlib=/usr --with-libxml-dir=ext/libxml \
   && make -j12 && make install
 
-# Start the daemon that allows access to the protected git-on-borg repos
-RUN git clone https://gerrit.googlesource.com/gcompute-tools /var/local/git/gcompute-tools
-RUN /var/local/git/gcompute-tools/git-cookie-authdaemon
-
 # Download the patched PHP protobuf so that PHP gRPC clients can be generated
 # from proto3 schemas.
-RUN git clone https://team.googlesource.com/one-platform-grpc-team/grpc-php-protobuf-php /var/local/git/protobuf-php
+RUN git clone git@github.com:murgatroid99/Protobuf-PHP.git /var/local/git/protobuf-php
 
 # Install ruby (via RVM) as ruby tools are dependencies for building Protobuf
 # PHP extensions.
diff --git a/tools/dockerfile/grpc_ruby/Dockerfile b/tools/dockerfile/grpc_ruby/Dockerfile
index c84548c..47972e7 100644
--- a/tools/dockerfile/grpc_ruby/Dockerfile
+++ b/tools/dockerfile/grpc_ruby/Dockerfile
@@ -6,17 +6,19 @@
   && git pull --recurse-submodules \
   && git submodule update --init --recursive
 
-# TODO: remove this, once make install is fixed
-RUN touch /var/local/git/grpc/include/grpc/support/string.h
-
 # Build the C core.
 RUN make install_c -C /var/local/git/grpc
 
 # Build ruby gRPC and run its tests
 RUN /bin/bash -l -c 'cd /var/local/git/grpc/src/ruby && bundle && rake'
 
-# Add a cacerts directory containing the Google root pem file, allowing the ruby client to access the production test instance
+# Add a cacerts directory containing the Google root pem file, allowing the
+# ruby client to access the production test instance
 ADD cacerts cacerts
 
-# Specify the default command such that the interop server runs on its known testing port
+# Add a service_account directory containing the auth creds file
+ADD service_account service_account
+
+# Specify the default command such that the interop server runs on its known
+# testing port
 CMD ["/bin/bash", "-l", "-c", "ruby /var/local/git/grpc/src/ruby/bin/interop/interop_server.rb --use_tls --port 8060"]
diff --git a/tools/dockerfile/grpc_ruby/README.md b/tools/dockerfile/grpc_ruby/README.md
index 51fb2f5..eaa8382 100644
--- a/tools/dockerfile/grpc_ruby/README.md
+++ b/tools/dockerfile/grpc_ruby/README.md
@@ -5,6 +5,6 @@
 
 As of 2014/10 this
 - is based on the GRPC Ruby base
-- adds a pull of the HEAD gRPC Ruby source from git-on-borg
+- adds a pull of the HEAD gRPC Ruby source from GitHub
 - it builds it
 - runs the tests, i.e, the image won't be created if the tests don't pass
diff --git a/tools/gce_setup/builder.sh b/tools/gce_setup/builder.sh
new file mode 100755
index 0000000..49b3c43
--- /dev/null
+++ b/tools/gce_setup/builder.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+main() {
+  # restart builder vm and wait for images to sync to it
+  source grpc_docker.sh
+  ./new_grpc_docker_builder.sh -igrpc-docker-builder-alt-2 -anone
+  cd ../../
+  sleep 3600
+
+  # build images for all languages
+  languages=(cxx java go ruby node)
+  for lan in "${languages[@]}"
+  do
+    grpc_update_image $lan
+  done
+
+  # restart client and server vm and wait for images to sync to them
+  cd tools/gce_setup
+  ./new_grpc_docker_builder.sh -igrpc-docker-testclients -anone
+  ./new_grpc_docker_builder.sh -igrpc-docker-server -anone
+  sleep 3600
+
+  # launch images for all languages on  server
+  grpc_launch_servers grpc-docker-server
+  
+}
+
+set -x
+main "$@"
diff --git a/tools/gce_setup/cloud_prod_runner.sh b/tools/gce_setup/cloud_prod_runner.sh
index 0c1163a..200f859 100755
--- a/tools/gce_setup/cloud_prod_runner.sh
+++ b/tools/gce_setup/cloud_prod_runner.sh
@@ -2,8 +2,8 @@
 
 main() {
   source grpc_docker.sh
-  test_cases=(large_unary empty_unary client_streaming server_streaming)
-  clients=(cxx java go ruby)
+  test_cases=(large_unary empty_unary ping_pong client_streaming server_streaming)
+  clients=(cxx java go ruby node)
   for test_case in "${test_cases[@]}"
   do
     for client in "${clients[@]}"
diff --git a/tools/gce_setup/grpc_docker.sh b/tools/gce_setup/grpc_docker.sh
index a97cc88..2e02653 100755
--- a/tools/gce_setup/grpc_docker.sh
+++ b/tools/gce_setup/grpc_docker.sh
@@ -317,7 +317,7 @@
     echo "$FUNCNAME: missing arg: test_case" 1>&2
     return 1
   }
-  echo "--server_host=$server_ip --server_port=$port --test_case=$test_case"
+  echo "--server_host_override=foo.test.google.fr --server_host=$server_ip --server_port=$port --test_case=$test_case"
 }
 
 # checks the positional args and assigns them to variables visible in the caller
@@ -590,7 +590,7 @@
   done
 }
 
-grpc_launch_server_args() {
+_grpc_show_servers_args() {
   [[ -n $1 ]] && {  # host
     host=$1
     shift
@@ -598,9 +598,78 @@
     echo "$FUNCNAME: missing arg: host" 1>&2
     return 1
   }
+}
 
-  [[ -n $1 ]] && {  # server_type
-    case $1 in
+
+# Shows servers on a docker instance.
+#
+# call-seq;
+#   grpc_show_servers <server_name>
+#   E.g
+#   grpc_show_server grpc-docker-server
+#
+# Shows the grpc servers on the GCE instance <server_name>
+grpc_show_servers() {
+  # declare vars local so that they don't pollute the shell environment
+  # where they this func is used.
+  local grpc_zone grpc_project dry_run  # set by _grpc_set_project_and_zone
+  # set by _grpc_show_servers
+  local host
+
+  # set the project zone and check that all necessary args are provided
+  _grpc_set_project_and_zone -f _grpc_show_servers_args "$@" || return 1
+  gce_has_instance $grpc_project $host || return 1;
+
+  local cmd="sudo docker ps | grep grpc_"
+  local ssh_cmd="bash -l -c \"$cmd\""
+  echo "will run:"
+  echo "  $ssh_cmd"
+  echo "on $host"
+  [[ $dry_run == 1 ]] && continue  # don't run the command on a dry run
+  gcloud compute $project_opt ssh $zone_opt $host --command "$cmd"
+}
+
+_grpc_launch_servers_args() {
+  [[ -n $1 ]] && {  # host
+    host=$1
+    shift
+  } || {
+    echo "$FUNCNAME: missing arg: host" 1>&2
+    return 1
+  }
+  [[ -n $1 ]] && {
+    servers="$@"
+  } || {
+    servers="cxx java go node ruby"
+    echo "$FUNCNAME: no servers specified, will launch defaults '$servers'"
+  }
+}
+
+# Launches servers on a docker instance.
+#
+# call-seq;
+#   grpc_launch_servers <server_name> [server1 server2 ...]
+#   E.g
+#   grpc_launch_server grpc-docker-server ruby node
+#
+# Restarts all the specified servers on the GCE instance <server_name>
+# If no servers are specified, it launches all known servers
+grpc_launch_servers() {
+  # declare vars local so that they don't pollute the shell environment
+  # where they this func is used.
+  local grpc_zone grpc_project dry_run  # set by _grpc_set_project_and_zone
+  # set by _grpc_launch_servers_args
+  local host servers
+
+  # set the project zone and check that all necessary args are provided
+  _grpc_set_project_and_zone -f _grpc_launch_servers_args "$@" || return 1
+  gce_has_instance $grpc_project $host || return 1;
+
+  # launch each of the servers in turn
+  for server in $servers
+  do
+    local grpc_port
+    case $server in
       cxx)    grpc_port=8010 ;;
       go)     grpc_port=8020 ;;
       java)   grpc_port=8030 ;;
@@ -609,44 +678,22 @@
       ruby)   grpc_port=8060 ;;
       *) echo "bad server_type: $1" 1>&2; return 1 ;;
     esac
-    docker_label="grpc/$1"
-    docker_name="grpc_interop_$1"
-    shift
-  } || {
-    echo "$FUNCNAME: missing arg: server_type" 1>&2
-    return 1
-  }
-}
+    local docker_label="grpc/$server"
+    local docker_name="grpc_interop_$server"
 
-# Launches a server on a docker instance.
-#
-# call-seq;
-#   grpc_launch_server <server_name> <server_type>
-#
-# Runs the server_type on a GCE instance running docker with server_name
-grpc_launch_server() {
-  # declare vars local so that they don't pollute the shell environment
-  # where they this func is used.
-  local grpc_zone grpc_project dry_run  # set by _grpc_set_project_and_zone
-  # set by grpc_launch_server_args
-  local docker_label docker_name host grpc_port
-
-  # set the project zone and check that all necessary args are provided
-  _grpc_set_project_and_zone -f grpc_launch_server_args "$@" || return 1
-  gce_has_instance $grpc_project $host || return 1;
-
-  cmd="sudo docker kill $docker_name > /dev/null 2>&1; "
-  cmd+="sudo docker rm $docker_name > /dev/null 2>&1; "
-  cmd+="sudo docker run -d --name $docker_name"
-  cmd+=" -p $grpc_port:$grpc_port $docker_label"
-  local project_opt="--project $grpc_project"
-  local zone_opt="--zone $grpc_zone"
-  local ssh_cmd="bash -l -c \"$cmd\""
-  echo "will run:"
-  echo "  $ssh_cmd"
-  echo "on $host"
-  [[ $dry_run == 1 ]] && return 0  # don't run the command on a dry run
-  gcloud compute $project_opt ssh $zone_opt $host --command "$cmd"
+    cmd="sudo docker kill $docker_name > /dev/null 2>&1; "
+    cmd+="sudo docker rm $docker_name > /dev/null 2>&1; "
+    cmd+="sudo docker run -d --name $docker_name"
+    cmd+=" -p $grpc_port:$grpc_port $docker_label"
+    local project_opt="--project $grpc_project"
+    local zone_opt="--zone $grpc_zone"
+    local ssh_cmd="bash -l -c \"$cmd\""
+    echo "will run:"
+    echo "  $ssh_cmd"
+    echo "on $host"
+    [[ $dry_run == 1 ]] && return 0  # don't run the command on a dry run
+    gcloud compute $project_opt ssh $zone_opt $host --command "$cmd"
+  done
 }
 
 # Runs a test command on a docker instance.
@@ -715,7 +762,16 @@
   echo "  $ssh_cmd"
   echo "on $host"
   [[ $dry_run == 1 ]] && return 0  # don't run the command on a dry run
-  gcloud compute $project_opt ssh $zone_opt $host --command "$cmd"
+  gcloud compute $project_opt ssh $zone_opt $host --command "$cmd" & 
+  PID=$!
+  sleep 10
+  echo "pid is $PID"
+  if ps -p $PID
+  then
+    kill $PID
+    return 1
+  fi
+
 }
 
 # Runs a test command on a docker instance.
@@ -761,7 +817,16 @@
   echo "  $ssh_cmd"
   echo "on $host"
   [[ $dry_run == 1 ]] && return 0  # don't run the command on a dry run
-  gcloud compute $project_opt ssh $zone_opt $host --command "$cmd"
+  gcloud compute $project_opt ssh $zone_opt $host --command "$cmd" & 
+  PID=$!
+  sleep 10
+  echo "pid is $PID"
+  if ps -p $PID
+  then
+    kill $PID
+    return 1
+  fi
+
 }
 
 # Runs a test command on a docker instance.
@@ -822,7 +887,6 @@
   echo $the_cmd
 }
 
-
 # constructs the full dockerized java interop test cmd.
 #
 # call-seq:
@@ -832,12 +896,43 @@
   local cmd_prefix="sudo docker run grpc/ruby bin/bash -l -c"
   local test_script="/var/local/git/grpc/src/ruby/bin/interop/interop_client.rb"
   local test_script+=" --use_tls"
-  local gfe_flags=" --server_port=443 --server_host=grpc-test.sandbox.google.com --server_host_override=grpc-test.sandbox.google.com"
+  local gfe_flags=$(_grpc_prod_gfe_flags)
   local env_prefix="SSL_CERT_FILE=/cacerts/roots.pem"
   local the_cmd="$cmd_prefix '$env_prefix ruby $test_script $gfe_flags $@'"
   echo $the_cmd
 }
 
+# constructs the full dockerized ruby service_account auth interop test cmd.
+#
+# call-seq:
+#   flags= .... # generic flags to include the command
+#   cmd=$($grpc_gen_test_cmd $flags)
+grpc_cloud_prod_auth_service_account_creds_gen_ruby_cmd() {
+  local cmd_prefix="sudo docker run grpc/ruby bin/bash -l -c";
+  local test_script="/var/local/git/grpc/src/ruby/bin/interop/interop_client.rb"
+  local test_script+=" --use_tls"
+  local gfe_flags=$(_grpc_prod_gfe_flags)
+  local added_gfe_flags=$(_grpc_svc_acc_test_flags)
+  local env_prefix="SSL_CERT_FILE=/cacerts/roots.pem"
+  local the_cmd="$cmd_prefix '$env_prefix ruby $test_script $gfe_flags $added_gfe_flag $@'"
+  echo $the_cmd
+}
+
+# constructs the full dockerized ruby gce auth interop test cmd.
+#
+# call-seq:
+#   flags= .... # generic flags to include the command
+#   cmd=$($grpc_gen_test_cmd $flags)
+grpc_cloud_prod_auth_compute_engine_creds_gen_ruby_cmd() {
+  local cmd_prefix="sudo docker run grpc/ruby bin/bash -l -c";
+  local test_script="/var/local/git/grpc/src/ruby/bin/interop/interop_client.rb"
+  local test_script+=" --use_tls"
+  local gfe_flags=$(_grpc_prod_gfe_flags)
+  local added_gfe_flags=$(_grpc_gce_test_flags)
+  local env_prefix="SSL_CERT_FILE=/cacerts/roots.pem"
+  local the_cmd="$cmd_prefix '$env_prefix ruby $test_script $gfe_flags $added_gfe_flag $@'"
+  echo $the_cmd
+}
 
 # constructs the full dockerized Go interop test cmd.
 #
@@ -874,7 +969,7 @@
 grpc_interop_gen_java_cmd() {
     local cmd_prefix="sudo docker run grpc/java";
     local test_script="/var/local/git/grpc-java/run-test-client.sh";
-    local test_script+=" --server_host_override=foo.test.google.fr --use_test_ca=true --use_tls=true"
+    local test_script+=" --use_test_ca=true --use_tls=true"
     local the_cmd="$cmd_prefix $test_script $@";
     echo $the_cmd
 }
@@ -888,14 +983,14 @@
     local cmd_prefix="sudo docker run grpc/java";
     local test_script="/var/local/git/grpc-java/run-test-client.sh";
     local test_script+=" --use_tls=true"
-    local gfe_flags=" --server_port=443 --server_host=grpc-test.sandbox.google.com --server_host_override=grpc-test.sandbox.google.com"
+    local gfe_flags=$(_grpc_prod_gfe_flags)
     local the_cmd="$cmd_prefix $test_script $gfe_flags $@";
     echo $the_cmd
 }
 
 # constructs the full dockerized php interop test cmd.
 #
-# TODO(mlumish): update this to use the script once that's on git-on-borg
+# TODO(mlumish): update this to use the script once that's on git
 #
 # call-seq:
 #   flags= .... # generic flags to include the command
@@ -909,8 +1004,19 @@
     echo $the_cmd
 }
 
-# constructs the full dockerized cpp interop test cmd.
+# constructs the full dockerized node interop test cmd.
 #
+# call-seq:
+#   flags= .... # generic flags to include the command
+#   cmd=$($grpc_gen_test_cmd $flags)
+grpc_interop_gen_node_cmd() {
+  local cmd_prefix="sudo docker run grpc/node";
+  local test_script="/usr/bin/nodejs /var/local/git/grpc/src/node/interop/interop_client.js --use_tls=true";
+  local the_cmd="$cmd_prefix $test_script $@";
+  echo $the_cmd
+}
+
+# constructs the full dockerized cpp interop test cmd.
 #
 # call-seq:
 #   flags= .... # generic flags to include the command
@@ -922,55 +1028,60 @@
     echo $the_cmd
 }
 
-grpc_interop_gen_node_cmd() {
-  local cmd_prefix="sudo docker run grpc/node";
-  local test_script="/usr/bin/nodejs /var/local/git/grpc/src/node/interop/interop_client.js --use_tls=true";
-  local the_cmd="$cmd_prefix $test_script $@";
-  echo $the_cmd
-}
-
-# constructs the full dockerized cpp interop test cmd.
-#
+# constructs the full dockerized cpp gce=>prod interop test cmd.
 #
 # call-seq:
 #   flags= .... # generic flags to include the command
 #   cmd=$($grpc_gen_test_cmd $flags)
 grpc_cloud_prod_gen_cxx_cmd() {
     local cmd_prefix="sudo docker run grpc/cxx";
-    local test_script="/var/local/git/grpc/bins/opt/interop_client --enable_ssl";
-    local gfe_flags=" --use_prod_roots --server_port=443 --server_host=grpc-test.sandbox.google.com --server_host_override=grpc-test.sandbox.google.com"
+    local test_script="/var/local/git/grpc/bins/opt/interop_client --enable_ssl --use_prod_roots";
+    local gfe_flags=$(_grpc_prod_gfe_flags)
     local the_cmd="$cmd_prefix $test_script $gfe_flags $@";
     echo $the_cmd
 }
 
-# constructs the full dockerized cpp interop test cmd.
-#
+# constructs the full dockerized cpp service_account auth interop test cmd.
 #
 # call-seq:
 #   flags= .... # generic flags to include the command
 #   cmd=$($grpc_gen_test_cmd $flags)
 grpc_cloud_prod_auth_service_account_creds_gen_cxx_cmd() {
     local cmd_prefix="sudo docker run grpc/cxx";
-    local test_script="/var/local/git/grpc/bins/opt/interop_client --enable_ssl";
-    local gfe_flags=" --use_prod_roots --server_port=443 --server_host=grpc-test.sandbox.google.com --server_host_override=grpc-test.sandbox.google.com"
-    local added_gfe_flags=" --service_account_key_file=/service_account/stubbyCloudTestingTest-7dd63462c60c.json --oauth_scope=https://www.googleapis.com/auth/xapi.zoo"
+    local test_script="/var/local/git/grpc/bins/opt/interop_client --enable_ssl --use_prod_roots";
+    local gfe_flags=$(_grpc_prod_gfe_flags)
+    local added_gfe_flags=$(_grpc_svc_acc_test_flags)
     local the_cmd="$cmd_prefix $test_script $gfe_flags $added_gfe_flags $@";
     echo $the_cmd
 }
 
-# constructs the full dockerized cpp interop test cmd.
-#
+# constructs the full dockerized cpp gce auth interop test cmd.
 #
 # call-seq:
 #   flags= .... # generic flags to include the command
 #   cmd=$($grpc_gen_test_cmd $flags)
 grpc_cloud_prod_auth_compute_engine_creds_gen_cxx_cmd() {
     local cmd_prefix="sudo docker run grpc/cxx";
-    local test_script="/var/local/git/grpc/bins/opt/interop_client --enable_ssl";
-    local gfe_flags=" --use_prod_roots --server_port=443 --server_host=grpc-test.sandbox.google.com --server_host_override=grpc-test.sandbox.google.com"
-    local added_gfe_flags=" --default_service_account=155450119199-r5aaqa2vqoa9g5mv2m6s3m1l293rlmel@developer.gserviceaccount.com --oauth_scope=https://www.googleapis.com/auth/xapi.zoo"
+    local test_script="/var/local/git/grpc/bins/opt/interop_client --enable_ssl --use_prod_roots";
+    local gfe_flags=$(_grpc_prod_gfe_flags)
+    local added_gfe_flags=$(_grpc_gce_test_flags)
     local the_cmd="$cmd_prefix $test_script $gfe_flags $added_gfe_flags $@";
     echo $the_cmd
 }
 
-# TODO(grpc-team): add grpc_interop_gen_xxx_cmd for python|nodejs
+# outputs the flags passed to gfe tests
+_grpc_prod_gfe_flags() {
+  echo " --server_port=443 --server_host=grpc-test.sandbox.google.com --server_host_override=grpc-test.sandbox.google.com"
+}
+
+# outputs the flags passed to the service account auth tests
+_grpc_svc_acc_test_flags() {
+  echo " --service_account_key_file=/service_account/stubbyCloudTestingTest-7dd63462c60c.json --oauth_scope=https://www.googleapis.com/auth/xapi.zoo"
+}
+
+# outputs the flags passed to the gcloud auth tests
+_grpc_gce_test_flags() {
+  echo " --default_service_account=155450119199-r5aaqa2vqoa9g5mv2m6s3m1l293rlmel@developer.gserviceaccount.com --oauth_scope=https://www.googleapis.com/auth/xapi.zoo"
+}
+
+# TODO(grpc-team): add grpc_interop_gen_xxx_cmd for python
diff --git a/tools/gce_setup/interop_test_runner.sh b/tools/gce_setup/interop_test_runner.sh
index 1c0d820..456ad4b 100755
--- a/tools/gce_setup/interop_test_runner.sh
+++ b/tools/gce_setup/interop_test_runner.sh
@@ -1,10 +1,14 @@
 #!/bin/bash
+thisfile=$(readlink -ne "${BASH_SOURCE[0]}")
+current_time=$(date "+%Y-%m-%d-%H-%M-%S")
+result_file_name=interop_result.$current_time.html
+echo $result_file_name
 
 main() {
   source grpc_docker.sh
   test_cases=(large_unary empty_unary ping_pong client_streaming server_streaming)
-  clients=(cxx java go ruby)
-  servers=(cxx java go ruby)
+  clients=(cxx java go ruby node)
+  servers=(cxx java go ruby node)
   for test_case in "${test_cases[@]}"
   do
     for client in "${clients[@]}"
@@ -13,15 +17,21 @@
       do
         if grpc_interop_test $test_case grpc-docker-testclients $client grpc-docker-server $server
         then
-          echo "$test_case $client $server passed" >> /tmp/interop_result.txt
+          echo "          ['$test_case', '$client', '$server', true]," >> /tmp/interop_result.txt
         else
-          echo "$test_case $client $server failed" >> /tmp/interop_result.txt
+          echo "          ['$test_case', '$client', '$server', false]," >> /tmp/interop_result.txt
         fi
       done
     done
   done
-  gsutil cp /tmp/interop_result.txt gs://stoked-keyword-656-output/interop_result.txt
-  rm /tmp/interop_result.txt
+  if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
+    cat pre.html /tmp/interop_result.txt post.html > /tmp/interop_result.html
+    gsutil cp /tmp/interop_result.txt gs://stoked-keyword-656-output/interop_result.txt
+    gsutil cp /tmp/interop_result.html gs://stoked-keyword-656-output/interop_result.html
+    gsutil cp /tmp/interop_result.html gs://stoked-keyword-656-output/result_history/$result_file_name
+    rm /tmp/interop_result.txt
+    rm /tmp/interop_result.html
+  fi
 }
 
 set -x
diff --git a/tools/gce_setup/new_grpc_docker_builder.sh b/tools/gce_setup/new_grpc_docker_builder.sh
index 5d4fc36..ea36cc5 100755
--- a/tools/gce_setup/new_grpc_docker_builder.sh
+++ b/tools/gce_setup/new_grpc_docker_builder.sh
@@ -86,7 +86,6 @@
   [[ -n $the_address ]] && address_flag="--address $the_address"
   local the_image='container-vm-v20140925'
   local scopes='compute-rw storage-full'
-  scopes+=' https://www.googleapis.com/auth/gerritcodereview'
   scopes+=' https://www.googleapis.com/auth/xapi.zoo'
   gcloud --project $project compute instances create $instance \
     $address_flag \
diff --git a/tools/gce_setup/new_grpc_docker_builder_on_startup.sh b/tools/gce_setup/new_grpc_docker_builder_on_startup.sh
index 87e8aac..cfd0541 100755
--- a/tools/gce_setup/new_grpc_docker_builder_on_startup.sh
+++ b/tools/gce_setup/new_grpc_docker_builder_on_startup.sh
@@ -3,8 +3,7 @@
 #
 # A grpc-docker GCE machine is based on docker container image.
 #
-# On startup, it copies the grpc dockerfiles to a local directory, and update its address
-# so that the docker containers within it have git-on-borg-access.
+# On startup, it copies the grpc dockerfiles to a local directory, and update its address.
 
 # _load_metadata curls a metadata url
 _load_metadata() {
@@ -42,7 +41,82 @@
   source $script_path
 }
 
+# Args:
+#   $1: numerator
+#   $2: denominator
+#   $3: threshold (optional; defaults to $THRESHOLD)
+#
+# Returns:
+#   1 if (numerator / denominator > threshold)
+#   0 otherwise
+_gce_disk_cmp_ratio() {
+  local DEFAULT_THRESHOLD="1.1"
+  local numer="${1}"
+  local denom="${2}"
+  local threshold="${3:-${DEFAULT_THRESHOLD}}"
+
+  if `which python > /dev/null 2>&1`; then
+    python -c "print(1 if (1. * ${numer} / ${denom} > ${threshold}) else 0)"
+  else
+    echo "Can't find python; calculation not done." 1>&2
+    return 1
+  fi
+}
+
+# Repartitions the disk or resizes the file system, depending on the current
+# state of the partition table.
+#
+# Automates the process described in
+# - https://cloud.google.com/compute/docs/disks/persistent-disks#repartitionrootpd
+_gce_disk_maybe_resize_then_reboot() {
+  # Determine the size in blocks, of the whole disk and the first partition.
+  local dev_sda="$(fdisk -s /dev/sda)"
+  local dev_sda1="$(fdisk -s /dev/sda1)"
+  local dev_sda1_start="$(sudo fdisk -l /dev/sda | grep /dev/sda1 | sed -e 's/ \+/ /g' | cut -d' ' -f 3)"
+
+  # Use fdisk to
+  # - first see if the partion 1 is using as much of the disk as it should
+  # - then to resize the partition if it's not
+  #
+  # fdisk(1) flags:
+  # -c: disable DOS compatibility mode
+  # -u: change display mode to sectors (from cylinders)
+  #
+  # fdisk(1) commands:
+  # d: delete partition (automatically selects the first one)
+  # n: new partition
+  # p: primary
+  # 1: partition number
+  # $dev_sda1_start: specify the value for the start sector, the default may be incorrect
+  # <1 blank lines>: accept the defaults for end sectors
+  # w: write partition table
+  if [ $(_gce_disk_cmp_ratio "${dev_sda}" "${dev_sda1}") -eq 1 ]; then
+    echo "$FUNCNAME: Updating the partition table to use full ${dev_sda} instead ${dev_sda1}"
+    cat <<EOF | fdisk -c -u /dev/sda
+d
+n
+p
+1
+$dev_sda1_start
+
+w
+EOF
+    echo "$FUNCNAME: ... updated the partition table"
+    shutdown -r now
+    return 0
+  fi
+
+  # After repartitioning, use resize2fs to expand sda1.
+  local df_size="$(df -B 1K / | grep ' /$' | sed -e 's/ \+/ /g' | cut -d' ' -f 2)"
+  if [ $(_gce_disk_cmp_ratio "${dev_sda}" "${df_size}") -eq 1 ]; then
+    echo "$FUNCNAME: resizing the partition to make full use of it"
+    resize2fs /dev/sda1
+    echo "$FUNCNAME: ... resize completed"
+  fi
+}
+
 main() {
+    _gce_disk_maybe_resize_then_reboot
     local script_attr='shared_startup_script_url'
     _source_gs_script $script_attr || {
       echo "halting, script 'attributes/$script_attr' could not be sourced"
@@ -54,10 +128,6 @@
     # Install git and emacs
     apt-get update && apt-get install -y git emacs || return 1
 
-    # Enable access to git repos on git-on-borg
-    local git_root='/var/local/git'
-    install_gob_daemon $git_root/gerrit-gcompute-tools || return 1
-
     # Startup the docker registry
     grpc_docker_launch_registry && grpc_docker_pull_known
 
diff --git a/tools/gce_setup/post.html b/tools/gce_setup/post.html
new file mode 100644
index 0000000..57cbc8c
--- /dev/null
+++ b/tools/gce_setup/post.html
@@ -0,0 +1,12 @@
+        ]);
+
+        var table = new google.visualization.Table(document.getElementById('table_div'));
+
+        table.draw(data, {showRowNumber: true});
+      }
+    </script>
+  </head>
+  <body>
+    <div id="table_div"></div>
+  </body>
+</html>
diff --git a/tools/gce_setup/pre.html b/tools/gce_setup/pre.html
new file mode 100644
index 0000000..74ce5ce
--- /dev/null
+++ b/tools/gce_setup/pre.html
@@ -0,0 +1,14 @@
+<html>
+  <head>
+    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
+    <script type="text/javascript">
+      google.load("visualization", "1", {packages:["table"]});
+      google.setOnLoadCallback(drawTable);
+
+      function drawTable() {
+        var data = new google.visualization.DataTable();
+        data.addColumn('string', 'TestCase');
+        data.addColumn('string', 'Client');
+        data.addColumn('string', 'Server');
+        data.addColumn('boolean', 'Pass');
+        data.addRows([
diff --git a/tools/gce_setup/shared_startup_funcs.sh b/tools/gce_setup/shared_startup_funcs.sh
index 3300eb2..a6f73d1 100755
--- a/tools/gce_setup/shared_startup_funcs.sh
+++ b/tools/gce_setup/shared_startup_funcs.sh
@@ -251,37 +251,6 @@
   }
 }
 
-# Allows instances to checkout repos on git-on-borg.
-#
-install_gob_daemon() {
-  local gob_dir=$1
-  [[ -n $gob_dir ]] || { echo "missing args: gob_dir" >&2; return 1;  }
-
-  local gob_repo=$2
-  [[ -n $gob_repo ]] || gob_repo='https://gerrit.googlesource.com/gcompute-tools/'
-
-  if [[ -e $gob_dir ]]
-  then
-    rm -fv $gob_dir || {
-      echo "could not remove existing git repo at $gob_dir" >&2
-      return 1
-    }
-  fi
-
-  git clone $gob_repo $gob_dir || { echo "failed to pull gerrit cookie repo" >&2; return 1; }
-  local startup_script=/etc/profile.d/gob_cookie_daemon.sh
-
-  cat <<EOF >> $startup_script
-#!/bin/bash
-
-$gob_dir/git-cookie-authdaemon
-
-EOF
-
-  chmod 755 $startup_script
-  $startup_script
-}
-
 # grpc_docker_add_docker_group
 #
 # Adds a docker group, restarts docker, relaunches the docker registry
@@ -403,7 +372,9 @@
 
   [[ -d $dockerfile_dir ]] || { echo "$FUNCNAME: not a valid dir: $dockerfile_dir"; return 1; }
 
-  # For specific base images, sync the ssh key into the .ssh dir in the dockerfile context
+  # For specific base images, sync private files.
+  #
+  # - the ssh key, ssh certs and/or service account info.
   [[ $image_label == "grpc/base" ]] && {
     grpc_docker_sync_github_key $dockerfile_dir/.ssh 'base_ssh_key' || return 1;
   }
@@ -415,8 +386,10 @@
   }
   [[ $image_label == "grpc/ruby" ]] && {
     grpc_docker_sync_roots_pem $dockerfile_dir/cacerts || return 1;
+    grpc_docker_sync_service_account $dockerfile_dir/service_account || return 1;
   }
   [[ $image_label == "grpc/cxx" ]] && {
+    grpc_docker_sync_roots_pem $dockerfile_dir/cacerts || return 1;
     grpc_docker_sync_service_account $dockerfile_dir/service_account || return 1;
   }
 
diff --git a/tools/run_tests/build_python.sh b/tools/run_tests/build_python.sh
index 6899ac7..4abb412 100755
--- a/tools/run_tests/build_python.sh
+++ b/tools/run_tests/build_python.sh
@@ -7,4 +7,5 @@
 
 root=`pwd`
 virtualenv python2.7_virtual_environment
-python2.7_virtual_environment/bin/pip install enum34==1.0.4 futures==2.2.0
+python2.7_virtual_environment/bin/pip install enum34==1.0.4 futures==2.2.0 protobuf==2.6.1
+python2.7_virtual_environment/bin/pip install src/python
diff --git a/tools/run_tests/jobset.py b/tools/run_tests/jobset.py
index 8f16a4f..19ae52e 100755
--- a/tools/run_tests/jobset.py
+++ b/tools/run_tests/jobset.py
@@ -86,19 +86,49 @@
   raise Exception('%s not found' % filename)
 
 
+class JobSpec(object):
+  """Specifies what to run for a job."""
+
+  def __init__(self, cmdline, shortname=None, environ={}, hash_targets=[]):
+    """
+    Arguments:
+      cmdline: a list of arguments to pass as the command line
+      environ: a dictionary of environment variables to set in the child process
+      hash_targets: which files to include in the hash representing the jobs version
+                    (or empty, indicating the job should not be hashed)
+    """
+    self.cmdline = cmdline
+    self.environ = environ
+    self.shortname = cmdline[0] if shortname is None else shortname
+    self.hash_targets = hash_targets or []
+
+  def identity(self):
+    return '%r %r %r' % (self.cmdline, self.environ, self.hash_targets)
+
+  def __hash__(self):
+    return hash(self.identity())
+
+  def __cmp__(self, other):
+    return self.identity() == other.identity()
+
+
 class Job(object):
   """Manages one job."""
 
-  def __init__(self, cmdline, bin_hash, newline_on_success):
-    self._cmdline = cmdline
+  def __init__(self, spec, bin_hash, newline_on_success):
+    self._spec = spec
     self._bin_hash = bin_hash
     self._tempfile = tempfile.TemporaryFile()
-    self._process = subprocess.Popen(args=cmdline,
+    env = os.environ.copy()
+    for k, v in spec.environ.iteritems():
+      env[k] = v
+    self._process = subprocess.Popen(args=spec.cmdline,
                                      stderr=subprocess.STDOUT,
-                                     stdout=self._tempfile)
+                                     stdout=self._tempfile,
+                                     env=env)
     self._state = _RUNNING
     self._newline_on_success = newline_on_success
-    message('START', ' '.join(self._cmdline))
+    message('START', spec.shortname)
 
   def state(self, update_cache):
     """Poll current state of the job. Prints messages at completion."""
@@ -108,12 +138,13 @@
         self._tempfile.seek(0)
         stdout = self._tempfile.read()
         message('FAILED', '%s [ret=%d]' % (
-            ' '.join(self._cmdline), self._process.returncode), stdout)
+            self._spec.shortname, self._process.returncode), stdout)
       else:
         self._state = _SUCCESS
-        message('PASSED', '%s' % ' '.join(self._cmdline),
+        message('PASSED', self._spec.shortname,
                 do_newline=self._newline_on_success)
-        update_cache.finished(self._cmdline, self._bin_hash)
+        if self._bin_hash:
+          update_cache.finished(self._spec.identity(), self._bin_hash)
     return self._state
 
   def kill(self):
@@ -135,16 +166,26 @@
     self._newline_on_success = newline_on_success
     self._cache = cache
 
-  def start(self, cmdline):
+  def start(self, spec):
     """Start a job. Return True on success, False on failure."""
     while len(self._running) >= self._maxjobs:
       if self.cancelled(): return False
       self.reap()
     if self.cancelled(): return False
-    with open(which(cmdline[0])) as f:
-      bin_hash = hashlib.sha1(f.read()).hexdigest()
-    if self._cache.should_run(cmdline, bin_hash):
-      self._running.add(Job(cmdline, bin_hash, self._newline_on_success))
+    if spec.hash_targets:
+      bin_hash = hashlib.sha1()
+      for fn in spec.hash_targets:
+        with open(which(fn)) as f:
+          bin_hash.update(f.read())
+      bin_hash = bin_hash.hexdigest()
+      should_run = self._cache.should_run(spec.identity(), bin_hash)
+    else:
+      bin_hash = None
+      should_run = True
+    if should_run:
+      self._running.add(Job(spec,
+                            bin_hash,
+                            self._newline_on_success))
     return True
 
   def reap(self):
diff --git a/tools/run_tests/run_python.sh b/tools/run_tests/run_python.sh
index ef40602..6e9405a 100755
--- a/tools/run_tests/run_python.sh
+++ b/tools/run_tests/run_python.sh
@@ -6,6 +6,19 @@
 cd $(dirname $0)/../..
 
 root=`pwd`
-PYTHONPATH=third_party/protobuf/python python2.7_virtual_environment/bin/python2.7 -B -m unittest discover -s src/python -p '*.py'
-# TODO(nathaniel): Get this working again (requires 3.X-friendly protobuf)
+# TODO(issue 215): Properly itemize these in run_tests.py so that they can be parallelized.
+python2.7_virtual_environment/bin/python2.7 -B -m _adapter._blocking_invocation_inline_service_test
+python2.7_virtual_environment/bin/python2.7 -B -m _adapter._c_test
+python2.7_virtual_environment/bin/python2.7 -B -m _adapter._event_invocation_synchronous_event_service_test
+python2.7_virtual_environment/bin/python2.7 -B -m _adapter._future_invocation_asynchronous_event_service_test
+python2.7_virtual_environment/bin/python2.7 -B -m _adapter._links_test
+python2.7_virtual_environment/bin/python2.7 -B -m _adapter._lonely_rear_link_test
+python2.7_virtual_environment/bin/python2.7 -B -m _adapter._low_test
+python2.7_virtual_environment/bin/python2.7 -B -m _framework.base.packets.implementations_test
+python2.7_virtual_environment/bin/python2.7 -B -m _framework.face.blocking_invocation_inline_service_test
+python2.7_virtual_environment/bin/python2.7 -B -m _framework.face.event_invocation_synchronous_event_service_test
+python2.7_virtual_environment/bin/python2.7 -B -m _framework.face.future_invocation_asynchronous_event_service_test
+python2.7_virtual_environment/bin/python2.7 -B -m _framework.foundation._later_test
+python2.7_virtual_environment/bin/python2.7 -B -m _framework.foundation._logging_pool_test
+# TODO(nathaniel): Get tests working under 3.4 (requires 3.X-friendly protobuf)
 # python3.4 -B -m unittest discover -s src/python -p '*.py'
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index a699399..cb54c0d 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python2.7
 """Run tests in parallel."""
 
 import argparse
@@ -17,13 +17,17 @@
 # SimpleConfig: just compile with CONFIG=config, and run the binary to test
 class SimpleConfig(object):
 
-  def __init__(self, config):
+  def __init__(self, config, environ={}):
     self.build_config = config
     self.maxjobs = 2 * multiprocessing.cpu_count()
     self.allow_hashing = (config != 'gcov')
+    self.environ = environ
 
-  def run_command(self, binary):
-    return [binary]
+  def job_spec(self, binary, hash_targets):
+    return jobset.JobSpec(cmdline=[binary],
+                          environ=self.environ,
+                          hash_targets=hash_targets
+                              if self.allow_hashing else None)
 
 
 # ValgrindConfig: compile with some CONFIG=config, but use valgrind to run
@@ -35,14 +39,14 @@
     self.maxjobs = 2 * multiprocessing.cpu_count()
     self.allow_hashing = False
 
-  def run_command(self, binary):
-    return ['valgrind', binary, '--tool=%s' % self.tool]
+  def job_spec(self, binary, hash_targets):
+    return JobSpec(cmdline=['valgrind', '--tool=%s' % self.tool, binary],
+                   hash_targets=None)
 
 
 class CLanguage(object):
 
   def __init__(self, make_target, test_lang):
-    self.allow_hashing = True
     self.make_target = make_target
     with open('tools/run_tests/tests.json') as f:
       js = json.load(f)
@@ -50,8 +54,12 @@
                        for tgt in js
                        if tgt['language'] == test_lang]
 
-  def test_binaries(self, config):
-    return ['bins/%s/%s' % (config, binary) for binary in self.binaries]
+  def test_specs(self, config):
+    out = []
+    for name in self.binaries:
+      binary = 'bins/%s/%s' % (config.build_config, name)
+      out.append(config.job_spec(binary, [binary]))
+    return out
 
   def make_targets(self):
     return ['buildtests_%s' % self.make_target]
@@ -59,13 +67,11 @@
   def build_steps(self):
     return []
 
+
 class NodeLanguage(object):
 
-  def __init__(self):
-    self.allow_hashing = False
-
-  def test_binaries(self, config):
-    return ['tools/run_tests/run_node.sh']
+  def test_specs(self, config):
+    return [config.job_spec('tools/run_tests/run_node.sh', None)]
 
   def make_targets(self):
     return ['static_c']
@@ -73,13 +79,11 @@
   def build_steps(self):
     return [['tools/run_tests/build_node.sh']]
 
+
 class PhpLanguage(object):
 
-  def __init__(self):
-    self.allow_hashing = False
-
-  def test_binaries(self, config):
-    return ['src/php/bin/run_tests.sh']
+  def test_specs(self, config):
+    return [config.job_spec('src/php/bin/run_tests.sh', None)]
 
   def make_targets(self):
     return ['static_c']
@@ -90,11 +94,8 @@
 
 class PythonLanguage(object):
 
-  def __init__(self):
-    self.allow_hashing = False
-
-  def test_binaries(self, config):
-    return ['tools/run_tests/run_python.sh']
+  def test_specs(self, config):
+    return [config.job_spec('tools/run_tests/run_python.sh', None)]
 
   def make_targets(self):
     return[]
@@ -107,9 +108,11 @@
 _CONFIGS = {
     'dbg': SimpleConfig('dbg'),
     'opt': SimpleConfig('opt'),
-    'tsan': SimpleConfig('tsan'),
+    'tsan': SimpleConfig('tsan', environ={
+        'TSAN_OPTIONS': 'suppressions=tools/tsan_suppressions.txt'}),
     'msan': SimpleConfig('msan'),
-    'asan': SimpleConfig('asan'),
+    'asan': SimpleConfig('asan', environ={
+        'ASAN_OPTIONS': 'detect_leaks=1:color=always:suppressions=tools/tsan_suppressions.txt'}),
     'gcov': SimpleConfig('gcov'),
     'memcheck': ValgrindConfig('valgrind', 'memcheck'),
     'helgrind': ValgrindConfig('dbg', 'helgrind')
@@ -123,7 +126,7 @@
     'node': NodeLanguage(),
     'php': PhpLanguage(),
     'python': PythonLanguage(),
-}
+    }
 
 # parse command line
 argp = argparse.ArgumentParser(description='Run grpc tests.')
@@ -155,14 +158,20 @@
 
 make_targets = []
 languages = set(_LANGUAGES[l] for l in args.language)
-build_steps = [['make',
-                '-j', '%d' % (multiprocessing.cpu_count() + 1),
-                'CONFIG=%s' % cfg] + list(set(
-                    itertools.chain.from_iterable(l.make_targets()
-                                                  for l in languages)))
-               for cfg in build_configs] + list(
-                   itertools.chain.from_iterable(l.build_steps()
-                                                 for l in languages))
+build_steps = [jobset.JobSpec(['make',
+                               '-j', '%d' % (multiprocessing.cpu_count() + 1),
+                               'CONFIG=%s' % cfg] + list(set(
+                                   itertools.chain.from_iterable(
+                                       l.make_targets() for l in languages))))
+               for cfg in build_configs] + list(set(
+                   jobset.JobSpec(cmdline)
+                   for l in languages
+                   for cmdline in l.build_steps()))
+one_run = set(
+    spec
+    for config in run_configs
+    for language in args.language
+    for spec in _LANGUAGES[language].test_specs(config))
 
 runs_per_test = args.runs_per_test
 forever = args.forever
@@ -175,7 +184,6 @@
     self._last_successful_run = {}
 
   def should_run(self, cmdline, bin_hash):
-    cmdline = ' '.join(cmdline)
     if cmdline not in self._last_successful_run:
       return True
     if self._last_successful_run[cmdline] != bin_hash:
@@ -183,7 +191,7 @@
     return False
 
   def finished(self, cmdline, bin_hash):
-    self._last_successful_run[' '.join(cmdline)] = bin_hash
+    self._last_successful_run[cmdline] = bin_hash
 
   def dump(self):
     return [{'cmdline': k, 'hash': v}
@@ -209,12 +217,6 @@
     return 1
 
   # run all the tests
-  one_run = dict(
-      (' '.join(config.run_command(x)), config.run_command(x))
-      for config in run_configs
-      for language in args.language
-      for x in _LANGUAGES[language].test_binaries(config.build_config)
-      ).values()
   all_runs = itertools.chain.from_iterable(
       itertools.repeat(one_run, runs_per_test))
   if not jobset.run(all_runs, check_cancelled,
@@ -226,12 +228,8 @@
   return 0
 
 
-test_cache = (None
-              if not all(x.allow_hashing
-                         for x in itertools.chain(languages, run_configs))
-              else TestCache())
-if test_cache:
-  test_cache.maybe_load()
+test_cache = TestCache()
+test_cache.maybe_load()
 
 if forever:
   success = True
@@ -248,7 +246,7 @@
                      'All tests are now passing properly',
                      do_newline=True)
     jobset.message('IDLE', 'No change detected')
-    if test_cache: test_cache.save()
+    test_cache.save()
     while not have_files_changed():
       time.sleep(1)
 else:
@@ -259,5 +257,5 @@
     jobset.message('SUCCESS', 'All tests passed', do_newline=True)
   else:
     jobset.message('FAILED', 'Some tests failed', do_newline=True)
-  if test_cache: test_cache.save()
+  test_cache.save()
   sys.exit(result)
diff --git a/tools/run_tests/tests.json b/tools/run_tests/tests.json
index b3ebc1f..facef4e 100644
--- a/tools/run_tests/tests.json
+++ b/tools/run_tests/tests.json
@@ -111,6 +111,14 @@
   }, 
   {
     "language": "c", 
+    "name": "gpr_file_test"
+  }, 
+  {
+    "language": "c", 
+    "name": "gpr_env_test"
+  }, 
+  {
+    "language": "c", 
     "name": "gpr_slice_buffer_test"
   }, 
   {
@@ -187,6 +195,14 @@
   }, 
   {
     "language": "c", 
+    "name": "json_rewrite_test"
+  }, 
+  {
+    "language": "c", 
+    "name": "json_test"
+  }, 
+  {
+    "language": "c", 
     "name": "lame_client_test"
   }, 
   {
@@ -250,14 +266,6 @@
     "name": "transport_metadata_test"
   }, 
   {
-    "language": "c", 
-    "name": "json_test"
-  }, 
-  {
-    "language": "c", 
-    "name": "json_rewrite_test"
-  }, 
-  {
     "language": "c++", 
     "name": "channel_arguments_test"
   }, 
@@ -271,7 +279,11 @@
   }, 
   {
     "language": "c++", 
-    "name": "tips_client_test"
+    "name": "tips_publisher_test"
+  }, 
+  {
+    "language": "c++", 
+    "name": "tips_subscriber_test"
   }, 
   {
     "language": "c++", 
diff --git a/tools/tsan_suppressions.txt b/tools/tsan_suppressions.txt
new file mode 100644
index 0000000..23d57f9
--- /dev/null
+++ b/tools/tsan_suppressions.txt
@@ -0,0 +1,2 @@
+# OPENSSL_cleanse does racy access to a global
+race:OPENSSL_cleanse
diff --git a/vsprojects/vs2013/build_and_run_tests.bat b/vsprojects/vs2013/build_and_run_tests.bat
index 7700073..88d9b2f 100644
--- a/vsprojects/vs2013/build_and_run_tests.bat
+++ b/vsprojects/vs2013/build_and_run_tests.bat
@@ -49,6 +49,22 @@
 test_bin\gpr_log_test.exe || echo TEST FAILED: gpr_log_test && exit /b
 echo(
 
+echo Building test gpr_file_test
+cl.exe /c /I..\.. /I..\..\include /nologo /ZI /W3 /WX- /sdl /D WIN32 /D _LIB /D _USE_32BIT_TIME_T /D _UNICODE /D UNICODE /Gm /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Gd /TC /analyze- /Fo:test_bin\ ..\..\test\core\support\file_test.c 
+link.exe /OUT:"test_bin\gpr_file_test.exe" /INCREMENTAL /NOLOGO /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /MACHINE:X86 Debug\gpr_test_util.lib Debug\gpr.lib test_bin\file_test.obj 
+echo(
+echo Running test gpr_file_test
+test_bin\gpr_file_test.exe || echo TEST FAILED: gpr_file_test && exit /b
+echo(
+
+echo Building test gpr_env_test
+cl.exe /c /I..\.. /I..\..\include /nologo /ZI /W3 /WX- /sdl /D WIN32 /D _LIB /D _USE_32BIT_TIME_T /D _UNICODE /D UNICODE /Gm /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Gd /TC /analyze- /Fo:test_bin\ ..\..\test\core\support\env_test.c 
+link.exe /OUT:"test_bin\gpr_env_test.exe" /INCREMENTAL /NOLOGO /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /MACHINE:X86 Debug\gpr_test_util.lib Debug\gpr.lib test_bin\env_test.obj 
+echo(
+echo Running test gpr_env_test
+test_bin\gpr_env_test.exe || echo TEST FAILED: gpr_env_test && exit /b
+echo(
+
 echo Building test gpr_slice_buffer_test
 cl.exe /c /I..\.. /I..\..\include /nologo /ZI /W3 /WX- /sdl /D WIN32 /D _LIB /D _USE_32BIT_TIME_T /D _UNICODE /D UNICODE /Gm /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Gd /TC /analyze- /Fo:test_bin\ ..\..\test\core\support\slice_buffer_test.c 
 link.exe /OUT:"test_bin\gpr_slice_buffer_test.exe" /INCREMENTAL /NOLOGO /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /MACHINE:X86 Debug\gpr_test_util.lib Debug\gpr.lib test_bin\slice_buffer_test.obj 
diff --git a/vsprojects/vs2013/gpr.vcxproj b/vsprojects/vs2013/gpr.vcxproj
index f71b586..d1f7085 100644
--- a/vsprojects/vs2013/gpr.vcxproj
+++ b/vsprojects/vs2013/gpr.vcxproj
@@ -91,15 +91,13 @@
     <ClInclude Include="..\..\include\grpc\support\sync_posix.h" />
     <ClInclude Include="..\..\include\grpc\support\sync_win32.h" />
     <ClInclude Include="..\..\include\grpc\support\thd.h" />
-    <ClInclude Include="..\..\include\grpc\support\thd_posix.h" />
-    <ClInclude Include="..\..\include\grpc\support\thd_win32.h" />
     <ClInclude Include="..\..\include\grpc\support\time.h" />
-    <ClInclude Include="..\..\include\grpc\support\time_posix.h" />
-    <ClInclude Include="..\..\include\grpc\support\time_win32.h" />
     <ClInclude Include="..\..\include\grpc\support\useful.h" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\src\core\support\cpu.h" />
+    <ClInclude Include="..\..\src\core\support\env.h" />
+    <ClInclude Include="..\..\src\core\support\file.h" />
     <ClInclude Include="..\..\src\core\support\murmur_hash.h" />
     <ClInclude Include="..\..\src\core\support\string.h" />
     <ClInclude Include="..\..\src\core\support\thd_internal.h" />
@@ -115,6 +113,18 @@
     </ClCompile>
     <ClCompile Include="..\..\src\core\support\cpu_posix.c">
     </ClCompile>
+    <ClCompile Include="..\..\src\core\support\env_linux.c">
+    </ClCompile>
+    <ClCompile Include="..\..\src\core\support\env_posix.c">
+    </ClCompile>
+    <ClCompile Include="..\..\src\core\support\env_win32.c">
+    </ClCompile>
+    <ClCompile Include="..\..\src\core\support\file.c">
+    </ClCompile>
+    <ClCompile Include="..\..\src\core\support\file_posix.c">
+    </ClCompile>
+    <ClCompile Include="..\..\src\core\support\file_win32.c">
+    </ClCompile>
     <ClCompile Include="..\..\src\core\support\histogram.c">
     </ClCompile>
     <ClCompile Include="..\..\src\core\support\host_port.c">
diff --git a/vsprojects/vs2013/gpr.vcxproj.filters b/vsprojects/vs2013/gpr.vcxproj.filters
index 013ed4b..13add83 100644
--- a/vsprojects/vs2013/gpr.vcxproj.filters
+++ b/vsprojects/vs2013/gpr.vcxproj.filters
@@ -16,6 +16,24 @@
     <ClCompile Include="..\..\src\core\support\cpu_posix.c">
       <Filter>src\core\support</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\core\support\env_linux.c">
+      <Filter>src\core\support</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\core\support\env_posix.c">
+      <Filter>src\core\support</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\core\support\env_win32.c">
+      <Filter>src\core\support</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\core\support\file.c">
+      <Filter>src\core\support</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\core\support\file_posix.c">
+      <Filter>src\core\support</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\core\support\file_win32.c">
+      <Filter>src\core\support</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\core\support\histogram.c">
       <Filter>src\core\support</Filter>
     </ClCompile>
@@ -135,21 +153,9 @@
     <ClInclude Include="..\..\include\grpc\support\thd.h">
       <Filter>include\grpc\support</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\include\grpc\support\thd_posix.h">
-      <Filter>include\grpc\support</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\grpc\support\thd_win32.h">
-      <Filter>include\grpc\support</Filter>
-    </ClInclude>
     <ClInclude Include="..\..\include\grpc\support\time.h">
       <Filter>include\grpc\support</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\include\grpc\support\time_posix.h">
-      <Filter>include\grpc\support</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\grpc\support\time_win32.h">
-      <Filter>include\grpc\support</Filter>
-    </ClInclude>
     <ClInclude Include="..\..\include\grpc\support\useful.h">
       <Filter>include\grpc\support</Filter>
     </ClInclude>
@@ -158,6 +164,12 @@
     <ClInclude Include="..\..\src\core\support\cpu.h">
       <Filter>src\core\support</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\core\support\env.h">
+      <Filter>src\core\support</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\core\support\file.h">
+      <Filter>src\core\support</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\core\support\murmur_hash.h">
       <Filter>src\core\support</Filter>
     </ClInclude>
diff --git a/vsprojects/vs2013/grpc.vcxproj b/vsprojects/vs2013/grpc.vcxproj
index 8756bbf..8c12b2d 100644
--- a/vsprojects/vs2013/grpc.vcxproj
+++ b/vsprojects/vs2013/grpc.vcxproj
@@ -134,8 +134,8 @@
     <ClInclude Include="..\..\src\core\iomgr\tcp_posix.h" />
     <ClInclude Include="..\..\src\core\iomgr\tcp_server.h" />
     <ClInclude Include="..\..\src\core\iomgr\time_averaged_stats.h" />
-    <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_posix.h" />
     <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_pipe.h" />
+    <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_posix.h" />
     <ClInclude Include="..\..\src\core\json\json.h" />
     <ClInclude Include="..\..\src\core\json\json_common.h" />
     <ClInclude Include="..\..\src\core\json\json_reader.h" />
@@ -146,6 +146,7 @@
     <ClInclude Include="..\..\src\core\statistics\census_tracing.h" />
     <ClInclude Include="..\..\src\core\statistics\hash_table.h" />
     <ClInclude Include="..\..\src\core\statistics\window_stats.h" />
+    <ClInclude Include="..\..\src\core\surface\byte_buffer_queue.h" />
     <ClInclude Include="..\..\src\core\surface\call.h" />
     <ClInclude Include="..\..\src\core\surface\channel.h" />
     <ClInclude Include="..\..\src\core\surface\client.h" />
@@ -312,6 +313,8 @@
     </ClCompile>
     <ClCompile Include="..\..\src\core\surface\byte_buffer.c">
     </ClCompile>
+    <ClCompile Include="..\..\src\core\surface\byte_buffer_queue.c">
+    </ClCompile>
     <ClCompile Include="..\..\src\core\surface\byte_buffer_reader.c">
     </ClCompile>
     <ClCompile Include="..\..\src\core\surface\call.c">
diff --git a/vsprojects/vs2013/grpc.vcxproj.filters b/vsprojects/vs2013/grpc.vcxproj.filters
index 5d39d40..62f0b60 100644
--- a/vsprojects/vs2013/grpc.vcxproj.filters
+++ b/vsprojects/vs2013/grpc.vcxproj.filters
@@ -202,6 +202,9 @@
     <ClCompile Include="..\..\src\core\surface\byte_buffer.c">
       <Filter>src\core\surface</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\core\surface\byte_buffer_queue.c">
+      <Filter>src\core\surface</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\core\surface\byte_buffer_reader.c">
       <Filter>src\core\surface</Filter>
     </ClCompile>
@@ -485,10 +488,10 @@
     <ClInclude Include="..\..\src\core\iomgr\time_averaged_stats.h">
       <Filter>src\core\iomgr</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_posix.h">
+    <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_pipe.h">
       <Filter>src\core\iomgr</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_pipe.h">
+    <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_posix.h">
       <Filter>src\core\iomgr</Filter>
     </ClInclude>
     <ClInclude Include="..\..\src\core\json\json.h">
@@ -521,6 +524,9 @@
     <ClInclude Include="..\..\src\core\statistics\window_stats.h">
       <Filter>src\core\statistics</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\core\surface\byte_buffer_queue.h">
+      <Filter>src\core\surface</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\core\surface\call.h">
       <Filter>src\core\surface</Filter>
     </ClInclude>
diff --git a/vsprojects/vs2013/grpc_unsecure.vcxproj b/vsprojects/vs2013/grpc_unsecure.vcxproj
index 8756bbf..8c12b2d 100644
--- a/vsprojects/vs2013/grpc_unsecure.vcxproj
+++ b/vsprojects/vs2013/grpc_unsecure.vcxproj
@@ -134,8 +134,8 @@
     <ClInclude Include="..\..\src\core\iomgr\tcp_posix.h" />
     <ClInclude Include="..\..\src\core\iomgr\tcp_server.h" />
     <ClInclude Include="..\..\src\core\iomgr\time_averaged_stats.h" />
-    <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_posix.h" />
     <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_pipe.h" />
+    <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_posix.h" />
     <ClInclude Include="..\..\src\core\json\json.h" />
     <ClInclude Include="..\..\src\core\json\json_common.h" />
     <ClInclude Include="..\..\src\core\json\json_reader.h" />
@@ -146,6 +146,7 @@
     <ClInclude Include="..\..\src\core\statistics\census_tracing.h" />
     <ClInclude Include="..\..\src\core\statistics\hash_table.h" />
     <ClInclude Include="..\..\src\core\statistics\window_stats.h" />
+    <ClInclude Include="..\..\src\core\surface\byte_buffer_queue.h" />
     <ClInclude Include="..\..\src\core\surface\call.h" />
     <ClInclude Include="..\..\src\core\surface\channel.h" />
     <ClInclude Include="..\..\src\core\surface\client.h" />
@@ -312,6 +313,8 @@
     </ClCompile>
     <ClCompile Include="..\..\src\core\surface\byte_buffer.c">
     </ClCompile>
+    <ClCompile Include="..\..\src\core\surface\byte_buffer_queue.c">
+    </ClCompile>
     <ClCompile Include="..\..\src\core\surface\byte_buffer_reader.c">
     </ClCompile>
     <ClCompile Include="..\..\src\core\surface\call.c">
diff --git a/vsprojects/vs2013/grpc_unsecure.vcxproj.filters b/vsprojects/vs2013/grpc_unsecure.vcxproj.filters
index b644d54..5ed5e9b 100644
--- a/vsprojects/vs2013/grpc_unsecure.vcxproj.filters
+++ b/vsprojects/vs2013/grpc_unsecure.vcxproj.filters
@@ -163,6 +163,9 @@
     <ClCompile Include="..\..\src\core\surface\byte_buffer.c">
       <Filter>src\core\surface</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\core\surface\byte_buffer_queue.c">
+      <Filter>src\core\surface</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\core\surface\byte_buffer_reader.c">
       <Filter>src\core\surface</Filter>
     </ClCompile>
@@ -410,10 +413,10 @@
     <ClInclude Include="..\..\src\core\iomgr\time_averaged_stats.h">
       <Filter>src\core\iomgr</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_posix.h">
+    <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_pipe.h">
       <Filter>src\core\iomgr</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_pipe.h">
+    <ClInclude Include="..\..\src\core\iomgr\wakeup_fd_posix.h">
       <Filter>src\core\iomgr</Filter>
     </ClInclude>
     <ClInclude Include="..\..\src\core\json\json.h">
@@ -446,6 +449,9 @@
     <ClInclude Include="..\..\src\core\statistics\window_stats.h">
       <Filter>src\core\statistics</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\core\surface\byte_buffer_queue.h">
+      <Filter>src\core\surface</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\core\surface\call.h">
       <Filter>src\core\surface</Filter>
     </ClInclude>