Merge pull request #13 from ctiller/nogo

Remove go codegen from this repository
diff --git a/.gitignore b/.gitignore
index 5202b53..3cae07e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
 bins
+coverage
 deps
+*.gcno
+gens
 libs
 objs
 *.pyc
-
diff --git a/.gitmodules b/.gitmodules
index 9a287d9..97b7197 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -9,10 +9,6 @@
 	path = third_party/protobuf
 	url = https://github.com/google/protobuf.git
 	branch = v3.0.0-alpha-1
-[submodule "third_party/libevent"]
-	path = third_party/libevent
-	url = https://github.com/libevent/libevent.git
-	branch = patches-2.0
 [submodule "third_party/gflags"]
 	path = third_party/gflags
 	url = https://code.google.com/p/gflags
diff --git a/INSTALL b/INSTALL
index 20e27c1..a9b0b58 100644
--- a/INSTALL
+++ b/INSTALL
@@ -12,8 +12,7 @@
 if you were to clone recursively our git repository.
 
 
-grpc core currently depends on zlib and OpenSSL 1.0.2beta3, and also requires
-libevent2 for the Linux port.
+grpc core currently depends on zlib and OpenSSL 1.0.2beta3.
 
 grpc++'s tests depends on protobuf 3.0.0, gtests and gflags.
 
@@ -46,7 +45,7 @@
 You may also install the dependencies yourself, from the sources, or from
 your distribution's package manager.
 
-The development packages needed for grpc are libevent2 under Linux, and zlib.
+The only development package needed for grpc is zlib.
 The development packages needed for grpc++'s tests are gtests, and gflags.
 
 To the best of our knowledge, no distribution has an OpenSSL package that
diff --git a/Makefile b/Makefile
index ff98ed7..388e5f3 100644
--- a/Makefile
+++ b/Makefile
@@ -49,6 +49,15 @@
 LDFLAGS_msan = -fsanitize=memory
 DEFINES_msan = NDEBUG
 
+VALID_CONFIG_gcov = 1
+CC_gcov = gcc
+CXX_gcov = g++
+LD_gcov = gcc
+LDXX_gcov = g++
+CPPFLAGS_gcov = -O0 -fprofile-arcs -ftest-coverage
+LDFLAGS_gcov = -fprofile-arcs -ftest-coverage
+DEFINES_gcov = NDEBUG
+
 # General settings.
 # You may want to change these depending on your system.
 
@@ -486,9 +495,9 @@
 
 privatelibs: privatelibs_c privatelibs_cxx
 
-privatelibs_c: dep_c libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libend2end_fixture_chttp2_fake_security.a libs/$(CONFIG)/libend2end_fixture_chttp2_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_simple_ssl_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_simple_ssl_with_oauth2_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_socket_pair.a libs/$(CONFIG)/libend2end_fixture_chttp2_socket_pair_one_byte_at_a_time.a libs/$(CONFIG)/libend2end_test_cancel_after_accept.a libs/$(CONFIG)/libend2end_test_cancel_after_accept_and_writes_closed.a libs/$(CONFIG)/libend2end_test_cancel_after_invoke.a libs/$(CONFIG)/libend2end_test_cancel_before_invoke.a libs/$(CONFIG)/libend2end_test_cancel_in_a_vacuum.a libs/$(CONFIG)/libend2end_test_census_simple_request.a libs/$(CONFIG)/libend2end_test_disappearing_server.a libs/$(CONFIG)/libend2end_test_early_server_shutdown_finishes_inflight_calls.a libs/$(CONFIG)/libend2end_test_early_server_shutdown_finishes_tags.a libs/$(CONFIG)/libend2end_test_invoke_large_request.a libs/$(CONFIG)/libend2end_test_max_concurrent_streams.a libs/$(CONFIG)/libend2end_test_no_op.a libs/$(CONFIG)/libend2end_test_ping_pong_streaming.a libs/$(CONFIG)/libend2end_test_request_response_with_binary_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_trailing_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_simple_delayed_request.a libs/$(CONFIG)/libend2end_test_simple_request.a libs/$(CONFIG)/libend2end_test_thread_stress.a libs/$(CONFIG)/libend2end_test_writes_done_hangs_with_pending_read.a libs/$(CONFIG)/libend2end_certs.a
+privatelibs_c: dep_c libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libend2end_fixture_chttp2_fake_security.a libs/$(CONFIG)/libend2end_fixture_chttp2_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_simple_ssl_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_simple_ssl_with_oauth2_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_socket_pair.a libs/$(CONFIG)/libend2end_fixture_chttp2_socket_pair_one_byte_at_a_time.a libs/$(CONFIG)/libend2end_test_cancel_after_accept.a libs/$(CONFIG)/libend2end_test_cancel_after_accept_and_writes_closed.a libs/$(CONFIG)/libend2end_test_cancel_after_invoke.a libs/$(CONFIG)/libend2end_test_cancel_before_invoke.a libs/$(CONFIG)/libend2end_test_cancel_in_a_vacuum.a libs/$(CONFIG)/libend2end_test_census_simple_request.a libs/$(CONFIG)/libend2end_test_disappearing_server.a libs/$(CONFIG)/libend2end_test_early_server_shutdown_finishes_inflight_calls.a libs/$(CONFIG)/libend2end_test_early_server_shutdown_finishes_tags.a libs/$(CONFIG)/libend2end_test_invoke_large_request.a libs/$(CONFIG)/libend2end_test_max_concurrent_streams.a libs/$(CONFIG)/libend2end_test_no_op.a libs/$(CONFIG)/libend2end_test_ping_pong_streaming.a libs/$(CONFIG)/libend2end_test_request_response_with_binary_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_trailing_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_simple_delayed_request.a libs/$(CONFIG)/libend2end_test_simple_request.a libs/$(CONFIG)/libend2end_test_thread_stress.a libs/$(CONFIG)/libend2end_test_writes_done_hangs_with_pending_read.a libs/$(CONFIG)/libend2end_certs.a
 
-privatelibs_cxx: dep_cxx libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libend2end_fixture_chttp2_fake_security.a libs/$(CONFIG)/libend2end_fixture_chttp2_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_simple_ssl_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_simple_ssl_with_oauth2_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_socket_pair.a libs/$(CONFIG)/libend2end_fixture_chttp2_socket_pair_one_byte_at_a_time.a libs/$(CONFIG)/libend2end_test_cancel_after_accept.a libs/$(CONFIG)/libend2end_test_cancel_after_accept_and_writes_closed.a libs/$(CONFIG)/libend2end_test_cancel_after_invoke.a libs/$(CONFIG)/libend2end_test_cancel_before_invoke.a libs/$(CONFIG)/libend2end_test_cancel_in_a_vacuum.a libs/$(CONFIG)/libend2end_test_census_simple_request.a libs/$(CONFIG)/libend2end_test_disappearing_server.a libs/$(CONFIG)/libend2end_test_early_server_shutdown_finishes_inflight_calls.a libs/$(CONFIG)/libend2end_test_early_server_shutdown_finishes_tags.a libs/$(CONFIG)/libend2end_test_invoke_large_request.a libs/$(CONFIG)/libend2end_test_max_concurrent_streams.a libs/$(CONFIG)/libend2end_test_no_op.a libs/$(CONFIG)/libend2end_test_ping_pong_streaming.a libs/$(CONFIG)/libend2end_test_request_response_with_binary_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_trailing_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_simple_delayed_request.a libs/$(CONFIG)/libend2end_test_simple_request.a libs/$(CONFIG)/libend2end_test_thread_stress.a libs/$(CONFIG)/libend2end_test_writes_done_hangs_with_pending_read.a libs/$(CONFIG)/libend2end_certs.a
+privatelibs_cxx: dep_cxx libs/$(CONFIG)/libgrpc++_test_util.a
 
 buildtests: buildtests_c buildtests_cxx
 
diff --git a/include/grpc++/stream.h b/include/grpc++/stream.h
index 49f88a6..b8982f4 100644
--- a/include/grpc++/stream.h
+++ b/include/grpc++/stream.h
@@ -96,7 +96,7 @@
 
   virtual bool Read(R* msg) { return context_->Read(msg); }
 
-  virtual void Cancel() { context_->FinishStream(Status::Cancelled, true); }
+  virtual void Cancel() { context_->Cancel(); }
 
   virtual const Status& Wait() { return context_->Wait(); }
 
@@ -122,7 +122,7 @@
 
   virtual void WritesDone() { context_->Write(nullptr, true); }
 
-  virtual void Cancel() { context_->FinishStream(Status::Cancelled, true); }
+  virtual void Cancel() { context_->Cancel(); }
 
   // Read the final response and wait for the final status.
   virtual const Status& Wait() {
@@ -165,7 +165,7 @@
 
   virtual void WritesDone() { context_->Write(nullptr, true); }
 
-  virtual void Cancel() { context_->FinishStream(Status::Cancelled, true); }
+  virtual void Cancel() { context_->Cancel(); }
 
   virtual const Status& Wait() { return context_->Wait(); }
 
diff --git a/include/grpc++/stream_context_interface.h b/include/grpc++/stream_context_interface.h
index 535c004..a841198 100644
--- a/include/grpc++/stream_context_interface.h
+++ b/include/grpc++/stream_context_interface.h
@@ -53,7 +53,7 @@
   virtual bool Read(google::protobuf::Message* msg) = 0;
   virtual bool Write(const google::protobuf::Message* msg, bool is_last) = 0;
   virtual const Status& Wait() = 0;
-  virtual void FinishStream(const Status& status, bool send) = 0;
+  virtual void Cancel() = 0;
 
   virtual google::protobuf::Message* request() = 0;
   virtual google::protobuf::Message* response() = 0;
diff --git a/include/grpc/grpc.h b/include/grpc/grpc.h
index d8fd03b..45915cb 100644
--- a/include/grpc/grpc.h
+++ b/include/grpc/grpc.h
@@ -365,6 +365,16 @@
    Can be called multiple times, from any thread. */
 grpc_call_error grpc_call_cancel(grpc_call *call);
 
+/* Called by clients to cancel an RPC on the server.
+   Can be called multiple times, from any thread.
+   If a status has not been received for the call, set it to the status code
+   and description passed in.
+   Importantly, this function does not send status nor description to the
+   remote endpoint. */
+grpc_call_error grpc_call_cancel_with_status(grpc_call *call,
+                                             grpc_status_code status,
+                                             const char *description);
+
 /* Queue a byte buffer for writing.
    flags is a bit-field combination of the write flags defined above.
    A write with byte_buffer null is allowed, and will not send any bytes on the
diff --git a/include/grpc/support/port_platform.h b/include/grpc/support/port_platform.h
index 9c999b9..05a5bbe 100644
--- a/include/grpc/support/port_platform.h
+++ b/include/grpc/support/port_platform.h
@@ -45,15 +45,16 @@
 #if defined(_WIN64) || defined(WIN64)
 #define GPR_WIN32 1
 #define GPR_ARCH_64 1
+#define GPR_GETPID_IN_PROCESS_H 1
 #elif defined(_WIN32) || defined(WIN32)
 #define GPR_ARCH_32 1
 #define GPR_WIN32 1
+#define GPR_GETPID_IN_PROCESS_H 1
 #elif defined(ANDROID) || defined(__ANDROID__)
 #define GPR_ANDROID 1
 #define GPR_ARCH_32 1
 #define GPR_CPU_LINUX 1
 #define GPR_GCC_SYNC 1
-#define GPR_LIBEVENT 1
 #define GPR_POSIX_MULTIPOLL_WITH_POLL 1
 #define GPR_POSIX_SOCKET 1
 #define GPR_POSIX_SOCKETADDR 1
@@ -61,10 +62,10 @@
 #define GPR_POSIX_STRING 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_GETPID_IN_UNISTD_H 1
 #elif defined(__linux__)
 #define GPR_CPU_LINUX 1
 #define GPR_GCC_ATOMIC 1
-#define GPR_LIBEVENT 1
 #define GPR_LINUX 1
 #define GPR_POSIX_MULTIPOLL_WITH_POLL 1
 #define GPR_POSIX_SOCKET 1
@@ -72,6 +73,7 @@
 #define GPR_POSIX_STRING 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_GETPID_IN_UNISTD_H 1
 #ifdef _LP64
 #define GPR_ARCH_64 1
 #else /* _LP64 */
@@ -80,7 +82,6 @@
 #elif defined(__APPLE__)
 #define GPR_CPU_POSIX 1
 #define GPR_GCC_ATOMIC 1
-#define GPR_LIBEVENT 1
 #define GPR_POSIX_LOG 1
 #define GPR_POSIX_MULTIPOLL_WITH_POLL 1
 #define GPR_POSIX_SOCKET 1
@@ -89,6 +90,7 @@
 #define GPR_POSIX_STRING 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_GETPID_IN_UNISTD_H 1
 #ifdef _LP64
 #define GPR_ARCH_64 1
 #else /* _LP64 */
diff --git a/src/compiler/cpp_generator.cc b/src/compiler/cpp_generator.cc
index 1116049..94e56d7 100644
--- a/src/compiler/cpp_generator.cc
+++ b/src/compiler/cpp_generator.cc
@@ -143,16 +143,16 @@
   } else if (ClientOnlyStreaming(method)) {
     printer->Print(
         *vars,
-        "::grpc::ClientWriter<$Request$>* $Method$("
+        "::grpc::ClientWriter< $Request$>* $Method$("
         "::grpc::ClientContext* context, $Response$* response);\n\n");
   } else if (ServerOnlyStreaming(method)) {
     printer->Print(
         *vars,
-        "::grpc::ClientReader<$Response$>* $Method$("
+        "::grpc::ClientReader< $Response$>* $Method$("
         "::grpc::ClientContext* context, const $Request$* request);\n\n");
   } else if (BidiStreaming(method)) {
     printer->Print(*vars,
-                   "::grpc::ClientReaderWriter<$Request$, $Response$>* "
+                   "::grpc::ClientReaderWriter< $Request$, $Response$>* "
                    "$Method$(::grpc::ClientContext* context);\n\n");
   }
 }
@@ -174,19 +174,20 @@
     printer->Print(*vars,
                    "virtual ::grpc::Status $Method$("
                    "::grpc::ServerContext* context, "
-                   "::grpc::ServerReader<$Request$>* reader, "
+                   "::grpc::ServerReader< $Request$>* reader, "
                    "$Response$* response);\n");
   } else if (ServerOnlyStreaming(method)) {
     printer->Print(*vars,
                    "virtual ::grpc::Status $Method$("
                    "::grpc::ServerContext* context, const $Request$* request, "
-                   "::grpc::ServerWriter<$Response$>* writer);\n");
+                   "::grpc::ServerWriter< $Response$>* writer);\n");
   } else if (BidiStreaming(method)) {
-    printer->Print(*vars,
-                   "virtual ::grpc::Status $Method$("
-                   "::grpc::ServerContext* context, "
-                   "::grpc::ServerReaderWriter<$Response$, $Request$>* stream);"
-                   "\n");
+    printer->Print(
+        *vars,
+        "virtual ::grpc::Status $Method$("
+        "::grpc::ServerContext* context, "
+        "::grpc::ServerReaderWriter< $Response$, $Request$>* stream);"
+        "\n");
   }
 }
 
@@ -211,7 +212,7 @@
   printer->Outdent();
   printer->Print("};\n");
   printer->Print(
-      "static Stub* NewStub(const std::shared_ptr<::grpc::ChannelInterface>& "
+      "static Stub* NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& "
       "channel);\n");
 
   printer->Print("\n");
@@ -269,11 +270,12 @@
                    "context, request, response);\n"
                    "}\n\n");
   } else if (ClientOnlyStreaming(method)) {
+    printer->Print(
+        *vars,
+        "::grpc::ClientWriter< $Request$>* $Service$::Stub::$Method$("
+        "::grpc::ClientContext* context, $Response$* response) {\n");
     printer->Print(*vars,
-                   "::grpc::ClientWriter<$Request$>* $Service$::Stub::$Method$("
-                   "::grpc::ClientContext* context, $Response$* response) {\n");
-    printer->Print(*vars,
-                   "  return new ::grpc::ClientWriter<$Request$>("
+                   "  return new ::grpc::ClientWriter< $Request$>("
                    "channel()->CreateStream("
                    "::grpc::RpcMethod(\"/$Package$$Service$/$Method$\", "
                    "::grpc::RpcMethod::RpcType::CLIENT_STREAMING), "
@@ -282,10 +284,10 @@
   } else if (ServerOnlyStreaming(method)) {
     printer->Print(
         *vars,
-        "::grpc::ClientReader<$Response$>* $Service$::Stub::$Method$("
+        "::grpc::ClientReader< $Response$>* $Service$::Stub::$Method$("
         "::grpc::ClientContext* context, const $Request$* request) {\n");
     printer->Print(*vars,
-                   "  return new ::grpc::ClientReader<$Response$>("
+                   "  return new ::grpc::ClientReader< $Response$>("
                    "channel()->CreateStream("
                    "::grpc::RpcMethod(\"/$Package$$Service$/$Method$\", "
                    "::grpc::RpcMethod::RpcType::SERVER_STREAMING), "
@@ -294,11 +296,11 @@
   } else if (BidiStreaming(method)) {
     printer->Print(
         *vars,
-        "::grpc::ClientReaderWriter<$Request$, $Response$>* "
+        "::grpc::ClientReaderWriter< $Request$, $Response$>* "
         "$Service$::Stub::$Method$(::grpc::ClientContext* context) {\n");
     printer->Print(
         *vars,
-        "  return new ::grpc::ClientReaderWriter<$Request$, $Response$>("
+        "  return new ::grpc::ClientReaderWriter< $Request$, $Response$>("
         "channel()->CreateStream("
         "::grpc::RpcMethod(\"/$Package$$Service$/$Method$\", "
         "::grpc::RpcMethod::RpcType::BIDI_STREAMING), "
@@ -328,7 +330,7 @@
     printer->Print(*vars,
                    "::grpc::Status $Service$::Service::$Method$("
                    "::grpc::ServerContext* context, "
-                   "::grpc::ServerReader<$Request$>* reader, "
+                   "::grpc::ServerReader< $Request$>* reader, "
                    "$Response$* response) {\n");
     printer->Print(
         "  return ::grpc::Status("
@@ -339,7 +341,7 @@
                    "::grpc::Status $Service$::Service::$Method$("
                    "::grpc::ServerContext* context, "
                    "const $Request$* request, "
-                   "::grpc::ServerWriter<$Response$>* writer) {\n");
+                   "::grpc::ServerWriter< $Response$>* writer) {\n");
     printer->Print(
         "  return ::grpc::Status("
         "::grpc::StatusCode::UNIMPLEMENTED);\n");
@@ -348,7 +350,7 @@
     printer->Print(*vars,
                    "::grpc::Status $Service$::Service::$Method$("
                    "::grpc::ServerContext* context, "
-                   "::grpc::ServerReaderWriter<$Response$, $Request$>* "
+                   "::grpc::ServerReaderWriter< $Response$, $Request$>* "
                    "stream) {\n");
     printer->Print(
         "  return ::grpc::Status("
@@ -361,13 +363,14 @@
                         const google::protobuf::ServiceDescriptor* service,
                         map<string, string>* vars) {
   (*vars)["Service"] = service->name();
-  printer->Print(*vars,
-                 "$Service$::Stub* $Service$::NewStub("
-                 "const std::shared_ptr<::grpc::ChannelInterface>& channel) {\n"
-                 "  $Service$::Stub* stub = new $Service$::Stub();\n"
-                 "  stub->set_channel(channel);\n"
-                 "  return stub;\n"
-                 "};\n\n");
+  printer->Print(
+      *vars,
+      "$Service$::Stub* $Service$::NewStub("
+      "const std::shared_ptr< ::grpc::ChannelInterface>& channel) {\n"
+      "  $Service$::Stub* stub = new $Service$::Stub();\n"
+      "  stub->set_channel(channel);\n"
+      "  return stub;\n"
+      "};\n\n");
   for (int i = 0; i < service->method_count(); ++i) {
     PrintSourceClientMethod(printer, service->method(i), vars);
   }
@@ -400,9 +403,9 @@
           "service_->AddMethod(new ::grpc::RpcServiceMethod(\n"
           "    \"/$Package$$Service$/$Method$\",\n"
           "    ::grpc::RpcMethod::NORMAL_RPC,\n"
-          "    new ::grpc::RpcMethodHandler<$Service$::Service, $Request$, "
+          "    new ::grpc::RpcMethodHandler< $Service$::Service, $Request$, "
           "$Response$>(\n"
-          "        std::function<::grpc::Status($Service$::Service*, "
+          "        std::function< ::grpc::Status($Service$::Service*, "
           "::grpc::ServerContext*, const $Request$*, $Response$*)>("
           "&$Service$::Service::$Method$), this),\n"
           "    new $Request$, new $Response$));\n");
@@ -412,11 +415,11 @@
           "service_->AddMethod(new ::grpc::RpcServiceMethod(\n"
           "    \"/$Package$$Service$/$Method$\",\n"
           "    ::grpc::RpcMethod::CLIENT_STREAMING,\n"
-          "    new ::grpc::ClientStreamingHandler<"
+          "    new ::grpc::ClientStreamingHandler< "
           "$Service$::Service, $Request$, $Response$>(\n"
-          "        std::function<::grpc::Status($Service$::Service*, "
+          "        std::function< ::grpc::Status($Service$::Service*, "
           "::grpc::ServerContext*, "
-          "::grpc::ServerReader<$Request$>*, $Response$*)>("
+          "::grpc::ServerReader< $Request$>*, $Response$*)>("
           "&$Service$::Service::$Method$), this),\n"
           "    new $Request$, new $Response$));\n");
     } else if (ServerOnlyStreaming(method)) {
@@ -425,11 +428,11 @@
           "service_->AddMethod(new ::grpc::RpcServiceMethod(\n"
           "    \"/$Package$$Service$/$Method$\",\n"
           "    ::grpc::RpcMethod::SERVER_STREAMING,\n"
-          "    new ::grpc::ServerStreamingHandler<"
+          "    new ::grpc::ServerStreamingHandler< "
           "$Service$::Service, $Request$, $Response$>(\n"
-          "        std::function<::grpc::Status($Service$::Service*, "
+          "        std::function< ::grpc::Status($Service$::Service*, "
           "::grpc::ServerContext*, "
-          "const $Request$*, ::grpc::ServerWriter<$Response$>*)>("
+          "const $Request$*, ::grpc::ServerWriter< $Response$>*)>("
           "&$Service$::Service::$Method$), this),\n"
           "    new $Request$, new $Response$));\n");
     } else if (BidiStreaming(method)) {
@@ -438,11 +441,11 @@
           "service_->AddMethod(new ::grpc::RpcServiceMethod(\n"
           "    \"/$Package$$Service$/$Method$\",\n"
           "    ::grpc::RpcMethod::BIDI_STREAMING,\n"
-          "    new ::grpc::BidiStreamingHandler<"
+          "    new ::grpc::BidiStreamingHandler< "
           "$Service$::Service, $Request$, $Response$>(\n"
-          "        std::function<::grpc::Status($Service$::Service*, "
+          "        std::function< ::grpc::Status($Service$::Service*, "
           "::grpc::ServerContext*, "
-          "::grpc::ServerReaderWriter<$Response$, $Request$>*)>("
+          "::grpc::ServerReaderWriter< $Response$, $Request$>*)>("
           "&$Service$::Service::$Method$), this),\n"
           "    new $Request$, new $Response$));\n");
     }
diff --git a/src/core/support/log_posix.c b/src/core/support/log_posix.c
index 0420570..ee2705a 100644
--- a/src/core/support/log_posix.c
+++ b/src/core/support/log_posix.c
@@ -31,21 +31,26 @@
  *
  */
 
-#define _POSIX_SOURCE
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200112L
+#endif
+
 #define _GNU_SOURCE
 #include <grpc/support/port_platform.h>
 
 #if defined(GPR_POSIX_LOG)
 
+#include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
+#include <stdio.h>
 #include <time.h>
 #include <pthread.h>
 
-static long gettid() { return pthread_self(); }
+static gpr_intptr gettid() { return (gpr_intptr)pthread_self(); }
 
 void gpr_log(const char *file, int line, gpr_log_severity severity,
              const char *format, ...) {
@@ -55,7 +60,7 @@
   int ret;
   va_list args;
   va_start(args, format);
-  ret = vsnprintf(buf, format, args);
+  ret = vsnprintf(buf, sizeof(buf), format, args);
   va_end(args);
   if (ret < 0) {
     message = NULL;
@@ -64,7 +69,7 @@
   } else {
     message = allocated = gpr_malloc(ret + 1);
     va_start(args, format);
-    vsnprintf(message, format, args);
+    vsnprintf(message, ret, format, args);
     va_end(args);
   }
   gpr_log_message(file, line, severity, message);
@@ -91,7 +96,7 @@
     strcpy(time_buffer, "error:strftime");
   }
 
-  fprintf(stderr, "%s%s.%09d %7ld %s:%d] %s\n",
+  fprintf(stderr, "%s%s.%09d %7tu %s:%d] %s\n",
           gpr_log_severity_string(args->severity), time_buffer,
           (int)(now.tv_nsec), gettid(), display_file, args->line,
           args->message);
diff --git a/src/core/support/log_win32.c b/src/core/support/log_win32.c
index ae5f23a..dc8c1d0 100644
--- a/src/core/support/log_win32.c
+++ b/src/core/support/log_win32.c
@@ -36,12 +36,13 @@
 #ifdef GPR_WIN32
 
 #include <grpc/support/log.h>
+#include <grpc/support/alloc.h>
 #include <stdio.h>
 #include <stdarg.h>
 
 void gpr_log(const char *file, int line, gpr_log_severity severity,
-             const char *message) {
-  const char *message = NULL;
+             const char *format, ...) {
+  char *message = NULL;
   va_list args;
   int ret;
 
@@ -53,7 +54,7 @@
     message = NULL;
   } else {
     /* Allocate a new buffer, with space for the NUL terminator. */
-    strp_buflen = (size_t)ret + 1;
+    size_t strp_buflen = (size_t)ret + 1;
     message = gpr_malloc(strp_buflen);
 
     /* Print to the buffer. */
@@ -73,7 +74,7 @@
 
 /* Simple starter implementation */
 void gpr_default_log(gpr_log_func_args *args) {
-  fprintf(stderr, "%s %s:%d: %s\n", gpr_log_severity_string(severity),
+  fprintf(stderr, "%s %s:%d: %s\n", gpr_log_severity_string(args->severity),
           args->file, args->line, args->message);
 }
 
diff --git a/src/core/support/time.c b/src/core/support/time.c
index 0e88c65..9724331 100644
--- a/src/core/support/time.c
+++ b/src/core/support/time.c
@@ -259,7 +259,7 @@
   } else if (t.tv_sec <= -2147483) {
     /* TODO(ctiller): correct handling here (it's so far in the past do we
        care?) */
-    return -2147483648;
+    return -2147483647;
   } else {
     return t.tv_sec * GPR_MS_PER_SEC + t.tv_nsec / GPR_NS_PER_MS;
   }
diff --git a/src/core/surface/call.c b/src/core/surface/call.c
index 4ea8378..3579a5b 100644
--- a/src/core/surface/call.c
+++ b/src/core/surface/call.c
@@ -178,6 +178,7 @@
   gpr_uint8 received_metadata;
   gpr_uint8 have_read;
   gpr_uint8 have_alarm;
+  gpr_uint8 got_status_code;
   /* The current outstanding read message tag (only valid if have_read == 1) */
   void *read_tag;
   void *metadata_tag;
@@ -225,6 +226,7 @@
   call->have_write = 0;
   call->have_alarm = 0;
   call->received_metadata = 0;
+  call->got_status_code = 0;
   call->status_code =
       server_transport_data != NULL ? GRPC_STATUS_OK : GRPC_STATUS_UNKNOWN;
   call->status_details = NULL;
@@ -268,6 +270,19 @@
   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_error grpc_call_cancel(grpc_call *c) {
   grpc_call_element *elem;
   grpc_call_op op;
@@ -284,6 +299,21 @@
   return GRPC_CALL_OK;
 }
 
+grpc_call_error grpc_call_cancel_with_status(grpc_call *c,
+                                             grpc_status_code status,
+                                             const char *description) {
+  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);
+  return grpc_call_cancel(c);
+}
+
 void grpc_call_execute_op(grpc_call *call, grpc_call_op *op) {
   grpc_call_element *elem;
   GPR_ASSERT(op->dir == GRPC_CALL_DOWN);
@@ -799,15 +829,14 @@
   grpc_call *call = CALL_FROM_TOP_ELEM(elem);
   grpc_mdelem *md = op->data.metadata;
   grpc_mdstr *key = md->key;
+  gpr_log(GPR_DEBUG, "call %p got metadata %s %s", call,
+          grpc_mdstr_as_c_string(md->key), grpc_mdstr_as_c_string(md->value));
   if (key == grpc_channel_get_status_string(call->channel)) {
-    call->status_code = decode_status(md);
+    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)) {
-    if (call->status_details) {
-      grpc_mdstr_unref(call->status_details);
-    }
-    call->status_details = grpc_mdstr_ref(md->value);
+    maybe_set_status_details(call, md->value);
     grpc_mdelem_unref(md);
     op->done_cb(op->user_data, GRPC_OP_OK);
   } else {
diff --git a/src/cpp/stream/stream_context.cc b/src/cpp/stream/stream_context.cc
index 7936a30..5ccf8c9 100644
--- a/src/cpp/stream/stream_context.cc
+++ b/src/cpp/stream/stream_context.cc
@@ -112,9 +112,8 @@
   if (read_ev->data.read) {
     if (!DeserializeProto(read_ev->data.read, msg)) {
       ret = false;
-      FinishStream(
-          Status(StatusCode::DATA_LOSS, "Failed to parse incoming proto"),
-          true);
+      grpc_call_cancel_with_status(call(), GRPC_STATUS_DATA_LOSS,
+                                   "Failed to parse incoming proto");
     }
   } else {
     ret = false;
@@ -132,9 +131,8 @@
   if (msg) {
     grpc_byte_buffer* out_buf = nullptr;
     if (!SerializeProto(*msg, &out_buf)) {
-      FinishStream(Status(StatusCode::INVALID_ARGUMENT,
-                          "Failed to serialize outgoing proto"),
-                   true);
+      grpc_call_cancel_with_status(call(), GRPC_STATUS_INVALID_ARGUMENT,
+                                   "Failed to serialize outgoing proto");
       return false;
     }
     int flag = is_last ? GRPC_WRITE_BUFFER_HINT : 0;
@@ -172,29 +170,18 @@
   grpc_event_finish(metadata_ev);
   // TODO(yangg) protect states by a mutex, including other places.
   if (!self_halfclosed_ || !peer_halfclosed_) {
-    FinishStream(Status::Cancelled, true);
-  } else {
-    grpc_event* finish_ev =
-        grpc_completion_queue_pluck(cq(), finished_tag(), gpr_inf_future);
-    GPR_ASSERT(finish_ev->type == GRPC_FINISHED);
-    final_status_ = Status(
-        static_cast<StatusCode>(finish_ev->data.finished.status),
-        finish_ev->data.finished.details ? finish_ev->data.finished.details
-                                         : "");
-    grpc_event_finish(finish_ev);
-  }
-  return final_status_;
-}
-
-void StreamContext::FinishStream(const Status& status, bool send) {
-  if (send) {
-    grpc_call_cancel(call());
+    Cancel();
   }
   grpc_event* finish_ev =
       grpc_completion_queue_pluck(cq(), finished_tag(), gpr_inf_future);
   GPR_ASSERT(finish_ev->type == GRPC_FINISHED);
+  final_status_ = Status(
+      static_cast<StatusCode>(finish_ev->data.finished.status),
+      finish_ev->data.finished.details ? finish_ev->data.finished.details : "");
   grpc_event_finish(finish_ev);
-  final_status_ = status;
+  return final_status_;
 }
 
+void StreamContext::Cancel() { grpc_call_cancel(call()); }
+
 }  // namespace grpc
diff --git a/src/cpp/stream/stream_context.h b/src/cpp/stream/stream_context.h
index f70fe6d..4781f27 100644
--- a/src/cpp/stream/stream_context.h
+++ b/src/cpp/stream/stream_context.h
@@ -48,7 +48,7 @@
 class ClientContext;
 class RpcMethod;
 
-class StreamContext : public StreamContextInterface {
+class StreamContext final : public StreamContextInterface {
  public:
   StreamContext(const RpcMethod& method, ClientContext* context,
                 const google::protobuf::Message* request,
@@ -63,7 +63,7 @@
   bool Read(google::protobuf::Message* msg) override;
   bool Write(const google::protobuf::Message* msg, bool is_last) override;
   const Status& Wait() override;
-  void FinishStream(const Status& status, bool send) override;
+  void Cancel() override;
 
   google::protobuf::Message* request() override { return request_; }
   google::protobuf::Message* response() override { return result_; }
diff --git a/src/node/README.md b/src/node/README.md
new file mode 100644
index 0000000..55329d8
--- /dev/null
+++ b/src/node/README.md
@@ -0,0 +1,12 @@
+# Node.js GRPC extension
+
+The package is built with
+
+    node-gyp configure
+    node-gyp build
+
+or, for brevity
+
+    node-gyp configure build
+
+The tests can be run with `npm test` on a dev install.
\ No newline at end of file
diff --git a/src/node/binding.gyp b/src/node/binding.gyp
new file mode 100644
index 0000000..4a1fd7a
--- /dev/null
+++ b/src/node/binding.gyp
@@ -0,0 +1,46 @@
+{
+  "targets" : [
+    {
+      'include_dirs': [
+        "<!(node -e \"require('nan')\")"
+      ],
+      'cxxflags': [
+        '-Wall',
+        '-pthread',
+        '-pedantic',
+        '-g',
+        '-zdefs'
+        '-Werror',
+      ],
+      'ldflags': [
+        '-g',
+        '-L/usr/local/google/home/mlumish/grpc_dev/lib'
+      ],
+      'link_settings': {
+        'libraries': [
+          '-lgrpc',
+          '-levent',
+          '-levent_pthreads',
+          '-levent_core',
+          '-lrt',
+          '-lgpr',
+          '-lpthread'
+        ],
+      },
+      "target_name": "grpc",
+      "sources": [
+        "byte_buffer.cc",
+        "call.cc",
+        "channel.cc",
+        "completion_queue_async_worker.cc",
+        "credentials.cc",
+        "event.cc",
+        "node_grpc.cc",
+        "server.cc",
+        "server_credentials.cc",
+        "tag.cc",
+        "timeval.cc"
+      ]
+    }
+  ]
+}
diff --git a/src/node/byte_buffer.cc b/src/node/byte_buffer.cc
new file mode 100644
index 0000000..1429514
--- /dev/null
+++ b/src/node/byte_buffer.cc
@@ -0,0 +1,79 @@
+/*
+ *
+ * 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 <string.h>
+#include <malloc.h>
+
+#include <node.h>
+#include <nan.h>
+#include "grpc/grpc.h"
+#include "grpc/support/slice.h"
+
+namespace grpc {
+namespace node {
+
+#include "byte_buffer.h"
+
+using ::node::Buffer;
+using v8::Handle;
+using v8::Value;
+
+grpc_byte_buffer *BufferToByteBuffer(Handle<Value> buffer) {
+  NanScope();
+  int length = Buffer::Length(buffer);
+  char *data = Buffer::Data(buffer);
+  gpr_slice slice = gpr_slice_malloc(length);
+  memcpy(GPR_SLICE_START_PTR(slice), data, length);
+  grpc_byte_buffer *byte_buffer(grpc_byte_buffer_create(&slice, 1));
+  gpr_slice_unref(slice);
+  return byte_buffer;
+}
+
+Handle<Value> ByteBufferToBuffer(grpc_byte_buffer *buffer) {
+  NanEscapableScope();
+  if (buffer == NULL) {
+    NanReturnNull();
+  }
+  size_t length = grpc_byte_buffer_length(buffer);
+  char *result = reinterpret_cast<char *>(calloc(length, sizeof(char)));
+  size_t offset = 0;
+  grpc_byte_buffer_reader *reader = grpc_byte_buffer_reader_create(buffer);
+  gpr_slice next;
+  while (grpc_byte_buffer_reader_next(reader, &next) != 0) {
+    memcpy(result + offset, GPR_SLICE_START_PTR(next), GPR_SLICE_LENGTH(next));
+    offset += GPR_SLICE_LENGTH(next);
+  }
+  return NanEscapeScope(NanNewBufferHandle(result, length));
+}
+}  // namespace node
+}  // namespace grpc
diff --git a/src/node/byte_buffer.h b/src/node/byte_buffer.h
new file mode 100644
index 0000000..ee2b4c0
--- /dev/null
+++ b/src/node/byte_buffer.h
@@ -0,0 +1,56 @@
+/*
+ *
+ * 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 NET_GRPC_NODE_BYTE_BUFFER_H_
+#define NET_GRPC_NODE_BYTE_BUFFER_H_
+
+#include <string.h>
+
+#include <node.h>
+#include <nan.h>
+#include "grpc/grpc.h"
+
+namespace grpc {
+namespace node {
+
+/* Convert a Node.js Buffer to grpc_byte_buffer. Requires that
+   ::node::Buffer::HasInstance(buffer) */
+grpc_byte_buffer *BufferToByteBuffer(v8::Handle<v8::Value> buffer);
+
+/* Convert a grpc_byte_buffer to a Node.js Buffer */
+v8::Handle<v8::Value> ByteBufferToBuffer(grpc_byte_buffer *buffer);
+
+}  // namespace node
+}  // namespace grpc
+
+#endif  // NET_GRPC_NODE_BYTE_BUFFER_H_
diff --git a/src/node/call.cc b/src/node/call.cc
new file mode 100644
index 0000000..b8ee178
--- /dev/null
+++ b/src/node/call.cc
@@ -0,0 +1,392 @@
+/*
+ *
+ * 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 <node.h>
+
+#include "grpc/grpc.h"
+#include "grpc/support/time.h"
+#include "byte_buffer.h"
+#include "call.h"
+#include "channel.h"
+#include "completion_queue_async_worker.h"
+#include "timeval.h"
+#include "tag.h"
+
+namespace grpc {
+namespace node {
+
+using ::node::Buffer;
+using v8::Arguments;
+using v8::Array;
+using v8::Exception;
+using v8::External;
+using v8::Function;
+using v8::FunctionTemplate;
+using v8::Handle;
+using v8::HandleScope;
+using v8::Integer;
+using v8::Local;
+using v8::Number;
+using v8::Object;
+using v8::ObjectTemplate;
+using v8::Persistent;
+using v8::Uint32;
+using v8::String;
+using v8::Value;
+
+Persistent<Function> Call::constructor;
+Persistent<FunctionTemplate> Call::fun_tpl;
+
+Call::Call(grpc_call *call) : wrapped_call(call) {}
+
+Call::~Call() { grpc_call_destroy(wrapped_call); }
+
+void Call::Init(Handle<Object> exports) {
+  NanScope();
+  Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
+  tpl->SetClassName(NanNew("Call"));
+  tpl->InstanceTemplate()->SetInternalFieldCount(1);
+  NanSetPrototypeTemplate(tpl, "addMetadata",
+                          FunctionTemplate::New(AddMetadata)->GetFunction());
+  NanSetPrototypeTemplate(tpl, "startInvoke",
+                          FunctionTemplate::New(StartInvoke)->GetFunction());
+  NanSetPrototypeTemplate(tpl, "serverAccept",
+                          FunctionTemplate::New(ServerAccept)->GetFunction());
+  NanSetPrototypeTemplate(
+      tpl, "serverEndInitialMetadata",
+      FunctionTemplate::New(ServerEndInitialMetadata)->GetFunction());
+  NanSetPrototypeTemplate(tpl, "cancel",
+                          FunctionTemplate::New(Cancel)->GetFunction());
+  NanSetPrototypeTemplate(tpl, "startWrite",
+                          FunctionTemplate::New(StartWrite)->GetFunction());
+  NanSetPrototypeTemplate(
+      tpl, "startWriteStatus",
+      FunctionTemplate::New(StartWriteStatus)->GetFunction());
+  NanSetPrototypeTemplate(tpl, "writesDone",
+                          FunctionTemplate::New(WritesDone)->GetFunction());
+  NanSetPrototypeTemplate(tpl, "startReadMetadata",
+                          FunctionTemplate::New(WritesDone)->GetFunction());
+  NanSetPrototypeTemplate(tpl, "startRead",
+                          FunctionTemplate::New(StartRead)->GetFunction());
+  NanAssignPersistent(fun_tpl, tpl);
+  NanAssignPersistent(constructor, tpl->GetFunction());
+  constructor->Set(NanNew("WRITE_BUFFER_HINT"),
+                   NanNew<Uint32, uint32_t>(GRPC_WRITE_BUFFER_HINT));
+  constructor->Set(NanNew("WRITE_NO_COMPRESS"),
+                   NanNew<Uint32, uint32_t>(GRPC_WRITE_NO_COMPRESS));
+  exports->Set(String::NewSymbol("Call"), constructor);
+}
+
+bool Call::HasInstance(Handle<Value> val) {
+  NanScope();
+  return NanHasInstance(fun_tpl, val);
+}
+
+Handle<Value> Call::WrapStruct(grpc_call *call) {
+  NanEscapableScope();
+  if (call == NULL) {
+    return NanEscapeScope(NanNull());
+  }
+  const int argc = 1;
+  Handle<Value> argv[argc] = {External::New(reinterpret_cast<void *>(call))};
+  return NanEscapeScope(constructor->NewInstance(argc, argv));
+}
+
+NAN_METHOD(Call::New) {
+  NanScope();
+
+  if (args.IsConstructCall()) {
+    Call *call;
+    if (args[0]->IsExternal()) {
+      // This option is used for wrapping an existing call
+      grpc_call *call_value =
+          reinterpret_cast<grpc_call *>(External::Unwrap(args[0]));
+      call = new Call(call_value);
+    } else {
+      if (!Channel::HasInstance(args[0])) {
+        return NanThrowTypeError("Call's first argument must be a Channel");
+      }
+      if (!args[1]->IsString()) {
+        return NanThrowTypeError("Call's second argument must be a string");
+      }
+      if (!(args[2]->IsNumber() || args[2]->IsDate())) {
+        return NanThrowTypeError(
+            "Call's third argument must be a date or a number");
+      }
+      Handle<Object> channel_object = args[0]->ToObject();
+      Channel *channel = ObjectWrap::Unwrap<Channel>(channel_object);
+      if (channel->GetWrappedChannel() == NULL) {
+        return NanThrowError("Call cannot be created from a closed channel");
+      }
+      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));
+      call = new Call(wrapped_call);
+      args.This()->SetHiddenValue(String::NewSymbol("channel_"),
+                                  channel_object);
+    }
+    call->Wrap(args.This());
+    NanReturnValue(args.This());
+  } else {
+    const int argc = 4;
+    Local<Value> argv[argc] = {args[0], args[1], args[2], args[3]};
+    NanReturnValue(constructor->NewInstance(argc, argv));
+  }
+}
+
+NAN_METHOD(Call::AddMetadata) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError("addMetadata can only be called on Call objects");
+  }
+  Call *call = ObjectWrap::Unwrap<Call>(args.This());
+  for (int i = 0; !args[i]->IsUndefined(); i++) {
+    if (!args[i]->IsObject()) {
+      return NanThrowTypeError(
+          "addMetadata arguments must be objects with key and value");
+    }
+    Handle<Object> item = args[i]->ToObject();
+    Handle<Value> key = item->Get(NanNew("key"));
+    if (!key->IsString()) {
+      return NanThrowTypeError(
+          "objects passed to addMetadata must have key->string");
+    }
+    Handle<Value> value = item->Get(NanNew("value"));
+    if (!Buffer::HasInstance(value)) {
+      return NanThrowTypeError(
+          "objects passed to addMetadata must have value->Buffer");
+    }
+    grpc_metadata metadata;
+    NanUtf8String utf8_key(key);
+    metadata.key = *utf8_key;
+    metadata.value = Buffer::Data(value);
+    metadata.value_length = Buffer::Length(value);
+    grpc_call_error error =
+        grpc_call_add_metadata(call->wrapped_call, &metadata, 0);
+    if (error != GRPC_CALL_OK) {
+      return NanThrowError("addMetadata failed", error);
+    }
+  }
+  NanReturnUndefined();
+}
+
+NAN_METHOD(Call::StartInvoke) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError("startInvoke can only be called on Call objects");
+  }
+  if (!args[0]->IsFunction()) {
+    return NanThrowTypeError("StartInvoke's first argument must be a function");
+  }
+  if (!args[1]->IsFunction()) {
+    return NanThrowTypeError(
+        "StartInvoke's second argument must be a function");
+  }
+  if (!args[2]->IsFunction()) {
+    return NanThrowTypeError("StartInvoke's third argument must be a function");
+  }
+  if (!args[3]->IsUint32()) {
+    return NanThrowTypeError(
+        "StartInvoke's fourth argument must be integer flags");
+  }
+  Call *call = ObjectWrap::Unwrap<Call>(args.This());
+  unsigned int flags = args[3]->Uint32Value();
+  grpc_call_error error = grpc_call_start_invoke(
+      call->wrapped_call, CompletionQueueAsyncWorker::GetQueue(),
+      CreateTag(args[0], args.This()), CreateTag(args[1], args.This()),
+      CreateTag(args[2], args.This()), flags);
+  if (error == GRPC_CALL_OK) {
+    CompletionQueueAsyncWorker::Next();
+    CompletionQueueAsyncWorker::Next();
+    CompletionQueueAsyncWorker::Next();
+  } else {
+    return NanThrowError("startInvoke failed", error);
+  }
+  NanReturnUndefined();
+}
+
+NAN_METHOD(Call::ServerAccept) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError("accept can only be called on Call objects");
+  }
+  if (!args[0]->IsFunction()) {
+    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(
+      call->wrapped_call, CompletionQueueAsyncWorker::GetQueue(),
+      CreateTag(args[0], args.This()));
+  if (error == GRPC_CALL_OK) {
+    CompletionQueueAsyncWorker::Next();
+  } else {
+    return NanThrowError("serverAccept failed", error);
+  }
+  NanReturnUndefined();
+}
+
+NAN_METHOD(Call::ServerEndInitialMetadata) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError(
+        "serverEndInitialMetadata can only be called on Call objects");
+  }
+  if (!args[0]->IsUint32()) {
+    return NanThrowTypeError(
+        "serverEndInitialMetadata's second argument must be integer flags");
+  }
+  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);
+  if (error != GRPC_CALL_OK) {
+    return NanThrowError("serverEndInitialMetadata failed", error);
+  }
+  NanReturnUndefined();
+}
+
+NAN_METHOD(Call::Cancel) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError("startInvoke can only be called on Call objects");
+  }
+  Call *call = ObjectWrap::Unwrap<Call>(args.This());
+  grpc_call_error error = grpc_call_cancel(call->wrapped_call);
+  if (error != GRPC_CALL_OK) {
+    return NanThrowError("cancel failed", error);
+  }
+  NanReturnUndefined();
+}
+
+NAN_METHOD(Call::StartWrite) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError("startWrite can only be called on Call objects");
+  }
+  if (!Buffer::HasInstance(args[0])) {
+    return NanThrowTypeError("startWrite's first argument must be a Buffer");
+  }
+  if (!args[1]->IsFunction()) {
+    return NanThrowTypeError("startWrite's second argument must be a function");
+  }
+  if (!args[2]->IsUint32()) {
+    return NanThrowTypeError(
+        "startWrite's third argument must be integer flags");
+  }
+  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(
+      call->wrapped_call, buffer, CreateTag(args[1], args.This()), flags);
+  if (error == GRPC_CALL_OK) {
+    CompletionQueueAsyncWorker::Next();
+  } else {
+    return NanThrowError("startWrite failed", error);
+  }
+  NanReturnUndefined();
+}
+
+NAN_METHOD(Call::StartWriteStatus) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError(
+        "startWriteStatus can only be called on Call objects");
+  }
+  if (!args[0]->IsUint32()) {
+    return NanThrowTypeError(
+        "startWriteStatus's first argument must be a status code");
+  }
+  if (!args[1]->IsString()) {
+    return NanThrowTypeError(
+        "startWriteStatus's second argument must be a string");
+  }
+  if (!args[2]->IsFunction()) {
+    return NanThrowTypeError(
+        "startWriteStatus's third argument must be a function");
+  }
+  Call *call = ObjectWrap::Unwrap<Call>(args.This());
+  NanUtf8String details(args[1]);
+  grpc_call_error error = grpc_call_start_write_status(
+      call->wrapped_call, (grpc_status_code)args[0]->Uint32Value(), *details,
+      CreateTag(args[2], args.This()));
+  if (error == GRPC_CALL_OK) {
+    CompletionQueueAsyncWorker::Next();
+  } else {
+    return NanThrowError("startWriteStatus failed", error);
+  }
+  NanReturnUndefined();
+}
+
+NAN_METHOD(Call::WritesDone) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError("writesDone can only be called on Call objects");
+  }
+  if (!args[0]->IsFunction()) {
+    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(
+      call->wrapped_call, CreateTag(args[0], args.This()));
+  if (error == GRPC_CALL_OK) {
+    CompletionQueueAsyncWorker::Next();
+  } else {
+    return NanThrowError("writesDone failed", error);
+  }
+  NanReturnUndefined();
+}
+
+NAN_METHOD(Call::StartRead) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError("startRead can only be called on Call objects");
+  }
+  if (!args[0]->IsFunction()) {
+    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()));
+  if (error == GRPC_CALL_OK) {
+    CompletionQueueAsyncWorker::Next();
+  } else {
+    return NanThrowError("startRead failed", error);
+  }
+  NanReturnUndefined();
+}
+
+}  // namespace node
+}  // namespace grpc
diff --git a/src/node/call.h b/src/node/call.h
new file mode 100644
index 0000000..55a6fc6
--- /dev/null
+++ b/src/node/call.h
@@ -0,0 +1,82 @@
+/*
+ *
+ * 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 NET_GRPC_NODE_CALL_H_
+#define NET_GRPC_NODE_CALL_H_
+
+#include <node.h>
+#include <nan.h>
+#include "grpc/grpc.h"
+
+#include "channel.h"
+
+namespace grpc {
+namespace node {
+
+/* Wrapper class for grpc_call structs. */
+class Call : public ::node::ObjectWrap {
+ public:
+  static void Init(v8::Handle<v8::Object> exports);
+  static bool HasInstance(v8::Handle<v8::Value> val);
+  /* Wrap a grpc_call struct in a javascript object */
+  static v8::Handle<v8::Value> WrapStruct(grpc_call *call);
+
+ private:
+  explicit Call(grpc_call *call);
+  ~Call();
+
+  // Prevent copying
+  Call(const Call &);
+  Call &operator=(const Call &);
+
+  static NAN_METHOD(New);
+  static NAN_METHOD(AddMetadata);
+  static NAN_METHOD(StartInvoke);
+  static NAN_METHOD(ServerAccept);
+  static NAN_METHOD(ServerEndInitialMetadata);
+  static NAN_METHOD(Cancel);
+  static NAN_METHOD(StartWrite);
+  static NAN_METHOD(StartWriteStatus);
+  static NAN_METHOD(WritesDone);
+  static NAN_METHOD(StartRead);
+  static v8::Persistent<v8::Function> constructor;
+  // Used for typechecking instances of this javascript class
+  static v8::Persistent<v8::FunctionTemplate> fun_tpl;
+
+  grpc_call *wrapped_call;
+};
+
+}  // namespace node
+}  // namespace grpc
+
+#endif  // NET_GRPC_NODE_CALL_H_
diff --git a/src/node/channel.cc b/src/node/channel.cc
new file mode 100644
index 0000000..9087d6f
--- /dev/null
+++ b/src/node/channel.cc
@@ -0,0 +1,182 @@
+/*
+ *
+ * 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 <malloc.h>
+
+#include <vector>
+
+#include <node.h>
+#include <nan.h>
+#include "grpc/grpc.h"
+#include "grpc/grpc_security.h"
+#include "channel.h"
+#include "credentials.h"
+
+namespace grpc {
+namespace node {
+
+using v8::Arguments;
+using v8::Array;
+using v8::Exception;
+using v8::Function;
+using v8::FunctionTemplate;
+using v8::Handle;
+using v8::HandleScope;
+using v8::Integer;
+using v8::Local;
+using v8::Object;
+using v8::Persistent;
+using v8::String;
+using v8::Value;
+
+Persistent<Function> Channel::constructor;
+Persistent<FunctionTemplate> Channel::fun_tpl;
+
+Channel::Channel(grpc_channel *channel, NanUtf8String *host)
+    : wrapped_channel(channel), host(host) {}
+
+Channel::~Channel() {
+  if (wrapped_channel != NULL) {
+    grpc_channel_destroy(wrapped_channel);
+  }
+  delete host;
+}
+
+void Channel::Init(Handle<Object> exports) {
+  NanScope();
+  Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
+  tpl->SetClassName(NanNew("Channel"));
+  tpl->InstanceTemplate()->SetInternalFieldCount(1);
+  NanSetPrototypeTemplate(tpl, "close",
+                          FunctionTemplate::New(Close)->GetFunction());
+  NanAssignPersistent(fun_tpl, tpl);
+  NanAssignPersistent(constructor, tpl->GetFunction());
+  exports->Set(NanNew("Channel"), constructor);
+}
+
+bool Channel::HasInstance(Handle<Value> val) {
+  NanScope();
+  return NanHasInstance(fun_tpl, val);
+}
+
+grpc_channel *Channel::GetWrappedChannel() { return this->wrapped_channel; }
+
+char *Channel::GetHost() { return **this->host; }
+
+NAN_METHOD(Channel::New) {
+  NanScope();
+
+  if (args.IsConstructCall()) {
+    if (!args[0]->IsString()) {
+      return NanThrowTypeError("Channel expects a string and an object");
+    }
+    grpc_channel *wrapped_channel;
+    // Owned by the Channel object
+    NanUtf8String *host = new NanUtf8String(args[0]);
+    if (args[1]->IsUndefined()) {
+      wrapped_channel = grpc_channel_create(**host, NULL);
+    } else if (args[1]->IsObject()) {
+      grpc_credentials *creds = NULL;
+      Handle<Object> args_hash(args[1]->ToObject()->Clone());
+      if (args_hash->HasOwnProperty(NanNew("credentials"))) {
+        Handle<Value> creds_value = args_hash->Get(NanNew("credentials"));
+        if (!Credentials::HasInstance(creds_value)) {
+          return NanThrowTypeError(
+              "credentials arg must be a Credentials object");
+        }
+        Credentials *creds_object =
+            ObjectWrap::Unwrap<Credentials>(creds_value->ToObject());
+        creds = creds_object->GetWrappedCredentials();
+        args_hash->Delete(NanNew("credentials"));
+      }
+      Handle<Array> keys(args_hash->GetOwnPropertyNames());
+      grpc_channel_args channel_args;
+      channel_args.num_args = keys->Length();
+      channel_args.args = reinterpret_cast<grpc_arg *>(
+          calloc(channel_args.num_args, sizeof(grpc_arg)));
+      /* These are used to keep all strings until then end of the block, then
+         destroy them */
+      std::vector<NanUtf8String *> key_strings(keys->Length());
+      std::vector<NanUtf8String *> value_strings(keys->Length());
+      for (unsigned int i = 0; i < channel_args.num_args; i++) {
+        Handle<String> current_key(keys->Get(i)->ToString());
+        Handle<Value> current_value(args_hash->Get(current_key));
+        key_strings[i] = new NanUtf8String(current_key);
+        channel_args.args[i].key = **key_strings[i];
+        if (current_value->IsInt32()) {
+          channel_args.args[i].type = GRPC_ARG_INTEGER;
+          channel_args.args[i].value.integer = current_value->Int32Value();
+        } else if (current_value->IsString()) {
+          channel_args.args[i].type = GRPC_ARG_STRING;
+          value_strings[i] = new NanUtf8String(current_value);
+          channel_args.args[i].value.string = **value_strings[i];
+        } else {
+          free(channel_args.args);
+          return NanThrowTypeError("Arg values must be strings");
+        }
+      }
+      if (creds == NULL) {
+        wrapped_channel = grpc_channel_create(**host, &channel_args);
+      } else {
+        wrapped_channel =
+            grpc_secure_channel_create(creds, **host, &channel_args);
+      }
+      free(channel_args.args);
+    } else {
+      return NanThrowTypeError("Channel expects a string and an object");
+    }
+    Channel *channel = new Channel(wrapped_channel, host);
+    channel->Wrap(args.This());
+    NanReturnValue(args.This());
+  } else {
+    const int argc = 2;
+    Local<Value> argv[argc] = {args[0], args[1]};
+    NanReturnValue(constructor->NewInstance(argc, argv));
+  }
+}
+
+NAN_METHOD(Channel::Close) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError("close can only be called on Channel objects");
+  }
+  Channel *channel = ObjectWrap::Unwrap<Channel>(args.This());
+  if (channel->wrapped_channel != NULL) {
+    grpc_channel_destroy(channel->wrapped_channel);
+    channel->wrapped_channel = NULL;
+  }
+  NanReturnUndefined();
+}
+
+}  // namespace node
+}  // namespace grpc
diff --git a/src/node/channel.h b/src/node/channel.h
new file mode 100644
index 0000000..140cbf2
--- /dev/null
+++ b/src/node/channel.h
@@ -0,0 +1,79 @@
+/*
+ *
+ * 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 NET_GRPC_NODE_CHANNEL_H_
+#define NET_GRPC_NODE_CHANNEL_H_
+
+#include <node.h>
+#include <nan.h>
+#include "grpc/grpc.h"
+
+namespace grpc {
+namespace node {
+
+/* Wrapper class for grpc_channel structs */
+class Channel : public ::node::ObjectWrap {
+ public:
+  static void Init(v8::Handle<v8::Object> exports);
+  static bool HasInstance(v8::Handle<v8::Value> val);
+  /* This is used to typecheck javascript objects before converting them to
+     this type */
+  static v8::Persistent<v8::Value> prototype;
+
+  /* Returns the grpc_channel struct that this object wraps */
+  grpc_channel *GetWrappedChannel();
+
+  /* Return the hostname that this channel connects to */
+  char *GetHost();
+
+ private:
+  explicit Channel(grpc_channel *channel, NanUtf8String *host);
+  ~Channel();
+
+  // Prevent copying
+  Channel(const Channel &);
+  Channel &operator=(const Channel &);
+
+  static NAN_METHOD(New);
+  static NAN_METHOD(Close);
+  static v8::Persistent<v8::Function> constructor;
+  static v8::Persistent<v8::FunctionTemplate> fun_tpl;
+
+  grpc_channel *wrapped_channel;
+  NanUtf8String *host;
+};
+
+}  // namespace node
+}  // namespace grpc
+
+#endif  // NET_GRPC_NODE_CHANNEL_H_
diff --git a/src/node/client.js b/src/node/client.js
new file mode 100644
index 0000000..edaa115
--- /dev/null
+++ b/src/node/client.js
@@ -0,0 +1,209 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var grpc = require('bindings')('grpc.node');
+
+var common = require('./common');
+
+var Duplex = require('stream').Duplex;
+var util = require('util');
+
+util.inherits(GrpcClientStream, Duplex);
+
+/**
+ * Class for representing a gRPC client side stream as a Node stream. Extends
+ * from stream.Duplex.
+ * @constructor
+ * @param {grpc.Call} call Call object to proxy
+ * @param {object} options Stream options
+ */
+function GrpcClientStream(call, options) {
+  Duplex.call(this, options);
+  var self = this;
+  // Indicates that we can start reading and have not received a null read
+  var can_read = false;
+  // Indicates that a read is currently pending
+  var reading = false;
+  // Indicates that we can call startWrite
+  var can_write = false;
+  // Indicates that a write is currently pending
+  var writing = false;
+  this._call = call;
+  /**
+   * Callback to handle receiving a READ event. Pushes the data from that event
+   * onto the read queue and starts reading again if applicable.
+   * @param {grpc.Event} event The READ event object
+   */
+  function readCallback(event) {
+    var data = event.data;
+    if (self.push(data)) {
+      if (data == null) {
+        // Disable starting to read after null read was received
+        can_read = false;
+        reading = false;
+      } else {
+        call.startRead(readCallback);
+      }
+    } else {
+      // Indicate that reading can be resumed by calling startReading
+      reading = false;
+    }
+  };
+  /**
+   * Initiate a read, which continues until self.push returns false (indicating
+   * that reading should be paused) or data is null (indicating that there is no
+   * more data to read).
+   */
+  function startReading() {
+    call.startRead(readCallback);
+  }
+  // TODO(mlumish): possibly change queue implementation due to shift slowness
+  var write_queue = [];
+  /**
+   * Write the next chunk of data in the write queue if there is one. Otherwise
+   * indicate that there is no pending write. When the write succeeds, this
+   * function is called again.
+   */
+  function writeNext() {
+    if (write_queue.length > 0) {
+      writing = true;
+      var next = write_queue.shift();
+      var writeCallback = function(event) {
+        next.callback();
+        writeNext();
+      };
+      call.startWrite(next.chunk, writeCallback, 0);
+    } else {
+      writing = false;
+    }
+  }
+  call.startInvoke(function(event) {
+    can_read = true;
+    can_write = true;
+    startReading();
+    writeNext();
+  }, function(event) {
+    self.emit('metadata', event.data);
+  }, function(event) {
+    self.emit('status', event.data);
+  }, 0);
+  this.on('finish', function() {
+    call.writesDone(function() {});
+  });
+  /**
+   * Indicate that reads should start, and start them if the INVOKE_ACCEPTED
+   * event has been received.
+   */
+  this._enableRead = function() {
+    if (!reading) {
+      reading = true;
+      if (can_read) {
+        startReading();
+      }
+    }
+  };
+  /**
+   * Push the chunk onto the write queue, and write from the write queue if
+   * there is not a pending write
+   * @param {Buffer} chunk The chunk of data to write
+   * @param {function(Error=)} callback The callback to call when the write
+   *     completes
+   */
+  this._tryWrite = function(chunk, callback) {
+    write_queue.push({chunk: chunk, callback: callback});
+    if (can_write && !writing) {
+      writeNext();
+    }
+  };
+}
+
+/**
+ * Start reading. This is an implementation of a method needed for implementing
+ * stream.Readable.
+ * @param {number} size Ignored
+ */
+GrpcClientStream.prototype._read = function(size) {
+  this._enableRead();
+};
+
+/**
+ * Attempt to write the given chunk. Calls the callback when done. This is an
+ * implementation of a method needed for implementing stream.Writable.
+ * @param {Buffer} chunk The chunk to write
+ * @param {string} encoding Ignored
+ * @param {function(Error=)} callback Ignored
+ */
+GrpcClientStream.prototype._write = function(chunk, encoding, callback) {
+  this._tryWrite(chunk, callback);
+};
+
+/**
+ * Make a request on the channel to the given method with the given arguments
+ * @param {grpc.Channel} channel The channel on which to make the request
+ * @param {string} method The method to request
+ * @param {array=} metadata Array of metadata key/value pairs to add to the call
+ * @param {(number|Date)=} deadline The deadline for processing this request.
+ *     Defaults to infinite future.
+ * @return {stream=} The stream of responses
+ */
+function makeRequest(channel,
+                     method,
+                     metadata,
+                     deadline) {
+  if (deadline === undefined) {
+    deadline = Infinity;
+  }
+  var call = new grpc.Call(channel, method, deadline);
+  if (metadata) {
+    call.addMetadata(metadata);
+  }
+  return new GrpcClientStream(call);
+}
+
+/**
+ * See documentation for makeRequest above
+ */
+exports.makeRequest = makeRequest;
+
+/**
+ * Represents a client side gRPC channel associated with a single host.
+ */
+exports.Channel = grpc.Channel;
+/**
+ * Status name to code number mapping
+ */
+exports.status = grpc.status;
+/**
+ * Call error name to code number mapping
+ */
+exports.callError = grpc.callError;
diff --git a/src/node/common.js b/src/node/common.js
new file mode 100644
index 0000000..c2dc276
--- /dev/null
+++ b/src/node/common.js
@@ -0,0 +1,62 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var _ = require('highland');
+
+/**
+ * When the given stream finishes without error, call the callback once. This
+ * will not be called until something begins to consume the stream.
+ * @param {function} callback The callback to call at stream end
+ * @param {stream} source The stream to watch
+ * @return {stream} The stream with the callback attached
+ */
+function onSuccessfulStreamEnd(callback, source) {
+  var error = false;
+  return source.consume(function(err, x, push, next) {
+    if (x === _.nil) {
+      if (!error) {
+        callback();
+      }
+      push(null, x);
+    } else if (err) {
+      error = true;
+      push(err);
+      next();
+    } else {
+      push(err, x);
+      next();
+    }
+  });
+}
+
+exports.onSuccessfulStreamEnd = onSuccessfulStreamEnd;
diff --git a/src/node/completion_queue_async_worker.cc b/src/node/completion_queue_async_worker.cc
new file mode 100644
index 0000000..8de7db6
--- /dev/null
+++ b/src/node/completion_queue_async_worker.cc
@@ -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 <node.h>
+#include <nan.h>
+
+#include "grpc/grpc.h"
+#include "grpc/support/time.h"
+#include "completion_queue_async_worker.h"
+#include "event.h"
+#include "tag.h"
+
+namespace grpc {
+namespace node {
+
+using v8::Function;
+using v8::Handle;
+using v8::Object;
+using v8::Persistent;
+using v8::Value;
+
+grpc_completion_queue *CompletionQueueAsyncWorker::queue;
+
+CompletionQueueAsyncWorker::CompletionQueueAsyncWorker()
+    : NanAsyncWorker(NULL) {}
+
+CompletionQueueAsyncWorker::~CompletionQueueAsyncWorker() {}
+
+void CompletionQueueAsyncWorker::Execute() {
+  result = grpc_completion_queue_next(queue, gpr_inf_future);
+}
+
+grpc_completion_queue *CompletionQueueAsyncWorker::GetQueue() { return queue; }
+
+void CompletionQueueAsyncWorker::Next() {
+  NanScope();
+  CompletionQueueAsyncWorker *worker = new CompletionQueueAsyncWorker();
+  NanAsyncQueueWorker(worker);
+}
+
+void CompletionQueueAsyncWorker::Init(Handle<Object> exports) {
+  NanScope();
+  queue = grpc_completion_queue_create();
+}
+
+void CompletionQueueAsyncWorker::HandleOKCallback() {
+  NanScope();
+  NanCallback event_callback(GetTagHandle(result->tag).As<Function>());
+  Handle<Value> argv[] = {CreateEventObject(result)};
+
+  DestroyTag(result->tag);
+  grpc_event_finish(result);
+  result = NULL;
+
+  event_callback.Call(1, argv);
+}
+
+}  // namespace node
+}  // namespace grpc
diff --git a/src/node/completion_queue_async_worker.h b/src/node/completion_queue_async_worker.h
new file mode 100644
index 0000000..2c928b7
--- /dev/null
+++ b/src/node/completion_queue_async_worker.h
@@ -0,0 +1,79 @@
+/*
+ *
+ * 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 NET_GRPC_NODE_COMPLETION_QUEUE_ASYNC_WORKER_H_
+#define NET_GRPC_NODE_COMPLETION_QUEUE_ASYNC_WORKER_H_
+#include <nan.h>
+
+#include "grpc/grpc.h"
+
+namespace grpc {
+namespace node {
+
+/* A worker that asynchronously calls completion_queue_next, and queues onto the
+   node event loop a call to the function stored in the event's tag. */
+class CompletionQueueAsyncWorker : public NanAsyncWorker {
+ public:
+  CompletionQueueAsyncWorker();
+
+  ~CompletionQueueAsyncWorker();
+  /* Calls completion_queue_next with the provided deadline, and stores the
+     event if there was one or sets an error message if there was not */
+  void Execute();
+
+  /* Returns the completion queue attached to this class */
+  static grpc_completion_queue *GetQueue();
+
+  /* Convenience function to create a worker with the given arguments and queue
+     it to run asynchronously */
+  static void Next();
+
+  /* Initialize the CompletionQueueAsyncWorker class */
+  static void Init(v8::Handle<v8::Object> exports);
+
+ protected:
+  /* Called when Execute has succeeded (completed without setting an error
+     message). Calls the saved callback with the event that came from
+     completion_queue_next */
+  void HandleOKCallback();
+
+ private:
+  grpc_event *result;
+
+  static grpc_completion_queue *queue;
+};
+
+}  // namespace node
+}  // namespace grpc
+
+#endif  // NET_GRPC_NODE_COMPLETION_QUEUE_ASYNC_WORKER_H_
diff --git a/src/node/credentials.cc b/src/node/credentials.cc
new file mode 100644
index 0000000..d58b7ed
--- /dev/null
+++ b/src/node/credentials.cc
@@ -0,0 +1,209 @@
+/*
+ *
+ * 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 <node.h>
+
+#include "grpc/grpc.h"
+#include "grpc/grpc_security.h"
+#include "grpc/support/log.h"
+#include "credentials.h"
+
+namespace grpc {
+namespace node {
+
+using ::node::Buffer;
+using v8::Arguments;
+using v8::Exception;
+using v8::External;
+using v8::Function;
+using v8::FunctionTemplate;
+using v8::Handle;
+using v8::HandleScope;
+using v8::Integer;
+using v8::Local;
+using v8::Object;
+using v8::ObjectTemplate;
+using v8::Persistent;
+using v8::Value;
+
+Persistent<Function> Credentials::constructor;
+Persistent<FunctionTemplate> Credentials::fun_tpl;
+
+Credentials::Credentials(grpc_credentials *credentials)
+    : wrapped_credentials(credentials) {}
+
+Credentials::~Credentials() {
+  gpr_log(GPR_DEBUG, "Destroying credentials object");
+  grpc_credentials_release(wrapped_credentials);
+}
+
+void Credentials::Init(Handle<Object> exports) {
+  NanScope();
+  Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
+  tpl->SetClassName(NanNew("Credentials"));
+  tpl->InstanceTemplate()->SetInternalFieldCount(1);
+  NanAssignPersistent(fun_tpl, tpl);
+  NanAssignPersistent(constructor, tpl->GetFunction());
+  constructor->Set(NanNew("createDefault"),
+                   FunctionTemplate::New(CreateDefault)->GetFunction());
+  constructor->Set(NanNew("createSsl"),
+                   FunctionTemplate::New(CreateSsl)->GetFunction());
+  constructor->Set(NanNew("createComposite"),
+                   FunctionTemplate::New(CreateComposite)->GetFunction());
+  constructor->Set(NanNew("createGce"),
+                   FunctionTemplate::New(CreateGce)->GetFunction());
+  constructor->Set(NanNew("createFake"),
+                   FunctionTemplate::New(CreateFake)->GetFunction());
+  constructor->Set(NanNew("createIam"),
+                   FunctionTemplate::New(CreateIam)->GetFunction());
+  exports->Set(NanNew("Credentials"), constructor);
+}
+
+bool Credentials::HasInstance(Handle<Value> val) {
+  NanScope();
+  return NanHasInstance(fun_tpl, val);
+}
+
+Handle<Value> Credentials::WrapStruct(grpc_credentials *credentials) {
+  NanEscapableScope();
+  if (credentials == NULL) {
+    return NanEscapeScope(NanNull());
+  }
+  const int argc = 1;
+  Handle<Value> argv[argc] = {
+      External::New(reinterpret_cast<void *>(credentials))};
+  return NanEscapeScope(constructor->NewInstance(argc, argv));
+}
+
+grpc_credentials *Credentials::GetWrappedCredentials() {
+  return wrapped_credentials;
+}
+
+NAN_METHOD(Credentials::New) {
+  NanScope();
+
+  if (args.IsConstructCall()) {
+    if (!args[0]->IsExternal()) {
+      return NanThrowTypeError(
+          "Credentials can only be created with the provided functions");
+    }
+    grpc_credentials *creds_value =
+        reinterpret_cast<grpc_credentials *>(External::Unwrap(args[0]));
+    Credentials *credentials = new Credentials(creds_value);
+    credentials->Wrap(args.This());
+    NanReturnValue(args.This());
+  } else {
+    const int argc = 1;
+    Local<Value> argv[argc] = {args[0]};
+    NanReturnValue(constructor->NewInstance(argc, argv));
+  }
+}
+
+NAN_METHOD(Credentials::CreateDefault) {
+  NanScope();
+  NanReturnValue(WrapStruct(grpc_default_credentials_create()));
+}
+
+NAN_METHOD(Credentials::CreateSsl) {
+  NanScope();
+  char *root_certs;
+  char *private_key = NULL;
+  char *cert_chain = NULL;
+  int root_certs_length, private_key_length = 0, cert_chain_length = 0;
+  if (!Buffer::HasInstance(args[0])) {
+    return NanThrowTypeError("createSsl's first argument must be a Buffer");
+  }
+  root_certs = Buffer::Data(args[0]);
+  root_certs_length = Buffer::Length(args[0]);
+  if (Buffer::HasInstance(args[1])) {
+    private_key = Buffer::Data(args[1]);
+    private_key_length = Buffer::Length(args[1]);
+  } else if (!(args[1]->IsNull() || args[1]->IsUndefined())) {
+    return NanThrowTypeError(
+        "createSSl's second argument must be a Buffer if provided");
+  }
+  if (Buffer::HasInstance(args[2])) {
+    cert_chain = Buffer::Data(args[2]);
+    cert_chain_length = Buffer::Length(args[2]);
+  } else if (!(args[2]->IsNull() || args[2]->IsUndefined())) {
+    return NanThrowTypeError(
+        "createSSl's third argument must be a Buffer if provided");
+  }
+  NanReturnValue(WrapStruct(grpc_ssl_credentials_create(
+      reinterpret_cast<unsigned char *>(root_certs), root_certs_length,
+      reinterpret_cast<unsigned char *>(private_key), private_key_length,
+      reinterpret_cast<unsigned char *>(cert_chain), cert_chain_length)));
+}
+
+NAN_METHOD(Credentials::CreateComposite) {
+  NanScope();
+  if (!HasInstance(args[0])) {
+    return NanThrowTypeError(
+        "createComposite's first argument must be a Credentials object");
+  }
+  if (!HasInstance(args[1])) {
+    return NanThrowTypeError(
+        "createComposite's second argument must be a Credentials object");
+  }
+  Credentials *creds1 = ObjectWrap::Unwrap<Credentials>(args[0]->ToObject());
+  Credentials *creds2 = ObjectWrap::Unwrap<Credentials>(args[1]->ToObject());
+  NanReturnValue(WrapStruct(grpc_composite_credentials_create(
+      creds1->wrapped_credentials, creds2->wrapped_credentials)));
+}
+
+NAN_METHOD(Credentials::CreateGce) {
+  NanScope();
+  NanReturnValue(WrapStruct(grpc_compute_engine_credentials_create()));
+}
+
+NAN_METHOD(Credentials::CreateFake) {
+  NanScope();
+  NanReturnValue(WrapStruct(grpc_fake_transport_security_credentials_create()));
+}
+
+NAN_METHOD(Credentials::CreateIam) {
+  NanScope();
+  if (!args[0]->IsString()) {
+    return NanThrowTypeError("createIam's first argument must be a string");
+  }
+  if (!args[1]->IsString()) {
+    return NanThrowTypeError("createIam's second argument must be a string");
+  }
+  NanUtf8String auth_token(args[0]);
+  NanUtf8String auth_selector(args[1]);
+  NanReturnValue(
+      WrapStruct(grpc_iam_credentials_create(*auth_token, *auth_selector)));
+}
+
+}  // namespace node
+}  // namespace grpc
diff --git a/src/node/credentials.h b/src/node/credentials.h
new file mode 100644
index 0000000..981e5a9
--- /dev/null
+++ b/src/node/credentials.h
@@ -0,0 +1,81 @@
+/*
+ *
+ * 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 NET_GRPC_NODE_CREDENTIALS_H_
+#define NET_GRPC_NODE_CREDENTIALS_H_
+
+#include <node.h>
+#include <nan.h>
+#include "grpc/grpc.h"
+#include "grpc/grpc_security.h"
+
+namespace grpc {
+namespace node {
+
+/* Wrapper class for grpc_credentials structs */
+class Credentials : public ::node::ObjectWrap {
+ public:
+  static void Init(v8::Handle<v8::Object> exports);
+  static bool HasInstance(v8::Handle<v8::Value> val);
+  /* Wrap a grpc_credentials struct in a javascript object */
+  static v8::Handle<v8::Value> WrapStruct(grpc_credentials *credentials);
+
+  /* Returns the grpc_credentials struct that this object wraps */
+  grpc_credentials *GetWrappedCredentials();
+
+ private:
+  explicit Credentials(grpc_credentials *credentials);
+  ~Credentials();
+
+  // Prevent copying
+  Credentials(const Credentials &);
+  Credentials &operator=(const Credentials &);
+
+  static NAN_METHOD(New);
+  static NAN_METHOD(CreateDefault);
+  static NAN_METHOD(CreateSsl);
+  static NAN_METHOD(CreateComposite);
+  static NAN_METHOD(CreateGce);
+  static NAN_METHOD(CreateFake);
+  static NAN_METHOD(CreateIam);
+  static v8::Persistent<v8::Function> constructor;
+  // Used for typechecking instances of this javascript class
+  static v8::Persistent<v8::FunctionTemplate> fun_tpl;
+
+  grpc_credentials *wrapped_credentials;
+};
+
+}  // namespace node
+}  // namespace grpc
+
+#endif  // NET_GRPC_NODE_CREDENTIALS_H_
diff --git a/src/node/event.cc b/src/node/event.cc
new file mode 100644
index 0000000..2ca38b7
--- /dev/null
+++ b/src/node/event.cc
@@ -0,0 +1,164 @@
+/*
+ *
+ * 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 <node.h>
+#include <nan.h>
+#include "grpc/grpc.h"
+#include "byte_buffer.h"
+#include "call.h"
+#include "event.h"
+#include "tag.h"
+#include "timeval.h"
+
+namespace grpc {
+namespace node {
+
+using v8::Array;
+using v8::Date;
+using v8::Handle;
+using v8::HandleScope;
+using v8::Number;
+using v8::Object;
+using v8::Persistent;
+using v8::String;
+using v8::Value;
+
+Handle<Value> GetEventData(grpc_event *event) {
+  NanEscapableScope();
+  size_t count;
+  grpc_metadata *items;
+  Handle<Array> metadata;
+  Handle<Object> status;
+  Handle<Object> rpc_new;
+  switch (event->type) {
+    case GRPC_READ:
+      return NanEscapeScope(ByteBufferToBuffer(event->data.read));
+    case GRPC_INVOKE_ACCEPTED:
+      return NanEscapeScope(NanNew<Number>(event->data.invoke_accepted));
+    case GRPC_WRITE_ACCEPTED:
+      return NanEscapeScope(NanNew<Number>(event->data.write_accepted));
+    case GRPC_FINISH_ACCEPTED:
+      return NanEscapeScope(NanNew<Number>(event->data.finish_accepted));
+    case GRPC_CLIENT_METADATA_READ:
+      count = event->data.client_metadata_read.count;
+      items = event->data.client_metadata_read.elements;
+      metadata = NanNew<Array>(static_cast<int>(count));
+      for (unsigned int i = 0; i < count; i++) {
+        Handle<Object> item_obj = NanNew<Object>();
+        item_obj->Set(NanNew<String, const char *>("key"),
+                      NanNew<String, char *>(items[i].key));
+        item_obj->Set(
+            NanNew<String, const char *>("value"),
+            NanNew<String, char *>(items[i].value,
+                                   static_cast<int>(items[i].value_length)));
+        metadata->Set(i, item_obj);
+      }
+      return NanEscapeScope(metadata);
+    case GRPC_FINISHED:
+      status = NanNew<Object>();
+      status->Set(NanNew("code"), NanNew<Number>(event->data.finished.status));
+      if (event->data.finished.details != NULL) {
+        status->Set(NanNew("details"),
+                    String::New(event->data.finished.details));
+      }
+      count = event->data.finished.metadata_count;
+      items = event->data.finished.metadata_elements;
+      metadata = NanNew<Array>(static_cast<int>(count));
+      for (unsigned int i = 0; i < count; i++) {
+        Handle<Object> item_obj = NanNew<Object>();
+        item_obj->Set(NanNew<String, const char *>("key"),
+                      NanNew<String, char *>(items[i].key));
+        item_obj->Set(
+            NanNew<String, const char *>("value"),
+            NanNew<String, char *>(items[i].value,
+                                   static_cast<int>(items[i].value_length)));
+        metadata->Set(i, item_obj);
+      }
+      status->Set(NanNew("metadata"), metadata);
+      return NanEscapeScope(status);
+    case GRPC_SERVER_RPC_NEW:
+      rpc_new = NanNew<Object>();
+      if (event->data.server_rpc_new.method == NULL) {
+        return NanEscapeScope(NanNull());
+      }
+      rpc_new->Set(
+          NanNew<String, const char *>("method"),
+          NanNew<String, const char *>(event->data.server_rpc_new.method));
+      rpc_new->Set(
+          NanNew<String, const char *>("host"),
+          NanNew<String, const char *>(event->data.server_rpc_new.host));
+      rpc_new->Set(NanNew<String, const char *>("absolute_deadline"),
+                   NanNew<Date>(TimespecToMilliseconds(
+                       event->data.server_rpc_new.deadline)));
+      count = event->data.server_rpc_new.metadata_count;
+      items = event->data.server_rpc_new.metadata_elements;
+      metadata = NanNew<Array>(static_cast<int>(count));
+      for (unsigned int i = 0; i < count; i++) {
+        Handle<Object> item_obj = Object::New();
+        item_obj->Set(NanNew<String, const char *>("key"),
+                      NanNew<String, char *>(items[i].key));
+        item_obj->Set(
+            NanNew<String, const char *>("value"),
+            NanNew<String, char *>(items[i].value,
+                                   static_cast<int>(items[i].value_length)));
+        metadata->Set(i, item_obj);
+      }
+      rpc_new->Set(NanNew<String, const char *>("metadata"), metadata);
+      return NanEscapeScope(rpc_new);
+    default:
+      return NanEscapeScope(NanNull());
+  }
+}
+
+Handle<Value> CreateEventObject(grpc_event *event) {
+  NanEscapableScope();
+  if (event == NULL) {
+    return NanEscapeScope(NanNull());
+  }
+  Handle<Object> event_obj = NanNew<Object>();
+  Handle<Value> call;
+  if (TagHasCall(event->tag)) {
+    call = TagGetCall(event->tag);
+  } else {
+    call = Call::WrapStruct(event->call);
+  }
+  event_obj->Set(NanNew<String, const char *>("call"), call);
+  event_obj->Set(NanNew<String, const char *>("type"),
+                 NanNew<Number>(event->type));
+  event_obj->Set(NanNew<String, const char *>("data"), GetEventData(event));
+
+  return NanEscapeScope(event_obj);
+}
+
+}  // namespace node
+}  // namespace grpc
diff --git a/src/node/event.h b/src/node/event.h
new file mode 100644
index 0000000..e06d8f0
--- /dev/null
+++ b/src/node/event.h
@@ -0,0 +1,48 @@
+/*
+ *
+ * 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 NET_GRPC_NODE_EVENT_H_
+#define NET_GRPC_NODE_EVENT_H_
+
+#include <node.h>
+#include "grpc/grpc.h"
+
+namespace grpc {
+namespace node {
+
+v8::Handle<v8::Value> CreateEventObject(grpc_event *event);
+
+}  // namespace node
+}  // namespace grpc
+
+#endif  // NET_GRPC_NODE_EVENT_H_
diff --git a/src/node/examples/math.proto b/src/node/examples/math.proto
new file mode 100644
index 0000000..14eff5d
--- /dev/null
+++ b/src/node/examples/math.proto
@@ -0,0 +1,25 @@
+syntax = "proto2";
+
+package math;
+
+message DivArgs {
+  required int64 dividend = 1;
+  required int64 divisor = 2;
+}
+
+message DivReply {
+  required int64 quotient = 1;
+  required int64 remainder = 2;
+}
+
+message FibArgs {
+  optional int64 limit = 1;
+}
+
+message Num {
+  required int64 num = 1;
+}
+
+message FibReply {
+  required int64 count = 1;
+}
\ No newline at end of file
diff --git a/src/node/examples/math_server.js b/src/node/examples/math_server.js
new file mode 100644
index 0000000..87336b6
--- /dev/null
+++ b/src/node/examples/math_server.js
@@ -0,0 +1,201 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var _ = require('underscore');
+var ProtoBuf = require('protobufjs');
+var fs = require('fs');
+var util = require('util');
+
+var Transform = require('stream').Transform;
+
+var builder = ProtoBuf.loadProtoFile(__dirname + '/math.proto');
+var math = builder.build('math');
+
+var makeConstructor = require('../surface_server.js').makeServerConstructor;
+
+/**
+ * Get a function that deserializes a specific type of protobuf.
+ * @param {function()} cls The constructor of the message type to deserialize
+ * @return {function(Buffer):cls} The deserialization function
+ */
+function deserializeCls(cls) {
+  /**
+   * Deserialize a buffer to a message object
+   * @param {Buffer} arg_buf The buffer to deserialize
+   * @return {cls} The resulting object
+   */
+  return function deserialize(arg_buf) {
+    return cls.decode(arg_buf);
+  };
+}
+
+/**
+ * Get a function that serializes objects to a buffer by protobuf class.
+ * @param {function()} Cls The constructor of the message type to serialize
+ * @return {function(Cls):Buffer} The serialization function
+ */
+function serializeCls(Cls) {
+  /**
+   * Serialize an object to a Buffer
+   * @param {Object} arg The object to serialize
+   * @return {Buffer} The serialized object
+   */
+  return function serialize(arg) {
+    return new Buffer(new Cls(arg).encode().toBuffer());
+  };
+}
+
+/* This function call creates a server constructor for servers that that expose
+ * the four specified methods. This specifies how to serialize messages that the
+ * server sends and deserialize messages that the client sends, and whether the
+ * client or the server will send a stream of messages, for each method. This
+ * also specifies a prefix that will be added to method names when sending them
+ * on the wire. This function call and all of the preceding code in this file
+ * are intended to approximate what the generated code will look like for the
+ * math service */
+var Server = makeConstructor({
+  Div: {
+    serialize: serializeCls(math.DivReply),
+    deserialize: deserializeCls(math.DivArgs),
+    client_stream: false,
+    server_stream: false
+  },
+  Fib: {
+    serialize: serializeCls(math.Num),
+    deserialize: deserializeCls(math.FibArgs),
+    client_stream: false,
+    server_stream: true
+  },
+  Sum: {
+    serialize: serializeCls(math.Num),
+    deserialize: deserializeCls(math.Num),
+    client_stream: true,
+    server_stream: false
+  },
+  DivMany: {
+    serialize: serializeCls(math.DivReply),
+    deserialize: deserializeCls(math.DivArgs),
+    client_stream: true,
+    server_stream: true
+  }
+}, '/Math/');
+
+/**
+ * Server function for division. Provides the /Math/DivMany and /Math/Div
+ * functions (Div is just DivMany with only one stream element). For each
+ * DivArgs parameter, responds with a DivReply with the results of the division
+ * @param {Object} call The object containing request and cancellation info
+ * @param {function(Error, *)} cb Response callback
+ */
+function mathDiv(call, cb) {
+  var req = call.request;
+  if (req.divisor == 0) {
+    cb(new Error('cannot divide by zero'));
+  }
+  cb(null, {
+    quotient: req.dividend / req.divisor,
+    remainder: req.dividend % req.divisor
+  });
+}
+
+/**
+ * Server function for Fibonacci numbers. Provides the /Math/Fib function. Reads
+ * a single parameter that indicates the number of responses, and then responds
+ * with a stream of that many Fibonacci numbers.
+ * @param {stream} stream The stream for sending responses.
+ */
+function mathFib(stream) {
+  // Here, call is a standard writable Node object Stream
+  var previous = 0, current = 1;
+  for (var i = 0; i < stream.request.limit; i++) {
+    stream.write({num: current});
+    var temp = current;
+    current += previous;
+    previous = temp;
+  }
+  stream.end();
+}
+
+/**
+ * Server function for summation. Provides the /Math/Sum function. Reads a
+ * stream of number parameters, then responds with their sum.
+ * @param {stream} call The stream of arguments.
+ * @param {function(Error, *)} cb Response callback
+ */
+function mathSum(call, cb) {
+  // Here, call is a standard readable Node object Stream
+  var sum = 0;
+  call.on('data', function(data) {
+    sum += data.num | 0;
+  });
+  call.on('end', function() {
+    cb(null, {num: sum});
+  });
+}
+
+function mathDivMany(stream) {
+  // Here, call is a standard duplex Node object Stream
+  util.inherits(DivTransform, Transform);
+  function DivTransform() {
+    var options = {objectMode: true};
+    Transform.call(this, options);
+  }
+  DivTransform.prototype._transform = function(div_args, encoding, callback) {
+    if (div_args.divisor == 0) {
+      callback(new Error('cannot divide by zero'));
+    }
+    callback(null, {
+      quotient: div_args.dividend / div_args.divisor,
+      remainder: div_args.dividend % div_args.divisor
+    });
+  };
+  var transform = new DivTransform();
+  stream.pipe(transform);
+  transform.pipe(stream);
+}
+
+var server = new Server({
+  Div: mathDiv,
+  Fib: mathFib,
+  Sum: mathSum,
+  DivMany: mathDivMany
+});
+
+if (require.main === module) {
+  server.bind('localhost:7070').listen();
+}
+
+/**
+ * See docs for server
+ */
+module.exports = server;
diff --git a/src/node/node_grpc.cc b/src/node/node_grpc.cc
new file mode 100644
index 0000000..acee038
--- /dev/null
+++ b/src/node/node_grpc.cc
@@ -0,0 +1,182 @@
+/*
+ *
+ * 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 <node.h>
+#include <nan.h>
+#include <v8.h>
+#include "grpc/grpc.h"
+
+#include "call.h"
+#include "channel.h"
+#include "event.h"
+#include "server.h"
+#include "completion_queue_async_worker.h"
+#include "credentials.h"
+#include "server_credentials.h"
+
+using v8::Handle;
+using v8::Value;
+using v8::Object;
+using v8::Uint32;
+using v8::String;
+
+void InitStatusConstants(Handle<Object> exports) {
+  NanScope();
+  Handle<Object> status = Object::New();
+  exports->Set(NanNew("status"), status);
+  Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_STATUS_OK));
+  status->Set(NanNew("OK"), OK);
+  Handle<Value> CANCELLED(NanNew<Uint32, uint32_t>(GRPC_STATUS_CANCELLED));
+  status->Set(NanNew("CANCELLED"), CANCELLED);
+  Handle<Value> UNKNOWN(NanNew<Uint32, uint32_t>(GRPC_STATUS_UNKNOWN));
+  status->Set(NanNew("UNKNOWN"), UNKNOWN);
+  Handle<Value> INVALID_ARGUMENT(
+      NanNew<Uint32, uint32_t>(GRPC_STATUS_INVALID_ARGUMENT));
+  status->Set(NanNew("INVALID_ARGUMENT"), INVALID_ARGUMENT);
+  Handle<Value> DEADLINE_EXCEEDED(
+      NanNew<Uint32, uint32_t>(GRPC_STATUS_DEADLINE_EXCEEDED));
+  status->Set(NanNew("DEADLINE_EXCEEDED"), DEADLINE_EXCEEDED);
+  Handle<Value> NOT_FOUND(NanNew<Uint32, uint32_t>(GRPC_STATUS_NOT_FOUND));
+  status->Set(NanNew("NOT_FOUND"), NOT_FOUND);
+  Handle<Value> ALREADY_EXISTS(
+      NanNew<Uint32, uint32_t>(GRPC_STATUS_ALREADY_EXISTS));
+  status->Set(NanNew("ALREADY_EXISTS"), ALREADY_EXISTS);
+  Handle<Value> PERMISSION_DENIED(
+      NanNew<Uint32, uint32_t>(GRPC_STATUS_PERMISSION_DENIED));
+  status->Set(NanNew("PERMISSION_DENIED"), PERMISSION_DENIED);
+  Handle<Value> UNAUTHENTICATED(
+      NanNew<Uint32, uint32_t>(GRPC_STATUS_UNAUTHENTICATED));
+  status->Set(NanNew("UNAUTHENTICATED"), UNAUTHENTICATED);
+  Handle<Value> RESOURCE_EXHAUSTED(
+      NanNew<Uint32, uint32_t>(GRPC_STATUS_RESOURCE_EXHAUSTED));
+  status->Set(NanNew("RESOURCE_EXHAUSTED"), RESOURCE_EXHAUSTED);
+  Handle<Value> FAILED_PRECONDITION(
+      NanNew<Uint32, uint32_t>(GRPC_STATUS_FAILED_PRECONDITION));
+  status->Set(NanNew("FAILED_PRECONDITION"), FAILED_PRECONDITION);
+  Handle<Value> ABORTED(NanNew<Uint32, uint32_t>(GRPC_STATUS_ABORTED));
+  status->Set(NanNew("ABORTED"), ABORTED);
+  Handle<Value> OUT_OF_RANGE(
+      NanNew<Uint32, uint32_t>(GRPC_STATUS_OUT_OF_RANGE));
+  status->Set(NanNew("OUT_OF_RANGE"), OUT_OF_RANGE);
+  Handle<Value> UNIMPLEMENTED(
+      NanNew<Uint32, uint32_t>(GRPC_STATUS_UNIMPLEMENTED));
+  status->Set(NanNew("UNIMPLEMENTED"), UNIMPLEMENTED);
+  Handle<Value> INTERNAL(NanNew<Uint32, uint32_t>(GRPC_STATUS_INTERNAL));
+  status->Set(NanNew("INTERNAL"), INTERNAL);
+  Handle<Value> UNAVAILABLE(NanNew<Uint32, uint32_t>(GRPC_STATUS_UNAVAILABLE));
+  status->Set(NanNew("UNAVAILABLE"), UNAVAILABLE);
+  Handle<Value> DATA_LOSS(NanNew<Uint32, uint32_t>(GRPC_STATUS_DATA_LOSS));
+  status->Set(NanNew("DATA_LOSS"), DATA_LOSS);
+}
+
+void InitCallErrorConstants(Handle<Object> exports) {
+  NanScope();
+  Handle<Object> call_error = Object::New();
+  exports->Set(NanNew("callError"), call_error);
+  Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_CALL_OK));
+  call_error->Set(NanNew("OK"), OK);
+  Handle<Value> ERROR(NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR));
+  call_error->Set(NanNew("ERROR"), ERROR);
+  Handle<Value> NOT_ON_SERVER(
+      NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_NOT_ON_SERVER));
+  call_error->Set(NanNew("NOT_ON_SERVER"), NOT_ON_SERVER);
+  Handle<Value> NOT_ON_CLIENT(
+      NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_NOT_ON_CLIENT));
+  call_error->Set(NanNew("NOT_ON_CLIENT"), NOT_ON_CLIENT);
+  Handle<Value> ALREADY_INVOKED(
+      NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_ALREADY_INVOKED));
+  call_error->Set(NanNew("ALREADY_INVOKED"), ALREADY_INVOKED);
+  Handle<Value> NOT_INVOKED(
+      NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_NOT_INVOKED));
+  call_error->Set(NanNew("NOT_INVOKED"), NOT_INVOKED);
+  Handle<Value> ALREADY_FINISHED(
+      NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_ALREADY_FINISHED));
+  call_error->Set(NanNew("ALREADY_FINISHED"), ALREADY_FINISHED);
+  Handle<Value> TOO_MANY_OPERATIONS(
+      NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_TOO_MANY_OPERATIONS));
+  call_error->Set(NanNew("TOO_MANY_OPERATIONS"), TOO_MANY_OPERATIONS);
+  Handle<Value> INVALID_FLAGS(
+      NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_INVALID_FLAGS));
+  call_error->Set(NanNew("INVALID_FLAGS"), INVALID_FLAGS);
+}
+
+void InitOpErrorConstants(Handle<Object> exports) {
+  NanScope();
+  Handle<Object> op_error = Object::New();
+  exports->Set(NanNew("opError"), op_error);
+  Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_OP_OK));
+  op_error->Set(NanNew("OK"), OK);
+  Handle<Value> ERROR(NanNew<Uint32, uint32_t>(GRPC_OP_ERROR));
+  op_error->Set(NanNew("ERROR"), ERROR);
+}
+
+void InitCompletionTypeConstants(Handle<Object> exports) {
+  NanScope();
+  Handle<Object> completion_type = Object::New();
+  exports->Set(NanNew("completionType"), completion_type);
+  Handle<Value> QUEUE_SHUTDOWN(NanNew<Uint32, uint32_t>(GRPC_QUEUE_SHUTDOWN));
+  completion_type->Set(NanNew("QUEUE_SHUTDOWN"), QUEUE_SHUTDOWN);
+  Handle<Value> READ(NanNew<Uint32, uint32_t>(GRPC_READ));
+  completion_type->Set(NanNew("READ"), READ);
+  Handle<Value> INVOKE_ACCEPTED(NanNew<Uint32, uint32_t>(GRPC_INVOKE_ACCEPTED));
+  completion_type->Set(NanNew("INVOKE_ACCEPTED"), INVOKE_ACCEPTED);
+  Handle<Value> WRITE_ACCEPTED(NanNew<Uint32, uint32_t>(GRPC_WRITE_ACCEPTED));
+  completion_type->Set(NanNew("WRITE_ACCEPTED"), WRITE_ACCEPTED);
+  Handle<Value> FINISH_ACCEPTED(NanNew<Uint32, uint32_t>(GRPC_FINISH_ACCEPTED));
+  completion_type->Set(NanNew("FINISH_ACCEPTED"), FINISH_ACCEPTED);
+  Handle<Value> CLIENT_METADATA_READ(
+      NanNew<Uint32, uint32_t>(GRPC_CLIENT_METADATA_READ));
+  completion_type->Set(NanNew("CLIENT_METADATA_READ"), CLIENT_METADATA_READ);
+  Handle<Value> FINISHED(NanNew<Uint32, uint32_t>(GRPC_FINISHED));
+  completion_type->Set(NanNew("FINISHED"), FINISHED);
+  Handle<Value> SERVER_RPC_NEW(NanNew<Uint32, uint32_t>(GRPC_SERVER_RPC_NEW));
+  completion_type->Set(NanNew("SERVER_RPC_NEW"), SERVER_RPC_NEW);
+}
+
+void init(Handle<Object> exports) {
+  NanScope();
+  grpc_init();
+  InitStatusConstants(exports);
+  InitCallErrorConstants(exports);
+  InitOpErrorConstants(exports);
+  InitCompletionTypeConstants(exports);
+
+  grpc::node::Call::Init(exports);
+  grpc::node::Channel::Init(exports);
+  grpc::node::Server::Init(exports);
+  grpc::node::CompletionQueueAsyncWorker::Init(exports);
+  grpc::node::Credentials::Init(exports);
+  grpc::node::ServerCredentials::Init(exports);
+}
+
+NODE_MODULE(grpc, init)
diff --git a/src/node/package.json b/src/node/package.json
new file mode 100644
index 0000000..a2940b2
--- /dev/null
+++ b/src/node/package.json
@@ -0,0 +1,18 @@
+{
+  "name": "grpc",
+  "version": "0.1.0",
+  "description": "gRPC Library for Node",
+  "scripts": {
+    "test": "./node_modules/mocha/bin/mocha"
+  },
+  "dependencies": {
+    "bindings": "^1.2.1",
+    "nan": "~1.3.0",
+    "underscore": "^1.7.0"
+  },
+  "devDependencies": {
+    "mocha": "~1.21.0",
+    "highland": "~2.0.0",
+    "protobufjs": "~3.8.0"
+  }
+}
diff --git a/src/node/port_picker.js b/src/node/port_picker.js
new file mode 100644
index 0000000..ad82f2a
--- /dev/null
+++ b/src/node/port_picker.js
@@ -0,0 +1,52 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var net = require('net');
+
+/**
+ * Finds a free port that a server can bind to, in the format
+ * "address:port"
+ * @param {function(string)} cb The callback that should execute when the port
+ *     is available
+ */
+function nextAvailablePort(cb) {
+  var server = net.createServer();
+  server.listen(function() {
+    var address = server.address();
+    server.close(function() {
+      cb(address.address + ':' + address.port.toString());
+    });
+  });
+}
+
+exports.nextAvailablePort = nextAvailablePort;
diff --git a/src/node/server.cc b/src/node/server.cc
new file mode 100644
index 0000000..6482689
--- /dev/null
+++ b/src/node/server.cc
@@ -0,0 +1,236 @@
+/*
+ *
+ * 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 "server.h"
+
+#include <node.h>
+#include <nan.h>
+
+#include <malloc.h>
+
+#include <vector>
+#include "grpc/grpc.h"
+#include "grpc/grpc_security.h"
+#include "call.h"
+#include "completion_queue_async_worker.h"
+#include "tag.h"
+#include "server_credentials.h"
+
+namespace grpc {
+namespace node {
+
+using v8::Arguments;
+using v8::Array;
+using v8::Boolean;
+using v8::Exception;
+using v8::Function;
+using v8::FunctionTemplate;
+using v8::Handle;
+using v8::HandleScope;
+using v8::Local;
+using v8::Number;
+using v8::Object;
+using v8::Persistent;
+using v8::String;
+using v8::Value;
+
+Persistent<Function> Server::constructor;
+Persistent<FunctionTemplate> Server::fun_tpl;
+
+Server::Server(grpc_server *server) : wrapped_server(server) {}
+
+Server::~Server() { grpc_server_destroy(wrapped_server); }
+
+void Server::Init(Handle<Object> exports) {
+  NanScope();
+  Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
+  tpl->SetClassName(String::NewSymbol("Server"));
+  tpl->InstanceTemplate()->SetInternalFieldCount(1);
+  NanSetPrototypeTemplate(tpl, "requestCall",
+                          FunctionTemplate::New(RequestCall)->GetFunction());
+
+  NanSetPrototypeTemplate(tpl, "addHttp2Port",
+                          FunctionTemplate::New(AddHttp2Port)->GetFunction());
+
+  NanSetPrototypeTemplate(
+      tpl, "addSecureHttp2Port",
+      FunctionTemplate::New(AddSecureHttp2Port)->GetFunction());
+
+  NanSetPrototypeTemplate(tpl, "start",
+                          FunctionTemplate::New(Start)->GetFunction());
+
+  NanSetPrototypeTemplate(tpl, "shutdown",
+                          FunctionTemplate::New(Shutdown)->GetFunction());
+
+  NanAssignPersistent(fun_tpl, tpl);
+  NanAssignPersistent(constructor, tpl->GetFunction());
+  exports->Set(String::NewSymbol("Server"), constructor);
+}
+
+bool Server::HasInstance(Handle<Value> val) {
+  return NanHasInstance(fun_tpl, val);
+}
+
+NAN_METHOD(Server::New) {
+  NanScope();
+
+  /* If this is not a constructor call, make a constructor call and return
+     the result */
+  if (!args.IsConstructCall()) {
+    const int argc = 1;
+    Local<Value> argv[argc] = {args[0]};
+    NanReturnValue(constructor->NewInstance(argc, argv));
+  }
+  grpc_server *wrapped_server;
+  grpc_completion_queue *queue = CompletionQueueAsyncWorker::GetQueue();
+  if (args[0]->IsUndefined()) {
+    wrapped_server = grpc_server_create(queue, NULL);
+  } else if (args[0]->IsObject()) {
+    grpc_server_credentials *creds = NULL;
+    Handle<Object> args_hash(args[0]->ToObject()->Clone());
+    if (args_hash->HasOwnProperty(NanNew("credentials"))) {
+      Handle<Value> creds_value = args_hash->Get(NanNew("credentials"));
+      if (!ServerCredentials::HasInstance(creds_value)) {
+        return NanThrowTypeError(
+            "credentials arg must be a ServerCredentials object");
+      }
+      ServerCredentials *creds_object =
+          ObjectWrap::Unwrap<ServerCredentials>(creds_value->ToObject());
+      creds = creds_object->GetWrappedServerCredentials();
+      args_hash->Delete(NanNew("credentials"));
+    }
+    Handle<Array> keys(args_hash->GetOwnPropertyNames());
+    grpc_channel_args channel_args;
+    channel_args.num_args = keys->Length();
+    channel_args.args = reinterpret_cast<grpc_arg *>(
+        calloc(channel_args.num_args, sizeof(grpc_arg)));
+    /* These are used to keep all strings until then end of the block, then
+       destroy them */
+    std::vector<NanUtf8String *> key_strings(keys->Length());
+    std::vector<NanUtf8String *> value_strings(keys->Length());
+    for (unsigned int i = 0; i < channel_args.num_args; i++) {
+      Handle<String> current_key(keys->Get(i)->ToString());
+      Handle<Value> current_value(args_hash->Get(current_key));
+      key_strings[i] = new NanUtf8String(current_key);
+      channel_args.args[i].key = **key_strings[i];
+      if (current_value->IsInt32()) {
+        channel_args.args[i].type = GRPC_ARG_INTEGER;
+        channel_args.args[i].value.integer = current_value->Int32Value();
+      } else if (current_value->IsString()) {
+        channel_args.args[i].type = GRPC_ARG_STRING;
+        value_strings[i] = new NanUtf8String(current_value);
+        channel_args.args[i].value.string = **value_strings[i];
+      } else {
+        free(channel_args.args);
+        return NanThrowTypeError("Arg values must be strings");
+      }
+    }
+    if (creds == NULL) {
+      wrapped_server = grpc_server_create(queue, &channel_args);
+    } else {
+      wrapped_server = grpc_secure_server_create(creds, queue, &channel_args);
+    }
+    free(channel_args.args);
+  } else {
+    return NanThrowTypeError("Server expects an object");
+  }
+  Server *server = new Server(wrapped_server);
+  server->Wrap(args.This());
+  NanReturnValue(args.This());
+}
+
+NAN_METHOD(Server::RequestCall) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    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(
+      server->wrapped_server, CreateTag(args[0], NanNull()));
+  if (error == GRPC_CALL_OK) {
+    CompletionQueueAsyncWorker::Next();
+  } else {
+    return NanThrowError("requestCall failed", error);
+  }
+  NanReturnUndefined();
+}
+
+NAN_METHOD(Server::AddHttp2Port) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError("addHttp2Port can only be called on a Server");
+  }
+  if (!args[0]->IsString()) {
+    return NanThrowTypeError("addHttp2Port's argument must be a String");
+  }
+  Server *server = ObjectWrap::Unwrap<Server>(args.This());
+  NanReturnValue(NanNew<Boolean>(grpc_server_add_http2_port(
+      server->wrapped_server, *NanUtf8String(args[0]))));
+}
+
+NAN_METHOD(Server::AddSecureHttp2Port) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError(
+        "addSecureHttp2Port can only be called on a Server");
+  }
+  if (!args[0]->IsString()) {
+    return NanThrowTypeError("addSecureHttp2Port's argument must be a String");
+  }
+  Server *server = ObjectWrap::Unwrap<Server>(args.This());
+  NanReturnValue(NanNew<Boolean>(grpc_server_add_secure_http2_port(
+      server->wrapped_server, *NanUtf8String(args[0]))));
+}
+
+NAN_METHOD(Server::Start) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError("start can only be called on a Server");
+  }
+  Server *server = ObjectWrap::Unwrap<Server>(args.This());
+  grpc_server_start(server->wrapped_server);
+  NanReturnUndefined();
+}
+
+NAN_METHOD(Server::Shutdown) {
+  NanScope();
+  if (!HasInstance(args.This())) {
+    return NanThrowTypeError("shutdown can only be called on a Server");
+  }
+  Server *server = ObjectWrap::Unwrap<Server>(args.This());
+  grpc_server_shutdown(server->wrapped_server);
+  NanReturnUndefined();
+}
+
+}  // namespace node
+}  // namespace grpc
diff --git a/src/node/server.h b/src/node/server.h
new file mode 100644
index 0000000..d50f1fb
--- /dev/null
+++ b/src/node/server.h
@@ -0,0 +1,79 @@
+/*
+ *
+ * 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 NET_GRPC_NODE_SERVER_H_
+#define NET_GRPC_NODE_SERVER_H_
+
+#include <node.h>
+#include <nan.h>
+#include "grpc/grpc.h"
+
+namespace grpc {
+namespace node {
+
+/* Wraps grpc_server as a JavaScript object. Provides a constructor
+   and wrapper methods for grpc_server_create, grpc_server_request_call,
+   grpc_server_add_http2_port, and grpc_server_start. */
+class Server : public ::node::ObjectWrap {
+ public:
+  /* Initializes the Server class and exposes the constructor and
+     wrapper methods to JavaScript */
+  static void Init(v8::Handle<v8::Object> exports);
+  /* Tests whether the given value was constructed by this class's
+     JavaScript constructor */
+  static bool HasInstance(v8::Handle<v8::Value> val);
+
+ private:
+  explicit Server(grpc_server *server);
+  ~Server();
+
+  // Prevent copying
+  Server(const Server &);
+  Server &operator=(const Server &);
+
+  static NAN_METHOD(New);
+  static NAN_METHOD(RequestCall);
+  static NAN_METHOD(AddHttp2Port);
+  static NAN_METHOD(AddSecureHttp2Port);
+  static NAN_METHOD(Start);
+  static NAN_METHOD(Shutdown);
+  static v8::Persistent<v8::Function> constructor;
+  static v8::Persistent<v8::FunctionTemplate> fun_tpl;
+
+  grpc_server *wrapped_server;
+};
+
+}  // namespace node
+}  // namespace grpc
+
+#endif  // NET_GRPC_NODE_SERVER_H_
diff --git a/src/node/server.js b/src/node/server.js
new file mode 100644
index 0000000..2704c68
--- /dev/null
+++ b/src/node/server.js
@@ -0,0 +1,262 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var grpc = require('bindings')('grpc.node');
+
+var common = require('./common');
+
+var Duplex = require('stream').Duplex;
+var util = require('util');
+
+util.inherits(GrpcServerStream, Duplex);
+
+/**
+ * Class for representing a gRPC server side stream as a Node stream. Extends
+ * from stream.Duplex.
+ * @constructor
+ * @param {grpc.Call} call Call object to proxy
+ * @param {object} options Stream options
+ */
+function GrpcServerStream(call, options) {
+  Duplex.call(this, options);
+  this._call = call;
+  // Indicate that a status has been sent
+  var finished = false;
+  var self = this;
+  var status = {
+    'code' : grpc.status.OK,
+    'details' : 'OK'
+  };
+  /**
+   * Send the pending status
+   */
+  function sendStatus() {
+    call.startWriteStatus(status.code, status.details, function() {
+    });
+    finished = true;
+  }
+  this.on('finish', sendStatus);
+  /**
+   * Set the pending status to a given error status. If the error does not have
+   * code or details properties, the code will be set to grpc.status.INTERNAL
+   * and the details will be set to 'Unknown Error'.
+   * @param {Error} err The error object
+   */
+  function setStatus(err) {
+    console.log('Server setting status to', err);
+    var code = grpc.status.INTERNAL;
+    var details = 'Unknown Error';
+
+    if (err.hasOwnProperty('code')) {
+      code = err.code;
+      if (err.hasOwnProperty('details')) {
+        details = err.details;
+      }
+    }
+    status = {'code': code, 'details': details};
+  }
+  /**
+   * Terminate the call. This includes indicating that reads are done, draining
+   * all pending writes, and sending the given error as a status
+   * @param {Error} err The error object
+   * @this GrpcServerStream
+   */
+  function terminateCall(err) {
+    // Drain readable data
+    this.on('data', function() {});
+    setStatus(err);
+    this.end();
+  }
+  this.on('error', terminateCall);
+  // Indicates that a read is pending
+  var reading = false;
+  /**
+   * Callback to be called when a READ event is received. Pushes the data onto
+   * the read queue and starts reading again if applicable
+   * @param {grpc.Event} event READ event object
+   */
+  function readCallback(event) {
+    if (finished) {
+      self.push(null);
+      return;
+    }
+    var data = event.data;
+    if (self.push(data) && data != null) {
+      self._call.startRead(readCallback);
+    } else {
+      reading = false;
+    }
+  }
+  /**
+   * Start reading if there is not already a pending read. Reading will
+   * continue until self.push returns false (indicating reads should slow
+   * down) or the read data is null (indicating that there is no more data).
+   */
+  this.startReading = function() {
+    if (finished) {
+      self.push(null);
+    } else {
+      if (!reading) {
+        reading = true;
+        self._call.startRead(readCallback);
+      }
+    }
+  };
+}
+
+/**
+ * Start reading from the gRPC data source. This is an implementation of a
+ * method required for implementing stream.Readable
+ * @param {number} size Ignored
+ */
+GrpcServerStream.prototype._read = function(size) {
+  this.startReading();
+};
+
+/**
+ * Start writing a chunk of data. This is an implementation of a method required
+ * for implementing stream.Writable.
+ * @param {Buffer} chunk The chunk of data to write
+ * @param {string} encoding Ignored
+ * @param {function(Error=)} callback Callback to indicate that the write is
+ *     complete
+ */
+GrpcServerStream.prototype._write = function(chunk, encoding, callback) {
+  var self = this;
+  self._call.startWrite(chunk, function(event) {
+    callback();
+  }, 0);
+};
+
+/**
+ * Constructs a server object that stores request handlers and delegates
+ * incoming requests to those handlers
+ * @constructor
+ * @param {Array} options Options that should be passed to the internal server
+ *     implementation
+ */
+function Server(options) {
+  this.handlers = {};
+  var handlers = this.handlers;
+  var server = new grpc.Server(options);
+  this._server = server;
+  var started = false;
+  /**
+   * Start the server and begin handling requests
+   * @this Server
+   */
+  this.start = function() {
+    if (this.started) {
+      throw 'Server is already running';
+    }
+    server.start();
+    /**
+     * Handles the SERVER_RPC_NEW event. If there is a handler associated with
+     * the requested method, use that handler to respond to the request. Then
+     * wait for the next request
+     * @param {grpc.Event} event The event to handle with tag SERVER_RPC_NEW
+     */
+    function handleNewCall(event) {
+      var call = event.call;
+      var data = event.data;
+      if (data == null) {
+        return;
+      }
+      server.requestCall(handleNewCall);
+      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;
+        }
+      }, 0);
+      call.serverEndInitialMetadata(0);
+      var stream = new GrpcServerStream(call);
+      Object.defineProperty(stream, 'cancelled', {
+        get: function() { return cancelled;}
+      });
+      try {
+        handler(stream, data.metadata);
+      } catch (e) {
+        stream.emit('error', e);
+      }
+    }
+    server.requestCall(handleNewCall);
+  };
+  /** Shuts down the server.
+   */
+  this.shutdown = function() {
+    server.shutdown();
+  };
+}
+
+/**
+ * Registers a handler to handle the named method. Fails if there already is
+ * a handler for the given method. Returns true on success
+ * @param {string} name The name of the method that the provided function should
+ *     handle/respond to.
+ * @param {function} handler Function that takes a stream of request values and
+ *     returns a stream of response values
+ * @return {boolean} True if the handler was set. False if a handler was already
+ *     set for that name.
+ */
+Server.prototype.register = function(name, handler) {
+  if (this.handlers.hasOwnProperty(name)) {
+    return false;
+  }
+  this.handlers[name] = handler;
+  return true;
+};
+
+/**
+ * Binds the server to the given port, with SSL enabled if secure is specified
+ * @param {string} port The port that the server should bind on, in the format
+ *     "address:port"
+ * @param {boolean=} secure Whether the server should open a secure port
+ */
+Server.prototype.bind = function(port, secure) {
+  if (secure) {
+    this._server.addSecureHttp2Port(port);
+  } else {
+    this._server.addHttp2Port(port);
+  }
+};
+
+/**
+ * See documentation for Server
+ */
+module.exports = Server;
diff --git a/src/node/server_credentials.cc b/src/node/server_credentials.cc
new file mode 100644
index 0000000..38df547
--- /dev/null
+++ b/src/node/server_credentials.cc
@@ -0,0 +1,161 @@
+/*
+ *
+ * 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 <node.h>
+
+#include "grpc/grpc.h"
+#include "grpc/grpc_security.h"
+#include "grpc/support/log.h"
+#include "server_credentials.h"
+
+namespace grpc {
+namespace node {
+
+using ::node::Buffer;
+using v8::Arguments;
+using v8::Exception;
+using v8::External;
+using v8::Function;
+using v8::FunctionTemplate;
+using v8::Handle;
+using v8::HandleScope;
+using v8::Integer;
+using v8::Local;
+using v8::Object;
+using v8::ObjectTemplate;
+using v8::Persistent;
+using v8::Value;
+
+Persistent<Function> ServerCredentials::constructor;
+Persistent<FunctionTemplate> ServerCredentials::fun_tpl;
+
+ServerCredentials::ServerCredentials(grpc_server_credentials *credentials)
+    : wrapped_credentials(credentials) {}
+
+ServerCredentials::~ServerCredentials() {
+  gpr_log(GPR_DEBUG, "Destroying server credentials object");
+  grpc_server_credentials_release(wrapped_credentials);
+}
+
+void ServerCredentials::Init(Handle<Object> exports) {
+  NanScope();
+  Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
+  tpl->SetClassName(NanNew("ServerCredentials"));
+  tpl->InstanceTemplate()->SetInternalFieldCount(1);
+  NanAssignPersistent(fun_tpl, tpl);
+  NanAssignPersistent(constructor, tpl->GetFunction());
+  constructor->Set(NanNew("createSsl"),
+                   FunctionTemplate::New(CreateSsl)->GetFunction());
+  constructor->Set(NanNew("createFake"),
+                   FunctionTemplate::New(CreateFake)->GetFunction());
+  exports->Set(NanNew("ServerCredentials"), constructor);
+}
+
+bool ServerCredentials::HasInstance(Handle<Value> val) {
+  NanScope();
+  return NanHasInstance(fun_tpl, val);
+}
+
+Handle<Value> ServerCredentials::WrapStruct(
+    grpc_server_credentials *credentials) {
+  NanEscapableScope();
+  if (credentials == NULL) {
+    return NanEscapeScope(NanNull());
+  }
+  const int argc = 1;
+  Handle<Value> argv[argc] = {
+      External::New(reinterpret_cast<void *>(credentials))};
+  return NanEscapeScope(constructor->NewInstance(argc, argv));
+}
+
+grpc_server_credentials *ServerCredentials::GetWrappedServerCredentials() {
+  return wrapped_credentials;
+}
+
+NAN_METHOD(ServerCredentials::New) {
+  NanScope();
+
+  if (args.IsConstructCall()) {
+    if (!args[0]->IsExternal()) {
+      return NanThrowTypeError(
+          "ServerCredentials can only be created with the provide functions");
+    }
+    grpc_server_credentials *creds_value =
+        reinterpret_cast<grpc_server_credentials *>(External::Unwrap(args[0]));
+    ServerCredentials *credentials = new ServerCredentials(creds_value);
+    credentials->Wrap(args.This());
+    NanReturnValue(args.This());
+  } else {
+    const int argc = 1;
+    Local<Value> argv[argc] = {args[0]};
+    NanReturnValue(constructor->NewInstance(argc, argv));
+  }
+}
+
+NAN_METHOD(ServerCredentials::CreateSsl) {
+  NanScope();
+  char *root_certs = NULL;
+  char *private_key;
+  char *cert_chain;
+  int root_certs_length = 0, private_key_length, cert_chain_length;
+  if (Buffer::HasInstance(args[0])) {
+    root_certs = Buffer::Data(args[0]);
+    root_certs_length = Buffer::Length(args[0]);
+  } else if (!(args[0]->IsNull() || args[0]->IsUndefined())) {
+    return NanThrowTypeError(
+        "createSSl's first argument must be a Buffer if provided");
+  }
+  if (!Buffer::HasInstance(args[1])) {
+    return NanThrowTypeError("createSsl's second argument must be a Buffer");
+  }
+  private_key = Buffer::Data(args[1]);
+  private_key_length = Buffer::Length(args[1]);
+  if (!Buffer::HasInstance(args[2])) {
+    return NanThrowTypeError("createSsl's third argument must be a Buffer");
+  }
+  cert_chain = Buffer::Data(args[2]);
+  cert_chain_length = Buffer::Length(args[2]);
+  NanReturnValue(WrapStruct(grpc_ssl_server_credentials_create(
+      reinterpret_cast<unsigned char *>(root_certs), root_certs_length,
+      reinterpret_cast<unsigned char *>(private_key), private_key_length,
+      reinterpret_cast<unsigned char *>(cert_chain), cert_chain_length)));
+}
+
+NAN_METHOD(ServerCredentials::CreateFake) {
+  NanScope();
+  NanReturnValue(
+      WrapStruct(grpc_fake_transport_security_server_credentials_create()));
+}
+
+}  // namespace node
+}  // namespace grpc
diff --git a/src/node/server_credentials.h b/src/node/server_credentials.h
new file mode 100644
index 0000000..8baae3f
--- /dev/null
+++ b/src/node/server_credentials.h
@@ -0,0 +1,77 @@
+/*
+ *
+ * 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 NET_GRPC_NODE_SERVER_CREDENTIALS_H_
+#define NET_GRPC_NODE_SERVER_CREDENTIALS_H_
+
+#include <node.h>
+#include <nan.h>
+#include "grpc/grpc.h"
+#include "grpc/grpc_security.h"
+
+namespace grpc {
+namespace node {
+
+/* Wrapper class for grpc_server_credentials structs */
+class ServerCredentials : public ::node::ObjectWrap {
+ public:
+  static void Init(v8::Handle<v8::Object> exports);
+  static bool HasInstance(v8::Handle<v8::Value> val);
+  /* Wrap a grpc_server_credentials struct in a javascript object */
+  static v8::Handle<v8::Value> WrapStruct(grpc_server_credentials *credentials);
+
+  /* Returns the grpc_server_credentials struct that this object wraps */
+  grpc_server_credentials *GetWrappedServerCredentials();
+
+ private:
+  explicit ServerCredentials(grpc_server_credentials *credentials);
+  ~ServerCredentials();
+
+  // Prevent copying
+  ServerCredentials(const ServerCredentials &);
+  ServerCredentials &operator=(const ServerCredentials &);
+
+  static NAN_METHOD(New);
+  static NAN_METHOD(CreateSsl);
+  static NAN_METHOD(CreateFake);
+  static v8::Persistent<v8::Function> constructor;
+  // Used for typechecking instances of this javascript class
+  static v8::Persistent<v8::FunctionTemplate> fun_tpl;
+
+  grpc_server_credentials *wrapped_credentials;
+};
+
+}  // namespace node
+}  // namespace grpc
+
+#endif  // NET_GRPC_NODE_SERVER_CREDENTIALS_H_
diff --git a/src/node/surface_client.js b/src/node/surface_client.js
new file mode 100644
index 0000000..acd2208
--- /dev/null
+++ b/src/node/surface_client.js
@@ -0,0 +1,373 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var _ = require('underscore');
+
+var client = require('./client.js');
+
+var EventEmitter = require('events').EventEmitter;
+
+var stream = require('stream');
+
+var Readable = stream.Readable;
+var Writable = stream.Writable;
+var Duplex = stream.Duplex;
+var util = require('util');
+
+function forwardEvent(fromEmitter, toEmitter, event) {
+  fromEmitter.on(event, function forward() {
+    _.partial(toEmitter.emit, event).apply(toEmitter, arguments);
+  });
+}
+
+util.inherits(ClientReadableObjectStream, Readable);
+
+/**
+ * Class for representing a gRPC server streaming call as a Node stream on the
+ * client side. Extends from stream.Readable.
+ * @constructor
+ * @param {stream} stream Underlying binary Duplex stream for the call
+ * @param {function(Buffer)} deserialize Function for deserializing binary data
+ * @param {object} options Stream options
+ */
+function ClientReadableObjectStream(stream, deserialize, options) {
+  options = _.extend(options, {objectMode: true});
+  Readable.call(this, options);
+  this._stream = stream;
+  var self = this;
+  forwardEvent(stream, this, 'status');
+  forwardEvent(stream, this, 'metadata');
+  this._stream.on('data', function forwardData(chunk) {
+    if (!self.push(deserialize(chunk))) {
+      self._stream.pause();
+    }
+  });
+  this._stream.pause();
+}
+
+util.inherits(ClientWritableObjectStream, Writable);
+
+/**
+ * Class for representing a gRPC client streaming call as a Node stream on the
+ * client side. Extends from stream.Writable.
+ * @constructor
+ * @param {stream} stream Underlying binary Duplex stream for the call
+ * @param {function(*):Buffer} serialize Function for serializing objects
+ * @param {object} options Stream options
+ */
+function ClientWritableObjectStream(stream, serialize, options) {
+  options = _.extend(options, {objectMode: true});
+  Writable.call(this, options);
+  this._stream = stream;
+  this._serialize = serialize;
+  forwardEvent(stream, this, 'status');
+  forwardEvent(stream, this, 'metadata');
+  this.on('finish', function() {
+    this._stream.end();
+  });
+}
+
+
+util.inherits(ClientBidiObjectStream, Duplex);
+
+/**
+ * Class for representing a gRPC bidi streaming call as a Node stream on the
+ * client side. Extends from stream.Duplex.
+ * @constructor
+ * @param {stream} stream Underlying binary Duplex stream for the call
+ * @param {function(*):Buffer} serialize Function for serializing objects
+ * @param {function(Buffer)} deserialize Function for deserializing binary data
+ * @param {object} options Stream options
+ */
+function ClientBidiObjectStream(stream, serialize, deserialize, options) {
+  options = _.extend(options, {objectMode: true});
+  Duplex.call(this, options);
+  this._stream = stream;
+  this._serialize = serialize;
+  var self = this;
+  forwardEvent(stream, this, 'status');
+  forwardEvent(stream, this, 'metadata');
+  this._stream.on('data', function forwardData(chunk) {
+    if (!self.push(deserialize(chunk))) {
+      self._stream.pause();
+    }
+  });
+  this._stream.pause();
+  this.on('finish', function() {
+    this._stream.end();
+  });
+}
+
+/**
+ * _read implementation for both types of streams that allow reading.
+ * @this {ClientReadableObjectStream|ClientBidiObjectStream}
+ * @param {number} size Ignored
+ */
+function _read(size) {
+  this._stream.resume();
+}
+
+/**
+ * See docs for _read
+ */
+ClientReadableObjectStream.prototype._read = _read;
+/**
+ * See docs for _read
+ */
+ClientBidiObjectStream.prototype._read = _read;
+
+/**
+ * _write implementation for both types of streams that allow writing
+ * @this {ClientWritableObjectStream|ClientBidiObjectStream}
+ * @param {*} chunk The value to write to the stream
+ * @param {string} encoding Ignored
+ * @param {function(Error)} callback Callback to call when finished writing
+ */
+function _write(chunk, encoding, callback) {
+  this._stream.write(this._serialize(chunk), encoding, callback);
+}
+
+/**
+ * See docs for _write
+ */
+ClientWritableObjectStream.prototype._write = _write;
+/**
+ * See docs for _write
+ */
+ClientBidiObjectStream.prototype._write = _write;
+
+/**
+ * Get a function that can make unary requests to the specified method.
+ * @param {string} method The name of the method to request
+ * @param {function(*):Buffer} serialize The serialization function for inputs
+ * @param {function(Buffer)} deserialize The deserialization function for
+ *     outputs
+ * @return {Function} makeUnaryRequest
+ */
+function makeUnaryRequestFunction(method, serialize, deserialize) {
+  /**
+   * Make a unary request with this method on the given channel with the given
+   * argument, callback, etc.
+   * @this {SurfaceClient} Client object. Must have a channel member.
+   * @param {*} argument The argument to the call. Should be serializable with
+   *     serialize
+   * @param {function(?Error, value=)} callback The callback to for when the
+   *     response is received
+   * @param {array=} metadata Array of metadata key/value pairs to add to the
+   *     call
+   * @param {(number|Date)=} deadline The deadline for processing this request.
+   *     Defaults to infinite future
+   * @return {EventEmitter} An event emitter for stream related events
+   */
+  function makeUnaryRequest(argument, callback, metadata, deadline) {
+    var stream = client.makeRequest(this.channel, method, metadata, deadline);
+    var emitter = new EventEmitter();
+    forwardEvent(stream, emitter, 'status');
+    forwardEvent(stream, emitter, 'metadata');
+    stream.write(serialize(argument));
+    stream.end();
+    stream.on('data', function forwardData(chunk) {
+      try {
+        callback(null, deserialize(chunk));
+      } catch (e) {
+        callback(e);
+      }
+    });
+    return emitter;
+  }
+  return makeUnaryRequest;
+}
+
+/**
+ * Get a function that can make client stream requests to the specified method.
+ * @param {string} method The name of the method to request
+ * @param {function(*):Buffer} serialize The serialization function for inputs
+ * @param {function(Buffer)} deserialize The deserialization function for
+ *     outputs
+ * @return {Function} makeClientStreamRequest
+ */
+function makeClientStreamRequestFunction(method, serialize, deserialize) {
+  /**
+   * Make a client stream request with this method on the given channel with the
+   * given callback, etc.
+   * @this {SurfaceClient} Client object. Must have a channel member.
+   * @param {function(?Error, value=)} callback The callback to for when the
+   *     response is received
+   * @param {array=} metadata Array of metadata key/value pairs to add to the
+   *     call
+   * @param {(number|Date)=} deadline The deadline for processing this request.
+   *     Defaults to infinite future
+   * @return {EventEmitter} An event emitter for stream related events
+   */
+  function makeClientStreamRequest(callback, metadata, deadline) {
+    var stream = client.makeRequest(this.channel, method, metadata, deadline);
+    var obj_stream = new ClientWritableObjectStream(stream, serialize, {});
+    stream.on('data', function forwardData(chunk) {
+      try {
+        callback(null, deserialize(chunk));
+      } catch (e) {
+        callback(e);
+      }
+    });
+    return obj_stream;
+  }
+  return makeClientStreamRequest;
+}
+
+/**
+ * Get a function that can make server stream requests to the specified method.
+ * @param {string} method The name of the method to request
+ * @param {function(*):Buffer} serialize The serialization function for inputs
+ * @param {function(Buffer)} deserialize The deserialization function for
+ *     outputs
+ * @return {Function} makeServerStreamRequest
+ */
+function makeServerStreamRequestFunction(method, serialize, deserialize) {
+  /**
+   * Make a server stream request with this method on the given channel with the
+   * given argument, etc.
+   * @this {SurfaceClient} Client object. Must have a channel member.
+   * @param {*} argument The argument to the call. Should be serializable with
+   *     serialize
+   * @param {array=} metadata Array of metadata key/value pairs to add to the
+   *     call
+   * @param {(number|Date)=} deadline The deadline for processing this request.
+   *     Defaults to infinite future
+   * @return {EventEmitter} An event emitter for stream related events
+   */
+  function makeServerStreamRequest(argument, metadata, deadline) {
+    var stream = client.makeRequest(this.channel, method, metadata, deadline);
+    var obj_stream = new ClientReadableObjectStream(stream, deserialize, {});
+    stream.write(serialize(argument));
+    stream.end();
+    return obj_stream;
+  }
+  return makeServerStreamRequest;
+}
+
+/**
+ * Get a function that can make bidirectional stream requests to the specified
+ * method.
+ * @param {string} method The name of the method to request
+ * @param {function(*):Buffer} serialize The serialization function for inputs
+ * @param {function(Buffer)} deserialize The deserialization function for
+ *     outputs
+ * @return {Function} makeBidiStreamRequest
+ */
+function makeBidiStreamRequestFunction(method, serialize, deserialize) {
+  /**
+   * Make a bidirectional stream request with this method on the given channel.
+   * @this {SurfaceClient} Client object. Must have a channel member.
+   * @param {array=} metadata Array of metadata key/value pairs to add to the
+   *     call
+   * @param {(number|Date)=} deadline The deadline for processing this request.
+   *     Defaults to infinite future
+   * @return {EventEmitter} An event emitter for stream related events
+   */
+  function makeBidiStreamRequest(metadata, deadline) {
+    var stream = client.makeRequest(this.channel, method, metadata, deadline);
+    var obj_stream = new ClientBidiObjectStream(stream,
+                                                serialize,
+                                                deserialize,
+                                                {});
+    return obj_stream;
+  }
+  return makeBidiStreamRequest;
+}
+
+/**
+ * Map with short names for each of the requester maker functions. Used in
+ * makeClientConstructor
+ */
+var requester_makers = {
+  unary: makeUnaryRequestFunction,
+  server_stream: makeServerStreamRequestFunction,
+  client_stream: makeClientStreamRequestFunction,
+  bidi: makeBidiStreamRequestFunction
+}
+
+/**
+ * Creates a constructor for clients with a service defined by the methods
+ * object. The methods object has string keys and values of this form:
+ * {serialize: function, deserialize: function, client_stream: bool,
+ *  server_stream: bool}
+ * @param {!Object<string, Object>} methods Method descriptor for each method
+ *     the client should expose
+ * @param {string} prefix The prefix to prepend to each method name
+ * @return {function(string, Object)} New client constructor
+ */
+function makeClientConstructor(methods, prefix) {
+  /**
+   * Create a client with the given methods
+   * @constructor
+   * @param {string} address The address of the server to connect to
+   * @param {Object} options Options to pass to the underlying channel
+   */
+  function SurfaceClient(address, options) {
+    this.channel = new client.Channel(address, options);
+  }
+
+  _.each(methods, function(method, name) {
+    var method_type;
+    if (method.client_stream) {
+      if (method.server_stream) {
+        method_type = 'bidi';
+      } else {
+        method_type = 'client_stream';
+      }
+    } else {
+      if (method.server_stream) {
+        method_type = 'server_stream';
+      } else {
+        method_type = 'unary';
+      }
+    }
+    SurfaceClient.prototype[name] = requester_makers[method_type](
+        prefix + name,
+        method.serialize,
+        method.deserialize);
+  });
+
+  return SurfaceClient;
+}
+
+exports.makeClientConstructor = makeClientConstructor;
+
+/**
+ * See docs for client.status
+ */
+exports.status = client.status;
+/**
+ * See docs for client.callError
+ */
+exports.callError = client.callError;
diff --git a/src/node/surface_server.js b/src/node/surface_server.js
new file mode 100644
index 0000000..295c1cc
--- /dev/null
+++ b/src/node/surface_server.js
@@ -0,0 +1,358 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var _ = require('underscore');
+
+var Server = require('./server.js');
+
+var stream = require('stream');
+
+var Readable = stream.Readable;
+var Writable = stream.Writable;
+var Duplex = stream.Duplex;
+var util = require('util');
+
+util.inherits(ServerReadableObjectStream, Readable);
+
+/**
+ * Class for representing a gRPC client streaming call as a Node stream on the
+ * server side. Extends from stream.Readable.
+ * @constructor
+ * @param {stream} stream Underlying binary Duplex stream for the call
+ * @param {function(Buffer)} deserialize Function for deserializing binary data
+ * @param {object} options Stream options
+ */
+function ServerReadableObjectStream(stream, deserialize, options) {
+  options = _.extend(options, {objectMode: true});
+  Readable.call(this, options);
+  this._stream = stream;
+  Object.defineProperty(this, 'cancelled', {
+    get: function() { return stream.cancelled; }
+  });
+  var self = this;
+  this._stream.on('data', function forwardData(chunk) {
+    if (!self.push(deserialize(chunk))) {
+      self._stream.pause();
+    }
+  });
+  this._stream.on('end', function forwardEnd() {
+    self.push(null);
+  });
+  this._stream.pause();
+}
+
+util.inherits(ServerWritableObjectStream, Writable);
+
+/**
+ * Class for representing a gRPC server streaming call as a Node stream on the
+ * server side. Extends from stream.Writable.
+ * @constructor
+ * @param {stream} stream Underlying binary Duplex stream for the call
+ * @param {function(*):Buffer} serialize Function for serializing objects
+ * @param {object} options Stream options
+ */
+function ServerWritableObjectStream(stream, serialize, options) {
+  options = _.extend(options, {objectMode: true});
+  Writable.call(this, options);
+  this._stream = stream;
+  this._serialize = serialize;
+  this.on('finish', function() {
+    this._stream.end();
+  });
+}
+
+util.inherits(ServerBidiObjectStream, Duplex);
+
+/**
+ * Class for representing a gRPC bidi streaming call as a Node stream on the
+ * server side. Extends from stream.Duplex.
+ * @constructor
+ * @param {stream} stream Underlying binary Duplex stream for the call
+ * @param {function(*):Buffer} serialize Function for serializing objects
+ * @param {function(Buffer)} deserialize Function for deserializing binary data
+ * @param {object} options Stream options
+ */
+function ServerBidiObjectStream(stream, serialize, deserialize, options) {
+  options = _.extend(options, {objectMode: true});
+  Duplex.call(this, options);
+  this._stream = stream;
+  this._serialize = serialize;
+  var self = this;
+  this._stream.on('data', function forwardData(chunk) {
+    if (!self.push(deserialize(chunk))) {
+      self._stream.pause();
+    }
+  });
+  this._stream.on('end', function forwardEnd() {
+    self.push(null);
+  });
+  this._stream.pause();
+  this.on('finish', function() {
+    this._stream.end();
+  });
+}
+
+/**
+ * _read implementation for both types of streams that allow reading.
+ * @this {ServerReadableObjectStream|ServerBidiObjectStream}
+ * @param {number} size Ignored
+ */
+function _read(size) {
+  this._stream.resume();
+}
+
+/**
+ * See docs for _read
+ */
+ServerReadableObjectStream.prototype._read = _read;
+/**
+ * See docs for _read
+ */
+ServerBidiObjectStream.prototype._read = _read;
+
+/**
+ * _write implementation for both types of streams that allow writing
+ * @this {ServerWritableObjectStream|ServerBidiObjectStream}
+ * @param {*} chunk The value to write to the stream
+ * @param {string} encoding Ignored
+ * @param {function(Error)} callback Callback to call when finished writing
+ */
+function _write(chunk, encoding, callback) {
+  this._stream.write(this._serialize(chunk), encoding, callback);
+}
+
+/**
+ * See docs for _write
+ */
+ServerWritableObjectStream.prototype._write = _write;
+/**
+ * See docs for _write
+ */
+ServerBidiObjectStream.prototype._write = _write;
+
+/**
+ * Creates a binary stream handler function from a unary handler function
+ * @param {function(Object, function(Error, *))} handler Unary call handler
+ * @param {function(*):Buffer} serialize Serialization function
+ * @param {function(Buffer):*} deserialize Deserialization function
+ * @return {function(stream)} Binary stream handler
+ */
+function makeUnaryHandler(handler, serialize, deserialize) {
+  /**
+   * Handles a stream by reading a single data value, passing it to the handler,
+   * and writing the response back to the stream.
+   * @param {stream} stream Binary data stream
+   */
+  return function handleUnaryCall(stream) {
+    stream.on('data', function handleUnaryData(value) {
+      var call = {request: deserialize(value)};
+      Object.defineProperty(call, 'cancelled', {
+        get: function() { return stream.cancelled;}
+      });
+      handler(call, function sendUnaryData(err, value) {
+        if (err) {
+          stream.emit('error', err);
+        } else {
+          stream.write(serialize(value));
+          stream.end();
+        }
+      });
+    });
+  };
+}
+
+/**
+ * Creates a binary stream handler function from a client stream handler
+ * function
+ * @param {function(Readable, function(Error, *))} handler Client stream call
+ *     handler
+ * @param {function(*):Buffer} serialize Serialization function
+ * @param {function(Buffer):*} deserialize Deserialization function
+ * @return {function(stream)} Binary stream handler
+ */
+function makeClientStreamHandler(handler, serialize, deserialize) {
+  /**
+   * Handles a stream by passing a deserializing stream to the handler and
+   * writing the response back to the stream.
+   * @param {stream} stream Binary data stream
+   */
+  return function handleClientStreamCall(stream) {
+    var object_stream = new ServerReadableObjectStream(stream, deserialize, {});
+    handler(object_stream, function sendClientStreamData(err, value) {
+        if (err) {
+          stream.emit('error', err);
+        } else {
+          stream.write(serialize(value));
+          stream.end();
+        }
+    });
+  };
+}
+
+/**
+ * Creates a binary stream handler function from a server stream handler
+ * function
+ * @param {function(Writable)} handler Server stream call handler
+ * @param {function(*):Buffer} serialize Serialization function
+ * @param {function(Buffer):*} deserialize Deserialization function
+ * @return {function(stream)} Binary stream handler
+ */
+function makeServerStreamHandler(handler, serialize, deserialize) {
+  /**
+   * Handles a stream by attaching it to a serializing stream, and passing it to
+   * the handler.
+   * @param {stream} stream Binary data stream
+   */
+  return function handleServerStreamCall(stream) {
+    stream.on('data', function handleClientData(value) {
+      var object_stream = new ServerWritableObjectStream(stream,
+                                                         serialize,
+                                                         {});
+      object_stream.request = deserialize(value);
+      handler(object_stream);
+    });
+  };
+}
+
+/**
+ * Creates a binary stream handler function from a bidi stream handler function
+ * @param {function(Duplex)} handler Unary call handler
+ * @param {function(*):Buffer} serialize Serialization function
+ * @param {function(Buffer):*} deserialize Deserialization function
+ * @return {function(stream)} Binary stream handler
+ */
+function makeBidiStreamHandler(handler, serialize, deserialize) {
+  /**
+   * Handles a stream by wrapping it in a serializing and deserializing object
+   * stream, and passing it to the handler.
+   * @param {stream} stream Binary data stream
+   */
+  return function handleBidiStreamCall(stream) {
+    var object_stream = new ServerBidiObjectStream(stream,
+                                                   serialize,
+                                                   deserialize,
+                                                   {});
+    handler(object_stream);
+  };
+}
+
+/**
+ * Map with short names for each of the handler maker functions. Used in
+ * makeServerConstructor
+ */
+var handler_makers = {
+  unary: makeUnaryHandler,
+  server_stream: makeServerStreamHandler,
+  client_stream: makeClientStreamHandler,
+  bidi: makeBidiStreamHandler
+};
+
+/**
+ * Creates a constructor for servers with a service defined by the methods
+ * object. The methods object has string keys and values of this form:
+ * {serialize: function, deserialize: function, client_stream: bool,
+ *  server_stream: bool}
+ * @param {Object} methods Method descriptor for each method the server should
+ *     expose
+ * @param {string} prefix The prefex to prepend to each method name
+ * @return {function(Object, Object)} New server constructor
+ */
+function makeServerConstructor(methods, prefix) {
+  /**
+   * Create a server with the given handlers for all of the methods.
+   * @constructor
+   * @param {Object} handlers Map from method names to method handlers.
+   * @param {Object} options Options to pass to the underlying server
+   */
+  function SurfaceServer(handlers, options) {
+    var server = new Server(options);
+    this.inner_server = server;
+    _.each(handlers, function(handler, name) {
+      var method = methods[name];
+      var method_type;
+      if (method.client_stream) {
+        if (method.server_stream) {
+          method_type = 'bidi';
+        } else {
+          method_type = 'client_stream';
+        }
+      } else {
+        if (method.server_stream) {
+          method_type = 'server_stream';
+        } else {
+          method_type = 'unary';
+        }
+      }
+      var binary_handler = handler_makers[method_type](handler,
+                                                       method.serialize,
+                                                       method.deserialize);
+      server.register('' + prefix + name, binary_handler);
+    }, this);
+  }
+
+  /**
+   * Binds the server to the given port, with SSL enabled if secure is specified
+   * @param {string} port The port that the server should bind on, in the format
+   *     "address:port"
+   * @param {boolean=} secure Whether the server should open a secure port
+   * @return {SurfaceServer} this
+   */
+  SurfaceServer.prototype.bind = function(port, secure) {
+    this.inner_server.bind(port, secure);
+    return this;
+  };
+
+  /**
+   * Starts the server listening on any bound ports
+   * @return {SurfaceServer} this
+   */
+  SurfaceServer.prototype.listen = function() {
+    this.inner_server.start();
+    return this;
+  };
+
+  /**
+   * Shuts the server down; tells it to stop listening for new requests and to
+   * kill old requests.
+   */
+  SurfaceServer.prototype.shutdown = function() {
+    this.inner_server.shutdown();
+  };
+
+  return SurfaceServer;
+}
+
+/**
+ * See documentation for makeServerConstructor
+ */
+exports.makeServerConstructor = makeServerConstructor;
diff --git a/src/node/tag.cc b/src/node/tag.cc
new file mode 100644
index 0000000..dc8e523
--- /dev/null
+++ b/src/node/tag.cc
@@ -0,0 +1,101 @@
+/*
+ *
+ * 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 <stdlib.h>
+#include <node.h>
+#include <nan.h>
+#include "tag.h"
+
+namespace grpc {
+namespace node {
+
+using v8::Handle;
+using v8::HandleScope;
+using v8::Persistent;
+using v8::Value;
+
+struct tag {
+  tag(Persistent<Value> *tag, Persistent<Value> *call)
+      : persist_tag(tag), persist_call(call) {}
+
+  ~tag() {
+    persist_tag->Dispose();
+    if (persist_call != NULL) {
+      persist_call->Dispose();
+    }
+  }
+  Persistent<Value> *persist_tag;
+  Persistent<Value> *persist_call;
+};
+
+void *CreateTag(Handle<Value> tag, Handle<Value> call) {
+  NanScope();
+  Persistent<Value> *persist_tag = new Persistent<Value>();
+  NanAssignPersistent(*persist_tag, tag);
+  Persistent<Value> *persist_call;
+  if (call->IsNull() || call->IsUndefined()) {
+    persist_call = NULL;
+  } else {
+    persist_call = new Persistent<Value>();
+    NanAssignPersistent(*persist_call, call);
+  }
+  struct tag *tag_struct = new struct tag(persist_tag, persist_call);
+  return reinterpret_cast<void *>(tag_struct);
+}
+
+Handle<Value> GetTagHandle(void *tag) {
+  NanEscapableScope();
+  struct tag *tag_struct = reinterpret_cast<struct tag *>(tag);
+  Handle<Value> tag_value = NanNew<Value>(*tag_struct->persist_tag);
+  return NanEscapeScope(tag_value);
+}
+
+bool TagHasCall(void *tag) {
+  struct tag *tag_struct = reinterpret_cast<struct tag *>(tag);
+  return tag_struct->persist_call != NULL;
+}
+
+Handle<Value> TagGetCall(void *tag) {
+  NanEscapableScope();
+  struct tag *tag_struct = reinterpret_cast<struct tag *>(tag);
+  if (tag_struct->persist_call == NULL) {
+    return NanEscapeScope(NanNull());
+  }
+  Handle<Value> call_value = NanNew<Value>(*tag_struct->persist_call);
+  return NanEscapeScope(call_value);
+}
+
+void DestroyTag(void *tag) { delete reinterpret_cast<struct tag *>(tag); }
+
+}  // namespace node
+}  // namespace grpc
diff --git a/src/node/tag.h b/src/node/tag.h
new file mode 100644
index 0000000..bdb0925
--- /dev/null
+++ b/src/node/tag.h
@@ -0,0 +1,59 @@
+/*
+ *
+ * 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 NET_GRPC_NODE_TAG_H_
+#define NET_GRPC_NODE_TAG_H_
+
+#include <node.h>
+
+namespace grpc {
+namespace node {
+
+/* Create a void* tag that can be passed to various grpc_call functions from
+   a javascript value and the javascript wrapper for the call. The call can be
+   null. */
+void *CreateTag(v8::Handle<v8::Value> tag, v8::Handle<v8::Value> call);
+/* Return the javascript value stored in the tag */
+v8::Handle<v8::Value> GetTagHandle(void *tag);
+/* Returns true if the call was set (non-null) when the tag was created */
+bool TagHasCall(void *tag);
+/* Returns the javascript wrapper for the call associated with this tag */
+v8::Handle<v8::Value> TagGetCall(void *call);
+/* Destroy the tag and all resources it is holding. It is illegal to call any
+   of these other functions on a tag after it has been destroyed. */
+void DestroyTag(void *tag);
+
+}  // namespace node
+}  // namespace grpc
+
+#endif  // NET_GRPC_NODE_TAG_H_
diff --git a/src/node/test/call_test.js b/src/node/test/call_test.js
new file mode 100644
index 0000000..e6dc966
--- /dev/null
+++ b/src/node/test/call_test.js
@@ -0,0 +1,202 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var assert = require('assert');
+var grpc = require('bindings')('grpc.node');
+
+var channel = new grpc.Channel('localhost:7070');
+
+/**
+ * Helper function to return an absolute deadline given a relative timeout in
+ * seconds.
+ * @param {number} timeout_secs The number of seconds to wait before timing out
+ * @return {Date} A date timeout_secs in the future
+ */
+function getDeadline(timeout_secs) {
+  var deadline = new Date();
+  deadline.setSeconds(deadline.getSeconds() + timeout_secs);
+  return deadline;
+}
+
+describe('call', function() {
+  describe('constructor', function() {
+    it('should reject anything less than 3 arguments', function() {
+      assert.throws(function() {
+        new grpc.Call();
+      }, TypeError);
+      assert.throws(function() {
+        new grpc.Call(channel);
+      }, TypeError);
+      assert.throws(function() {
+        new grpc.Call(channel, 'method');
+      }, TypeError);
+    });
+    it('should succeed with a Channel, a string, and a date or number',
+       function() {
+         assert.doesNotThrow(function() {
+           new grpc.Call(channel, 'method', new Date());
+         });
+         assert.doesNotThrow(function() {
+           new grpc.Call(channel, 'method', 0);
+         });
+       });
+    it('should fail with a closed channel', function() {
+      var local_channel = new grpc.Channel('hostname');
+      local_channel.close();
+      assert.throws(function() {
+        new grpc.Call(channel, 'method');
+      });
+    });
+    it('should fail with other types', function() {
+      assert.throws(function() {
+        new grpc.Call({}, 'method', 0);
+      }, TypeError);
+      assert.throws(function() {
+        new grpc.Call(channel, null, 0);
+      }, TypeError);
+      assert.throws(function() {
+        new grpc.Call(channel, 'method', 'now');
+      }, TypeError);
+    });
+  });
+  describe('addMetadata', function() {
+    it('should succeed with objects containing keys and values', function() {
+      var call = new grpc.Call(channel, 'method', getDeadline(1));
+      assert.doesNotThrow(function() {
+        call.addMetadata();
+      });
+      assert.doesNotThrow(function() {
+        call.addMetadata({'key' : 'key',
+                          'value' : new Buffer('value')});
+      });
+      assert.doesNotThrow(function() {
+        call.addMetadata({'key' : 'key1',
+                          'value' : new Buffer('value1')},
+                         {'key' : 'key2',
+                          'value' : new Buffer('value2')});
+      });
+    });
+    it('should fail with other parameter types', function() {
+      var call = new grpc.Call(channel, 'method', getDeadline(1));
+      assert.throws(function() {
+        call.addMetadata(null);
+      }, TypeError);
+      assert.throws(function() {
+        call.addMetadata('value');
+      }, TypeError);
+      assert.throws(function() {
+        call.addMetadata(5);
+      }, TypeError);
+    });
+    it('should fail if startInvoke was already called', function(done) {
+      var call = new grpc.Call(channel, 'method', getDeadline(1));
+      call.startInvoke(function() {},
+                       function() {},
+                       function() {done();},
+                       0);
+      assert.throws(function() {
+        call.addMetadata({'key' : 'key', 'value' : new Buffer('value') });
+      }, function(err) {
+        return err.code === grpc.callError.ALREADY_INVOKED;
+      });
+      // Cancel to speed up the test
+      call.cancel();
+    });
+  });
+  describe('startInvoke', function() {
+    it('should fail with fewer than 4 arguments', function() {
+      var call = new grpc.Call(channel, 'method', getDeadline(1));
+      assert.throws(function() {
+        call.startInvoke();
+      }, TypeError);
+      assert.throws(function() {
+        call.startInvoke(function() {});
+      }, TypeError);
+      assert.throws(function() {
+        call.startInvoke(function() {},
+                         function() {});
+      }, TypeError);
+      assert.throws(function() {
+        call.startInvoke(function() {},
+                         function() {},
+                         function() {});
+      }, TypeError);
+    });
+    it('should work with 3 args and an int', function(done) {
+      assert.doesNotThrow(function() {
+        var call = new grpc.Call(channel, 'method', getDeadline(1));
+        call.startInvoke(function() {},
+                         function() {},
+                         function() {done();},
+                         0);
+        // Cancel to speed up the test
+        call.cancel();
+      });
+    });
+    it('should reject incorrectly typed arguments', function() {
+      var call = new grpc.Call(channel, 'method', getDeadline(1));
+      assert.throws(function() {
+        call.startInvoke(0, 0, 0, 0);
+      }, TypeError);
+      assert.throws(function() {
+        call.startInvoke(function() {},
+                         function() {},
+                         function() {}, 'test');
+      });
+    });
+  });
+  describe('serverAccept', function() {
+    it('should fail with fewer than 1 argument1', function() {
+      var call = new grpc.Call(channel, 'method', getDeadline(1));
+      assert.throws(function() {
+        call.serverAccept();
+      }, TypeError);
+    });
+    it('should return an error when called on a client Call', function() {
+      var call = new grpc.Call(channel, 'method', getDeadline(1));
+      assert.throws(function() {
+        call.serverAccept(function() {});
+      }, function(err) {
+        return err.code === grpc.callError.NOT_ON_CLIENT;
+      });
+    });
+  });
+  describe('cancel', function() {
+    it('should succeed', function() {
+      var call = new grpc.Call(channel, 'method', getDeadline(1));
+      assert.doesNotThrow(function() {
+        call.cancel();
+      });
+    });
+  });
+});
diff --git a/src/node/test/channel_test.js b/src/node/test/channel_test.js
new file mode 100644
index 0000000..4d8cfc4
--- /dev/null
+++ b/src/node/test/channel_test.js
@@ -0,0 +1,88 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var assert = require('assert');
+var grpc = require('bindings')('grpc.node');
+
+describe('channel', function() {
+  describe('constructor', function() {
+    it('should require a string for the first argument', function() {
+      assert.doesNotThrow(function() {
+        new grpc.Channel('hostname');
+      });
+      assert.throws(function() {
+        new grpc.Channel();
+      }, TypeError);
+      assert.throws(function() {
+        new grpc.Channel(5);
+      });
+    });
+    it('should accept an object for the second parameter', function() {
+      assert.doesNotThrow(function() {
+        new grpc.Channel('hostname', {});
+      });
+      assert.throws(function() {
+        new grpc.Channel('hostname', 5);
+      });
+    });
+    it('should only accept objects with string or int values', function() {
+      assert.doesNotThrow(function() {
+        new grpc.Channel('hostname', {'key' : 'value'});
+      });
+      assert.doesNotThrow(function() {
+        new grpc.Channel('hostname', {'key' : 5});
+      });
+      assert.throws(function() {
+        new grpc.Channel('hostname', {'key' : null});
+      });
+      assert.throws(function() {
+        new grpc.Channel('hostname', {'key' : new Date()});
+      });
+    });
+  });
+  describe('close', function() {
+    it('should succeed silently', function() {
+      var channel = new grpc.Channel('hostname', {});
+      assert.doesNotThrow(function() {
+        channel.close();
+      });
+    });
+    it('should be idempotent', function() {
+      var channel = new grpc.Channel('hostname', {});
+      assert.doesNotThrow(function() {
+        channel.close();
+        channel.close();
+      });
+    });
+  });
+});
diff --git a/src/node/test/client_server_test.js b/src/node/test/client_server_test.js
new file mode 100644
index 0000000..534a5c4
--- /dev/null
+++ b/src/node/test/client_server_test.js
@@ -0,0 +1,183 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var assert = require('assert');
+var fs = require('fs');
+var path = require('path');
+var grpc = require('bindings')('grpc.node');
+var Server = require('../server');
+var client = require('../client');
+var port_picker = require('../port_picker');
+var common = require('../common');
+var _ = require('highland');
+
+var ca_path = path.join(__dirname, 'data/ca.pem');
+
+var key_path = path.join(__dirname, 'data/server1.key');
+
+var pem_path = path.join(__dirname, 'data/server1.pem');
+
+/**
+ * Helper function to return an absolute deadline given a relative timeout in
+ * seconds.
+ * @param {number} timeout_secs The number of seconds to wait before timing out
+ * @return {Date} A date timeout_secs in the future
+ */
+function getDeadline(timeout_secs) {
+  var deadline = new Date();
+  deadline.setSeconds(deadline.getSeconds() + timeout_secs);
+  return deadline;
+}
+
+/**
+ * Responds to every request with the same data as a response
+ * @param {Stream} stream
+ */
+function echoHandler(stream) {
+  stream.pipe(stream);
+}
+
+/**
+ * Responds to every request with an error status
+ * @param {Stream} stream
+ */
+function errorHandler(stream) {
+  throw {
+    'code' : grpc.status.UNIMPLEMENTED,
+    'details' : 'error details'
+  };
+}
+
+describe('echo client', function() {
+  it('should receive echo responses', function(done) {
+    port_picker.nextAvailablePort(function(port) {
+      var server = new Server();
+      server.bind(port);
+      server.register('echo', echoHandler);
+      server.start();
+
+      var messages = ['echo1', 'echo2', 'echo3', 'echo4'];
+      var channel = new grpc.Channel(port);
+      var stream = client.makeRequest(
+          channel,
+          'echo');
+      _(messages).map(function(val) {
+        return new Buffer(val);
+      }).pipe(stream);
+      var index = 0;
+      stream.on('data', function(chunk) {
+        assert.equal(messages[index], chunk.toString());
+        index += 1;
+      });
+      stream.on('end', function() {
+        server.shutdown();
+        done();
+      });
+    });
+  });
+  it('should get an error status that the server throws', function(done) {
+    port_picker.nextAvailablePort(function(port) {
+      var server = new Server();
+      server.bind(port);
+      server.register('error', errorHandler);
+      server.start();
+
+      var channel = new grpc.Channel(port);
+      var stream = client.makeRequest(
+          channel,
+          'error',
+          null,
+          getDeadline(1));
+
+      stream.on('data', function() {});
+      stream.write(new Buffer('test'));
+      stream.end();
+      stream.on('status', function(status) {
+        assert.equal(status.code, grpc.status.UNIMPLEMENTED);
+        assert.equal(status.details, 'error details');
+        server.shutdown();
+        done();
+      });
+
+    });
+  });
+});
+/* TODO(mlumish): explore options for reducing duplication between this test
+ * and the insecure echo client test */
+describe('secure echo client', function() {
+  it('should recieve echo responses', function(done) {
+    port_picker.nextAvailablePort(function(port) {
+      fs.readFile(ca_path, function(err, ca_data) {
+        assert.ifError(err);
+        fs.readFile(key_path, function(err, key_data) {
+          assert.ifError(err);
+          fs.readFile(pem_path, function(err, pem_data) {
+            assert.ifError(err);
+            var creds = grpc.Credentials.createSsl(ca_data);
+            var server_creds = grpc.ServerCredentials.createSsl(null,
+                                                                key_data,
+                                                                pem_data);
+
+            var server = new Server({'credentials' : server_creds});
+            server.bind(port, true);
+            server.register('echo', echoHandler);
+            server.start();
+
+            var messages = ['echo1', 'echo2', 'echo3', 'echo4'];
+            var channel = new grpc.Channel(port, {
+              'grpc.ssl_target_name_override' : 'foo.test.google.com',
+              'credentials' : creds
+            });
+            var stream = client.makeRequest(
+                channel,
+                'echo');
+
+            _(messages).map(function(val) {
+              return new Buffer(val);
+            }).pipe(stream);
+            var index = 0;
+            stream.on('data', function(chunk) {
+              assert.equal(messages[index], chunk.toString());
+              index += 1;
+            });
+            stream.on('end', function() {
+              server.shutdown();
+              done();
+            });
+          });
+
+        });
+      });
+    });
+  });
+});
diff --git a/src/node/test/constant_test.js b/src/node/test/constant_test.js
new file mode 100644
index 0000000..f65eea3
--- /dev/null
+++ b/src/node/test/constant_test.js
@@ -0,0 +1,130 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var assert = require('assert');
+var grpc = require('bindings')('grpc.node');
+
+/**
+ * List of all status names
+ * @const
+ * @type {Array.<string>}
+ */
+var statusNames = [
+  'OK',
+  'CANCELLED',
+  'UNKNOWN',
+  'INVALID_ARGUMENT',
+  'DEADLINE_EXCEEDED',
+  'NOT_FOUND',
+  'ALREADY_EXISTS',
+  'PERMISSION_DENIED',
+  'UNAUTHENTICATED',
+  'RESOURCE_EXHAUSTED',
+  'FAILED_PRECONDITION',
+  'ABORTED',
+  'OUT_OF_RANGE',
+  'UNIMPLEMENTED',
+  'INTERNAL',
+  'UNAVAILABLE',
+  'DATA_LOSS'
+];
+
+/**
+ * List of all call error names
+ * @const
+ * @type {Array.<string>}
+ */
+var callErrorNames = [
+  'OK',
+  'ERROR',
+  'NOT_ON_SERVER',
+  'NOT_ON_CLIENT',
+  'ALREADY_INVOKED',
+  'NOT_INVOKED',
+  'ALREADY_FINISHED',
+  'TOO_MANY_OPERATIONS',
+  'INVALID_FLAGS'
+];
+
+/**
+ * List of all op error names
+ * @const
+ * @type {Array.<string>}
+ */
+var opErrorNames = [
+  'OK',
+  'ERROR'
+];
+
+/**
+ * List of all completion type names
+ * @const
+ * @type {Array.<string>}
+ */
+var completionTypeNames = [
+  'QUEUE_SHUTDOWN',
+  'READ',
+  'INVOKE_ACCEPTED',
+  'WRITE_ACCEPTED',
+  'FINISH_ACCEPTED',
+  'CLIENT_METADATA_READ',
+  'FINISHED',
+  'SERVER_RPC_NEW'
+];
+
+describe('constants', function() {
+  it('should have all of the status constants', function() {
+    for (var i = 0; i < statusNames.length; i++) {
+      assert(grpc.status.hasOwnProperty(statusNames[i]),
+             'status missing: ' + statusNames[i]);
+    }
+  });
+  it('should have all of the call errors', function() {
+    for (var i = 0; i < callErrorNames.length; i++) {
+      assert(grpc.callError.hasOwnProperty(callErrorNames[i]),
+             'call error missing: ' + callErrorNames[i]);
+    }
+  });
+  it('should have all of the op errors', function() {
+    for (var i = 0; i < opErrorNames.length; i++) {
+      assert(grpc.opError.hasOwnProperty(opErrorNames[i]),
+             'op error missing: ' + opErrorNames[i]);
+    }
+  });
+  it('should have all of the completion types', function() {
+    for (var i = 0; i < completionTypeNames.length; i++) {
+      assert(grpc.completionType.hasOwnProperty(completionTypeNames[i]),
+             'completion type missing: ' + completionTypeNames[i]);
+    }
+  });
+});
diff --git a/src/node/test/data/README b/src/node/test/data/README
new file mode 100644
index 0000000..888d95b
--- /dev/null
+++ b/src/node/test/data/README
@@ -0,0 +1 @@
+CONFIRMEDTESTKEY
diff --git a/src/node/test/data/ca.pem b/src/node/test/data/ca.pem
new file mode 100644
index 0000000..6c8511a
--- /dev/null
+++ b/src/node/test/data/ca.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla
+Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0
+YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT
+BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7
++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu
+g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd
+Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau
+sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m
+oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG
+Dfcog5wrJytaQ6UA0wE=
+-----END CERTIFICATE-----
diff --git a/src/node/test/data/server1.key b/src/node/test/data/server1.key
new file mode 100644
index 0000000..143a5b8
--- /dev/null
+++ b/src/node/test/data/server1.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD
+M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf
+3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY
+AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm
+V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY
+tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p
+dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q
+K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR
+81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff
+DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd
+aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2
+ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3
+XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe
+F98XJ7tIFfJq
+-----END PRIVATE KEY-----
diff --git a/src/node/test/data/server1.pem b/src/node/test/data/server1.pem
new file mode 100644
index 0000000..8e582e5
--- /dev/null
+++ b/src/node/test/data/server1.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICmzCCAgSgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJBVTET
+MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzIyMDYwMDU3WhcNMjQwNzE5
+MDYwMDU3WjBkMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
+BAcTB0NoaWNhZ28xFDASBgNVBAoTC0dvb2dsZSBJbmMuMRowGAYDVQQDFBEqLnRl
+c3QuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4cMVJygs
+JUmlgMMzgdi0h1XoCR7+ww1pop04OMMyy7H/i0PJ2W6Y35+b4CM8QrkYeEafUGDO
+RYX6yV/cHGGsD/x02ye6ey1UDtkGAD/mpDEx8YCrjAc1Vfvt8Fk6Cn1WVIxV/J30
+3xjBsFgByQ55RBp1OLZfVLo6AleBDSbcxaECAwEAAaNrMGkwCQYDVR0TBAIwADAL
+BgNVHQ8EBAMCBeAwTwYDVR0RBEgwRoIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6
+b29pLnRlc3QuZ29vZ2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQMwDQYJ
+KoZIhvcNAQEFBQADgYEAM2Ii0LgTGbJ1j4oqX9bxVcxm+/R5Yf8oi0aZqTJlnLYS
+wXcBykxTx181s7WyfJ49WwrYXo78zTDAnf1ma0fPq3e4mpspvyndLh1a+OarHa1e
+aT0DIIYk7qeEa1YcVljx2KyLd0r1BBAfrwyGaEPVeJQVYWaOJRU2we/KD4ojf9s=
+-----END CERTIFICATE-----
diff --git a/src/node/test/end_to_end_test.js b/src/node/test/end_to_end_test.js
new file mode 100644
index 0000000..40bb5f3
--- /dev/null
+++ b/src/node/test/end_to_end_test.js
@@ -0,0 +1,201 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var assert = require('assert');
+var grpc = require('bindings')('grpc.node');
+var port_picker = require('../port_picker');
+
+/**
+ * This is used for testing functions with multiple asynchronous calls that
+ * can happen in different orders. This should be passed the number of async
+ * function invocations that can occur last, and each of those should call this
+ * function's return value
+ * @param {function()} done The function that should be called when a test is
+ *     complete.
+ * @param {number} count The number of calls to the resulting function if the
+ *     test passes.
+ * @return {function()} The function that should be called at the end of each
+ *     sequence of asynchronous functions.
+ */
+function multiDone(done, count) {
+  return function() {
+    count -= 1;
+    if (count <= 0) {
+      done();
+    }
+  };
+}
+
+describe('end-to-end', function() {
+  it('should start and end a request without error', function(complete) {
+    port_picker.nextAvailablePort(function(port) {
+      var server = new grpc.Server();
+      var done = multiDone(function() {
+        complete();
+        server.shutdown();
+      }, 2);
+      server.addHttp2Port(port);
+      var channel = new grpc.Channel(port);
+      var deadline = new Date();
+      deadline.setSeconds(deadline.getSeconds() + 3);
+      var status_text = 'xyz';
+      var call = new grpc.Call(channel,
+                               'dummy_method',
+                               deadline);
+      call.startInvoke(function(event) {
+        assert.strictEqual(event.type,
+                           grpc.completionType.INVOKE_ACCEPTED);
+
+        call.writesDone(function(event) {
+          assert.strictEqual(event.type,
+                             grpc.completionType.FINISH_ACCEPTED);
+          assert.strictEqual(event.data, grpc.opError.OK);
+        });
+      },function(event) {
+        assert.strictEqual(event.type,
+                           grpc.completionType.CLIENT_METADATA_READ);
+      },function(event) {
+        assert.strictEqual(event.type, grpc.completionType.FINISHED);
+        var status = event.data;
+        assert.strictEqual(status.code, grpc.status.OK);
+        assert.strictEqual(status.details, status_text);
+        done();
+      }, 0);
+
+      server.start();
+      server.requestCall(function(event) {
+        assert.strictEqual(event.type, grpc.completionType.SERVER_RPC_NEW);
+        var server_call = event.call;
+        assert.notEqual(server_call, null);
+        server_call.serverAccept(function(event) {
+          assert.strictEqual(event.type, grpc.completionType.FINISHED);
+        }, 0);
+        server_call.serverEndInitialMetadata(0);
+        server_call.startWriteStatus(
+            grpc.status.OK,
+            status_text,
+            function(event) {
+              assert.strictEqual(event.type,
+                                 grpc.completionType.FINISH_ACCEPTED);
+              assert.strictEqual(event.data, grpc.opError.OK);
+              done();
+            });
+      });
+    });
+  });
+
+  it('should send and receive data without error', function(complete) {
+    port_picker.nextAvailablePort(function(port) {
+      var req_text = 'client_request';
+      var reply_text = 'server_response';
+      var server = new grpc.Server();
+      var done = multiDone(function() {
+        complete();
+        server.shutdown();
+      }, 6);
+      server.addHttp2Port(port);
+      var channel = new grpc.Channel(port);
+      var deadline = new Date();
+      deadline.setSeconds(deadline.getSeconds() + 3);
+      var status_text = 'success';
+      var call = new grpc.Call(channel,
+                               'dummy_method',
+                               deadline);
+      call.startInvoke(function(event) {
+        assert.strictEqual(event.type,
+                           grpc.completionType.INVOKE_ACCEPTED);
+        call.startWrite(
+            new Buffer(req_text),
+            function(event) {
+              assert.strictEqual(event.type,
+                                 grpc.completionType.WRITE_ACCEPTED);
+              assert.strictEqual(event.data, grpc.opError.OK);
+              call.writesDone(function(event) {
+                assert.strictEqual(event.type,
+                                   grpc.completionType.FINISH_ACCEPTED);
+                assert.strictEqual(event.data, grpc.opError.OK);
+                done();
+              });
+            }, 0);
+        call.startRead(function(event) {
+          assert.strictEqual(event.type, grpc.completionType.READ);
+          assert.strictEqual(event.data.toString(), reply_text);
+          done();
+        });
+      },function(event) {
+        assert.strictEqual(event.type,
+                           grpc.completionType.CLIENT_METADATA_READ);
+        done();
+      },function(event) {
+        assert.strictEqual(event.type, grpc.completionType.FINISHED);
+        var status = event.data;
+        assert.strictEqual(status.code, grpc.status.OK);
+        assert.strictEqual(status.details, status_text);
+        done();
+      }, 0);
+
+      server.start();
+      server.requestCall(function(event) {
+        assert.strictEqual(event.type, grpc.completionType.SERVER_RPC_NEW);
+        var server_call = event.call;
+        assert.notEqual(server_call, null);
+        server_call.serverAccept(function(event) {
+          assert.strictEqual(event.type, grpc.completionType.FINISHED);
+          done();
+        });
+        server_call.serverEndInitialMetadata(0);
+        server_call.startRead(function(event) {
+          assert.strictEqual(event.type, grpc.completionType.READ);
+          assert.strictEqual(event.data.toString(), req_text);
+          server_call.startWrite(
+              new Buffer(reply_text),
+              function(event) {
+                assert.strictEqual(event.type,
+                                   grpc.completionType.WRITE_ACCEPTED);
+                assert.strictEqual(event.data,
+                                   grpc.opError.OK);
+                server_call.startWriteStatus(
+                    grpc.status.OK,
+                    status_text,
+                    function(event) {
+                      assert.strictEqual(event.type,
+                                         grpc.completionType.FINISH_ACCEPTED);
+                      assert.strictEqual(event.data, grpc.opError.OK);
+                      done();
+                    });
+              }, 0);
+        });
+      });
+    });
+  });
+});
diff --git a/src/node/test/math_client_test.js b/src/node/test/math_client_test.js
new file mode 100644
index 0000000..5b34a22
--- /dev/null
+++ b/src/node/test/math_client_test.js
@@ -0,0 +1,191 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var assert = require('assert');
+var ProtoBuf = require('protobufjs');
+var port_picker = require('../port_picker');
+
+var builder = ProtoBuf.loadProtoFile(__dirname + '/../examples/math.proto');
+var math = builder.build('math');
+
+var client = require('../surface_client.js');
+var makeConstructor = client.makeClientConstructor;
+/**
+ * Get a function that deserializes a specific type of protobuf.
+ * @param {function()} cls The constructor of the message type to deserialize
+ * @return {function(Buffer):cls} The deserialization function
+ */
+function deserializeCls(cls) {
+  /**
+   * Deserialize a buffer to a message object
+   * @param {Buffer} arg_buf The buffer to deserialize
+   * @return {cls} The resulting object
+   */
+  return function deserialize(arg_buf) {
+    return cls.decode(arg_buf);
+  };
+}
+
+/**
+ * Get a function that serializes objects to a buffer by protobuf class.
+ * @param {function()} Cls The constructor of the message type to serialize
+ * @return {function(Cls):Buffer} The serialization function
+ */
+function serializeCls(Cls) {
+  /**
+   * Serialize an object to a Buffer
+   * @param {Object} arg The object to serialize
+   * @return {Buffer} The serialized object
+   */
+  return function serialize(arg) {
+    return new Buffer(new Cls(arg).encode().toBuffer());
+  };
+}
+
+/* This function call creates a client constructor for clients that expose the
+ * four specified methods. This specifies how to serialize messages that the
+ * client sends and deserialize messages that the server sends, and whether the
+ * client or the server will send a stream of messages, for each method. This
+ * also specifies a prefix that will be added to method names when sending them
+ * on the wire. This function call and all of the preceding code in this file
+ * are intended to approximate what the generated code will look like for the
+ * math client */
+var MathClient = makeConstructor({
+  Div: {
+    serialize: serializeCls(math.DivArgs),
+    deserialize: deserializeCls(math.DivReply),
+    client_stream: false,
+    server_stream: false
+  },
+  Fib: {
+    serialize: serializeCls(math.FibArgs),
+    deserialize: deserializeCls(math.Num),
+    client_stream: false,
+    server_stream: true
+  },
+  Sum: {
+    serialize: serializeCls(math.Num),
+    deserialize: deserializeCls(math.Num),
+    client_stream: true,
+    server_stream: false
+  },
+  DivMany: {
+    serialize: serializeCls(math.DivArgs),
+    deserialize: deserializeCls(math.DivReply),
+    client_stream: true,
+    server_stream: true
+  }
+}, '/Math/');
+
+/**
+ * Channel to use to make requests to a running server.
+ */
+var math_client;
+
+/**
+ * Server to test against
+ */
+var server = require('../examples/math_server.js');
+
+
+describe('Math client', function() {
+  before(function(done) {
+    port_picker.nextAvailablePort(function(port) {
+      server.bind(port).listen();
+      math_client = new MathClient(port);
+      done();
+    });
+  });
+  after(function() {
+    server.shutdown();
+  });
+  it('should handle a single request', function(done) {
+    var arg = {dividend: 7, divisor: 4};
+    var call = math_client.Div(arg, function handleDivResult(err, value) {
+      assert.ifError(err);
+      assert.equal(value.quotient, 1);
+      assert.equal(value.remainder, 3);
+    });
+    call.on('status', function checkStatus(status) {
+      assert.strictEqual(status.code, client.status.OK);
+      done();
+    });
+  });
+  it('should handle a server streaming request', function(done) {
+    var call = math_client.Fib({limit: 7});
+    var expected_results = [1, 1, 2, 3, 5, 8, 13];
+    var next_expected = 0;
+    call.on('data', function checkResponse(value) {
+      assert.equal(value.num, expected_results[next_expected]);
+      next_expected += 1;
+    });
+    call.on('status', function checkStatus(status) {
+      assert.strictEqual(status.code, client.status.OK);
+      done();
+    });
+  });
+  it('should handle a client streaming request', function(done) {
+    var call = math_client.Sum(function handleSumResult(err, value) {
+      assert.ifError(err);
+      assert.equal(value.num, 21);
+    });
+    for (var i = 0; i < 7; i++) {
+      call.write({'num': i});
+    }
+    call.end();
+    call.on('status', function checkStatus(status) {
+      assert.strictEqual(status.code, client.status.OK);
+      done();
+    });
+  });
+  it('should handle a bidirectional streaming request', function(done) {
+    function checkResponse(index, value) {
+      assert.equal(value.quotient, index);
+      assert.equal(value.remainder, 1);
+    }
+    var call = math_client.DivMany();
+    var response_index = 0;
+    call.on('data', function(value) {
+      checkResponse(response_index, value);
+      response_index += 1;
+    });
+    for (var i = 0; i < 7; i++) {
+      call.write({dividend: 2 * i + 1, divisor: 2});
+    }
+    call.end();
+    call.on('status', function checkStatus(status) {
+      assert.strictEqual(status.code, client.status.OK);
+      done();
+    });
+  });
+});
diff --git a/src/node/test/server_test.js b/src/node/test/server_test.js
new file mode 100644
index 0000000..79f7b32
--- /dev/null
+++ b/src/node/test/server_test.js
@@ -0,0 +1,121 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+var assert = require('assert');
+var grpc = require('bindings')('grpc.node');
+var Server = require('../server');
+var port_picker = require('../port_picker');
+
+/**
+ * This is used for testing functions with multiple asynchronous calls that
+ * can happen in different orders. This should be passed the number of async
+ * function invocations that can occur last, and each of those should call this
+ * function's return value
+ * @param {function()} done The function that should be called when a test is
+ *     complete.
+ * @param {number} count The number of calls to the resulting function if the
+ *     test passes.
+ * @return {function()} The function that should be called at the end of each
+ *     sequence of asynchronous functions.
+ */
+function multiDone(done, count) {
+  return function() {
+    count -= 1;
+    if (count <= 0) {
+      done();
+    }
+  };
+}
+
+/**
+ * Responds to every request with the same data as a response
+ * @param {Stream} stream
+ */
+function echoHandler(stream) {
+  stream.pipe(stream);
+}
+
+describe('echo server', function() {
+  it('should echo inputs as responses', function(done) {
+    done = multiDone(done, 4);
+    port_picker.nextAvailablePort(function(port) {
+      var server = new Server();
+      server.bind(port);
+      server.register('echo', echoHandler);
+      server.start();
+
+      var req_text = 'echo test string';
+      var status_text = 'OK';
+
+      var channel = new grpc.Channel(port);
+      var deadline = new Date();
+      deadline.setSeconds(deadline.getSeconds() + 3);
+      var call = new grpc.Call(channel,
+                               'echo',
+                               deadline);
+      call.startInvoke(function(event) {
+        assert.strictEqual(event.type,
+                           grpc.completionType.INVOKE_ACCEPTED);
+        call.startWrite(
+            new Buffer(req_text),
+            function(event) {
+              assert.strictEqual(event.type,
+                                 grpc.completionType.WRITE_ACCEPTED);
+              assert.strictEqual(event.data, grpc.opError.OK);
+              call.writesDone(function(event) {
+                assert.strictEqual(event.type,
+                                   grpc.completionType.FINISH_ACCEPTED);
+                assert.strictEqual(event.data, grpc.opError.OK);
+                done();
+              });
+            }, 0);
+        call.startRead(function(event) {
+          assert.strictEqual(event.type, grpc.completionType.READ);
+          assert.strictEqual(event.data.toString(), req_text);
+          done();
+        });
+      },function(event) {
+        assert.strictEqual(event.type,
+                           grpc.completionType.CLIENT_METADATA_READ);
+        done();
+      },function(event) {
+        assert.strictEqual(event.type, grpc.completionType.FINISHED);
+        var status = event.data;
+        assert.strictEqual(status.code, grpc.status.OK);
+        assert.strictEqual(status.details, status_text);
+        server.shutdown();
+        done();
+      }, 0);
+    });
+  });
+});
diff --git a/src/node/timeval.cc b/src/node/timeval.cc
new file mode 100644
index 0000000..687e335
--- /dev/null
+++ b/src/node/timeval.cc
@@ -0,0 +1,66 @@
+/*
+ *
+ * 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 <limits>
+
+#include "grpc/grpc.h"
+#include "grpc/support/time.h"
+#include "timeval.h"
+
+namespace grpc {
+namespace node {
+
+gpr_timespec MillisecondsToTimespec(double millis) {
+  if (millis == std::numeric_limits<double>::infinity()) {
+    return gpr_inf_future;
+  } else if (millis == -std::numeric_limits<double>::infinity()) {
+    return gpr_inf_past;
+  } else {
+    return gpr_time_from_micros(static_cast<int64_t>(millis * 1000));
+  }
+}
+
+double TimespecToMilliseconds(gpr_timespec timespec) {
+  if (gpr_time_cmp(timespec, gpr_inf_future) == 0) {
+    return std::numeric_limits<double>::infinity();
+  } 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);
+  }
+}
+
+}  // namespace node
+}  // namespace grpc
diff --git a/src/node/timeval.h b/src/node/timeval.h
new file mode 100644
index 0000000..1fb0f2c
--- /dev/null
+++ b/src/node/timeval.h
@@ -0,0 +1,48 @@
+/*
+ *
+ * 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 NET_GRPC_NODE_TIMEVAL_H_
+#define NET_GRPC_NODE_TIMEVAL_H_
+
+#include "grpc/support/time.h"
+
+namespace grpc {
+namespace node {
+
+double TimespecToMilliseconds(gpr_timespec time);
+gpr_timespec MillisecondsToTimespec(double millis);
+
+}  // namespace node
+}  // namespace grpc
+
+#endif  // NET_GRPC_NODE_TIMEVAL_H_
diff --git a/src/php/ext/grpc/config.m4 b/src/php/ext/grpc/config.m4
index 40e4dd3..d7d13f4 100755
--- a/src/php/ext/grpc/config.m4
+++ b/src/php/ext/grpc/config.m4
@@ -32,6 +32,9 @@
   GRPC_SHARED_LIBADD="-lpthread $GRPC_SHARED_LIBADD"
   PHP_ADD_LIBRARY(pthread)
 
+  PHP_ADD_LIBRARY(dl,,GRPC_SHARED_LIBADD)
+  PHP_ADD_LIBRARY(dl)
+
   PHP_ADD_LIBRARY(rt,,GRPC_SHARED_LIBADD)
   PHP_ADD_LIBRARY(rt)
 
diff --git a/templates/Makefile.template b/templates/Makefile.template
index d0ebb10..256380c 100644
--- a/templates/Makefile.template
+++ b/templates/Makefile.template
@@ -66,6 +66,15 @@
 LDFLAGS_msan = -fsanitize=memory
 DEFINES_msan = NDEBUG
 
+VALID_CONFIG_gcov = 1
+CC_gcov = gcc
+CXX_gcov = g++
+LD_gcov = gcc
+LDXX_gcov = g++
+CPPFLAGS_gcov = -O0 -fprofile-arcs -ftest-coverage
+LDFLAGS_gcov = -fprofile-arcs -ftest-coverage
+DEFINES_gcov = NDEBUG
+
 # General settings.
 # You may want to change these depending on your system.
 
@@ -329,7 +338,7 @@
 
 privatelibs_c: dep_c\
 % for lib in libs:
-% if lib.build == 'private':
+% if lib.build == 'private' and not lib.get('c++', False):
  libs/$(CONFIG)/lib${lib.name}.a\
 % endif
 % endfor
@@ -337,7 +346,7 @@
 
 privatelibs_cxx: dep_cxx\
 % for lib in libs:
-% if lib.build == 'private':
+% if lib.build == 'private' and lib.get('c++', False):
  libs/$(CONFIG)/lib${lib.name}.a\
 % endif
 % endfor
diff --git a/templates/vsprojects/vs2013/build_and_run_tests.bat.template b/templates/vsprojects/vs2013/build_and_run_tests.bat.template
index 8679bee..4a15e01 100644
--- a/templates/vsprojects/vs2013/build_and_run_tests.bat.template
+++ b/templates/vsprojects/vs2013/build_and_run_tests.bat.template
@@ -13,7 +13,7 @@
 
 @rem Build the library dependencies first
 MSBuild.exe gpr.vcxproj /p:Configuration=Debug
-MSBuild.exe grpc_test_util.vcxproj /p:Configuration=Debug
+MSBuild.exe gpr_test_util.vcxproj /p:Configuration=Debug
 
 mkdir ${test_bin_dir}
 
diff --git a/test/build/event2.c b/test/build/event2.c
deleted file mode 100644
index f632b1e..0000000
--- a/test/build/event2.c
+++ /dev/null
@@ -1,8 +0,0 @@
-/* This is only a compilation test, to see if we have libevent installed. */
-
-#include <event2/event.h>
-
-int main() {
-  event_base_new();
-  return 0;
-}
diff --git a/test/core/iomgr/tcp_posix_test.c b/test/core/iomgr/tcp_posix_test.c
index 7fd2567..6af3ded 100644
--- a/test/core/iomgr/tcp_posix_test.c
+++ b/test/core/iomgr/tcp_posix_test.c
@@ -36,7 +36,6 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <string.h>
-#include <signal.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <unistd.h>
@@ -491,8 +490,6 @@
 int main(int argc, char **argv) {
   grpc_test_init(argc, argv);
   grpc_iomgr_init();
-  /* disable SIGPIPE */
-  signal(SIGPIPE, SIG_IGN);
   run_tests();
   grpc_endpoint_tests(configs[0]);
   grpc_iomgr_shutdown();
diff --git a/test/core/transport/chttp2_transport_end2end_test.c b/test/core/transport/chttp2_transport_end2end_test.c
index 30d2a17..8b0f9aa 100644
--- a/test/core/transport/chttp2_transport_end2end_test.c
+++ b/test/core/transport/chttp2_transport_end2end_test.c
@@ -107,9 +107,6 @@
 int main(int argc, char **argv) {
   size_t i;
 
-  /* disable SIGPIPE */
-  signal(SIGPIPE, SIG_IGN);
-
   grpc_test_init(argc, argv);
   grpc_iomgr_init();
 
diff --git a/test/core/util/test_config.c b/test/core/util/test_config.c
index fc5de9b..44ab35f 100644
--- a/test/core/util/test_config.c
+++ b/test/core/util/test_config.c
@@ -33,11 +33,24 @@
 
 #include "test/core/util/test_config.h"
 
+#include <grpc/support/port_platform.h>
 #include <stdlib.h>
+#include <signal.h>
+
+#if GPR_GETPID_IN_UNISTD_H
 #include <unistd.h>
+static int seed() { return getpid(); }
+#endif
+
+#if GPR_GETPID_IN_PROCESS_H
+#include <process.h>
+static int seed(void) { return _getpid(); }
+#endif
 
 void grpc_test_init(int argc, char **argv) {
+  /* disable SIGPIPE */
+  signal(SIGPIPE, SIG_IGN);
   /* seed rng with pid, so we don't end up with the same random numbers as a
      concurrently running test binary */
-  srand(getpid());
+  srand(seed());
 }
diff --git a/test/cpp/end2end/end2end_test.cc b/test/cpp/end2end/end2end_test.cc
index e01a6ef..3a1da68 100644
--- a/test/cpp/end2end/end2end_test.cc
+++ b/test/cpp/end2end/end2end_test.cc
@@ -34,6 +34,7 @@
 #include <chrono>
 #include <thread>
 
+#include "test/core/util/test_config.h"
 #include "test/cpp/util/echo_duplicate.pb.h"
 #include "test/cpp/util/echo.pb.h"
 #include "src/cpp/util/time.h"
@@ -435,6 +436,7 @@
 }  // namespace grpc
 
 int main(int argc, char** argv) {
+  grpc_test_init(argc, argv);
   grpc_init();
   ::testing::InitGoogleTest(&argc, argv);
   int result = RUN_ALL_TESTS();
diff --git a/third_party/libevent b/third_party/libevent
deleted file mode 160000
index f7d92c6..0000000
--- a/third_party/libevent
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit f7d92c63928a1460f3d99b9bc418bd3b686a0dca
diff --git a/tools/dockerfile/grpc_base/Dockerfile b/tools/dockerfile/grpc_base/Dockerfile
index 76e585a..45be172 100644
--- a/tools/dockerfile/grpc_base/Dockerfile
+++ b/tools/dockerfile/grpc_base/Dockerfile
@@ -13,7 +13,6 @@
   libc6 \
   libc6-dbg \
   libc6-dev \
-  libevent-dev \
   libtool \
   make \
   strace \
diff --git a/tools/gce_setup/grpc_docker.sh b/tools/gce_setup/grpc_docker.sh
index d2614fb..c5d02d7 100755
--- a/tools/gce_setup/grpc_docker.sh
+++ b/tools/gce_setup/grpc_docker.sh
@@ -18,6 +18,20 @@
 #  * on startup, some of the docker images will be regenerated automatically
 # - used grpc_update_image to update images via that instance
 
+
+# Creates the ssh key file expect by 'gcloud compute ssh' if it does not exist.
+#
+# Allows gcloud ssh commands to run on freshly started docker instances.
+_grpc_ensure_gcloud_ssh() {
+  local default_key_file="$HOME/.ssh/google_compute_engine"
+  [ -f $default_key_file ] || {
+    ssh-keygen -f $default_key_file -N '' > /dev/null || {
+      echo "could not precreate $default_key_file" 1>&2
+      return 1
+    }
+  }
+}
+
 # Pushes a dockerfile dir to cloud storage.
 #
 # dockerfile is expected to the parent directory to a nunber of directoies each
@@ -50,6 +64,7 @@
 # Adds the user to docker group on a GCE instance, and restarts the docker
 # daemon
 grpc_add_docker_user() {
+  _grpc_ensure_gcloud_ssh || return 1;
   local host=$1
   [[ -n $host ]] || {
     echo "$FUNCNAME: missing arg: host" 1>&2
@@ -81,6 +96,7 @@
 # grpc_update_image gs://bucket/path/to/dockerfile parent \.
 #   image_label path/to/docker_dir docker_gce_instance [project] [zone]
 grpc_update_image() {
+  _grpc_ensure_gcloud_ssh || return 1;
   local gs_root_uri=$1
   [[ -n $gs_root_uri ]] || {
     echo "$FUNCNAME: missing arg: gs_root_uri" 1>&2
@@ -350,6 +366,8 @@
 #
 # Updates the GCE docker instance <server_name>
 grpc_update_docker_images() {
+  _grpc_ensure_gcloud_ssh || return 1;
+
   # 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
@@ -469,6 +487,7 @@
 #
 # --server_host=<svr_addr>  --server_port=<svr_port> --test_case=<...>
 grpc_interop_test() {
+  _grpc_ensure_gcloud_ssh || return 1;
   # declare vars local so that they don't pollute the shell environment
   # where they this func is used.
 
diff --git a/tools/run_tests/run_lcov.sh b/tools/run_tests/run_lcov.sh
new file mode 100755
index 0000000..6f22b0e
--- /dev/null
+++ b/tools/run_tests/run_lcov.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -ex
+
+out=`realpath ${1:-coverage}`
+
+root=`realpath $(dirname $0)/../..`
+tmp=`mktemp`
+cd $root
+tools/run_tests/run_tests.py -c gcov
+lcov --capture --directory . --output-file $tmp
+genhtml $tmp --output-directory $out
+rm $tmp
+if which xdg-open > /dev/null
+then
+  xdg-open file://$out/index.html
+fi
+
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index 6ab80d9..428f6c4 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -36,6 +36,7 @@
   'tsan': SimpleConfig('tsan'),
   'msan': SimpleConfig('msan'),
   'asan': SimpleConfig('asan'),
+  'gcov': SimpleConfig('gcov'),
   'valgrind': ValgrindConfig('dbg'),
   }
 
@@ -74,22 +75,23 @@
   if not jobset.run(
       (['make',
         '-j', '%d' % (multiprocessing.cpu_count() + 1),
-        target,
-        'CONFIG=%s' % cfg]
-       for cfg in build_configs
-       for target in _MAKE_TEST_TARGETS),
+        'CONFIG=%s' % cfg] + _MAKE_TEST_TARGETS
+       for cfg in build_configs),
       check_cancelled, maxjobs=1):
-    sys.exit(1)
+    return 1
 
   # run all the tests
-  jobset.run((
+  if not jobset.run((
       config.run_command(x)
       for config in run_configs
       for filt in filters
       for x in itertools.chain.from_iterable(itertools.repeat(
           glob.glob('bins/%s/%s_test' % (
               config.build_config, filt)),
-          runs_per_test))), check_cancelled)
+          runs_per_test))), check_cancelled):
+    return 2
+
+  return 0
 
 
 if forever:
@@ -101,5 +103,5 @@
     while not have_files_changed():
       time.sleep(1)
 else:
-  _build_and_run(lambda: False)
+  sys.exit(_build_and_run(lambda: False))
 
diff --git a/vsprojects/vs2013/build_and_run_tests.bat b/vsprojects/vs2013/build_and_run_tests.bat
index 06e9776..3e36dcf 100644
--- a/vsprojects/vs2013/build_and_run_tests.bat
+++ b/vsprojects/vs2013/build_and_run_tests.bat
@@ -5,7 +5,7 @@
 
 @rem Build the library dependencies first
 MSBuild.exe gpr.vcxproj /p:Configuration=Debug
-MSBuild.exe grpc_test_util.vcxproj /p:Configuration=Debug
+MSBuild.exe gpr_test_util.vcxproj /p:Configuration=Debug
 
 mkdir test_bin