Merge remote-tracking branch 'upstream/master'
diff --git a/Makefile b/Makefile
index a3f1aaf..13f5129 100644
--- a/Makefile
+++ b/Makefile
@@ -383,6 +383,9 @@
 end2end_test: bins/$(CONFIG)/end2end_test
 interop_client: bins/$(CONFIG)/interop_client
 interop_server: bins/$(CONFIG)/interop_server
+tips_client: bins/$(CONFIG)/tips_client
+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
@@ -570,7 +573,7 @@
 
 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)/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)/qps_client bins/$(CONFIG)/qps_server bins/$(CONFIG)/status_test bins/$(CONFIG)/sync_client_async_server_test bins/$(CONFIG)/thread_pool_test bins/$(CONFIG)/tips_client bins/$(CONFIG)/tips_client_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 bins/$(CONFIG)/tips_client bins/$(CONFIG)/tips_client_test
 
 test: test_c test_cxx
 
@@ -974,6 +977,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_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"
@@ -2242,7 +2249,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 +2268,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 +2296,8 @@
 
 
 
-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
 
 
 LIBEND2END_FIXTURE_CHTTP2_FAKE_SECURITY_SRC = \
@@ -5689,6 +5699,99 @@
 endif
 
 
+TIPS_CLIENT_SRC = \
+    examples/tips/main.cc \
+
+TIPS_CLIENT_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(TIPS_CLIENT_SRC))))
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL with ALPN.
+
+bins/$(CONFIG)/tips_client: openssl_dep_error
+
+else
+
+bins/$(CONFIG)/tips_client: $(TIPS_CLIENT_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_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
+
+endif
+
+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)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(TIPS_CLIENT_OBJS:.o=.dep)
+endif
+endif
+
+
+TIPS_PUBLISHER_TEST_SRC = \
+    examples/tips/publisher_test.cc \
+
+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_publisher_test: openssl_dep_error
+
+else
+
+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_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/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_publisher_test: $(TIPS_PUBLISHER_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-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
+
+
 QPS_CLIENT_SRC = \
     gens/test/cpp/qps/qpstest.pb.cc \
     test/cpp/qps/client.cc \
diff --git a/build.json b/build.json
index 119e612..1cd8775 100644
--- a/build.json
+++ b/build.json
@@ -433,7 +433,8 @@
         "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++",
@@ -1568,6 +1569,58 @@
       "run": false
     },
     {
+      "name": "tips_client",
+      "build": "test",
+      "run": false,
+      "language": "c++",
+      "src": [
+        "examples/tips/main.cc"
+      ],
+      "deps": [
+        "tips_client_lib",
+        "grpc++_test_util",
+        "grpc_test_util",
+        "grpc++",
+        "grpc",
+        "gpr_test_util",
+        "gpr"
+      ]
+    },
+    {
+      "name": "tips_publisher_test",
+      "build": "test",
+      "language": "c++",
+      "src": [
+        "examples/tips/publisher_test.cc"
+      ],
+      "deps": [
+        "tips_client_lib",
+        "grpc++_test_util",
+        "grpc_test_util",
+        "grpc++",
+        "grpc",
+        "gpr_test_util",
+        "gpr"
+      ]
+    },
+    {
+      "name": "tips_subscriber_test",
+      "build": "test",
+      "language": "c++",
+      "src": [
+        "examples/tips/subscriber_test.cc"
+      ],
+      "deps": [
+        "tips_client_lib",
+        "grpc++_test_util",
+        "grpc_test_util",
+        "grpc++",
+        "grpc",
+        "gpr_test_util",
+        "gpr"
+      ]
+    },
+    {
       "name": "qps_client",
       "build": "test",
       "language": "c++",
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/tools/run_tests/tests.json b/tools/run_tests/tests.json
index fd15182..01d5dc7 100644
--- a/tools/run_tests/tests.json
+++ b/tools/run_tests/tests.json
@@ -271,6 +271,14 @@
   }, 
   {
     "language": "c++", 
+    "name": "tips_publisher_test"
+  }, 
+  {
+    "language": "c++", 
+    "name": "tips_subscriber_test"
+  }, 
+  {
+    "language": "c++", 
     "name": "status_test"
   }, 
   {