diff --git a/BUILD b/BUILD
index cc5fd3eb47a487b19e79bc992cf540aae67e6d8e..bdea522ad15f97e0adb1eb111478246a388bb12d 100644
--- a/BUILD
+++ b/BUILD
@@ -282,6 +282,7 @@ grpc_cc_library(
         "src/core/ext/census/grpc_filter.c",
         "src/core/ext/census/grpc_plugin.c",
         "src/core/ext/census/initialize.c",
+        "src/core/ext/census/intrusive_hash_map.c",
         "src/core/ext/census/mlog.c",
         "src/core/ext/census/operation.c",
         "src/core/ext/census/placeholders.c",
@@ -297,6 +298,8 @@ grpc_cc_library(
         "src/core/ext/census/gen/census.pb.h",
         "src/core/ext/census/gen/trace_context.pb.h",
         "src/core/ext/census/grpc_filter.h",
+        "src/core/ext/census/intrusive_hash_map.h",
+        "src/core/ext/census/intrusive_hash_map_internal.h",
         "src/core/ext/census/mlog.h",
         "src/core/ext/census/resource.h",
         "src/core/ext/census/rpc_metric_id.h",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1a235456021dfcf1ba0be0f24c00c8a900dceaa4..1635d5dc2d545c3ee17b034e7f2351d3c1a6cfca 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -375,6 +375,7 @@ add_dependencies(buildtests_c bdp_estimator_test)
 add_dependencies(buildtests_c bin_decoder_test)
 add_dependencies(buildtests_c bin_encoder_test)
 add_dependencies(buildtests_c census_context_test)
+add_dependencies(buildtests_c census_intrusive_hash_map_test)
 add_dependencies(buildtests_c census_resource_test)
 add_dependencies(buildtests_c census_trace_context_test)
 add_dependencies(buildtests_c channel_create_test)
@@ -1157,6 +1158,7 @@ add_library(grpc
   src/core/ext/census/grpc_filter.c
   src/core/ext/census/grpc_plugin.c
   src/core/ext/census/initialize.c
+  src/core/ext/census/intrusive_hash_map.c
   src/core/ext/census/mlog.c
   src/core/ext/census/operation.c
   src/core/ext/census/placeholders.c
@@ -2048,6 +2050,7 @@ add_library(grpc_unsecure
   src/core/ext/census/grpc_filter.c
   src/core/ext/census/grpc_plugin.c
   src/core/ext/census/initialize.c
+  src/core/ext/census/intrusive_hash_map.c
   src/core/ext/census/mlog.c
   src/core/ext/census/operation.c
   src/core/ext/census/placeholders.c
@@ -2659,6 +2662,7 @@ add_library(grpc++_cronet
   src/core/ext/census/grpc_filter.c
   src/core/ext/census/grpc_plugin.c
   src/core/ext/census/initialize.c
+  src/core/ext/census/intrusive_hash_map.c
   src/core/ext/census/mlog.c
   src/core/ext/census/operation.c
   src/core/ext/census/placeholders.c
@@ -4769,6 +4773,37 @@ target_link_libraries(census_context_test
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 
+add_executable(census_intrusive_hash_map_test
+  test/core/census/intrusive_hash_map_test.c
+)
+
+
+target_include_directories(census_intrusive_hash_map_test
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${BORINGSSL_ROOT_DIR}/include
+  PRIVATE ${PROTOBUF_ROOT_DIR}/src
+  PRIVATE ${BENCHMARK_ROOT_DIR}/include
+  PRIVATE ${ZLIB_ROOT_DIR}
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib
+  PRIVATE ${CARES_BUILD_INCLUDE_DIR}
+  PRIVATE ${CARES_INCLUDE_DIR}
+  PRIVATE ${CARES_PLATFORM_INCLUDE_DIR}
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/cares/cares
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/gflags/include
+)
+
+target_link_libraries(census_intrusive_hash_map_test
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc_test_util
+  grpc
+  gpr_test_util
+  gpr
+)
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
 add_executable(census_resource_test
   test/core/census/resource_test.c
 )
diff --git a/Makefile b/Makefile
index 5e5cd956bb8c9f56a2227010e3ccc90b7ff04430..188ca8b2682f584fdbf6cb6fefe7aa813fb20f96 100644
--- a/Makefile
+++ b/Makefile
@@ -967,6 +967,7 @@ bdp_estimator_test: $(BINDIR)/$(CONFIG)/bdp_estimator_test
 bin_decoder_test: $(BINDIR)/$(CONFIG)/bin_decoder_test
 bin_encoder_test: $(BINDIR)/$(CONFIG)/bin_encoder_test
 census_context_test: $(BINDIR)/$(CONFIG)/census_context_test
+census_intrusive_hash_map_test: $(BINDIR)/$(CONFIG)/census_intrusive_hash_map_test
 census_resource_test: $(BINDIR)/$(CONFIG)/census_resource_test
 census_trace_context_test: $(BINDIR)/$(CONFIG)/census_trace_context_test
 channel_create_test: $(BINDIR)/$(CONFIG)/channel_create_test
@@ -1360,6 +1361,7 @@ buildtests_c: privatelibs_c \
   $(BINDIR)/$(CONFIG)/bin_decoder_test \
   $(BINDIR)/$(CONFIG)/bin_encoder_test \
   $(BINDIR)/$(CONFIG)/census_context_test \
+  $(BINDIR)/$(CONFIG)/census_intrusive_hash_map_test \
   $(BINDIR)/$(CONFIG)/census_resource_test \
   $(BINDIR)/$(CONFIG)/census_trace_context_test \
   $(BINDIR)/$(CONFIG)/channel_create_test \
@@ -1763,6 +1765,8 @@ test_c: buildtests_c
 	$(Q) $(BINDIR)/$(CONFIG)/bin_encoder_test || ( echo test bin_encoder_test failed ; exit 1 )
 	$(E) "[RUN]     Testing census_context_test"
 	$(Q) $(BINDIR)/$(CONFIG)/census_context_test || ( echo test census_context_test failed ; exit 1 )
+	$(E) "[RUN]     Testing census_intrusive_hash_map_test"
+	$(Q) $(BINDIR)/$(CONFIG)/census_intrusive_hash_map_test || ( echo test census_intrusive_hash_map_test failed ; exit 1 )
 	$(E) "[RUN]     Testing census_resource_test"
 	$(Q) $(BINDIR)/$(CONFIG)/census_resource_test || ( echo test census_resource_test failed ; exit 1 )
 	$(E) "[RUN]     Testing census_trace_context_test"
@@ -3134,6 +3138,7 @@ LIBGRPC_SRC = \
     src/core/ext/census/grpc_filter.c \
     src/core/ext/census/grpc_plugin.c \
     src/core/ext/census/initialize.c \
+    src/core/ext/census/intrusive_hash_map.c \
     src/core/ext/census/mlog.c \
     src/core/ext/census/operation.c \
     src/core/ext/census/placeholders.c \
@@ -3994,6 +3999,7 @@ LIBGRPC_UNSECURE_SRC = \
     src/core/ext/census/grpc_filter.c \
     src/core/ext/census/grpc_plugin.c \
     src/core/ext/census/initialize.c \
+    src/core/ext/census/intrusive_hash_map.c \
     src/core/ext/census/mlog.c \
     src/core/ext/census/operation.c \
     src/core/ext/census/placeholders.c \
@@ -4591,6 +4597,7 @@ LIBGRPC++_CRONET_SRC = \
     src/core/ext/census/grpc_filter.c \
     src/core/ext/census/grpc_plugin.c \
     src/core/ext/census/initialize.c \
+    src/core/ext/census/intrusive_hash_map.c \
     src/core/ext/census/mlog.c \
     src/core/ext/census/operation.c \
     src/core/ext/census/placeholders.c \
@@ -8687,6 +8694,38 @@ endif
 endif
 
 
+CENSUS_INTRUSIVE_HASH_MAP_TEST_SRC = \
+    test/core/census/intrusive_hash_map_test.c \
+
+CENSUS_INTRUSIVE_HASH_MAP_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(CENSUS_INTRUSIVE_HASH_MAP_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/census_intrusive_hash_map_test: openssl_dep_error
+
+else
+
+
+
+$(BINDIR)/$(CONFIG)/census_intrusive_hash_map_test: $(CENSUS_INTRUSIVE_HASH_MAP_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(CENSUS_INTRUSIVE_HASH_MAP_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o $(BINDIR)/$(CONFIG)/census_intrusive_hash_map_test
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/census/intrusive_hash_map_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_census_intrusive_hash_map_test: $(CENSUS_INTRUSIVE_HASH_MAP_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(CENSUS_INTRUSIVE_HASH_MAP_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 CENSUS_RESOURCE_TEST_SRC = \
     test/core/census/resource_test.c \
 
diff --git a/binding.gyp b/binding.gyp
index 8aafdaa62b8f1b3c2cc8e86b0f6c0af0d51743d6..e6174851340b485dd2085c2f10163d8dde2e1e02 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -890,6 +890,7 @@
         'src/core/ext/census/grpc_filter.c',
         'src/core/ext/census/grpc_plugin.c',
         'src/core/ext/census/initialize.c',
+        'src/core/ext/census/intrusive_hash_map.c',
         'src/core/ext/census/mlog.c',
         'src/core/ext/census/operation.c',
         'src/core/ext/census/placeholders.c',
diff --git a/build.yaml b/build.yaml
index 98401b84c55bfacb48a066f4a86d9b7d02a7c1f1..d2ea16c8c2024b9afa45336a89abc48174c7cd11 100644
--- a/build.yaml
+++ b/build.yaml
@@ -27,6 +27,8 @@ filegroups:
   - src/core/ext/census/gen/census.pb.h
   - src/core/ext/census/gen/trace_context.pb.h
   - src/core/ext/census/grpc_filter.h
+  - src/core/ext/census/intrusive_hash_map.h
+  - src/core/ext/census/intrusive_hash_map_internal.h
   - src/core/ext/census/mlog.h
   - src/core/ext/census/resource.h
   - src/core/ext/census/rpc_metric_id.h
@@ -45,6 +47,7 @@ filegroups:
   - src/core/ext/census/grpc_filter.c
   - src/core/ext/census/grpc_plugin.c
   - src/core/ext/census/initialize.c
+  - src/core/ext/census/intrusive_hash_map.c
   - src/core/ext/census/mlog.c
   - src/core/ext/census/operation.c
   - src/core/ext/census/placeholders.c
@@ -1676,6 +1679,16 @@ targets:
   - grpc
   - gpr_test_util
   - gpr
+- name: census_intrusive_hash_map_test
+  build: test
+  language: c
+  src:
+  - test/core/census/intrusive_hash_map_test.c
+  deps:
+  - grpc_test_util
+  - grpc
+  - gpr_test_util
+  - gpr
 - name: census_resource_test
   build: test
   language: c
diff --git a/config.m4 b/config.m4
index ec463b2243f900a1c6ff2a91de706ad5073a7ff6..3b9cac1abed020bc3b1848a6821fac0a6a8d030c 100644
--- a/config.m4
+++ b/config.m4
@@ -322,6 +322,7 @@ if test "$PHP_GRPC" != "no"; then
     src/core/ext/census/grpc_filter.c \
     src/core/ext/census/grpc_plugin.c \
     src/core/ext/census/initialize.c \
+    src/core/ext/census/intrusive_hash_map.c \
     src/core/ext/census/mlog.c \
     src/core/ext/census/operation.c \
     src/core/ext/census/placeholders.c \
diff --git a/config.w32 b/config.w32
index 0d82f9d757f62dc1d3a0560c2b0fab141907ae71..7c407e848a442a1779dd3de501fbdd399c7f6970 100644
--- a/config.w32
+++ b/config.w32
@@ -299,6 +299,7 @@ if (PHP_GRPC != "no") {
     "src\\core\\ext\\census\\grpc_filter.c " +
     "src\\core\\ext\\census\\grpc_plugin.c " +
     "src\\core\\ext\\census\\initialize.c " +
+    "src\\core\\ext\\census\\intrusive_hash_map.c " +
     "src\\core\\ext\\census\\mlog.c " +
     "src\\core\\ext\\census\\operation.c " +
     "src\\core\\ext\\census\\placeholders.c " +
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index 1176a15f2bc90cab3cbaf729ef6d43528ac3b7e6..d9de2d78a051947bdaf06bd7e86b47138f48c80a 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -463,6 +463,8 @@ Pod::Spec.new do |s|
                       'src/core/ext/census/gen/census.pb.h',
                       'src/core/ext/census/gen/trace_context.pb.h',
                       'src/core/ext/census/grpc_filter.h',
+                      'src/core/ext/census/intrusive_hash_map.h',
+                      'src/core/ext/census/intrusive_hash_map_internal.h',
                       'src/core/ext/census/mlog.h',
                       'src/core/ext/census/resource.h',
                       'src/core/ext/census/rpc_metric_id.h',
@@ -713,6 +715,7 @@ Pod::Spec.new do |s|
                       'src/core/ext/census/grpc_filter.c',
                       'src/core/ext/census/grpc_plugin.c',
                       'src/core/ext/census/initialize.c',
+                      'src/core/ext/census/intrusive_hash_map.c',
                       'src/core/ext/census/mlog.c',
                       'src/core/ext/census/operation.c',
                       'src/core/ext/census/placeholders.c',
@@ -946,6 +949,8 @@ Pod::Spec.new do |s|
                               'src/core/ext/census/gen/census.pb.h',
                               'src/core/ext/census/gen/trace_context.pb.h',
                               'src/core/ext/census/grpc_filter.h',
+                              'src/core/ext/census/intrusive_hash_map.h',
+                              'src/core/ext/census/intrusive_hash_map_internal.h',
                               'src/core/ext/census/mlog.h',
                               'src/core/ext/census/resource.h',
                               'src/core/ext/census/rpc_metric_id.h',
diff --git a/grpc.gemspec b/grpc.gemspec
index 32c11644566af75e847600361c634dab98218f47..b334efb0b5d770eee958cf7125e7f640618c2b08 100755
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -379,6 +379,8 @@ Gem::Specification.new do |s|
   s.files += %w( src/core/ext/census/gen/census.pb.h )
   s.files += %w( src/core/ext/census/gen/trace_context.pb.h )
   s.files += %w( src/core/ext/census/grpc_filter.h )
+  s.files += %w( src/core/ext/census/intrusive_hash_map.h )
+  s.files += %w( src/core/ext/census/intrusive_hash_map_internal.h )
   s.files += %w( src/core/ext/census/mlog.h )
   s.files += %w( src/core/ext/census/resource.h )
   s.files += %w( src/core/ext/census/rpc_metric_id.h )
@@ -629,6 +631,7 @@ Gem::Specification.new do |s|
   s.files += %w( src/core/ext/census/grpc_filter.c )
   s.files += %w( src/core/ext/census/grpc_plugin.c )
   s.files += %w( src/core/ext/census/initialize.c )
+  s.files += %w( src/core/ext/census/intrusive_hash_map.c )
   s.files += %w( src/core/ext/census/mlog.c )
   s.files += %w( src/core/ext/census/operation.c )
   s.files += %w( src/core/ext/census/placeholders.c )
diff --git a/package.xml b/package.xml
index 5bcffd6eede3aff6eb1b63a95f75247107afd339..9179ef238c9fbaa84f13429626cced982165eb5e 100644
--- a/package.xml
+++ b/package.xml
@@ -393,6 +393,8 @@
     <file baseinstalldir="/" name="src/core/ext/census/gen/census.pb.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/gen/trace_context.pb.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/grpc_filter.h" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/census/intrusive_hash_map.h" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/census/intrusive_hash_map_internal.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/mlog.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/resource.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/rpc_metric_id.h" role="src" />
@@ -643,6 +645,7 @@
     <file baseinstalldir="/" name="src/core/ext/census/grpc_filter.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/grpc_plugin.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/initialize.c" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/census/intrusive_hash_map.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/mlog.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/operation.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/placeholders.c" role="src" />
diff --git a/src/core/ext/census/intrusive_hash_map.c b/src/core/ext/census/intrusive_hash_map.c
new file mode 100644
index 0000000000000000000000000000000000000000..e64e8086ca204f2da2f5188fb2be4eaa2db089f3
--- /dev/null
+++ b/src/core/ext/census/intrusive_hash_map.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/core/ext/census/intrusive_hash_map.h"
+#include <string.h>
+
+extern bool hm_index_compare(const hm_index *A, const hm_index *B);
+
+/* Simple hashing function that takes lower 32 bits. */
+static inline uint32_t chunked_vector_hasher(uint64_t key) {
+  return (uint32_t)key;
+}
+
+/* Vector chunks are 1MiB divided by pointer size. */
+static const size_t VECTOR_CHUNK_SIZE = (1 << 20) / sizeof(void *);
+
+/* Helper functions which return buckets from the chunked vector. */
+static inline void **get_mutable_bucket(const chunked_vector *buckets,
+                                        uint32_t index) {
+  if (index < VECTOR_CHUNK_SIZE) {
+    return &buckets->first_[index];
+  }
+  size_t rest_index = (index - VECTOR_CHUNK_SIZE) / VECTOR_CHUNK_SIZE;
+  return &buckets->rest_[rest_index][index % VECTOR_CHUNK_SIZE];
+}
+
+static inline void *get_bucket(const chunked_vector *buckets, uint32_t index) {
+  if (index < VECTOR_CHUNK_SIZE) {
+    return buckets->first_[index];
+  }
+  size_t rest_index = (index - VECTOR_CHUNK_SIZE) / VECTOR_CHUNK_SIZE;
+  return buckets->rest_[rest_index][index % VECTOR_CHUNK_SIZE];
+}
+
+/* Helper function. */
+static inline size_t RestSize(const chunked_vector *vec) {
+  return (vec->size_ <= VECTOR_CHUNK_SIZE)
+             ? 0
+             : (vec->size_ - VECTOR_CHUNK_SIZE - 1) / VECTOR_CHUNK_SIZE + 1;
+}
+
+/* Initialize chunked vector to size of 0. */
+static void chunked_vector_init(chunked_vector *vec) {
+  vec->size_ = 0;
+  vec->first_ = NULL;
+  vec->rest_ = NULL;
+}
+
+/* Clear chunked vector and free all memory that has been allocated then
+   initialize chunked vector. */
+static void chunked_vector_clear(chunked_vector *vec) {
+  if (vec->first_ != NULL) {
+    gpr_free(vec->first_);
+  }
+  if (vec->rest_ != NULL) {
+    size_t rest_size = RestSize(vec);
+    for (size_t i = 0; i < rest_size; ++i) {
+      if (vec->rest_[i] != NULL) {
+        gpr_free(vec->rest_[i]);
+      }
+    }
+    gpr_free(vec->rest_);
+  }
+  chunked_vector_init(vec);
+}
+
+/* Clear chunked vector and then resize it to n entries. Allow the first 1MB to
+   be read w/o an extra cache miss. The rest of the elements are stored in an
+   array of arrays to avoid large mallocs. */
+static void chunked_vector_reset(chunked_vector *vec, size_t n) {
+  chunked_vector_clear(vec);
+  vec->size_ = n;
+  if (n <= VECTOR_CHUNK_SIZE) {
+    vec->first_ = (void **)gpr_malloc(sizeof(void *) * n);
+    memset(vec->first_, 0, sizeof(void *) * n);
+  } else {
+    vec->first_ = (void **)gpr_malloc(sizeof(void *) * VECTOR_CHUNK_SIZE);
+    memset(vec->first_, 0, sizeof(void *) * VECTOR_CHUNK_SIZE);
+    size_t rest_size = RestSize(vec);
+    vec->rest_ = (void ***)gpr_malloc(sizeof(void **) * rest_size);
+    memset(vec->rest_, 0, sizeof(void **) * rest_size);
+    int i = 0;
+    n -= VECTOR_CHUNK_SIZE;
+    while (n > 0) {
+      size_t this_size = GPR_MIN(n, VECTOR_CHUNK_SIZE);
+      vec->rest_[i] = (void **)gpr_malloc(sizeof(void *) * this_size);
+      memset(vec->rest_[i], 0, sizeof(void *) * this_size);
+      n -= this_size;
+      ++i;
+    }
+  }
+}
+
+void intrusive_hash_map_init(intrusive_hash_map *hash_map,
+                             uint32_t initial_log2_table_size) {
+  hash_map->log2_num_buckets = initial_log2_table_size;
+  hash_map->num_items = 0;
+  uint32_t num_buckets = (uint32_t)1 << hash_map->log2_num_buckets;
+  hash_map->extend_threshold = num_buckets >> 1;
+  chunked_vector_init(&hash_map->buckets);
+  chunked_vector_reset(&hash_map->buckets, num_buckets);
+  hash_map->hash_mask = num_buckets - 1;
+}
+
+bool intrusive_hash_map_empty(const intrusive_hash_map *hash_map) {
+  return hash_map->num_items == 0;
+}
+
+size_t intrusive_hash_map_size(const intrusive_hash_map *hash_map) {
+  return hash_map->num_items;
+}
+
+void intrusive_hash_map_end(const intrusive_hash_map *hash_map, hm_index *idx) {
+  idx->bucket_index = (uint32_t)hash_map->buckets.size_;
+  GPR_ASSERT(idx->bucket_index <= UINT32_MAX);
+  idx->item = NULL;
+}
+
+void intrusive_hash_map_next(const intrusive_hash_map *hash_map,
+                             hm_index *idx) {
+  idx->item = idx->item->hash_link;
+  while (idx->item == NULL) {
+    idx->bucket_index++;
+    if (idx->bucket_index >= hash_map->buckets.size_) {
+      /* Reached end of table. */
+      idx->item = NULL;
+      return;
+    }
+    idx->item = (hm_item *)get_bucket(&hash_map->buckets, idx->bucket_index);
+  }
+}
+
+void intrusive_hash_map_begin(const intrusive_hash_map *hash_map,
+                              hm_index *idx) {
+  for (uint32_t i = 0; i < hash_map->buckets.size_; ++i) {
+    if (get_bucket(&hash_map->buckets, i) != NULL) {
+      idx->bucket_index = i;
+      idx->item = (hm_item *)get_bucket(&hash_map->buckets, i);
+      return;
+    }
+  }
+  intrusive_hash_map_end(hash_map, idx);
+}
+
+hm_item *intrusive_hash_map_find(const intrusive_hash_map *hash_map,
+                                 uint64_t key) {
+  uint32_t index = chunked_vector_hasher(key) & hash_map->hash_mask;
+
+  hm_item *p = (hm_item *)get_bucket(&hash_map->buckets, index);
+  while (p != NULL) {
+    if (key == p->key) {
+      return p;
+    }
+    p = p->hash_link;
+  }
+  return NULL;
+}
+
+hm_item *intrusive_hash_map_erase(intrusive_hash_map *hash_map, uint64_t key) {
+  uint32_t index = chunked_vector_hasher(key) & hash_map->hash_mask;
+
+  hm_item **slot = (hm_item **)get_mutable_bucket(&hash_map->buckets, index);
+  hm_item *p = *slot;
+  if (p == NULL) {
+    return NULL;
+  }
+
+  if (key == p->key) {
+    *slot = p->hash_link;
+    p->hash_link = NULL;
+    hash_map->num_items--;
+    return p;
+  }
+
+  hm_item *prev = p;
+  p = p->hash_link;
+
+  while (p) {
+    if (key == p->key) {
+      prev->hash_link = p->hash_link;
+      p->hash_link = NULL;
+      hash_map->num_items--;
+      return p;
+    }
+    prev = p;
+    p = p->hash_link;
+  }
+  return NULL;
+}
+
+/* Insert an hm_item* into the underlying chunked vector. hash_mask is
+ * array_size-1. Returns true if it is a new hm_item and false if the hm_item
+ * already existed.
+ */
+static inline bool intrusive_hash_map_internal_insert(chunked_vector *buckets,
+                                                      uint32_t hash_mask,
+                                                      hm_item *item) {
+  const uint64_t key = item->key;
+  uint32_t index = chunked_vector_hasher(key) & hash_mask;
+  hm_item **slot = (hm_item **)get_mutable_bucket(buckets, index);
+  hm_item *p = *slot;
+  item->hash_link = p;
+
+  /* Check to see if key already exists. */
+  while (p) {
+    if (p->key == key) {
+      return false;
+    }
+    p = p->hash_link;
+  }
+
+  /* Otherwise add new entry. */
+  *slot = item;
+  return true;
+}
+
+/* Extend the allocated number of elements in the hash map by a factor of 2. */
+void intrusive_hash_map_extend(intrusive_hash_map *hash_map) {
+  uint32_t new_log2_num_buckets = 1 + hash_map->log2_num_buckets;
+  uint32_t new_num_buckets = (uint32_t)1 << new_log2_num_buckets;
+  GPR_ASSERT(new_num_buckets <= UINT32_MAX && new_num_buckets > 0);
+  chunked_vector new_buckets;
+  chunked_vector_init(&new_buckets);
+  chunked_vector_reset(&new_buckets, new_num_buckets);
+  uint32_t new_hash_mask = new_num_buckets - 1;
+
+  hm_index cur_idx;
+  hm_index end_idx;
+  intrusive_hash_map_end(hash_map, &end_idx);
+  intrusive_hash_map_begin(hash_map, &cur_idx);
+  while (!hm_index_compare(&cur_idx, &end_idx)) {
+    hm_item *new_item = cur_idx.item;
+    intrusive_hash_map_next(hash_map, &cur_idx);
+    intrusive_hash_map_internal_insert(&new_buckets, new_hash_mask, new_item);
+  }
+
+  /* Set values for new chunked_vector. extend_threshold is set to half of
+   * new_num_buckets. */
+  hash_map->log2_num_buckets = new_log2_num_buckets;
+  chunked_vector_clear(&hash_map->buckets);
+  hash_map->buckets = new_buckets;
+  hash_map->hash_mask = new_hash_mask;
+  hash_map->extend_threshold = new_num_buckets >> 1;
+}
+
+/* Insert a hm_item. The hm_item must remain live until it is removed from the
+   table. This object does not take the ownership of hm_item. The caller must
+   remove this hm_item from the table and delete it before this table is
+   deleted. If hm_item exists already num_items is not changed. */
+bool intrusive_hash_map_insert(intrusive_hash_map *hash_map, hm_item *item) {
+  if (hash_map->num_items >= hash_map->extend_threshold) {
+    intrusive_hash_map_extend(hash_map);
+  }
+  if (intrusive_hash_map_internal_insert(&hash_map->buckets,
+                                         hash_map->hash_mask, item)) {
+    hash_map->num_items++;
+    return true;
+  }
+  return false;
+}
+
+void intrusive_hash_map_clear(intrusive_hash_map *hash_map,
+                              void (*free_object)(void *)) {
+  hm_index cur;
+  hm_index end;
+  intrusive_hash_map_end(hash_map, &end);
+  intrusive_hash_map_begin(hash_map, &cur);
+
+  while (!hm_index_compare(&cur, &end)) {
+    hm_index next = cur;
+    intrusive_hash_map_next(hash_map, &next);
+    if (cur.item != NULL) {
+      hm_item *item = intrusive_hash_map_erase(hash_map, cur.item->key);
+      (*free_object)((void *)item);
+      gpr_free(item);
+    }
+    cur = next;
+  }
+}
+
+void intrusive_hash_map_free(intrusive_hash_map *hash_map,
+                             void (*free_object)(void *)) {
+  intrusive_hash_map_clear(hash_map, (*free_object));
+  hash_map->num_items = 0;
+  hash_map->extend_threshold = 0;
+  hash_map->log2_num_buckets = 0;
+  hash_map->hash_mask = 0;
+  chunked_vector_clear(&hash_map->buckets);
+}
diff --git a/src/core/ext/census/intrusive_hash_map.h b/src/core/ext/census/intrusive_hash_map.h
new file mode 100644
index 0000000000000000000000000000000000000000..9a7fd07f20afe1c8c3b1893b0d6d2388d0b3aa18
--- /dev/null
+++ b/src/core/ext/census/intrusive_hash_map.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GRPC_CORE_EXT_CENSUS_INTRUSIVE_HASH_MAP_H
+#define GRPC_CORE_EXT_CENSUS_INTRUSIVE_HASH_MAP_H
+
+#include "src/core/ext/census/intrusive_hash_map_internal.h"
+
+/* intrusive_hash_map is a fast chained hash table. This hash map is faster than
+ * a dense hash map when the application calls insert and erase more often than
+ * find. When the workload is dominated by find() a dense hash map may be
+ * faster.
+ *
+ * intrusive_hash_map uses an intrusive header placed within a user defined
+ * struct. The header field IHM_key MUST be set to a valid value before
+ * insertion into the hash map or undefined behavior may occur. The header field
+ * IHM_hash_link MUST to be set to NULL initially.
+ *
+ * EXAMPLE USAGE:
+ *
+ *  typedef struct string_item {
+ *    INTRUSIVE_HASH_MAP_HEADER;
+ *    // User data.
+ *    char *str_buf;
+ *    uint16_t len;
+ *  } string_item;
+ *
+ *  static string_item *make_string_item(uint64_t key, const char *buf,
+ *                                       uint16_t len) {
+ *    string_item *item = (string_item *)gpr_malloc(sizeof(string_item));
+ *    item->IHM_key = key;
+ *    item->IHM_hash_link = NULL;
+ *    item->len = len;
+ *    item->str_buf = (char *)malloc(len);
+ *    memcpy(item->str_buf, buf, len);
+ *    return item;
+ *  }
+ *
+ *  intrusive_hash_map hash_map;
+ *  intrusive_hash_map_init(&hash_map, 4);
+ *  string_item *new_item1 = make_string_item(10, "test1", 5);
+ *  bool ok = intrusive_hash_map_insert(&hash_map, (hm_item *)new_item1);
+ *
+ *  string_item *item1 =
+ *    (string_item *)intrusive_hash_map_find(&hash_map, 10);
+ */
+
+/* Hash map item. Stores key and a pointer to the actual object. A user defined
+ * version of this can be passed in provided the first 2 entries (key and
+ * hash_link) are the same. These entries must be first in the user defined
+ * struct. Pointer to struct will need to be cast as (hm_item *) when passed to
+ * hash map. This allows it to be intrusive. */
+typedef struct hm_item {
+  uint64_t key;
+  struct hm_item *hash_link;
+  /* Optional user defined data after this. */
+} hm_item;
+
+/* Macro provided for ease of use.  This must be first in the user defined
+ * struct (i.e. uint64_t key and hm_item * must be the first two elements in
+ * that order). */
+#define INTRUSIVE_HASH_MAP_HEADER \
+  uint64_t IHM_key;               \
+  struct hm_item *IHM_hash_link
+
+/* Index struct which acts as a pseudo-iterator within the hash map. */
+typedef struct hm_index {
+  uint32_t bucket_index;  // hash map bucket index.
+  hm_item *item;          // Pointer to hm_item within the hash map.
+} hm_index;
+
+/* Returns true if two hm_indices point to the same object within the hash map
+ * and false otherwise. */
+inline bool hm_index_compare(const hm_index *A, const hm_index *B) {
+  return (A->item == B->item && A->bucket_index == B->bucket_index);
+}
+
+/*
+ * Helper functions for iterating over the hash map.
+ */
+
+/* On return idx will contain an invalid index which is always equal to
+ * hash_map->buckets.size_ */
+void intrusive_hash_map_end(const intrusive_hash_map *hash_map, hm_index *idx);
+
+/* Iterates index to the next valid entry in the hash map and stores the
+ * index within idx. If end of table is reached, idx will contain the same
+ * values as if intrusive_hash_map_end() was called. */
+void intrusive_hash_map_next(const intrusive_hash_map *hash_map, hm_index *idx);
+
+/* On return, idx will contain the index of the first non-null entry in the hash
+ * map. If the hash map is empty, idx will contain the same values as if
+ * intrusive_hash_map_end() was called. */
+void intrusive_hash_map_begin(const intrusive_hash_map *hash_map,
+                              hm_index *idx);
+
+/* Initialize intrusive hash map data structure. This must be called before
+ * the hash map can be used. The initial size of an intrusive hash map will be
+ * 2^initial_log2_map_size (valid range is [0, 31]). */
+void intrusive_hash_map_init(intrusive_hash_map *hash_map,
+                             uint32_t initial_log2_map_size);
+
+/* Returns true if the hash map is empty and false otherwise. */
+bool intrusive_hash_map_empty(const intrusive_hash_map *hash_map);
+
+/* Returns the number of elements currently in the hash map. */
+size_t intrusive_hash_map_size(const intrusive_hash_map *hash_map);
+
+/* Find a hm_item within the hash map by key. Returns NULL if item was not
+ * found. */
+hm_item *intrusive_hash_map_find(const intrusive_hash_map *hash_map,
+                                 uint64_t key);
+
+/* Erase the hm_item that corresponds with key. If the hm_item is found, return
+ * the pointer to the hm_item. Else returns NULL. */
+hm_item *intrusive_hash_map_erase(intrusive_hash_map *hash_map, uint64_t key);
+
+/* Attempts to insert a new hm_item into the hash map.  If an element with the
+ * same key already exists, it will not insert the new item and return false.
+ * Otherwise, it will insert the new item and return true. */
+bool intrusive_hash_map_insert(intrusive_hash_map *hash_map, hm_item *item);
+
+/* Clears entire contents of the hash map, but leaves internal data structure
+ * untouched. Second argument takes a function pointer to a function that will
+ * free the object designated by the user and pointed to by hash_map->value. */
+void intrusive_hash_map_clear(intrusive_hash_map *hash_map,
+                              void (*free_object)(void *));
+
+/* Erase all contents of hash map and free the memory. Hash map is invalid
+ * after calling this function and cannot be used until it has been
+ * reinitialized (intrusive_hash_map_init()). This function takes a function
+ * pointer to a function that will free the object designated by the user and
+ * pointed to by hash_map->value. */
+void intrusive_hash_map_free(intrusive_hash_map *hash_map,
+                             void (*free_object)(void *));
+
+#endif // GRPC_CORE_EXT_CENSUS_INTRUSIVE_HASH_MAP_H
diff --git a/src/core/ext/census/intrusive_hash_map_internal.h b/src/core/ext/census/intrusive_hash_map_internal.h
new file mode 100644
index 0000000000000000000000000000000000000000..c0a3c6f59a80931200b7c51f1613179bb33d6864
--- /dev/null
+++ b/src/core/ext/census/intrusive_hash_map_internal.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GRPC_CORE_EXT_CENSUS_INTRUSIVE_HASH_MAP_INTERNAL_H
+#define GRPC_CORE_EXT_CENSUS_INTRUSIVE_HASH_MAP_INTERNAL_H
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/useful.h>
+#include <stdbool.h>
+
+/* The chunked vector is a data structure that allocates buckets for use in the
+ * hash map. ChunkedVector is logically equivalent to T*[N] (cast void* as
+ * T*). It's internally implemented as an array of 1MB arrays to avoid
+ * allocating large consecutive memory chunks. This is an internal data
+ * structure that should never be accessed directly. */
+typedef struct chunked_vector {
+  size_t size_;
+  void **first_;
+  void ***rest_;
+} chunked_vector;
+
+/* Core intrusive hash map data structure. All internal elements are managed by
+ * functions and should not be altered manually. */
+typedef struct intrusive_hash_map {
+  uint32_t num_items;
+  uint32_t extend_threshold;
+  uint32_t log2_num_buckets;
+  uint32_t hash_mask;
+  chunked_vector buckets;
+} intrusive_hash_map;
+
+#endif // GRPC_CORE_EXT_CENSUS_INTRUSIVE_HASH_MAP_INTERNAL_H
diff --git a/src/core/lib/http/httpcli.c b/src/core/lib/http/httpcli.c
index c3421e1b55ce7ac7a5c8f84d381213fca8867c48..f5588a9a767055c6a47f4f66110ad5bd0d8b6c21 100644
--- a/src/core/lib/http/httpcli.c
+++ b/src/core/lib/http/httpcli.c
@@ -245,7 +245,7 @@ static void next_address(grpc_exec_ctx *exec_ctx, internal_request *req,
 static void on_resolved(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error) {
   internal_request *req = arg;
   if (error != GRPC_ERROR_NONE) {
-    finish(exec_ctx, req, error);
+    finish(exec_ctx, req, GRPC_ERROR_REF(error));
     return;
   }
   req->next_address = 0;
diff --git a/src/core/lib/http/httpcli_security_connector.c b/src/core/lib/http/httpcli_security_connector.c
index 76946434f0698159547ca3f8bb437ebcb3fb2046..ea7c1122c17a25e7f7c9e4ca6d320d98f14b91f5 100644
--- a/src/core/lib/http/httpcli_security_connector.c
+++ b/src/core/lib/http/httpcli_security_connector.c
@@ -44,6 +44,7 @@
 #include "src/core/lib/slice/slice_internal.h"
 #include "src/core/lib/support/string.h"
 #include "src/core/tsi/ssl_transport_security.h"
+#include "src/core/tsi/transport_security_adapter.h"
 
 typedef struct {
   grpc_channel_security_connector base;
@@ -78,7 +79,8 @@ static void httpcli_ssl_add_handshakers(grpc_exec_ctx *exec_ctx,
   }
   grpc_handshake_manager_add(
       handshake_mgr,
-      grpc_security_handshaker_create(exec_ctx, handshaker, &sc->base));
+      grpc_security_handshaker_create(
+          exec_ctx, tsi_create_adapter_handshaker(handshaker), &sc->base));
 }
 
 static void httpcli_ssl_check_peer(grpc_exec_ctx *exec_ctx,
diff --git a/src/core/lib/security/transport/security_connector.c b/src/core/lib/security/transport/security_connector.c
index 30431a4e4a5247f935cfa96caf273566bc2b03bd..416a3bdb3588ad57229592840562a65ac79f7d90 100644
--- a/src/core/lib/security/transport/security_connector.c
+++ b/src/core/lib/security/transport/security_connector.c
@@ -56,6 +56,7 @@
 #include "src/core/lib/support/string.h"
 #include "src/core/tsi/fake_transport_security.h"
 #include "src/core/tsi/ssl_transport_security.h"
+#include "src/core/tsi/transport_security_adapter.h"
 
 /* -- Constants. -- */
 
@@ -390,7 +391,8 @@ static void fake_channel_add_handshakers(
   grpc_handshake_manager_add(
       handshake_mgr,
       grpc_security_handshaker_create(
-          exec_ctx, tsi_create_fake_handshaker(true /* is_client */),
+          exec_ctx, tsi_create_adapter_handshaker(
+                        tsi_create_fake_handshaker(true /* is_client */)),
           &sc->base));
 }
 
@@ -400,7 +402,8 @@ static void fake_server_add_handshakers(grpc_exec_ctx *exec_ctx,
   grpc_handshake_manager_add(
       handshake_mgr,
       grpc_security_handshaker_create(
-          exec_ctx, tsi_create_fake_handshaker(false /* is_client */),
+          exec_ctx, tsi_create_adapter_handshaker(
+                        tsi_create_fake_handshaker(false /* is_client */)),
           &sc->base));
 }
 
@@ -495,8 +498,10 @@ static void ssl_channel_add_handshakers(grpc_exec_ctx *exec_ctx,
   }
 
   // Create handshakers.
-  grpc_handshake_manager_add(handshake_mgr, grpc_security_handshaker_create(
-                                                exec_ctx, tsi_hs, &sc->base));
+  grpc_handshake_manager_add(
+      handshake_mgr,
+      grpc_security_handshaker_create(
+          exec_ctx, tsi_create_adapter_handshaker(tsi_hs), &sc->base));
 }
 
 static void ssl_server_add_handshakers(grpc_exec_ctx *exec_ctx,
@@ -515,8 +520,10 @@ static void ssl_server_add_handshakers(grpc_exec_ctx *exec_ctx,
   }
 
   // Create handshakers.
-  grpc_handshake_manager_add(handshake_mgr, grpc_security_handshaker_create(
-                                                exec_ctx, tsi_hs, &sc->base));
+  grpc_handshake_manager_add(
+      handshake_mgr,
+      grpc_security_handshaker_create(
+          exec_ctx, tsi_create_adapter_handshaker(tsi_hs), &sc->base));
 }
 
 static int ssl_host_matches_name(const tsi_peer *peer, const char *peer_name) {
diff --git a/src/core/lib/security/transport/security_handshaker.c b/src/core/lib/security/transport/security_handshaker.c
index 509b4b556d6eb784a28aa820d1ee402997cd9615..3bc113e20ffc861a05246006349a6cce6d19d441 100644
--- a/src/core/lib/security/transport/security_handshaker.c
+++ b/src/core/lib/security/transport/security_handshaker.c
@@ -71,12 +71,12 @@ typedef struct {
 
   unsigned char *handshake_buffer;
   size_t handshake_buffer_size;
-  grpc_slice_buffer left_overs;
   grpc_slice_buffer outgoing;
   grpc_closure on_handshake_data_sent_to_peer;
   grpc_closure on_handshake_data_received_from_peer;
   grpc_closure on_peer_checked;
   grpc_auth_context *auth_context;
+  tsi_handshaker_result *handshaker_result;
 } security_handshaker;
 
 static void security_handshaker_unref(grpc_exec_ctx *exec_ctx,
@@ -84,6 +84,7 @@ static void security_handshaker_unref(grpc_exec_ctx *exec_ctx,
   if (gpr_unref(&h->refs)) {
     gpr_mu_destroy(&h->mu);
     tsi_handshaker_destroy(h->handshaker);
+    tsi_handshaker_result_destroy(h->handshaker_result);
     if (h->endpoint_to_destroy != NULL) {
       grpc_endpoint_destroy(exec_ctx, h->endpoint_to_destroy);
     }
@@ -92,7 +93,6 @@ static void security_handshaker_unref(grpc_exec_ctx *exec_ctx,
       gpr_free(h->read_buffer_to_destroy);
     }
     gpr_free(h->handshake_buffer);
-    grpc_slice_buffer_destroy_internal(exec_ctx, &h->left_overs);
     grpc_slice_buffer_destroy_internal(exec_ctx, &h->outgoing);
     GRPC_AUTH_CONTEXT_UNREF(h->auth_context, "handshake");
     GRPC_SECURITY_CONNECTOR_UNREF(exec_ctx, h->connector, "handshake");
@@ -150,10 +150,10 @@ static void on_peer_checked(grpc_exec_ctx *exec_ctx, void *arg,
     security_handshake_failed_locked(exec_ctx, h, GRPC_ERROR_REF(error));
     goto done;
   }
-  // Get frame protector.
+  // Create frame protector.
   tsi_frame_protector *protector;
-  tsi_result result =
-      tsi_handshaker_create_frame_protector(h->handshaker, NULL, &protector);
+  tsi_result result = tsi_handshaker_result_create_frame_protector(
+      h->handshaker_result, NULL, &protector);
   if (result != TSI_OK) {
     error = grpc_set_tsi_error_result(
         GRPC_ERROR_CREATE_FROM_STATIC_STRING("Frame protector creation failed"),
@@ -161,14 +161,25 @@ static void on_peer_checked(grpc_exec_ctx *exec_ctx, void *arg,
     security_handshake_failed_locked(exec_ctx, h, error);
     goto done;
   }
-  // Success.
+  // Get unused bytes.
+  unsigned char *unused_bytes = NULL;
+  size_t unused_bytes_size = 0;
+  result = tsi_handshaker_result_get_unused_bytes(
+      h->handshaker_result, &unused_bytes, &unused_bytes_size);
   // Create secure endpoint.
-  h->args->endpoint = grpc_secure_endpoint_create(
-      protector, h->args->endpoint, h->left_overs.slices, h->left_overs.count);
-  h->left_overs.count = 0;
-  h->left_overs.length = 0;
-  // Clear out the read buffer before it gets passed to the transport,
-  // since any excess bytes were already copied to h->left_overs.
+  if (unused_bytes_size > 0) {
+    grpc_slice slice =
+        grpc_slice_from_copied_buffer((char *)unused_bytes, unused_bytes_size);
+    h->args->endpoint =
+        grpc_secure_endpoint_create(protector, h->args->endpoint, &slice, 1);
+    grpc_slice_unref_internal(exec_ctx, slice);
+  } else {
+    h->args->endpoint =
+        grpc_secure_endpoint_create(protector, h->args->endpoint, NULL, 0);
+  }
+  tsi_handshaker_result_destroy(h->handshaker_result);
+  h->handshaker_result = NULL;
+  // Clear out the read buffer before it gets passed to the transport.
   grpc_slice_buffer_reset_and_unref_internal(exec_ctx, h->args->read_buffer);
   // Add auth context to channel args.
   grpc_arg auth_context_arg = grpc_auth_context_to_arg(h->auth_context);
@@ -189,7 +200,8 @@ done:
 static grpc_error *check_peer_locked(grpc_exec_ctx *exec_ctx,
                                      security_handshaker *h) {
   tsi_peer peer;
-  tsi_result result = tsi_handshaker_extract_peer(h->handshaker, &peer);
+  tsi_result result =
+      tsi_handshaker_result_extract_peer(h->handshaker_result, &peer);
   if (result != TSI_OK) {
     return grpc_set_tsi_error_result(
         GRPC_ERROR_CREATE_FROM_STATIC_STRING("Peer extraction failed"), result);
@@ -199,34 +211,87 @@ static grpc_error *check_peer_locked(grpc_exec_ctx *exec_ctx,
   return GRPC_ERROR_NONE;
 }
 
-static grpc_error *send_handshake_bytes_to_peer_locked(grpc_exec_ctx *exec_ctx,
-                                                       security_handshaker *h) {
-  // Get data to send.
-  tsi_result result = TSI_OK;
-  size_t offset = 0;
-  do {
-    size_t to_send_size = h->handshake_buffer_size - offset;
-    result = tsi_handshaker_get_bytes_to_send_to_peer(
-        h->handshaker, h->handshake_buffer + offset, &to_send_size);
-    offset += to_send_size;
-    if (result == TSI_INCOMPLETE_DATA) {
-      h->handshake_buffer_size *= 2;
-      h->handshake_buffer =
-          gpr_realloc(h->handshake_buffer, h->handshake_buffer_size);
-    }
-  } while (result == TSI_INCOMPLETE_DATA);
+static grpc_error *on_handshake_next_done_locked(
+    grpc_exec_ctx *exec_ctx, security_handshaker *h, tsi_result result,
+    const unsigned char *bytes_to_send, size_t bytes_to_send_size,
+    tsi_handshaker_result *handshaker_result) {
+  grpc_error *error = GRPC_ERROR_NONE;
+  // Read more if we need to.
+  if (result == TSI_INCOMPLETE_DATA) {
+    GPR_ASSERT(bytes_to_send_size == 0);
+    grpc_endpoint_read(exec_ctx, h->args->endpoint, h->args->read_buffer,
+                       &h->on_handshake_data_received_from_peer);
+    return error;
+  }
   if (result != TSI_OK) {
     return grpc_set_tsi_error_result(
         GRPC_ERROR_CREATE_FROM_STATIC_STRING("Handshake failed"), result);
   }
-  // Send data.
-  grpc_slice to_send =
-      grpc_slice_from_copied_buffer((const char *)h->handshake_buffer, offset);
-  grpc_slice_buffer_reset_and_unref_internal(exec_ctx, &h->outgoing);
-  grpc_slice_buffer_add(&h->outgoing, to_send);
-  grpc_endpoint_write(exec_ctx, h->args->endpoint, &h->outgoing,
-                      &h->on_handshake_data_sent_to_peer);
-  return GRPC_ERROR_NONE;
+  // Update handshaker result.
+  if (handshaker_result != NULL) {
+    GPR_ASSERT(h->handshaker_result == NULL);
+    h->handshaker_result = handshaker_result;
+  }
+  if (bytes_to_send_size > 0) {
+    // Send data to peer, if needed.
+    grpc_slice to_send = grpc_slice_from_copied_buffer(
+        (const char *)bytes_to_send, bytes_to_send_size);
+    grpc_slice_buffer_reset_and_unref_internal(exec_ctx, &h->outgoing);
+    grpc_slice_buffer_add(&h->outgoing, to_send);
+    grpc_endpoint_write(exec_ctx, h->args->endpoint, &h->outgoing,
+                        &h->on_handshake_data_sent_to_peer);
+  } else if (handshaker_result == NULL) {
+    // There is nothing to send, but need to read from peer.
+    grpc_endpoint_read(exec_ctx, h->args->endpoint, h->args->read_buffer,
+                       &h->on_handshake_data_received_from_peer);
+  } else {
+    // Handshake has finished, check peer and so on.
+    error = check_peer_locked(exec_ctx, h);
+  }
+  return error;
+}
+
+static void on_handshake_next_done_grpc_wrapper(
+    tsi_result result, void *user_data, const unsigned char *bytes_to_send,
+    size_t bytes_to_send_size, tsi_handshaker_result *handshaker_result) {
+  security_handshaker *h = user_data;
+  // This callback will be invoked by TSI in a non-grpc thread, so it's
+  // safe to create our own exec_ctx here.
+  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  gpr_mu_lock(&h->mu);
+  grpc_error *error =
+      on_handshake_next_done_locked(&exec_ctx, h, result, bytes_to_send,
+                                    bytes_to_send_size, handshaker_result);
+  if (error != GRPC_ERROR_NONE) {
+    security_handshake_failed_locked(&exec_ctx, h, error);
+    gpr_mu_unlock(&h->mu);
+    security_handshaker_unref(&exec_ctx, h);
+  } else {
+    gpr_mu_unlock(&h->mu);
+  }
+  grpc_exec_ctx_finish(&exec_ctx);
+}
+
+static grpc_error *do_handshaker_next_locked(
+    grpc_exec_ctx *exec_ctx, security_handshaker *h,
+    const unsigned char *bytes_received, size_t bytes_received_size) {
+  // Invoke TSI handshaker.
+  unsigned char *bytes_to_send = NULL;
+  size_t bytes_to_send_size = 0;
+  tsi_handshaker_result *handshaker_result = NULL;
+  tsi_result result = tsi_handshaker_next(
+      h->handshaker, bytes_received, bytes_received_size, &bytes_to_send,
+      &bytes_to_send_size, &handshaker_result,
+      &on_handshake_next_done_grpc_wrapper, h);
+  if (result == TSI_ASYNC) {
+    // Handshaker operating asynchronously. Nothing else to do here;
+    // callback will be invoked in a TSI thread.
+    return GRPC_ERROR_NONE;
+  }
+  // Handshaker returned synchronously. Invoke callback directly in
+  // this thread with our existing exec_ctx.
+  return on_handshake_next_done_locked(exec_ctx, h, result, bytes_to_send,
+                                       bytes_to_send_size, handshaker_result);
 }
 
 static void on_handshake_data_received_from_peer(grpc_exec_ctx *exec_ctx,
@@ -241,72 +306,34 @@ static void on_handshake_data_received_from_peer(grpc_exec_ctx *exec_ctx,
     security_handshaker_unref(exec_ctx, h);
     return;
   }
-  // Process received data.
-  tsi_result result = TSI_OK;
-  size_t consumed_slice_size = 0;
+  // Copy all slices received.
   size_t i;
+  size_t bytes_received_size = 0;
   for (i = 0; i < h->args->read_buffer->count; i++) {
-    consumed_slice_size = GRPC_SLICE_LENGTH(h->args->read_buffer->slices[i]);
-    result = tsi_handshaker_process_bytes_from_peer(
-        h->handshaker, GRPC_SLICE_START_PTR(h->args->read_buffer->slices[i]),
-        &consumed_slice_size);
-    if (!tsi_handshaker_is_in_progress(h->handshaker)) break;
+    bytes_received_size += GRPC_SLICE_LENGTH(h->args->read_buffer->slices[i]);
   }
-  if (tsi_handshaker_is_in_progress(h->handshaker)) {
-    /* We may need more data. */
-    if (result == TSI_INCOMPLETE_DATA) {
-      grpc_endpoint_read(exec_ctx, h->args->endpoint, h->args->read_buffer,
-                         &h->on_handshake_data_received_from_peer);
-      goto done;
-    } else {
-      error = send_handshake_bytes_to_peer_locked(exec_ctx, h);
-      if (error != GRPC_ERROR_NONE) {
-        security_handshake_failed_locked(exec_ctx, h, error);
-        gpr_mu_unlock(&h->mu);
-        security_handshaker_unref(exec_ctx, h);
-        return;
-      }
-      goto done;
-    }
+  if (bytes_received_size > h->handshake_buffer_size) {
+    h->handshake_buffer = gpr_realloc(h->handshake_buffer, bytes_received_size);
+    h->handshake_buffer_size = bytes_received_size;
   }
-  if (result != TSI_OK) {
-    security_handshake_failed_locked(
-        exec_ctx, h,
-        grpc_set_tsi_error_result(
-            GRPC_ERROR_CREATE_FROM_STATIC_STRING("Handshake failed"), result));
-    gpr_mu_unlock(&h->mu);
-    security_handshaker_unref(exec_ctx, h);
-    return;
-  }
-  /* Handshake is done and successful this point. */
-  bool has_left_overs_in_current_slice =
-      (consumed_slice_size <
-       GRPC_SLICE_LENGTH(h->args->read_buffer->slices[i]));
-  size_t num_left_overs = (has_left_overs_in_current_slice ? 1 : 0) +
-                          h->args->read_buffer->count - i - 1;
-  if (num_left_overs > 0) {
-    /* Put the leftovers in our buffer (ownership transfered). */
-    if (has_left_overs_in_current_slice) {
-      grpc_slice tail = grpc_slice_split_tail(&h->args->read_buffer->slices[i],
-                                              consumed_slice_size);
-      grpc_slice_buffer_add(&h->left_overs, tail);
-      /* split_tail above increments refcount. */
-      grpc_slice_unref_internal(exec_ctx, tail);
-    }
-    grpc_slice_buffer_addn(
-        &h->left_overs, &h->args->read_buffer->slices[i + 1],
-        num_left_overs - (size_t)has_left_overs_in_current_slice);
+  size_t offset = 0;
+  for (i = 0; i < h->args->read_buffer->count; i++) {
+    size_t slice_size = GPR_SLICE_LENGTH(h->args->read_buffer->slices[i]);
+    memcpy(h->handshake_buffer + offset,
+           GRPC_SLICE_START_PTR(h->args->read_buffer->slices[i]), slice_size);
+    offset += slice_size;
   }
-  // Check peer.
-  error = check_peer_locked(exec_ctx, h);
+  // Call TSI handshaker.
+  error = do_handshaker_next_locked(exec_ctx, h, h->handshake_buffer,
+                                    bytes_received_size);
+
   if (error != GRPC_ERROR_NONE) {
     security_handshake_failed_locked(exec_ctx, h, error);
     gpr_mu_unlock(&h->mu);
     security_handshaker_unref(exec_ctx, h);
-    return;
+  } else {
+    gpr_mu_unlock(&h->mu);
   }
-done:
-  gpr_mu_unlock(&h->mu);
 }
 
 static void on_handshake_data_sent_to_peer(grpc_exec_ctx *exec_ctx, void *arg,
@@ -321,8 +348,8 @@ static void on_handshake_data_sent_to_peer(grpc_exec_ctx *exec_ctx, void *arg,
     security_handshaker_unref(exec_ctx, h);
     return;
   }
-  /* We may be done. */
-  if (tsi_handshaker_is_in_progress(h->handshaker)) {
+  // We may be done.
+  if (h->handshaker_result == NULL) {
     grpc_endpoint_read(exec_ctx, h->args->endpoint, h->args->read_buffer,
                        &h->on_handshake_data_received_from_peer);
   } else {
@@ -371,7 +398,7 @@ static void security_handshaker_do_handshake(grpc_exec_ctx *exec_ctx,
   h->args = args;
   h->on_handshake_done = on_handshake_done;
   gpr_ref(&h->refs);
-  grpc_error *error = send_handshake_bytes_to_peer_locked(exec_ctx, h);
+  grpc_error *error = do_handshaker_next_locked(exec_ctx, h, NULL, 0);
   if (error != GRPC_ERROR_NONE) {
     security_handshake_failed_locked(exec_ctx, h, error);
     gpr_mu_unlock(&h->mu);
@@ -404,7 +431,6 @@ static grpc_handshaker *security_handshaker_create(
                     grpc_schedule_on_exec_ctx);
   grpc_closure_init(&h->on_peer_checked, on_peer_checked, h,
                     grpc_schedule_on_exec_ctx);
-  grpc_slice_buffer_init(&h->left_overs);
   grpc_slice_buffer_init(&h->outgoing);
   return &h->base;
 }
diff --git a/src/node/README.md b/src/node/README.md
index 4b906643bc0eae283d86aa4d0bc0a647395f08ab..3b98b97879be4ca65457686ba3cd1a401414bf98 100644
--- a/src/node/README.md
+++ b/src/node/README.md
@@ -2,9 +2,9 @@
 # Node.js gRPC Library
 
 ## PREREQUISITES
-- `node`: This requires `node` to be installed, version `0.12` or above. If you instead have the `nodejs` executable on Debian, you should install the [`nodejs-legacy`](https://packages.debian.org/sid/nodejs-legacy) package.
+- `node`: This requires `node` to be installed, version `4.0` or above. If you instead have the `nodejs` executable on Debian, you should install the [`nodejs-legacy`](https://packages.debian.org/sid/nodejs-legacy) package.
 
-- **Note:** If you installed `node` via a package manager and the version is still less than `0.12`, try directly installing it from [nodejs.org](https://nodejs.org).
+- **Note:** If you installed `node` via a package manager and the version is still less than `4.0`, try directly installing it from [nodejs.org](https://nodejs.org).
 
 ## INSTALLATION
 
@@ -16,7 +16,7 @@ npm install grpc
 
 ## BUILD FROM SOURCE
  1. Clone [the grpc Git Repository](https://github.com/grpc/grpc).
- 2. Run `npm install` from the repository root.
+ 2. Run `npm install --build-from-source` from the repository root.
 
  - **Note:** On Windows, this might fail due to [nodejs issue #4932](https://github.com/nodejs/node/issues/4932) in which case, you will see something like the following in `npm install`'s output (towards the very beginning):
 
@@ -34,61 +34,3 @@ npm install grpc
 
 ## TESTING
 To run the test suite, simply run `npm test` in the install location.
-
-## API
-This library internally uses [ProtoBuf.js](https://github.com/dcodeIO/ProtoBuf.js), and some structures it exports match those exported by that library.
-
-If you require this module, you will get an object with the following members
-
-```javascript
-function load(filename)
-```
-
-Takes a filename of a [Protocol Buffer](https://developers.google.com/protocol-buffers/) file, and returns an object representing the structure of the protocol buffer in the following way:
-
- - Namespaces become maps from the names of their direct members to those member objects
- - Service definitions become client constructors for clients for that service. They also have a `service` member that can be used for constructing servers.
- - Message definitions become Message constructors like those that ProtoBuf.js would create
- - Enum definitions become Enum objects like those that ProtoBuf.js would create
- - Anything else becomes the relevant reflection object that ProtoBuf.js would create
-
-
-```javascript
-function loadObject(reflectionObject)
-```
-
-Returns the same structure that `load` returns, but takes a reflection object from `ProtoBuf.js` instead of a file name.
-
-```javascript
-function Server([serverOptions])
-```
-
-Constructs a server to which service/implementation pairs can be added.
-
-
-```javascript
-status
-```
-
-An object mapping status names to status code numbers.
-
-
-```javascript
-callError
-```
-
-An object mapping call error names to codes. This is primarily useful for tracking down certain kinds of internal errors.
-
-
-```javascript
-Credentials
-```
-
-An object with factory methods for creating credential objects for clients.
-
-
-```javascript
-ServerCredentials
-```
-
-An object with factory methods for creating credential objects for servers.
diff --git a/src/node/index.js b/src/node/index.js
index 2da77c3eaee06a0315fb22e045d39c53d0dd49a3..177628e22d49f4fbcdee2bb9268f913b06467979 100644
--- a/src/node/index.js
+++ b/src/node/index.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2015, Google Inc.
  * All rights reserved.
  *
@@ -31,10 +31,6 @@
  *
  */
 
-/**
- * @module
- */
-
 'use strict';
 
 var path = require('path');
@@ -64,24 +60,30 @@ var constants = require('./src/constants.js');
 grpc.setDefaultRootsPem(fs.readFileSync(SSL_ROOTS_PATH, 'ascii'));
 
 /**
- * Load a ProtoBuf.js object as a gRPC object. The options object can provide
- * the following options:
- * - binaryAsBase64: deserialize bytes values as base64 strings instead of
- *   Buffers. Defaults to false
- * - longsAsStrings: deserialize long values as strings instead of objects.
- *   Defaults to true
- * - deprecatedArgumentOrder: Use the beta method argument order for client
- *   methods, with optional arguments after the callback. Defaults to false.
- *   This option is only a temporary stopgap measure to smooth an API breakage.
- *   It is deprecated, and new code should not use it.
- * - protobufjsVersion: Available values are 5, 6, and 'detect'. 5 and 6
- *   respectively indicate that an object from the corresponding version of
- *   ProtoBuf.js is provided in the value argument. If the option is 'detect',
- *   gRPC will guess what the version is based on the structure of the value.
- *   Defaults to 'detect'.
+ * @namespace grpc
+ */
+
+/**
+ * Load a ProtoBuf.js object as a gRPC object.
+ * @memberof grpc
+ * @alias grpc.loadObject
  * @param {Object} value The ProtoBuf.js reflection object to load
  * @param {Object=} options Options to apply to the loaded file
- * @return {Object<string, *>} The resulting gRPC object
+ * @param {bool=} [options.binaryAsBase64=false] deserialize bytes values as
+ *     base64 strings instead of Buffers
+ * @param {bool=} [options.longsAsStrings=true] deserialize long values as
+ *     strings instead of objects
+ * @param {bool=} [options.enumsAsStrings=true] deserialize enum values as
+ *     strings instead of numbers. Only works with Protobuf.js 6 values.
+ * @param {bool=} [options.deprecatedArgumentOrder=false] use the beta method
+ *     argument order for client methods, with optional arguments after the
+ *     callback. This option is only a temporary stopgap measure to smooth an
+ *     API breakage. It is deprecated, and new code should not use it.
+ * @param {(number|string)=} [options.protobufjsVersion='detect'] 5 and 6
+ *     respectively indicate that an object from the corresponding version of
+ *     Protobuf.js is provided in the value argument. If the option is 'detect',
+ *     gRPC wll guess what the version is based on the structure of the value.
+ * @return {Object<string, *>} The resulting gRPC object.
  */
 exports.loadObject = function loadObject(value, options) {
   options = _.defaults(options, common.defaultGrpcOptions);
@@ -112,22 +114,23 @@ exports.loadObject = function loadObject(value, options) {
 var loadObject = exports.loadObject;
 
 /**
- * Load a gRPC object from a .proto file. The options object can provide the
- * following options:
- * - convertFieldsToCamelCase: Load this file with field names in camel case
- *   instead of their original case
- * - binaryAsBase64: deserialize bytes values as base64 strings instead of
- *   Buffers. Defaults to false
- * - longsAsStrings: deserialize long values as strings instead of objects.
- *   Defaults to true
- * - deprecatedArgumentOrder: Use the beta method argument order for client
- *   methods, with optional arguments after the callback. Defaults to false.
- *   This option is only a temporary stopgap measure to smooth an API breakage.
- *   It is deprecated, and new code should not use it.
+ * Load a gRPC object from a .proto file.
+ * @memberof grpc
+ * @alias grpc.load
  * @param {string|{root: string, file: string}} filename The file to load
  * @param {string=} format The file format to expect. Must be either 'proto' or
  *     'json'. Defaults to 'proto'
  * @param {Object=} options Options to apply to the loaded file
+ * @param {bool=} [options.convertFieldsToCamelCase=false] Load this file with
+ *     field names in camel case instead of their original case
+ * @param {bool=} [options.binaryAsBase64=false] deserialize bytes values as
+ *     base64 strings instead of Buffers
+ * @param {bool=} [options.longsAsStrings=true] deserialize long values as
+ *     strings instead of objects
+ * @param {bool=} [options.deprecatedArgumentOrder=false] use the beta method
+ *     argument order for client methods, with optional arguments after the
+ *     callback. This option is only a temporary stopgap measure to smooth an
+ *     API breakage. It is deprecated, and new code should not use it.
  * @return {Object<string, *>} The resulting gRPC object
  */
 exports.load = function load(filename, format, options) {
@@ -168,6 +171,8 @@ var log_template = _.template(
  * called. Note: the output format here is intended to be informational, and
  * is not guaranteed to stay the same in the future.
  * Logs will be directed to logger.error.
+ * @memberof grpc
+ * @alias grpc.setLogger
  * @param {Console} logger A Console-like object.
  */
 exports.setLogger = function setLogger(logger) {
@@ -187,6 +192,8 @@ exports.setLogger = function setLogger(logger) {
 /**
  * Sets the logger verbosity for gRPC module logging. The options are members
  * of the grpc.logVerbosity map.
+ * @memberof grpc
+ * @alias grpc.setLogVerbosity
  * @param {Number} verbosity The minimum severity to log
  */
 exports.setLogVerbosity = function setLogVerbosity(verbosity) {
@@ -194,71 +201,70 @@ exports.setLogVerbosity = function setLogVerbosity(verbosity) {
   grpc.setLogVerbosity(verbosity);
 };
 
-/**
- * @see module:src/server.Server
- */
 exports.Server = server.Server;
 
-/**
- * @see module:src/metadata
- */
 exports.Metadata = Metadata;
 
-/**
- * Status name to code number mapping
- */
 exports.status = constants.status;
 
-/**
- * Propagate flag name to number mapping
- */
 exports.propagate = constants.propagate;
 
-/**
- * Call error name to code number mapping
- */
 exports.callError = constants.callError;
 
-/**
- * Write flag name to code number mapping
- */
 exports.writeFlags = constants.writeFlags;
 
-/**
- * Log verbosity setting name to code number mapping
- */
 exports.logVerbosity = constants.logVerbosity;
 
-/**
- * Credentials factories
- */
 exports.credentials = require('./src/credentials.js');
 
 /**
  * ServerCredentials factories
+ * @constructor ServerCredentials
+ * @memberof grpc
  */
 exports.ServerCredentials = grpc.ServerCredentials;
 
 /**
- * @see module:src/client.makeClientConstructor
+ * Create insecure server credentials
+ * @name grpc.ServerCredentials.createInsecure
+ * @kind function
+ * @return grpc.ServerCredentials
  */
-exports.makeGenericClientConstructor = client.makeClientConstructor;
 
 /**
- * @see module:src/client.getClientChannel
+ * A private key and certificate pair
+ * @typedef {Object} grpc.ServerCredentials~keyCertPair
+ * @property {Buffer} privateKey The server's private key
+ * @property {Buffer} certChain The server's certificate chain
  */
-exports.getClientChannel = client.getClientChannel;
 
 /**
- * @see module:src/client.waitForClientReady
+ * Create SSL server credentials
+ * @name grpc.ServerCredentials.createInsecure
+ * @kind function
+ * @param {?Buffer} rootCerts Root CA certificates for validating client
+ *     certificates
+ * @param {Array<grpc.ServerCredentials~keyCertPair>} keyCertPairs A list of
+ *     private key and certificate chain pairs to be used for authenticating
+ *     the server
+ * @param {boolean} [checkClientCertificate=false] Indicates that the server
+ *     should request and verify the client's certificates
+ * @return grpc.ServerCredentials
  */
+
+exports.makeGenericClientConstructor = client.makeClientConstructor;
+
+exports.getClientChannel = client.getClientChannel;
+
 exports.waitForClientReady = client.waitForClientReady;
 
+/**
+ * @memberof grpc
+ * @alias grpc.closeClient
+ * @param {grpc.Client} client_obj The client to close
+ */
 exports.closeClient = function closeClient(client_obj) {
   client.Client.prototype.close.apply(client_obj);
 };
 
-/**
- * @see module:src/client.Client
- */
 exports.Client = client.Client;
diff --git a/src/node/src/client.js b/src/node/src/client.js
index 16fe06a54d2c7c0bcfc62fdb01a22cdf71d00f64..cf4c104144c50bfb03b3f067c333a491d5c3da82 100644
--- a/src/node/src/client.js
+++ b/src/node/src/client.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2015, Google Inc.
  * All rights reserved.
  *
@@ -43,8 +43,6 @@
  * var Client = proto_obj.package.subpackage.ServiceName;
  * var client = new Client(server_address, client_credentials);
  * var call = client.unaryMethod(arguments, callback);
- *
- * @module
  */
 
 'use strict';
@@ -70,13 +68,26 @@ var Duplex = stream.Duplex;
 var util = require('util');
 var version = require('../../../package.json').version;
 
+/**
+ * Initial response metadata sent by the server when it starts processing the
+ * call
+ * @event grpc~ClientUnaryCall#metadata
+ * @type {grpc.Metadata}
+ */
+
+/**
+ * Status of the call when it has completed.
+ * @event grpc~ClientUnaryCall#status
+ * @type grpc~StatusObject
+ */
+
 util.inherits(ClientUnaryCall, EventEmitter);
 
 /**
- * An EventEmitter. Used for unary calls
- * @constructor
+ * An EventEmitter. Used for unary calls.
+ * @constructor grpc~ClientUnaryCall
  * @extends external:EventEmitter
- * @param {grpc.Call} call The call object associated with the request
+ * @param {grpc.internal~Call} call The call object associated with the request
  */
 function ClientUnaryCall(call) {
   EventEmitter.call(this);
@@ -88,14 +99,16 @@ util.inherits(ClientWritableStream, Writable);
 /**
  * A stream that the client can write to. Used for calls that are streaming from
  * the client side.
- * @constructor
+ * @constructor grpc~ClientWritableStream
  * @extends external:Writable
- * @borrows module:src/client~ClientUnaryCall#cancel as
- *     module:src/client~ClientWritableStream#cancel
- * @borrows module:src/client~ClientUnaryCall#getPeer as
- *     module:src/client~ClientWritableStream#getPeer
- * @param {grpc.Call} call The call object to send data with
- * @param {module:src/common~serialize=} [serialize=identity] Serialization
+ * @borrows grpc~ClientUnaryCall#cancel as grpc~ClientWritableStream#cancel
+ * @borrows grpc~ClientUnaryCall#getPeer as grpc~ClientWritableStream#getPeer
+ * @borrows grpc~ClientUnaryCall#event:metadata as
+ *     grpc~ClientWritableStream#metadata
+ * @borrows grpc~ClientUnaryCall#event:status as
+ *     grpc~ClientWritableStream#status
+ * @param {grpc.internal~Call} call The call object to send data with
+ * @param {grpc~serialize=} [serialize=identity] Serialization
  *     function for writes.
  */
 function ClientWritableStream(call, serialize) {
@@ -109,12 +122,25 @@ function ClientWritableStream(call, serialize) {
   });
 }
 
+/**
+ * Write a message to the request stream. If serializing the argument fails,
+ * the call will be cancelled and the stream will end with an error.
+ * @name grpc~ClientWritableStream#write
+ * @kind function
+ * @override
+ * @param {*} message The message to write. Must be a valid argument to the
+ *     serialize function of the corresponding method
+ * @param {grpc.writeFlags} flags Flags to modify how the message is written
+ * @param {Function} callback Callback for when this chunk of data is flushed
+ * @return {boolean} As defined for [Writable]{@link external:Writable}
+ */
+
 /**
  * Attempt to write the given chunk. Calls the callback when done. This is an
  * implementation of a method needed for implementing stream.Writable.
- * @access private
- * @param {Buffer} chunk The chunk to write
- * @param {string} encoding Used to pass write flags
+ * @private
+ * @param {*} chunk The chunk to write
+ * @param {grpc.writeFlags} encoding Used to pass write flags
  * @param {function(Error=)} callback Called when the write is complete
  */
 function _write(chunk, encoding, callback) {
@@ -155,14 +181,16 @@ util.inherits(ClientReadableStream, Readable);
 /**
  * A stream that the client can read from. Used for calls that are streaming
  * from the server side.
- * @constructor
+ * @constructor grpc~ClientReadableStream
  * @extends external:Readable
- * @borrows module:src/client~ClientUnaryCall#cancel as
- *     module:src/client~ClientReadableStream#cancel
- * @borrows module:src/client~ClientUnaryCall#getPeer as
- *     module:src/client~ClientReadableStream#getPeer
- * @param {grpc.Call} call The call object to read data with
- * @param {module:src/common~deserialize=} [deserialize=identity]
+ * @borrows grpc~ClientUnaryCall#cancel as grpc~ClientReadableStream#cancel
+ * @borrows grpc~ClientUnaryCall#getPeer as grpc~ClientReadableStream#getPeer
+ * @borrows grpc~ClientUnaryCall#event:metadata as
+ *     grpc~ClientReadableStream#metadata
+ * @borrows grpc~ClientUnaryCall#event:status as
+ *     grpc~ClientReadableStream#status
+ * @param {grpc.internal~Call} call The call object to read data with
+ * @param {grpc~deserialize=} [deserialize=identity]
  *     Deserialization function for reads
  */
 function ClientReadableStream(call, deserialize) {
@@ -183,7 +211,7 @@ function ClientReadableStream(call, deserialize) {
  * parameter indicates that the call should end with that status. status
  * defaults to OK if not provided.
  * @param {Object!} status The status that the call should end with
- * @access private
+ * @private
  */
 function _readsDone(status) {
   /* jshint validthis: true */
@@ -202,7 +230,7 @@ ClientReadableStream.prototype._readsDone = _readsDone;
 
 /**
  * Called to indicate that we have received a status from the server.
- * @access private
+ * @private
  */
 function _receiveStatus(status) {
   /* jshint validthis: true */
@@ -215,7 +243,7 @@ ClientReadableStream.prototype._receiveStatus = _receiveStatus;
 /**
  * If we have both processed all incoming messages and received the status from
  * the server, emit the status. Otherwise, do nothing.
- * @access private
+ * @private
  */
 function _emitStatusIfDone() {
   /* jshint validthis: true */
@@ -242,7 +270,7 @@ ClientReadableStream.prototype._emitStatusIfDone = _emitStatusIfDone;
 
 /**
  * Read the next object from the stream.
- * @access private
+ * @private
  * @param {*} size Ignored because we use objectMode=true
  */
 function _read(size) {
@@ -300,16 +328,19 @@ util.inherits(ClientDuplexStream, Duplex);
 /**
  * A stream that the client can read from or write to. Used for calls with
  * duplex streaming.
- * @constructor
+ * @constructor grpc~ClientDuplexStream
  * @extends external:Duplex
- * @borrows module:src/client~ClientUnaryCall#cancel as
- *     module:src/client~ClientDuplexStream#cancel
- * @borrows module:src/client~ClientUnaryCall#getPeer as
- *     module:src/client~ClientDuplexStream#getPeer
- * @param {grpc.Call} call Call object to proxy
- * @param {module:src/common~serialize=} [serialize=identity] Serialization
+ * @borrows grpc~ClientUnaryCall#cancel as grpc~ClientDuplexStream#cancel
+ * @borrows grpc~ClientUnaryCall#getPeer as grpc~ClientDuplexStream#getPeer
+ * @borrows grpc~ClientWritableStream#write as grpc~ClientDuplexStream#write
+ * @borrows grpc~ClientUnaryCall#event:metadata as
+ *     grpc~ClientDuplexStream#metadata
+ * @borrows grpc~ClientUnaryCall#event:status as
+ *     grpc~ClientDuplexStream#status
+ * @param {grpc.internal~Call} call Call object to proxy
+ * @param {grpc~serialize=} [serialize=identity] Serialization
  *     function for requests
- * @param {module:src/common~deserialize=} [deserialize=identity]
+ * @param {grpc~deserialize=} [deserialize=identity]
  *     Deserialization function for responses
  */
 function ClientDuplexStream(call, serialize, deserialize) {
@@ -336,8 +367,9 @@ ClientDuplexStream.prototype._read = _read;
 ClientDuplexStream.prototype._write = _write;
 
 /**
- * Cancel the ongoing call
- * @alias module:src/client~ClientUnaryCall#cancel
+ * Cancel the ongoing call. Results in the call ending with a CANCELLED status,
+ * unless it has already ended with some other status.
+ * @alias grpc~ClientUnaryCall#cancel
  */
 function cancel() {
   /* jshint validthis: true */
@@ -352,7 +384,7 @@ ClientDuplexStream.prototype.cancel = cancel;
 /**
  * Get the endpoint this call/stream is connected to.
  * @return {string} The URI of the endpoint
- * @alias module:src/client~ClientUnaryCall#getPeer
+ * @alias grpc~ClientUnaryCall#getPeer
  */
 function getPeer() {
   /* jshint validthis: true */
@@ -368,33 +400,31 @@ ClientDuplexStream.prototype.getPeer = getPeer;
  * Any client call type
  * @typedef {(ClientUnaryCall|ClientReadableStream|
  *            ClientWritableStream|ClientDuplexStream)}
- *     module:src/client~Call
+ *     grpc.Client~Call
  */
 
 /**
  * Options that can be set on a call.
- * @typedef {Object} module:src/client~CallOptions
- * @property {(date|number)} deadline The deadline for the entire call to
- *     complete. A value of Infinity indicates that no deadline should be set.
- * @property {(string)} host Server hostname to set on the call. Only meaningful
+ * @typedef {Object} grpc.Client~CallOptions
+ * @property {grpc~Deadline} deadline The deadline for the entire call to
+ *     complete.
+ * @property {string} host Server hostname to set on the call. Only meaningful
  *     if different from the server address used to construct the client.
- * @property {module:src/client~Call} parent Parent call. Used in servers when
+ * @property {grpc.Client~Call} parent Parent call. Used in servers when
  *     making a call as part of the process of handling a call. Used to
  *     propagate some information automatically, as specified by
  *     propagate_flags.
  * @property {number} propagate_flags Indicates which properties of a parent
  *     call should propagate to this call. Bitwise combination of flags in
- *     [grpc.propagate]{@link module:index.propagate}.
- * @property {module:src/credentials~CallCredentials} credentials The
- *     credentials that should be used to make this particular call.
+ *     {@link grpc.propagate}.
+ * @property {grpc.credentials~CallCredentials} credentials The credentials that
+ *     should be used to make this particular call.
  */
 
 /**
- * Get a call object built with the provided options. Keys for options are
- * 'deadline', which takes a date or number, and 'host', which takes a string
- * and overrides the hostname to connect to.
+ * Get a call object built with the provided options.
  * @access private
- * @param {module:src/client~CallOptions=} options Options object.
+ * @param {grpc.Client~CallOptions=} options Options object.
  */
 function getCall(channel, method, options) {
   var deadline;
@@ -422,14 +452,14 @@ function getCall(channel, method, options) {
 
 /**
  * A generic gRPC client. Primarily useful as a base class for generated clients
- * @alias module:src/client.Client
+ * @memberof grpc
  * @constructor
  * @param {string} address Server address to connect to
- * @param {module:src/credentials~ChannelCredentials} credentials Credentials to
- *     use to connect to the server
+ * @param {grpc~ChannelCredentials} credentials Credentials to use to connect to
+ *     the server
  * @param {Object} options Options to apply to channel creation
  */
-var Client = exports.Client = function Client(address, credentials, options) {
+function Client(address, credentials, options) {
   if (!options) {
     options = {};
   }
@@ -445,19 +475,13 @@ var Client = exports.Client = function Client(address, credentials, options) {
   /* Private fields use $ as a prefix instead of _ because it is an invalid
    * prefix of a method name */
   this.$channel = new grpc.Channel(address, credentials, options);
-};
+}
 
-/**
- * @typedef {Error} module:src/client.Client~ServiceError
- * @property {number} code The error code, a key of
- *     [grpc.status]{@link module:src/client.status}
- * @property {module:metadata.Metadata} metadata Metadata sent with the status
- *     by the server, if any
- */
+exports.Client = Client;
 
 /**
- * @callback module:src/client.Client~requestCallback
- * @param {?module:src/client.Client~ServiceError} error The error, if the call
+ * @callback grpc.Client~requestCallback
+ * @param {?grpc~ServiceError} error The error, if the call
  *     failed
  * @param {*} value The response value, if the call succeeded
  */
@@ -466,17 +490,17 @@ var Client = exports.Client = function Client(address, credentials, options) {
  * Make a unary request to the given method, using the given serialize
  * and deserialize functions, with the given argument.
  * @param {string} method The name of the method to request
- * @param {module:src/common~serialize} serialize The serialization function for
+ * @param {grpc~serialize} serialize The serialization function for
  *     inputs
- * @param {module:src/common~deserialize} deserialize The deserialization
+ * @param {grpc~deserialize} deserialize The deserialization
  *     function for outputs
  * @param {*} argument The argument to the call. Should be serializable with
  *     serialize
- * @param {module:src/metadata.Metadata=} metadata Metadata to add to the call
- * @param {module:src/client~CallOptions=} options Options map
- * @param {module:src/client.Client~requestCallback} callback The callback to
+ * @param {grpc.Metadata=} metadata Metadata to add to the call
+ * @param {grpc.Client~CallOptions=} options Options map
+ * @param {grpc.Client~requestCallback} callback The callback to
  *     for when the response is received
- * @return {EventEmitter} An event emitter for stream related events
+ * @return {grpc~ClientUnaryCall} An event emitter for stream related events
  */
 Client.prototype.makeUnaryRequest = function(method, serialize, deserialize,
                                              argument, metadata, options,
@@ -548,17 +572,17 @@ Client.prototype.makeUnaryRequest = function(method, serialize, deserialize,
  * Make a client stream request to the given method, using the given serialize
  * and deserialize functions, with the given argument.
  * @param {string} method The name of the method to request
- * @param {module:src/common~serialize} serialize The serialization function for
+ * @param {grpc~serialize} serialize The serialization function for
  *     inputs
- * @param {module:src/common~deserialize} deserialize The deserialization
+ * @param {grpc~deserialize} deserialize The deserialization
  *     function for outputs
- * @param {module:src/metadata.Metadata=} metadata Array of metadata key/value
- *     pairs to add to the call
- * @param {module:src/client~CallOptions=} options Options map
- * @param {Client~requestCallback} callback The callback to for when the
+ * @param {grpc.Metadata=} metadata Array of metadata key/value pairs to add to
+ *     the call
+ * @param {grpc.Client~CallOptions=} options Options map
+ * @param {grpc.Client~requestCallback} callback The callback to for when the
  *     response is received
- * @return {module:src/client~ClientWritableStream} An event emitter for stream
- *     related events
+ * @return {grpc~ClientWritableStream} An event emitter for stream related
+ *     events
  */
 Client.prototype.makeClientStreamRequest = function(method, serialize,
                                                       deserialize, metadata,
@@ -631,17 +655,16 @@ Client.prototype.makeClientStreamRequest = function(method, serialize,
  * Make a server stream request to the given method, with the given serialize
  * and deserialize function, using the given argument
  * @param {string} method The name of the method to request
- * @param {module:src/common~serialize} serialize The serialization function for
- *     inputs
- * @param {module:src/common~deserialize} deserialize The deserialization
+ * @param {grpc~serialize} serialize The serialization function for inputs
+ * @param {grpc~deserialize} deserialize The deserialization
  *     function for outputs
  * @param {*} argument The argument to the call. Should be serializable with
  *     serialize
- * @param {module:src/metadata.Metadata=} metadata Array of metadata key/value
- *     pairs to add to the call
- * @param {module:src/client~CallOptions=} options Options map
- * @return {module:src/client~ClientReadableStream} An event emitter for stream
- *     related events
+ * @param {grpc.Metadata=} metadata Array of metadata key/value pairs to add to
+ *     the call
+ * @param {grpc.Client~CallOptions=} options Options map
+ * @return {grpc~ClientReadableStream} An event emitter for stream related
+ *     events
  */
 Client.prototype.makeServerStreamRequest = function(method, serialize,
                                                     deserialize, argument,
@@ -693,15 +716,13 @@ Client.prototype.makeServerStreamRequest = function(method, serialize,
 /**
  * Make a bidirectional stream request with this method on the given channel.
  * @param {string} method The name of the method to request
- * @param {module:src/common~serialize} serialize The serialization function for
- *     inputs
- * @param {module:src/common~deserialize} deserialize The deserialization
+ * @param {grpc~serialize} serialize The serialization function for inputs
+ * @param {grpc~deserialize} deserialize The deserialization
  *     function for outputs
- * @param {module:src/metadata.Metadata=} metadata Array of metadata key/value
+ * @param {grpc.Metadata=} metadata Array of metadata key/value
  *     pairs to add to the call
- * @param {module:src/client~CallOptions=} options Options map
- * @return {module:src/client~ClientDuplexStream} An event emitter for stream
- *     related events
+ * @param {grpc.Client~CallOptions=} options Options map
+ * @return {grpc~ClientDuplexStream} An event emitter for stream related events
  */
 Client.prototype.makeBidiStreamRequest = function(method, serialize,
                                                   deserialize, metadata,
@@ -743,6 +764,9 @@ Client.prototype.makeBidiStreamRequest = function(method, serialize,
   return stream;
 };
 
+/**
+ * Close this client.
+ */
 Client.prototype.close = function() {
   this.$channel.close();
 };
@@ -761,8 +785,7 @@ Client.prototype.getChannel = function() {
  * with an error if the attempt to connect to the server has unrecoverablly
  * failed or if the deadline expires. This function will make the channel
  * start connecting if it has not already done so.
- * @param {(Date|Number)} deadline When to stop waiting for a connection. Pass
- *     Infinity to wait forever.
+ * @param {grpc~Deadline} deadline When to stop waiting for a connection.
  * @param {function(Error)} callback The callback to call when done attempting
  *     to connect.
  */
@@ -788,7 +811,7 @@ Client.prototype.waitForReady = function(deadline, callback) {
 /**
  * Map with short names for each of the requester maker functions. Used in
  * makeClientConstructor
- * @access private
+ * @private
  */
 var requester_funcs = {
   unary: Client.prototype.makeUnaryRequest,
@@ -834,9 +857,15 @@ var deprecated_request_wrap = {
 
 /**
  * Creates a constructor for a client with the given methods, as specified in
- * the methods argument.
- * @param {module:src/common~ServiceDefinition} methods An object mapping
- *     method names to method attributes
+ * the methods argument. The resulting class will have an instance method for
+ * each method in the service, which is a partial application of one of the
+ * [Client]{@link grpc.Client} request methods, depending on `requestSerialize`
+ * and `responseSerialize`, with the `method`, `serialize`, and `deserialize`
+ * arguments predefined.
+ * @memberof grpc
+ * @alias grpc~makeGenericClientConstructor
+ * @param {grpc~ServiceDefinition} methods An object mapping method names to
+ *     method attributes
  * @param {string} serviceName The fully qualified name of the service
  * @param {Object} class_options An options object.
  * @param {boolean=} [class_options.deprecatedArgumentOrder=false] Indicates
@@ -844,9 +873,8 @@ var deprecated_request_wrap = {
  *     arguments at the end instead of the callback at the end. This option
  *     is only a temporary stopgap measure to smooth an API breakage.
  *     It is deprecated, and new code should not use it.
- * @return {function(string, Object)} New client constructor, which is a
- *     subclass of [grpc.Client]{@link module:src/client.Client}, and has the
- *     same arguments as that constructor.
+ * @return {function} New client constructor, which is a subclass of
+ *     {@link grpc.Client}, and has the same arguments as that constructor.
  */
 exports.makeClientConstructor = function(methods, serviceName,
                                          class_options) {
@@ -898,8 +926,11 @@ exports.makeClientConstructor = function(methods, serviceName,
 
 /**
  * Return the underlying channel object for the specified client
+ * @memberof grpc
+ * @alias grpc~getClientChannel
  * @param {Client} client
  * @return {Channel} The channel
+ * @see grpc.Client#getChannel
  */
 exports.getClientChannel = function(client) {
   return Client.prototype.getChannel.call(client);
@@ -911,22 +942,15 @@ exports.getClientChannel = function(client) {
  * with an error if the attempt to connect to the server has unrecoverablly
  * failed or if the deadline expires. This function will make the channel
  * start connecting if it has not already done so.
+ * @memberof grpc
+ * @alias grpc~waitForClientReady
  * @param {Client} client The client to wait on
- * @param {(Date|Number)} deadline When to stop waiting for a connection. Pass
+ * @param {grpc~Deadline} deadline When to stop waiting for a connection. Pass
  *     Infinity to wait forever.
  * @param {function(Error)} callback The callback to call when done attempting
  *     to connect.
+ * @see grpc.Client#waitForReady
  */
 exports.waitForClientReady = function(client, deadline, callback) {
   Client.prototype.waitForReady.call(client, deadline, callback);
 };
-
-/**
- * Map of status code names to status codes
- */
-exports.status = constants.status;
-
-/**
- * See docs for client.callError
- */
-exports.callError = grpc.callError;
diff --git a/src/node/src/common.js b/src/node/src/common.js
index 4dad60e630f7476fd4e22174c9865425dfc4b2ab..0f835317ea6e042599fbfdacfc4164432b02d77c 100644
--- a/src/node/src/common.js
+++ b/src/node/src/common.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2015, Google Inc.
  * All rights reserved.
  *
@@ -31,12 +31,6 @@
  *
  */
 
-/**
- * This module contains functions that are common to client and server
- * code. None of them should be used directly by gRPC users.
- * @module
- */
-
 'use strict';
 
 var _ = require('lodash');
@@ -62,16 +56,19 @@ exports.wrapIgnoreNull = function wrapIgnoreNull(func) {
 
 /**
  * The logger object for the gRPC module. Defaults to console.
+ * @private
  */
 exports.logger = console;
 
 /**
  * The current logging verbosity. 0 corresponds to logging everything
+ * @private
  */
 exports.logVerbosity = 0;
 
 /**
  * Log a message if the severity is at least as high as the current verbosity
+ * @private
  * @param {Number} severity A value of the grpc.logVerbosity map
  * @param {String} message The message to log
  */
@@ -83,6 +80,7 @@ exports.log = function log(severity, message) {
 
 /**
  * Default options for loading proto files into gRPC
+ * @alias grpc~defaultLoadOptions
  */
 exports.defaultGrpcOptions = {
   convertFieldsToCamelCase: false,
@@ -94,6 +92,30 @@ exports.defaultGrpcOptions = {
 
 // JSDoc definitions that are used in multiple other modules
 
+/**
+ * Represents the status of a completed request. If `code` is
+ * {@link grpc.status}.OK, then the request has completed successfully.
+ * Otherwise, the request has failed, `details` will contain a description of
+ * the error. Either way, `metadata` contains the trailing response metadata
+ * sent by the server when it finishes processing the call.
+ * @typedef {object} grpc~StatusObject
+ * @property {number} code The error code, a key of {@link grpc.status}
+ * @property {string} details Human-readable description of the status
+ * @property {grpc.Metadata} metadata Trailing metadata sent with the status,
+ *     if applicable
+ */
+
+/**
+ * Describes how a request has failed. The member `message` will be the same as
+ * `details` in {@link grpc~StatusObject}, and `code` and `metadata` are the
+ * same as in that object.
+ * @typedef {Error} grpc~ServiceError
+ * @property {number} code The error code, a key of {@link grpc.status} that is
+ *     not `grpc.status.OK`
+ * @property {grpc.Metadata} metadata Trailing metadata sent with the status,
+ *     if applicable
+ */
+
 /**
  * The EventEmitter class in the event standard module
  * @external EventEmitter
@@ -120,38 +142,46 @@ exports.defaultGrpcOptions = {
 
 /**
  * A serialization function
- * @callback module:src/common~serialize
+ * @callback grpc~serialize
  * @param {*} value The value to serialize
  * @return {Buffer} The value serialized as a byte sequence
  */
 
 /**
  * A deserialization function
- * @callback module:src/common~deserialize
+ * @callback grpc~deserialize
  * @param {Buffer} data The byte sequence to deserialize
  * @return {*} The data deserialized as a value
  */
 
+/**
+ * The deadline of an operation. If it is a date, the deadline is reached at
+ * the date and time specified. If it is a finite number, it is treated as
+ * a number of milliseconds since the Unix Epoch. If it is Infinity, the
+ * deadline will never be reached. If it is -Infinity, the deadline has already
+ * passed.
+ * @typedef {(number|date)} grpc~Deadline
+ */
+
 /**
  * An object that completely defines a service method signature.
- * @typedef {Object} module:src/common~MethodDefinition
+ * @typedef {Object} grpc~MethodDefinition
  * @property {string} path The method's URL path
  * @property {boolean} requestStream Indicates whether the method accepts
  *     a stream of requests
  * @property {boolean} responseStream Indicates whether the method returns
  *     a stream of responses
- * @property {module:src/common~serialize} requestSerialize Serialization
+ * @property {grpc~serialize} requestSerialize Serialization
  *     function for request values
- * @property {module:src/common~serialize} responseSerialize Serialization
+ * @property {grpc~serialize} responseSerialize Serialization
  *     function for response values
- * @property {module:src/common~deserialize} requestDeserialize Deserialization
+ * @property {grpc~deserialize} requestDeserialize Deserialization
  *     function for request data
- * @property {module:src/common~deserialize} responseDeserialize Deserialization
+ * @property {grpc~deserialize} responseDeserialize Deserialization
  *     function for repsonse data
  */
 
 /**
  * An object that completely defines a service.
- * @typedef {Object.<string, module:src/common~MethodDefinition>}
- *     module:src/common~ServiceDefinition
+ * @typedef {Object.<string, grpc~MethodDefinition>} grpc~ServiceDefinition
  */
diff --git a/src/node/src/constants.js b/src/node/src/constants.js
index 528dab120e0ee1fb4098a2d60fed1cb3d3e63678..c441ee740b2b06c01d95bc9bf9623f26dcc7d246 100644
--- a/src/node/src/constants.js
+++ b/src/node/src/constants.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2017, Google Inc.
  * All rights reserved.
  *
@@ -31,16 +31,14 @@
  *
  */
 
-/**
- * @module
- */
-
 /* The comments about status codes are copied verbatim (with some formatting
  * modifications) from include/grpc/impl/codegen/status.h, for the purpose of
  * including them in generated documentation.
  */
 /**
  * Enum of status codes that gRPC can return
+ * @memberof grpc
+ * @alias grpc.status
  * @readonly
  * @enum {number}
  */
@@ -178,6 +176,8 @@ exports.status = {
  * Users are encouraged to write propagation masks as deltas from the default.
  * i.e. write `grpc.propagate.DEFAULTS & ~grpc.propagate.DEADLINE` to disable
  * deadline propagation.
+ * @memberof grpc
+ * @alias grpc.propagate
  * @enum {number}
  */
 exports.propagate = {
@@ -194,9 +194,11 @@ exports.propagate = {
 /**
  * Call error constants. Call errors almost always indicate bugs in the gRPC
  * library, and these error codes are mainly useful for finding those bugs.
+ * @memberof grpc
+ * @readonly
  * @enum {number}
  */
-exports.callError = {
+const callError = {
   OK: 0,
   ERROR: 1,
   NOT_ON_SERVER: 2,
@@ -213,9 +215,14 @@ exports.callError = {
   PAYLOAD_TYPE_MISMATCH: 14
 };
 
+exports.callError = callError;
+
 /**
  * Write flags: these can be bitwise or-ed to form write options that modify
  * how data is written.
+ * @memberof grpc
+ * @alias grpc.writeFlags
+ * @readonly
  * @enum {number}
  */
 exports.writeFlags = {
@@ -232,6 +239,9 @@ exports.writeFlags = {
 };
 
 /**
+ * @memberof grpc
+ * @alias grpc.logVerbosity
+ * @readonly
  * @enum {number}
  */
 exports.logVerbosity = {
diff --git a/src/node/src/credentials.js b/src/node/src/credentials.js
index b1e86bbd09024409b618d29bbdf70a60686c1156..b1dbc1c450b0999ab3ff4e812072eb0a50ecdf23 100644
--- a/src/node/src/credentials.js
+++ b/src/node/src/credentials.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2015, Google Inc.
  * All rights reserved.
  *
@@ -48,6 +48,7 @@
  * For example, to create a client secured with SSL that uses Google
  * default application credentials to authenticate:
  *
+ * @example
  * var channel_creds = credentials.createSsl(root_certs);
  * (new GoogleAuth()).getApplicationDefault(function(err, credential) {
  *   var call_creds = credentials.createFromGoogleCredential(credential);
@@ -56,15 +57,25 @@
  *   var client = new Client(address, combined_creds);
  * });
  *
- * @module
+ * @namespace grpc.credentials
  */
 
 'use strict';
 
 var grpc = require('./grpc_extension');
 
+/**
+ * This cannot be constructed directly. Instead, instances of this class should
+ * be created using the factory functions in {@link grpc.credentials}
+ * @constructor grpc.credentials~CallCredentials
+ */
 var CallCredentials = grpc.CallCredentials;
 
+/**
+ * This cannot be constructed directly. Instead, instances of this class should
+ * be created using the factory functions in {@link grpc.credentials}
+ * @constructor grpc.credentials~ChannelCredentials
+ */
 var ChannelCredentials = grpc.ChannelCredentials;
 
 var Metadata = require('./metadata.js');
@@ -75,25 +86,49 @@ var constants = require('./constants');
 
 var _ = require('lodash');
 
+/**
+ * @external GoogleCredential
+ * @see https://github.com/google/google-auth-library-nodejs
+ */
+
 /**
  * Create an SSL Credentials object. If using a client-side certificate, both
  * the second and third arguments must be passed.
+ * @memberof grpc.credentials
+ * @alias grpc.credentials.createSsl
+ * @kind function
  * @param {Buffer} root_certs The root certificate data
  * @param {Buffer=} private_key The client certificate private key, if
  *     applicable
  * @param {Buffer=} cert_chain The client certificate cert chain, if applicable
- * @return {ChannelCredentials} The SSL Credentials object
+ * @return {grpc.credentials.ChannelCredentials} The SSL Credentials object
  */
 exports.createSsl = ChannelCredentials.createSsl;
 
+/**
+ * @callback grpc.credentials~metadataCallback
+ * @param {Error} error The error, if getting metadata failed
+ * @param {grpc.Metadata} metadata The metadata
+ */
+
+/**
+ * @callback grpc.credentials~generateMetadata
+ * @param {Object} params Parameters that can modify metadata generation
+ * @param {string} params.service_url The URL of the service that the call is
+ *     going to
+ * @param {grpc.credentials~metadataCallback} callback
+ */
+
 /**
  * Create a gRPC credentials object from a metadata generation function. This
  * function gets the service URL and a callback as parameters. The error
  * passed to the callback can optionally have a 'code' value attached to it,
  * which corresponds to a status code that this library uses.
- * @param {function(String, function(Error, Metadata))} metadata_generator The
- *     function that generates metadata
- * @return {CallCredentials} The credentials object
+ * @memberof grpc.credentials
+ * @alias grpc.credentials.createFromMetadataGenerator
+ * @param {grpc.credentials~generateMetadata} metadata_generator The function
+ *     that generates metadata
+ * @return {grpc.credentials.CallCredentials} The credentials object
  */
 exports.createFromMetadataGenerator = function(metadata_generator) {
   return CallCredentials.createFromPlugin(function(service_url, cb_data,
@@ -119,8 +154,11 @@ exports.createFromMetadataGenerator = function(metadata_generator) {
 
 /**
  * Create a gRPC credential from a Google credential object.
- * @param {Object} google_credential The Google credential object to use
- * @return {CallCredentials} The resulting credentials object
+ * @memberof grpc.credentials
+ * @alias grpc.credentials.createFromGoogleCredential
+ * @param {external:GoogleCredential} google_credential The Google credential
+ *     object to use
+ * @return {grpc.credentials.CallCredentials} The resulting credentials object
  */
 exports.createFromGoogleCredential = function(google_credential) {
   return exports.createFromMetadataGenerator(function(auth_context, callback) {
@@ -141,6 +179,8 @@ exports.createFromGoogleCredential = function(google_credential) {
 /**
  * Combine a ChannelCredentials with any number of CallCredentials into a single
  * ChannelCredentials object.
+ * @memberof grpc.credentials
+ * @alias grpc.credentials.combineChannelCredentials
  * @param {ChannelCredentials} channel_credential The ChannelCredentials to
  *     start with
  * @param {...CallCredentials} credentials The CallCredentials to compose
@@ -157,6 +197,8 @@ exports.combineChannelCredentials = function(channel_credential) {
 
 /**
  * Combine any number of CallCredentials into a single CallCredentials object
+ * @memberof grpc.credentials
+ * @alias grpc.credentials.combineCallCredentials
  * @param {...CallCredentials} credentials the CallCredentials to compose
  * @return CallCredentials A credentials object that combines all of the input
  *     credentials
@@ -172,6 +214,9 @@ exports.combineCallCredentials = function() {
 /**
  * Create an insecure credentials object. This is used to create a channel that
  * does not use SSL. This cannot be composed with anything.
+ * @memberof grpc.credentials
+ * @alias grpc.credentials.createInsecure
+ * @kind function
  * @return {ChannelCredentials} The insecure credentials object
  */
 exports.createInsecure = ChannelCredentials.createInsecure;
diff --git a/src/node/src/grpc_extension.js b/src/node/src/grpc_extension.js
index 63a281ddbcff552ce4c5a3e43e4d87314e6ee943..864da973144bb566df3dd93b8957e9b059d8b42f 100644
--- a/src/node/src/grpc_extension.js
+++ b/src/node/src/grpc_extension.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2016, Google Inc.
  * All rights reserved.
  *
diff --git a/src/node/src/metadata.js b/src/node/src/metadata.js
index 92cf23998b38b8a4052746a0fd422e69c4276be5..c757d520f8d0d913c967a9860443a9d53a1b654b 100644
--- a/src/node/src/metadata.js
+++ b/src/node/src/metadata.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2015, Google Inc.
  * All rights reserved.
  *
@@ -31,15 +31,6 @@
  *
  */
 
-/**
- * Metadata module
- *
- * This module defines the Metadata class, which represents header and trailer
- * metadata for gRPC calls.
- *
- * @module
- */
-
 'use strict';
 
 var _ = require('lodash');
@@ -48,8 +39,8 @@ var grpc = require('./grpc_extension');
 
 /**
  * Class for storing metadata. Keys are normalized to lowercase ASCII.
+ * @memberof grpc
  * @constructor
- * @alias module:src/metadata.Metadata
  * @example
  * var metadata = new metadata_module.Metadata();
  * metadata.set('key1', 'value1');
diff --git a/src/node/src/protobuf_js_5_common.js b/src/node/src/protobuf_js_5_common.js
index 4041e05390f21388cab9426a55832bb5094d5190..1663a3a4005e929b96e0403e4ed87f669d0c0f07 100644
--- a/src/node/src/protobuf_js_5_common.js
+++ b/src/node/src/protobuf_js_5_common.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2017, Google Inc.
  * All rights reserved.
  *
@@ -31,6 +31,11 @@
  *
  */
 
+/**
+ * @module
+ * @private
+ */
+
 'use strict';
 
 var _ = require('lodash');
diff --git a/src/node/src/protobuf_js_6_common.js b/src/node/src/protobuf_js_6_common.js
index 00f11f27363857ea30dfeb2f567fcf955040783a..91a458aa20e2718f89fb88e47e9f2df105444c0a 100644
--- a/src/node/src/protobuf_js_6_common.js
+++ b/src/node/src/protobuf_js_6_common.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2017, Google Inc.
  * All rights reserved.
  *
@@ -31,6 +31,11 @@
  *
  */
 
+/**
+ * @module
+ * @private
+ */
+
 'use strict';
 
 var _ = require('lodash');
diff --git a/src/node/src/server.js b/src/node/src/server.js
index 1d9cc7d2c18c73dac459288657f2d2dd8178d69d..ae4fcb1dc456bd9dfa940539392a4d8df9c569c8 100644
--- a/src/node/src/server.js
+++ b/src/node/src/server.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2015, Google Inc.
  * All rights reserved.
  *
@@ -31,22 +31,6 @@
  *
  */
 
-/**
- * Server module
- *
- * This module contains all the server code for Node gRPC: both the Server
- * class itself and the method handler code for all types of methods.
- *
- * For example, to create a Server, add a service, and start it:
- *
- * var server = new server_module.Server();
- * server.addProtoService(protobuf_service_descriptor, service_implementation);
- * server.bind('address:port', server_credential);
- * server.start();
- *
- * @module
- */
-
 'use strict';
 
 var _ = require('lodash');
@@ -70,9 +54,9 @@ var EventEmitter = require('events').EventEmitter;
 
 /**
  * Handle an error on a call by sending it as a status
- * @access private
- * @param {grpc.Call} call The call to send the error on
- * @param {Object} error The error object
+ * @private
+ * @param {grpc.internal~Call} call The call to send the error on
+ * @param {(Object|Error)} error The error object
  */
 function handleError(call, error) {
   var statusMetadata = new Metadata();
@@ -104,14 +88,14 @@ function handleError(call, error) {
 
 /**
  * Send a response to a unary or client streaming call.
- * @access private
+ * @private
  * @param {grpc.Call} call The call to respond on
  * @param {*} value The value to respond with
- * @param {function(*):Buffer=} serialize Serialization function for the
+ * @param {grpc~serialize} serialize Serialization function for the
  *     response
- * @param {Metadata=} metadata Optional trailing metadata to send with status
- * @param {number=} flags Flags for modifying how the message is sent.
- *     Defaults to 0.
+ * @param {grpc.Metadata=} metadata Optional trailing metadata to send with
+ *     status
+ * @param {number=} [flags=0] Flags for modifying how the message is sent.
  */
 function sendUnaryResponse(call, value, serialize, metadata, flags) {
   var end_batch = {};
@@ -146,7 +130,7 @@ function sendUnaryResponse(call, value, serialize, metadata, flags) {
 /**
  * Initialize a writable stream. This is used for both the writable and duplex
  * stream constructors.
- * @access private
+ * @private
  * @param {Writable} stream The stream to set up
  * @param {function(*):Buffer=} Serialization function for responses
  */
@@ -225,9 +209,9 @@ function setUpWritable(stream, serialize) {
 /**
  * Initialize a readable stream. This is used for both the readable and duplex
  * stream constructors.
- * @access private
+ * @private
  * @param {Readable} stream The stream to initialize
- * @param {function(Buffer):*=} deserialize Deserialization function for
+ * @param {grpc~deserialize} deserialize Deserialization function for
  *     incoming data.
  */
 function setUpReadable(stream, deserialize) {
@@ -245,34 +229,88 @@ function setUpReadable(stream, deserialize) {
   });
 }
 
+/**
+ * Emitted when the call has been cancelled. After this has been emitted, the
+ * call's `cancelled` property will be set to `true`.
+ * @event grpc~ServerUnaryCall~cancelled
+ */
+
 util.inherits(ServerUnaryCall, EventEmitter);
 
-function ServerUnaryCall(call) {
+/**
+ * An EventEmitter. Used for unary calls.
+ * @constructor grpc~ServerUnaryCall
+ * @extends external:EventEmitter
+ * @param {grpc.internal~Call} call The call object associated with the request
+ * @param {grpc.Metadata} metadata The request metadata from the client
+ */
+function ServerUnaryCall(call, metadata) {
   EventEmitter.call(this);
   this.call = call;
+  /**
+   * Indicates if the call has been cancelled
+   * @member {boolean} grpc~ServerUnaryCall#cancelled
+   */
+  this.cancelled = false;
+  /**
+   * The request metadata from the client
+   * @member {grpc.Metadata} grpc~ServerUnaryCall#metadata
+   */
+  this.metadata = metadata;
+  /**
+   * The request message from the client
+   * @member {*} grpc~ServerUnaryCall#request
+   */
+  this.request = undefined;
 }
 
+/**
+ * Emitted when the call has been cancelled. After this has been emitted, the
+ * call's `cancelled` property will be set to `true`.
+ * @event grpc~ServerWritableStream~cancelled
+ */
+
 util.inherits(ServerWritableStream, Writable);
 
 /**
  * A stream that the server can write to. Used for calls that are streaming from
  * the server side.
- * @constructor
- * @param {grpc.Call} call The call object to send data with
- * @param {function(*):Buffer=} serialize Serialization function for writes
+ * @constructor grpc~ServerWritableStream
+ * @extends external:Writable
+ * @borrows grpc~ServerUnaryCall#sendMetadata as
+ *     grpc~ServerWritableStream#sendMetadata
+ * @borrows grpc~ServerUnaryCall#getPeer as grpc~ServerWritableStream#getPeer
+ * @param {grpc.internal~Call} call The call object to send data with
+ * @param {grpc.Metadata} metadata The request metadata from the client
+ * @param {grpc~serialize} serialize Serialization function for writes
  */
-function ServerWritableStream(call, serialize) {
+function ServerWritableStream(call, metadata, serialize) {
   Writable.call(this, {objectMode: true});
   this.call = call;
 
   this.finished = false;
   setUpWritable(this, serialize);
+  /**
+   * Indicates if the call has been cancelled
+   * @member {boolean} grpc~ServerWritableStream#cancelled
+   */
+  this.cancelled = false;
+  /**
+   * The request metadata from the client
+   * @member {grpc.Metadata} grpc~ServerWritableStream#metadata
+   */
+  this.metadata = metadata;
+  /**
+   * The request message from the client
+   * @member {*} grpc~ServerWritableStream#request
+   */
+  this.request = undefined;
 }
 
 /**
  * Start writing a chunk of data. This is an implementation of a method required
  * for implementing stream.Writable.
- * @access private
+ * @private
  * @param {Buffer} chunk The chunk of data to write
  * @param {string} encoding Used to pass write flags
  * @param {function(Error=)} callback Callback to indicate that the write is
@@ -312,19 +350,40 @@ function _write(chunk, encoding, callback) {
 
 ServerWritableStream.prototype._write = _write;
 
+/**
+ * Emitted when the call has been cancelled. After this has been emitted, the
+ * call's `cancelled` property will be set to `true`.
+ * @event grpc~ServerReadableStream~cancelled
+ */
+
 util.inherits(ServerReadableStream, Readable);
 
 /**
  * A stream that the server can read from. Used for calls that are streaming
  * from the client side.
- * @constructor
- * @param {grpc.Call} call The call object to read data with
- * @param {function(Buffer):*=} deserialize Deserialization function for reads
+ * @constructor grpc~ServerReadableStream
+ * @extends external:Readable
+ * @borrows grpc~ServerUnaryCall#sendMetadata as
+ *     grpc~ServerReadableStream#sendMetadata
+ * @borrows grpc~ServerUnaryCall#getPeer as grpc~ServerReadableStream#getPeer
+ * @param {grpc.internal~Call} call The call object to read data with
+ * @param {grpc.Metadata} metadata The request metadata from the client
+ * @param {grpc~deserialize} deserialize Deserialization function for reads
  */
-function ServerReadableStream(call, deserialize) {
+function ServerReadableStream(call, metadata, deserialize) {
   Readable.call(this, {objectMode: true});
   this.call = call;
   setUpReadable(this, deserialize);
+  /**
+   * Indicates if the call has been cancelled
+   * @member {boolean} grpc~ServerReadableStream#cancelled
+   */
+  this.cancelled = false;
+  /**
+   * The request metadata from the client
+   * @member {grpc.Metadata} grpc~ServerReadableStream#metadata
+   */
+  this.metadata = metadata;
 }
 
 /**
@@ -381,22 +440,43 @@ function _read(size) {
 
 ServerReadableStream.prototype._read = _read;
 
+/**
+ * Emitted when the call has been cancelled. After this has been emitted, the
+ * call's `cancelled` property will be set to `true`.
+ * @event grpc~ServerDuplexStream~cancelled
+ */
+
 util.inherits(ServerDuplexStream, Duplex);
 
 /**
  * A stream that the server can read from or write to. Used for calls with
  * duplex streaming.
- * @constructor
- * @param {grpc.Call} call Call object to proxy
- * @param {function(*):Buffer=} serialize Serialization function for requests
- * @param {function(Buffer):*=} deserialize Deserialization function for
+ * @constructor grpc~ServerDuplexStream
+ * @extends external:Duplex
+ * @borrows grpc~ServerUnaryCall#sendMetadata as
+ *     grpc~ServerDuplexStream#sendMetadata
+ * @borrows grpc~ServerUnaryCall#getPeer as grpc~ServerDuplexStream#getPeer
+ * @param {grpc.internal~Call} call Call object to proxy
+ * @param {grpc.Metadata} metadata The request metadata from the client
+ * @param {grpc~serialize} serialize Serialization function for requests
+ * @param {grpc~deserialize} deserialize Deserialization function for
  *     responses
  */
-function ServerDuplexStream(call, serialize, deserialize) {
+function ServerDuplexStream(call, metadata, serialize, deserialize) {
   Duplex.call(this, {objectMode: true});
   this.call = call;
   setUpWritable(this, serialize);
   setUpReadable(this, deserialize);
+  /**
+   * Indicates if the call has been cancelled
+   * @member {boolean} grpc~ServerReadableStream#cancelled
+   */
+  this.cancelled = false;
+  /**
+   * The request metadata from the client
+   * @member {grpc.Metadata} grpc~ServerReadableStream#metadata
+   */
+  this.metadata = metadata;
 }
 
 ServerDuplexStream.prototype._read = _read;
@@ -404,6 +484,7 @@ ServerDuplexStream.prototype._write = _write;
 
 /**
  * Send the initial metadata for a writable stream.
+ * @alias grpc~ServerUnaryCall#sendMetadata
  * @param {Metadata} responseMetadata Metadata to send
  */
 function sendMetadata(responseMetadata) {
@@ -430,6 +511,7 @@ ServerDuplexStream.prototype.sendMetadata = sendMetadata;
 
 /**
  * Get the endpoint this call/stream is connected to.
+ * @alias grpc~ServerUnaryCall#getPeer
  * @return {string} The URI of the endpoint
  */
 function getPeer() {
@@ -445,6 +527,7 @@ ServerDuplexStream.prototype.getPeer = getPeer;
 /**
  * Wait for the client to close, then emit a cancelled event if the client
  * cancelled.
+ * @private
  */
 function waitForCancel() {
   /* jshint validthis: true */
@@ -467,19 +550,42 @@ ServerReadableStream.prototype.waitForCancel = waitForCancel;
 ServerWritableStream.prototype.waitForCancel = waitForCancel;
 ServerDuplexStream.prototype.waitForCancel = waitForCancel;
 
+/**
+ * Callback function passed to server handlers that handle methods with unary
+ * responses.
+ * @callback grpc.Server~sendUnaryData
+ * @param {grpc~ServiceError} error An error, if the call failed
+ * @param {*} value The response value. Must be a valid argument to the
+ *     `responseSerialize` method of the method that is being handled
+ * @param {grpc.Metadata=} trailer Trailing metadata to send, if applicable
+ * @param {grpc.writeFlags=} flags Flags to modify writing the response
+ */
+
+/**
+ * User-provided method to handle unary requests on a server
+ * @callback grpc.Server~handleUnaryCall
+ * @param {grpc~ServerUnaryCall} call The call object
+ * @param {grpc.Server~sendUnaryData} callback The callback to call to respond
+ *     to the request
+ */
+
 /**
  * Fully handle a unary call
- * @access private
- * @param {grpc.Call} call The call to handle
+ * @private
+ * @param {grpc.internal~Call} call The call to handle
  * @param {Object} handler Request handler object for the method that was called
- * @param {Metadata} metadata Metadata from the client
+ * @param {grpc~Server.handleUnaryCall} handler.func The handler function
+ * @param {grpc~deserialize} handler.deserialize The deserialization function
+ *     for request data
+ * @param {grpc~serialize} handler.serialize The serialization function for
+ *     response data
+ * @param {grpc.Metadata} metadata Metadata from the client
  */
 function handleUnary(call, handler, metadata) {
-  var emitter = new ServerUnaryCall(call);
+  var emitter = new ServerUnaryCall(call, metadata);
   emitter.on('error', function(error) {
     handleError(call, error);
   });
-  emitter.metadata = metadata;
   emitter.waitForCancel();
   var batch = {};
   batch[grpc.opType.RECV_MESSAGE] = true;
@@ -511,17 +617,28 @@ function handleUnary(call, handler, metadata) {
   });
 }
 
+/**
+ * User provided method to handle server streaming methods on the server.
+ * @callback grpc.Server~handleServerStreamingCall
+ * @param {grpc~ServerWritableStream} call The call object
+ */
+
 /**
  * Fully handle a server streaming call
- * @access private
- * @param {grpc.Call} call The call to handle
+ * @private
+ * @param {grpc.internal~Call} call The call to handle
  * @param {Object} handler Request handler object for the method that was called
- * @param {Metadata} metadata Metadata from the client
+ * @param {grpc~Server.handleServerStreamingCall} handler.func The handler
+ *     function
+ * @param {grpc~deserialize} handler.deserialize The deserialization function
+ *     for request data
+ * @param {grpc~serialize} handler.serialize The serialization function for
+ *     response data
+ * @param {grpc.Metadata} metadata Metadata from the client
  */
 function handleServerStreaming(call, handler, metadata) {
-  var stream = new ServerWritableStream(call, handler.serialize);
+  var stream = new ServerWritableStream(call, metadata, handler.serialize);
   stream.waitForCancel();
-  stream.metadata = metadata;
   var batch = {};
   batch[grpc.opType.RECV_MESSAGE] = true;
   call.startBatch(batch, function(err, result) {
@@ -540,20 +657,33 @@ function handleServerStreaming(call, handler, metadata) {
   });
 }
 
+/**
+ * User provided method to handle client streaming methods on the server.
+ * @callback grpc.Server~handleClientStreamingCall
+ * @param {grpc~ServerReadableStream} call The call object
+ * @param {grpc.Server~sendUnaryData} callback The callback to call to respond
+ *     to the request
+ */
+
 /**
  * Fully handle a client streaming call
  * @access private
- * @param {grpc.Call} call The call to handle
+ * @param {grpc.internal~Call} call The call to handle
  * @param {Object} handler Request handler object for the method that was called
- * @param {Metadata} metadata Metadata from the client
+ * @param {grpc~Server.handleClientStreamingCall} handler.func The handler
+ *     function
+ * @param {grpc~deserialize} handler.deserialize The deserialization function
+ *     for request data
+ * @param {grpc~serialize} handler.serialize The serialization function for
+ *     response data
+ * @param {grpc.Metadata} metadata Metadata from the client
  */
 function handleClientStreaming(call, handler, metadata) {
-  var stream = new ServerReadableStream(call, handler.deserialize);
+  var stream = new ServerReadableStream(call, metadata, handler.deserialize);
   stream.on('error', function(error) {
     handleError(call, error);
   });
   stream.waitForCancel();
-  stream.metadata = metadata;
   handler.func(stream, function(err, value, trailer, flags) {
     stream.terminate();
     if (err) {
@@ -567,18 +697,29 @@ function handleClientStreaming(call, handler, metadata) {
   });
 }
 
+/**
+ * User provided method to handle bidirectional streaming calls on the server.
+ * @callback grpc.Server~handleBidiStreamingCall
+ * @param {grpc~ServerDuplexStream} call The call object
+ */
+
 /**
  * Fully handle a bidirectional streaming call
- * @access private
- * @param {grpc.Call} call The call to handle
+ * @private
+ * @param {grpc.internal~Call} call The call to handle
  * @param {Object} handler Request handler object for the method that was called
+ * @param {grpc~Server.handleBidiStreamingCall} handler.func The handler
+ *     function
+ * @param {grpc~deserialize} handler.deserialize The deserialization function
+ *     for request data
+ * @param {grpc~serialize} handler.serialize The serialization function for
+ *     response data
  * @param {Metadata} metadata Metadata from the client
  */
 function handleBidiStreaming(call, handler, metadata) {
-  var stream = new ServerDuplexStream(call, handler.serialize,
+  var stream = new ServerDuplexStream(call, metadata, handler.serialize,
                                       handler.deserialize);
   stream.waitForCancel();
-  stream.metadata = metadata;
   handler.func(stream);
 }
 
@@ -592,96 +733,90 @@ var streamHandlers = {
 /**
  * Constructs a server object that stores request handlers and delegates
  * incoming requests to those handlers
+ * @memberof grpc
  * @constructor
  * @param {Object=} options Options that should be passed to the internal server
  *     implementation
+ * @example
+ * var server = new grpc.Server();
+ * server.addProtoService(protobuf_service_descriptor, service_implementation);
+ * server.bind('address:port', server_credential);
+ * server.start();
  */
 function Server(options) {
   this.handlers = {};
-  var handlers = this.handlers;
   var server = new grpc.Server(options);
   this._server = server;
   this.started = false;
+}
+
+/**
+ * Start the server and begin handling requests
+ */
+Server.prototype.start = function() {
+  if (this.started) {
+    throw new Error('Server is already running');
+  }
+  var self = this;
+  this.started = true;
+  this._server.start();
   /**
-   * Start the server and begin handling requests
-   * @this Server
+   * 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.internal~Event} event The event to handle with tag
+   *     SERVER_RPC_NEW
    */
-  this.start = function() {
-    if (this.started) {
-      throw new Error('Server is already running');
+  function handleNewCall(err, event) {
+    if (err) {
+      return;
     }
-    this.started = true;
-    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(err, event) {
-      if (err) {
-        return;
-      }
-      var details = event.new_call;
-      var call = details.call;
-      var method = details.method;
-      var metadata = Metadata._fromCoreRepresentation(details.metadata);
-      if (method === null) {
-        return;
-      }
-      server.requestCall(handleNewCall);
-      var handler;
-      if (handlers.hasOwnProperty(method)) {
-        handler = handlers[method];
-      } else {
-        var batch = {};
-        batch[grpc.opType.SEND_INITIAL_METADATA] =
-            (new Metadata())._getCoreRepresentation();
-        batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
-          code: constants.status.UNIMPLEMENTED,
-          details: '',
-          metadata: {}
-        };
-        batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
-        call.startBatch(batch, function() {});
-        return;
-      }
-      streamHandlers[handler.type](call, handler, metadata);
+    var details = event.new_call;
+    var call = details.call;
+    var method = details.method;
+    var metadata = Metadata._fromCoreRepresentation(details.metadata);
+    if (method === null) {
+      return;
     }
-    server.requestCall(handleNewCall);
-  };
-
-  /**
-   * Gracefully shuts down the server. The server will stop receiving new calls,
-   * and any pending calls will complete. The callback will be called when all
-   * pending calls have completed and the server is fully shut down. This method
-   * is idempotent with itself and forceShutdown.
-   * @param {function()} callback The shutdown complete callback
-   */
-  this.tryShutdown = function(callback) {
-    server.tryShutdown(callback);
-  };
+    self._server.requestCall(handleNewCall);
+    var handler;
+    if (self.handlers.hasOwnProperty(method)) {
+      handler = self.handlers[method];
+    } else {
+      var batch = {};
+      batch[grpc.opType.SEND_INITIAL_METADATA] =
+          (new Metadata())._getCoreRepresentation();
+      batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
+        code: constants.status.UNIMPLEMENTED,
+        details: '',
+        metadata: {}
+      };
+      batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
+      call.startBatch(batch, function() {});
+      return;
+    }
+    streamHandlers[handler.type](call, handler, metadata);
+  }
+  this._server.requestCall(handleNewCall);
+};
 
-  /**
-   * Forcibly shuts down the server. The server will stop receiving new calls
-   * and cancel all pending calls. When it returns, the server has shut down.
-   * This method is idempotent with itself and tryShutdown, and it will trigger
-   * any outstanding tryShutdown callbacks.
-   */
-  this.forceShutdown = function() {
-    server.forceShutdown();
-  };
-}
+/**
+ * Unified type for application handlers for all types of calls
+ * @typedef {(grpc.Server~handleUnaryCall
+ *            |grpc.Server~handleClientStreamingCall
+ *            |grpc.Server~handleServerStreamingCall
+ *            |grpc.Server~handleBidiStreamingCall)} grpc.Server~handleCall
+ */
 
 /**
  * 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
- * @param {function(*):Buffer} serialize Serialization function for responses
- * @param {function(Buffer):*} deserialize Deserialization function for requests
+ * @param {grpc.Server~handleCall} handler Function that takes a stream of
+ *     request values and returns a stream of response values
+ * @param {grpc~serialize} serialize Serialization function for responses
+ * @param {grpc~deserialize} deserialize Deserialization function for requests
  * @param {string} type The streaming type of method that this handles
  * @return {boolean} True if the handler was set. False if a handler was already
  *     set for that name.
@@ -700,6 +835,27 @@ Server.prototype.register = function(name, handler, serialize, deserialize,
   return true;
 };
 
+/**
+ * Gracefully shuts down the server. The server will stop receiving new calls,
+ * and any pending calls will complete. The callback will be called when all
+ * pending calls have completed and the server is fully shut down. This method
+ * is idempotent with itself and forceShutdown.
+ * @param {function()} callback The shutdown complete callback
+ */
+Server.prototype.tryShutdown = function(callback) {
+  this._server.tryShutdown(callback);
+};
+
+/**
+ * Forcibly shuts down the server. The server will stop receiving new calls
+ * and cancel all pending calls. When it returns, the server has shut down.
+ * This method is idempotent with itself and tryShutdown, and it will trigger
+ * any outstanding tryShutdown callbacks.
+ */
+Server.prototype.forceShutdown = function() {
+  this._server.forceShutdown();
+};
+
 var unimplementedStatusResponse = {
   code: constants.status.UNIMPLEMENTED,
   details: 'The server does not implement this method'
@@ -721,13 +877,10 @@ var defaultHandler = {
 };
 
 /**
- * Add a service to the server, with a corresponding implementation. If you are
- * generating this from a proto file, you should instead use
- * addProtoService.
- * @param {Object<String, *>} service The service descriptor, as
- *     {@link module:src/common.getProtobufServiceAttrs} returns
- * @param {Object<String, function>} implementation Map of method names to
- *     method implementation for the provided service.
+ * Add a service to the server, with a corresponding implementation.
+ * @param {grpc~ServiceDefinition} service The service descriptor
+ * @param {Object<String, grpc.Server~handleCall>} implementation Map of method
+ *     names to method implementation for the provided service.
  */
 Server.prototype.addService = function(service, implementation) {
   if (!_.isObject(service) || !_.isObject(implementation)) {
@@ -788,10 +941,10 @@ var logAddProtoServiceDeprecationOnce = _.once(function() {
 
 /**
  * Add a proto service to the server, with a corresponding implementation
- * @deprecated Use grpc.load and Server#addService instead
+ * @deprecated Use {@link grpc.Server#addService} instead
  * @param {Protobuf.Reflect.Service} service The proto service descriptor
- * @param {Object<String, function>} implementation Map of method names to
- *     method implementation for the provided service.
+ * @param {Object<String, grpc.Server~handleCall>} implementation Map of method
+ *     names to method implementation for the provided service.
  */
 Server.prototype.addProtoService = function(service, implementation) {
   var options;
@@ -815,10 +968,11 @@ Server.prototype.addProtoService = function(service, implementation) {
 };
 
 /**
- * Binds the server to the given port, with SSL enabled if creds is given
+ * Binds the server to the given port, with SSL disabled if creds is an
+ * insecure credentials object
  * @param {string} port The port that the server should bind on, in the format
  *     "address:port"
- * @param {ServerCredentials=} creds Server credential object to be used for
+ * @param {grpc.ServerCredentials} creds Server credential object to be used for
  *     SSL. Pass an insecure credentials object for an insecure port.
  */
 Server.prototype.bind = function(port, creds) {
@@ -828,7 +982,4 @@ Server.prototype.bind = function(port, creds) {
   return this._server.addHttp2Port(port, creds);
 };
 
-/**
- * @see module:src/server~Server
- */
 exports.Server = Server;
diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js
index d2f0511af2db0e3e719fa0bae3f4668fa98beecc..6f1c269267cc6ab05943a87e6b417a30acb55924 100644
--- a/src/node/test/surface_test.js
+++ b/src/node/test/surface_test.js
@@ -1322,14 +1322,14 @@ describe('Cancelling surface client', function() {
   });
   it('Should correctly cancel a unary call', function(done) {
     var call = client.div({'divisor': 0, 'dividend': 0}, function(err, resp) {
-      assert.strictEqual(err.code, surface_client.status.CANCELLED);
+      assert.strictEqual(err.code, grpc.status.CANCELLED);
       done();
     });
     call.cancel();
   });
   it('Should correctly cancel a client stream call', function(done) {
     var call = client.sum(function(err, resp) {
-      assert.strictEqual(err.code, surface_client.status.CANCELLED);
+      assert.strictEqual(err.code, grpc.status.CANCELLED);
       done();
     });
     call.cancel();
@@ -1338,7 +1338,7 @@ describe('Cancelling surface client', function() {
     var call = client.fib({'limit': 5});
     call.on('data', function() {});
     call.on('error', function(error) {
-      assert.strictEqual(error.code, surface_client.status.CANCELLED);
+      assert.strictEqual(error.code, grpc.status.CANCELLED);
       done();
     });
     call.cancel();
@@ -1347,7 +1347,7 @@ describe('Cancelling surface client', function() {
     var call = client.divMany();
     call.on('data', function() {});
     call.on('error', function(error) {
-      assert.strictEqual(error.code, surface_client.status.CANCELLED);
+      assert.strictEqual(error.code, grpc.status.CANCELLED);
       done();
     });
     call.cancel();
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index 9770301d0932e0b323fcde87ffea1b8d8860ab4d..5f8075467da4d5e938d22c57299701def50ae596 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -313,6 +313,7 @@ CORE_SOURCE_FILES = [
   'src/core/ext/census/grpc_filter.c',
   'src/core/ext/census/grpc_plugin.c',
   'src/core/ext/census/initialize.c',
+  'src/core/ext/census/intrusive_hash_map.c',
   'src/core/ext/census/mlog.c',
   'src/core/ext/census/operation.c',
   'src/core/ext/census/placeholders.c',
diff --git a/test/core/census/intrusive_hash_map_test.c b/test/core/census/intrusive_hash_map_test.c
new file mode 100644
index 0000000000000000000000000000000000000000..a0a46ebadfa0f53f7140ae52d0d731f552029c2d
--- /dev/null
+++ b/test/core/census/intrusive_hash_map_test.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/core/ext/census/intrusive_hash_map.h"
+
+#include <grpc/support/log.h>
+#include <grpc/support/useful.h>
+#include "test/core/util/test_config.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* The initial size of an intrusive hash map will be 2 to this power. */
+static const uint32_t kInitialLog2Size = 4;
+
+/* Simple object used for testing intrusive_hash_map. */
+typedef struct object { uint64_t val; } object;
+
+/* Helper function to allocate and initialize object. */
+static inline object *make_new_object(uint64_t val) {
+  object *obj = (object *)gpr_malloc(sizeof(object));
+  obj->val = val;
+  return obj;
+}
+
+/* Wrapper struct for object. */
+typedef struct ptr_item {
+  INTRUSIVE_HASH_MAP_HEADER;
+  object *obj;
+} ptr_item;
+
+/* Helper function that creates a new hash map item.  It is up to the user to
+ * free the item that was allocated. */
+static inline ptr_item *make_ptr_item(uint64_t key, uint64_t value) {
+  ptr_item *new_item = (ptr_item *)gpr_malloc(sizeof(ptr_item));
+  new_item->IHM_key = key;
+  new_item->IHM_hash_link = NULL;
+  new_item->obj = make_new_object(value);
+  return new_item;
+}
+
+/* Helper function to deallocate ptr_item. */
+static void free_ptr_item(void *ptr) { gpr_free(((ptr_item *)ptr)->obj); }
+
+/* Simple string object used for testing intrusive_hash_map. */
+typedef struct string_item {
+  INTRUSIVE_HASH_MAP_HEADER;
+  // User data.
+  char buf[32];
+  uint16_t len;
+} string_item;
+
+/* Helper function to allocate and initialize string object. */
+static string_item *make_string_item(uint64_t key, const char *buf,
+                                     uint16_t len) {
+  string_item *item = (string_item *)gpr_malloc(sizeof(string_item));
+  item->IHM_key = key;
+  item->IHM_hash_link = NULL;
+  item->len = len;
+  memcpy(item->buf, buf, sizeof(char) * len);
+  return item;
+}
+
+/* Helper function for comparing two string objects. */
+static bool compare_string_item(const string_item *A, const string_item *B) {
+  if (A->IHM_key != B->IHM_key || A->len != B->len)
+    return false;
+  else {
+    for (int i = 0; i < A->len; ++i) {
+      if (A->buf[i] != B->buf[i]) return false;
+    }
+  }
+
+  return true;
+}
+
+void test_empty() {
+  intrusive_hash_map hash_map;
+  intrusive_hash_map_init(&hash_map, kInitialLog2Size);
+  GPR_ASSERT(0 == intrusive_hash_map_size(&hash_map));
+  GPR_ASSERT(intrusive_hash_map_empty(&hash_map));
+  intrusive_hash_map_free(&hash_map, NULL);
+}
+
+void test_single_item() {
+  intrusive_hash_map hash_map;
+  intrusive_hash_map_init(&hash_map, kInitialLog2Size);
+
+  ptr_item *new_item = make_ptr_item(10, 20);
+  bool ok = intrusive_hash_map_insert(&hash_map, (hm_item *)new_item);
+  GPR_ASSERT(ok);
+
+  ptr_item *item1 =
+      (ptr_item *)intrusive_hash_map_find(&hash_map, (uint64_t)10);
+  GPR_ASSERT(item1->obj->val == 20);
+  GPR_ASSERT(item1 == new_item);
+
+  ptr_item *item2 =
+      (ptr_item *)intrusive_hash_map_erase(&hash_map, (uint64_t)10);
+  GPR_ASSERT(item2 == new_item);
+
+  gpr_free(new_item->obj);
+  gpr_free(new_item);
+  GPR_ASSERT(0 == intrusive_hash_map_size(&hash_map));
+  intrusive_hash_map_free(&hash_map, &free_ptr_item);
+}
+
+void test_two_items() {
+  intrusive_hash_map hash_map;
+  intrusive_hash_map_init(&hash_map, kInitialLog2Size);
+
+  string_item *new_item1 = make_string_item(10, "test1", 5);
+  bool ok = intrusive_hash_map_insert(&hash_map, (hm_item *)new_item1);
+  GPR_ASSERT(ok);
+  string_item *new_item2 = make_string_item(20, "test2", 5);
+  ok = intrusive_hash_map_insert(&hash_map, (hm_item *)new_item2);
+  GPR_ASSERT(ok);
+
+  string_item *item1 =
+      (string_item *)intrusive_hash_map_find(&hash_map, (uint64_t)10);
+  GPR_ASSERT(compare_string_item(new_item1, item1));
+  GPR_ASSERT(item1 == new_item1);
+  string_item *item2 =
+      (string_item *)intrusive_hash_map_find(&hash_map, (uint64_t)20);
+  GPR_ASSERT(compare_string_item(new_item2, item2));
+  GPR_ASSERT(item2 == new_item2);
+
+  item1 = (string_item *)intrusive_hash_map_erase(&hash_map, (uint64_t)10);
+  GPR_ASSERT(item1 == new_item1);
+  item2 = (string_item *)intrusive_hash_map_erase(&hash_map, (uint64_t)20);
+  GPR_ASSERT(item2 == new_item2);
+
+  gpr_free(new_item1);
+  gpr_free(new_item2);
+  GPR_ASSERT(0 == intrusive_hash_map_size(&hash_map));
+  intrusive_hash_map_free(&hash_map, NULL);
+}
+
+// Test resetting and clearing the hash map.
+void test_reset_clear() {
+  intrusive_hash_map hash_map;
+  intrusive_hash_map_init(&hash_map, kInitialLog2Size);
+
+  // Add some data to the hash_map.
+  for (uint64_t i = 0; i < 3; ++i) {
+    intrusive_hash_map_insert(&hash_map, (hm_item *)make_ptr_item(i, i));
+  }
+  GPR_ASSERT(3 == intrusive_hash_map_size(&hash_map));
+
+  // Test find.
+  for (uint64_t i = 0; i < 3; ++i) {
+    ptr_item *item = (ptr_item *)intrusive_hash_map_find(&hash_map, i);
+    GPR_ASSERT(item != NULL);
+    GPR_ASSERT(item->IHM_key == i && item->obj->val == i);
+  }
+
+  intrusive_hash_map_clear(&hash_map, &free_ptr_item);
+  GPR_ASSERT(intrusive_hash_map_empty(&hash_map));
+  intrusive_hash_map_free(&hash_map, &free_ptr_item);
+}
+
+// Check that the hash_map contains every key between [min_value, max_value]
+// (inclusive).
+void check_hash_map_values(intrusive_hash_map *hash_map, uint64_t min_value,
+                           uint64_t max_value) {
+  GPR_ASSERT(intrusive_hash_map_size(hash_map) == max_value - min_value + 1);
+
+  for (uint64_t i = min_value; i <= max_value; ++i) {
+    ptr_item *item = (ptr_item *)intrusive_hash_map_find(hash_map, i);
+    GPR_ASSERT(item != NULL);
+    GPR_ASSERT(item->obj->val == i);
+  }
+}
+
+// Add many items and cause the hash_map to extend.
+void test_extend() {
+  intrusive_hash_map hash_map;
+  intrusive_hash_map_init(&hash_map, kInitialLog2Size);
+
+  const uint64_t kNumValues = (1 << 16);
+
+  for (uint64_t i = 0; i < kNumValues; ++i) {
+    ptr_item *item = make_ptr_item(i, i);
+    bool ok = intrusive_hash_map_insert(&hash_map, (hm_item *)item);
+    GPR_ASSERT(ok);
+    if (i % 1000 == 0) {
+      check_hash_map_values(&hash_map, 0, i);
+    }
+  }
+
+  for (uint64_t i = 0; i < kNumValues; ++i) {
+    ptr_item *item = (ptr_item *)intrusive_hash_map_find(&hash_map, i);
+    GPR_ASSERT(item != NULL);
+    GPR_ASSERT(item->IHM_key == i && item->obj->val == i);
+    ptr_item *item2 = (ptr_item *)intrusive_hash_map_erase(&hash_map, i);
+    GPR_ASSERT(item == item2);
+    gpr_free(item->obj);
+    gpr_free(item);
+  }
+
+  GPR_ASSERT(intrusive_hash_map_empty(&hash_map));
+  intrusive_hash_map_free(&hash_map, &free_ptr_item);
+}
+
+void test_stress() {
+  intrusive_hash_map hash_map;
+  intrusive_hash_map_init(&hash_map, kInitialLog2Size);
+  size_t n = 0;
+
+  // Randomly add and insert entries 1000000 times.
+  for (uint64_t i = 0; i < 1000000; ++i) {
+    int op = rand() & 0x1;
+
+    switch (op) {
+      // Case 0 is insertion of entry.
+      case 0: {
+        uint64_t key = (uint64_t)(rand() % 10000);
+        ptr_item *item = make_ptr_item(key, key);
+        bool ok = intrusive_hash_map_insert(&hash_map, (hm_item *)item);
+        if (ok) {
+          n++;
+        } else {
+          gpr_free(item->obj);
+          gpr_free(item);
+        }
+        break;
+      }
+      // Case 1 is removal of entry.
+      case 1: {
+        uint64_t key = (uint64_t)(rand() % 10000);
+        ptr_item *item = (ptr_item *)intrusive_hash_map_find(&hash_map, key);
+        if (item != NULL) {
+          n--;
+          GPR_ASSERT(key == item->obj->val);
+          ptr_item *item2 =
+              (ptr_item *)intrusive_hash_map_erase(&hash_map, key);
+          GPR_ASSERT(item == item2);
+          gpr_free(item->obj);
+          gpr_free(item);
+        }
+        break;
+      }
+    }
+  }
+  // Check size
+  GPR_ASSERT(n == intrusive_hash_map_size(&hash_map));
+
+  // Clean the hash_map up.
+  intrusive_hash_map_clear(&hash_map, &free_ptr_item);
+  GPR_ASSERT(intrusive_hash_map_empty(&hash_map));
+  intrusive_hash_map_free(&hash_map, &free_ptr_item);
+}
+
+int main(int argc, char **argv) {
+  grpc_test_init(argc, argv);
+  gpr_time_init();
+  srand((unsigned)gpr_now(GPR_CLOCK_REALTIME).tv_nsec);
+
+  test_empty();
+  test_single_item();
+  test_two_items();
+  test_reset_clear();
+  test_extend();
+  test_stress();
+
+  return 0;
+}
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index eb0883b7971b63b1231e9d2488a187189c1a2814..15410dec0193b077c6cf2c30a30eebd052f2011c 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -883,6 +883,9 @@ src/core/ext/census/grpc_filter.c \
 src/core/ext/census/grpc_filter.h \
 src/core/ext/census/grpc_plugin.c \
 src/core/ext/census/initialize.c \
+src/core/ext/census/intrusive_hash_map.c \
+src/core/ext/census/intrusive_hash_map.h \
+src/core/ext/census/intrusive_hash_map_internal.h \
 src/core/ext/census/mlog.c \
 src/core/ext/census/mlog.h \
 src/core/ext/census/operation.c \
diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json
index ec2836ec4292d39e3dad4713d4519c5e454e18a6..97e079d8a8e02d756d893a506520dd2be74be4b5 100644
--- a/tools/run_tests/generated/sources_and_headers.json
+++ b/tools/run_tests/generated/sources_and_headers.json
@@ -181,6 +181,23 @@
     "third_party": false, 
     "type": "target"
   }, 
+  {
+    "deps": [
+      "gpr", 
+      "gpr_test_util", 
+      "grpc", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c", 
+    "name": "census_intrusive_hash_map_test", 
+    "src": [
+      "test/core/census/intrusive_hash_map_test.c"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
   {
     "deps": [
       "gpr", 
@@ -7541,6 +7558,8 @@
       "src/core/ext/census/gen/census.pb.h", 
       "src/core/ext/census/gen/trace_context.pb.h", 
       "src/core/ext/census/grpc_filter.h", 
+      "src/core/ext/census/intrusive_hash_map.h", 
+      "src/core/ext/census/intrusive_hash_map_internal.h", 
       "src/core/ext/census/mlog.h", 
       "src/core/ext/census/resource.h", 
       "src/core/ext/census/rpc_metric_id.h", 
@@ -7571,6 +7590,9 @@
       "src/core/ext/census/grpc_filter.h", 
       "src/core/ext/census/grpc_plugin.c", 
       "src/core/ext/census/initialize.c", 
+      "src/core/ext/census/intrusive_hash_map.c", 
+      "src/core/ext/census/intrusive_hash_map.h", 
+      "src/core/ext/census/intrusive_hash_map_internal.h", 
       "src/core/ext/census/mlog.c", 
       "src/core/ext/census/mlog.h", 
       "src/core/ext/census/operation.c", 
diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json
index d1e3a99a084da1866d955bab0fc9755382b7e6cb..82db441a1a58a13290d7fe07a08d86303f29d74a 100644
--- a/tools/run_tests/generated/tests.json
+++ b/tools/run_tests/generated/tests.json
@@ -223,6 +223,28 @@
       "windows"
     ]
   }, 
+  {
+    "args": [], 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "gtest": false, 
+    "language": "c", 
+    "name": "census_intrusive_hash_map_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ]
+  }, 
   {
     "args": [], 
     "ci_platforms": [
diff --git a/vsprojects/buildtests_c.sln b/vsprojects/buildtests_c.sln
index 97a75e776234371b85fbbe0cdc08514d2b793a25..284dd68c165765554325e91249e13b45827045b2 100644
--- a/vsprojects/buildtests_c.sln
+++ b/vsprojects/buildtests_c.sln
@@ -129,6 +129,17 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "census_context_test", "vcxp
 		{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} = {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}
 	EndProjectSection
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "census_intrusive_hash_map_test", "vcxproj\test\census_intrusive_hash_map_test\census_intrusive_hash_map_test.vcxproj", "{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}"
+	ProjectSection(myProperties) = preProject
+        	lib = "False"
+	EndProjectSection
+	ProjectSection(ProjectDependencies) = postProject
+		{17BCAFC0-5FDC-4C94-AEB9-95F3E220614B} = {17BCAFC0-5FDC-4C94-AEB9-95F3E220614B}
+		{29D16885-7228-4C31-81ED-5F9187C7F2A9} = {29D16885-7228-4C31-81ED-5F9187C7F2A9}
+		{EAB0A629-17A9-44DB-B5FF-E91A721FE037} = {EAB0A629-17A9-44DB-B5FF-E91A721FE037}
+		{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} = {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}
+	EndProjectSection
+EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "census_resource_test", "vcxproj\test\census_resource_test\census_resource_test.vcxproj", "{18CF99B5-3C61-EC3D-9509-3C95334C3B88}"
 	ProjectSection(myProperties) = preProject
         	lib = "False"
@@ -1899,6 +1910,22 @@ Global
 		{5C1CFC2D-AF3C-D7CB-BA74-D267E91CBC73}.Release-DLL|Win32.Build.0 = Release|Win32
 		{5C1CFC2D-AF3C-D7CB-BA74-D267E91CBC73}.Release-DLL|x64.ActiveCfg = Release|x64
 		{5C1CFC2D-AF3C-D7CB-BA74-D267E91CBC73}.Release-DLL|x64.Build.0 = Release|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug|Win32.ActiveCfg = Debug|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug|x64.ActiveCfg = Debug|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release|Win32.ActiveCfg = Release|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release|x64.ActiveCfg = Release|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug|Win32.Build.0 = Debug|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug|x64.Build.0 = Debug|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release|Win32.Build.0 = Release|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release|x64.Build.0 = Release|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug-DLL|Win32.ActiveCfg = Debug|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug-DLL|Win32.Build.0 = Debug|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug-DLL|x64.ActiveCfg = Debug|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug-DLL|x64.Build.0 = Debug|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release-DLL|Win32.ActiveCfg = Release|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release-DLL|Win32.Build.0 = Release|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release-DLL|x64.ActiveCfg = Release|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release-DLL|x64.Build.0 = Release|x64
 		{18CF99B5-3C61-EC3D-9509-3C95334C3B88}.Debug|Win32.ActiveCfg = Debug|Win32
 		{18CF99B5-3C61-EC3D-9509-3C95334C3B88}.Debug|x64.ActiveCfg = Debug|x64
 		{18CF99B5-3C61-EC3D-9509-3C95334C3B88}.Release|Win32.ActiveCfg = Release|Win32
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj b/vsprojects/vcxproj/grpc/grpc.vcxproj
index 2ccda23694f5ae33cfa41da19cca69e50866eb68..1303366574d2321f70e334656b4a7f2017b4d2f4 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj
@@ -504,6 +504,8 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\gen\census.pb.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\gen\trace_context.pb.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\grpc_filter.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map_internal.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\mlog.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\resource.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\rpc_metric_id.h" />
@@ -993,6 +995,8 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\initialize.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\mlog.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\operation.c">
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
index 3a1c1f9c5a7f5dc93241404f7b4eaee3ba9a4b8d..9f25a1c179cac4a2b9823e87ea411d9f78e38ef3 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
@@ -712,6 +712,9 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\initialize.c">
       <Filter>src\core\ext\census</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.c">
+      <Filter>src\core\ext\census</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\mlog.c">
       <Filter>src\core\ext\census</Filter>
     </ClCompile>
@@ -1454,6 +1457,12 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\grpc_filter.h">
       <Filter>src\core\ext\census</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.h">
+      <Filter>src\core\ext\census</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map_internal.h">
+      <Filter>src\core\ext\census</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\mlog.h">
       <Filter>src\core\ext\census</Filter>
     </ClInclude>
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
index d084d70702a90cdc18706cad5fdcd13af63d6d05..ac403a7c485849af06181e32636938c89d269227 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
@@ -469,6 +469,8 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\gen\census.pb.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\gen\trace_context.pb.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\grpc_filter.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map_internal.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\mlog.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\resource.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\rpc_metric_id.h" />
@@ -900,6 +902,8 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\initialize.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\mlog.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\operation.c">
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
index ba826852a3b71773e51d85af5000abac810eda22..9fee2ec22b7883c2dfe8d266ac174515ca789781 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
@@ -625,6 +625,9 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\initialize.c">
       <Filter>src\core\ext\census</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.c">
+      <Filter>src\core\ext\census</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\mlog.c">
       <Filter>src\core\ext\census</Filter>
     </ClCompile>
@@ -1289,6 +1292,12 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\grpc_filter.h">
       <Filter>src\core\ext\census</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.h">
+      <Filter>src\core\ext\census</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map_internal.h">
+      <Filter>src\core\ext\census</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\mlog.h">
       <Filter>src\core\ext\census</Filter>
     </ClInclude>
diff --git a/vsprojects/vcxproj/test/census_intrusive_hash_map_test/census_intrusive_hash_map_test.vcxproj b/vsprojects/vcxproj/test/census_intrusive_hash_map_test/census_intrusive_hash_map_test.vcxproj
new file mode 100644
index 0000000000000000000000000000000000000000..46ea06b2a0f2caf5a9064a055770f187ea326326
--- /dev/null
+++ b/vsprojects/vcxproj/test/census_intrusive_hash_map_test/census_intrusive_hash_map_test.vcxproj
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\1.0.204.1.props')" />
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}</ProjectGuid>
+    <IgnoreWarnIntDirInTempDetected>true</IgnoreWarnIntDirInTempDetected>
+    <IntDir>$(SolutionDir)IntDir\$(MSBuildProjectName)\</IntDir>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '10.0'" Label="Configuration">
+    <PlatformToolset>v100</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '11.0'" Label="Configuration">
+    <PlatformToolset>v110</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '12.0'" Label="Configuration">
+    <PlatformToolset>v120</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '14.0'" Label="Configuration">
+    <PlatformToolset>v140</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(SolutionDir)\..\vsprojects\global.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\openssl.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\winsock.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\zlib.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+    <TargetName>census_intrusive_hash_map_test</TargetName>
+    <Linkage-grpc_dependencies_zlib>static</Linkage-grpc_dependencies_zlib>
+    <Configuration-grpc_dependencies_zlib>Debug</Configuration-grpc_dependencies_zlib>
+    <Linkage-grpc_dependencies_openssl>static</Linkage-grpc_dependencies_openssl>
+    <Configuration-grpc_dependencies_openssl>Debug</Configuration-grpc_dependencies_openssl>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Release'">
+    <TargetName>census_intrusive_hash_map_test</TargetName>
+    <Linkage-grpc_dependencies_zlib>static</Linkage-grpc_dependencies_zlib>
+    <Configuration-grpc_dependencies_zlib>Release</Configuration-grpc_dependencies_zlib>
+    <Linkage-grpc_dependencies_openssl>static</Linkage-grpc_dependencies_openssl>
+    <Configuration-grpc_dependencies_openssl>Release</Configuration-grpc_dependencies_openssl>
+  </PropertyGroup>
+    <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+      <MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+
+    <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+      <MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+
+    <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+      <MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+
+    <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+      <MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+
+  <ItemGroup>
+    <ClCompile Include="$(SolutionDir)\..\test\core\census\intrusive_hash_map_test.c">
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\grpc_test_util\grpc_test_util.vcxproj">
+      <Project>{17BCAFC0-5FDC-4C94-AEB9-95F3E220614B}</Project>
+    </ProjectReference>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\grpc\grpc.vcxproj">
+      <Project>{29D16885-7228-4C31-81ED-5F9187C7F2A9}</Project>
+    </ProjectReference>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\gpr_test_util\gpr_test_util.vcxproj">
+      <Project>{EAB0A629-17A9-44DB-B5FF-E91A721FE037}</Project>
+    </ProjectReference>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\gpr\gpr.vcxproj">
+      <Project>{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies\grpc.dependencies.zlib.targets')" />
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies\grpc.dependencies.zlib.targets')" />
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies\grpc.dependencies.openssl.targets')" />
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies\grpc.dependencies.openssl.targets')" />
+  </ImportGroup>
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets')" />
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets')" />
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets')" />
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props')" />
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets')" />
+  </Target>
+</Project>
+
diff --git a/vsprojects/vcxproj/test/census_intrusive_hash_map_test/census_intrusive_hash_map_test.vcxproj.filters b/vsprojects/vcxproj/test/census_intrusive_hash_map_test/census_intrusive_hash_map_test.vcxproj.filters
new file mode 100644
index 0000000000000000000000000000000000000000..2dfa300e97629a345bce6803064d31a80d88228a
--- /dev/null
+++ b/vsprojects/vcxproj/test/census_intrusive_hash_map_test/census_intrusive_hash_map_test.vcxproj.filters
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <ClCompile Include="$(SolutionDir)\..\test\core\census\intrusive_hash_map_test.c">
+      <Filter>test\core\census</Filter>
+    </ClCompile>
+  </ItemGroup>
+
+  <ItemGroup>
+    <Filter Include="test">
+      <UniqueIdentifier>{24bfacc6-bd89-4cdf-4183-3ff53180fa48}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\core">
+      <UniqueIdentifier>{71b1debf-71c7-c1e9-9e01-21330ede0d7f}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\core\census">
+      <UniqueIdentifier>{0228063a-a601-967e-27ed-9f6197cb3629}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+</Project>
+