Merge pull request #4846 from murgatroid99/ruby_coverage_reporting

Fix path where we check for ruby coverage report
diff --git a/BUILD b/BUILD
index 642b6c4..fa19d3d 100644
--- a/BUILD
+++ b/BUILD
@@ -418,6 +418,7 @@
     "src/core/census/context.c",
     "src/core/census/initialize.c",
     "src/core/census/operation.c",
+    "src/core/census/tag_set.c",
     "src/core/census/tracing.c",
   ],
   hdrs = [
@@ -693,6 +694,7 @@
     "src/core/census/context.c",
     "src/core/census/initialize.c",
     "src/core/census/operation.c",
+    "src/core/census/tag_set.c",
     "src/core/census/tracing.c",
   ],
   hdrs = [
@@ -1231,6 +1233,7 @@
     "src/core/census/context.c",
     "src/core/census/initialize.c",
     "src/core/census/operation.c",
+    "src/core/census/tag_set.c",
     "src/core/census/tracing.c",
   ],
   hdrs = [
diff --git a/Makefile b/Makefile
index 6643dd5..d3597c2 100644
--- a/Makefile
+++ b/Makefile
@@ -907,6 +907,7 @@
 sockaddr_resolver_test: $(BINDIR)/$(CONFIG)/sockaddr_resolver_test
 sockaddr_utils_test: $(BINDIR)/$(CONFIG)/sockaddr_utils_test
 socket_utils_test: $(BINDIR)/$(CONFIG)/socket_utils_test
+tag_set_test: $(BINDIR)/$(CONFIG)/tag_set_test
 tcp_client_posix_test: $(BINDIR)/$(CONFIG)/tcp_client_posix_test
 tcp_posix_test: $(BINDIR)/$(CONFIG)/tcp_posix_test
 tcp_server_posix_test: $(BINDIR)/$(CONFIG)/tcp_server_posix_test
@@ -1206,6 +1207,7 @@
   $(BINDIR)/$(CONFIG)/sockaddr_resolver_test \
   $(BINDIR)/$(CONFIG)/sockaddr_utils_test \
   $(BINDIR)/$(CONFIG)/socket_utils_test \
+  $(BINDIR)/$(CONFIG)/tag_set_test \
   $(BINDIR)/$(CONFIG)/tcp_client_posix_test \
   $(BINDIR)/$(CONFIG)/tcp_posix_test \
   $(BINDIR)/$(CONFIG)/tcp_server_posix_test \
@@ -1499,6 +1501,8 @@
 	$(Q) $(BINDIR)/$(CONFIG)/sockaddr_utils_test || ( echo test sockaddr_utils_test failed ; exit 1 )
 	$(E) "[RUN]     Testing socket_utils_test"
 	$(Q) $(BINDIR)/$(CONFIG)/socket_utils_test || ( echo test socket_utils_test failed ; exit 1 )
+	$(E) "[RUN]     Testing tag_set_test"
+	$(Q) $(BINDIR)/$(CONFIG)/tag_set_test || ( echo test tag_set_test failed ; exit 1 )
 	$(E) "[RUN]     Testing tcp_client_posix_test"
 	$(Q) $(BINDIR)/$(CONFIG)/tcp_client_posix_test || ( echo test tcp_client_posix_test failed ; exit 1 )
 	$(E) "[RUN]     Testing tcp_posix_test"
@@ -2479,6 +2483,7 @@
     src/core/census/context.c \
     src/core/census/initialize.c \
     src/core/census/operation.c \
+    src/core/census/tag_set.c \
     src/core/census/tracing.c \
 
 PUBLIC_HEADERS_C += \
@@ -2782,6 +2787,7 @@
     src/core/census/context.c \
     src/core/census/initialize.c \
     src/core/census/operation.c \
+    src/core/census/tag_set.c \
     src/core/census/tracing.c \
 
 PUBLIC_HEADERS_C += \
@@ -8344,6 +8350,38 @@
 endif
 
 
+TAG_SET_TEST_SRC = \
+    test/core/census/tag_set_test.c \
+
+TAG_SET_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(TAG_SET_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/tag_set_test: openssl_dep_error
+
+else
+
+
+
+$(BINDIR)/$(CONFIG)/tag_set_test: $(TAG_SET_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) $(TAG_SET_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)/tag_set_test
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/census/tag_set_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_tag_set_test: $(TAG_SET_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(TAG_SET_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 TCP_CLIENT_POSIX_TEST_SRC = \
     test/core/iomgr/tcp_client_posix_test.c \
 
diff --git a/PYTHON-MANIFEST.in b/PYTHON-MANIFEST.in
index 02bd9b5..52ef1ab 100644
--- a/PYTHON-MANIFEST.in
+++ b/PYTHON-MANIFEST.in
@@ -3,6 +3,7 @@
 graft src/core
 graft include/grpc
 graft third_party/boringssl
+graft third_party/zlib
 include src/python/grpcio/commands.py
 include src/python/grpcio/grpc_core_dependencies.py
 include src/python/grpcio/README.rst
diff --git a/README.md b/README.md
index e0553ec..f894def 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 [gRPC - An RPC library and framework](http://github.com/grpc/grpc)
 ===================================
 
-Copyright 2015 Google Inc.
+Copyright 2015-2016 Google Inc.
 
 #Documentation
 
diff --git a/binding.gyp b/binding.gyp
index a5e9933..f6d60b9 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -312,6 +312,7 @@
         'src/core/census/context.c',
         'src/core/census/initialize.c',
         'src/core/census/operation.c',
+        'src/core/census/tag_set.c',
         'src/core/census/tracing.c',
       ],
       "conditions": [
diff --git a/build.yaml b/build.yaml
index 9aaa0d3..500ec85 100644
--- a/build.yaml
+++ b/build.yaml
@@ -20,6 +20,7 @@
   - src/core/census/context.c
   - src/core/census/initialize.c
   - src/core/census/operation.c
+  - src/core/census/tag_set.c
   - src/core/census/tracing.c
 - name: grpc++_base
   public_headers:
@@ -1630,6 +1631,16 @@
   - mac
   - linux
   - posix
+- name: tag_set_test
+  build: test
+  language: c
+  src:
+  - test/core/census/tag_set_test.c
+  deps:
+  - grpc_test_util
+  - grpc
+  - gpr_test_util
+  - gpr
 - name: tcp_client_posix_test
   cpu_cost: 0.5
   build: test
@@ -2625,3 +2636,4 @@
   - grpc
   - gpr
   - boringssl
+  - z
diff --git a/examples/objective-c/auth_sample/MakeRPCViewController.m b/examples/objective-c/auth_sample/MakeRPCViewController.m
index 6013186..108eda4 100644
--- a/examples/objective-c/auth_sample/MakeRPCViewController.m
+++ b/examples/objective-c/auth_sample/MakeRPCViewController.m
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015, Google Inc.
+ * Copyright 2015-2016, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
diff --git a/gRPC.podspec b/gRPC.podspec
index 97e13c0..18c05e2 100644
--- a/gRPC.podspec
+++ b/gRPC.podspec
@@ -429,6 +429,7 @@
                       'src/core/census/context.c',
                       'src/core/census/initialize.c',
                       'src/core/census/operation.c',
+                      'src/core/census/tag_set.c',
                       'src/core/census/tracing.c'
 
     ss.private_header_files = 'src/core/profiling/timers.h',
diff --git a/grpc.gemspec b/grpc.gemspec
index 4f3a3f2..47b66ae 100755
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -412,5 +412,6 @@
   s.files += %w( src/core/census/context.c )
   s.files += %w( src/core/census/initialize.c )
   s.files += %w( src/core/census/operation.c )
+  s.files += %w( src/core/census/tag_set.c )
   s.files += %w( src/core/census/tracing.c )
 end
diff --git a/include/grpc/census.h b/include/grpc/census.h
index 369827d..f4130c7 100644
--- a/include/grpc/census.h
+++ b/include/grpc/census.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015, Google Inc.
+ * Copyright 2015-2016, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -324,61 +324,143 @@
 /** End a scan previously started by census_trace_scan_start() */
 void census_trace_scan_end();
 
-/* Max number of characters in tag key */
-#define CENSUS_MAX_TAG_KEY_LENGTH 20
-/* Max number of tag value characters */
-#define CENSUS_MAX_TAG_VALUE_LENGTH 50
-
 /* A Census tag set is a collection of key:value string pairs; these form the
    basis against which Census metrics will be recorded. Keys are unique within
    a tag set. All contexts have an associated tag set. */
 typedef struct census_tag_set census_tag_set;
 
-/* Returns a pointer to a newly created, empty tag set. If size_hint > 0,
-   indicates that the tag set is intended to hold approximately that number
-   of tags. */
-census_tag_set *census_tag_set_create(size_t size_hint);
+/* A tag is a key:value pair. The key is a non-empty, printable (UTF-8
+   encoded), nil-terminated string. The value is a binary string, that may be
+   printable. There are limits on the sizes of both keys and values (see
+   CENSUS_MAX_TAG_KB_LEN definition below), and the number of tags that can be
+   propagated (CENSUS_MAX_PROPAGATED_TAGS). Users should also remember that
+   some systems may have limits on, e.g., the number of bytes that can be
+   transmitted as metadata, and that larger tags means more memory consumed
+   and time in processing. */
+typedef struct {
+  const char *key;
+  const char *value;
+  size_t value_len;
+  uint8_t flags;
+} census_tag;
 
-/* Add a new tag key/value to an existing tag set; if the tag key already exists
-   in the tag set, then its value is overwritten with the new one. Can also be
-   used to delete a tag, by specifying a NULL value. If key is NULL, returns
-   the number of tags in the tag set.
-   Return values:
-   -1: invalid length key or value
-   non-negative value: the number of tags in the tag set. */
-int census_tag_set_add(census_tag_set *tags, const char *key,
-                       const char *value);
+/* Maximum length of a tag's key or value. */
+#define CENSUS_MAX_TAG_KV_LEN 255
+/* Maximum number of propagatable tags. */
+#define CENSUS_MAX_PROPAGATED_TAGS 255
 
-/* Destroys a tag set. This function must be called to prevent memory leaks.
-   Once called, the tag set cannot be used again. */
+/* Tag flags. */
+#define CENSUS_TAG_PROPAGATE 1 /* Tag should be propagated over RPC */
+#define CENSUS_TAG_STATS 2     /* Tag will be used for statistics aggregation */
+#define CENSUS_TAG_BINARY 4    /* Tag value is not printable */
+#define CENSUS_TAG_RESERVED 8  /* Reserved for internal use. */
+/* Flag values 8,16,32,64,128 are reserved for future/internal use. Clients
+   should not use or rely on their values. */
+
+#define CENSUS_TAG_IS_PROPAGATED(flags) (flags & CENSUS_TAG_PROPAGATE)
+#define CENSUS_TAG_IS_STATS(flags) (flags & CENSUS_TAG_STATS)
+#define CENSUS_TAG_IS_BINARY(flags) (flags & CENSUS_TAG_BINARY)
+
+typedef struct {
+  int n_propagated_tags;        /* number of propagated printable tags */
+  int n_propagated_binary_tags; /* number of propagated binary tags */
+  int n_local_tags;             /* number of non-propagated (local) tags */
+  int n_deleted_tags;           /* number of tags that were deleted */
+  int n_added_tags;             /* number of tags that were added */
+  int n_modified_tags;          /* number of tags that were modified */
+  int n_invalid_tags;           /* number of tags with bad keys or values (e.g.
+                                   longer than CENSUS_MAX_TAG_KV_LEN) */
+  int n_ignored_tags;           /* number of tags ignored because of
+                                   CENSUS_MAX_PROPAGATED_TAGS limit. */
+} census_tag_set_create_status;
+
+/* Create a new tag set, adding and removing tags from an existing tag set.
+   This will copy all tags from it's input parameters, so it is recommended
+   to add as many tags in a single operation as is practical for the client.
+   @param base Base tag set to build upon. Can be NULL.
+   @param tags A set of tags to be added/changed/deleted. Tags with keys that
+   are in 'tags', but not 'base', are added to the tag set. Keys that are in
+   both 'tags' and 'base' will have their value/flags modified. Tags with keys
+   in both, but with NULL or zero-length values, will be deleted from the tag
+   set. Tags with invalid (too long or short) keys or values will be ignored.
+   If adding a tag will result in more than CENSUS_MAX_PROPAGATED_TAGS in either
+   binary or non-binary tags, they will be ignored, as will deletions of
+   tags that don't exist.
+   @param ntags number of tags in 'tags'
+   @param status If not NULL, will return a pointer to a
+   census_tag_set_create_status structure containing information about the new
+   tag set and status of the tags used in its creation.
+   @return A new, valid census_tag_set.
+*/
+census_tag_set *census_tag_set_create(
+    const census_tag_set *base, const census_tag *tags, int ntags,
+    census_tag_set_create_status const **status);
+
+/* Destroy a tag set created by census_tag_set_create(). Once this function
+   has been called, the tag set cannot be reused. */
 void census_tag_set_destroy(census_tag_set *tags);
 
+/* Get a pointer to the original status from the creation of this tag set. */
+const census_tag_set_create_status *census_tag_set_get_create_status(
+    const census_tag_set *tags);
+
+/* Structure used for tag set iteration. API clients should not use or
+   reference internal fields - neither their contents or presence/absence are
+   guaranteed. */
+typedef struct {
+  const census_tag_set *tags;
+  int base;
+  int index;
+  char *kvm;
+} census_tag_set_iterator;
+
+/* Initialize a tag set iterator. Must be called before first use of the
+   iterator. */
+void census_tag_set_initialize_iterator(const census_tag_set *tags,
+                                        census_tag_set_iterator *iterator);
+
+/* Get the contents of the "next" tag in the tag set. If there are no more
+   tags in the tag set, returns 0 (and 'tag' contents will be unchanged),
+   otherwise returns 1. */
+int census_tag_set_next_tag(census_tag_set_iterator *iterator, census_tag *tag);
+
+/* Get a tag by its key. Returns 0 if the key is not present in the tag
+   set. */
+int census_tag_set_get_tag_by_key(const census_tag_set *tags, const char *key,
+                                  census_tag *tag);
+
+/* Tag set encode/decode functionality. These functionas are intended
+   for use by RPC systems only, for purposes of transmitting/receiving tag
+   sets. */
+
+/* Encode a tag set into a buffer. The propagated tags are encoded into the
+   buffer in two regions: one for printable tags, and one for binary tags.
+   @param tags tag set to be encoded
+   @param buffer pointer to buffer. This address will be used to encode the
+                 printable tags.
+   @param buf_size number of available bytes in buffer.
+   @param print_buf_size Will be set to the number of bytes consumed by
+                         printable tags.
+   @param bin_buf_size Will be set to the number of bytes used to encode the
+                       binary tags.
+   @return A pointer to the binary tag's encoded, or NULL if the buffer was
+           insufficiently large to hold the encoded tags. Thus, if successful,
+           printable tags are encoded into
+           [buffer, buffer + *print_buf_size) and binary tags into
+           [returned-ptr, returned-ptr + *bin_buf_size) (and the return value
+           should be buffer + *print_buf_size) */
+char *census_tag_set_encode(const census_tag_set *tags, char *buffer,
+                            size_t buf_size, size_t *print_buf_size,
+                            size_t *bin_buf_size);
+
+/* Decode tag set buffers encoded with census_tag_set_encode_*(). Returns NULL
+   if there is an error in parsing either buffer. */
+census_tag_set *census_tag_set_decode(const char *buffer, size_t size,
+                                      const char *bin_buffer, size_t bin_size);
+
 /* Get a contexts tag set. */
 census_tag_set *census_context_tag_set(census_context *context);
 
-/* A read-only representation of a tag for use by census clients. */
-typedef struct {
-  size_t key_len;    /* Number of bytes in tag key. */
-  const char *key;   /* A pointer to the tag key. May not be null-terminated. */
-  size_t value_len;  /* Number of bytes in tag value. */
-  const char *value; /* Pointer to the tag value. May not be null-terminated. */
-} census_tag_const;
-
-/* Used to iterate through a tag sets contents. */
-typedef struct census_tag_set_iterator census_tag_set_iterator;
-
-/* Open a tag set for iteration. The tag set must not be modified while
-   iteration is ongoing. Returns an iterator for use in following functions. */
-census_tag_set_iterator *census_tag_set_open(census_tag_set *tags);
-
-/* Get the next tag in the tag set, by writing into the 'tag' argument. Returns
-   1 if there is a "next" tag, 0 if there are no more tags. */
-int census_tag_set_next(census_tag_set_iterator *it, census_tag_const *tag);
-
-/* Close an iterator opened by census_tag_set_open(). The iterator will be
-   invalidated, and should not be used once close is called. */
-void census_tag_set_close(census_tag_set_iterator *it);
-
 /* Core stats collection API's. The following concepts are used:
    * Aggregation: A collection of values. Census supports the following
        aggregation types:
diff --git a/package.json b/package.json
index e0bbb7d..0b87f6a 100644
--- a/package.json
+++ b/package.json
@@ -363,6 +363,7 @@
     "src/core/census/context.c",
     "src/core/census/initialize.c",
     "src/core/census/operation.c",
+    "src/core/census/tag_set.c",
     "src/core/census/tracing.c",
     "include/grpc/support/alloc.h",
     "include/grpc/support/atm.h",
diff --git a/setup.py b/setup.py
index 63b56f3..c54ac22 100644
--- a/setup.py
+++ b/setup.py
@@ -45,6 +45,7 @@
 PYTHON_STEM = './src/python/grpcio'
 CORE_INCLUDE = ('./include', '.',)
 BORINGSSL_INCLUDE = ('./third_party/boringssl/include',)
+ZLIB_INCLUDE = ('./third_party/zlib',)
 
 # Ensure we're in the proper directory whether or not we're being used by pip.
 os.chdir(os.path.dirname(os.path.abspath(__file__)))
@@ -75,9 +76,9 @@
 CYTHON_EXTENSION_MODULE_NAMES = ('grpc._cython.cygrpc',)
 
 EXTENSION_INCLUDE_DIRECTORIES = (
-    (PYTHON_STEM,) + CORE_INCLUDE + BORINGSSL_INCLUDE)
+    (PYTHON_STEM,) + CORE_INCLUDE + BORINGSSL_INCLUDE + ZLIB_INCLUDE)
 
-EXTENSION_LIBRARIES = ('z', 'm',)
+EXTENSION_LIBRARIES = ('m',)
 if not "darwin" in sys.platform:
     EXTENSION_LIBRARIES += ('rt',)
 
diff --git a/src/core/census/tag_set.c b/src/core/census/tag_set.c
new file mode 100644
index 0000000..9b65a1d
--- /dev/null
+++ b/src/core/census/tag_set.c
@@ -0,0 +1,535 @@
+/*
+ *
+ * Copyright 2015-2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <grpc/census.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/port_platform.h>
+#include <grpc/support/useful.h>
+#include <stdbool.h>
+#include <string.h>
+#include "src/core/support/string.h"
+
+// Functions in this file support the public tag_set API, as well as
+// encoding/decoding tag_sets as part of context propagation across
+// RPC's. The overall requirements (in approximate priority order) for the
+// tag_set representations:
+// 1. Efficient conversion to/from wire format
+// 2. Minimal bytes used on-wire
+// 3. Efficient tag set creation
+// 4. Efficient lookup of value for a key
+// 5. Efficient lookup of value for an index (to support iteration)
+// 6. Minimal memory footprint
+//
+// Notes on tradeoffs/decisions:
+// * tag includes 1 byte length of key, as well as nil-terminating byte. These
+//   are to aid in efficient parsing and the ability to directly return key
+//   strings. This is more important than saving a single byte/tag on the wire.
+// * The wire encoding uses only single byte values. This eliminates the need
+//   to handle endian-ness conversions. It also means there is a hard upper
+//   limit of 255 for both CENSUS_MAX_TAG_KV_LEN and CENSUS_MAX_PROPAGATED_TAGS.
+// * Keep all tag information (keys/values/flags) in a single memory buffer,
+//   that can be directly copied to the wire.14
+// * Binary tags share the same structure as, but are encoded separately from,
+//   non-binary tags. This is primarily because non-binary tags are far more
+//   likely to be repeated across multiple RPC calls, so are more efficiently
+//   cached and compressed in any metadata schemes.
+// * all lengths etc. are restricted to one byte. This eliminates endian
+//   issues.
+
+// Structure representing a set of tags. Essentially a count of number of tags
+// present, and pointer to a chunk of memory that contains the per-tag details.
+struct tag_set {
+  int ntags;        // number of tags.
+  int ntags_alloc;  // ntags + number of deleted tags (total number of tags
+  // in all of kvm). This will always be == ntags, except during the process
+  // of building a new tag set.
+  size_t kvm_size;  // number of bytes allocated for key/value storage.
+  size_t kvm_used;  // number of bytes of used key/value memory
+  char *kvm;        // key/value memory. Consists of repeated entries of:
+  //   Offset  Size  Description
+  //     0      1    Key length, including trailing 0. (K)
+  //     1      1    Value length. (V)
+  //     2      1    Flags
+  //     3      K    Key bytes
+  //     3 + K  V    Value bytes
+  //
+  // We refer to the first 3 entries as the 'tag header'. If extra values are
+  // introduced in the header, you will need to modify the TAG_HEADER_SIZE
+  // constant, the raw_tag structure (and everything that uses it) and the
+  // encode/decode functions appropriately.
+};
+
+// Number of bytes in tag header.
+#define TAG_HEADER_SIZE 3  // key length (1) + value length (1) + flags (1)
+// Offsets to tag header entries.
+#define KEY_LEN_OFFSET 0
+#define VALUE_LEN_OFFSET 1
+#define FLAG_OFFSET 2
+
+// raw_tag represents the raw-storage form of a tag in the kvm of a tag_set.
+struct raw_tag {
+  uint8_t key_len;
+  uint8_t value_len;
+  uint8_t flags;
+  char *key;
+  char *value;
+};
+
+// Use a reserved flag bit for indication of deleted tag.
+#define CENSUS_TAG_DELETED CENSUS_TAG_RESERVED
+#define CENSUS_TAG_IS_DELETED(flags) (flags & CENSUS_TAG_DELETED)
+
+// Primary (external) representation of a tag set. Composed of 3 underlying
+// tag_set structs, one for each of the binary/printable propagated tags, and
+// one for everything else. This is to efficiently support tag
+// encoding/decoding.
+struct census_tag_set {
+  struct tag_set tags[3];
+  census_tag_set_create_status status;
+};
+
+// Indices into the tags member of census_tag_set
+#define PROPAGATED_TAGS 0
+#define PROPAGATED_BINARY_TAGS 1
+#define LOCAL_TAGS 2
+
+// Extract a raw tag given a pointer (raw) to the tag header. Allow for some
+// extra bytes in the tag header (see encode/decode functions for usage: this
+// allows for future expansion of the tag header).
+static char *decode_tag(struct raw_tag *tag, char *header, int offset) {
+  tag->key_len = (uint8_t)(*header++);
+  tag->value_len = (uint8_t)(*header++);
+  tag->flags = (uint8_t)(*header++);
+  header += offset;
+  tag->key = header;
+  header += tag->key_len;
+  tag->value = header;
+  return header + tag->value_len;
+}
+
+// Make a copy (in 'to') of an existing tag_set.
+static void tag_set_copy(struct tag_set *to, const struct tag_set *from) {
+  memcpy(to, from, sizeof(struct tag_set));
+  to->kvm = gpr_malloc(to->kvm_size);
+  memcpy(to->kvm, from->kvm, from->kvm_used);
+}
+
+// Delete a tag from a tag_set, if it exists (returns true if it did).
+static bool tag_set_delete_tag(struct tag_set *tags, const char *key,
+                               size_t key_len) {
+  char *kvp = tags->kvm;
+  for (int i = 0; i < tags->ntags_alloc; i++) {
+    uint8_t *flags = (uint8_t *)(kvp + FLAG_OFFSET);
+    struct raw_tag tag;
+    kvp = decode_tag(&tag, kvp, 0);
+    if (CENSUS_TAG_IS_DELETED(tag.flags)) continue;
+    if ((key_len == tag.key_len) && (memcmp(key, tag.key, key_len) == 0)) {
+      *flags |= CENSUS_TAG_DELETED;
+      tags->ntags--;
+      return true;
+    }
+  }
+  return false;
+}
+
+// Delete a tag from a census_tag_set, return true if it existed.
+static bool cts_delete_tag(census_tag_set *tags, const census_tag *tag,
+                           size_t key_len) {
+  return (tag_set_delete_tag(&tags->tags[LOCAL_TAGS], tag->key, key_len) ||
+          tag_set_delete_tag(&tags->tags[PROPAGATED_TAGS], tag->key, key_len) ||
+          tag_set_delete_tag(&tags->tags[PROPAGATED_BINARY_TAGS], tag->key,
+                             key_len));
+}
+
+// Add a tag to a tag_set. Return true on sucess, false if the tag could
+// not be added because of constraints on tag set size. This function should
+// not be called if the tag may already exist (in a non-deleted state) in
+// the tag_set, as that would result in two tags with the same key.
+static bool tag_set_add_tag(struct tag_set *tags, const census_tag *tag,
+                            size_t key_len) {
+  if (tags->ntags == CENSUS_MAX_PROPAGATED_TAGS) {
+    return false;
+  }
+  const size_t tag_size = key_len + tag->value_len + TAG_HEADER_SIZE;
+  if (tags->kvm_used + tag_size > tags->kvm_size) {
+    // allocate new memory if needed
+    tags->kvm_size += 2 * CENSUS_MAX_TAG_KV_LEN + TAG_HEADER_SIZE;
+    char *new_kvm = gpr_malloc(tags->kvm_size);
+    memcpy(new_kvm, tags->kvm, tags->kvm_used);
+    gpr_free(tags->kvm);
+    tags->kvm = new_kvm;
+  }
+  char *kvp = tags->kvm + tags->kvm_used;
+  *kvp++ = (char)key_len;
+  *kvp++ = (char)tag->value_len;
+  // ensure reserved flags are not used.
+  *kvp++ = (char)(tag->flags & (CENSUS_TAG_PROPAGATE | CENSUS_TAG_STATS |
+                                CENSUS_TAG_BINARY));
+  memcpy(kvp, tag->key, key_len);
+  kvp += key_len;
+  memcpy(kvp, tag->value, tag->value_len);
+  tags->kvm_used += tag_size;
+  tags->ntags++;
+  tags->ntags_alloc++;
+  return true;
+}
+
+// Add/modify/delete a tag to/in a census_tag_set. Caller must validate that
+// tag key etc. are valid.
+static void cts_modify_tag(census_tag_set *tags, const census_tag *tag,
+                           size_t key_len) {
+  // First delete the tag if it is already present.
+  bool deleted = cts_delete_tag(tags, tag, key_len);
+  // Determine if we need to add it back.
+  bool call_add = tag->value != NULL && tag->value_len != 0;
+  bool added = false;
+  if (call_add) {
+    if (CENSUS_TAG_IS_PROPAGATED(tag->flags)) {
+      if (CENSUS_TAG_IS_BINARY(tag->flags)) {
+        added =
+            tag_set_add_tag(&tags->tags[PROPAGATED_BINARY_TAGS], tag, key_len);
+      } else {
+        added = tag_set_add_tag(&tags->tags[PROPAGATED_TAGS], tag, key_len);
+      }
+    } else {
+      added = tag_set_add_tag(&tags->tags[LOCAL_TAGS], tag, key_len);
+    }
+  }
+  if (deleted) {
+    if (call_add) {
+      tags->status.n_modified_tags++;
+    } else {
+      tags->status.n_deleted_tags++;
+    }
+  } else {
+    if (added) {
+      tags->status.n_added_tags++;
+    } else {
+      tags->status.n_ignored_tags++;
+    }
+  }
+}
+
+// Remove memory used for deleted tags from the tag set. Basic algorithm:
+// 1) Walk through tag set to find first deleted tag. Record where it is.
+// 2) Find the next not-deleted tag. Copy all of kvm from there to the end
+//    "over" the deleted tags
+// 3) repeat #1 and #2 until we have seen all tags
+// 4) if we are still looking for a not-deleted tag, then all the end portion
+//    of the kvm is deleted. Just reduce the used amount of memory by the
+//    appropriate amount.
+static void tag_set_flatten(struct tag_set *tags) {
+  if (tags->ntags == tags->ntags_alloc) return;
+  bool found_deleted = false;  // found a deleted tag.
+  char *kvp = tags->kvm;
+  char *dbase = NULL;  // record location of deleted tag
+  for (int i = 0; i < tags->ntags_alloc; i++) {
+    struct raw_tag tag;
+    char *next_kvp = decode_tag(&tag, kvp, 0);
+    if (found_deleted) {
+      if (!CENSUS_TAG_IS_DELETED(tag.flags)) {
+        ptrdiff_t reduce = kvp - dbase;  // #bytes in deleted tags
+        GPR_ASSERT(reduce > 0);
+        ptrdiff_t copy_size = tags->kvm + tags->kvm_used - kvp;
+        GPR_ASSERT(copy_size > 0);
+        memmove(dbase, kvp, (size_t)copy_size);
+        tags->kvm_used -= (size_t)reduce;
+        next_kvp -= reduce;
+        found_deleted = false;
+      }
+    } else {
+      if (CENSUS_TAG_IS_DELETED(tag.flags)) {
+        dbase = kvp;
+        found_deleted = true;
+      }
+    }
+    kvp = next_kvp;
+  }
+  if (found_deleted) {
+    GPR_ASSERT(dbase > tags->kvm);
+    tags->kvm_used = (size_t)(dbase - tags->kvm);
+  }
+  tags->ntags_alloc = tags->ntags;
+}
+
+census_tag_set *census_tag_set_create(
+    const census_tag_set *base, const census_tag *tags, int ntags,
+    census_tag_set_create_status const **status) {
+  census_tag_set *new_ts = gpr_malloc(sizeof(census_tag_set));
+  // If we are given a base, copy it into our new tag set. Otherwise set it
+  // to zero/NULL everything.
+  if (base == NULL) {
+    memset(new_ts, 0, sizeof(census_tag_set));
+  } else {
+    tag_set_copy(&new_ts->tags[PROPAGATED_TAGS], &base->tags[PROPAGATED_TAGS]);
+    tag_set_copy(&new_ts->tags[PROPAGATED_BINARY_TAGS],
+                 &base->tags[PROPAGATED_BINARY_TAGS]);
+    tag_set_copy(&new_ts->tags[LOCAL_TAGS], &base->tags[LOCAL_TAGS]);
+    memset(&new_ts->status, 0, sizeof(new_ts->status));
+  }
+  // Walk over the additional tags and, for those that aren't invalid, modify
+  // the tag set to add/replace/delete as required.
+  for (int i = 0; i < ntags; i++) {
+    const census_tag *tag = &tags[i];
+    size_t key_len = strlen(tag->key) + 1;
+    // ignore the tag if it is too long/short.
+    if (key_len != 1 && key_len <= CENSUS_MAX_TAG_KV_LEN &&
+        tag->value_len <= CENSUS_MAX_TAG_KV_LEN) {
+      cts_modify_tag(new_ts, tag, key_len);
+    } else {
+      new_ts->status.n_invalid_tags++;
+    }
+  }
+  // Remove any deleted tags, update status if needed, and return.
+  tag_set_flatten(&new_ts->tags[PROPAGATED_TAGS]);
+  tag_set_flatten(&new_ts->tags[PROPAGATED_BINARY_TAGS]);
+  tag_set_flatten(&new_ts->tags[LOCAL_TAGS]);
+  new_ts->status.n_propagated_tags = new_ts->tags[PROPAGATED_TAGS].ntags;
+  new_ts->status.n_propagated_binary_tags =
+      new_ts->tags[PROPAGATED_BINARY_TAGS].ntags;
+  new_ts->status.n_local_tags = new_ts->tags[LOCAL_TAGS].ntags;
+  if (status) {
+    *status = &new_ts->status;
+  }
+  return new_ts;
+}
+
+const census_tag_set_create_status *census_tag_set_get_create_status(
+    const census_tag_set *tags) {
+  return &tags->status;
+}
+
+void census_tag_set_destroy(census_tag_set *tags) {
+  gpr_free(tags->tags[PROPAGATED_TAGS].kvm);
+  gpr_free(tags->tags[PROPAGATED_BINARY_TAGS].kvm);
+  gpr_free(tags->tags[LOCAL_TAGS].kvm);
+  gpr_free(tags);
+}
+
+// Initialize a tag set iterator. Must be called before first use of the
+// iterator.
+void census_tag_set_initialize_iterator(const census_tag_set *tags,
+                                        census_tag_set_iterator *iterator) {
+  iterator->tags = tags;
+  iterator->index = 0;
+  if (tags->tags[PROPAGATED_TAGS].ntags != 0) {
+    iterator->base = PROPAGATED_TAGS;
+    iterator->kvm = tags->tags[PROPAGATED_TAGS].kvm;
+  } else if (tags->tags[PROPAGATED_BINARY_TAGS].ntags != 0) {
+    iterator->base = PROPAGATED_BINARY_TAGS;
+    iterator->kvm = tags->tags[PROPAGATED_BINARY_TAGS].kvm;
+  } else if (tags->tags[LOCAL_TAGS].ntags != 0) {
+    iterator->base = LOCAL_TAGS;
+    iterator->kvm = tags->tags[LOCAL_TAGS].kvm;
+  } else {
+    iterator->base = -1;
+  }
+}
+
+// Get the contents of the "next" tag in the tag set. If there are no more
+// tags in the tag set, returns 0 (and 'tag' contents will be unchanged),
+// otherwise returns 1. */
+int census_tag_set_next_tag(census_tag_set_iterator *iterator,
+                            census_tag *tag) {
+  if (iterator->base < 0) {
+    return 0;
+  }
+  struct raw_tag raw;
+  iterator->kvm = decode_tag(&raw, iterator->kvm, 0);
+  tag->key = raw.key;
+  tag->value = raw.value;
+  tag->value_len = raw.value_len;
+  tag->flags = raw.flags;
+  if (++iterator->index == iterator->tags->tags[iterator->base].ntags) {
+    do {
+      if (iterator->base == LOCAL_TAGS) {
+        iterator->base = -1;
+        return 1;
+      }
+    } while (iterator->tags->tags[++iterator->base].ntags == 0);
+    iterator->index = 0;
+    iterator->kvm = iterator->tags->tags[iterator->base].kvm;
+  }
+  return 1;
+}
+
+// Find a tag in a tag_set by key. Return true if found, false otherwise.
+static bool tag_set_get_tag_by_key(const struct tag_set *tags, const char *key,
+                                   size_t key_len, census_tag *tag) {
+  char *kvp = tags->kvm;
+  for (int i = 0; i < tags->ntags; i++) {
+    struct raw_tag raw;
+    kvp = decode_tag(&raw, kvp, 0);
+    if (key_len == raw.key_len && memcmp(raw.key, key, key_len) == 0) {
+      tag->key = raw.key;
+      tag->value = raw.value;
+      tag->value_len = raw.value_len;
+      tag->flags = raw.flags;
+      return true;
+    }
+  }
+  return false;
+}
+
+int census_tag_set_get_tag_by_key(const census_tag_set *tags, const char *key,
+                                  census_tag *tag) {
+  size_t key_len = strlen(key) + 1;
+  if (key_len == 1) {
+    return 0;
+  }
+  if (tag_set_get_tag_by_key(&tags->tags[PROPAGATED_TAGS], key, key_len, tag) ||
+      tag_set_get_tag_by_key(&tags->tags[PROPAGATED_BINARY_TAGS], key, key_len,
+                             tag) ||
+      tag_set_get_tag_by_key(&tags->tags[LOCAL_TAGS], key, key_len, tag)) {
+    return 1;
+  }
+  return 0;
+}
+
+// tag_set encoding and decoding functions.
+//
+// Wire format for tag sets on the wire:
+//
+// First, a tag set header:
+//
+// offset   bytes  description
+//   0        1    version number
+//   1        1    number of bytes in this header. This allows for future
+//                 expansion.
+//   2        1    number of bytes in each tag header.
+//   3        1    ntags value from tag set.
+//
+//   This is followed by the key/value memory from struct tag_set.
+
+#define ENCODED_VERSION 0      // Version number
+#define ENCODED_HEADER_SIZE 4  // size of tag set header
+
+// Encode a tag set. Returns 0 if buffer is too small.
+static size_t tag_set_encode(const struct tag_set *tags, char *buffer,
+                             size_t buf_size) {
+  if (buf_size < ENCODED_HEADER_SIZE + tags->kvm_used) {
+    return 0;
+  }
+  buf_size -= ENCODED_HEADER_SIZE;
+  *buffer++ = (char)ENCODED_VERSION;
+  *buffer++ = (char)ENCODED_HEADER_SIZE;
+  *buffer++ = (char)TAG_HEADER_SIZE;
+  *buffer++ = (char)tags->ntags;
+  if (tags->ntags == 0) {
+    return ENCODED_HEADER_SIZE;
+  }
+  memcpy(buffer, tags->kvm, tags->kvm_used);
+  return ENCODED_HEADER_SIZE + tags->kvm_used;
+}
+
+char *census_tag_set_encode(const census_tag_set *tags, char *buffer,
+                            size_t buf_size, size_t *print_buf_size,
+                            size_t *bin_buf_size) {
+  *print_buf_size =
+      tag_set_encode(&tags->tags[PROPAGATED_TAGS], buffer, buf_size);
+  if (*print_buf_size == 0) {
+    return NULL;
+  }
+  char *b_buffer = buffer + *print_buf_size;
+  *bin_buf_size = tag_set_encode(&tags->tags[PROPAGATED_BINARY_TAGS], b_buffer,
+                                 buf_size - *print_buf_size);
+  if (*bin_buf_size == 0) {
+    return NULL;
+  }
+  return b_buffer;
+}
+
+// Decode a tag set.
+static void tag_set_decode(struct tag_set *tags, const char *buffer,
+                           size_t size) {
+  uint8_t version = (uint8_t)(*buffer++);
+  uint8_t header_size = (uint8_t)(*buffer++);
+  uint8_t tag_header_size = (uint8_t)(*buffer++);
+  tags->ntags = tags->ntags_alloc = (int)(*buffer++);
+  if (tags->ntags == 0) {
+    tags->ntags_alloc = 0;
+    tags->kvm_size = 0;
+    tags->kvm_used = 0;
+    tags->kvm = NULL;
+    return;
+  }
+  if (header_size != ENCODED_HEADER_SIZE) {
+    GPR_ASSERT(version != ENCODED_VERSION);
+    GPR_ASSERT(ENCODED_HEADER_SIZE < header_size);
+    buffer += (header_size - ENCODED_HEADER_SIZE);
+  }
+  tags->kvm_used = size - header_size;
+  tags->kvm_size = tags->kvm_used + CENSUS_MAX_TAG_KV_LEN;
+  tags->kvm = gpr_malloc(tags->kvm_size);
+  if (tag_header_size != TAG_HEADER_SIZE) {
+    // something new in the tag information. I don't understand it, so
+    // don't copy it over.
+    GPR_ASSERT(version != ENCODED_VERSION);
+    GPR_ASSERT(tag_header_size > TAG_HEADER_SIZE);
+    char *kvp = tags->kvm;
+    for (int i = 0; i < tags->ntags; i++) {
+      memcpy(kvp, buffer, TAG_HEADER_SIZE);
+      kvp += header_size;
+      struct raw_tag raw;
+      buffer =
+          decode_tag(&raw, (char *)buffer, tag_header_size - TAG_HEADER_SIZE);
+      memcpy(kvp, raw.key, (size_t)raw.key_len + raw.value_len);
+      kvp += raw.key_len + raw.value_len;
+    }
+  } else {
+    memcpy(tags->kvm, buffer, tags->kvm_used);
+  }
+}
+
+census_tag_set *census_tag_set_decode(const char *buffer, size_t size,
+                                      const char *bin_buffer, size_t bin_size) {
+  census_tag_set *new_ts = gpr_malloc(sizeof(census_tag_set));
+  memset(&new_ts->tags[LOCAL_TAGS], 0, sizeof(struct tag_set));
+  if (buffer == NULL) {
+    memset(&new_ts->tags[PROPAGATED_TAGS], 0, sizeof(struct tag_set));
+  } else {
+    tag_set_decode(&new_ts->tags[PROPAGATED_TAGS], buffer, size);
+  }
+  if (bin_buffer == NULL) {
+    memset(&new_ts->tags[PROPAGATED_BINARY_TAGS], 0, sizeof(struct tag_set));
+  } else {
+    tag_set_decode(&new_ts->tags[PROPAGATED_BINARY_TAGS], bin_buffer, bin_size);
+  }
+  memset(&new_ts->status, 0, sizeof(new_ts->status));
+  new_ts->status.n_propagated_tags = new_ts->tags[PROPAGATED_TAGS].ntags;
+  new_ts->status.n_propagated_binary_tags =
+      new_ts->tags[PROPAGATED_BINARY_TAGS].ntags;
+  // TODO(aveitch): check that BINARY flag is correct for each type.
+  return new_ts;
+}
diff --git a/src/cpp/server/server_context.cc b/src/cpp/server/server_context.cc
index 8193e70..b3a74c7 100644
--- a/src/cpp/server/server_context.cc
+++ b/src/cpp/server/server_context.cc
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015, Google Inc.
+ * Copyright 2015-2016, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -122,6 +122,7 @@
     : completion_op_(nullptr),
       has_notify_when_done_tag_(false),
       async_notify_when_done_tag_(nullptr),
+      deadline_(gpr_inf_future(GPR_CLOCK_REALTIME)),
       call_(nullptr),
       cq_(nullptr),
       sent_initial_metadata_(false) {}
diff --git a/src/objective-c/examples/Sample/Sample/ViewController.m b/src/objective-c/examples/Sample/Sample/ViewController.m
index 433a8a2..a2bb3ee 100644
--- a/src/objective-c/examples/Sample/Sample/ViewController.m
+++ b/src/objective-c/examples/Sample/Sample/ViewController.m
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015, Google Inc.
+ * Copyright 2015-2016, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
diff --git a/src/objective-c/tests/InteropTestsRemote.m b/src/objective-c/tests/InteropTestsRemote.m
index 758cc93..00eadc2 100644
--- a/src/objective-c/tests/InteropTestsRemote.m
+++ b/src/objective-c/tests/InteropTestsRemote.m
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015, Google Inc.
+ * Copyright 2015-2016, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index 952898d..98ab1ff 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -221,6 +221,7 @@
   'src/core/census/context.c',
   'src/core/census/initialize.c',
   'src/core/census/operation.c',
+  'src/core/census/tag_set.c',
   'src/core/census/tracing.c',
   'src/boringssl/err_data.c',
   'third_party/boringssl/crypto/aes/aes.c',
@@ -516,4 +517,19 @@
   'third_party/boringssl/ssl/t1_enc.c',
   'third_party/boringssl/ssl/t1_lib.c',
   'third_party/boringssl/ssl/tls_record.c',
+  'third_party/zlib/adler32.c',
+  'third_party/zlib/compress.c',
+  'third_party/zlib/crc32.c',
+  'third_party/zlib/deflate.c',
+  'third_party/zlib/gzclose.c',
+  'third_party/zlib/gzlib.c',
+  'third_party/zlib/gzread.c',
+  'third_party/zlib/gzwrite.c',
+  'third_party/zlib/infback.c',
+  'third_party/zlib/inffast.c',
+  'third_party/zlib/inflate.c',
+  'third_party/zlib/inftrees.c',
+  'third_party/zlib/trees.c',
+  'third_party/zlib/uncompr.c',
+  'third_party/zlib/zutil.c',
 ]
diff --git a/test/core/census/tag_set_test.c b/test/core/census/tag_set_test.c
new file mode 100644
index 0000000..1056e98
--- /dev/null
+++ b/test/core/census/tag_set_test.c
@@ -0,0 +1,375 @@
+/*
+ *
+ * Copyright 2015-2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+// Test census_tag_set functions, including encoding/decoding
+
+#include <grpc/census.h>
+#include <grpc/support/log.h>
+#include <grpc/support/time.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "test/core/util/test_config.h"
+
+static uint8_t one_byte_val = 7;
+static uint32_t four_byte_val = 0x12345678;
+static uint64_t eight_byte_val = 0x1234567890abcdef;
+
+// A set of tags Used to create a basic tag_set for testing. Each tag has a
+// unique set of flags. Note that replace_add_delete_test() relies on specific
+// offsets into this array - if you add or delete entries, you will also need
+// to change the test.
+#define BASIC_TAG_COUNT 8
+static census_tag basic_tags[BASIC_TAG_COUNT] = {
+    /* 0 */ {"key0", "printable", 10, 0},
+    /* 1 */ {"k1", "a", 2, CENSUS_TAG_PROPAGATE},
+    /* 2 */ {"k2", "longer printable string", 24, CENSUS_TAG_STATS},
+    /* 3 */ {"key_three", (char *)&one_byte_val, 1, CENSUS_TAG_BINARY},
+    /* 4 */ {"really_long_key_4", "random", 7,
+             CENSUS_TAG_PROPAGATE | CENSUS_TAG_STATS},
+    /* 5 */ {"k5", (char *)&four_byte_val, 4,
+             CENSUS_TAG_PROPAGATE | CENSUS_TAG_BINARY},
+    /* 6 */ {"k6", (char *)&eight_byte_val, 8,
+             CENSUS_TAG_STATS | CENSUS_TAG_BINARY},
+    /* 7 */ {"k7", (char *)&four_byte_val, 4,
+             CENSUS_TAG_PROPAGATE | CENSUS_TAG_STATS | CENSUS_TAG_BINARY}};
+
+// Set of tags used to modify the basic tag_set. Note that
+// replace_add_delete_test() relies on specific offsets into this array - if
+// you add or delete entries, you will also need to change the test. Other
+// tests that rely on specific instances have XXX_XXX_OFFSET definitions (also
+// change the defines below if you add/delete entires).
+#define MODIFY_TAG_COUNT 11
+static census_tag modify_tags[MODIFY_TAG_COUNT] = {
+#define REPLACE_VALUE_OFFSET 0
+    /* 0 */ {"key0", "replace printable", 18, 0},  // replaces tag value only
+#define ADD_TAG_OFFSET 1
+    /* 1 */ {"new_key", "xyzzy", 6, CENSUS_TAG_STATS},  // new tag
+#define DELETE_TAG_OFFSET 2
+    /* 2 */ {"k5", NULL, 5,
+             0},  // should delete tag, despite bogus value length
+    /* 3 */ {"k6", "foo", 0, 0},  // should delete tag, despite bogus value
+    /* 4 */ {"k6", "foo", 0, 0},  // try deleting already-deleted tag
+    /* 5 */ {"non-existent", NULL, 0, 0},  // another non-existent tag
+#define REPLACE_FLAG_OFFSET 6
+    /* 6 */ {"k1", "a", 2, 0},                   // change flags only
+    /* 7 */ {"k7", "bar", 4, CENSUS_TAG_STATS},  // change flags and value
+    /* 8 */ {"k2", (char *)&eight_byte_val, 8,
+             CENSUS_TAG_BINARY | CENSUS_TAG_PROPAGATE},  // more flags change
+                                                         // non-binary -> binary
+    /* 9 */ {"k6", "bar", 4, 0},  // add back tag, with different value
+    /* 10 */ {"foo", "bar", 4, CENSUS_TAG_PROPAGATE},  // another new tag
+};
+
+// Utility function to compare tags. Returns true if all fields match.
+static bool compare_tag(const census_tag *t1, const census_tag *t2) {
+  return (strcmp(t1->key, t2->key) == 0 && t1->value_len == t2->value_len &&
+          memcmp(t1->value, t2->value, t1->value_len) == 0 &&
+          t1->flags == t2->flags);
+}
+
+// Utility function to validate a tag exists in tag set.
+static bool validate_tag(const census_tag_set *cts, const census_tag *tag) {
+  census_tag tag2;
+  if (census_tag_set_get_tag_by_key(cts, tag->key, &tag2) != 1) return false;
+  return compare_tag(tag, &tag2);
+}
+
+// Create an empty tag_set.
+static void empty_test(void) {
+  struct census_tag_set *cts = census_tag_set_create(NULL, NULL, 0, NULL);
+  GPR_ASSERT(cts != NULL);
+  const census_tag_set_create_status *status =
+      census_tag_set_get_create_status(cts);
+  census_tag_set_create_status expected = {0, 0, 0, 0, 0, 0, 0, 0};
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  census_tag_set_destroy(cts);
+}
+
+// Test create and iteration over basic tag set.
+static void basic_test(void) {
+  const census_tag_set_create_status *status;
+  struct census_tag_set *cts =
+      census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT, &status);
+  census_tag_set_create_status expected = {2, 2, 4, 0, 8, 0, 0, 0};
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  census_tag_set_iterator it;
+  census_tag_set_initialize_iterator(cts, &it);
+  census_tag tag;
+  while (census_tag_set_next_tag(&it, &tag)) {
+    // can't rely on tag return order: make sure it matches exactly one.
+    int matches = 0;
+    for (int i = 0; i < BASIC_TAG_COUNT; i++) {
+      if (compare_tag(&tag, &basic_tags[i])) matches++;
+    }
+    GPR_ASSERT(matches == 1);
+  }
+  census_tag_set_destroy(cts);
+}
+
+// Test that census_tag_set_get_tag_by_key().
+static void lookup_by_key_test(void) {
+  struct census_tag_set *cts =
+      census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT, NULL);
+  census_tag tag;
+  for (int i = 0; i < BASIC_TAG_COUNT; i++) {
+    GPR_ASSERT(census_tag_set_get_tag_by_key(cts, basic_tags[i].key, &tag) ==
+               1);
+    GPR_ASSERT(compare_tag(&tag, &basic_tags[i]));
+  }
+  // non-existent keys
+  GPR_ASSERT(census_tag_set_get_tag_by_key(cts, "key", &tag) == 0);
+  GPR_ASSERT(census_tag_set_get_tag_by_key(cts, "key01", &tag) == 0);
+  GPR_ASSERT(census_tag_set_get_tag_by_key(cts, "k9", &tag) == 0);
+  GPR_ASSERT(census_tag_set_get_tag_by_key(cts, "random", &tag) == 0);
+  GPR_ASSERT(census_tag_set_get_tag_by_key(cts, "", &tag) == 0);
+  census_tag_set_destroy(cts);
+}
+
+// Try creating tag set with invalid entries.
+static void invalid_test(void) {
+  char key[300];
+  memset(key, 'k', 299);
+  key[299] = 0;
+  char value[300];
+  memset(value, 'v', 300);
+  census_tag tag = {key, value, 3, CENSUS_TAG_BINARY};
+  // long keys, short value. Key lengths (including terminator) should be
+  // <= 255 (CENSUS_MAX_TAG_KV_LEN)
+  GPR_ASSERT(strlen(key) == 299);
+  const census_tag_set_create_status *status;
+  struct census_tag_set *cts = census_tag_set_create(NULL, &tag, 1, &status);
+  census_tag_set_create_status expected = {0, 0, 0, 0, 0, 0, 1, 0};
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  census_tag_set_destroy(cts);
+  key[CENSUS_MAX_TAG_KV_LEN] = 0;
+  GPR_ASSERT(strlen(key) == CENSUS_MAX_TAG_KV_LEN);
+  cts = census_tag_set_create(NULL, &tag, 1, &status);
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  census_tag_set_destroy(cts);
+  key[CENSUS_MAX_TAG_KV_LEN - 1] = 0;
+  GPR_ASSERT(strlen(key) == CENSUS_MAX_TAG_KV_LEN - 1);
+  cts = census_tag_set_create(NULL, &tag, 1, &status);
+  census_tag_set_create_status expected2 = {0, 0, 1, 0, 1, 0, 0, 0};
+  GPR_ASSERT(memcmp(status, &expected2, sizeof(expected2)) == 0);
+  census_tag_set_destroy(cts);
+  // now try with long values
+  tag.value_len = 300;
+  cts = census_tag_set_create(NULL, &tag, 1, &status);
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  census_tag_set_destroy(cts);
+  tag.value_len = CENSUS_MAX_TAG_KV_LEN + 1;
+  cts = census_tag_set_create(NULL, &tag, 1, &status);
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  census_tag_set_destroy(cts);
+  tag.value_len = CENSUS_MAX_TAG_KV_LEN;
+  cts = census_tag_set_create(NULL, &tag, 1, &status);
+  GPR_ASSERT(memcmp(status, &expected2, sizeof(expected2)) == 0);
+  census_tag_set_destroy(cts);
+  // 0 length key.
+  key[0] = 0;
+  cts = census_tag_set_create(NULL, &tag, 1, &status);
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  census_tag_set_destroy(cts);
+}
+
+// Make a copy of a tag set
+static void copy_test(void) {
+  struct census_tag_set *cts =
+      census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT, NULL);
+  const census_tag_set_create_status *status;
+  struct census_tag_set *cts2 = census_tag_set_create(cts, NULL, 0, &status);
+  census_tag_set_create_status expected = {2, 2, 4, 0, 0, 0, 0, 0};
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  for (int i = 0; i < BASIC_TAG_COUNT; i++) {
+    census_tag tag;
+    GPR_ASSERT(census_tag_set_get_tag_by_key(cts2, basic_tags[i].key, &tag) ==
+               1);
+    GPR_ASSERT(compare_tag(&tag, &basic_tags[i]));
+  }
+  census_tag_set_destroy(cts);
+  census_tag_set_destroy(cts2);
+}
+
+// replace a single tag value
+static void replace_value_test(void) {
+  struct census_tag_set *cts =
+      census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT, NULL);
+  const census_tag_set_create_status *status;
+  struct census_tag_set *cts2 = census_tag_set_create(
+      cts, modify_tags + REPLACE_VALUE_OFFSET, 1, &status);
+  census_tag_set_create_status expected = {2, 2, 4, 0, 0, 1, 0, 0};
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  census_tag tag;
+  GPR_ASSERT(census_tag_set_get_tag_by_key(
+                 cts2, modify_tags[REPLACE_VALUE_OFFSET].key, &tag) == 1);
+  GPR_ASSERT(compare_tag(&tag, &modify_tags[REPLACE_VALUE_OFFSET]));
+  census_tag_set_destroy(cts);
+  census_tag_set_destroy(cts2);
+}
+
+// replace a single tags flags
+static void replace_flags_test(void) {
+  struct census_tag_set *cts =
+      census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT, NULL);
+  const census_tag_set_create_status *status;
+  struct census_tag_set *cts2 =
+      census_tag_set_create(cts, modify_tags + REPLACE_FLAG_OFFSET, 1, &status);
+  census_tag_set_create_status expected = {1, 2, 5, 0, 0, 1, 0, 0};
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  census_tag tag;
+  GPR_ASSERT(census_tag_set_get_tag_by_key(
+                 cts2, modify_tags[REPLACE_FLAG_OFFSET].key, &tag) == 1);
+  GPR_ASSERT(compare_tag(&tag, &modify_tags[REPLACE_FLAG_OFFSET]));
+  census_tag_set_destroy(cts);
+  census_tag_set_destroy(cts2);
+}
+
+// delete a single tag.
+static void delete_tag_test(void) {
+  struct census_tag_set *cts =
+      census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT, NULL);
+  const census_tag_set_create_status *status;
+  struct census_tag_set *cts2 =
+      census_tag_set_create(cts, modify_tags + DELETE_TAG_OFFSET, 1, &status);
+  census_tag_set_create_status expected = {2, 1, 4, 1, 0, 0, 0, 0};
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  census_tag tag;
+  GPR_ASSERT(census_tag_set_get_tag_by_key(
+                 cts2, modify_tags[DELETE_TAG_OFFSET].key, &tag) == 0);
+  census_tag_set_destroy(cts);
+  census_tag_set_destroy(cts2);
+}
+
+// add a single new tag.
+static void add_tag_test(void) {
+  struct census_tag_set *cts =
+      census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT, NULL);
+  const census_tag_set_create_status *status;
+  struct census_tag_set *cts2 =
+      census_tag_set_create(cts, modify_tags + ADD_TAG_OFFSET, 1, &status);
+  census_tag_set_create_status expected = {2, 2, 5, 0, 1, 0, 0, 0};
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  census_tag tag;
+  GPR_ASSERT(census_tag_set_get_tag_by_key(
+                 cts2, modify_tags[ADD_TAG_OFFSET].key, &tag) == 1);
+  GPR_ASSERT(compare_tag(&tag, &modify_tags[ADD_TAG_OFFSET]));
+  census_tag_set_destroy(cts);
+  census_tag_set_destroy(cts2);
+}
+
+// test many changes at once.
+static void replace_add_delete_test(void) {
+  struct census_tag_set *cts =
+      census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT, NULL);
+  const census_tag_set_create_status *status;
+  struct census_tag_set *cts2 =
+      census_tag_set_create(cts, modify_tags, MODIFY_TAG_COUNT, &status);
+  census_tag_set_create_status expected = {2, 1, 6, 2, 3, 4, 0, 2};
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  // validate tag set contents. Use specific indices into the two arrays
+  // holding tag values.
+  GPR_ASSERT(validate_tag(cts2, &basic_tags[3]));
+  GPR_ASSERT(validate_tag(cts2, &basic_tags[4]));
+  GPR_ASSERT(validate_tag(cts2, &modify_tags[0]));
+  GPR_ASSERT(validate_tag(cts2, &modify_tags[1]));
+  GPR_ASSERT(validate_tag(cts2, &modify_tags[6]));
+  GPR_ASSERT(validate_tag(cts2, &modify_tags[7]));
+  GPR_ASSERT(validate_tag(cts2, &modify_tags[8]));
+  GPR_ASSERT(validate_tag(cts2, &modify_tags[9]));
+  GPR_ASSERT(validate_tag(cts2, &modify_tags[10]));
+  GPR_ASSERT(!validate_tag(cts2, &basic_tags[0]));
+  GPR_ASSERT(!validate_tag(cts2, &basic_tags[1]));
+  GPR_ASSERT(!validate_tag(cts2, &basic_tags[2]));
+  GPR_ASSERT(!validate_tag(cts2, &basic_tags[5]));
+  GPR_ASSERT(!validate_tag(cts2, &basic_tags[6]));
+  GPR_ASSERT(!validate_tag(cts2, &basic_tags[7]));
+  census_tag_set_destroy(cts);
+  census_tag_set_destroy(cts2);
+}
+
+#define BUF_SIZE 200
+
+// test encode/decode.
+static void encode_decode_test(void) {
+  char buffer[BUF_SIZE];
+  struct census_tag_set *cts =
+      census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT, NULL);
+  size_t print_bsize;
+  size_t bin_bsize;
+  // Test with too small a buffer
+  GPR_ASSERT(census_tag_set_encode(cts, buffer, 2, &print_bsize, &bin_bsize) ==
+             NULL);
+  char *b_buffer =
+      census_tag_set_encode(cts, buffer, BUF_SIZE, &print_bsize, &bin_bsize);
+  GPR_ASSERT(b_buffer != NULL && print_bsize > 0 && bin_bsize > 0 &&
+             print_bsize + bin_bsize <= BUF_SIZE &&
+             b_buffer == buffer + print_bsize);
+  census_tag_set *cts2 =
+      census_tag_set_decode(buffer, print_bsize, b_buffer, bin_bsize);
+  GPR_ASSERT(cts2 != NULL);
+  const census_tag_set_create_status *status =
+      census_tag_set_get_create_status(cts2);
+  census_tag_set_create_status expected = {2, 2, 0, 0, 0, 0, 0, 0};
+  GPR_ASSERT(memcmp(status, &expected, sizeof(expected)) == 0);
+  for (int i = 0; i < BASIC_TAG_COUNT; i++) {
+    census_tag tag;
+    if (CENSUS_TAG_IS_PROPAGATED(basic_tags[i].flags)) {
+      GPR_ASSERT(census_tag_set_get_tag_by_key(cts2, basic_tags[i].key, &tag) ==
+                 1);
+      GPR_ASSERT(compare_tag(&tag, &basic_tags[i]));
+    } else {
+      GPR_ASSERT(census_tag_set_get_tag_by_key(cts2, basic_tags[i].key, &tag) ==
+                 0);
+    }
+  }
+  census_tag_set_destroy(cts2);
+  census_tag_set_destroy(cts);
+}
+
+int main(int argc, char *argv[]) {
+  grpc_test_init(argc, argv);
+  empty_test();
+  basic_test();
+  lookup_by_key_test();
+  invalid_test();
+  copy_test();
+  replace_value_test();
+  replace_flags_test();
+  delete_tag_test();
+  add_tag_test();
+  replace_add_delete_test();
+  encode_decode_test();
+  return 0;
+}
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index 2280fde..f6b6e59 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -1047,6 +1047,7 @@
 src/core/census/context.c \
 src/core/census/initialize.c \
 src/core/census/operation.c \
+src/core/census/tag_set.c \
 src/core/census/tracing.c \
 include/grpc/support/alloc.h \
 include/grpc/support/atm.h \
diff --git a/tools/run_tests/pre_build_csharp.sh b/tools/run_tests/pre_build_csharp.sh
index 42ff60b..4341c02 100755
--- a/tools/run_tests/pre_build_csharp.sh
+++ b/tools/run_tests/pre_build_csharp.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# Copyright 2015, Google Inc.
+# Copyright 2015-2016, Google Inc.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -35,6 +35,11 @@
 
 root=`pwd`
 
+if [ -x "$(command -v nuget)" ]
+then
+  nuget restore Grpc.sln
+fi
+
 if [ -n "$NUGET" ]
 then
   $NUGET restore Grpc.sln
diff --git a/tools/run_tests/sources_and_headers.json b/tools/run_tests/sources_and_headers.json
index 8d3785b..a311a04 100644
--- a/tools/run_tests/sources_and_headers.json
+++ b/tools/run_tests/sources_and_headers.json
@@ -1098,6 +1098,20 @@
     ], 
     "headers": [], 
     "language": "c", 
+    "name": "tag_set_test", 
+    "src": [
+      "test/core/census/tag_set_test.c"
+    ]
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "gpr_test_util", 
+      "grpc", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "language": "c", 
     "name": "tcp_client_posix_test", 
     "src": [
       "test/core/iomgr/tcp_client_posix_test.c"
@@ -3023,6 +3037,7 @@
       "src/core/census/initialize.c", 
       "src/core/census/operation.c", 
       "src/core/census/rpc_metric_id.h", 
+      "src/core/census/tag_set.c", 
       "src/core/census/tracing.c", 
       "src/core/channel/channel_args.c", 
       "src/core/channel/channel_args.h", 
@@ -3521,6 +3536,7 @@
       "src/core/census/initialize.c", 
       "src/core/census/operation.c", 
       "src/core/census/rpc_metric_id.h", 
+      "src/core/census/tag_set.c", 
       "src/core/census/tracing.c", 
       "src/core/channel/channel_args.c", 
       "src/core/channel/channel_args.h", 
diff --git a/tools/run_tests/tests.json b/tools/run_tests/tests.json
index e3dd29e..8c76b3f 100644
--- a/tools/run_tests/tests.json
+++ b/tools/run_tests/tests.json
@@ -1404,6 +1404,26 @@
     "ci_platforms": [
       "linux", 
       "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "tag_set_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ]
+  }, 
+  {
+    "args": [], 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
       "posix"
     ], 
     "cpu_cost": 0.5, 
diff --git a/vsprojects/buildtests_c.sln b/vsprojects/buildtests_c.sln
index 3996c56..d8ecdea 100644
--- a/vsprojects/buildtests_c.sln
+++ b/vsprojects/buildtests_c.sln
@@ -848,6 +848,17 @@
 		{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} = {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}
 	EndProjectSection
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tag_set_test", "vcxproj\test\tag_set_test\tag_set_test.vcxproj", "{430F8F07-6AAD-0150-B35B-DB9E2E21941A}"
+	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}") = "time_averaged_stats_test", "vcxproj\test\time_averaged_stats_test\time_averaged_stats_test.vcxproj", "{D1EB2A9B-8508-62D7-8FC4-11A11B1CBFD3}"
 	ProjectSection(myProperties) = preProject
         	lib = "False"
@@ -2624,6 +2635,22 @@
 		{529771F0-10B0-9B1A-1E7E-8A8E01870348}.Release-DLL|Win32.Build.0 = Release|Win32
 		{529771F0-10B0-9B1A-1E7E-8A8E01870348}.Release-DLL|x64.ActiveCfg = Release|x64
 		{529771F0-10B0-9B1A-1E7E-8A8E01870348}.Release-DLL|x64.Build.0 = Release|x64
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug|Win32.ActiveCfg = Debug|Win32
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug|x64.ActiveCfg = Debug|x64
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release|Win32.ActiveCfg = Release|Win32
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release|x64.ActiveCfg = Release|x64
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug|Win32.Build.0 = Debug|Win32
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug|x64.Build.0 = Debug|x64
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release|Win32.Build.0 = Release|Win32
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release|x64.Build.0 = Release|x64
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug-DLL|Win32.ActiveCfg = Debug|Win32
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug-DLL|Win32.Build.0 = Debug|Win32
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug-DLL|x64.ActiveCfg = Debug|x64
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug-DLL|x64.Build.0 = Debug|x64
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release-DLL|Win32.ActiveCfg = Release|Win32
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release-DLL|Win32.Build.0 = Release|Win32
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release-DLL|x64.ActiveCfg = Release|x64
+		{430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release-DLL|x64.Build.0 = Release|x64
 		{D1EB2A9B-8508-62D7-8FC4-11A11B1CBFD3}.Debug|Win32.ActiveCfg = Debug|Win32
 		{D1EB2A9B-8508-62D7-8FC4-11A11B1CBFD3}.Debug|x64.ActiveCfg = Debug|x64
 		{D1EB2A9B-8508-62D7-8FC4-11A11B1CBFD3}.Release|Win32.ActiveCfg = Release|Win32
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj b/vsprojects/vcxproj/grpc/grpc.vcxproj
index 8d6800e..438667a 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj
@@ -708,6 +708,8 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\census\operation.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\census\tag_set.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\census\tracing.c">
     </ClCompile>
   </ItemGroup>
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
index f29f881..90395ca 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
@@ -451,6 +451,9 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\census\operation.c">
       <Filter>src\core\census</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\census\tag_set.c">
+      <Filter>src\core\census</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\census\tracing.c">
       <Filter>src\core\census</Filter>
     </ClCompile>
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
index 78ffe14..014e67f 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
@@ -644,6 +644,8 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\census\operation.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\census\tag_set.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\census\tracing.c">
     </ClCompile>
   </ItemGroup>
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
index e068c3f..4f8a77d 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
@@ -391,6 +391,9 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\census\operation.c">
       <Filter>src\core\census</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\census\tag_set.c">
+      <Filter>src\core\census</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\census\tracing.c">
       <Filter>src\core\census</Filter>
     </ClCompile>
diff --git a/vsprojects/vcxproj/test/tag_set_test/tag_set_test.vcxproj b/vsprojects/vcxproj/test/tag_set_test/tag_set_test.vcxproj
new file mode 100644
index 0000000..9a468af
--- /dev/null
+++ b/vsprojects/vcxproj/test/tag_set_test/tag_set_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>{430F8F07-6AAD-0150-B35B-DB9E2E21941A}</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>tag_set_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>tag_set_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\tag_set_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/tag_set_test/tag_set_test.vcxproj.filters b/vsprojects/vcxproj/test/tag_set_test/tag_set_test.vcxproj.filters
new file mode 100644
index 0000000..6b31532
--- /dev/null
+++ b/vsprojects/vcxproj/test/tag_set_test/tag_set_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\tag_set_test.c">
+      <Filter>test\core\census</Filter>
+    </ClCompile>
+  </ItemGroup>
+
+  <ItemGroup>
+    <Filter Include="test">
+      <UniqueIdentifier>{500aa440-5924-8047-996a-4c5096d1ef96}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\core">
+      <UniqueIdentifier>{a3bf80f0-5b13-f623-277b-05f0231dd933}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\core\census">
+      <UniqueIdentifier>{b6ed1b86-7795-4da9-a169-9eccf836852c}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+</Project>
+