Initial import.
diff --git a/src/core/transport/chttp2/frame.h b/src/core/transport/chttp2/frame.h
new file mode 100644
index 0000000..7c0bbe0
--- /dev/null
+++ b/src/core/transport/chttp2/frame.h
@@ -0,0 +1,74 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_H__
+
+#include <grpc/support/port_platform.h>
+
+/* Common definitions for frame handling in the chttp2 transport */
+
+typedef enum {
+  GRPC_CHTTP2_PARSE_OK,
+  GRPC_CHTTP2_STREAM_ERROR,
+  GRPC_CHTTP2_CONNECTION_ERROR
+} grpc_chttp2_parse_error;
+
+typedef struct {
+  gpr_uint8 end_of_stream;
+  gpr_uint8 need_flush_reads;
+  gpr_uint8 metadata_boundary;
+  gpr_uint8 ack_settings;
+  gpr_uint8 send_ping_ack;
+  gpr_uint8 process_ping_reply;
+
+  gpr_uint32 window_update;
+} grpc_chttp2_parse_state;
+
+#define GRPC_CHTTP2_FRAME_DATA 0
+#define GRPC_CHTTP2_FRAME_HEADER 1
+#define GRPC_CHTTP2_FRAME_CONTINUATION 9
+#define GRPC_CHTTP2_FRAME_RST_STREAM 3
+#define GRPC_CHTTP2_FRAME_SETTINGS 4
+#define GRPC_CHTTP2_FRAME_PING 6
+#define GRPC_CHTTP2_FRAME_WINDOW_UPDATE 8
+
+#define GRPC_CHTTP2_MAX_PAYLOAD_LENGTH ((1 << 14) - 1)
+
+#define GRPC_CHTTP2_DATA_FLAG_END_STREAM 1
+#define GRPC_CHTTP2_FLAG_ACK 1
+#define GRPC_CHTTP2_DATA_FLAG_END_HEADERS 4
+#define GRPC_CHTTP2_DATA_FLAG_PADDED 8
+#define GRPC_CHTTP2_FLAG_HAS_PRIORITY 0x20
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_H__ */
diff --git a/src/core/transport/chttp2/frame_data.c b/src/core/transport/chttp2/frame_data.c
new file mode 100644
index 0000000..fbd3b6c
--- /dev/null
+++ b/src/core/transport/chttp2/frame_data.c
@@ -0,0 +1,164 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/frame_data.h"
+
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string.h>
+#include <grpc/support/useful.h>
+#include "src/core/transport/transport.h"
+
+grpc_chttp2_parse_error grpc_chttp2_data_parser_init(
+    grpc_chttp2_data_parser *parser) {
+  parser->state = GRPC_CHTTP2_DATA_FH_0;
+  grpc_sopb_init(&parser->incoming_sopb);
+  return GRPC_CHTTP2_PARSE_OK;
+}
+
+void grpc_chttp2_data_parser_destroy(grpc_chttp2_data_parser *parser) {
+  grpc_sopb_destroy(&parser->incoming_sopb);
+}
+
+grpc_chttp2_parse_error grpc_chttp2_data_parser_begin_frame(
+    grpc_chttp2_data_parser *parser, gpr_uint8 flags) {
+  if (flags & ~GRPC_CHTTP2_DATA_FLAG_END_STREAM) {
+    gpr_log(GPR_ERROR, "unsupported data flags: 0x%02x", flags);
+    return GRPC_CHTTP2_STREAM_ERROR;
+  }
+
+  if (flags & GRPC_CHTTP2_DATA_FLAG_END_STREAM) {
+    parser->is_last_frame = 1;
+  } else {
+    parser->is_last_frame = 0;
+  }
+
+  return GRPC_CHTTP2_PARSE_OK;
+}
+
+grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
+    void *parser, grpc_chttp2_parse_state *state, gpr_slice slice,
+    int is_last) {
+  gpr_uint8 *const beg = GPR_SLICE_START_PTR(slice);
+  gpr_uint8 *const end = GPR_SLICE_END_PTR(slice);
+  gpr_uint8 *cur = beg;
+  grpc_chttp2_data_parser *p = parser;
+
+  if (is_last && p->is_last_frame) {
+    state->end_of_stream = 1;
+    state->need_flush_reads = 1;
+  }
+
+  if (cur == end) {
+    return GRPC_CHTTP2_PARSE_OK;
+  }
+
+  switch (p->state) {
+  fh_0:
+    case GRPC_CHTTP2_DATA_FH_0:
+      p->frame_type = *cur;
+      if (++cur == end) {
+        p->state = GRPC_CHTTP2_DATA_FH_1;
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+      switch (p->frame_type) {
+        case 0:
+          break;
+        case 1:
+          gpr_log(GPR_ERROR, "Compressed GRPC frames not yet supported");
+          return GRPC_CHTTP2_STREAM_ERROR;
+        default:
+          gpr_log(GPR_ERROR, "Bad GRPC frame type 0x%02x", p->frame_type);
+          return GRPC_CHTTP2_STREAM_ERROR;
+      }
+    /* fallthrough */
+    case GRPC_CHTTP2_DATA_FH_1:
+      p->frame_size = ((gpr_uint32)*cur) << 24;
+      if (++cur == end) {
+        p->state = GRPC_CHTTP2_DATA_FH_2;
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+    /* fallthrough */
+    case GRPC_CHTTP2_DATA_FH_2:
+      p->frame_size |= ((gpr_uint32)*cur) << 16;
+      if (++cur == end) {
+        p->state = GRPC_CHTTP2_DATA_FH_3;
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+    /* fallthrough */
+    case GRPC_CHTTP2_DATA_FH_3:
+      p->frame_size |= ((gpr_uint32)*cur) << 8;
+      if (++cur == end) {
+        p->state = GRPC_CHTTP2_DATA_FH_4;
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+    /* fallthrough */
+    case GRPC_CHTTP2_DATA_FH_4:
+      p->frame_size |= ((gpr_uint32)*cur);
+      p->state = GRPC_CHTTP2_DATA_FRAME;
+      ++cur;
+      state->need_flush_reads = 1;
+      grpc_sopb_add_begin_message(&p->incoming_sopb, p->frame_size, 0);
+    /* fallthrough */
+    case GRPC_CHTTP2_DATA_FRAME:
+      if (cur == end) {
+        return GRPC_CHTTP2_PARSE_OK;
+      } else if (end - cur == p->frame_size) {
+        state->need_flush_reads = 1;
+        grpc_sopb_add_slice(&p->incoming_sopb,
+                            gpr_slice_sub(slice, cur - beg, end - beg));
+        p->state = GRPC_CHTTP2_DATA_FH_0;
+        return GRPC_CHTTP2_PARSE_OK;
+      } else if (end - cur > p->frame_size) {
+        state->need_flush_reads = 1;
+        grpc_sopb_add_slice(
+            &p->incoming_sopb,
+            gpr_slice_sub(slice, cur - beg, cur + p->frame_size - beg));
+        cur += p->frame_size;
+        goto fh_0; /* loop */
+      } else {
+        state->need_flush_reads = 1;
+        grpc_sopb_add_slice(&p->incoming_sopb,
+                            gpr_slice_sub(slice, cur - beg, end - beg));
+        p->frame_size -= (end - cur);
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+  }
+
+  gpr_log(GPR_ERROR, "should never reach here");
+  abort();
+  return GRPC_CHTTP2_CONNECTION_ERROR;
+}
+
diff --git a/src/core/transport/chttp2/frame_data.h b/src/core/transport/chttp2/frame_data.h
new file mode 100644
index 0000000..abe26da
--- /dev/null
+++ b/src/core/transport/chttp2/frame_data.h
@@ -0,0 +1,80 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_DATA_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_DATA_H__
+
+/* Parser for GRPC streams embedded in DATA frames */
+
+#include <grpc/support/slice.h>
+#include <grpc/support/slice_buffer.h>
+#include "src/core/transport/stream_op.h"
+#include "src/core/transport/chttp2/frame.h"
+
+typedef enum {
+  GRPC_CHTTP2_DATA_FH_0,
+  GRPC_CHTTP2_DATA_FH_1,
+  GRPC_CHTTP2_DATA_FH_2,
+  GRPC_CHTTP2_DATA_FH_3,
+  GRPC_CHTTP2_DATA_FH_4,
+  GRPC_CHTTP2_DATA_FRAME
+} grpc_chttp2_stream_state;
+
+typedef struct {
+  grpc_chttp2_stream_state state;
+  gpr_uint8 is_last_frame;
+  gpr_uint8 frame_type;
+  gpr_uint32 frame_size;
+
+  grpc_stream_op_buffer incoming_sopb;
+} grpc_chttp2_data_parser;
+
+/* initialize per-stream state for data frame parsing */
+grpc_chttp2_parse_error grpc_chttp2_data_parser_init(
+    grpc_chttp2_data_parser *parser);
+
+void grpc_chttp2_data_parser_destroy(grpc_chttp2_data_parser *parser);
+
+/* start processing a new data frame */
+grpc_chttp2_parse_error grpc_chttp2_data_parser_begin_frame(
+    grpc_chttp2_data_parser *parser, gpr_uint8 flags);
+
+/* handle a slice of a data frame - is_last indicates the last slice of a
+   frame */
+grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
+    void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, int is_last);
+
+/* create a slice with an empty data frame and is_last set */
+gpr_slice grpc_chttp2_data_frame_create_empty_close(gpr_uint32 id);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_DATA_H__ */
diff --git a/src/core/transport/chttp2/frame_ping.c b/src/core/transport/chttp2/frame_ping.c
new file mode 100644
index 0000000..9556c0c
--- /dev/null
+++ b/src/core/transport/chttp2/frame_ping.c
@@ -0,0 +1,93 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/frame_ping.h"
+
+#include <string.h>
+
+#include <grpc/support/log.h>
+
+gpr_slice grpc_chttp2_ping_create(gpr_uint8 ack, gpr_uint8 *opaque_8bytes) {
+  gpr_slice slice = gpr_slice_malloc(9 + 8);
+  gpr_uint8 *p = GPR_SLICE_START_PTR(slice);
+
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 8;
+  *p++ = GRPC_CHTTP2_FRAME_PING;
+  *p++ = ack ? 1 : 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  memcpy(p, opaque_8bytes, 8);
+
+  return slice;
+}
+
+grpc_chttp2_parse_error grpc_chttp2_ping_parser_begin_frame(
+    grpc_chttp2_ping_parser *parser, gpr_uint32 length, gpr_uint8 flags) {
+  if (flags & 0xfe || length != 8) {
+    gpr_log(GPR_ERROR, "invalid ping: length=%d, flags=%02x", length, flags);
+    return GRPC_CHTTP2_CONNECTION_ERROR;
+  }
+  parser->byte = 0;
+  parser->is_ack = flags;
+  return GRPC_CHTTP2_PARSE_OK;
+}
+
+grpc_chttp2_parse_error grpc_chttp2_ping_parser_parse(
+    void *parser, grpc_chttp2_parse_state *state, gpr_slice slice,
+    int is_last) {
+  gpr_uint8 *const beg = GPR_SLICE_START_PTR(slice);
+  gpr_uint8 *const end = GPR_SLICE_END_PTR(slice);
+  gpr_uint8 *cur = beg;
+  grpc_chttp2_ping_parser *p = parser;
+
+  while (p->byte != 8 && cur != end) {
+    p->opaque_8bytes[p->byte] = *cur;
+    cur++;
+    p->byte++;
+  }
+
+  if (p->byte == 8) {
+    GPR_ASSERT(is_last);
+    if (p->is_ack) {
+      state->process_ping_reply = 1;
+    } else {
+      state->send_ping_ack = 1;
+    }
+  }
+
+  return GRPC_CHTTP2_PARSE_OK;
+}
diff --git a/src/core/transport/chttp2/frame_ping.h b/src/core/transport/chttp2/frame_ping.h
new file mode 100644
index 0000000..a64d536
--- /dev/null
+++ b/src/core/transport/chttp2/frame_ping.h
@@ -0,0 +1,53 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_PING_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_PING_H__
+
+#include <grpc/support/slice.h>
+#include "src/core/transport/chttp2/frame.h"
+
+typedef struct {
+  gpr_uint8 byte;
+  gpr_uint8 is_ack;
+  gpr_uint8 opaque_8bytes[8];
+} grpc_chttp2_ping_parser;
+
+gpr_slice grpc_chttp2_ping_create(gpr_uint8 ack, gpr_uint8 *opaque_8bytes);
+
+grpc_chttp2_parse_error grpc_chttp2_ping_parser_begin_frame(
+    grpc_chttp2_ping_parser *parser, gpr_uint32 length, gpr_uint8 flags);
+grpc_chttp2_parse_error grpc_chttp2_ping_parser_parse(
+    void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, int is_last);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_PING_H__ */
diff --git a/src/core/transport/chttp2/frame_rst_stream.c b/src/core/transport/chttp2/frame_rst_stream.c
new file mode 100644
index 0000000..825e156
--- /dev/null
+++ b/src/core/transport/chttp2/frame_rst_stream.c
@@ -0,0 +1,56 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/frame_rst_stream.h"
+#include "src/core/transport/chttp2/frame.h"
+
+gpr_slice grpc_chttp2_rst_stream_create(gpr_uint32 id, gpr_uint32 code) {
+  gpr_slice slice = gpr_slice_malloc(13);
+  gpr_uint8 *p = GPR_SLICE_START_PTR(slice);
+
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 4;
+  *p++ = GRPC_CHTTP2_FRAME_RST_STREAM;
+  *p++ = 0;
+  *p++ = id >> 24;
+  *p++ = id >> 16;
+  *p++ = id >> 8;
+  *p++ = id;
+  *p++ = code >> 24;
+  *p++ = code >> 16;
+  *p++ = code >> 8;
+  *p++ = code;
+
+  return slice;
+}
diff --git a/src/core/transport/chttp2/frame_rst_stream.h b/src/core/transport/chttp2/frame_rst_stream.h
new file mode 100644
index 0000000..78aea0f
--- /dev/null
+++ b/src/core/transport/chttp2/frame_rst_stream.h
@@ -0,0 +1,41 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_RST_STREAM_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_RST_STREAM_H__
+
+#include <grpc/support/slice.h>
+
+gpr_slice grpc_chttp2_rst_stream_create(gpr_uint32 stream_id, gpr_uint32 code);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_RST_STREAM_H__ */
diff --git a/src/core/transport/chttp2/frame_settings.c b/src/core/transport/chttp2/frame_settings.c
new file mode 100644
index 0000000..488b96a
--- /dev/null
+++ b/src/core/transport/chttp2/frame_settings.c
@@ -0,0 +1,227 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/frame_settings.h"
+
+#include <string.h>
+
+#include "src/core/transport/chttp2/frame.h"
+#include <grpc/support/log.h>
+#include <grpc/support/useful.h>
+
+/* HTTP/2 mandated initial connection settings */
+const grpc_chttp2_setting_parameters
+    grpc_chttp2_settings_parameters[GRPC_CHTTP2_NUM_SETTINGS] = {
+        {NULL, 0, 0, 0, GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE},
+        {"HEADER_TABLE_SIZE", 4096, 0, 0xffffffff,
+         GRPC_CHTTP2_CLAMP_INVALID_VALUE},
+        {"ENABLE_PUSH", 1, 0, 1, GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE},
+        {"MAX_CONCURRENT_STREAMS", 0xffffffffu, 0, 0xffffffffu,
+         GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE},
+        {"INITIAL_WINDOW_SIZE", 65535, 0, 0xffffffffu,
+         GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE},
+        {"MAX_FRAME_SIZE", 16384, 16384, 16777215,
+         GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE},
+        {"MAX_HEADER_LIST_SIZE", 0xffffffffu, 0, 0xffffffffu,
+         GRPC_CHTTP2_CLAMP_INVALID_VALUE},
+};
+
+static gpr_uint8 *fill_header(gpr_uint8 *out, gpr_uint32 length,
+                              gpr_uint8 flags) {
+  *out++ = length >> 16;
+  *out++ = length >> 8;
+  *out++ = length;
+  *out++ = GRPC_CHTTP2_FRAME_SETTINGS;
+  *out++ = flags;
+  *out++ = 0;
+  *out++ = 0;
+  *out++ = 0;
+  *out++ = 0;
+  return out;
+}
+
+gpr_slice grpc_chttp2_settings_create(gpr_uint32 *old, const gpr_uint32 *new,
+                                      size_t count) {
+  size_t i;
+  size_t n = 0;
+  gpr_slice output;
+  gpr_uint8 *p;
+
+  for (i = 0; i < count; i++) {
+    n += (new[i] != old[i]);
+  }
+
+  output = gpr_slice_malloc(9 + 6 * n);
+  p = fill_header(GPR_SLICE_START_PTR(output), 6 * n, 0);
+
+  for (i = 0; i < count; i++) {
+    if (new[i] != old[i]) {
+      GPR_ASSERT(i);
+      *p++ = i >> 8;
+      *p++ = i;
+      *p++ = new[i] >> 24;
+      *p++ = new[i] >> 16;
+      *p++ = new[i] >> 8;
+      *p++ = new[i];
+      old[i] = new[i];
+    }
+  }
+
+  GPR_ASSERT(p == GPR_SLICE_END_PTR(output));
+
+  return output;
+}
+
+gpr_slice grpc_chttp2_settings_ack_create() {
+  gpr_slice output = gpr_slice_malloc(9);
+  fill_header(GPR_SLICE_START_PTR(output), 0, GRPC_CHTTP2_FLAG_ACK);
+  return output;
+}
+
+grpc_chttp2_parse_error grpc_chttp2_settings_parser_begin_frame(
+    grpc_chttp2_settings_parser *parser, gpr_uint32 length, gpr_uint8 flags,
+    gpr_uint32 *settings) {
+  parser->target_settings = settings;
+  memcpy(parser->incoming_settings, settings,
+         GRPC_CHTTP2_NUM_SETTINGS * sizeof(gpr_uint32));
+  parser->is_ack = 0;
+  parser->state = GRPC_CHTTP2_SPS_ID0;
+  if (flags == GRPC_CHTTP2_FLAG_ACK) {
+    parser->is_ack = 1;
+    if (length != 0) {
+      gpr_log(GPR_ERROR, "non-empty settings ack frame received");
+      return GRPC_CHTTP2_CONNECTION_ERROR;
+    }
+    return GRPC_CHTTP2_PARSE_OK;
+  } else if (flags != 0) {
+    gpr_log(GPR_ERROR, "invalid flags on settings frame");
+    return GRPC_CHTTP2_CONNECTION_ERROR;
+  } else if (length % 6 != 0) {
+    gpr_log(GPR_ERROR, "settings frames must be a multiple of six bytes");
+    return GRPC_CHTTP2_CONNECTION_ERROR;
+  } else {
+    return GRPC_CHTTP2_PARSE_OK;
+  }
+}
+
+grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse(
+    void *p, grpc_chttp2_parse_state *state, gpr_slice slice, int is_last) {
+  grpc_chttp2_settings_parser *parser = p;
+  const gpr_uint8 *cur = GPR_SLICE_START_PTR(slice);
+  const gpr_uint8 *end = GPR_SLICE_END_PTR(slice);
+
+  if (parser->is_ack) {
+    return GRPC_CHTTP2_PARSE_OK;
+  }
+
+  for (;;) {
+    switch (parser->state) {
+      case GRPC_CHTTP2_SPS_ID0:
+        if (cur == end) {
+          parser->state = GRPC_CHTTP2_SPS_ID0;
+          if (is_last) {
+            memcpy(parser->target_settings, parser->incoming_settings,
+                   GRPC_CHTTP2_NUM_SETTINGS * sizeof(gpr_uint32));
+            state->ack_settings = 1;
+          }
+          return GRPC_CHTTP2_PARSE_OK;
+        }
+        parser->id = ((gpr_uint16)*cur) << 8;
+        cur++;
+      /* fallthrough */
+      case GRPC_CHTTP2_SPS_ID1:
+        if (cur == end) {
+          parser->state = GRPC_CHTTP2_SPS_ID1;
+          return GRPC_CHTTP2_PARSE_OK;
+        }
+        parser->id |= (*cur);
+        cur++;
+      /* fallthrough */
+      case GRPC_CHTTP2_SPS_VAL0:
+        if (cur == end) {
+          parser->state = GRPC_CHTTP2_SPS_VAL0;
+          return GRPC_CHTTP2_PARSE_OK;
+        }
+        parser->value = ((gpr_uint32)*cur) << 24;
+        cur++;
+      /* fallthrough */
+      case GRPC_CHTTP2_SPS_VAL1:
+        if (cur == end) {
+          parser->state = GRPC_CHTTP2_SPS_VAL1;
+          return GRPC_CHTTP2_PARSE_OK;
+        }
+        parser->value |= ((gpr_uint32)*cur) << 16;
+        cur++;
+      /* fallthrough */
+      case GRPC_CHTTP2_SPS_VAL2:
+        if (cur == end) {
+          parser->state = GRPC_CHTTP2_SPS_VAL2;
+          return GRPC_CHTTP2_PARSE_OK;
+        }
+        parser->value |= ((gpr_uint32)*cur) << 8;
+        cur++;
+      /* fallthrough */
+      case GRPC_CHTTP2_SPS_VAL3:
+        if (cur == end) {
+          parser->state = GRPC_CHTTP2_SPS_VAL3;
+          return GRPC_CHTTP2_PARSE_OK;
+        } else {
+          parser->state = GRPC_CHTTP2_SPS_ID0;
+        }
+        parser->value |= *cur;
+        cur++;
+
+        if (parser->id > 0 && parser->id < GRPC_CHTTP2_NUM_SETTINGS) {
+          const grpc_chttp2_setting_parameters *sp =
+              &grpc_chttp2_settings_parameters[parser->id];
+          if (parser->value < sp->min_value || parser->value > sp->max_value) {
+            switch (sp->invalid_value_behavior) {
+              case GRPC_CHTTP2_CLAMP_INVALID_VALUE:
+                parser->value =
+                    GPR_CLAMP(parser->value, sp->min_value, sp->max_value);
+                break;
+              case GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE:
+                gpr_log(GPR_ERROR, "invalid value %u passed for %s",
+                        parser->value, sp->name);
+                return GRPC_CHTTP2_CONNECTION_ERROR;
+            }
+          }
+          parser->incoming_settings[parser->id] = parser->value;
+        } else {
+          gpr_log(GPR_ERROR, "CHTTP2: Ignoring unknown setting %d (value %d)",
+                  parser->id, parser->value);
+        }
+        break;
+    }
+  }
+}
diff --git a/src/core/transport/chttp2/frame_settings.h b/src/core/transport/chttp2/frame_settings.h
new file mode 100644
index 0000000..74e2b4f
--- /dev/null
+++ b/src/core/transport/chttp2/frame_settings.h
@@ -0,0 +1,99 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_SETTINGS_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_SETTINGS_H__
+
+#include <grpc/support/port_platform.h>
+#include <grpc/support/slice.h>
+#include "src/core/transport/chttp2/frame.h"
+
+typedef enum {
+  GRPC_CHTTP2_SPS_ID0,
+  GRPC_CHTTP2_SPS_ID1,
+  GRPC_CHTTP2_SPS_VAL0,
+  GRPC_CHTTP2_SPS_VAL1,
+  GRPC_CHTTP2_SPS_VAL2,
+  GRPC_CHTTP2_SPS_VAL3
+} grpc_chttp2_settings_parse_state;
+
+/* The things HTTP/2 defines as connection level settings */
+typedef enum {
+  GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE = 1,
+  GRPC_CHTTP2_SETTINGS_ENABLE_PUSH = 2,
+  GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS = 3,
+  GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 4,
+  GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE = 5,
+  GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 6,
+  GRPC_CHTTP2_NUM_SETTINGS
+} grpc_chttp2_setting_id;
+
+typedef struct {
+  grpc_chttp2_settings_parse_state state;
+  gpr_uint32 *target_settings;
+  gpr_uint8 is_ack;
+  gpr_uint16 id;
+  gpr_uint32 value;
+  gpr_uint32 incoming_settings[GRPC_CHTTP2_NUM_SETTINGS];
+} grpc_chttp2_settings_parser;
+
+typedef enum {
+  GRPC_CHTTP2_CLAMP_INVALID_VALUE,
+  GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE
+} grpc_chttp2_invalid_value_behavior;
+
+typedef struct {
+  const char *name;
+  gpr_uint32 default_value;
+  gpr_uint32 min_value;
+  gpr_uint32 max_value;
+  grpc_chttp2_invalid_value_behavior invalid_value_behavior;
+} grpc_chttp2_setting_parameters;
+
+/* HTTP/2 mandated connection setting parameters */
+extern const grpc_chttp2_setting_parameters
+    grpc_chttp2_settings_parameters[GRPC_CHTTP2_NUM_SETTINGS];
+
+/* Create a settings frame by diffing old & new, and updating old to be new */
+gpr_slice grpc_chttp2_settings_create(gpr_uint32 *old, const gpr_uint32 *new,
+                                      size_t count);
+/* Create an ack settings frame */
+gpr_slice grpc_chttp2_settings_ack_create();
+
+grpc_chttp2_parse_error grpc_chttp2_settings_parser_begin_frame(
+    grpc_chttp2_settings_parser *parser, gpr_uint32 length, gpr_uint8 flags,
+    gpr_uint32 *settings);
+grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse(
+    void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, int is_last);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_SETTINGS_H__ */
diff --git a/src/core/transport/chttp2/frame_window_update.c b/src/core/transport/chttp2/frame_window_update.c
new file mode 100644
index 0000000..f61714f
--- /dev/null
+++ b/src/core/transport/chttp2/frame_window_update.c
@@ -0,0 +1,99 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/frame_window_update.h"
+
+#include <grpc/support/log.h>
+
+gpr_slice grpc_chttp2_window_update_create(gpr_uint32 id,
+                                           gpr_uint32 window_update) {
+  gpr_slice slice = gpr_slice_malloc(13);
+  gpr_uint8 *p = GPR_SLICE_START_PTR(slice);
+
+  GPR_ASSERT(window_update);
+
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 4;
+  *p++ = GRPC_CHTTP2_FRAME_WINDOW_UPDATE;
+  *p++ = 0;
+  *p++ = id >> 24;
+  *p++ = id >> 16;
+  *p++ = id >> 8;
+  *p++ = id;
+  *p++ = window_update >> 24;
+  *p++ = window_update >> 16;
+  *p++ = window_update >> 8;
+  *p++ = window_update;
+
+  return slice;
+}
+
+grpc_chttp2_parse_error grpc_chttp2_window_update_parser_begin_frame(
+    grpc_chttp2_window_update_parser *parser, gpr_uint32 length,
+    gpr_uint8 flags) {
+  if (flags || length != 4) {
+    gpr_log(GPR_ERROR, "invalid window update: length=%d, flags=%02x", length,
+            flags);
+    return GRPC_CHTTP2_CONNECTION_ERROR;
+  }
+  parser->byte = 0;
+  parser->amount = 0;
+  return GRPC_CHTTP2_PARSE_OK;
+}
+
+grpc_chttp2_parse_error grpc_chttp2_window_update_parser_parse(
+    void *parser, grpc_chttp2_parse_state *state, gpr_slice slice,
+    int is_last) {
+  gpr_uint8 *const beg = GPR_SLICE_START_PTR(slice);
+  gpr_uint8 *const end = GPR_SLICE_END_PTR(slice);
+  gpr_uint8 *cur = beg;
+  grpc_chttp2_window_update_parser *p = parser;
+
+  while (p->byte != 4 && cur != end) {
+    p->amount |= ((gpr_uint32)*cur) << (8 * (3 - p->byte));
+    cur++;
+    p->byte++;
+  }
+
+  if (p->byte == 4) {
+    if (p->amount == 0 || (p->amount & 0x80000000u)) {
+      gpr_log(GPR_ERROR, "invalid window update bytes: %d", p->amount);
+      return GRPC_CHTTP2_CONNECTION_ERROR;
+    }
+    GPR_ASSERT(is_last);
+    state->window_update = p->amount;
+  }
+
+  return GRPC_CHTTP2_PARSE_OK;
+}
diff --git a/src/core/transport/chttp2/frame_window_update.h b/src/core/transport/chttp2/frame_window_update.h
new file mode 100644
index 0000000..4b789fc
--- /dev/null
+++ b/src/core/transport/chttp2/frame_window_update.h
@@ -0,0 +1,55 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_WINDOW_UPDATE_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_WINDOW_UPDATE_H__
+
+#include <grpc/support/slice.h>
+#include "src/core/transport/chttp2/frame.h"
+
+typedef struct {
+  gpr_uint8 byte;
+  gpr_uint8 is_connection_update;
+  gpr_uint32 amount;
+} grpc_chttp2_window_update_parser;
+
+gpr_slice grpc_chttp2_window_update_create(gpr_uint32 id,
+                                           gpr_uint32 window_delta);
+
+grpc_chttp2_parse_error grpc_chttp2_window_update_parser_begin_frame(
+    grpc_chttp2_window_update_parser *parser, gpr_uint32 length,
+    gpr_uint8 flags);
+grpc_chttp2_parse_error grpc_chttp2_window_update_parser_parse(
+    void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, int is_last);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_WINDOW_UPDATE_H__ */
diff --git a/src/core/transport/chttp2/gen_hpack_tables.c b/src/core/transport/chttp2/gen_hpack_tables.c
new file mode 100644
index 0000000..cc94a73
--- /dev/null
+++ b/src/core/transport/chttp2/gen_hpack_tables.c
@@ -0,0 +1,589 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/* generates constant tables for hpack.c */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <grpc/support/log.h>
+
+/*
+ * first byte LUT generation
+ */
+
+typedef struct {
+  const char *call;
+  /* bit prefix for the field type */
+  unsigned char prefix;
+  /* length of the bit prefix for the field type */
+  unsigned char prefix_length;
+  /* index value: 0 = all zeros, 2 = all ones, 1 otherwise */
+  unsigned char index;
+} spec;
+
+static const spec fields[] = {
+    {"INDEXED_FIELD", 0X80, 1, 1},
+    {"INDEXED_FIELD_X", 0X80, 1, 2},
+    {"LITHDR_INCIDX", 0X40, 2, 1},
+    {"LITHDR_INCIDX_X", 0X40, 2, 2},
+    {"LITHDR_INCIDX_V", 0X40, 2, 0},
+    {"LITHDR_NOTIDX", 0X00, 4, 1},
+    {"LITHDR_NOTIDX_X", 0X00, 4, 2},
+    {"LITHDR_NOTIDX_V", 0X00, 4, 0},
+    {"LITHDR_NVRIDX", 0X10, 4, 1},
+    {"LITHDR_NVRIDX_X", 0X10, 4, 2},
+    {"LITHDR_NVRIDX_V", 0X10, 4, 0},
+    {"MAX_TBL_SIZE", 0X20, 3, 1},
+    {"MAX_TBL_SIZE_X", 0X20, 3, 2},
+};
+
+static const int num_fields = sizeof(fields) / sizeof(*fields);
+
+static unsigned char prefix_mask(unsigned char prefix_len) {
+  unsigned char i;
+  unsigned char out = 0;
+  for (i = 0; i < prefix_len; i++) {
+    out |= 1 << (7 - i);
+  }
+  return out;
+}
+
+static unsigned char suffix_mask(unsigned char prefix_len) {
+  return ~prefix_mask(prefix_len);
+}
+
+static void generate_first_byte_lut() {
+  int i, j, n;
+  const spec *chrspec;
+  unsigned char suffix;
+
+  n = printf("static CALLTYPE first_byte[256] = {");
+  /* for each potential first byte of a header */
+  for (i = 0; i < 256; i++) {
+    /* find the field type that matches it */
+    chrspec = NULL;
+    for (j = 0; j < num_fields; j++) {
+      if ((prefix_mask(fields[j].prefix_length) & i) == fields[j].prefix) {
+        suffix = suffix_mask(fields[j].prefix_length) & i;
+        if (suffix == suffix_mask(fields[j].prefix_length)) {
+          if (fields[j].index != 2) continue;
+        } else if (suffix == 0) {
+          if (fields[j].index != 0) continue;
+        } else {
+          if (fields[j].index != 1) continue;
+        }
+        GPR_ASSERT(chrspec == NULL);
+        chrspec = &fields[j];
+      }
+    }
+    if (chrspec) {
+      n += printf("%s, ", chrspec->call);
+    } else {
+      n += printf("ILLEGAL, ");
+    }
+    /* make some small effort towards readable output */
+    if (n > 70) {
+      printf("\n  ");
+      n = 2;
+    }
+  }
+  printf("};\n");
+}
+
+/*
+ * Huffman decoder table generation
+ */
+
+#define NSYMS 257
+#define MAXHUFFSTATES 1024
+
+/* Constants pulled from the HPACK spec, and converted to C using the vim
+   command:
+   :%s/.*   \([0-9a-f]\+\)  \[ *\([0-9]\+\)\]/{0x\1, \2},/g */
+static const struct {
+  unsigned bits;
+  unsigned length;
+} huffsyms[NSYMS] = {
+      {0x1ff8, 13},
+      {0x7fffd8, 23},
+      {0xfffffe2, 28},
+      {0xfffffe3, 28},
+      {0xfffffe4, 28},
+      {0xfffffe5, 28},
+      {0xfffffe6, 28},
+      {0xfffffe7, 28},
+      {0xfffffe8, 28},
+      {0xffffea, 24},
+      {0x3ffffffc, 30},
+      {0xfffffe9, 28},
+      {0xfffffea, 28},
+      {0x3ffffffd, 30},
+      {0xfffffeb, 28},
+      {0xfffffec, 28},
+      {0xfffffed, 28},
+      {0xfffffee, 28},
+      {0xfffffef, 28},
+      {0xffffff0, 28},
+      {0xffffff1, 28},
+      {0xffffff2, 28},
+      {0x3ffffffe, 30},
+      {0xffffff3, 28},
+      {0xffffff4, 28},
+      {0xffffff5, 28},
+      {0xffffff6, 28},
+      {0xffffff7, 28},
+      {0xffffff8, 28},
+      {0xffffff9, 28},
+      {0xffffffa, 28},
+      {0xffffffb, 28},
+      {0x14, 6},
+      {0x3f8, 10},
+      {0x3f9, 10},
+      {0xffa, 12},
+      {0x1ff9, 13},
+      {0x15, 6},
+      {0xf8, 8},
+      {0x7fa, 11},
+      {0x3fa, 10},
+      {0x3fb, 10},
+      {0xf9, 8},
+      {0x7fb, 11},
+      {0xfa, 8},
+      {0x16, 6},
+      {0x17, 6},
+      {0x18, 6},
+      {0x0, 5},
+      {0x1, 5},
+      {0x2, 5},
+      {0x19, 6},
+      {0x1a, 6},
+      {0x1b, 6},
+      {0x1c, 6},
+      {0x1d, 6},
+      {0x1e, 6},
+      {0x1f, 6},
+      {0x5c, 7},
+      {0xfb, 8},
+      {0x7ffc, 15},
+      {0x20, 6},
+      {0xffb, 12},
+      {0x3fc, 10},
+      {0x1ffa, 13},
+      {0x21, 6},
+      {0x5d, 7},
+      {0x5e, 7},
+      {0x5f, 7},
+      {0x60, 7},
+      {0x61, 7},
+      {0x62, 7},
+      {0x63, 7},
+      {0x64, 7},
+      {0x65, 7},
+      {0x66, 7},
+      {0x67, 7},
+      {0x68, 7},
+      {0x69, 7},
+      {0x6a, 7},
+      {0x6b, 7},
+      {0x6c, 7},
+      {0x6d, 7},
+      {0x6e, 7},
+      {0x6f, 7},
+      {0x70, 7},
+      {0x71, 7},
+      {0x72, 7},
+      {0xfc, 8},
+      {0x73, 7},
+      {0xfd, 8},
+      {0x1ffb, 13},
+      {0x7fff0, 19},
+      {0x1ffc, 13},
+      {0x3ffc, 14},
+      {0x22, 6},
+      {0x7ffd, 15},
+      {0x3, 5},
+      {0x23, 6},
+      {0x4, 5},
+      {0x24, 6},
+      {0x5, 5},
+      {0x25, 6},
+      {0x26, 6},
+      {0x27, 6},
+      {0x6, 5},
+      {0x74, 7},
+      {0x75, 7},
+      {0x28, 6},
+      {0x29, 6},
+      {0x2a, 6},
+      {0x7, 5},
+      {0x2b, 6},
+      {0x76, 7},
+      {0x2c, 6},
+      {0x8, 5},
+      {0x9, 5},
+      {0x2d, 6},
+      {0x77, 7},
+      {0x78, 7},
+      {0x79, 7},
+      {0x7a, 7},
+      {0x7b, 7},
+      {0x7ffe, 15},
+      {0x7fc, 11},
+      {0x3ffd, 14},
+      {0x1ffd, 13},
+      {0xffffffc, 28},
+      {0xfffe6, 20},
+      {0x3fffd2, 22},
+      {0xfffe7, 20},
+      {0xfffe8, 20},
+      {0x3fffd3, 22},
+      {0x3fffd4, 22},
+      {0x3fffd5, 22},
+      {0x7fffd9, 23},
+      {0x3fffd6, 22},
+      {0x7fffda, 23},
+      {0x7fffdb, 23},
+      {0x7fffdc, 23},
+      {0x7fffdd, 23},
+      {0x7fffde, 23},
+      {0xffffeb, 24},
+      {0x7fffdf, 23},
+      {0xffffec, 24},
+      {0xffffed, 24},
+      {0x3fffd7, 22},
+      {0x7fffe0, 23},
+      {0xffffee, 24},
+      {0x7fffe1, 23},
+      {0x7fffe2, 23},
+      {0x7fffe3, 23},
+      {0x7fffe4, 23},
+      {0x1fffdc, 21},
+      {0x3fffd8, 22},
+      {0x7fffe5, 23},
+      {0x3fffd9, 22},
+      {0x7fffe6, 23},
+      {0x7fffe7, 23},
+      {0xffffef, 24},
+      {0x3fffda, 22},
+      {0x1fffdd, 21},
+      {0xfffe9, 20},
+      {0x3fffdb, 22},
+      {0x3fffdc, 22},
+      {0x7fffe8, 23},
+      {0x7fffe9, 23},
+      {0x1fffde, 21},
+      {0x7fffea, 23},
+      {0x3fffdd, 22},
+      {0x3fffde, 22},
+      {0xfffff0, 24},
+      {0x1fffdf, 21},
+      {0x3fffdf, 22},
+      {0x7fffeb, 23},
+      {0x7fffec, 23},
+      {0x1fffe0, 21},
+      {0x1fffe1, 21},
+      {0x3fffe0, 22},
+      {0x1fffe2, 21},
+      {0x7fffed, 23},
+      {0x3fffe1, 22},
+      {0x7fffee, 23},
+      {0x7fffef, 23},
+      {0xfffea, 20},
+      {0x3fffe2, 22},
+      {0x3fffe3, 22},
+      {0x3fffe4, 22},
+      {0x7ffff0, 23},
+      {0x3fffe5, 22},
+      {0x3fffe6, 22},
+      {0x7ffff1, 23},
+      {0x3ffffe0, 26},
+      {0x3ffffe1, 26},
+      {0xfffeb, 20},
+      {0x7fff1, 19},
+      {0x3fffe7, 22},
+      {0x7ffff2, 23},
+      {0x3fffe8, 22},
+      {0x1ffffec, 25},
+      {0x3ffffe2, 26},
+      {0x3ffffe3, 26},
+      {0x3ffffe4, 26},
+      {0x7ffffde, 27},
+      {0x7ffffdf, 27},
+      {0x3ffffe5, 26},
+      {0xfffff1, 24},
+      {0x1ffffed, 25},
+      {0x7fff2, 19},
+      {0x1fffe3, 21},
+      {0x3ffffe6, 26},
+      {0x7ffffe0, 27},
+      {0x7ffffe1, 27},
+      {0x3ffffe7, 26},
+      {0x7ffffe2, 27},
+      {0xfffff2, 24},
+      {0x1fffe4, 21},
+      {0x1fffe5, 21},
+      {0x3ffffe8, 26},
+      {0x3ffffe9, 26},
+      {0xffffffd, 28},
+      {0x7ffffe3, 27},
+      {0x7ffffe4, 27},
+      {0x7ffffe5, 27},
+      {0xfffec, 20},
+      {0xfffff3, 24},
+      {0xfffed, 20},
+      {0x1fffe6, 21},
+      {0x3fffe9, 22},
+      {0x1fffe7, 21},
+      {0x1fffe8, 21},
+      {0x7ffff3, 23},
+      {0x3fffea, 22},
+      {0x3fffeb, 22},
+      {0x1ffffee, 25},
+      {0x1ffffef, 25},
+      {0xfffff4, 24},
+      {0xfffff5, 24},
+      {0x3ffffea, 26},
+      {0x7ffff4, 23},
+      {0x3ffffeb, 26},
+      {0x7ffffe6, 27},
+      {0x3ffffec, 26},
+      {0x3ffffed, 26},
+      {0x7ffffe7, 27},
+      {0x7ffffe8, 27},
+      {0x7ffffe9, 27},
+      {0x7ffffea, 27},
+      {0x7ffffeb, 27},
+      {0xffffffe, 28},
+      {0x7ffffec, 27},
+      {0x7ffffed, 27},
+      {0x7ffffee, 27},
+      {0x7ffffef, 27},
+      {0x7fffff0, 27},
+      {0x3ffffee, 26},
+      {0x3fffffff, 30},
+};
+
+/* represents a set of symbols as an array of booleans indicating inclusion */
+typedef struct { char included[NSYMS]; } symset;
+/* represents a lookup table indexed by a nibble */
+typedef struct { int values[16]; } nibblelut;
+
+/* returns a symset that includes all possible symbols */
+static symset symset_all() {
+  symset x;
+  memset(x.included, 1, sizeof(x.included));
+  return x;
+}
+
+/* returns a symset that includes no symbols */
+static symset symset_none() {
+  symset x;
+  memset(x.included, 0, sizeof(x.included));
+  return x;
+}
+
+/* returns an empty nibblelut */
+static nibblelut nibblelut_empty() {
+  nibblelut x;
+  int i;
+  for (i = 0; i < 16; i++) {
+    x.values[i] = -1;
+  }
+  return x;
+}
+
+/* counts symbols in a symset - only used for debug builds */
+#ifndef NDEBUG
+static int nsyms(symset s) {
+  int i;
+  int c = 0;
+  for (i = 0; i < NSYMS; i++) {
+    c += s.included[i] != 0;
+  }
+  return c;
+}
+#endif
+
+/* global table of discovered huffman decoding states */
+static struct {
+  /* the bit offset that this state starts at */
+  int bitofs;
+  /* the set of symbols that this state started with */
+  symset syms;
+
+  /* lookup table for the next state */
+  nibblelut next;
+  /* lookup table for what to emit */
+  nibblelut emit;
+} huffstates[MAXHUFFSTATES];
+static int nhuffstates = 0;
+
+/* given a number of decoded bits and a set of symbols that are live,
+   return the index into the decoder table for this state.
+   set isnew to 1 if this state was previously undiscovered */
+static int state_index(int bitofs, symset syms, int *isnew) {
+  int i;
+  for (i = 0; i < nhuffstates; i++) {
+    if (huffstates[i].bitofs != bitofs) continue;
+    if (0 != memcmp(huffstates[i].syms.included, syms.included, NSYMS))
+      continue;
+    *isnew = 0;
+    return i;
+  }
+  GPR_ASSERT(nhuffstates != MAXHUFFSTATES);
+  i = nhuffstates++;
+  huffstates[i].bitofs = bitofs;
+  huffstates[i].syms = syms;
+  huffstates[i].next = nibblelut_empty();
+  huffstates[i].emit = nibblelut_empty();
+  *isnew = 1;
+  return i;
+}
+
+/* recursively build a decoding table
+
+   state   - the huffman state that we are trying to fill in
+   nibble  - the current nibble
+   nibbits - the number of bits in the nibble that have been filled in
+   bitofs  - the number of bits of symbol that have been decoded
+   emit    - the symbol to emit on this nibble (or -1 if no symbol has been
+             found)
+   syms    - the set of symbols that could be matched */
+static void build_dec_tbl(int state, int nibble, int nibbits, int bitofs,
+                          int emit, symset syms) {
+  int i;
+  int bit;
+
+  /* If we have four bits in the nibble we're looking at, then we can fill in
+     a slot in the lookup tables. */
+  if (nibbits == 4) {
+    int isnew;
+    /* Find the state that we are in: this may be a new state, in which case
+       we recurse to fill it in, or we may have already seen this state, in
+       which case the recursion terminates */
+    int st = state_index(bitofs, syms, &isnew);
+    GPR_ASSERT(huffstates[state].next.values[nibble] == -1);
+    huffstates[state].next.values[nibble] = st;
+    huffstates[state].emit.values[nibble] = emit;
+    if (isnew) {
+      build_dec_tbl(st, 0, 0, bitofs, -1, syms);
+    }
+    return;
+  }
+
+  assert(nsyms(syms));
+
+  /* A bit can be 0 or 1 */
+  for (bit = 0; bit < 2; bit++) {
+    /* walk over active symbols and see if they have this bit set */
+    symset nextsyms = symset_none();
+    for (i = 0; i < NSYMS; i++) {
+      if (!syms.included[i]) continue; /* disregard inactive symbols */
+      if (((huffsyms[i].bits >> (huffsyms[i].length - bitofs - 1)) & 1) ==
+          bit) {
+        /* the bit is set, include it in the next recursive set */
+        if (huffsyms[i].length == bitofs + 1) {
+          /* additionally, we've gotten to the end of a symbol - this is a
+             special recursion step: re-activate all the symbols, reset
+             bitofs to zero, and recurse */
+          build_dec_tbl(state, (nibble << 1) | bit, nibbits + 1, 0, i,
+                        symset_all());
+          /* skip the remainder of this loop */
+          goto next;
+        }
+        nextsyms.included[i] = 1;
+      }
+    }
+    /* recurse down for this bit */
+    build_dec_tbl(state, (nibble << 1) | bit, nibbits + 1, bitofs + 1, emit,
+                  nextsyms);
+  next:
+    ;
+  }
+}
+
+static nibblelut ctbl[MAXHUFFSTATES];
+static int nctbl;
+
+static int ctbl_idx(nibblelut x) {
+  int i;
+  for (i = 0; i < nctbl; i++) {
+    if (0 == memcmp(&x, ctbl + i, sizeof(nibblelut))) return i;
+  }
+  ctbl[i] = x;
+  nctbl++;
+  return i;
+}
+
+static void dump_ctbl(const char *name) {
+  int i, j;
+  printf("static const gpr_int16 %s[%d*16] = {\n", name, nctbl);
+  for (i = 0; i < nctbl; i++) {
+    for (j = 0; j < 16; j++) {
+      printf("%d,", ctbl[i].values[j]);
+    }
+    printf("\n");
+  }
+  printf("};\n");
+}
+
+static void generate_huff_tables() {
+  int i;
+  build_dec_tbl(state_index(0, symset_all(), &i), 0, 0, 0, -1, symset_all());
+
+  nctbl = 0;
+  printf("static const gpr_uint8 next_tbl[%d] = {", nhuffstates);
+  for (i = 0; i < nhuffstates; i++) {
+    printf("%d,", ctbl_idx(huffstates[i].next));
+  }
+  printf("};\n");
+  dump_ctbl("next_sub_tbl");
+
+  nctbl = 0;
+  printf("static const gpr_uint16 emit_tbl[%d] = {", nhuffstates);
+  for (i = 0; i < nhuffstates; i++) {
+    printf("%d,", ctbl_idx(huffstates[i].emit));
+  }
+  printf("};\n");
+  dump_ctbl("emit_sub_tbl");
+}
+
+int main(void) {
+  generate_huff_tables();
+  generate_first_byte_lut();
+
+  return 0;
+}
diff --git a/src/core/transport/chttp2/hpack_parser.c b/src/core/transport/chttp2/hpack_parser.c
new file mode 100644
index 0000000..33588a7
--- /dev/null
+++ b/src/core/transport/chttp2/hpack_parser.c
@@ -0,0 +1,1212 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/hpack_parser.h"
+
+#include <stddef.h>
+#include <string.h>
+#include <assert.h>
+
+#include "src/core/support/murmur_hash.h"
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/port_platform.h>
+#include <grpc/support/string.h>
+#include <grpc/support/useful.h>
+
+/* How parsing works:
+
+   The parser object keeps track of a function pointer which represents the
+   current parse state.
+
+   Each time new bytes are presented, we call into the current state, which
+   recursively parses until all bytes in the given chunk are exhausted.
+
+   The parse state that terminates then saves its function pointer to be the
+   current state so that it can resume when more bytes are available.
+
+   It's expected that most optimizing compilers will turn this code into
+   a set of indirect jumps, and so not waste stack space. */
+
+/* forward declarations for parsing states */
+static int parse_begin(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                       const gpr_uint8 *end);
+static int parse_error(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                       const gpr_uint8 *end);
+
+static int parse_string_prefix(grpc_chttp2_hpack_parser *p,
+                               const gpr_uint8 *cur, const gpr_uint8 *end);
+static int parse_key_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                            const gpr_uint8 *end);
+static int parse_value_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                              const gpr_uint8 *end);
+
+static int parse_value0(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                        const gpr_uint8 *end);
+static int parse_value1(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                        const gpr_uint8 *end);
+static int parse_value2(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                        const gpr_uint8 *end);
+static int parse_value3(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                        const gpr_uint8 *end);
+static int parse_value4(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                        const gpr_uint8 *end);
+static int parse_value5up(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                          const gpr_uint8 *end);
+
+static int parse_indexed_field(grpc_chttp2_hpack_parser *p,
+                               const gpr_uint8 *cur, const gpr_uint8 *end);
+static int parse_indexed_field_x(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end);
+static int parse_lithdr_incidx(grpc_chttp2_hpack_parser *p,
+                               const gpr_uint8 *cur, const gpr_uint8 *end);
+static int parse_lithdr_incidx_x(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end);
+static int parse_lithdr_incidx_v(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end);
+static int parse_lithdr_notidx(grpc_chttp2_hpack_parser *p,
+                               const gpr_uint8 *cur, const gpr_uint8 *end);
+static int parse_lithdr_notidx_x(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end);
+static int parse_lithdr_notidx_v(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end);
+static int parse_lithdr_nvridx(grpc_chttp2_hpack_parser *p,
+                               const gpr_uint8 *cur, const gpr_uint8 *end);
+static int parse_lithdr_nvridx_x(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end);
+static int parse_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end);
+static int parse_max_tbl_size(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                              const gpr_uint8 *end);
+static int parse_max_tbl_size_x(grpc_chttp2_hpack_parser *p,
+                                const gpr_uint8 *cur, const gpr_uint8 *end);
+
+/* we translate the first byte of a hpack field into one of these decoding
+   cases, then use a lookup table to jump directly to the appropriate parser.
+
+   _X => the integer index is all ones, meaning we need to do varint decoding
+   _V => the integer index is all zeros, meaning we need to decode an additional
+         string value */
+typedef enum {
+  INDEXED_FIELD,
+  INDEXED_FIELD_X,
+  LITHDR_INCIDX,
+  LITHDR_INCIDX_X,
+  LITHDR_INCIDX_V,
+  LITHDR_NOTIDX,
+  LITHDR_NOTIDX_X,
+  LITHDR_NOTIDX_V,
+  LITHDR_NVRIDX,
+  LITHDR_NVRIDX_X,
+  LITHDR_NVRIDX_V,
+  MAX_TBL_SIZE,
+  MAX_TBL_SIZE_X,
+  ILLEGAL
+} first_byte_type;
+
+/* jump table of parse state functions -- order must match first_byte_type
+   above */
+static const grpc_chttp2_hpack_parser_state first_byte_action[] = {
+    parse_indexed_field,   parse_indexed_field_x, parse_lithdr_incidx,
+    parse_lithdr_incidx_x, parse_lithdr_incidx_v, parse_lithdr_notidx,
+    parse_lithdr_notidx_x, parse_lithdr_notidx_v, parse_lithdr_nvridx,
+    parse_lithdr_nvridx_x, parse_lithdr_nvridx_v, parse_max_tbl_size,
+    parse_max_tbl_size_x,  parse_error};
+
+/* indexes the first byte to a parse state function - generated by
+   gen_hpack_tables.c */
+static const gpr_uint8 first_byte_lut[256] = {
+    LITHDR_NOTIDX_V, LITHDR_NOTIDX, LITHDR_NOTIDX, LITHDR_NOTIDX,
+    LITHDR_NOTIDX,   LITHDR_NOTIDX, LITHDR_NOTIDX, LITHDR_NOTIDX,
+    LITHDR_NOTIDX,   LITHDR_NOTIDX, LITHDR_NOTIDX, LITHDR_NOTIDX,
+    LITHDR_NOTIDX,   LITHDR_NOTIDX, LITHDR_NOTIDX, LITHDR_NOTIDX_X,
+    LITHDR_NVRIDX_V, LITHDR_NVRIDX, LITHDR_NVRIDX, LITHDR_NVRIDX,
+    LITHDR_NVRIDX,   LITHDR_NVRIDX, LITHDR_NVRIDX, LITHDR_NVRIDX,
+    LITHDR_NVRIDX,   LITHDR_NVRIDX, LITHDR_NVRIDX, LITHDR_NVRIDX,
+    LITHDR_NVRIDX,   LITHDR_NVRIDX, LITHDR_NVRIDX, LITHDR_NVRIDX_X,
+    ILLEGAL,         MAX_TBL_SIZE,  MAX_TBL_SIZE,  MAX_TBL_SIZE,
+    MAX_TBL_SIZE,    MAX_TBL_SIZE,  MAX_TBL_SIZE,  MAX_TBL_SIZE,
+    MAX_TBL_SIZE,    MAX_TBL_SIZE,  MAX_TBL_SIZE,  MAX_TBL_SIZE,
+    MAX_TBL_SIZE,    MAX_TBL_SIZE,  MAX_TBL_SIZE,  MAX_TBL_SIZE,
+    MAX_TBL_SIZE,    MAX_TBL_SIZE,  MAX_TBL_SIZE,  MAX_TBL_SIZE,
+    MAX_TBL_SIZE,    MAX_TBL_SIZE,  MAX_TBL_SIZE,  MAX_TBL_SIZE,
+    MAX_TBL_SIZE,    MAX_TBL_SIZE,  MAX_TBL_SIZE,  MAX_TBL_SIZE,
+    MAX_TBL_SIZE,    MAX_TBL_SIZE,  MAX_TBL_SIZE,  MAX_TBL_SIZE_X,
+    LITHDR_INCIDX_V, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX,
+    LITHDR_INCIDX,   LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX_X,
+    ILLEGAL,         INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD,
+    INDEXED_FIELD,   INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD_X,
+};
+
+/* state table for huffman decoding: given a state, gives an index/16 into
+   next_sub_tbl. Taking that index and adding the value of the nibble being
+   considered returns the next state.
+
+   generated by gen_hpack_tables.c */
+static const gpr_uint8 next_tbl[256] = {
+    0,  1,  2,  3,  4,  1,  2, 5,  6,  1, 7,  8,  1,  3,  3,  9,  10, 11, 1,  1,
+    1,  12, 1,  2,  13, 1,  1, 1,  1,  1, 1,  1,  1,  1,  1,  1,  1,  1,  1,  2,
+    14, 1,  15, 16, 1,  17, 1, 15, 2,  7, 3,  18, 19, 1,  1,  1,  1,  20, 1,  1,
+    1,  1,  1,  1,  1,  1,  1, 1,  15, 2, 2,  7,  21, 1,  22, 1,  1,  1,  1,  1,
+    1,  1,  1,  15, 2,  2,  2, 2,  2,  2, 23, 24, 25, 1,  1,  1,  1,  2,  2,  2,
+    26, 3,  3,  27, 10, 28, 1, 1,  1,  1, 1,  1,  2,  3,  29, 10, 30, 1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1, 1,  1,  1, 1,  31, 1,  1,  1,  1,  1,  1,  1,  2,
+    2,  2,  2,  2,  2,  2,  2, 32, 1,  1, 15, 33, 1,  34, 35, 9,  36, 1,  1,  1,
+    1,  1,  1,  1,  37, 1,  1, 1,  1,  1, 1,  2,  2,  2,  2,  2,  2,  2,  26, 9,
+    38, 1,  1,  1,  1,  1,  1, 1,  15, 2, 2,  2,  2,  26, 3,  3,  39, 1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1, 1,  2,  2, 2,  2,  2,  2,  7,  3,  3,  3,  40, 2,
+    41, 1,  1,  1,  42, 43, 1, 1,  44, 1, 1,  1,  1,  15, 2,  2,  2,  2,  2,  2,
+    3,  3,  3,  45, 46, 1,  1, 2,  2,  2, 35, 3,  3,  18, 47, 2,
+};
+/* next state, based upon current state and the current nibble: see above.
+   generated by gen_hpack_tables.c */
+static const gpr_int16 next_sub_tbl[48 * 16] = {
+    1,   204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217,
+    218, 2,   6,   10,  13,  14,  15,  16,  17,  2,   6,   10,  13,  14,  15,
+    16,  17,  3,   7,   11,  24,  3,   7,   11,  24,  3,   7,   11,  24,  3,
+    7,   11,  24,  4,   8,   4,   8,   4,   8,   4,   8,   4,   8,   4,   8,
+    4,   8,   4,   8,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   5,
+    199, 200, 201, 202, 203, 4,   8,   4,   8,   0,   0,   0,   0,   0,   0,
+    0,   0,   0,   0,   0,   0,   9,   133, 134, 135, 136, 137, 138, 139, 140,
+    141, 142, 143, 144, 145, 146, 147, 3,   7,   11,  24,  3,   7,   11,  24,
+    4,   8,   4,   8,   4,   8,   4,   8,   0,   0,   0,   0,   0,   0,   0,
+    0,   0,   0,   0,   0,   0,   0,   12,  132, 4,   8,   4,   8,   4,   8,
+    4,   8,   4,   8,   4,   8,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+    0,   0,   0,   0,   0,   0,   0,   0,   18,  19,  20,  21,  4,   8,   4,
+    8,   4,   8,   4,   8,   4,   8,   0,   0,   0,   22,  23,  91,  25,  26,
+    27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,  3,
+    7,   11,  24,  3,   7,   11,  24,  0,   0,   0,   0,   0,   41,  42,  43,
+    2,   6,   10,  13,  14,  15,  16,  17,  3,   7,   11,  24,  3,   7,   11,
+    24,  4,   8,   4,   8,   4,   8,   4,   8,   4,   8,   4,   8,   0,   0,
+    44,  45,  2,   6,   10,  13,  14,  15,  16,  17,  46,  47,  48,  49,  50,
+    51,  52,  57,  4,   8,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+    0,   53,  54,  55,  56,  58,  59,  60,  61,  62,  63,  64,  65,  66,  67,
+    68,  69,  70,  71,  72,  74,  0,   0,   0,   0,   0,   0,   0,   0,   0,
+    0,   0,   0,   0,   0,   0,   73,  75,  76,  77,  78,  79,  80,  81,  82,
+    83,  84,  85,  86,  87,  88,  89,  90,  3,   7,   11,  24,  3,   7,   11,
+    24,  3,   7,   11,  24,  0,   0,   0,   0,   3,   7,   11,  24,  3,   7,
+    11,  24,  4,   8,   4,   8,   0,   0,   0,   92,  0,   0,   0,   93,  94,
+    95,  96,  97,  98,  99,  100, 101, 102, 103, 104, 105, 3,   7,   11,  24,
+    4,   8,   4,   8,   4,   8,   4,   8,   4,   8,   4,   8,   4,   8,   4,
+    8,   4,   8,   4,   8,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+    0,   0,   0,   106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 4,
+    8,   4,   8,   4,   8,   4,   8,   4,   8,   4,   8,   4,   8,   0,   0,
+    0,   117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130,
+    131, 2,   6,   10,  13,  14,  15,  16,  17,  4,   8,   4,   8,   4,   8,
+    4,   8,   4,   8,   4,   8,   4,   8,   4,   8,   4,   8,   4,   8,   148,
+    149, 150, 151, 3,   7,   11,  24,  4,   8,   4,   8,   0,   0,   0,   0,
+    0,   0,   152, 153, 3,   7,   11,  24,  3,   7,   11,  24,  3,   7,   11,
+    24,  154, 155, 156, 164, 3,   7,   11,  24,  3,   7,   11,  24,  3,   7,
+    11,  24,  4,   8,   4,   8,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+    157, 158, 159, 160, 161, 162, 163, 165, 166, 167, 168, 169, 170, 171, 172,
+    173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187,
+    188, 189, 190, 191, 192, 193, 194, 195, 196, 4,   8,   4,   8,   4,   8,
+    4,   8,   4,   8,   4,   8,   4,   8,   197, 198, 4,   8,   4,   8,   4,
+    8,   4,   8,   0,   0,   0,   0,   0,   0,   219, 220, 3,   7,   11,  24,
+    4,   8,   4,   8,   4,   8,   0,   0,   221, 222, 223, 224, 3,   7,   11,
+    24,  3,   7,   11,  24,  4,   8,   4,   8,   4,   8,   225, 228, 4,   8,
+    4,   8,   4,   8,   0,   0,   0,   0,   0,   0,   0,   0,   226, 227, 229,
+    230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244,
+    4,   8,   4,   8,   4,   8,   4,   8,   4,   8,   0,   0,   0,   0,   0,
+    0,   0,   0,   0,   0,   0,   0,   245, 246, 247, 248, 249, 250, 251, 252,
+    253, 254, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+    0,   0,   255,
+};
+/* emission table: indexed like next_tbl, ultimately gives the byte to be
+   emitted, or -1 for no byte, or 256 for end of stream
+
+   generated by gen_hpack_tables.c */
+static const gpr_uint16 emit_tbl[256] = {
+    0,   1,   2,   3,   4,   5,   6,   7,   0,   8,   9,   10,  11,  12,  13,
+    14,  15,  16,  17,  18,  19,  20,  21,  22,  0,   23,  24,  25,  26,  27,
+    28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,
+    43,  44,  45,  46,  47,  48,  49,  50,  51,  52,  53,  54,  0,   55,  56,
+    57,  58,  59,  60,  61,  62,  63,  64,  65,  66,  67,  68,  69,  70,  0,
+    71,  72,  73,  74,  75,  76,  77,  78,  79,  80,  81,  82,  83,  84,  85,
+    86,  87,  88,  89,  90,  91,  92,  93,  94,  95,  96,  97,  98,  99,  100,
+    101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115,
+    116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130,
+    131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145,
+    146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 0,
+    160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174,
+    0,   175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188,
+    189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203,
+    204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218,
+    219, 220, 221, 0,   222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232,
+    233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247,
+    248,
+};
+/* generated by gen_hpack_tables.c */
+static const gpr_int16 emit_sub_tbl[249 * 16] = {
+    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+    -1,  48,  48,  48,  48,  48,  48,  48,  48,  49,  49,  49,  49,  49,  49,
+    49,  49,  48,  48,  48,  48,  49,  49,  49,  49,  50,  50,  50,  50,  97,
+    97,  97,  97,  48,  48,  49,  49,  50,  50,  97,  97,  99,  99,  101, 101,
+    105, 105, 111, 111, 48,  49,  50,  97,  99,  101, 105, 111, 115, 116, -1,
+    -1,  -1,  -1,  -1,  -1,  32,  32,  32,  32,  32,  32,  32,  32,  37,  37,
+    37,  37,  37,  37,  37,  37,  99,  99,  99,  99,  101, 101, 101, 101, 105,
+    105, 105, 105, 111, 111, 111, 111, 115, 115, 116, 116, 32,  37,  45,  46,
+    47,  51,  52,  53,  54,  55,  56,  57,  61,  61,  61,  61,  61,  61,  61,
+    61,  65,  65,  65,  65,  65,  65,  65,  65,  115, 115, 115, 115, 116, 116,
+    116, 116, 32,  32,  37,  37,  45,  45,  46,  46,  61,  65,  95,  98,  100,
+    102, 103, 104, 108, 109, 110, 112, 114, 117, -1,  -1,  58,  58,  58,  58,
+    58,  58,  58,  58,  66,  66,  66,  66,  66,  66,  66,  66,  47,  47,  51,
+    51,  52,  52,  53,  53,  54,  54,  55,  55,  56,  56,  57,  57,  61,  61,
+    65,  65,  95,  95,  98,  98,  100, 100, 102, 102, 103, 103, 104, 104, 108,
+    108, 109, 109, 110, 110, 112, 112, 114, 114, 117, 117, 58,  66,  67,  68,
+    69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,  80,  81,  82,  83,
+    84,  85,  86,  87,  89,  106, 107, 113, 118, 119, 120, 121, 122, -1,  -1,
+    -1,  -1,  38,  38,  38,  38,  38,  38,  38,  38,  42,  42,  42,  42,  42,
+    42,  42,  42,  44,  44,  44,  44,  44,  44,  44,  44,  59,  59,  59,  59,
+    59,  59,  59,  59,  88,  88,  88,  88,  88,  88,  88,  88,  90,  90,  90,
+    90,  90,  90,  90,  90,  33,  33,  34,  34,  40,  40,  41,  41,  63,  63,
+    39,  43,  124, -1,  -1,  -1,  35,  35,  35,  35,  35,  35,  35,  35,  62,
+    62,  62,  62,  62,  62,  62,  62,  0,   0,   0,   0,   36,  36,  36,  36,
+    64,  64,  64,  64,  91,  91,  91,  91,  69,  69,  69,  69,  69,  69,  69,
+    69,  70,  70,  70,  70,  70,  70,  70,  70,  71,  71,  71,  71,  71,  71,
+    71,  71,  72,  72,  72,  72,  72,  72,  72,  72,  73,  73,  73,  73,  73,
+    73,  73,  73,  74,  74,  74,  74,  74,  74,  74,  74,  75,  75,  75,  75,
+    75,  75,  75,  75,  76,  76,  76,  76,  76,  76,  76,  76,  77,  77,  77,
+    77,  77,  77,  77,  77,  78,  78,  78,  78,  78,  78,  78,  78,  79,  79,
+    79,  79,  79,  79,  79,  79,  80,  80,  80,  80,  80,  80,  80,  80,  81,
+    81,  81,  81,  81,  81,  81,  81,  82,  82,  82,  82,  82,  82,  82,  82,
+    83,  83,  83,  83,  83,  83,  83,  83,  84,  84,  84,  84,  84,  84,  84,
+    84,  85,  85,  85,  85,  85,  85,  85,  85,  86,  86,  86,  86,  86,  86,
+    86,  86,  87,  87,  87,  87,  87,  87,  87,  87,  89,  89,  89,  89,  89,
+    89,  89,  89,  106, 106, 106, 106, 106, 106, 106, 106, 107, 107, 107, 107,
+    107, 107, 107, 107, 113, 113, 113, 113, 113, 113, 113, 113, 118, 118, 118,
+    118, 118, 118, 118, 118, 119, 119, 119, 119, 119, 119, 119, 119, 120, 120,
+    120, 120, 120, 120, 120, 120, 121, 121, 121, 121, 121, 121, 121, 121, 122,
+    122, 122, 122, 122, 122, 122, 122, 38,  38,  38,  38,  42,  42,  42,  42,
+    44,  44,  44,  44,  59,  59,  59,  59,  88,  88,  88,  88,  90,  90,  90,
+    90,  33,  34,  40,  41,  63,  -1,  -1,  -1,  39,  39,  39,  39,  39,  39,
+    39,  39,  43,  43,  43,  43,  43,  43,  43,  43,  124, 124, 124, 124, 124,
+    124, 124, 124, 35,  35,  35,  35,  62,  62,  62,  62,  0,   0,   36,  36,
+    64,  64,  91,  91,  93,  93,  126, 126, 94,  125, -1,  -1,  60,  60,  60,
+    60,  60,  60,  60,  60,  96,  96,  96,  96,  96,  96,  96,  96,  123, 123,
+    123, 123, 123, 123, 123, 123, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  92,
+    92,  92,  92,  92,  92,  92,  92,  195, 195, 195, 195, 195, 195, 195, 195,
+    208, 208, 208, 208, 208, 208, 208, 208, 128, 128, 128, 128, 130, 130, 130,
+    130, 131, 131, 131, 131, 162, 162, 162, 162, 184, 184, 184, 184, 194, 194,
+    194, 194, 224, 224, 224, 224, 226, 226, 226, 226, 153, 153, 161, 161, 167,
+    167, 172, 172, 176, 176, 177, 177, 179, 179, 209, 209, 216, 216, 217, 217,
+    227, 227, 229, 229, 230, 230, 129, 132, 133, 134, 136, 146, 154, 156, 160,
+    163, 164, 169, 170, 173, 178, 181, 185, 186, 187, 189, 190, 196, 198, 228,
+    232, 233, -1,  -1,  -1,  -1,  1,   1,   1,   1,   1,   1,   1,   1,   135,
+    135, 135, 135, 135, 135, 135, 135, 137, 137, 137, 137, 137, 137, 137, 137,
+    138, 138, 138, 138, 138, 138, 138, 138, 139, 139, 139, 139, 139, 139, 139,
+    139, 140, 140, 140, 140, 140, 140, 140, 140, 141, 141, 141, 141, 141, 141,
+    141, 141, 143, 143, 143, 143, 143, 143, 143, 143, 147, 147, 147, 147, 147,
+    147, 147, 147, 149, 149, 149, 149, 149, 149, 149, 149, 150, 150, 150, 150,
+    150, 150, 150, 150, 151, 151, 151, 151, 151, 151, 151, 151, 152, 152, 152,
+    152, 152, 152, 152, 152, 155, 155, 155, 155, 155, 155, 155, 155, 157, 157,
+    157, 157, 157, 157, 157, 157, 158, 158, 158, 158, 158, 158, 158, 158, 165,
+    165, 165, 165, 165, 165, 165, 165, 166, 166, 166, 166, 166, 166, 166, 166,
+    168, 168, 168, 168, 168, 168, 168, 168, 174, 174, 174, 174, 174, 174, 174,
+    174, 175, 175, 175, 175, 175, 175, 175, 175, 180, 180, 180, 180, 180, 180,
+    180, 180, 182, 182, 182, 182, 182, 182, 182, 182, 183, 183, 183, 183, 183,
+    183, 183, 183, 188, 188, 188, 188, 188, 188, 188, 188, 191, 191, 191, 191,
+    191, 191, 191, 191, 197, 197, 197, 197, 197, 197, 197, 197, 231, 231, 231,
+    231, 231, 231, 231, 231, 239, 239, 239, 239, 239, 239, 239, 239, 9,   9,
+    9,   9,   142, 142, 142, 142, 144, 144, 144, 144, 145, 145, 145, 145, 148,
+    148, 148, 148, 159, 159, 159, 159, 171, 171, 171, 171, 206, 206, 206, 206,
+    215, 215, 215, 215, 225, 225, 225, 225, 236, 236, 236, 236, 237, 237, 237,
+    237, 199, 199, 207, 207, 234, 234, 235, 235, 192, 193, 200, 201, 202, 205,
+    210, 213, 218, 219, 238, 240, 242, 243, 255, -1,  203, 203, 203, 203, 203,
+    203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 211, 211, 211, 211,
+    211, 211, 211, 211, 212, 212, 212, 212, 212, 212, 212, 212, 214, 214, 214,
+    214, 214, 214, 214, 214, 221, 221, 221, 221, 221, 221, 221, 221, 222, 222,
+    222, 222, 222, 222, 222, 222, 223, 223, 223, 223, 223, 223, 223, 223, 241,
+    241, 241, 241, 241, 241, 241, 241, 244, 244, 244, 244, 244, 244, 244, 244,
+    245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 246, 246, 246, 246, 246,
+    246, 247, 247, 247, 247, 247, 247, 247, 247, 248, 248, 248, 248, 248, 248,
+    248, 248, 250, 250, 250, 250, 250, 250, 250, 250, 251, 251, 251, 251, 251,
+    251, 251, 251, 252, 252, 252, 252, 252, 252, 252, 252, 253, 253, 253, 253,
+    253, 253, 253, 253, 254, 254, 254, 254, 254, 254, 254, 254, 2,   2,   2,
+    2,   3,   3,   3,   3,   4,   4,   4,   4,   5,   5,   5,   5,   6,   6,
+    6,   6,   7,   7,   7,   7,   8,   8,   8,   8,   11,  11,  11,  11,  12,
+    12,  12,  12,  14,  14,  14,  14,  15,  15,  15,  15,  16,  16,  16,  16,
+    17,  17,  17,  17,  18,  18,  18,  18,  19,  19,  19,  19,  20,  20,  20,
+    20,  21,  21,  21,  21,  23,  23,  23,  23,  24,  24,  24,  24,  25,  25,
+    25,  25,  26,  26,  26,  26,  27,  27,  27,  27,  28,  28,  28,  28,  29,
+    29,  29,  29,  30,  30,  30,  30,  31,  31,  31,  31,  127, 127, 127, 127,
+    220, 220, 220, 220, 249, 249, 249, 249, 10,  13,  22,  256, 93,  93,  93,
+    93,  126, 126, 126, 126, 94,  94,  125, 125, 60,  96,  123, -1,  92,  195,
+    208, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  128,
+    128, 128, 128, 128, 128, 128, 128, 130, 130, 130, 130, 130, 130, 130, 130,
+    131, 131, 131, 131, 131, 131, 131, 131, 162, 162, 162, 162, 162, 162, 162,
+    162, 184, 184, 184, 184, 184, 184, 184, 184, 194, 194, 194, 194, 194, 194,
+    194, 194, 224, 224, 224, 224, 224, 224, 224, 224, 226, 226, 226, 226, 226,
+    226, 226, 226, 153, 153, 153, 153, 161, 161, 161, 161, 167, 167, 167, 167,
+    172, 172, 172, 172, 176, 176, 176, 176, 177, 177, 177, 177, 179, 179, 179,
+    179, 209, 209, 209, 209, 216, 216, 216, 216, 217, 217, 217, 217, 227, 227,
+    227, 227, 229, 229, 229, 229, 230, 230, 230, 230, 129, 129, 132, 132, 133,
+    133, 134, 134, 136, 136, 146, 146, 154, 154, 156, 156, 160, 160, 163, 163,
+    164, 164, 169, 169, 170, 170, 173, 173, 178, 178, 181, 181, 185, 185, 186,
+    186, 187, 187, 189, 189, 190, 190, 196, 196, 198, 198, 228, 228, 232, 232,
+    233, 233, 1,   135, 137, 138, 139, 140, 141, 143, 147, 149, 150, 151, 152,
+    155, 157, 158, 165, 166, 168, 174, 175, 180, 182, 183, 188, 191, 197, 231,
+    239, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  9,   9,   9,
+    9,   9,   9,   9,   9,   142, 142, 142, 142, 142, 142, 142, 142, 144, 144,
+    144, 144, 144, 144, 144, 144, 145, 145, 145, 145, 145, 145, 145, 145, 148,
+    148, 148, 148, 148, 148, 148, 148, 159, 159, 159, 159, 159, 159, 159, 159,
+    171, 171, 171, 171, 171, 171, 171, 171, 206, 206, 206, 206, 206, 206, 206,
+    206, 215, 215, 215, 215, 215, 215, 215, 215, 225, 225, 225, 225, 225, 225,
+    225, 225, 236, 236, 236, 236, 236, 236, 236, 236, 237, 237, 237, 237, 237,
+    237, 237, 237, 199, 199, 199, 199, 207, 207, 207, 207, 234, 234, 234, 234,
+    235, 235, 235, 235, 192, 192, 193, 193, 200, 200, 201, 201, 202, 202, 205,
+    205, 210, 210, 213, 213, 218, 218, 219, 219, 238, 238, 240, 240, 242, 242,
+    243, 243, 255, 255, 203, 204, 211, 212, 214, 221, 222, 223, 241, 244, 245,
+    246, 247, 248, 250, 251, 252, 253, 254, -1,  -1,  -1,  -1,  -1,  -1,  -1,
+    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  2,   2,   2,   2,   2,   2,   2,
+    2,   3,   3,   3,   3,   3,   3,   3,   3,   4,   4,   4,   4,   4,   4,
+    4,   4,   5,   5,   5,   5,   5,   5,   5,   5,   6,   6,   6,   6,   6,
+    6,   6,   6,   7,   7,   7,   7,   7,   7,   7,   7,   8,   8,   8,   8,
+    8,   8,   8,   8,   11,  11,  11,  11,  11,  11,  11,  11,  12,  12,  12,
+    12,  12,  12,  12,  12,  14,  14,  14,  14,  14,  14,  14,  14,  15,  15,
+    15,  15,  15,  15,  15,  15,  16,  16,  16,  16,  16,  16,  16,  16,  17,
+    17,  17,  17,  17,  17,  17,  17,  18,  18,  18,  18,  18,  18,  18,  18,
+    19,  19,  19,  19,  19,  19,  19,  19,  20,  20,  20,  20,  20,  20,  20,
+    20,  21,  21,  21,  21,  21,  21,  21,  21,  23,  23,  23,  23,  23,  23,
+    23,  23,  24,  24,  24,  24,  24,  24,  24,  24,  25,  25,  25,  25,  25,
+    25,  25,  25,  26,  26,  26,  26,  26,  26,  26,  26,  27,  27,  27,  27,
+    27,  27,  27,  27,  28,  28,  28,  28,  28,  28,  28,  28,  29,  29,  29,
+    29,  29,  29,  29,  29,  30,  30,  30,  30,  30,  30,  30,  30,  31,  31,
+    31,  31,  31,  31,  31,  31,  127, 127, 127, 127, 127, 127, 127, 127, 220,
+    220, 220, 220, 220, 220, 220, 220, 249, 249, 249, 249, 249, 249, 249, 249,
+    10,  10,  13,  13,  22,  22,  256, 256, 67,  67,  67,  67,  67,  67,  67,
+    67,  68,  68,  68,  68,  68,  68,  68,  68,  95,  95,  95,  95,  95,  95,
+    95,  95,  98,  98,  98,  98,  98,  98,  98,  98,  100, 100, 100, 100, 100,
+    100, 100, 100, 102, 102, 102, 102, 102, 102, 102, 102, 103, 103, 103, 103,
+    103, 103, 103, 103, 104, 104, 104, 104, 104, 104, 104, 104, 108, 108, 108,
+    108, 108, 108, 108, 108, 109, 109, 109, 109, 109, 109, 109, 109, 110, 110,
+    110, 110, 110, 110, 110, 110, 112, 112, 112, 112, 112, 112, 112, 112, 114,
+    114, 114, 114, 114, 114, 114, 114, 117, 117, 117, 117, 117, 117, 117, 117,
+    58,  58,  58,  58,  66,  66,  66,  66,  67,  67,  67,  67,  68,  68,  68,
+    68,  69,  69,  69,  69,  70,  70,  70,  70,  71,  71,  71,  71,  72,  72,
+    72,  72,  73,  73,  73,  73,  74,  74,  74,  74,  75,  75,  75,  75,  76,
+    76,  76,  76,  77,  77,  77,  77,  78,  78,  78,  78,  79,  79,  79,  79,
+    80,  80,  80,  80,  81,  81,  81,  81,  82,  82,  82,  82,  83,  83,  83,
+    83,  84,  84,  84,  84,  85,  85,  85,  85,  86,  86,  86,  86,  87,  87,
+    87,  87,  89,  89,  89,  89,  106, 106, 106, 106, 107, 107, 107, 107, 113,
+    113, 113, 113, 118, 118, 118, 118, 119, 119, 119, 119, 120, 120, 120, 120,
+    121, 121, 121, 121, 122, 122, 122, 122, 38,  38,  42,  42,  44,  44,  59,
+    59,  88,  88,  90,  90,  -1,  -1,  -1,  -1,  33,  33,  33,  33,  33,  33,
+    33,  33,  34,  34,  34,  34,  34,  34,  34,  34,  40,  40,  40,  40,  40,
+    40,  40,  40,  41,  41,  41,  41,  41,  41,  41,  41,  63,  63,  63,  63,
+    63,  63,  63,  63,  39,  39,  39,  39,  43,  43,  43,  43,  124, 124, 124,
+    124, 35,  35,  62,  62,  0,   36,  64,  91,  93,  126, -1,  -1,  94,  94,
+    94,  94,  94,  94,  94,  94,  125, 125, 125, 125, 125, 125, 125, 125, 60,
+    60,  60,  60,  96,  96,  96,  96,  123, 123, 123, 123, -1,  -1,  -1,  -1,
+    92,  92,  92,  92,  195, 195, 195, 195, 208, 208, 208, 208, 128, 128, 130,
+    130, 131, 131, 162, 162, 184, 184, 194, 194, 224, 224, 226, 226, 153, 161,
+    167, 172, 176, 177, 179, 209, 216, 217, 227, 229, 230, -1,  -1,  -1,  -1,
+    -1,  -1,  -1,  129, 129, 129, 129, 129, 129, 129, 129, 132, 132, 132, 132,
+    132, 132, 132, 132, 133, 133, 133, 133, 133, 133, 133, 133, 134, 134, 134,
+    134, 134, 134, 134, 134, 136, 136, 136, 136, 136, 136, 136, 136, 146, 146,
+    146, 146, 146, 146, 146, 146, 154, 154, 154, 154, 154, 154, 154, 154, 156,
+    156, 156, 156, 156, 156, 156, 156, 160, 160, 160, 160, 160, 160, 160, 160,
+    163, 163, 163, 163, 163, 163, 163, 163, 164, 164, 164, 164, 164, 164, 164,
+    164, 169, 169, 169, 169, 169, 169, 169, 169, 170, 170, 170, 170, 170, 170,
+    170, 170, 173, 173, 173, 173, 173, 173, 173, 173, 178, 178, 178, 178, 178,
+    178, 178, 178, 181, 181, 181, 181, 181, 181, 181, 181, 185, 185, 185, 185,
+    185, 185, 185, 185, 186, 186, 186, 186, 186, 186, 186, 186, 187, 187, 187,
+    187, 187, 187, 187, 187, 189, 189, 189, 189, 189, 189, 189, 189, 190, 190,
+    190, 190, 190, 190, 190, 190, 196, 196, 196, 196, 196, 196, 196, 196, 198,
+    198, 198, 198, 198, 198, 198, 198, 228, 228, 228, 228, 228, 228, 228, 228,
+    232, 232, 232, 232, 232, 232, 232, 232, 233, 233, 233, 233, 233, 233, 233,
+    233, 1,   1,   1,   1,   135, 135, 135, 135, 137, 137, 137, 137, 138, 138,
+    138, 138, 139, 139, 139, 139, 140, 140, 140, 140, 141, 141, 141, 141, 143,
+    143, 143, 143, 147, 147, 147, 147, 149, 149, 149, 149, 150, 150, 150, 150,
+    151, 151, 151, 151, 152, 152, 152, 152, 155, 155, 155, 155, 157, 157, 157,
+    157, 158, 158, 158, 158, 165, 165, 165, 165, 166, 166, 166, 166, 168, 168,
+    168, 168, 174, 174, 174, 174, 175, 175, 175, 175, 180, 180, 180, 180, 182,
+    182, 182, 182, 183, 183, 183, 183, 188, 188, 188, 188, 191, 191, 191, 191,
+    197, 197, 197, 197, 231, 231, 231, 231, 239, 239, 239, 239, 9,   9,   142,
+    142, 144, 144, 145, 145, 148, 148, 159, 159, 171, 171, 206, 206, 215, 215,
+    225, 225, 236, 236, 237, 237, 199, 207, 234, 235, 192, 192, 192, 192, 192,
+    192, 192, 192, 193, 193, 193, 193, 193, 193, 193, 193, 200, 200, 200, 200,
+    200, 200, 200, 200, 201, 201, 201, 201, 201, 201, 201, 201, 202, 202, 202,
+    202, 202, 202, 202, 202, 205, 205, 205, 205, 205, 205, 205, 205, 210, 210,
+    210, 210, 210, 210, 210, 210, 213, 213, 213, 213, 213, 213, 213, 213, 218,
+    218, 218, 218, 218, 218, 218, 218, 219, 219, 219, 219, 219, 219, 219, 219,
+    238, 238, 238, 238, 238, 238, 238, 238, 240, 240, 240, 240, 240, 240, 240,
+    240, 242, 242, 242, 242, 242, 242, 242, 242, 243, 243, 243, 243, 243, 243,
+    243, 243, 255, 255, 255, 255, 255, 255, 255, 255, 203, 203, 203, 203, 204,
+    204, 204, 204, 211, 211, 211, 211, 212, 212, 212, 212, 214, 214, 214, 214,
+    221, 221, 221, 221, 222, 222, 222, 222, 223, 223, 223, 223, 241, 241, 241,
+    241, 244, 244, 244, 244, 245, 245, 245, 245, 246, 246, 246, 246, 247, 247,
+    247, 247, 248, 248, 248, 248, 250, 250, 250, 250, 251, 251, 251, 251, 252,
+    252, 252, 252, 253, 253, 253, 253, 254, 254, 254, 254, 2,   2,   3,   3,
+    4,   4,   5,   5,   6,   6,   7,   7,   8,   8,   11,  11,  12,  12,  14,
+    14,  15,  15,  16,  16,  17,  17,  18,  18,  19,  19,  20,  20,  21,  21,
+    23,  23,  24,  24,  25,  25,  26,  26,  27,  27,  28,  28,  29,  29,  30,
+    30,  31,  31,  127, 127, 220, 220, 249, 249, -1,  -1,  10,  10,  10,  10,
+    10,  10,  10,  10,  13,  13,  13,  13,  13,  13,  13,  13,  22,  22,  22,
+    22,  22,  22,  22,  22,  256, 256, 256, 256, 256, 256, 256, 256, 45,  45,
+    45,  45,  45,  45,  45,  45,  46,  46,  46,  46,  46,  46,  46,  46,  47,
+    47,  47,  47,  47,  47,  47,  47,  51,  51,  51,  51,  51,  51,  51,  51,
+    52,  52,  52,  52,  52,  52,  52,  52,  53,  53,  53,  53,  53,  53,  53,
+    53,  54,  54,  54,  54,  54,  54,  54,  54,  55,  55,  55,  55,  55,  55,
+    55,  55,  56,  56,  56,  56,  56,  56,  56,  56,  57,  57,  57,  57,  57,
+    57,  57,  57,  50,  50,  50,  50,  50,  50,  50,  50,  97,  97,  97,  97,
+    97,  97,  97,  97,  99,  99,  99,  99,  99,  99,  99,  99,  101, 101, 101,
+    101, 101, 101, 101, 101, 105, 105, 105, 105, 105, 105, 105, 105, 111, 111,
+    111, 111, 111, 111, 111, 111, 115, 115, 115, 115, 115, 115, 115, 115, 116,
+    116, 116, 116, 116, 116, 116, 116, 32,  32,  32,  32,  37,  37,  37,  37,
+    45,  45,  45,  45,  46,  46,  46,  46,  47,  47,  47,  47,  51,  51,  51,
+    51,  52,  52,  52,  52,  53,  53,  53,  53,  54,  54,  54,  54,  55,  55,
+    55,  55,  56,  56,  56,  56,  57,  57,  57,  57,  61,  61,  61,  61,  65,
+    65,  65,  65,  95,  95,  95,  95,  98,  98,  98,  98,  100, 100, 100, 100,
+    102, 102, 102, 102, 103, 103, 103, 103, 104, 104, 104, 104, 108, 108, 108,
+    108, 109, 109, 109, 109, 110, 110, 110, 110, 112, 112, 112, 112, 114, 114,
+    114, 114, 117, 117, 117, 117, 58,  58,  66,  66,  67,  67,  68,  68,  69,
+    69,  70,  70,  71,  71,  72,  72,  73,  73,  74,  74,  75,  75,  76,  76,
+    77,  77,  78,  78,  79,  79,  80,  80,  81,  81,  82,  82,  83,  83,  84,
+    84,  85,  85,  86,  86,  87,  87,  89,  89,  106, 106, 107, 107, 113, 113,
+    118, 118, 119, 119, 120, 120, 121, 121, 122, 122, 38,  42,  44,  59,  88,
+    90,  -1,  -1,  33,  33,  33,  33,  34,  34,  34,  34,  40,  40,  40,  40,
+    41,  41,  41,  41,  63,  63,  63,  63,  39,  39,  43,  43,  124, 124, 35,
+    62,  -1,  -1,  -1,  -1,  0,   0,   0,   0,   0,   0,   0,   0,   36,  36,
+    36,  36,  36,  36,  36,  36,  64,  64,  64,  64,  64,  64,  64,  64,  91,
+    91,  91,  91,  91,  91,  91,  91,  93,  93,  93,  93,  93,  93,  93,  93,
+    126, 126, 126, 126, 126, 126, 126, 126, 94,  94,  94,  94,  125, 125, 125,
+    125, 60,  60,  96,  96,  123, 123, -1,  -1,  92,  92,  195, 195, 208, 208,
+    128, 130, 131, 162, 184, 194, 224, 226, -1,  -1,  153, 153, 153, 153, 153,
+    153, 153, 153, 161, 161, 161, 161, 161, 161, 161, 161, 167, 167, 167, 167,
+    167, 167, 167, 167, 172, 172, 172, 172, 172, 172, 172, 172, 176, 176, 176,
+    176, 176, 176, 176, 176, 177, 177, 177, 177, 177, 177, 177, 177, 179, 179,
+    179, 179, 179, 179, 179, 179, 209, 209, 209, 209, 209, 209, 209, 209, 216,
+    216, 216, 216, 216, 216, 216, 216, 217, 217, 217, 217, 217, 217, 217, 217,
+    227, 227, 227, 227, 227, 227, 227, 227, 229, 229, 229, 229, 229, 229, 229,
+    229, 230, 230, 230, 230, 230, 230, 230, 230, 129, 129, 129, 129, 132, 132,
+    132, 132, 133, 133, 133, 133, 134, 134, 134, 134, 136, 136, 136, 136, 146,
+    146, 146, 146, 154, 154, 154, 154, 156, 156, 156, 156, 160, 160, 160, 160,
+    163, 163, 163, 163, 164, 164, 164, 164, 169, 169, 169, 169, 170, 170, 170,
+    170, 173, 173, 173, 173, 178, 178, 178, 178, 181, 181, 181, 181, 185, 185,
+    185, 185, 186, 186, 186, 186, 187, 187, 187, 187, 189, 189, 189, 189, 190,
+    190, 190, 190, 196, 196, 196, 196, 198, 198, 198, 198, 228, 228, 228, 228,
+    232, 232, 232, 232, 233, 233, 233, 233, 1,   1,   135, 135, 137, 137, 138,
+    138, 139, 139, 140, 140, 141, 141, 143, 143, 147, 147, 149, 149, 150, 150,
+    151, 151, 152, 152, 155, 155, 157, 157, 158, 158, 165, 165, 166, 166, 168,
+    168, 174, 174, 175, 175, 180, 180, 182, 182, 183, 183, 188, 188, 191, 191,
+    197, 197, 231, 231, 239, 239, 9,   142, 144, 145, 148, 159, 171, 206, 215,
+    225, 236, 237, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  199, 199,
+    199, 199, 199, 199, 199, 199, 207, 207, 207, 207, 207, 207, 207, 207, 234,
+    234, 234, 234, 234, 234, 234, 234, 235, 235, 235, 235, 235, 235, 235, 235,
+    192, 192, 192, 192, 193, 193, 193, 193, 200, 200, 200, 200, 201, 201, 201,
+    201, 202, 202, 202, 202, 205, 205, 205, 205, 210, 210, 210, 210, 213, 213,
+    213, 213, 218, 218, 218, 218, 219, 219, 219, 219, 238, 238, 238, 238, 240,
+    240, 240, 240, 242, 242, 242, 242, 243, 243, 243, 243, 255, 255, 255, 255,
+    203, 203, 204, 204, 211, 211, 212, 212, 214, 214, 221, 221, 222, 222, 223,
+    223, 241, 241, 244, 244, 245, 245, 246, 246, 247, 247, 248, 248, 250, 250,
+    251, 251, 252, 252, 253, 253, 254, 254, 2,   3,   4,   5,   6,   7,   8,
+    11,  12,  14,  15,  16,  17,  18,  19,  20,  21,  23,  24,  25,  26,  27,
+    28,  29,  30,  31,  127, 220, 249, -1,  10,  10,  10,  10,  13,  13,  13,
+    13,  22,  22,  22,  22,  256, 256, 256, 256,
+};
+
+/* emission helpers */
+static void on_hdr(grpc_chttp2_hpack_parser *p, grpc_mdelem *md,
+                   int add_to_table) {
+  if (add_to_table) {
+    grpc_mdelem_ref(md);
+    grpc_chttp2_hptbl_add(&p->table, md);
+  }
+  p->on_header(p->on_header_user_data, md);
+}
+
+static grpc_mdstr *take_string(grpc_chttp2_hpack_parser *p,
+                               grpc_chttp2_hpack_parser_string *str) {
+  grpc_mdstr *s = grpc_mdstr_from_buffer(p->table.mdctx, (gpr_uint8 *)str->str,
+                                         str->length);
+  str->length = 0;
+  return s;
+}
+
+/* jump to the next state */
+static int parse_next(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                      const gpr_uint8 *end) {
+  p->state = *p->next_state++;
+  return p->state(p, cur, end);
+}
+
+/* begin parsing a header: all functionality is encoded into lookup tables
+   above */
+static int parse_begin(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                       const gpr_uint8 *end) {
+  if (cur == end) {
+    p->state = parse_begin;
+    return 1;
+  }
+
+  return first_byte_action[first_byte_lut[*cur]](p, cur, end);
+}
+
+/* stream dependency and prioritization data: we just skip it */
+static int parse_stream_weight(grpc_chttp2_hpack_parser *p,
+                               const gpr_uint8 *cur, const gpr_uint8 *end) {
+  if (cur == end) {
+    p->state = parse_stream_weight;
+    return 1;
+  }
+
+  return parse_begin(p, cur + 1, end);
+}
+
+static int parse_stream_dep3(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                             const gpr_uint8 *end) {
+  if (cur == end) {
+    p->state = parse_stream_dep3;
+    return 1;
+  }
+
+  return parse_stream_weight(p, cur + 1, end);
+}
+
+static int parse_stream_dep2(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                             const gpr_uint8 *end) {
+  if (cur == end) {
+    p->state = parse_stream_dep2;
+    return 1;
+  }
+
+  return parse_stream_dep3(p, cur + 1, end);
+}
+
+static int parse_stream_dep1(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                             const gpr_uint8 *end) {
+  if (cur == end) {
+    p->state = parse_stream_dep1;
+    return 1;
+  }
+
+  return parse_stream_dep2(p, cur + 1, end);
+}
+
+static int parse_stream_dep0(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                             const gpr_uint8 *end) {
+  if (cur == end) {
+    p->state = parse_stream_dep0;
+    return 1;
+  }
+
+  return parse_stream_dep1(p, cur + 1, end);
+}
+
+/* emit an indexed field; for now just logs it to console; jumps to
+   begin the next field on completion */
+static int finish_indexed_field(grpc_chttp2_hpack_parser *p,
+                                const gpr_uint8 *cur, const gpr_uint8 *end) {
+  grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
+  grpc_mdelem_ref(md);
+  on_hdr(p, md, 0);
+  return parse_begin(p, cur, end);
+}
+
+/* parse an indexed field with index < 127 */
+static int parse_indexed_field(grpc_chttp2_hpack_parser *p,
+                               const gpr_uint8 *cur, const gpr_uint8 *end) {
+  p->index = (*cur) & 0x7f;
+  return finish_indexed_field(p, cur + 1, end);
+}
+
+/* parse an indexed field with index >= 127 */
+static int parse_indexed_field_x(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end) {
+  static const grpc_chttp2_hpack_parser_state and_then[] = {
+      finish_indexed_field};
+  p->next_state = and_then;
+  p->index = 0x7f;
+  p->parsing.value = &p->index;
+  return parse_value0(p, cur + 1, end);
+}
+
+/* finish a literal header with incremental indexing: just log, and jump to '
+   begin */
+static int finish_lithdr_incidx(grpc_chttp2_hpack_parser *p,
+                                const gpr_uint8 *cur, const gpr_uint8 *end) {
+  grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
+  on_hdr(p, grpc_mdelem_from_metadata_strings(p->table.mdctx,
+                                              grpc_mdstr_ref(md->key),
+                                              take_string(p, &p->value)),
+         1);
+  return parse_begin(p, cur, end);
+}
+
+/* finish a literal header with incremental indexing with no index */
+static int finish_lithdr_incidx_v(grpc_chttp2_hpack_parser *p,
+                                  const gpr_uint8 *cur, const gpr_uint8 *end) {
+  on_hdr(p, grpc_mdelem_from_metadata_strings(p->table.mdctx,
+                                              take_string(p, &p->key),
+                                              take_string(p, &p->value)),
+         1);
+  return parse_begin(p, cur, end);
+}
+
+/* parse a literal header with incremental indexing; index < 63 */
+static int parse_lithdr_incidx(grpc_chttp2_hpack_parser *p,
+                               const gpr_uint8 *cur, const gpr_uint8 *end) {
+  static const grpc_chttp2_hpack_parser_state and_then[] = {
+      parse_value_string, finish_lithdr_incidx};
+  p->next_state = and_then;
+  p->index = (*cur) & 0x3f;
+  return parse_string_prefix(p, cur + 1, end);
+}
+
+/* parse a literal header with incremental indexing; index >= 63 */
+static int parse_lithdr_incidx_x(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end) {
+  static const grpc_chttp2_hpack_parser_state and_then[] = {
+      parse_string_prefix, parse_value_string, finish_lithdr_incidx};
+  p->next_state = and_then;
+  p->index = 0x3f;
+  p->parsing.value = &p->index;
+  return parse_value0(p, cur + 1, end);
+}
+
+/* parse a literal header with incremental indexing; index = 0 */
+static int parse_lithdr_incidx_v(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end) {
+  static const grpc_chttp2_hpack_parser_state and_then[] = {
+      parse_key_string, parse_string_prefix, parse_value_string,
+      finish_lithdr_incidx_v};
+  p->next_state = and_then;
+  return parse_string_prefix(p, cur + 1, end);
+}
+
+/* finish a literal header without incremental indexing */
+static int finish_lithdr_notidx(grpc_chttp2_hpack_parser *p,
+                                const gpr_uint8 *cur, const gpr_uint8 *end) {
+  grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
+  on_hdr(p, grpc_mdelem_from_metadata_strings(p->table.mdctx,
+                                              grpc_mdstr_ref(md->key),
+                                              take_string(p, &p->value)),
+         0);
+  return parse_begin(p, cur, end);
+}
+
+/* finish a literal header without incremental indexing with index = 0 */
+static int finish_lithdr_notidx_v(grpc_chttp2_hpack_parser *p,
+                                  const gpr_uint8 *cur, const gpr_uint8 *end) {
+  on_hdr(p, grpc_mdelem_from_metadata_strings(p->table.mdctx,
+                                              take_string(p, &p->key),
+                                              take_string(p, &p->value)),
+         0);
+  return parse_begin(p, cur, end);
+}
+
+/* parse a literal header without incremental indexing; index < 15 */
+static int parse_lithdr_notidx(grpc_chttp2_hpack_parser *p,
+                               const gpr_uint8 *cur, const gpr_uint8 *end) {
+  static const grpc_chttp2_hpack_parser_state and_then[] = {
+      parse_value_string, finish_lithdr_notidx};
+  p->next_state = and_then;
+  p->index = (*cur) & 0xf;
+  return parse_string_prefix(p, cur + 1, end);
+}
+
+/* parse a literal header without incremental indexing; index >= 15 */
+static int parse_lithdr_notidx_x(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end) {
+  static const grpc_chttp2_hpack_parser_state and_then[] = {
+      parse_string_prefix, parse_value_string, finish_lithdr_notidx};
+  p->next_state = and_then;
+  p->index = 0xf;
+  p->parsing.value = &p->index;
+  return parse_value0(p, cur + 1, end);
+}
+
+/* parse a literal header without incremental indexing; index == 0 */
+static int parse_lithdr_notidx_v(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end) {
+  static const grpc_chttp2_hpack_parser_state and_then[] = {
+      parse_key_string, parse_string_prefix, parse_value_string,
+      finish_lithdr_notidx_v};
+  p->next_state = and_then;
+  return parse_string_prefix(p, cur + 1, end);
+}
+
+/* finish a literal header that is never indexed */
+static int finish_lithdr_nvridx(grpc_chttp2_hpack_parser *p,
+                                const gpr_uint8 *cur, const gpr_uint8 *end) {
+  grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
+  on_hdr(p, grpc_mdelem_from_metadata_strings(p->table.mdctx,
+                                              grpc_mdstr_ref(md->key),
+                                              take_string(p, &p->value)),
+         0);
+  return parse_begin(p, cur, end);
+}
+
+/* finish a literal header that is never indexed with an extra value */
+static int finish_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p,
+                                  const gpr_uint8 *cur, const gpr_uint8 *end) {
+  on_hdr(p, grpc_mdelem_from_metadata_strings(p->table.mdctx,
+                                              take_string(p, &p->key),
+                                              take_string(p, &p->value)),
+         0);
+  return parse_begin(p, cur, end);
+}
+
+/* parse a literal header that is never indexed; index < 15 */
+static int parse_lithdr_nvridx(grpc_chttp2_hpack_parser *p,
+                               const gpr_uint8 *cur, const gpr_uint8 *end) {
+  static const grpc_chttp2_hpack_parser_state and_then[] = {
+      parse_value_string, finish_lithdr_nvridx};
+  p->next_state = and_then;
+  p->index = (*cur) & 0xf;
+  return parse_string_prefix(p, cur + 1, end);
+}
+
+/* parse a literal header that is never indexed; index >= 15 */
+static int parse_lithdr_nvridx_x(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end) {
+  static const grpc_chttp2_hpack_parser_state and_then[] = {
+      parse_string_prefix, parse_value_string, finish_lithdr_nvridx};
+  p->next_state = and_then;
+  p->index = 0xf;
+  p->parsing.value = &p->index;
+  return parse_value0(p, cur + 1, end);
+}
+
+/* parse a literal header that is never indexed; index == 0 */
+static int parse_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p,
+                                 const gpr_uint8 *cur, const gpr_uint8 *end) {
+  static const grpc_chttp2_hpack_parser_state and_then[] = {
+      parse_key_string, parse_string_prefix, parse_value_string,
+      finish_lithdr_nvridx_v};
+  p->next_state = and_then;
+  return parse_string_prefix(p, cur + 1, end);
+}
+
+/* finish parsing a max table size change */
+static int finish_max_tbl_size(grpc_chttp2_hpack_parser *p,
+                               const gpr_uint8 *cur, const gpr_uint8 *end) {
+  gpr_log(GPR_INFO, "MAX TABLE SIZE: %d", p->index);
+  abort(); /* not implemented */
+  return parse_begin(p, cur, end);
+}
+
+/* parse a max table size change, max size < 15 */
+static int parse_max_tbl_size(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                              const gpr_uint8 *end) {
+  p->index = (*cur) & 0xf;
+  return finish_max_tbl_size(p, cur + 1, end);
+}
+
+/* parse a max table size change, max size >= 15 */
+static int parse_max_tbl_size_x(grpc_chttp2_hpack_parser *p,
+                                const gpr_uint8 *cur, const gpr_uint8 *end) {
+  static const grpc_chttp2_hpack_parser_state and_then[] = {
+      finish_max_tbl_size};
+  p->next_state = and_then;
+  p->index = 0xf;
+  p->parsing.value = &p->index;
+  return parse_value0(p, cur + 1, end);
+}
+
+/* a parse error: jam the parse state into parse_error, and return error */
+static int parse_error(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                       const gpr_uint8 *end) {
+  p->state = parse_error;
+  return 0;
+}
+
+/* parse the 1st byte of a varint into p->parsing.value
+   no overflow is possible */
+static int parse_value0(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                        const gpr_uint8 *end) {
+  if (cur == end) {
+    p->state = parse_value0;
+    return 1;
+  }
+
+  *p->parsing.value += (*cur) & 0x7f;
+
+  if ((*cur) & 0x80) {
+    return parse_value1(p, cur + 1, end);
+  } else {
+    return parse_next(p, cur + 1, end);
+  }
+}
+
+/* parse the 2nd byte of a varint into p->parsing.value
+   no overflow is possible */
+static int parse_value1(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                        const gpr_uint8 *end) {
+  if (cur == end) {
+    p->state = parse_value1;
+    return 1;
+  }
+
+  *p->parsing.value += (((gpr_uint32)*cur) & 0x7f) << 7;
+
+  if ((*cur) & 0x80) {
+    return parse_value2(p, cur + 1, end);
+  } else {
+    return parse_next(p, cur + 1, end);
+  }
+}
+
+/* parse the 3rd byte of a varint into p->parsing.value
+   no overflow is possible */
+static int parse_value2(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                        const gpr_uint8 *end) {
+  if (cur == end) {
+    p->state = parse_value2;
+    return 1;
+  }
+
+  *p->parsing.value += (((gpr_uint32)*cur) & 0x7f) << 14;
+
+  if ((*cur) & 0x80) {
+    return parse_value3(p, cur + 1, end);
+  } else {
+    return parse_next(p, cur + 1, end);
+  }
+}
+
+/* parse the 4th byte of a varint into p->parsing.value
+   no overflow is possible */
+static int parse_value3(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                        const gpr_uint8 *end) {
+  if (cur == end) {
+    p->state = parse_value3;
+    return 1;
+  }
+
+  *p->parsing.value += (((gpr_uint32)*cur) & 0x7f) << 21;
+
+  if ((*cur) & 0x80) {
+    return parse_value4(p, cur + 1, end);
+  } else {
+    return parse_next(p, cur + 1, end);
+  }
+}
+
+/* parse the 5th byte of a varint into p->parsing.value
+   depending on the byte, we may overflow, and care must be taken */
+static int parse_value4(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                        const gpr_uint8 *end) {
+  gpr_uint8 c;
+  gpr_uint32 cur_value;
+  gpr_uint32 add_value;
+
+  if (cur == end) {
+    p->state = parse_value4;
+    return 1;
+  }
+
+  c = (*cur) & 0x7f;
+  if (c > 0xf) {
+    goto error;
+  }
+
+  cur_value = *p->parsing.value;
+  add_value = ((gpr_uint32)c) << 28;
+  if (add_value > 0xffffffffu - cur_value) {
+    goto error;
+  }
+
+  *p->parsing.value = cur_value + add_value;
+
+  if ((*cur) & 0x80) {
+    return parse_value5up(p, cur + 1, end);
+  } else {
+    return parse_next(p, cur + 1, end);
+  }
+
+error:
+  gpr_log(GPR_ERROR,
+          "integer overflow in hpack integer decoding: have 0x%08x, "
+          "got byte 0x%02x",
+          *p->parsing.value, *cur);
+  return parse_error(p, cur, end);
+}
+
+/* parse any trailing bytes in a varint: it's possible to append an arbitrary
+   number of 0x80's and not affect the value - a zero will terminate - and
+   anything else will overflow */
+static int parse_value5up(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                          const gpr_uint8 *end) {
+  while (cur != end && *cur == 0x80) {
+    ++cur;
+  }
+
+  if (cur == end) {
+    p->state = parse_value5up;
+    return 1;
+  }
+
+  if (*cur == 0) {
+    return parse_next(p, cur + 1, end);
+  }
+
+  gpr_log(GPR_ERROR,
+          "integer overflow in hpack integer decoding: have 0x%08x, "
+          "got byte 0x%02x sometime after byte 4");
+  return parse_error(p, cur, end);
+}
+
+/* parse a string prefix */
+static int parse_string_prefix(grpc_chttp2_hpack_parser *p,
+                               const gpr_uint8 *cur, const gpr_uint8 *end) {
+  if (cur == end) {
+    p->state = parse_string_prefix;
+    return 1;
+  }
+
+  p->strlen = (*cur) & 0x7f;
+  p->huff = (*cur) >> 7;
+  if (p->strlen == 0x7f) {
+    p->parsing.value = &p->strlen;
+    return parse_value0(p, cur + 1, end);
+  } else {
+    return parse_next(p, cur + 1, end);
+  }
+}
+
+/* append some bytes to a string */
+static void append_string(grpc_chttp2_hpack_parser_string *str,
+                          const gpr_uint8 *data, size_t length) {
+  if (length + str->length > str->capacity) {
+    str->capacity = str->length + length;
+    str->str = gpr_realloc(str->str, str->capacity);
+  }
+  memcpy(str->str + str->length, data, length);
+  str->length += length;
+}
+
+/* append a null terminator to a string */
+static void finish_str(grpc_chttp2_hpack_parser_string *str) {
+  gpr_uint8 terminator = 0;
+  append_string(str, &terminator, 1);
+  str->length--; /* don't actually count the null terminator */
+}
+
+/* decode a nibble from a huffman encoded stream */
+static void huff_nibble(grpc_chttp2_hpack_parser *p, gpr_uint8 nibble) {
+  gpr_int16 emit = emit_sub_tbl[16 * emit_tbl[p->huff_state] + nibble];
+  gpr_int16 next = next_sub_tbl[16 * next_tbl[p->huff_state] + nibble];
+  if (emit != -1) {
+    if (emit >= 0 && emit < 256) {
+      gpr_uint8 c = emit;
+      append_string(p->parsing.str, &c, 1);
+    } else {
+      assert(emit == 256);
+    }
+  }
+  p->huff_state = next;
+}
+
+/* decode full bytes from a huffman encoded stream */
+static void add_huff_bytes(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                           const gpr_uint8 *end) {
+  for (; cur != end; ++cur) {
+    huff_nibble(p, *cur >> 4);
+    huff_nibble(p, *cur & 0xf);
+  }
+}
+
+/* decode some string bytes based on the current decoding mode
+   (huffman or not) */
+static void add_str_bytes(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                          const gpr_uint8 *end) {
+  if (p->huff) {
+    add_huff_bytes(p, cur, end);
+  } else {
+    append_string(p->parsing.str, cur, end - cur);
+  }
+}
+
+/* parse a string - tries to do large chunks at a time */
+static int parse_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                        const gpr_uint8 *end) {
+  size_t remaining = p->strlen - p->strgot;
+  size_t given = end - cur;
+  if (remaining <= given) {
+    add_str_bytes(p, cur, cur + remaining);
+    finish_str(p->parsing.str);
+    return parse_next(p, cur + remaining, end);
+  } else {
+    add_str_bytes(p, cur, cur + given);
+    p->strgot += given;
+    p->state = parse_string;
+    return 1;
+  }
+}
+
+/* begin parsing a string - performs setup, calls parse_string */
+static int begin_parse_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                              const gpr_uint8 *end,
+                              grpc_chttp2_hpack_parser_string *str) {
+  p->strgot = 0;
+  str->length = 0;
+  p->parsing.str = str;
+  p->huff_state = 0;
+  return parse_string(p, cur, end);
+}
+
+/* parse the key string */
+static int parse_key_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                            const gpr_uint8 *end) {
+  return begin_parse_string(p, cur, end, &p->key);
+}
+
+/* parse the value string */
+static int parse_value_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur,
+                              const gpr_uint8 *end) {
+  return begin_parse_string(p, cur, end, &p->value);
+}
+
+/* PUBLIC INTERFACE */
+
+static void on_header_not_set(void *user_data, grpc_mdelem *md) {
+  char *keyhex =
+      gpr_hexdump(grpc_mdstr_as_c_string(md->key),
+                  GPR_SLICE_LENGTH(md->key->slice), GPR_HEXDUMP_PLAINTEXT);
+  char *valuehex =
+      gpr_hexdump(grpc_mdstr_as_c_string(md->value),
+                  GPR_SLICE_LENGTH(md->value->slice), GPR_HEXDUMP_PLAINTEXT);
+  gpr_log(GPR_ERROR, "on_header callback not set; key=%s value=%s", keyhex,
+          valuehex);
+  gpr_free(keyhex);
+  gpr_free(valuehex);
+  grpc_mdelem_unref(md);
+  abort();
+}
+
+void grpc_chttp2_hpack_parser_init(grpc_chttp2_hpack_parser *p,
+                                   grpc_mdctx *mdctx) {
+  p->on_header = on_header_not_set;
+  p->on_header_user_data = NULL;
+  p->state = parse_begin;
+  p->key.str = NULL;
+  p->key.capacity = 0;
+  p->key.length = 0;
+  p->value.str = NULL;
+  p->value.capacity = 0;
+  p->value.length = 0;
+  grpc_chttp2_hptbl_init(&p->table, mdctx);
+}
+
+void grpc_chttp2_hpack_parser_set_has_priority(grpc_chttp2_hpack_parser *p) {
+  GPR_ASSERT(p->state == parse_begin);
+  p->state = parse_stream_dep0;
+}
+
+void grpc_chttp2_hpack_parser_destroy(grpc_chttp2_hpack_parser *p) {
+  grpc_chttp2_hptbl_destroy(&p->table);
+  gpr_free(p->key.str);
+  gpr_free(p->value.str);
+}
+
+int grpc_chttp2_hpack_parser_parse(grpc_chttp2_hpack_parser *p,
+                                   const gpr_uint8 *beg, const gpr_uint8 *end) {
+  /* TODO(ctiller): limit the distance of end from beg, and perform multiple
+                    steps in the event of a large chunk of data to limit
+                    stack space usage when no tail call optimization is
+                    available */
+  return p->state(p, beg, end);
+}
+
+grpc_chttp2_parse_error grpc_chttp2_header_parser_parse(
+    void *hpack_parser, grpc_chttp2_parse_state *state, gpr_slice slice,
+    int is_last) {
+  grpc_chttp2_hpack_parser *parser = hpack_parser;
+  if (!grpc_chttp2_hpack_parser_parse(parser, GPR_SLICE_START_PTR(slice),
+                                      GPR_SLICE_END_PTR(slice))) {
+    return GRPC_CHTTP2_CONNECTION_ERROR;
+  }
+  if (is_last) {
+    if (parser->is_boundary && parser->state != parse_begin) {
+      gpr_log(GPR_ERROR,
+              "end of header frame not aligned with a hpack record boundary");
+      return GRPC_CHTTP2_CONNECTION_ERROR;
+    }
+    state->metadata_boundary = parser->is_boundary;
+    state->end_of_stream = parser->is_eof;
+    state->need_flush_reads = parser->is_eof;
+    parser->on_header = on_header_not_set;
+    parser->on_header_user_data = NULL;
+    parser->is_boundary = 0xde;
+    parser->is_eof = 0xde;
+  }
+  return GRPC_CHTTP2_PARSE_OK;
+}
diff --git a/src/core/transport/chttp2/hpack_parser.h b/src/core/transport/chttp2/hpack_parser.h
new file mode 100644
index 0000000..cf68042
--- /dev/null
+++ b/src/core/transport/chttp2/hpack_parser.h
@@ -0,0 +1,108 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_HPACK_PARSER_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_HPACK_PARSER_H__
+
+#include <stddef.h>
+
+#include <grpc/support/port_platform.h>
+#include "src/core/transport/chttp2/frame.h"
+#include "src/core/transport/chttp2/hpack_table.h"
+#include "src/core/transport/metadata.h"
+
+typedef struct grpc_chttp2_hpack_parser grpc_chttp2_hpack_parser;
+
+typedef int (*grpc_chttp2_hpack_parser_state)(grpc_chttp2_hpack_parser *p,
+                                              const gpr_uint8 *beg,
+                                              const gpr_uint8 *end);
+
+typedef struct {
+  char *str;
+  gpr_uint32 length;
+  gpr_uint32 capacity;
+} grpc_chttp2_hpack_parser_string;
+
+struct grpc_chttp2_hpack_parser {
+  /* user specified callback for each header output */
+  void (*on_header)(void *user_data, grpc_mdelem *md);
+  void *on_header_user_data;
+
+  /* current parse state - or a function that implements it */
+  grpc_chttp2_hpack_parser_state state;
+  /* future states dependent on the opening op code */
+  const grpc_chttp2_hpack_parser_state *next_state;
+  /* the value we're currently parsing */
+  union {
+    gpr_uint32 *value;
+    grpc_chttp2_hpack_parser_string *str;
+  } parsing;
+  /* string parameters for each chunk */
+  grpc_chttp2_hpack_parser_string key;
+  grpc_chttp2_hpack_parser_string value;
+  /* parsed index */
+  gpr_uint32 index;
+  /* length of source bytes for the currently parsing string */
+  gpr_uint32 strlen;
+  /* number of source bytes read for the currently parsing string */
+  gpr_uint32 strgot;
+  /* huffman decoding state */
+  gpr_uint16 huff_state;
+  /* is the current string huffman encoded? */
+  gpr_uint8 huff;
+  /* set by higher layers, used by grpc_chttp2_header_parser_parse to signal
+     it should append a metadata boundary at the end of frame */
+  gpr_uint8 is_boundary;
+  gpr_uint8 is_eof;
+
+  /* hpack table */
+  grpc_chttp2_hptbl table;
+};
+
+void grpc_chttp2_hpack_parser_init(grpc_chttp2_hpack_parser *p,
+                                   grpc_mdctx *mdctx);
+void grpc_chttp2_hpack_parser_destroy(grpc_chttp2_hpack_parser *p);
+
+void grpc_chttp2_hpack_parser_set_has_priority(grpc_chttp2_hpack_parser *p);
+
+/* returns 1 on success, 0 on error */
+int grpc_chttp2_hpack_parser_parse(grpc_chttp2_hpack_parser *p,
+                                   const gpr_uint8 *beg, const gpr_uint8 *end);
+
+/* wraps grpc_chttp2_hpack_parser_parse to provide a frame level parser for
+   the transport */
+grpc_chttp2_parse_error grpc_chttp2_header_parser_parse(
+    void *hpack_parser, grpc_chttp2_parse_state *state, gpr_slice slice,
+    int is_last);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_HPACK_PARSER_H__ */
diff --git a/src/core/transport/chttp2/hpack_table.c b/src/core/transport/chttp2/hpack_table.c
new file mode 100644
index 0000000..8f2ebec
--- /dev/null
+++ b/src/core/transport/chttp2/hpack_table.c
@@ -0,0 +1,210 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/hpack_table.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include <grpc/support/log.h>
+#include "src/core/support/murmur_hash.h"
+
+static struct {
+  const char *key;
+  const char *value;
+} static_table[] = {
+      /* 0: */ {NULL, NULL},
+      /* 1: */ {":authority", ""},
+      /* 2: */ {":method", "GET"},
+      /* 3: */ {":method", "POST"},
+      /* 4: */ {":path", "/"},
+      /* 5: */ {":path", "/index.html"},
+      /* 6: */ {":scheme", "http"},
+      /* 7: */ {":scheme", "https"},
+      /* 8: */ {":status", "200"},
+      /* 9: */ {":status", "204"},
+      /* 10: */ {":status", "206"},
+      /* 11: */ {":status", "304"},
+      /* 12: */ {":status", "400"},
+      /* 13: */ {":status", "404"},
+      /* 14: */ {":status", "500"},
+      /* 15: */ {"accept-charset", ""},
+      /* 16: */ {"accept-encoding", "gzip, deflate"},
+      /* 17: */ {"accept-language", ""},
+      /* 18: */ {"accept-ranges", ""},
+      /* 19: */ {"accept", ""},
+      /* 20: */ {"access-control-allow-origin", ""},
+      /* 21: */ {"age", ""},
+      /* 22: */ {"allow", ""},
+      /* 23: */ {"authorization", ""},
+      /* 24: */ {"cache-control", ""},
+      /* 25: */ {"content-disposition", ""},
+      /* 26: */ {"content-encoding", ""},
+      /* 27: */ {"content-language", ""},
+      /* 28: */ {"content-length", ""},
+      /* 29: */ {"content-location", ""},
+      /* 30: */ {"content-range", ""},
+      /* 31: */ {"content-type", ""},
+      /* 32: */ {"cookie", ""},
+      /* 33: */ {"date", ""},
+      /* 34: */ {"etag", ""},
+      /* 35: */ {"expect", ""},
+      /* 36: */ {"expires", ""},
+      /* 37: */ {"from", ""},
+      /* 38: */ {"host", ""},
+      /* 39: */ {"if-match", ""},
+      /* 40: */ {"if-modified-since", ""},
+      /* 41: */ {"if-none-match", ""},
+      /* 42: */ {"if-range", ""},
+      /* 43: */ {"if-unmodified-since", ""},
+      /* 44: */ {"last-modified", ""},
+      /* 45: */ {"link", ""},
+      /* 46: */ {"location", ""},
+      /* 47: */ {"max-forwards", ""},
+      /* 48: */ {"proxy-authenticate", ""},
+      /* 49: */ {"proxy-authorization", ""},
+      /* 50: */ {"range", ""},
+      /* 51: */ {"referer", ""},
+      /* 52: */ {"refresh", ""},
+      /* 53: */ {"retry-after", ""},
+      /* 54: */ {"server", ""},
+      /* 55: */ {"set-cookie", ""},
+      /* 56: */ {"strict-transport-security", ""},
+      /* 57: */ {"transfer-encoding", ""},
+      /* 58: */ {"user-agent", ""},
+      /* 59: */ {"vary", ""},
+      /* 60: */ {"via", ""},
+      /* 61: */ {"www-authenticate", ""},
+};
+
+void grpc_chttp2_hptbl_init(grpc_chttp2_hptbl *tbl, grpc_mdctx *mdctx) {
+  size_t i;
+
+  memset(tbl, 0, sizeof(*tbl));
+  tbl->mdctx = mdctx;
+  tbl->max_bytes = GRPC_CHTTP2_INITIAL_HPACK_TABLE_SIZE;
+  for (i = 1; i <= GRPC_CHTTP2_LAST_STATIC_ENTRY; i++) {
+    tbl->static_ents[i - 1] = grpc_mdelem_from_strings(
+        mdctx, static_table[i].key, static_table[i].value);
+  }
+}
+
+void grpc_chttp2_hptbl_destroy(grpc_chttp2_hptbl *tbl) {
+  size_t i;
+  for (i = 0; i < GRPC_CHTTP2_LAST_STATIC_ENTRY; i++) {
+    grpc_mdelem_unref(tbl->static_ents[i]);
+  }
+  for (i = 0; i < tbl->num_ents; i++) {
+    grpc_mdelem_unref(
+        tbl->ents[(tbl->first_ent + i) % GRPC_CHTTP2_MAX_TABLE_COUNT]);
+  }
+}
+
+grpc_mdelem *grpc_chttp2_hptbl_lookup(const grpc_chttp2_hptbl *tbl,
+                                      gpr_uint32 index) {
+  /* Static table comes first, just return an entry from it */
+  if (index <= GRPC_CHTTP2_LAST_STATIC_ENTRY) {
+    return tbl->static_ents[index - 1];
+  }
+  /* Otherwise, find the value in the list of valid entries */
+  index -= (GRPC_CHTTP2_LAST_STATIC_ENTRY + 1);
+  if (index < tbl->num_ents) {
+    gpr_uint32 offset = (tbl->num_ents - 1 - index + tbl->first_ent) %
+                        GRPC_CHTTP2_MAX_TABLE_COUNT;
+    return tbl->ents[offset];
+  }
+  /* Invalid entry: return error */
+  return NULL;
+}
+
+/* Evict one element from the table */
+static void evict1(grpc_chttp2_hptbl *tbl) {
+  grpc_mdelem *first_ent = tbl->ents[tbl->first_ent];
+  tbl->mem_used -= GPR_SLICE_LENGTH(first_ent->key->slice) +
+                   GPR_SLICE_LENGTH(first_ent->value->slice) +
+                   GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD;
+  tbl->first_ent = (tbl->first_ent + 1) % GRPC_CHTTP2_MAX_TABLE_COUNT;
+  tbl->num_ents--;
+  grpc_mdelem_unref(first_ent);
+}
+
+void grpc_chttp2_hptbl_add(grpc_chttp2_hptbl *tbl, grpc_mdelem *md) {
+  /* determine how many bytes of buffer this entry represents */
+  gpr_uint16 elem_bytes = GPR_SLICE_LENGTH(md->key->slice) +
+                          GPR_SLICE_LENGTH(md->value->slice) +
+                          GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD;
+
+  /* we can't add elements bigger than the max table size */
+  assert(elem_bytes <= tbl->max_bytes);
+
+  /* evict entries to ensure no overflow */
+  while (elem_bytes > tbl->max_bytes - tbl->mem_used) {
+    evict1(tbl);
+  }
+
+  /* copy the finalized entry in */
+  tbl->ents[tbl->last_ent] = md;
+
+  /* update accounting values */
+  tbl->last_ent = (tbl->last_ent + 1) % GRPC_CHTTP2_MAX_TABLE_COUNT;
+  tbl->num_ents++;
+  tbl->mem_used += elem_bytes;
+}
+
+grpc_chttp2_hptbl_find_result grpc_chttp2_hptbl_find(
+    const grpc_chttp2_hptbl *tbl, grpc_mdelem *md) {
+  grpc_chttp2_hptbl_find_result r = {0, 0};
+  int i;
+
+  /* See if the string is in the static table */
+  for (i = 0; i < GRPC_CHTTP2_LAST_STATIC_ENTRY; i++) {
+    grpc_mdelem *ent = tbl->static_ents[i];
+    if (md->key != ent->key) continue;
+    r.index = i + 1;
+    r.has_value = md->value == ent->value;
+    if (r.has_value) return r;
+  }
+
+  /* Scan the dynamic table */
+  for (i = 0; i < tbl->num_ents; i++) {
+    int idx = tbl->num_ents - i + GRPC_CHTTP2_LAST_STATIC_ENTRY;
+    grpc_mdelem *ent =
+        tbl->ents[(tbl->first_ent + i) % GRPC_CHTTP2_MAX_TABLE_COUNT];
+    if (md->key != ent->key) continue;
+    r.index = idx;
+    r.has_value = md->value == ent->value;
+    if (r.has_value) return r;
+  }
+
+  return r;
+}
diff --git a/src/core/transport/chttp2/hpack_table.h b/src/core/transport/chttp2/hpack_table.h
new file mode 100644
index 0000000..a3a07ad
--- /dev/null
+++ b/src/core/transport/chttp2/hpack_table.h
@@ -0,0 +1,97 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_HPACK_TABLE_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_HPACK_TABLE_H__
+
+#include "src/core/transport/metadata.h"
+#include <grpc/support/port_platform.h>
+#include <grpc/support/slice.h>
+
+/* HPACK header table */
+
+/* last index in the static table */
+#define GRPC_CHTTP2_LAST_STATIC_ENTRY 61
+
+/* Initial table size as per the spec */
+#define GRPC_CHTTP2_INITIAL_HPACK_TABLE_SIZE 4096
+/* Maximum table size that we'll use */
+#define GRPC_CHTTP2_MAX_HPACK_TABLE_SIZE GRPC_CHTTP2_INITIAL_HPACK_TABLE_SIZE
+/* Per entry overhead bytes as per the spec */
+#define GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD 32
+/* Maximum number of entries we could possibly fit in the table, given defined
+   overheads */
+#define GRPC_CHTTP2_MAX_TABLE_COUNT                                            \
+  ((GRPC_CHTTP2_MAX_HPACK_TABLE_SIZE + GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD - 1) / \
+   GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD)
+
+/* hpack decoder table */
+typedef struct {
+  grpc_mdctx *mdctx;
+  /* the first used entry in ents */
+  gpr_uint16 first_ent;
+  /* the last used entry in ents */
+  gpr_uint16 last_ent;
+  /* how many entries are in the table */
+  gpr_uint16 num_ents;
+  /* the amount of memory used by the table, according to the hpack algorithm */
+  gpr_uint16 mem_used;
+  /* the max memory allowed to be used by the table, according to the hpack
+     algorithm */
+  gpr_uint16 max_bytes;
+  /* a circular buffer of headers - this is stored in the opposite order to
+     what hpack specifies, in order to simplify table management a little...
+     meaning lookups need to SUBTRACT from the end position */
+  grpc_mdelem *ents[GRPC_CHTTP2_MAX_TABLE_COUNT];
+  grpc_mdelem *static_ents[GRPC_CHTTP2_LAST_STATIC_ENTRY];
+} grpc_chttp2_hptbl;
+
+/* initialize a hpack table */
+void grpc_chttp2_hptbl_init(grpc_chttp2_hptbl *tbl, grpc_mdctx *mdctx);
+void grpc_chttp2_hptbl_destroy(grpc_chttp2_hptbl *tbl);
+
+/* lookup a table entry based on its hpack index */
+grpc_mdelem *grpc_chttp2_hptbl_lookup(const grpc_chttp2_hptbl *tbl,
+                                      gpr_uint32 index);
+/* add a table entry to the index */
+void grpc_chttp2_hptbl_add(grpc_chttp2_hptbl *tbl, grpc_mdelem *md);
+/* Find a key/value pair in the table... returns the index in the table of the
+   most similar entry, or 0 if the value was not found */
+typedef struct {
+  gpr_uint16 index;
+  gpr_uint8 has_value;
+} grpc_chttp2_hptbl_find_result;
+grpc_chttp2_hptbl_find_result grpc_chttp2_hptbl_find(
+    const grpc_chttp2_hptbl *tbl, grpc_mdelem *md);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_HPACK_TABLE_H__ */
diff --git a/src/core/transport/chttp2/hpack_tables.txt b/src/core/transport/chttp2/hpack_tables.txt
new file mode 100644
index 0000000..08842a0
--- /dev/null
+++ b/src/core/transport/chttp2/hpack_tables.txt
@@ -0,0 +1,66 @@
+Static table, from the spec:
+          +-------+-----------------------------+---------------+
+          | Index | Header Name                 | Header Value  |
+          +-------+-----------------------------+---------------+
+          | 1     | :authority                  |               |
+          | 2     | :method                     | GET           |
+          | 3     | :method                     | POST          |
+          | 4     | :path                       | /             |
+          | 5     | :path                       | /index.html   |
+          | 6     | :scheme                     | http          |
+          | 7     | :scheme                     | https         |
+          | 8     | :status                     | 200           |
+          | 9     | :status                     | 204           |
+          | 10    | :status                     | 206           |
+          | 11    | :status                     | 304           |
+          | 12    | :status                     | 400           |
+          | 13    | :status                     | 404           |
+          | 14    | :status                     | 500           |
+          | 15    | accept-charset              |               |
+          | 16    | accept-encoding             | gzip, deflate |
+          | 17    | accept-language             |               |
+          | 18    | accept-ranges               |               |
+          | 19    | accept                      |               |
+          | 20    | access-control-allow-origin |               |
+          | 21    | age                         |               |
+          | 22    | allow                       |               |
+          | 23    | authorization               |               |
+          | 24    | cache-control               |               |
+          | 25    | content-disposition         |               |
+          | 26    | content-encoding            |               |
+          | 27    | content-language            |               |
+          | 28    | content-length              |               |
+          | 29    | content-location            |               |
+          | 30    | content-range               |               |
+          | 31    | content-type                |               |
+          | 32    | cookie                      |               |
+          | 33    | date                        |               |
+          | 34    | etag                        |               |
+          | 35    | expect                      |               |
+          | 36    | expires                     |               |
+          | 37    | from                        |               |
+          | 38    | host                        |               |
+          | 39    | if-match                    |               |
+          | 40    | if-modified-since           |               |
+          | 41    | if-none-match               |               |
+          | 42    | if-range                    |               |
+          | 43    | if-unmodified-since         |               |
+          | 44    | last-modified               |               |
+          | 45    | link                        |               |
+          | 46    | location                    |               |
+          | 47    | max-forwards                |               |
+          | 48    | proxy-authenticate          |               |
+          | 49    | proxy-authorization         |               |
+          | 50    | range                       |               |
+          | 51    | referer                     |               |
+          | 52    | refresh                     |               |
+          | 53    | retry-after                 |               |
+          | 54    | server                      |               |
+          | 55    | set-cookie                  |               |
+          | 56    | strict-transport-security   |               |
+          | 57    | transfer-encoding           |               |
+          | 58    | user-agent                  |               |
+          | 59    | vary                        |               |
+          | 60    | via                         |               |
+          | 61    | www-authenticate            |               |
+          +-------+-----------------------------+---------------+
diff --git a/src/core/transport/chttp2/http2_errors.h b/src/core/transport/chttp2/http2_errors.h
new file mode 100644
index 0000000..d065422
--- /dev/null
+++ b/src/core/transport/chttp2/http2_errors.h
@@ -0,0 +1,56 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_HTTP2_ERRORS_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_HTTP2_ERRORS_H__
+
+/* error codes for RST_STREAM from http2 draft 14 section 7 */
+typedef enum {
+  GRPC_CHTTP2_NO_ERROR = 0x0,
+  GRPC_CHTTP2_PROTOCOL_ERROR = 0x1,
+  GRPC_CHTTP2_INTERNAL_ERROR = 0x2,
+  GRPC_CHTTP2_FLOW_CONTROL_ERROR = 0x3,
+  GRPC_CHTTP2_SETTINGS_TIMEOUT = 0x4,
+  GRPC_CHTTP2_STREAM_CLOSED = 0x5,
+  GRPC_CHTTP2_FRAME_SIZE_ERROR = 0x6,
+  GRPC_CHTTP2_REFUSED_STREAM = 0x7,
+  GRPC_CHTTP2_CANCEL = 0x8,
+  GRPC_CHTTP2_COMPRESSION_ERROR = 0x9,
+  GRPC_CHTTP2_CONNECT_ERROR = 0xa,
+  GRPC_CHTTP2_ENHANCE_YOUR_CALM = 0xb,
+  GRPC_CHTTP2_INADEQUATE_SECURITY = 0xc,
+  /* force use of a default clause */
+  GRPC_CHTTP2__ERROR_DO_NOT_USE = -1
+} grpc_chttp2_error_code;
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_HTTP2_ERRORS_H__ */
diff --git a/src/core/transport/chttp2/status_conversion.c b/src/core/transport/chttp2/status_conversion.c
new file mode 100644
index 0000000..7bd85e8
--- /dev/null
+++ b/src/core/transport/chttp2/status_conversion.c
@@ -0,0 +1,109 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/status_conversion.h"
+
+int grpc_chttp2_grpc_status_to_http2_error(grpc_status_code status) {
+  switch (status) {
+    case GRPC_STATUS_OK:
+      return GRPC_CHTTP2_NO_ERROR;
+    case GRPC_STATUS_CANCELLED:
+      return GRPC_CHTTP2_CANCEL;
+    case GRPC_STATUS_RESOURCE_EXHAUSTED:
+      return GRPC_CHTTP2_ENHANCE_YOUR_CALM;
+    case GRPC_STATUS_PERMISSION_DENIED:
+      return GRPC_CHTTP2_INADEQUATE_SECURITY;
+    case GRPC_STATUS_UNAVAILABLE:
+      return GRPC_CHTTP2_REFUSED_STREAM;
+    default:
+      return GRPC_CHTTP2_INTERNAL_ERROR;
+  }
+}
+
+grpc_status_code grpc_chttp2_http2_error_to_grpc_status(
+    grpc_chttp2_error_code error) {
+  switch (error) {
+    case GRPC_CHTTP2_NO_ERROR:
+      /* should never be received */
+      return GRPC_STATUS_INTERNAL;
+    case GRPC_CHTTP2_CANCEL:
+      return GRPC_STATUS_CANCELLED;
+    case GRPC_CHTTP2_ENHANCE_YOUR_CALM:
+      return GRPC_STATUS_RESOURCE_EXHAUSTED;
+    case GRPC_CHTTP2_INADEQUATE_SECURITY:
+      return GRPC_STATUS_PERMISSION_DENIED;
+    case GRPC_CHTTP2_REFUSED_STREAM:
+      return GRPC_STATUS_UNAVAILABLE;
+    default:
+      return GRPC_STATUS_INTERNAL;
+  }
+}
+
+grpc_status_code grpc_chttp2_http2_status_to_grpc_status(int status) {
+  switch (status) {
+    /* these HTTP2 status codes are called out explicitly in status.proto */
+    case 200:
+      return GRPC_STATUS_OK;
+    case 400:
+      return GRPC_STATUS_INVALID_ARGUMENT;
+    case 401:
+      return GRPC_STATUS_UNAUTHENTICATED;
+    case 403:
+      return GRPC_STATUS_PERMISSION_DENIED;
+    case 404:
+      return GRPC_STATUS_NOT_FOUND;
+    case 409:
+      return GRPC_STATUS_ABORTED;
+    case 412:
+      return GRPC_STATUS_FAILED_PRECONDITION;
+    case 429:
+      return GRPC_STATUS_RESOURCE_EXHAUSTED;
+    case 499:
+      return GRPC_STATUS_CANCELLED;
+    case 500:
+      return GRPC_STATUS_UNKNOWN;
+    case 501:
+      return GRPC_STATUS_UNIMPLEMENTED;
+    case 503:
+      return GRPC_STATUS_UNAVAILABLE;
+    case 504:
+      return GRPC_STATUS_DEADLINE_EXCEEDED;
+    /* everything else is unknown */
+    default:
+      return GRPC_STATUS_UNKNOWN;
+  }
+}
+
+int grpc_chttp2_grpc_status_to_http2_status(grpc_status_code status) {
+  return 200;
+}
diff --git a/src/core/transport/chttp2/status_conversion.h b/src/core/transport/chttp2/status_conversion.h
new file mode 100644
index 0000000..ae9e7f2
--- /dev/null
+++ b/src/core/transport/chttp2/status_conversion.h
@@ -0,0 +1,50 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_STATUS_CONVERSION_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_STATUS_CONVERSION_H__
+
+#include <grpc/grpc.h>
+#include "src/core/transport/chttp2/http2_errors.h"
+
+/* Conversion of grpc status codes to http2 error codes (for RST_STREAM) */
+grpc_chttp2_error_code grpc_chttp2_grpc_status_to_http2_error(
+    grpc_status_code status);
+grpc_status_code grpc_chttp2_http2_error_to_grpc_status(
+    grpc_chttp2_error_code error);
+
+/* Conversion of HTTP status codes (:status) to grpc status codes */
+grpc_status_code grpc_chttp2_http2_status_to_grpc_status(int status);
+int grpc_chttp2_grpc_status_to_http2_status(grpc_status_code status);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_STATUS_CONVERSION_H__ */
diff --git a/src/core/transport/chttp2/stream_encoder.c b/src/core/transport/chttp2/stream_encoder.c
new file mode 100644
index 0000000..d46366d
--- /dev/null
+++ b/src/core/transport/chttp2/stream_encoder.c
@@ -0,0 +1,553 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/stream_encoder.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include <grpc/support/log.h>
+#include <grpc/support/useful.h>
+#include "src/core/transport/chttp2/hpack_table.h"
+#include "src/core/transport/chttp2/timeout_encoding.h"
+#include "src/core/transport/chttp2/varint.h"
+
+#define HASH_FRAGMENT_1(x) ((x)&255)
+#define HASH_FRAGMENT_2(x) ((x >> 8) & 255)
+#define HASH_FRAGMENT_3(x) ((x >> 16) & 255)
+#define HASH_FRAGMENT_4(x) ((x >> 24) & 255)
+
+/* if the probability of this item being seen again is < 1/x then don't add
+   it to the table */
+#define ONE_ON_ADD_PROBABILITY 128
+/* don't consider adding anything bigger than this to the hpack table */
+#define MAX_DECODER_SPACE_USAGE 512
+
+/* what kind of frame our we encoding? */
+typedef enum { HEADER, DATA, NONE } frame_type;
+
+typedef struct {
+  frame_type cur_frame_type;
+  /* number of bytes in 'output' when we started the frame - used to calculate
+     frame length */
+  size_t output_length_at_start_of_frame;
+  /* index (in output) of the header for the current frame */
+  size_t header_idx;
+  /* was the last frame emitted a header? (if yes, we'll need a CONTINUATION */
+  gpr_uint8 last_was_header;
+  /* output stream id */
+  gpr_uint32 stream_id;
+  /* number of flow controlled bytes written */
+  gpr_uint32 output_size;
+  gpr_slice_buffer *output;
+} framer_state;
+
+/* fills p (which is expected to be 9 bytes long) with a data frame header */
+static void fill_header(gpr_uint8 *p, gpr_uint8 type, gpr_uint32 id,
+                        gpr_uint32 len, gpr_uint8 flags) {
+  *p++ = len >> 16;
+  *p++ = len >> 8;
+  *p++ = len;
+  *p++ = type;
+  *p++ = flags;
+  *p++ = id >> 24;
+  *p++ = id >> 16;
+  *p++ = id >> 8;
+  *p++ = id;
+}
+
+/* finish a frame - fill in the previously reserved header */
+static void finish_frame(framer_state *st, int is_header_boundary,
+                         int is_last_in_stream) {
+  gpr_uint8 type = 0xff;
+  switch (st->cur_frame_type) {
+    case HEADER:
+      type = st->last_was_header ? GRPC_CHTTP2_FRAME_CONTINUATION
+                                 : GRPC_CHTTP2_FRAME_HEADER;
+      st->last_was_header = 1;
+      break;
+    case DATA:
+      type = GRPC_CHTTP2_FRAME_DATA;
+      st->last_was_header = 0;
+      is_header_boundary = 0;
+      break;
+    case NONE:
+      return;
+  }
+  fill_header(GPR_SLICE_START_PTR(st->output->slices[st->header_idx]), type,
+              st->stream_id,
+              st->output->length - st->output_length_at_start_of_frame,
+              (is_last_in_stream ? GRPC_CHTTP2_DATA_FLAG_END_STREAM : 0) |
+                  (is_header_boundary ? GRPC_CHTTP2_DATA_FLAG_END_HEADERS : 0));
+  st->cur_frame_type = NONE;
+}
+
+/* begin a new frame: reserve off header space, remember how many bytes we'd
+   output before beginning */
+static void begin_frame(framer_state *st, frame_type type) {
+  GPR_ASSERT(type != NONE);
+  GPR_ASSERT(st->cur_frame_type == NONE);
+  st->cur_frame_type = type;
+  st->header_idx =
+      gpr_slice_buffer_add_indexed(st->output, gpr_slice_malloc(9));
+  st->output_length_at_start_of_frame = st->output->length;
+}
+
+/* make sure that the current frame is of the type desired, and has sufficient
+   space to add at least about_to_add bytes -- finishes the current frame if
+   needed */
+static void ensure_frame_type(framer_state *st, frame_type type,
+                              int need_bytes) {
+  if (st->cur_frame_type == type &&
+      st->output->length - st->output_length_at_start_of_frame + need_bytes <=
+          GRPC_CHTTP2_MAX_PAYLOAD_LENGTH) {
+    return;
+  }
+  finish_frame(st, type != HEADER, 0);
+  begin_frame(st, type);
+}
+
+/* increment a filter count, halve all counts if one element reaches max */
+static void inc_filter(gpr_uint8 idx, gpr_uint32 *sum, gpr_uint8 *elems) {
+  elems[idx]++;
+  if (elems[idx] < 255) {
+    (*sum)++;
+  } else {
+    int i;
+    *sum = 0;
+    for (i = 0; i < GRPC_CHTTP2_HPACKC_NUM_FILTERS; i++) {
+      elems[i] /= 2;
+      (*sum) += elems[i];
+    }
+  }
+}
+
+static void add_header_data(framer_state *st, gpr_slice slice) {
+  size_t len = GPR_SLICE_LENGTH(slice);
+  size_t remaining;
+  if (len == 0) return;
+  ensure_frame_type(st, HEADER, 1);
+  remaining = GRPC_CHTTP2_MAX_PAYLOAD_LENGTH +
+              st->output_length_at_start_of_frame - st->output->length;
+  if (len <= remaining) {
+    gpr_slice_buffer_add(st->output, slice);
+  } else {
+    gpr_slice_buffer_add(st->output, gpr_slice_split_head(&slice, remaining));
+    add_header_data(st, slice);
+  }
+}
+
+static gpr_uint8 *add_tiny_header_data(framer_state *st, int len) {
+  ensure_frame_type(st, HEADER, len);
+  return gpr_slice_buffer_tiny_add(st->output, len);
+}
+
+static void add_elem(grpc_chttp2_hpack_compressor *c, grpc_mdelem *elem) {
+  gpr_uint32 key_hash = elem->key->hash;
+  gpr_uint32 elem_hash = key_hash ^ elem->value->hash;
+  gpr_uint32 new_index = c->tail_remote_index + c->table_elems + 1;
+  gpr_uint32 elem_size = 32 + GPR_SLICE_LENGTH(elem->key->slice) +
+                         GPR_SLICE_LENGTH(elem->value->slice);
+  int drop_ref;
+
+  /* Reserve space for this element in the remote table: if this overflows
+     the current table, drop elements until it fits, matching the decompressor
+     algorithm */
+  /* TODO(ctiller): constant */
+  while (c->table_size + elem_size > 4096) {
+    c->tail_remote_index++;
+    GPR_ASSERT(c->tail_remote_index > 0);
+    GPR_ASSERT(c->table_size >=
+               c->table_elem_size[c->tail_remote_index %
+                                  GRPC_CHTTP2_HPACKC_MAX_TABLE_ELEMS]);
+    GPR_ASSERT(c->table_elems > 0);
+    c->table_size -= c->table_elem_size[c->tail_remote_index %
+                                        GRPC_CHTTP2_HPACKC_MAX_TABLE_ELEMS];
+    c->table_elems--;
+  }
+  GPR_ASSERT(c->table_elems < GRPC_CHTTP2_HPACKC_MAX_TABLE_ELEMS);
+  c->table_elem_size[new_index % GRPC_CHTTP2_HPACKC_MAX_TABLE_ELEMS] =
+      elem_size;
+  c->table_size += elem_size;
+  c->table_elems++;
+
+  /* Store this element into {entries,indices}_elem */
+  if (c->entries_elems[HASH_FRAGMENT_2(elem_hash)] == elem) {
+    /* already there: update with new index */
+    c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index;
+    drop_ref = 1;
+  } else if (c->entries_elems[HASH_FRAGMENT_3(elem_hash)] == elem) {
+    /* already there (cuckoo): update with new index */
+    c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index;
+    drop_ref = 1;
+  } else if (c->entries_elems[HASH_FRAGMENT_2(elem_hash)] == NULL) {
+    /* not there, but a free element: add */
+    c->entries_elems[HASH_FRAGMENT_2(elem_hash)] = elem;
+    c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index;
+    drop_ref = 0;
+  } else if (c->entries_elems[HASH_FRAGMENT_3(elem_hash)] == NULL) {
+    /* not there (cuckoo), but a free element: add */
+    c->entries_elems[HASH_FRAGMENT_3(elem_hash)] = elem;
+    c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index;
+    drop_ref = 0;
+  } else if (c->indices_elems[HASH_FRAGMENT_2(elem_hash)] <
+             c->indices_elems[HASH_FRAGMENT_3(elem_hash)]) {
+    /* not there: replace oldest */
+    grpc_mdelem_unref(c->entries_elems[HASH_FRAGMENT_2(elem_hash)]);
+    c->entries_elems[HASH_FRAGMENT_2(elem_hash)] = elem;
+    c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index;
+    drop_ref = 0;
+  } else {
+    /* not there: replace oldest */
+    grpc_mdelem_unref(c->entries_elems[HASH_FRAGMENT_3(elem_hash)]);
+    c->entries_elems[HASH_FRAGMENT_3(elem_hash)] = elem;
+    c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index;
+    drop_ref = 0;
+  }
+
+  /* do exactly the same for the key (so we can find by that again too) */
+
+  if (c->entries_keys[HASH_FRAGMENT_2(key_hash)] == elem->key) {
+    c->indices_keys[HASH_FRAGMENT_2(key_hash)] = new_index;
+  } else if (c->entries_keys[HASH_FRAGMENT_3(key_hash)] == elem->key) {
+    c->indices_keys[HASH_FRAGMENT_3(key_hash)] = new_index;
+  } else if (c->entries_keys[HASH_FRAGMENT_2(key_hash)] == NULL) {
+    c->entries_keys[HASH_FRAGMENT_2(key_hash)] = grpc_mdstr_ref(elem->key);
+    c->indices_keys[HASH_FRAGMENT_2(key_hash)] = new_index;
+  } else if (c->entries_keys[HASH_FRAGMENT_3(key_hash)] == NULL) {
+    c->entries_keys[HASH_FRAGMENT_3(key_hash)] = grpc_mdstr_ref(elem->key);
+    c->indices_keys[HASH_FRAGMENT_3(key_hash)] = new_index;
+  } else if (c->indices_keys[HASH_FRAGMENT_2(key_hash)] <
+             c->indices_keys[HASH_FRAGMENT_3(key_hash)]) {
+    grpc_mdstr_unref(c->entries_keys[HASH_FRAGMENT_2(key_hash)]);
+    c->entries_keys[HASH_FRAGMENT_2(key_hash)] = grpc_mdstr_ref(elem->key);
+    c->indices_keys[HASH_FRAGMENT_2(key_hash)] = new_index;
+  } else {
+    grpc_mdstr_unref(c->entries_keys[HASH_FRAGMENT_3(key_hash)]);
+    c->entries_keys[HASH_FRAGMENT_3(key_hash)] = grpc_mdstr_ref(elem->key);
+    c->indices_keys[HASH_FRAGMENT_3(key_hash)] = new_index;
+  }
+
+  if (drop_ref) {
+    grpc_mdelem_unref(elem);
+  }
+}
+
+static void emit_indexed(grpc_chttp2_hpack_compressor *c, gpr_uint32 index,
+                         framer_state *st) {
+  int len = GRPC_CHTTP2_VARINT_LENGTH(index, 1);
+  GRPC_CHTTP2_WRITE_VARINT(index, 1, 0x80, add_tiny_header_data(st, len), len);
+}
+
+static void emit_lithdr_incidx(grpc_chttp2_hpack_compressor *c,
+                               gpr_uint32 key_index, grpc_mdstr *value,
+                               framer_state *st) {
+  int len_pfx = GRPC_CHTTP2_VARINT_LENGTH(key_index, 2);
+  int len_val = GPR_SLICE_LENGTH(value->slice);
+  int len_val_len = GRPC_CHTTP2_VARINT_LENGTH(len_val, 1);
+  GRPC_CHTTP2_WRITE_VARINT(key_index, 2, 0x40,
+                           add_tiny_header_data(st, len_pfx), len_pfx);
+  GRPC_CHTTP2_WRITE_VARINT(len_val, 1, 0x00,
+                           add_tiny_header_data(st, len_val_len), len_val_len);
+  add_header_data(st, gpr_slice_ref(value->slice));
+}
+
+static void emit_lithdr_noidx(grpc_chttp2_hpack_compressor *c,
+                              gpr_uint32 key_index, grpc_mdstr *value,
+                              framer_state *st) {
+  int len_pfx = GRPC_CHTTP2_VARINT_LENGTH(key_index, 4);
+  int len_val = GPR_SLICE_LENGTH(value->slice);
+  int len_val_len = GRPC_CHTTP2_VARINT_LENGTH(len_val, 1);
+  GRPC_CHTTP2_WRITE_VARINT(key_index, 4, 0x00,
+                           add_tiny_header_data(st, len_pfx), len_pfx);
+  GRPC_CHTTP2_WRITE_VARINT(len_val, 1, 0x00,
+                           add_tiny_header_data(st, len_val_len), len_val_len);
+  add_header_data(st, gpr_slice_ref(value->slice));
+}
+
+static void emit_lithdr_incidx_v(grpc_chttp2_hpack_compressor *c,
+                                 grpc_mdstr *key, grpc_mdstr *value,
+                                 framer_state *st) {
+  int len_key = GPR_SLICE_LENGTH(key->slice);
+  int len_val = GPR_SLICE_LENGTH(value->slice);
+  int len_key_len = GRPC_CHTTP2_VARINT_LENGTH(len_key, 1);
+  int len_val_len = GRPC_CHTTP2_VARINT_LENGTH(len_val, 1);
+  *add_tiny_header_data(st, 1) = 0x40;
+  GRPC_CHTTP2_WRITE_VARINT(len_key, 1, 0x00,
+                           add_tiny_header_data(st, len_key_len), len_key_len);
+  add_header_data(st, gpr_slice_ref(key->slice));
+  GRPC_CHTTP2_WRITE_VARINT(len_val, 1, 0x00,
+                           add_tiny_header_data(st, len_val_len), len_val_len);
+  add_header_data(st, gpr_slice_ref(value->slice));
+}
+
+static void emit_lithdr_noidx_v(grpc_chttp2_hpack_compressor *c,
+                                grpc_mdstr *key, grpc_mdstr *value,
+                                framer_state *st) {
+  int len_key = GPR_SLICE_LENGTH(key->slice);
+  int len_val = GPR_SLICE_LENGTH(value->slice);
+  int len_key_len = GRPC_CHTTP2_VARINT_LENGTH(len_key, 1);
+  int len_val_len = GRPC_CHTTP2_VARINT_LENGTH(len_val, 1);
+  *add_tiny_header_data(st, 1) = 0x00;
+  GRPC_CHTTP2_WRITE_VARINT(len_key, 1, 0x00,
+                           add_tiny_header_data(st, len_key_len), len_key_len);
+  add_header_data(st, gpr_slice_ref(key->slice));
+  GRPC_CHTTP2_WRITE_VARINT(len_val, 1, 0x00,
+                           add_tiny_header_data(st, len_val_len), len_val_len);
+  add_header_data(st, gpr_slice_ref(value->slice));
+}
+
+static gpr_uint32 dynidx(grpc_chttp2_hpack_compressor *c, gpr_uint32 index) {
+  return 1 + GRPC_CHTTP2_LAST_STATIC_ENTRY + c->tail_remote_index +
+         c->table_elems - index;
+}
+
+/* encode an mdelem, taking ownership of it */
+static void hpack_enc(grpc_chttp2_hpack_compressor *c, grpc_mdelem *elem,
+                      framer_state *st) {
+  gpr_uint32 key_hash = elem->key->hash;
+  gpr_uint32 elem_hash = key_hash ^ elem->value->hash;
+  size_t decoder_space_usage;
+  int should_add_elem;
+
+  inc_filter(HASH_FRAGMENT_1(elem_hash), &c->filter_elems_sum, c->filter_elems);
+
+  /* is this elem currently in the decoders table? */
+
+  if (c->entries_elems[HASH_FRAGMENT_2(elem_hash)] == elem &&
+      c->indices_elems[HASH_FRAGMENT_2(elem_hash)] > c->tail_remote_index) {
+    /* HIT: complete element (first cuckoo hash) */
+    emit_indexed(c, dynidx(c, c->indices_elems[HASH_FRAGMENT_2(elem_hash)]),
+                 st);
+    grpc_mdelem_unref(elem);
+    return;
+  }
+
+  if (c->entries_elems[HASH_FRAGMENT_3(elem_hash)] == elem &&
+      c->indices_elems[HASH_FRAGMENT_3(elem_hash)] > c->tail_remote_index) {
+    /* HIT: complete element (second cuckoo hash) */
+    emit_indexed(c, dynidx(c, c->indices_elems[HASH_FRAGMENT_3(elem_hash)]),
+                 st);
+    grpc_mdelem_unref(elem);
+    return;
+  }
+
+  /* should this elem be in the table? */
+  decoder_space_usage = 32 + GPR_SLICE_LENGTH(elem->key->slice) +
+                        GPR_SLICE_LENGTH(elem->value->slice);
+  should_add_elem = decoder_space_usage < MAX_DECODER_SPACE_USAGE &&
+                    c->filter_elems[HASH_FRAGMENT_1(elem_hash)] >=
+                        c->filter_elems_sum / ONE_ON_ADD_PROBABILITY;
+
+  /* no hits for the elem... maybe there's a key? */
+
+  if (c->entries_keys[HASH_FRAGMENT_2(key_hash)] == elem->key &&
+      c->indices_keys[HASH_FRAGMENT_2(key_hash)] > c->tail_remote_index) {
+    /* HIT: key (first cuckoo hash) */
+    if (should_add_elem) {
+      emit_lithdr_incidx(c,
+                         dynidx(c, c->indices_keys[HASH_FRAGMENT_2(key_hash)]),
+                         elem->value, st);
+      add_elem(c, elem);
+    } else {
+      emit_lithdr_noidx(c,
+                        dynidx(c, c->indices_keys[HASH_FRAGMENT_2(key_hash)]),
+                        elem->value, st);
+      grpc_mdelem_unref(elem);
+    }
+    return;
+  }
+
+  if (c->entries_keys[HASH_FRAGMENT_3(key_hash)] == elem->key &&
+      c->indices_keys[HASH_FRAGMENT_3(key_hash)] > c->tail_remote_index) {
+    /* HIT: key (first cuckoo hash) */
+    if (should_add_elem) {
+      emit_lithdr_incidx(c,
+                         dynidx(c, c->indices_keys[HASH_FRAGMENT_3(key_hash)]),
+                         elem->value, st);
+      add_elem(c, elem);
+    } else {
+      emit_lithdr_noidx(c,
+                        dynidx(c, c->indices_keys[HASH_FRAGMENT_3(key_hash)]),
+                        elem->value, st);
+      grpc_mdelem_unref(elem);
+    }
+    return;
+  }
+
+  /* no elem, key in the table... fall back to literal emission */
+
+  if (should_add_elem) {
+    emit_lithdr_incidx_v(c, elem->key, elem->value, st);
+    add_elem(c, elem);
+  } else {
+    emit_lithdr_noidx_v(c, elem->key, elem->value, st);
+    grpc_mdelem_unref(elem);
+  }
+}
+
+#define STRLEN_LIT(x) (sizeof(x) - 1)
+#define TIMEOUT_KEY "grpc-timeout"
+
+static void deadline_enc(grpc_chttp2_hpack_compressor *c, gpr_timespec deadline,
+                         framer_state *st) {
+  char timeout_str[32];
+  grpc_chttp2_encode_timeout(gpr_time_sub(deadline, gpr_now()), timeout_str);
+  hpack_enc(c, grpc_mdelem_from_metadata_strings(
+                   c->mdctx, grpc_mdstr_ref(c->timeout_key_str),
+                   grpc_mdstr_from_string(c->mdctx, timeout_str)),
+            st);
+}
+
+gpr_slice grpc_chttp2_data_frame_create_empty_close(gpr_uint32 id) {
+  gpr_slice slice = gpr_slice_malloc(9);
+  fill_header(GPR_SLICE_START_PTR(slice), GRPC_CHTTP2_FRAME_DATA, id, 0, 1);
+  return slice;
+}
+
+void grpc_chttp2_hpack_compressor_init(grpc_chttp2_hpack_compressor *c,
+                                       grpc_mdctx *ctx) {
+  memset(c, 0, sizeof(*c));
+  c->mdctx = ctx;
+  c->timeout_key_str = grpc_mdstr_from_string(ctx, "grpc-timeout");
+}
+
+void grpc_chttp2_hpack_compressor_destroy(grpc_chttp2_hpack_compressor *c) {
+  int i;
+  for (i = 0; i < GRPC_CHTTP2_HPACKC_NUM_VALUES; i++) {
+    if (c->entries_keys[i]) grpc_mdstr_unref(c->entries_keys[i]);
+    if (c->entries_elems[i]) grpc_mdelem_unref(c->entries_elems[i]);
+  }
+  grpc_mdstr_unref(c->timeout_key_str);
+}
+
+gpr_uint32 grpc_chttp2_encode_some(grpc_stream_op *ops, size_t *ops_count,
+                                   int eof, gpr_slice_buffer *output,
+                                   gpr_uint32 max_bytes, gpr_uint32 stream_id,
+                                   grpc_chttp2_hpack_compressor *compressor) {
+  framer_state st;
+  gpr_slice slice;
+  grpc_stream_op *op;
+  gpr_uint32 max_take_size;
+  gpr_uint32 curop = 0;
+  gpr_uint32 nops = *ops_count;
+  gpr_uint8 *p;
+
+  GPR_ASSERT(stream_id != 0);
+
+  st.cur_frame_type = NONE;
+  st.last_was_header = 0;
+  st.stream_id = stream_id;
+  st.output = output;
+  st.output_size = 0;
+
+  while (curop < nops) {
+    GPR_ASSERT(st.output_size <= max_bytes);
+    op = &ops[curop];
+    switch (op->type) {
+      case GRPC_NO_OP:
+        curop++;
+        break;
+      case GRPC_OP_FLOW_CTL_CB:
+        op->data.flow_ctl_cb.cb(op->data.flow_ctl_cb.arg, GRPC_OP_OK);
+        curop++;
+        break;
+      case GRPC_OP_METADATA:
+        hpack_enc(compressor, op->data.metadata, &st);
+        curop++;
+        break;
+      case GRPC_OP_DEADLINE:
+        deadline_enc(compressor, op->data.deadline, &st);
+        curop++;
+        break;
+      case GRPC_OP_METADATA_BOUNDARY:
+        ensure_frame_type(&st, HEADER, 0);
+        finish_frame(&st, 1, 0);
+        st.last_was_header = 0; /* force a new header frame */
+        curop++;
+        break;
+      case GRPC_OP_BEGIN_MESSAGE:
+        /* begin op: for now we just convert the op to a slice and fall
+           through - this lets us reuse the slice framing code below */
+        slice = gpr_slice_malloc(5);
+        p = GPR_SLICE_START_PTR(slice);
+        p[0] = 0;
+        p[1] = op->data.begin_message.length >> 24;
+        p[2] = op->data.begin_message.length >> 16;
+        p[3] = op->data.begin_message.length >> 8;
+        p[4] = op->data.begin_message.length;
+        op->type = GRPC_OP_SLICE;
+        op->data.slice = slice;
+      /* fallthrough */
+      case GRPC_OP_SLICE:
+        slice = op->data.slice;
+        if (!GPR_SLICE_LENGTH(slice)) {
+          curop++;
+          break;
+        }
+        if (st.output_size == max_bytes) {
+          goto exit_loop;
+        }
+        if (st.cur_frame_type == DATA &&
+            st.output->length - st.output_length_at_start_of_frame ==
+                GRPC_CHTTP2_MAX_PAYLOAD_LENGTH) {
+          finish_frame(&st, 0, 0);
+        }
+        ensure_frame_type(&st, DATA, 1);
+        max_take_size =
+            GPR_MIN(max_bytes - st.output_size,
+                    GRPC_CHTTP2_MAX_PAYLOAD_LENGTH +
+                        st.output_length_at_start_of_frame - st.output->length);
+        if (GPR_SLICE_LENGTH(slice) > max_take_size) {
+          slice = gpr_slice_split_head(&op->data.slice, max_take_size);
+        } else {
+          /* consume this op immediately */
+          curop++;
+        }
+        st.output_size += GPR_SLICE_LENGTH(slice);
+        gpr_slice_buffer_add(output, slice);
+        break;
+    }
+  }
+exit_loop:
+  if (eof && st.cur_frame_type == NONE) {
+    begin_frame(&st, DATA);
+  }
+  finish_frame(&st, 1, eof && curop == nops);
+
+  nops -= curop;
+  *ops_count = nops;
+  memmove(ops, ops + curop, nops * sizeof(grpc_stream_op));
+
+  return st.output_size;
+}
diff --git a/src/core/transport/chttp2/stream_encoder.h b/src/core/transport/chttp2/stream_encoder.h
new file mode 100644
index 0000000..dad6469
--- /dev/null
+++ b/src/core/transport/chttp2/stream_encoder.h
@@ -0,0 +1,86 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_STREAM_ENCODER_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_STREAM_ENCODER_H__
+
+#include "src/core/transport/chttp2/frame.h"
+#include "src/core/transport/metadata.h"
+#include "src/core/transport/stream_op.h"
+#include <grpc/support/port_platform.h>
+#include <grpc/support/slice.h>
+#include <grpc/support/slice_buffer.h>
+
+#define GRPC_CHTTP2_HPACKC_NUM_FILTERS 256
+#define GRPC_CHTTP2_HPACKC_NUM_VALUES 256
+#define GRPC_CHTTP2_HPACKC_MAX_TABLE_ELEMS (4096 / 32)
+
+typedef struct {
+  gpr_uint32 filter_elems_sum;
+  /* one before the lowest usable table index */
+  gpr_uint32 tail_remote_index;
+  gpr_uint16 table_size;
+  gpr_uint16 table_elems;
+
+  /* filter tables for elems: this tables provides an approximate
+     popularity count for particular hashes, and are used to determine whether
+     a new literal should be added to the compression table or not.
+     They track a single integer that counts how often a particular value has
+     been seen. When that count reaches max (255), all values are halved. */
+  gpr_uint8 filter_elems[GRPC_CHTTP2_HPACKC_NUM_FILTERS];
+
+  /* metadata context */
+  grpc_mdctx *mdctx;
+  /* the string 'grpc-timeout' */
+  grpc_mdstr *timeout_key_str;
+
+  /* entry tables for keys & elems: these tables track values that have been
+     seen and *may* be in the decompressor table */
+  grpc_mdstr *entries_keys[GRPC_CHTTP2_HPACKC_NUM_VALUES];
+  grpc_mdelem *entries_elems[GRPC_CHTTP2_HPACKC_NUM_VALUES];
+  gpr_uint32 indices_keys[GRPC_CHTTP2_HPACKC_NUM_VALUES];
+  gpr_uint32 indices_elems[GRPC_CHTTP2_HPACKC_NUM_VALUES];
+
+  gpr_uint16 table_elem_size[GRPC_CHTTP2_HPACKC_MAX_TABLE_ELEMS];
+} grpc_chttp2_hpack_compressor;
+
+void grpc_chttp2_hpack_compressor_init(grpc_chttp2_hpack_compressor *c,
+                                       grpc_mdctx *mdctx);
+void grpc_chttp2_hpack_compressor_destroy(grpc_chttp2_hpack_compressor *c);
+
+gpr_uint32 grpc_chttp2_encode_some(grpc_stream_op *ops, size_t *ops_count,
+                                   int eof, gpr_slice_buffer *output,
+                                   gpr_uint32 max_bytes, gpr_uint32 stream_id,
+                                   grpc_chttp2_hpack_compressor *compressor);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_STREAM_ENCODER_H__ */
diff --git a/src/core/transport/chttp2/stream_map.c b/src/core/transport/chttp2/stream_map.c
new file mode 100644
index 0000000..9ac3a47
--- /dev/null
+++ b/src/core/transport/chttp2/stream_map.c
@@ -0,0 +1,154 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/stream_map.h"
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+void grpc_chttp2_stream_map_init(grpc_chttp2_stream_map *map,
+                                 size_t initial_capacity) {
+  GPR_ASSERT(initial_capacity > 1);
+  map->keys = gpr_malloc(sizeof(gpr_uint32) * initial_capacity);
+  map->values = gpr_malloc(sizeof(void *) * initial_capacity);
+  map->count = 0;
+  map->free = 0;
+  map->capacity = initial_capacity;
+}
+
+void grpc_chttp2_stream_map_destroy(grpc_chttp2_stream_map *map) {
+  gpr_free(map->keys);
+  gpr_free(map->values);
+}
+
+static size_t compact(gpr_uint32 *keys, void **values, size_t count) {
+  size_t i, out;
+
+  for (i = 0, out = 0; i < count; i++) {
+    if (values[i]) {
+      keys[out] = keys[i];
+      values[out] = values[i];
+      out++;
+    }
+  }
+
+  return out;
+}
+
+void grpc_chttp2_stream_map_add(grpc_chttp2_stream_map *map, gpr_uint32 key,
+                                void *value) {
+  size_t count = map->count;
+  size_t capacity = map->capacity;
+  gpr_uint32 *keys = map->keys;
+  void **values = map->values;
+
+  GPR_ASSERT(count == 0 || keys[count - 1] < key);
+  GPR_ASSERT(value);
+
+  if (count == capacity) {
+    if (map->free > capacity / 4) {
+      count = compact(keys, values, count);
+      map->free = 0;
+    } else {
+      /* resize when less than 25% of the table is free, because compaction
+         won't help much */
+      map->capacity = capacity = 3 * capacity / 2;
+      map->keys = keys = gpr_realloc(keys, capacity * sizeof(gpr_uint32));
+      map->values = values = gpr_realloc(values, capacity * sizeof(void *));
+    }
+  }
+
+  keys[count] = key;
+  values[count] = value;
+  map->count = count + 1;
+}
+
+static void **find(grpc_chttp2_stream_map *map, gpr_uint32 key) {
+  size_t min_idx = 0;
+  size_t max_idx = map->count;
+  size_t mid_idx;
+  gpr_uint32 *keys = map->keys;
+  void **values = map->values;
+  gpr_uint32 mid_key;
+
+  if (max_idx == 0) return NULL;
+
+  while (min_idx < max_idx) {
+    /* find the midpoint, avoiding overflow */
+    mid_idx = min_idx + ((max_idx - min_idx) / 2);
+    mid_key = keys[mid_idx];
+
+    if (mid_key < key) {
+      min_idx = mid_idx + 1;
+    } else if (mid_key > key) {
+      max_idx = mid_idx;
+    } else /* mid_key == key */ {
+      return &values[mid_idx];
+    }
+  }
+
+  return NULL;
+}
+
+void *grpc_chttp2_stream_map_delete(grpc_chttp2_stream_map *map,
+                                    gpr_uint32 key) {
+  void **pvalue = find(map, key);
+  void *out = NULL;
+  if (pvalue != NULL) {
+    out = *pvalue;
+    *pvalue = NULL;
+    map->free += (out != NULL);
+  }
+  return out;
+}
+
+void *grpc_chttp2_stream_map_find(grpc_chttp2_stream_map *map, gpr_uint32 key) {
+  void **pvalue = find(map, key);
+  return pvalue != NULL ? *pvalue : NULL;
+}
+
+size_t grpc_chttp2_stream_map_size(grpc_chttp2_stream_map *map) {
+  return map->count - map->free;
+}
+
+void grpc_chttp2_stream_map_for_each(grpc_chttp2_stream_map *map,
+                                     void (*f)(void *user_data, gpr_uint32 key,
+                                               void *value),
+                                     void *user_data) {
+  size_t i;
+
+  for (i = 0; i < map->count; i++) {
+    if (map->values[i]) {
+      f(user_data, map->keys[i], map->values[i]);
+    }
+  }
+}
diff --git a/src/core/transport/chttp2/stream_map.h b/src/core/transport/chttp2/stream_map.h
new file mode 100644
index 0000000..caaee30
--- /dev/null
+++ b/src/core/transport/chttp2/stream_map.h
@@ -0,0 +1,81 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_STREAM_MAP_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_STREAM_MAP_H__
+
+#include <grpc/support/port_platform.h>
+
+#include <stddef.h>
+
+/* Data structure to map a gpr_uint32 to a data object (represented by a void*)
+
+   Represented as a sorted array of keys, and a corresponding array of values.
+   Lookups are performed with binary search.
+   Adds are restricted to strictly higher keys than previously seen (this is
+   guaranteed by http2). */
+typedef struct {
+  gpr_uint32 *keys;
+  void **values;
+  size_t count;
+  size_t free;
+  size_t capacity;
+} grpc_chttp2_stream_map;
+
+void grpc_chttp2_stream_map_init(grpc_chttp2_stream_map *map,
+                                 size_t initial_capacity);
+void grpc_chttp2_stream_map_destroy(grpc_chttp2_stream_map *map);
+
+/* Add a new key: given http2 semantics, new keys must always be greater than
+   existing keys - this is asserted */
+void grpc_chttp2_stream_map_add(grpc_chttp2_stream_map *map, gpr_uint32 key,
+                                void *value);
+
+/* Delete an existing key - returns the previous value of the key if it existed,
+   or NULL otherwise */
+void *grpc_chttp2_stream_map_delete(grpc_chttp2_stream_map *map,
+                                    gpr_uint32 key);
+
+/* Return an existing key, or NULL if it does not exist */
+void *grpc_chttp2_stream_map_find(grpc_chttp2_stream_map *map, gpr_uint32 key);
+
+/* How many (populated) entries are in the stream map? */
+size_t grpc_chttp2_stream_map_size(grpc_chttp2_stream_map *map);
+
+/* Callback on each stream */
+void grpc_chttp2_stream_map_for_each(grpc_chttp2_stream_map *map,
+                                     void (*f)(void *user_data, gpr_uint32 key,
+                                               void *value),
+                                     void *user_data);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_STREAM_MAP_H__ */
diff --git a/src/core/transport/chttp2/timeout_encoding.c b/src/core/transport/chttp2/timeout_encoding.c
new file mode 100644
index 0000000..2706c36
--- /dev/null
+++ b/src/core/transport/chttp2/timeout_encoding.c
@@ -0,0 +1,176 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/timeout_encoding.h"
+
+#include <stdio.h>
+#include <string.h>
+
+static int round_up(int x, int divisor) {
+  return (x / divisor + (x % divisor != 0)) * divisor;
+}
+
+/* round an integer up to the next value with three significant figures */
+static int round_up_to_three_sig_figs(int x) {
+  if (x < 1000) return x;
+  if (x < 10000) return round_up(x, 10);
+  if (x < 100000) return round_up(x, 100);
+  if (x < 1000000) return round_up(x, 1000);
+  if (x < 10000000) return round_up(x, 10000);
+  if (x < 100000000) return round_up(x, 100000);
+  if (x < 1000000000) return round_up(x, 1000000);
+  return round_up(x, 10000000);
+}
+
+/* encode our minimum viable timeout value */
+static void enc_tiny(char *buffer) { strcpy(buffer, "1n"); }
+
+static void enc_seconds(char *buffer, long sec) {
+  if (sec % 3600 == 0) {
+    sprintf(buffer, "%ldH", sec / 3600);
+  } else if (sec % 60 == 0) {
+    sprintf(buffer, "%ldM", sec / 60);
+  } else {
+    sprintf(buffer, "%ldS", sec);
+  }
+}
+
+static void enc_nanos(char *buffer, int x) {
+  x = round_up_to_three_sig_figs(x);
+  if (x < 100000) {
+    if (x % 1000 == 0) {
+      sprintf(buffer, "%du", x / 1000);
+    } else {
+      sprintf(buffer, "%dn", x);
+    }
+  } else if (x < 100000000) {
+    if (x % 1000000 == 0) {
+      sprintf(buffer, "%dm", x / 1000000);
+    } else {
+      sprintf(buffer, "%du", x / 1000);
+    }
+  } else if (x < 1000000000) {
+    sprintf(buffer, "%dm", x / 1000000);
+  } else {
+    /* note that this is only ever called with times of less than one second,
+       so if we reach here the time must have been rounded up to a whole second
+       (and no more) */
+    strcpy(buffer, "1S");
+  }
+}
+
+static void enc_micros(char *buffer, int x) {
+  x = round_up_to_three_sig_figs(x);
+  if (x < 100000) {
+    if (x % 1000 == 0) {
+      sprintf(buffer, "%dm", x / 1000);
+    } else {
+      sprintf(buffer, "%du", x);
+    }
+  } else if (x < 100000000) {
+    if (x % 1000000 == 0) {
+      sprintf(buffer, "%dS", x / 1000000);
+    } else {
+      sprintf(buffer, "%dm", x / 1000);
+    }
+  } else {
+    sprintf(buffer, "%dS", x / 1000000);
+  }
+}
+
+void grpc_chttp2_encode_timeout(gpr_timespec timeout, char *buffer) {
+  if (timeout.tv_sec < 0) {
+    enc_tiny(buffer);
+  } else if (timeout.tv_sec == 0) {
+    enc_nanos(buffer, timeout.tv_nsec);
+  } else if (timeout.tv_sec < 1000 && timeout.tv_nsec != 0) {
+    enc_micros(buffer,
+               timeout.tv_sec * 1000000 +
+                   (timeout.tv_nsec / 1000 + (timeout.tv_nsec % 1000 != 0)));
+  } else {
+    enc_seconds(buffer, timeout.tv_sec + (timeout.tv_nsec != 0));
+  }
+}
+
+static int is_all_whitespace(const char *p) {
+  while (*p == ' ') p++;
+  return *p == 0;
+}
+
+int grpc_chttp2_decode_timeout(const char *buffer, gpr_timespec *timeout) {
+  gpr_uint32 x = 0;
+  const char *p = buffer;
+  int have_digit = 0;
+  /* skip whitespace */
+  for (; *p == ' '; p++)
+    ;
+  /* decode numeric part */
+  for (; *p >= '0' && *p <= '9'; p++) {
+    gpr_uint32 xp = x * 10 + *p - '0';
+    have_digit = 1;
+    if (xp < x) {
+      *timeout = gpr_inf_future;
+      return 1;
+    }
+    x = xp;
+  }
+  if (!have_digit) return 0;
+  /* skip whitespace */
+  for (; *p == ' '; p++)
+    ;
+  /* decode unit specifier */
+  switch (*p) {
+    case 'n':
+      *timeout = gpr_time_from_nanos(x);
+      break;
+    case 'u':
+      *timeout = gpr_time_from_micros(x);
+      break;
+    case 'm':
+      *timeout = gpr_time_from_millis(x);
+      break;
+    case 'S':
+      *timeout = gpr_time_from_seconds(x);
+      break;
+    case 'M':
+      *timeout = gpr_time_from_minutes(x);
+      break;
+    case 'H':
+      *timeout = gpr_time_from_hours(x);
+      break;
+    default:
+      return 0;
+  }
+  p++;
+  return is_all_whitespace(p);
+}
diff --git a/src/core/transport/chttp2/timeout_encoding.h b/src/core/transport/chttp2/timeout_encoding.h
new file mode 100644
index 0000000..a458256
--- /dev/null
+++ b/src/core/transport/chttp2/timeout_encoding.h
@@ -0,0 +1,44 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_TIMEOUT_ENCODING_H_
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_TIMEOUT_ENCODING_H_
+
+#include <grpc/support/time.h>
+
+/* Encode/decode timeouts to the GRPC over HTTP2 format;
+   encoding may round up arbitrarily */
+void grpc_chttp2_encode_timeout(gpr_timespec timeout, char *buffer);
+int grpc_chttp2_decode_timeout(const char *buffer, gpr_timespec *timeout);
+
+#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_TIMEOUT_ENCODING_H_ */
diff --git a/src/core/transport/chttp2/varint.c b/src/core/transport/chttp2/varint.c
new file mode 100644
index 0000000..5d551be
--- /dev/null
+++ b/src/core/transport/chttp2/varint.c
@@ -0,0 +1,65 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/varint.h"
+
+int grpc_chttp2_hpack_varint_length(gpr_uint32 tail_value) {
+  if (tail_value < (1 << 7)) {
+    return 2;
+  } else if (tail_value < (1 << 14)) {
+    return 3;
+  } else if (tail_value < (1 << 21)) {
+    return 4;
+  } else if (tail_value < (1 << 28)) {
+    return 5;
+  } else {
+    return 6;
+  }
+}
+
+void grpc_chttp2_hpack_write_varint_tail(gpr_uint32 tail_value,
+                                         gpr_uint8* target, int tail_length) {
+  switch (tail_length) {
+    case 5:
+      target[4] = (gpr_uint8)((tail_value >> 28) | 0x80);
+    case 4:
+      target[3] = (gpr_uint8)((tail_value >> 21) | 0x80);
+    case 3:
+      target[2] = (gpr_uint8)((tail_value >> 14) | 0x80);
+    case 2:
+      target[1] = (gpr_uint8)((tail_value >> 7) | 0x80);
+    case 1:
+      target[0] = (gpr_uint8)((tail_value) | 0x80);
+  }
+  target[tail_length - 1] &= 0x7f;
+}
diff --git a/src/core/transport/chttp2/varint.h b/src/core/transport/chttp2/varint.h
new file mode 100644
index 0000000..7803902
--- /dev/null
+++ b/src/core/transport/chttp2/varint.h
@@ -0,0 +1,73 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_VARINT_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_VARINT_H__
+
+#include <grpc/support/port_platform.h>
+
+/* Helpers for hpack varint encoding */
+
+/* length of a value that needs varint tail encoding (it's bigger than can be
+   bitpacked into the opcode byte) - returned value includes the length of the
+   opcode byte */
+int grpc_chttp2_hpack_varint_length(gpr_uint32 tail_value);
+
+void grpc_chttp2_hpack_write_varint_tail(gpr_uint32 tail_value,
+                                         gpr_uint8* target, int tail_length);
+
+/* maximum value that can be bitpacked with the opcode if the opcode has a
+   prefix
+   of length prefix_bits */
+#define GRPC_CHTTP2_MAX_IN_PREFIX(prefix_bits) ((1 << (8 - (prefix_bits))) - 1)
+
+/* length required to bitpack a value */
+#define GRPC_CHTTP2_VARINT_LENGTH(n, prefix_bits) \
+  ((n) < GRPC_CHTTP2_MAX_IN_PREFIX(prefix_bits)   \
+       ? 1                                        \
+       : grpc_chttp2_hpack_varint_length(         \
+             (n)-GRPC_CHTTP2_MAX_IN_PREFIX(prefix_bits)))
+
+#define GRPC_CHTTP2_WRITE_VARINT(n, prefix_bits, prefix_or, target, length)   \
+  do {                                                                        \
+    gpr_uint8* tgt = target;                                                  \
+    if ((length) == 1) {                                                      \
+      (tgt)[0] = (prefix_or) | (n);                                           \
+    } else {                                                                  \
+      (tgt)[0] = (prefix_or) | GRPC_CHTTP2_MAX_IN_PREFIX(prefix_bits);        \
+      grpc_chttp2_hpack_write_varint_tail(                                    \
+          (n)-GRPC_CHTTP2_MAX_IN_PREFIX(prefix_bits), (tgt) + 1, (length)-1); \
+    }                                                                         \
+  } while (0)
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_VARINT_H__ */
diff --git a/src/core/transport/chttp2_transport.c b/src/core/transport/chttp2_transport.c
new file mode 100644
index 0000000..8a6b427
--- /dev/null
+++ b/src/core/transport/chttp2_transport.c
@@ -0,0 +1,1615 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2_transport.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/slice_buffer.h>
+#include <grpc/support/string.h>
+#include <grpc/support/useful.h>
+#include "src/core/transport/transport_impl.h"
+#include "src/core/transport/chttp2/http2_errors.h"
+#include "src/core/transport/chttp2/hpack_parser.h"
+#include "src/core/transport/chttp2/frame_data.h"
+#include "src/core/transport/chttp2/frame_ping.h"
+#include "src/core/transport/chttp2/frame_rst_stream.h"
+#include "src/core/transport/chttp2/frame_settings.h"
+#include "src/core/transport/chttp2/frame_window_update.h"
+#include "src/core/transport/chttp2/status_conversion.h"
+#include "src/core/transport/chttp2/stream_encoder.h"
+#include "src/core/transport/chttp2/stream_map.h"
+#include "src/core/transport/chttp2/timeout_encoding.h"
+
+#define DEFAULT_WINDOW 65536
+#define MAX_WINDOW 0x7fffffffu
+
+#define CLIENT_CONNECT_STRING "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
+#define CLIENT_CONNECT_STRLEN 24
+
+typedef struct transport transport;
+typedef struct stream stream;
+
+/* streams are kept in various linked lists depending on what things need to
+   happen to them... this enum labels each list */
+typedef enum {
+  /* streams that have pending writes */
+  WRITABLE = 0,
+  /* streams that want to send window updates */
+  WINDOW_UPDATE,
+  /* streams that are waiting to start because there are too many concurrent
+     streams on the connection */
+  WAITING_FOR_CONCURRENCY,
+  /* streams that want to callback the application */
+  PENDING_CALLBACKS,
+  /* streams that *ARE* calling back to the application */
+  EXECUTING_CALLBACKS,
+  STREAM_LIST_COUNT /* must be last */
+} stream_list_id;
+
+/* deframer state for the overall http2 stream of bytes */
+typedef enum {
+  /* prefix: one entry per http2 connection prefix byte */
+  DTS_CLIENT_PREFIX_0 = 0,
+  DTS_CLIENT_PREFIX_1,
+  DTS_CLIENT_PREFIX_2,
+  DTS_CLIENT_PREFIX_3,
+  DTS_CLIENT_PREFIX_4,
+  DTS_CLIENT_PREFIX_5,
+  DTS_CLIENT_PREFIX_6,
+  DTS_CLIENT_PREFIX_7,
+  DTS_CLIENT_PREFIX_8,
+  DTS_CLIENT_PREFIX_9,
+  DTS_CLIENT_PREFIX_10,
+  DTS_CLIENT_PREFIX_11,
+  DTS_CLIENT_PREFIX_12,
+  DTS_CLIENT_PREFIX_13,
+  DTS_CLIENT_PREFIX_14,
+  DTS_CLIENT_PREFIX_15,
+  DTS_CLIENT_PREFIX_16,
+  DTS_CLIENT_PREFIX_17,
+  DTS_CLIENT_PREFIX_18,
+  DTS_CLIENT_PREFIX_19,
+  DTS_CLIENT_PREFIX_20,
+  DTS_CLIENT_PREFIX_21,
+  DTS_CLIENT_PREFIX_22,
+  DTS_CLIENT_PREFIX_23,
+  /* frame header byte 0... */
+  /* must follow from the prefix states */
+  DTS_FH_0,
+  DTS_FH_1,
+  DTS_FH_2,
+  DTS_FH_3,
+  DTS_FH_4,
+  DTS_FH_5,
+  DTS_FH_6,
+  DTS_FH_7,
+  /* ... frame header byte 8 */
+  DTS_FH_8,
+  /* inside a http2 frame */
+  DTS_FRAME
+} deframe_transport_state;
+
+typedef struct {
+  stream *head;
+  stream *tail;
+} stream_list;
+
+typedef struct {
+  stream *next;
+  stream *prev;
+} stream_link;
+
+typedef enum {
+  ERROR_STATE_NONE,
+  ERROR_STATE_SEEN,
+  ERROR_STATE_NOTIFIED
+} error_state;
+
+/* We keep several sets of connection wide parameters */
+typedef enum {
+  /* The settings our peer has asked for (and we have acked) */
+  PEER_SETTINGS = 0,
+  /* The settings we'd like to have */
+  LOCAL_SETTINGS,
+  /* The settings we've published to our peer */
+  SENT_SETTINGS,
+  /* The settings the peer has acked */
+  ACKED_SETTINGS,
+  NUM_SETTING_SETS
+} setting_set;
+
+/* Outstanding ping request data */
+typedef struct {
+  gpr_uint8 id[8];
+  void (*cb)(void *user_data);
+  void *user_data;
+} outstanding_ping;
+
+struct transport {
+  grpc_transport base; /* must be first */
+  const grpc_transport_callbacks *cb;
+  void *cb_user_data;
+  grpc_endpoint *ep;
+  grpc_mdctx *metadata_context;
+  gpr_refcount refs;
+  gpr_uint8 is_client;
+
+  gpr_mu mu;
+  gpr_cv cv;
+
+  /* basic state management - what are we doing at the moment? */
+  gpr_uint8 reading;
+  gpr_uint8 writing;
+  gpr_uint8 calling_back;
+  error_state error_state;
+
+  /* stream indexing */
+  gpr_uint32 next_stream_id;
+
+  /* settings */
+  gpr_uint32 settings[NUM_SETTING_SETS][GRPC_CHTTP2_NUM_SETTINGS];
+  gpr_uint8 sent_local_settings;
+  gpr_uint8 dirtied_local_settings;
+
+  /* window management */
+  gpr_uint32 outgoing_window;
+  gpr_uint32 incoming_window;
+
+  /* deframing */
+  deframe_transport_state deframe_state;
+  gpr_uint8 incoming_frame_type;
+  gpr_uint8 incoming_frame_flags;
+  gpr_uint8 header_eof;
+  gpr_uint32 expect_continuation_stream_id;
+  gpr_uint32 incoming_frame_size;
+  gpr_uint32 incoming_stream_id;
+
+  /* hpack encoding */
+  grpc_chttp2_hpack_compressor hpack_compressor;
+
+  /* various parsers */
+  grpc_chttp2_hpack_parser hpack_parser;
+  /* simple one shot parsers */
+  union {
+    grpc_chttp2_window_update_parser window_update;
+    grpc_chttp2_settings_parser settings;
+    grpc_chttp2_ping_parser ping;
+  } simple_parsers;
+
+  /* state for a stream that's not yet been created */
+  grpc_stream_op_buffer new_stream_sopb;
+
+  /* active parser */
+  void *parser_data;
+  stream *incoming_stream;
+  grpc_chttp2_parse_error (*parser)(void *parser_user_data,
+                                    grpc_chttp2_parse_state *state,
+                                    gpr_slice slice, int is_last);
+
+  gpr_slice_buffer outbuf;
+  gpr_slice_buffer qbuf;
+
+  stream_list lists[STREAM_LIST_COUNT];
+  grpc_chttp2_stream_map stream_map;
+
+  /* metadata object cache */
+  grpc_mdstr *str_grpc_timeout;
+
+  /* pings */
+  outstanding_ping *pings;
+  size_t ping_count;
+  size_t ping_capacity;
+  gpr_int64 ping_counter;
+};
+
+struct stream {
+  gpr_uint32 id;
+
+  gpr_uint32 outgoing_window;
+  gpr_uint32 incoming_window;
+  gpr_uint8 write_closed;
+  gpr_uint8 read_closed;
+  gpr_uint8 cancelled;
+  gpr_uint8 allow_window_updates;
+  gpr_uint8 published_close;
+
+  stream_link links[STREAM_LIST_COUNT];
+  gpr_uint8 included[STREAM_LIST_COUNT];
+
+  grpc_stream_op_buffer outgoing_sopb;
+
+  grpc_chttp2_data_parser parser;
+
+  grpc_stream_state callback_state;
+  grpc_stream_op_buffer callback_sopb;
+};
+
+static const grpc_transport_vtable vtable;
+
+static void push_setting(transport *t, grpc_chttp2_setting_id id,
+                         gpr_uint32 value);
+
+static int prepare_callbacks(transport *t);
+static void run_callbacks(transport *t);
+
+static int prepare_write(transport *t);
+static void finish_write(void *t, grpc_endpoint_cb_status status);
+
+static void lock(transport *t);
+static void unlock(transport *t);
+
+static void drop_connection(transport *t);
+static void end_all_the_calls(transport *t);
+
+static stream *stream_list_remove_head(transport *t, stream_list_id id);
+static void stream_list_remove(transport *t, stream *s, stream_list_id id);
+static void stream_list_add_tail(transport *t, stream *s, stream_list_id id);
+static void stream_list_join(transport *t, stream *s, stream_list_id id);
+
+static void cancel_stream_id(transport *t, gpr_uint32 id,
+                             grpc_status_code local_status,
+                             grpc_chttp2_error_code error_code, int send_rst);
+static void cancel_stream(transport *t, stream *s,
+                          grpc_status_code local_status,
+                          grpc_chttp2_error_code error_code, int send_rst);
+static stream *lookup_stream(transport *t, gpr_uint32 id);
+static void remove_from_stream_map(transport *t, stream *s);
+static void maybe_start_some_streams(transport *t);
+
+static void become_skip_parser(transport *t);
+
+/*
+ * CONSTRUCTION/DESTRUCTION/REFCOUNTING
+ */
+
+static void unref_transport(transport *t) {
+  size_t i;
+
+  if (!gpr_unref(&t->refs)) return;
+
+  gpr_mu_lock(&t->mu);
+
+  GPR_ASSERT(t->ep == NULL);
+
+  gpr_slice_buffer_destroy(&t->outbuf);
+  gpr_slice_buffer_destroy(&t->qbuf);
+  grpc_chttp2_hpack_parser_destroy(&t->hpack_parser);
+  grpc_chttp2_hpack_compressor_destroy(&t->hpack_compressor);
+
+  grpc_mdstr_unref(t->str_grpc_timeout);
+
+  for (i = 0; i < STREAM_LIST_COUNT; i++) {
+    GPR_ASSERT(t->lists[i].head == NULL);
+    GPR_ASSERT(t->lists[i].tail == NULL);
+  }
+
+  GPR_ASSERT(grpc_chttp2_stream_map_size(&t->stream_map) == 0);
+
+  grpc_chttp2_stream_map_destroy(&t->stream_map);
+
+  gpr_mu_unlock(&t->mu);
+  gpr_mu_destroy(&t->mu);
+
+  /* callback remaining pings: they're not allowed to call into the transpot,
+     and maybe they hold resources that need to be freed */
+  for (i = 0; i < t->ping_count; i++) {
+    t->pings[i].cb(t->pings[i].user_data);
+  }
+  gpr_free(t->pings);
+
+  gpr_free(t);
+}
+
+static void ref_transport(transport *t) { gpr_ref(&t->refs); }
+
+static void init_transport(transport *t, grpc_transport_setup_callback setup,
+                           void *arg, const grpc_channel_args *channel_args,
+                           grpc_endpoint *ep, grpc_mdctx *mdctx,
+                           int is_client) {
+  size_t i;
+  int j;
+  grpc_transport_setup_result sr;
+
+  GPR_ASSERT(strlen(CLIENT_CONNECT_STRING) == CLIENT_CONNECT_STRLEN);
+
+  t->base.vtable = &vtable;
+  t->ep = ep;
+  /* one ref is for destroy, the other for when ep becomes NULL */
+  gpr_ref_init(&t->refs, 2);
+  gpr_mu_init(&t->mu);
+  gpr_cv_init(&t->cv);
+  t->metadata_context = mdctx;
+  t->str_grpc_timeout =
+      grpc_mdstr_from_string(t->metadata_context, "grpc-timeout");
+  t->reading = 1;
+  t->writing = 0;
+  t->error_state = ERROR_STATE_NONE;
+  t->next_stream_id = is_client ? 1 : 2;
+  t->is_client = is_client;
+  t->outgoing_window = DEFAULT_WINDOW;
+  t->incoming_window = DEFAULT_WINDOW;
+  t->deframe_state = is_client ? DTS_FH_0 : DTS_CLIENT_PREFIX_0;
+  t->expect_continuation_stream_id = 0;
+  t->pings = NULL;
+  t->ping_count = 0;
+  t->ping_capacity = 0;
+  t->ping_counter = gpr_now().tv_nsec;
+  grpc_chttp2_hpack_compressor_init(&t->hpack_compressor, mdctx);
+  gpr_slice_buffer_init(&t->outbuf);
+  gpr_slice_buffer_init(&t->qbuf);
+  if (is_client) {
+    gpr_slice_buffer_add(&t->qbuf,
+                         gpr_slice_from_copied_string(CLIENT_CONNECT_STRING));
+  }
+  /* 8 is a random stab in the dark as to a good initial size: it's small enough
+     that it shouldn't waste memory for infrequently used connections, yet
+     large enough that the exponential growth should happen nicely when it's
+     needed.
+     TODO(ctiller): tune this */
+  grpc_chttp2_stream_map_init(&t->stream_map, 8);
+  memset(&t->lists, 0, sizeof(t->lists));
+
+  /* copy in initial settings to all setting sets */
+  for (i = 0; i < NUM_SETTING_SETS; i++) {
+    for (j = 0; j < GRPC_CHTTP2_NUM_SETTINGS; j++) {
+      t->settings[i][j] = grpc_chttp2_settings_parameters[j].default_value;
+    }
+  }
+  t->dirtied_local_settings = 1;
+  t->sent_local_settings = 0;
+
+  /* configure http2 the way we like it */
+  if (t->is_client) {
+    push_setting(t, GRPC_CHTTP2_SETTINGS_ENABLE_PUSH, 0);
+    push_setting(t, GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 0);
+  }
+
+  if (channel_args) {
+    for (i = 0; i < channel_args->num_args; i++) {
+      if (0 ==
+          strcmp(channel_args->args[i].key, GRPC_ARG_MAX_CONCURRENT_STREAMS)) {
+        if (t->is_client) {
+          gpr_log(GPR_ERROR, "%s: is ignored on the client",
+                  GRPC_ARG_MAX_CONCURRENT_STREAMS);
+        } else if (channel_args->args[i].type != GRPC_ARG_INTEGER) {
+          gpr_log(GPR_ERROR, "%s: must be an integer",
+                  GRPC_ARG_MAX_CONCURRENT_STREAMS);
+        } else {
+          push_setting(t, GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
+                       channel_args->args[i].value.integer);
+        }
+      }
+    }
+  }
+
+  gpr_mu_lock(&t->mu);
+  t->calling_back = 1;
+  ref_transport(t);
+  gpr_mu_unlock(&t->mu);
+
+  sr = setup(arg, &t->base, t->metadata_context);
+
+  lock(t);
+  t->cb = sr.callbacks;
+  t->cb_user_data = sr.user_data;
+  grpc_chttp2_hpack_parser_init(&t->hpack_parser, t->metadata_context);
+  t->calling_back = 0;
+  gpr_cv_broadcast(&t->cv);
+  unlock(t);
+  unref_transport(t);
+}
+
+static void destroy_transport(grpc_transport *gt) {
+  transport *t = (transport *)gt;
+
+  gpr_mu_lock(&t->mu);
+  while (t->calling_back) {
+    gpr_cv_wait(&t->cv, &t->mu, gpr_inf_future);
+  }
+  t->cb = NULL;
+  gpr_mu_unlock(&t->mu);
+
+  unref_transport(t);
+}
+
+static void close_transport(grpc_transport *gt) {
+  transport *t = (transport *)gt;
+  gpr_mu_lock(&t->mu);
+  if (t->ep) {
+    grpc_endpoint_shutdown(t->ep);
+  }
+  gpr_mu_unlock(&t->mu);
+}
+
+static int init_stream(grpc_transport *gt, grpc_stream *gs,
+                       const void *server_data) {
+  transport *t = (transport *)gt;
+  stream *s = (stream *)gs;
+
+  ref_transport(t);
+
+  if (!server_data) {
+    lock(t);
+    s->id = 0;
+  } else {
+    s->id = (gpr_uint32)(gpr_uintptr)server_data;
+    t->incoming_stream = s;
+    grpc_chttp2_stream_map_add(&t->stream_map, s->id, s);
+  }
+
+  s->outgoing_window = DEFAULT_WINDOW;
+  s->incoming_window = DEFAULT_WINDOW;
+  s->write_closed = 0;
+  s->read_closed = 0;
+  s->cancelled = 0;
+  s->allow_window_updates = 0;
+  s->published_close = 0;
+  memset(&s->links, 0, sizeof(s->links));
+  memset(&s->included, 0, sizeof(s->included));
+  grpc_sopb_init(&s->outgoing_sopb);
+  grpc_chttp2_data_parser_init(&s->parser);
+  grpc_sopb_init(&s->callback_sopb);
+
+  if (!server_data) {
+    unlock(t);
+  }
+
+  return 0;
+}
+
+static void destroy_stream(grpc_transport *gt, grpc_stream *gs) {
+  transport *t = (transport *)gt;
+  stream *s = (stream *)gs;
+  size_t i;
+
+  gpr_mu_lock(&t->mu);
+
+  /* await pending callbacks
+     TODO(ctiller): this could be optimized to check if this stream is getting
+     callbacks */
+  while (t->calling_back) {
+    gpr_cv_wait(&t->cv, &t->mu, gpr_inf_future);
+  }
+
+  /* stop parsing if we're currently parsing this stream */
+  if (t->deframe_state == DTS_FRAME && t->incoming_stream_id == s->id &&
+      s->id != 0) {
+    become_skip_parser(t);
+  }
+
+  for (i = 0; i < STREAM_LIST_COUNT; i++) {
+    stream_list_remove(t, s, i);
+  }
+  remove_from_stream_map(t, s);
+
+  gpr_cv_broadcast(&t->cv);
+  gpr_mu_unlock(&t->mu);
+
+  grpc_sopb_destroy(&s->outgoing_sopb);
+  grpc_chttp2_data_parser_destroy(&s->parser);
+  grpc_sopb_destroy(&s->callback_sopb);
+
+  unref_transport(t);
+}
+
+/*
+ * LIST MANAGEMENT
+ */
+
+static stream *stream_list_remove_head(transport *t, stream_list_id id) {
+  stream *s = t->lists[id].head;
+  if (s) {
+    stream *new_head = s->links[id].next;
+    GPR_ASSERT(s->included[id]);
+    if (new_head) {
+      t->lists[id].head = new_head;
+      new_head->links[id].prev = NULL;
+    } else {
+      t->lists[id].head = NULL;
+      t->lists[id].tail = NULL;
+    }
+    s->included[id] = 0;
+  }
+  return s;
+}
+
+static void stream_list_remove(transport *t, stream *s, stream_list_id id) {
+  if (!s->included[id]) return;
+  s->included[id] = 0;
+  if (s->links[id].prev) {
+    s->links[id].prev->links[id].next = s->links[id].next;
+  } else {
+    GPR_ASSERT(t->lists[id].head == s);
+    t->lists[id].head = s->links[id].next;
+  }
+  if (s->links[id].next) {
+    s->links[id].next->links[id].prev = s->links[id].prev;
+  } else {
+    t->lists[id].tail = s->links[id].prev;
+  }
+}
+
+static void stream_list_add_tail(transport *t, stream *s, stream_list_id id) {
+  stream *old_tail;
+  GPR_ASSERT(!s->included[id]);
+  old_tail = t->lists[id].tail;
+  s->links[id].next = NULL;
+  s->links[id].prev = old_tail;
+  if (old_tail) {
+    old_tail->links[id].next = s;
+  } else {
+    s->links[id].prev = NULL;
+    t->lists[id].head = s;
+  }
+  t->lists[id].tail = s;
+  s->included[id] = 1;
+}
+
+static void stream_list_join(transport *t, stream *s, stream_list_id id) {
+  if (s->included[id]) {
+    return;
+  }
+  stream_list_add_tail(t, s, id);
+}
+
+static void remove_from_stream_map(transport *t, stream *s) {
+  if (s->id == 0) return;
+  if (grpc_chttp2_stream_map_delete(&t->stream_map, s->id)) {
+    maybe_start_some_streams(t);
+  }
+}
+
+/*
+ * LOCK MANAGEMENT
+ */
+
+/* We take a transport-global lock in response to calls coming in from above,
+   and in response to data being received from below. New data to be written
+   is always queued, as are callbacks to process data. During unlock() we
+   check our todo lists and initiate callbacks and flush writes. */
+
+static void lock(transport *t) { gpr_mu_lock(&t->mu); }
+
+static void unlock(transport *t) {
+  int start_write = 0;
+  int perform_callbacks = 0;
+  int call_closed = 0;
+  grpc_endpoint *ep = t->ep;
+
+  /* see if we need to trigger a write - and if so, get the data ready */
+  if (ep && !t->writing) {
+    t->writing = start_write = prepare_write(t);
+    if (start_write) {
+      ref_transport(t);
+    }
+  }
+
+  /* gather any callbacks that need to be made */
+  if (!t->calling_back && t->cb) {
+    perform_callbacks = prepare_callbacks(t);
+    if (perform_callbacks) {
+      t->calling_back = 1;
+    }
+    if (t->error_state == ERROR_STATE_SEEN) {
+      call_closed = 1;
+      t->calling_back = 1;
+      t->error_state = ERROR_STATE_NOTIFIED;
+    }
+  }
+
+  if (perform_callbacks || call_closed) {
+    ref_transport(t);
+  }
+
+  /* finally unlock */
+  gpr_mu_unlock(&t->mu);
+
+  /* perform some callbacks if necessary */
+  if (perform_callbacks) {
+    run_callbacks(t);
+  }
+
+  if (call_closed) {
+    t->cb->closed(t->cb_user_data, &t->base);
+  }
+
+  /* write some bytes if necessary */
+  while (start_write) {
+    switch (grpc_endpoint_write(ep, t->outbuf.slices, t->outbuf.count,
+                                finish_write, t, gpr_inf_future)) {
+      case GRPC_ENDPOINT_WRITE_DONE:
+        /* grab the lock directly without wrappers since we just want to
+           continue writes if we loop: no need to check read callbacks again */
+        gpr_mu_lock(&t->mu);
+        t->outbuf.count = 0;
+        t->outbuf.length = 0;
+        t->writing = start_write = prepare_write(t);
+        if (!start_write) {
+          if (!t->reading) {
+            grpc_endpoint_destroy(t->ep);
+            t->ep = NULL;
+            gpr_cv_broadcast(&t->cv);
+            /* endpoint ref: safe because we'll still have the ref for write */
+            unref_transport(t);
+          }
+        }
+        gpr_mu_unlock(&t->mu);
+        if (!start_write) {
+          unref_transport(t);
+        }
+        break;
+      case GRPC_ENDPOINT_WRITE_ERROR:
+        start_write = 0;
+        /* use the wrapper lock/unlock here as we drop_connection, causing
+           read callbacks to be queued (which will be cleared during unlock) */
+        lock(t);
+        t->outbuf.count = 0;
+        t->outbuf.length = 0;
+        t->writing = 0;
+        drop_connection(t);
+        if (!t->reading) {
+          grpc_endpoint_destroy(t->ep);
+          t->ep = NULL;
+          gpr_cv_broadcast(&t->cv);
+          /* endpoint ref: safe because we'll still have the ref for write */
+          unref_transport(t);
+        }
+        unlock(t);
+        unref_transport(t);
+        break;
+      case GRPC_ENDPOINT_WRITE_PENDING:
+        start_write = 0;
+        break;
+    }
+  }
+
+  if (perform_callbacks || call_closed) {
+    lock(t);
+    t->calling_back = 0;
+    gpr_cv_broadcast(&t->cv);
+    unlock(t);
+    unref_transport(t);
+  }
+}
+
+/*
+ * OUTPUT PROCESSING
+ */
+
+static void push_setting(transport *t, grpc_chttp2_setting_id id,
+                         gpr_uint32 value) {
+  const grpc_chttp2_setting_parameters *sp =
+      &grpc_chttp2_settings_parameters[id];
+  gpr_uint32 use_value = GPR_CLAMP(value, sp->min_value, sp->max_value);
+  if (use_value != value) {
+    gpr_log(GPR_INFO, "Requested parameter %s clamped from %d to %d", sp->name,
+            value, use_value);
+  }
+  if (use_value != t->settings[LOCAL_SETTINGS][id]) {
+    t->settings[LOCAL_SETTINGS][id] = use_value;
+    t->dirtied_local_settings = 1;
+  }
+}
+
+static void finish_write(void *tp, grpc_endpoint_cb_status error) {
+  transport *t = tp;
+
+  lock(t);
+  if (error != GRPC_ENDPOINT_CB_OK) {
+    drop_connection(t);
+  }
+  t->outbuf.count = 0;
+  t->outbuf.length = 0;
+  /* leave the writing flag up on shutdown to prevent further writes in unlock()
+     from starting */
+  t->writing = 0;
+  if (!t->reading) {
+    grpc_endpoint_destroy(t->ep);
+    t->ep = NULL;
+    gpr_cv_broadcast(&t->cv);
+    unref_transport(t); /* safe because we'll still have the ref for write */
+  }
+  unlock(t);
+
+  unref_transport(t);
+}
+
+static int prepare_write(transport *t) {
+  stream *s;
+  gpr_slice_buffer tempbuf;
+
+  /* simple writes are queued to qbuf, and flushed here */
+  tempbuf = t->qbuf;
+  t->qbuf = t->outbuf;
+  t->outbuf = tempbuf;
+  GPR_ASSERT(t->qbuf.count == 0);
+
+  if (t->dirtied_local_settings && !t->sent_local_settings) {
+    gpr_slice_buffer_add(
+        &t->outbuf, grpc_chttp2_settings_create(t->settings[SENT_SETTINGS],
+                                                t->settings[LOCAL_SETTINGS],
+                                                GRPC_CHTTP2_NUM_SETTINGS));
+    t->dirtied_local_settings = 0;
+    t->sent_local_settings = 1;
+  }
+
+  /* for each stream that's become writable, frame it's data (according to
+     available window sizes) and add to the output buffer */
+  while (t->outgoing_window && (s = stream_list_remove_head(t, WRITABLE))) {
+    gpr_uint32 written = grpc_chttp2_encode_some(
+        s->outgoing_sopb.ops, &s->outgoing_sopb.nops, s->write_closed,
+        &t->outbuf, GPR_MIN(t->outgoing_window, s->outgoing_window), s->id,
+        &t->hpack_compressor);
+    t->outgoing_window -= written;
+    s->outgoing_window -= written;
+
+    /* if there are no more writes to do and writes are closed, we need to
+       queue a callback to let the application know */
+    if (s->write_closed && s->outgoing_sopb.nops == 0) {
+      stream_list_join(t, s, PENDING_CALLBACKS);
+    }
+
+    /* if there are still writes to do and the stream still has window
+       available, then schedule a further write */
+    if (s->outgoing_sopb.nops && s->outgoing_window) {
+      GPR_ASSERT(!t->outgoing_window);
+      stream_list_add_tail(t, s, WRITABLE);
+    }
+  }
+
+  /* for each stream that wants to update its window, add that window here */
+  while ((s = stream_list_remove_head(t, WINDOW_UPDATE))) {
+    gpr_uint32 window_add = DEFAULT_WINDOW - s->incoming_window;
+    if (!s->read_closed && window_add) {
+      gpr_slice_buffer_add(&t->outbuf,
+                           grpc_chttp2_window_update_create(s->id, window_add));
+      s->incoming_window += window_add;
+    }
+  }
+
+  /* if the transport is ready to send a window update, do so here also */
+  if (t->incoming_window < DEFAULT_WINDOW / 2) {
+    gpr_uint32 window_add = DEFAULT_WINDOW - t->incoming_window;
+    gpr_slice_buffer_add(&t->outbuf,
+                         grpc_chttp2_window_update_create(0, window_add));
+    t->incoming_window += window_add;
+  }
+
+  return t->outbuf.length > 0;
+}
+
+static void maybe_start_some_streams(transport *t) {
+  while (
+      grpc_chttp2_stream_map_size(&t->stream_map) <
+      t->settings[PEER_SETTINGS][GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS]) {
+    stream *s = stream_list_remove_head(t, WAITING_FOR_CONCURRENCY);
+    if (!s) break;
+
+    GPR_ASSERT(s->id == 0);
+    s->id = t->next_stream_id;
+    t->next_stream_id += 2;
+    grpc_chttp2_stream_map_add(&t->stream_map, s->id, s);
+    stream_list_join(t, s, WRITABLE);
+  }
+}
+
+static void send_batch(grpc_transport *gt, grpc_stream *gs, grpc_stream_op *ops,
+                       size_t ops_count, int is_last) {
+  transport *t = (transport *)gt;
+  stream *s = (stream *)gs;
+
+  lock(t);
+
+  if (is_last) {
+    s->write_closed = 1;
+  }
+  if (!s->cancelled) {
+    grpc_sopb_append(&s->outgoing_sopb, ops, ops_count);
+    if (is_last && s->outgoing_sopb.nops == 0) {
+      if (s->id != 0) {
+        gpr_slice_buffer_add(&t->qbuf,
+                             grpc_chttp2_data_frame_create_empty_close(s->id));
+      }
+    } else if (s->id == 0) {
+      stream_list_join(t, s, WAITING_FOR_CONCURRENCY);
+      maybe_start_some_streams(t);
+    } else if (s->outgoing_window) {
+      stream_list_join(t, s, WRITABLE);
+    }
+  } else {
+    grpc_stream_ops_unref_owned_objects(ops, ops_count);
+  }
+  if (is_last && s->outgoing_sopb.nops == 0 && s->read_closed) {
+    stream_list_join(t, s, PENDING_CALLBACKS);
+  }
+
+  unlock(t);
+}
+
+static void abort_stream(grpc_transport *gt, grpc_stream *gs,
+                         grpc_status_code status) {
+  transport *t = (transport *)gt;
+  stream *s = (stream *)gs;
+
+  lock(t);
+  cancel_stream(t, s, status, grpc_chttp2_grpc_status_to_http2_error(status),
+                1);
+  unlock(t);
+}
+
+static void send_ping(grpc_transport *gt, void (*cb)(void *user_data),
+                      void *user_data) {
+  transport *t = (transport *)gt;
+  outstanding_ping *p;
+
+  lock(t);
+  if (t->ping_capacity == t->ping_count) {
+    t->ping_capacity = GPR_MAX(1, t->ping_capacity * 3 / 2);
+    t->pings =
+        gpr_realloc(t->pings, sizeof(outstanding_ping) * t->ping_capacity);
+  }
+  p = &t->pings[t->ping_count++];
+  p->id[0] = t->ping_counter >> 56;
+  p->id[1] = t->ping_counter >> 48;
+  p->id[2] = t->ping_counter >> 40;
+  p->id[3] = t->ping_counter >> 32;
+  p->id[4] = t->ping_counter >> 24;
+  p->id[5] = t->ping_counter >> 16;
+  p->id[6] = t->ping_counter >> 8;
+  p->id[7] = t->ping_counter;
+  p->cb = cb;
+  p->user_data = user_data;
+  gpr_slice_buffer_add(&t->qbuf, grpc_chttp2_ping_create(0, p->id));
+  unlock(t);
+}
+
+/*
+ * INPUT PROCESSING
+ */
+
+static void cancel_stream_inner(transport *t, stream *s, gpr_uint32 id,
+                                grpc_status_code local_status,
+                                grpc_chttp2_error_code error_code,
+                                int send_rst) {
+  char buffer[32];
+  int had_outgoing;
+
+  if (s) {
+    /* clear out any unreported input & output: nobody cares anymore */
+    grpc_sopb_reset(&s->parser.incoming_sopb);
+    had_outgoing = s->outgoing_sopb.nops != 0;
+    grpc_sopb_reset(&s->outgoing_sopb);
+    if (s->cancelled) {
+      send_rst = 0;
+    } else if (!s->read_closed || !s->write_closed || had_outgoing) {
+      s->cancelled = 1;
+      s->read_closed = 1;
+      s->write_closed = 1;
+
+      sprintf(buffer, "%d", local_status);
+      grpc_sopb_add_metadata(
+          &s->parser.incoming_sopb,
+          grpc_mdelem_from_strings(t->metadata_context, "grpc-status", buffer));
+
+      stream_list_join(t, s, PENDING_CALLBACKS);
+    }
+  }
+  if (!id) send_rst = 0;
+  if (send_rst) {
+    gpr_slice_buffer_add(&t->qbuf,
+                         grpc_chttp2_rst_stream_create(id, error_code));
+  }
+}
+
+static void cancel_stream_id(transport *t, gpr_uint32 id,
+                             grpc_status_code local_status,
+                             grpc_chttp2_error_code error_code, int send_rst) {
+  cancel_stream_inner(t, lookup_stream(t, id), id, local_status, error_code,
+                      send_rst);
+}
+
+static void cancel_stream(transport *t, stream *s,
+                          grpc_status_code local_status,
+                          grpc_chttp2_error_code error_code, int send_rst) {
+  cancel_stream_inner(t, s, s->id, local_status, error_code, send_rst);
+}
+
+static void cancel_stream_cb(void *user_data, gpr_uint32 id, void *stream) {
+  cancel_stream(user_data, stream, GRPC_STATUS_UNAVAILABLE,
+                GRPC_CHTTP2_INTERNAL_ERROR, 0);
+}
+
+static void end_all_the_calls(transport *t) {
+  grpc_chttp2_stream_map_for_each(&t->stream_map, cancel_stream_cb, t);
+}
+
+static void drop_connection(transport *t) {
+  if (t->error_state == ERROR_STATE_NONE) {
+    t->error_state = ERROR_STATE_SEEN;
+  }
+  end_all_the_calls(t);
+}
+
+static void maybe_join_window_updates(transport *t, stream *s) {
+  if (s->allow_window_updates && s->incoming_window < DEFAULT_WINDOW / 2) {
+    stream_list_join(t, s, WINDOW_UPDATE);
+  }
+}
+
+static void set_allow_window_updates(grpc_transport *tp, grpc_stream *sp,
+                                     int allow) {
+  transport *t = (transport *)tp;
+  stream *s = (stream *)sp;
+
+  lock(t);
+  s->allow_window_updates = allow;
+  if (allow) {
+    maybe_join_window_updates(t, s);
+  } else {
+    stream_list_remove(t, s, WINDOW_UPDATE);
+  }
+  unlock(t);
+}
+
+static grpc_chttp2_parse_error update_incoming_window(transport *t, stream *s) {
+  if (t->incoming_frame_size > t->incoming_window) {
+    gpr_log(GPR_ERROR, "frame of size %d overflows incoming window of %d",
+            t->incoming_frame_size, t->incoming_window);
+    return GRPC_CHTTP2_CONNECTION_ERROR;
+  }
+
+  if (t->incoming_frame_size > s->incoming_window) {
+    gpr_log(GPR_ERROR, "frame of size %d overflows incoming window of %d",
+            t->incoming_frame_size, s->incoming_window);
+    return GRPC_CHTTP2_CONNECTION_ERROR;
+  }
+
+  t->incoming_window -= t->incoming_frame_size;
+  s->incoming_window -= t->incoming_frame_size;
+
+  /* if the stream incoming window is getting low, schedule an update */
+  maybe_join_window_updates(t, s);
+
+  return GRPC_CHTTP2_PARSE_OK;
+}
+
+static stream *lookup_stream(transport *t, gpr_uint32 id) {
+  return grpc_chttp2_stream_map_find(&t->stream_map, id);
+}
+
+static grpc_chttp2_parse_error skip_parser(void *parser,
+                                           grpc_chttp2_parse_state *st,
+                                           gpr_slice slice, int is_last) {
+  return GRPC_CHTTP2_PARSE_OK;
+}
+
+static void skip_header(void *tp, grpc_mdelem *md) { grpc_mdelem_unref(md); }
+
+static int init_skip_frame(transport *t, int is_header) {
+  if (is_header) {
+    int is_eoh = t->expect_continuation_stream_id != 0;
+    t->parser = grpc_chttp2_header_parser_parse;
+    t->parser_data = &t->hpack_parser;
+    t->hpack_parser.on_header = skip_header;
+    t->hpack_parser.on_header_user_data = NULL;
+    t->hpack_parser.is_boundary = is_eoh;
+    t->hpack_parser.is_eof = is_eoh ? t->header_eof : 0;
+  } else {
+    t->parser = skip_parser;
+  }
+  return 1;
+}
+
+static void become_skip_parser(transport *t) {
+  init_skip_frame(t, t->parser == grpc_chttp2_header_parser_parse);
+}
+
+static int init_data_frame_parser(transport *t) {
+  stream *s = lookup_stream(t, t->incoming_stream_id);
+  grpc_chttp2_parse_error err = GRPC_CHTTP2_PARSE_OK;
+  if (!s || s->read_closed) return init_skip_frame(t, 0);
+  if (err == GRPC_CHTTP2_PARSE_OK) {
+    err = update_incoming_window(t, s);
+  }
+  if (err == GRPC_CHTTP2_PARSE_OK) {
+    err = grpc_chttp2_data_parser_begin_frame(&s->parser,
+                                              t->incoming_frame_flags);
+  }
+  switch (err) {
+    case GRPC_CHTTP2_PARSE_OK:
+      t->incoming_stream = s;
+      t->parser = grpc_chttp2_data_parser_parse;
+      t->parser_data = &s->parser;
+      return 1;
+    case GRPC_CHTTP2_STREAM_ERROR:
+      cancel_stream(t, s, grpc_chttp2_http2_error_to_grpc_status(
+                              GRPC_CHTTP2_INTERNAL_ERROR),
+                    GRPC_CHTTP2_INTERNAL_ERROR, 1);
+      return init_skip_frame(t, 0);
+    case GRPC_CHTTP2_CONNECTION_ERROR:
+      drop_connection(t);
+      return 0;
+  }
+  gpr_log(GPR_ERROR, "should never reach here");
+  abort();
+  return 0;
+}
+
+static void free_timeout(void *p) { gpr_free(p); }
+
+static void on_header(void *tp, grpc_mdelem *md) {
+  transport *t = tp;
+  stream *s = t->incoming_stream;
+
+  GPR_ASSERT(s);
+  stream_list_join(t, s, PENDING_CALLBACKS);
+  if (md->key == t->str_grpc_timeout) {
+    gpr_timespec *cached_timeout = grpc_mdelem_get_user_data(md, free_timeout);
+    if (!cached_timeout) {
+      /* not already parsed: parse it now, and store the result away */
+      cached_timeout = gpr_malloc(sizeof(gpr_timespec));
+      if (!grpc_chttp2_decode_timeout(grpc_mdstr_as_c_string(md->value),
+                                      cached_timeout)) {
+        gpr_log(GPR_ERROR, "Ignoring bad timeout value '%s'",
+                grpc_mdstr_as_c_string(md->value));
+        *cached_timeout = gpr_inf_future;
+      }
+      grpc_mdelem_set_user_data(md, free_timeout, cached_timeout);
+    }
+    grpc_sopb_add_deadline(&s->parser.incoming_sopb,
+                           gpr_time_add(gpr_now(), *cached_timeout));
+    grpc_mdelem_unref(md);
+  } else {
+    grpc_sopb_add_metadata(&s->parser.incoming_sopb, md);
+  }
+}
+
+static int init_header_frame_parser(transport *t, int is_continuation) {
+  int is_eoh =
+      (t->incoming_frame_flags & GRPC_CHTTP2_DATA_FLAG_END_HEADERS) != 0;
+  stream *s;
+
+  if (is_eoh) {
+    t->expect_continuation_stream_id = 0;
+  } else {
+    t->expect_continuation_stream_id = t->incoming_stream_id;
+  }
+
+  if (!is_continuation) {
+    t->header_eof =
+        (t->incoming_frame_flags & GRPC_CHTTP2_DATA_FLAG_END_STREAM) != 0;
+  }
+
+  /* could be a new stream or an existing stream */
+  s = lookup_stream(t, t->incoming_stream_id);
+  if (!s) {
+    if (is_continuation) {
+      gpr_log(GPR_ERROR, "stream disbanded before CONTINUATION received");
+      return init_skip_frame(t, 1);
+    }
+    if (t->is_client) {
+      if ((t->incoming_stream_id & 1) &&
+          t->incoming_stream_id < t->next_stream_id) {
+        /* this is an old (probably cancelled) stream */
+      } else {
+        gpr_log(GPR_ERROR, "ignoring new stream creation on client");
+      }
+      return init_skip_frame(t, 1);
+    }
+    t->incoming_stream = NULL;
+    /* if stream is accepted, we set incoming_stream in init_stream */
+    t->cb->accept_stream(t->cb_user_data, &t->base,
+                         (void *)(gpr_uintptr)t->incoming_stream_id);
+    s = t->incoming_stream;
+    if (!s) {
+      gpr_log(GPR_ERROR, "stream not accepted");
+      return init_skip_frame(t, 1);
+    }
+  } else {
+    t->incoming_stream = s;
+  }
+  if (t->incoming_stream->read_closed) {
+    gpr_log(GPR_ERROR, "skipping already closed stream header");
+    t->incoming_stream = NULL;
+    return init_skip_frame(t, 1);
+  }
+  t->parser = grpc_chttp2_header_parser_parse;
+  t->parser_data = &t->hpack_parser;
+  t->hpack_parser.on_header = on_header;
+  t->hpack_parser.on_header_user_data = t;
+  t->hpack_parser.is_boundary = is_eoh;
+  t->hpack_parser.is_eof = is_eoh ? t->header_eof : 0;
+  if (!is_continuation &&
+      (t->incoming_frame_flags & GRPC_CHTTP2_FLAG_HAS_PRIORITY)) {
+    grpc_chttp2_hpack_parser_set_has_priority(&t->hpack_parser);
+  }
+  return 1;
+}
+
+static int init_window_update_frame_parser(transport *t) {
+  int ok = GRPC_CHTTP2_PARSE_OK == grpc_chttp2_window_update_parser_begin_frame(
+                                       &t->simple_parsers.window_update,
+                                       t->incoming_frame_size,
+                                       t->incoming_frame_flags);
+  if (!ok) {
+    drop_connection(t);
+  }
+  t->parser = grpc_chttp2_window_update_parser_parse;
+  t->parser_data = &t->simple_parsers.window_update;
+  return ok;
+}
+
+static int init_ping_parser(transport *t) {
+  int ok = GRPC_CHTTP2_PARSE_OK ==
+           grpc_chttp2_ping_parser_begin_frame(&t->simple_parsers.ping,
+                                               t->incoming_frame_size,
+                                               t->incoming_frame_flags);
+  if (!ok) {
+    drop_connection(t);
+  }
+  t->parser = grpc_chttp2_ping_parser_parse;
+  t->parser_data = &t->simple_parsers.ping;
+  return ok;
+}
+
+static int init_settings_frame_parser(transport *t) {
+  int ok = GRPC_CHTTP2_PARSE_OK ==
+           grpc_chttp2_settings_parser_begin_frame(
+               &t->simple_parsers.settings, t->incoming_frame_size,
+               t->incoming_frame_flags, t->settings[PEER_SETTINGS]);
+  if (!ok) {
+    drop_connection(t);
+  }
+  if (t->incoming_frame_flags & GRPC_CHTTP2_FLAG_ACK) {
+    memcpy(t->settings[ACKED_SETTINGS], t->settings[SENT_SETTINGS],
+           GRPC_CHTTP2_NUM_SETTINGS * sizeof(gpr_uint32));
+  }
+  t->parser = grpc_chttp2_settings_parser_parse;
+  t->parser_data = &t->simple_parsers.settings;
+  return ok;
+}
+
+static int init_frame_parser(transport *t) {
+  if (t->expect_continuation_stream_id != 0) {
+    if (t->incoming_frame_type != GRPC_CHTTP2_FRAME_CONTINUATION) {
+      gpr_log(GPR_ERROR, "Expected CONTINUATION frame, got frame type %02x",
+              t->incoming_frame_type);
+      return 0;
+    }
+    if (t->expect_continuation_stream_id != t->incoming_stream_id) {
+      gpr_log(GPR_ERROR,
+              "Expected CONTINUATION frame for stream %08x, got stream %08x",
+              t->expect_continuation_stream_id, t->incoming_stream_id);
+      return 0;
+    }
+    return init_header_frame_parser(t, 1);
+  }
+  switch (t->incoming_frame_type) {
+    case GRPC_CHTTP2_FRAME_DATA:
+      return init_data_frame_parser(t);
+    case GRPC_CHTTP2_FRAME_HEADER:
+      return init_header_frame_parser(t, 0);
+    case GRPC_CHTTP2_FRAME_CONTINUATION:
+      gpr_log(GPR_ERROR, "Unexpected CONTINUATION frame");
+      return 0;
+    case GRPC_CHTTP2_FRAME_RST_STREAM:
+      /* TODO(ctiller): actually parse the reason */
+      cancel_stream_id(
+          t, t->incoming_stream_id,
+          grpc_chttp2_http2_error_to_grpc_status(GRPC_CHTTP2_CANCEL),
+          GRPC_CHTTP2_CANCEL, 0);
+      return init_skip_frame(t, 0);
+    case GRPC_CHTTP2_FRAME_SETTINGS:
+      return init_settings_frame_parser(t);
+    case GRPC_CHTTP2_FRAME_WINDOW_UPDATE:
+      return init_window_update_frame_parser(t);
+    case GRPC_CHTTP2_FRAME_PING:
+      return init_ping_parser(t);
+    default:
+      gpr_log(GPR_ERROR, "Unknown frame type %02x", t->incoming_frame_type);
+      return init_skip_frame(t, 0);
+  }
+}
+
+static int is_window_update_legal(gpr_uint32 window_update, gpr_uint32 window) {
+  return window_update < MAX_WINDOW - window;
+}
+
+static int parse_frame_slice(transport *t, gpr_slice slice, int is_last) {
+  grpc_chttp2_parse_state st;
+  size_t i;
+  memset(&st, 0, sizeof(st));
+  switch (t->parser(t->parser_data, &st, slice, is_last)) {
+    case GRPC_CHTTP2_PARSE_OK:
+      if (st.end_of_stream) {
+        t->incoming_stream->read_closed = 1;
+        stream_list_join(t, t->incoming_stream, PENDING_CALLBACKS);
+      }
+      if (st.need_flush_reads) {
+        stream_list_join(t, t->incoming_stream, PENDING_CALLBACKS);
+      }
+      if (st.metadata_boundary) {
+        grpc_sopb_add_metadata_boundary(
+            &t->incoming_stream->parser.incoming_sopb);
+        stream_list_join(t, t->incoming_stream, PENDING_CALLBACKS);
+      }
+      if (st.ack_settings) {
+        gpr_slice_buffer_add(&t->qbuf, grpc_chttp2_settings_ack_create());
+        maybe_start_some_streams(t);
+      }
+      if (st.send_ping_ack) {
+        gpr_slice_buffer_add(
+            &t->qbuf,
+            grpc_chttp2_ping_create(1, t->simple_parsers.ping.opaque_8bytes));
+      }
+      if (st.process_ping_reply) {
+        for (i = 0; i < t->ping_count; i++) {
+          if (0 ==
+              memcmp(t->pings[i].id, t->simple_parsers.ping.opaque_8bytes, 8)) {
+            t->pings[i].cb(t->pings[i].user_data);
+            memmove(&t->pings[i], &t->pings[i + 1],
+                    (t->ping_count - i - 1) * sizeof(outstanding_ping));
+            t->ping_count--;
+            break;
+          }
+        }
+      }
+      if (st.window_update) {
+        if (t->incoming_stream_id) {
+          /* if there was a stream id, this is for some stream */
+          stream *s = lookup_stream(t, t->incoming_stream_id);
+          if (s) {
+            int was_window_empty = s->outgoing_window == 0;
+            if (!is_window_update_legal(st.window_update, s->outgoing_window)) {
+              cancel_stream(t, s, grpc_chttp2_http2_error_to_grpc_status(
+                                      GRPC_CHTTP2_FLOW_CONTROL_ERROR),
+                            GRPC_CHTTP2_FLOW_CONTROL_ERROR, 1);
+            } else {
+              s->outgoing_window += st.window_update;
+              /* if this window update makes outgoing ops writable again,
+                 flag that */
+              if (was_window_empty && s->outgoing_sopb.nops) {
+                stream_list_join(t, s, WRITABLE);
+              }
+            }
+          }
+        } else {
+          /* transport level window update */
+          if (!is_window_update_legal(st.window_update, t->outgoing_window)) {
+            drop_connection(t);
+          } else {
+            t->outgoing_window += st.window_update;
+          }
+        }
+      }
+      return 1;
+    case GRPC_CHTTP2_STREAM_ERROR:
+      become_skip_parser(t);
+      cancel_stream_id(
+          t, t->incoming_stream_id,
+          grpc_chttp2_http2_error_to_grpc_status(GRPC_CHTTP2_INTERNAL_ERROR),
+          GRPC_CHTTP2_INTERNAL_ERROR, 1);
+      return 1;
+    case GRPC_CHTTP2_CONNECTION_ERROR:
+      drop_connection(t);
+      return 0;
+  }
+  gpr_log(GPR_ERROR, "should never reach here");
+  abort();
+  return 0;
+}
+
+static int process_read(transport *t, gpr_slice slice) {
+  gpr_uint8 *beg = GPR_SLICE_START_PTR(slice);
+  gpr_uint8 *end = GPR_SLICE_END_PTR(slice);
+  gpr_uint8 *cur = beg;
+
+  if (cur == end) return 1;
+
+  switch (t->deframe_state) {
+    case DTS_CLIENT_PREFIX_0:
+    case DTS_CLIENT_PREFIX_1:
+    case DTS_CLIENT_PREFIX_2:
+    case DTS_CLIENT_PREFIX_3:
+    case DTS_CLIENT_PREFIX_4:
+    case DTS_CLIENT_PREFIX_5:
+    case DTS_CLIENT_PREFIX_6:
+    case DTS_CLIENT_PREFIX_7:
+    case DTS_CLIENT_PREFIX_8:
+    case DTS_CLIENT_PREFIX_9:
+    case DTS_CLIENT_PREFIX_10:
+    case DTS_CLIENT_PREFIX_11:
+    case DTS_CLIENT_PREFIX_12:
+    case DTS_CLIENT_PREFIX_13:
+    case DTS_CLIENT_PREFIX_14:
+    case DTS_CLIENT_PREFIX_15:
+    case DTS_CLIENT_PREFIX_16:
+    case DTS_CLIENT_PREFIX_17:
+    case DTS_CLIENT_PREFIX_18:
+    case DTS_CLIENT_PREFIX_19:
+    case DTS_CLIENT_PREFIX_20:
+    case DTS_CLIENT_PREFIX_21:
+    case DTS_CLIENT_PREFIX_22:
+    case DTS_CLIENT_PREFIX_23:
+      while (cur != end && t->deframe_state != DTS_FH_0) {
+        if (*cur != CLIENT_CONNECT_STRING[t->deframe_state]) {
+          gpr_log(GPR_ERROR,
+                  "Connect string mismatch: expected '%c' (%d) got '%c' (%d) "
+                  "at byte %d",
+                  CLIENT_CONNECT_STRING[t->deframe_state],
+                  (int)(gpr_uint8)CLIENT_CONNECT_STRING[t->deframe_state], *cur,
+                  (int)*cur, t->deframe_state);
+          return 0;
+        }
+        ++cur;
+        ++t->deframe_state;
+      }
+      if (cur == end) {
+        return 1;
+      }
+    /* fallthrough */
+    dts_fh_0:
+    case DTS_FH_0:
+      GPR_ASSERT(cur < end);
+      t->incoming_frame_size = ((gpr_uint32)*cur) << 16;
+      if (++cur == end) {
+        t->deframe_state = DTS_FH_1;
+        return 1;
+      }
+    /* fallthrough */
+    case DTS_FH_1:
+      GPR_ASSERT(cur < end);
+      t->incoming_frame_size |= ((gpr_uint32)*cur) << 8;
+      if (++cur == end) {
+        t->deframe_state = DTS_FH_2;
+        return 1;
+      }
+    /* fallthrough */
+    case DTS_FH_2:
+      GPR_ASSERT(cur < end);
+      t->incoming_frame_size |= *cur;
+      if (++cur == end) {
+        t->deframe_state = DTS_FH_3;
+        return 1;
+      }
+    /* fallthrough */
+    case DTS_FH_3:
+      GPR_ASSERT(cur < end);
+      t->incoming_frame_type = *cur;
+      if (++cur == end) {
+        t->deframe_state = DTS_FH_4;
+        return 1;
+      }
+    /* fallthrough */
+    case DTS_FH_4:
+      GPR_ASSERT(cur < end);
+      t->incoming_frame_flags = *cur;
+      if (++cur == end) {
+        t->deframe_state = DTS_FH_5;
+        return 1;
+      }
+    /* fallthrough */
+    case DTS_FH_5:
+      GPR_ASSERT(cur < end);
+      t->incoming_stream_id = (((gpr_uint32)*cur) << 24) & 0x7f;
+      if (++cur == end) {
+        t->deframe_state = DTS_FH_6;
+        return 1;
+      }
+    /* fallthrough */
+    case DTS_FH_6:
+      GPR_ASSERT(cur < end);
+      t->incoming_stream_id |= ((gpr_uint32)*cur) << 16;
+      if (++cur == end) {
+        t->deframe_state = DTS_FH_7;
+        return 1;
+      }
+    /* fallthrough */
+    case DTS_FH_7:
+      GPR_ASSERT(cur < end);
+      t->incoming_stream_id |= ((gpr_uint32)*cur) << 8;
+      if (++cur == end) {
+        t->deframe_state = DTS_FH_8;
+        return 1;
+      }
+    /* fallthrough */
+    case DTS_FH_8:
+      GPR_ASSERT(cur < end);
+      t->incoming_stream_id |= ((gpr_uint32)*cur);
+      t->deframe_state = DTS_FRAME;
+      if (!init_frame_parser(t)) {
+        return 0;
+      }
+      if (t->incoming_frame_size == 0) {
+        if (!parse_frame_slice(t, gpr_empty_slice(), 1)) {
+          return 0;
+        }
+        if (++cur == end) {
+          t->deframe_state = DTS_FH_0;
+          return 1;
+        }
+        goto dts_fh_0; /* loop */
+      }
+      if (++cur == end) {
+        return 1;
+      }
+    /* fallthrough */
+    case DTS_FRAME:
+      GPR_ASSERT(cur < end);
+      if (end - cur == t->incoming_frame_size) {
+        if (!parse_frame_slice(
+                t, gpr_slice_sub_no_ref(slice, cur - beg, end - beg), 1)) {
+          return 0;
+        }
+        t->deframe_state = DTS_FH_0;
+        return 1;
+      } else if (end - cur > t->incoming_frame_size) {
+        if (!parse_frame_slice(
+                t, gpr_slice_sub_no_ref(slice, cur - beg,
+                                        cur + t->incoming_frame_size - beg),
+                1)) {
+          return 0;
+        }
+        cur += t->incoming_frame_size;
+        goto dts_fh_0; /* loop */
+      } else {
+        if (!parse_frame_slice(
+                t, gpr_slice_sub_no_ref(slice, cur - beg, end - beg), 0)) {
+          return 0;
+        }
+        t->incoming_frame_size -= (end - cur);
+        return 1;
+      }
+      gpr_log(GPR_ERROR, "should never reach here");
+      abort();
+  }
+
+  gpr_log(GPR_ERROR, "should never reach here");
+  abort();
+}
+
+/* tcp read callback */
+static void recv_data(void *tp, gpr_slice *slices, size_t nslices,
+                      grpc_endpoint_cb_status error) {
+  transport *t = tp;
+  size_t i;
+  int keep_reading = 0;
+
+  switch (error) {
+    case GRPC_ENDPOINT_CB_SHUTDOWN:
+    case GRPC_ENDPOINT_CB_EOF:
+    case GRPC_ENDPOINT_CB_ERROR:
+    case GRPC_ENDPOINT_CB_TIMED_OUT:
+      lock(t);
+      drop_connection(t);
+      t->reading = 0;
+      if (!t->writing && t->ep) {
+        grpc_endpoint_destroy(t->ep);
+        t->ep = NULL;
+        gpr_cv_broadcast(&t->cv);
+        unref_transport(t); /* safe as we still have a ref for read */
+      }
+      unlock(t);
+      unref_transport(t);
+      break;
+    case GRPC_ENDPOINT_CB_OK:
+      lock(t);
+      for (i = 0; i < nslices && process_read(t, slices[i]); i++)
+        ;
+      unlock(t);
+      keep_reading = 1;
+      break;
+  }
+
+  for (i = 0; i < nslices; i++) gpr_slice_unref(slices[i]);
+
+  if (keep_reading) {
+    grpc_endpoint_notify_on_read(t->ep, recv_data, t, gpr_inf_future);
+  }
+}
+
+/*
+ * CALLBACK LOOP
+ */
+
+static grpc_stream_state compute_state(gpr_uint8 write_closed,
+                                       gpr_uint8 read_closed) {
+  if (write_closed && read_closed) return GRPC_STREAM_CLOSED;
+  if (write_closed) return GRPC_STREAM_SEND_CLOSED;
+  if (read_closed) return GRPC_STREAM_RECV_CLOSED;
+  return GRPC_STREAM_OPEN;
+}
+
+static int prepare_callbacks(transport *t) {
+  stream *s;
+  grpc_stream_op_buffer temp_sopb;
+  int n = 0;
+  while ((s = stream_list_remove_head(t, PENDING_CALLBACKS))) {
+    int execute = 1;
+    temp_sopb = s->parser.incoming_sopb;
+    s->parser.incoming_sopb = s->callback_sopb;
+    s->callback_sopb = temp_sopb;
+
+    s->callback_state = compute_state(
+        s->write_closed && s->outgoing_sopb.nops == 0, s->read_closed);
+    if (s->callback_state == GRPC_STREAM_CLOSED) {
+      remove_from_stream_map(t, s);
+      if (s->published_close) {
+        execute = 0;
+      }
+      s->published_close = 1;
+    }
+
+    if (execute) {
+      stream_list_add_tail(t, s, EXECUTING_CALLBACKS);
+      n = 1;
+    }
+  }
+  return n;
+}
+
+static void run_callbacks(transport *t) {
+  stream *s;
+  while ((s = stream_list_remove_head(t, EXECUTING_CALLBACKS))) {
+    size_t nops = s->callback_sopb.nops;
+    s->callback_sopb.nops = 0;
+    t->cb->recv_batch(t->cb_user_data, &t->base, (grpc_stream *)s,
+                      s->callback_sopb.ops, nops, s->callback_state);
+  }
+}
+
+/*
+ * INTEGRATION GLUE
+ */
+
+static const grpc_transport_vtable vtable = {
+    sizeof(stream), init_stream, send_batch, set_allow_window_updates,
+    destroy_stream, abort_stream, close_transport, send_ping,
+    destroy_transport};
+
+void grpc_create_chttp2_transport(grpc_transport_setup_callback setup,
+                                  void *arg,
+                                  const grpc_channel_args *channel_args,
+                                  grpc_endpoint *ep, gpr_slice *slices,
+                                  size_t nslices, grpc_mdctx *mdctx,
+                                  int is_client) {
+  transport *t = gpr_malloc(sizeof(transport));
+  init_transport(t, setup, arg, channel_args, ep, mdctx, is_client);
+  ref_transport(t);
+  recv_data(t, slices, nslices, GRPC_ENDPOINT_CB_OK);
+}
diff --git a/src/core/transport/chttp2_transport.h b/src/core/transport/chttp2_transport.h
new file mode 100644
index 0000000..37eb84e
--- /dev/null
+++ b/src/core/transport/chttp2_transport.h
@@ -0,0 +1,47 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_TRANSPORT_H__
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_TRANSPORT_H__
+
+#include "src/core/endpoint/tcp.h"
+#include "src/core/transport/transport.h"
+
+void grpc_create_chttp2_transport(grpc_transport_setup_callback setup,
+                                  void *arg,
+                                  const grpc_channel_args *channel_args,
+                                  grpc_endpoint *ep, gpr_slice *slices,
+                                  size_t nslices, grpc_mdctx *metadata_context,
+                                  int is_client);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_TRANSPORT_H__ */
diff --git a/src/core/transport/metadata.c b/src/core/transport/metadata.c
new file mode 100644
index 0000000..ceb77df
--- /dev/null
+++ b/src/core/transport/metadata.c
@@ -0,0 +1,525 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/metadata.h"
+
+#include <stddef.h>
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include "src/core/support/murmur_hash.h"
+#include <grpc/support/time.h>
+
+#define INITIAL_STRTAB_CAPACITY 4
+#define INITIAL_MDTAB_CAPACITY 4
+
+#define KV_HASH(key, value) ((key)->hash ^ (value)->hash)
+
+typedef struct internal_string {
+  /* must be byte compatible with grpc_mdstr */
+  gpr_slice slice;
+  gpr_uint32 hash;
+
+  /* private only data */
+  gpr_uint32 refs;
+  gpr_slice_refcount refcount;
+
+  grpc_mdctx *context;
+
+  struct internal_string *bucket_next;
+} internal_string;
+
+typedef struct internal_metadata {
+  /* must be byte compatible with grpc_mdelem */
+  internal_string *key;
+  internal_string *value;
+
+  /* private only data */
+  void *user_data;
+  void (*destroy_user_data)(void *user_data);
+
+  gpr_uint32 refs;
+  grpc_mdctx *context;
+  struct internal_metadata *bucket_next;
+} internal_metadata;
+
+struct grpc_mdctx {
+  gpr_uint32 hash_seed;
+  int orphaned;
+
+  gpr_mu mu;
+
+  internal_string **strtab;
+  size_t strtab_count;
+  size_t strtab_capacity;
+
+  internal_metadata **mdtab;
+  size_t mdtab_count;
+  size_t mdtab_free;
+  size_t mdtab_capacity;
+};
+
+static void internal_string_ref(internal_string *s);
+static void internal_string_unref(internal_string *s);
+static void discard_metadata(grpc_mdctx *ctx);
+static void gc_mdtab(grpc_mdctx *ctx);
+static void metadata_context_destroy(grpc_mdctx *ctx);
+
+static void lock(grpc_mdctx *ctx) { gpr_mu_lock(&ctx->mu); }
+
+static void unlock(grpc_mdctx *ctx) {
+  /* If the context has been orphaned we'd like to delete it soon. We check
+     conditions in unlock as it signals the end of mutations on a context.
+
+     We need to ensure all grpc_mdelem and grpc_mdstr elements have been deleted
+     first. This is equivalent to saying that both tables have zero counts,
+     which is equivalent to saying that strtab_count is zero (as mdelem's MUST
+     reference an mdstr for their key and value slots).
+
+     To encourage that to happen, we start discarding zero reference count
+     mdelems on every unlock (instead of the usual 'I'm too loaded' trigger
+     case), since otherwise we can be stuck waiting for a garbage collection
+     that will never happen. */
+  if (ctx->orphaned) {
+    /* uncomment if you're having trouble diagnosing an mdelem leak to make
+       things clearer (slows down destruction a lot, however) */
+    /* gc_mdtab(ctx); */
+    if (ctx->mdtab_count && ctx->mdtab_count == ctx->mdtab_free) {
+      discard_metadata(ctx);
+    }
+    if (ctx->strtab_count == 0) {
+      gpr_mu_unlock(&ctx->mu);
+      metadata_context_destroy(ctx);
+      return;
+    }
+  }
+  gpr_mu_unlock(&ctx->mu);
+}
+
+static void ref_md(internal_metadata *md) {
+  if (0 == md->refs++) {
+    md->context->mdtab_free--;
+  }
+}
+
+grpc_mdctx *grpc_mdctx_create_with_seed(gpr_uint32 seed) {
+  grpc_mdctx *ctx = gpr_malloc(sizeof(grpc_mdctx));
+
+  ctx->orphaned = 0;
+  ctx->hash_seed = seed;
+  gpr_mu_init(&ctx->mu);
+  ctx->strtab = gpr_malloc(sizeof(internal_string *) * INITIAL_STRTAB_CAPACITY);
+  memset(ctx->strtab, 0, sizeof(grpc_mdstr *) * INITIAL_STRTAB_CAPACITY);
+  ctx->strtab_count = 0;
+  ctx->strtab_capacity = INITIAL_STRTAB_CAPACITY;
+  ctx->mdtab = gpr_malloc(sizeof(internal_metadata *) * INITIAL_MDTAB_CAPACITY);
+  memset(ctx->mdtab, 0, sizeof(grpc_mdelem *) * INITIAL_MDTAB_CAPACITY);
+  ctx->mdtab_count = 0;
+  ctx->mdtab_capacity = INITIAL_MDTAB_CAPACITY;
+  ctx->mdtab_free = 0;
+
+  return ctx;
+}
+
+grpc_mdctx *grpc_mdctx_create() {
+  /* This seed is used to prevent remote connections from controlling hash table
+   * collisions. It needs to be somewhat unpredictable to a remote connection.
+   */
+  return grpc_mdctx_create_with_seed(gpr_now().tv_nsec);
+}
+
+static void discard_metadata(grpc_mdctx *ctx) {
+  size_t i;
+  internal_metadata *next, *cur;
+
+  for (i = 0; i < ctx->mdtab_capacity; i++) {
+    cur = ctx->mdtab[i];
+    while (cur) {
+      GPR_ASSERT(cur->refs == 0);
+      next = cur->bucket_next;
+      internal_string_unref(cur->key);
+      internal_string_unref(cur->value);
+      if (cur->user_data) {
+        cur->destroy_user_data(cur->user_data);
+      }
+      gpr_free(cur);
+      cur = next;
+      ctx->mdtab_free--;
+      ctx->mdtab_count--;
+    }
+    ctx->mdtab[i] = NULL;
+  }
+}
+
+static void metadata_context_destroy(grpc_mdctx *ctx) {
+  gpr_mu_lock(&ctx->mu);
+  GPR_ASSERT(ctx->strtab_count == 0);
+  GPR_ASSERT(ctx->mdtab_count == 0);
+  GPR_ASSERT(ctx->mdtab_free == 0);
+  gpr_free(ctx->strtab);
+  gpr_free(ctx->mdtab);
+  gpr_mu_unlock(&ctx->mu);
+  gpr_mu_destroy(&ctx->mu);
+  gpr_free(ctx);
+}
+
+void grpc_mdctx_orphan(grpc_mdctx *ctx) {
+  lock(ctx);
+  GPR_ASSERT(!ctx->orphaned);
+  ctx->orphaned = 1;
+  unlock(ctx);
+}
+
+static void grow_strtab(grpc_mdctx *ctx) {
+  size_t capacity = ctx->strtab_capacity * 2;
+  size_t i;
+  internal_string **strtab = gpr_malloc(sizeof(internal_string *) * capacity);
+  internal_string *s, *next;
+  memset(strtab, 0, sizeof(internal_string *) * capacity);
+
+  for (i = 0; i < ctx->strtab_capacity; i++) {
+    for (s = ctx->strtab[i]; s; s = next) {
+      next = s->bucket_next;
+      s->bucket_next = strtab[s->hash % capacity];
+      strtab[s->hash % capacity] = s;
+    }
+  }
+
+  gpr_free(ctx->strtab);
+  ctx->strtab = strtab;
+  ctx->strtab_capacity = capacity;
+}
+
+static void internal_destroy_string(internal_string *is) {
+  internal_string **prev_next;
+  internal_string *cur;
+  grpc_mdctx *ctx = is->context;
+  for (prev_next = &ctx->strtab[is->hash % ctx->strtab_capacity],
+      cur = *prev_next;
+       cur != is; prev_next = &cur->bucket_next, cur = cur->bucket_next)
+    ;
+  *prev_next = cur->bucket_next;
+  ctx->strtab_count--;
+  gpr_free(is);
+}
+
+static void internal_string_ref(internal_string *s) { ++s->refs; }
+
+static void internal_string_unref(internal_string *s) {
+  GPR_ASSERT(s->refs > 0);
+  if (0 == --s->refs) {
+    internal_destroy_string(s);
+  }
+}
+
+static void slice_ref(void *p) {
+  internal_string *is =
+      (internal_string *)((char *)p - offsetof(internal_string, refcount));
+  grpc_mdctx *ctx = is->context;
+  lock(ctx);
+  internal_string_ref(is);
+  unlock(ctx);
+}
+
+static void slice_unref(void *p) {
+  internal_string *is =
+      (internal_string *)((char *)p - offsetof(internal_string, refcount));
+  grpc_mdctx *ctx = is->context;
+  lock(ctx);
+  internal_string_unref(is);
+  unlock(ctx);
+}
+
+grpc_mdstr *grpc_mdstr_from_string(grpc_mdctx *ctx, const char *str) {
+  return grpc_mdstr_from_buffer(ctx, (const gpr_uint8 *)str, strlen(str));
+}
+
+grpc_mdstr *grpc_mdstr_from_slice(grpc_mdctx *ctx, gpr_slice slice) {
+  grpc_mdstr *result = grpc_mdstr_from_buffer(ctx, GPR_SLICE_START_PTR(slice),
+                                              GPR_SLICE_LENGTH(slice));
+  gpr_slice_unref(slice);
+  return result;
+}
+
+grpc_mdstr *grpc_mdstr_from_buffer(grpc_mdctx *ctx, const gpr_uint8 *buf,
+                                   size_t length) {
+  gpr_uint32 hash = gpr_murmur_hash3(buf, length, ctx->hash_seed);
+  internal_string *s;
+
+  lock(ctx);
+
+  /* search for an existing string */
+  for (s = ctx->strtab[hash % ctx->strtab_capacity]; s; s = s->bucket_next) {
+    if (s->hash == hash && GPR_SLICE_LENGTH(s->slice) == length &&
+        0 == memcmp(buf, GPR_SLICE_START_PTR(s->slice), length)) {
+      internal_string_ref(s);
+      unlock(ctx);
+      return (grpc_mdstr *)s;
+    }
+  }
+
+  /* not found: create a new string */
+  if (length + 1 < GPR_SLICE_INLINED_SIZE) {
+    /* string data goes directly into the slice */
+    s = gpr_malloc(sizeof(internal_string));
+    s->refs = 1;
+    s->slice.refcount = NULL;
+    memcpy(s->slice.data.inlined.bytes, buf, length);
+    s->slice.data.inlined.bytes[length] = 0;
+    s->slice.data.inlined.length = length;
+  } else {
+    /* string data goes after the internal_string header, and we +1 for null
+       terminator */
+    s = gpr_malloc(sizeof(internal_string) + length + 1);
+    s->refs = 1;
+    s->refcount.ref = slice_ref;
+    s->refcount.unref = slice_unref;
+    s->slice.refcount = &s->refcount;
+    s->slice.data.refcounted.bytes = (gpr_uint8 *)(s + 1);
+    s->slice.data.refcounted.length = length;
+    memcpy(s->slice.data.refcounted.bytes, buf, length);
+    /* add a null terminator for cheap c string conversion when desired */
+    s->slice.data.refcounted.bytes[length] = 0;
+  }
+  s->hash = hash;
+  s->context = ctx;
+  s->bucket_next = ctx->strtab[hash % ctx->strtab_capacity];
+  ctx->strtab[hash % ctx->strtab_capacity] = s;
+
+  ctx->strtab_count++;
+
+  if (ctx->strtab_count > ctx->strtab_capacity * 2) {
+    grow_strtab(ctx);
+  }
+
+  unlock(ctx);
+
+  return (grpc_mdstr *)s;
+}
+
+static void gc_mdtab(grpc_mdctx *ctx) {
+  size_t i;
+  internal_metadata **prev_next;
+  internal_metadata *md, *next;
+
+  for (i = 0; i < ctx->mdtab_capacity; i++) {
+    prev_next = &ctx->mdtab[i];
+    for (md = ctx->mdtab[i]; md; md = next) {
+      next = md->bucket_next;
+      if (md->refs == 0) {
+        internal_string_unref(md->key);
+        internal_string_unref(md->value);
+        if (md->user_data) {
+          md->destroy_user_data(md->user_data);
+        }
+        gpr_free(md);
+        *prev_next = next;
+        ctx->mdtab_free--;
+        ctx->mdtab_count--;
+      } else {
+        prev_next = &md->bucket_next;
+      }
+    }
+  }
+
+  GPR_ASSERT(ctx->mdtab_free == 0);
+}
+
+static void grow_mdtab(grpc_mdctx *ctx) {
+  size_t capacity = ctx->mdtab_capacity * 2;
+  size_t i;
+  internal_metadata **mdtab =
+      gpr_malloc(sizeof(internal_metadata *) * capacity);
+  internal_metadata *md, *next;
+  gpr_uint32 hash;
+  memset(mdtab, 0, sizeof(internal_metadata *) * capacity);
+
+  for (i = 0; i < ctx->mdtab_capacity; i++) {
+    for (md = ctx->mdtab[i]; md; md = next) {
+      hash = KV_HASH(md->key, md->value);
+      next = md->bucket_next;
+      md->bucket_next = mdtab[hash % capacity];
+      mdtab[hash % capacity] = md;
+    }
+  }
+
+  gpr_free(ctx->mdtab);
+  ctx->mdtab = mdtab;
+  ctx->mdtab_capacity = capacity;
+}
+
+static void rehash_mdtab(grpc_mdctx *ctx) {
+  if (ctx->mdtab_free > ctx->mdtab_capacity / 4) {
+    gc_mdtab(ctx);
+  } else {
+    grow_mdtab(ctx);
+  }
+}
+
+grpc_mdelem *grpc_mdelem_from_metadata_strings(grpc_mdctx *ctx,
+                                               grpc_mdstr *mkey,
+                                               grpc_mdstr *mvalue) {
+  internal_string *key = (internal_string *)mkey;
+  internal_string *value = (internal_string *)mvalue;
+  gpr_uint32 hash = KV_HASH(mkey, mvalue);
+  internal_metadata *md;
+
+  GPR_ASSERT(key->context == ctx);
+  GPR_ASSERT(value->context == ctx);
+
+  lock(ctx);
+
+  /* search for an existing pair */
+  for (md = ctx->mdtab[hash % ctx->mdtab_capacity]; md; md = md->bucket_next) {
+    if (md->key == key && md->value == value) {
+      ref_md(md);
+      internal_string_unref(key);
+      internal_string_unref(value);
+      unlock(ctx);
+      return (grpc_mdelem *)md;
+    }
+  }
+
+  /* not found: create a new pair */
+  md = gpr_malloc(sizeof(internal_metadata));
+  md->refs = 1;
+  md->context = ctx;
+  md->key = key;
+  md->value = value;
+  md->user_data = NULL;
+  md->destroy_user_data = NULL;
+  md->bucket_next = ctx->mdtab[hash % ctx->mdtab_capacity];
+  ctx->mdtab[hash % ctx->mdtab_capacity] = md;
+  ctx->mdtab_count++;
+
+  if (ctx->mdtab_count > ctx->mdtab_capacity * 2) {
+    rehash_mdtab(ctx);
+  }
+
+  unlock(ctx);
+
+  return (grpc_mdelem *)md;
+}
+
+grpc_mdelem *grpc_mdelem_from_strings(grpc_mdctx *ctx, const char *key,
+                                      const char *value) {
+  return grpc_mdelem_from_metadata_strings(ctx,
+                                           grpc_mdstr_from_string(ctx, key),
+                                           grpc_mdstr_from_string(ctx, value));
+}
+
+grpc_mdelem *grpc_mdelem_from_slices(grpc_mdctx *ctx, gpr_slice key,
+                                     gpr_slice value) {
+  return grpc_mdelem_from_metadata_strings(ctx, grpc_mdstr_from_slice(ctx, key),
+                                           grpc_mdstr_from_slice(ctx, value));
+}
+
+grpc_mdelem *grpc_mdelem_from_string_and_buffer(grpc_mdctx *ctx,
+                                                const char *key,
+                                                const gpr_uint8 *value,
+                                                size_t value_length) {
+  return grpc_mdelem_from_metadata_strings(
+      ctx, grpc_mdstr_from_string(ctx, key),
+      grpc_mdstr_from_buffer(ctx, value, value_length));
+}
+
+grpc_mdelem *grpc_mdelem_ref(grpc_mdelem *gmd) {
+  internal_metadata *md = (internal_metadata *)gmd;
+  grpc_mdctx *ctx = md->context;
+  lock(ctx);
+  ref_md(md);
+  unlock(ctx);
+  return gmd;
+}
+
+void grpc_mdelem_unref(grpc_mdelem *gmd) {
+  internal_metadata *md = (internal_metadata *)gmd;
+  grpc_mdctx *ctx = md->context;
+  lock(ctx);
+  GPR_ASSERT(md->refs);
+  if (0 == --md->refs) {
+    ctx->mdtab_free++;
+  }
+  unlock(ctx);
+}
+
+const char *grpc_mdstr_as_c_string(grpc_mdstr *s) {
+  return (const char *)GPR_SLICE_START_PTR(s->slice);
+}
+
+grpc_mdstr *grpc_mdstr_ref(grpc_mdstr *gs) {
+  internal_string *s = (internal_string *)gs;
+  grpc_mdctx *ctx = s->context;
+  lock(ctx);
+  internal_string_ref(s);
+  unlock(ctx);
+  return gs;
+}
+
+void grpc_mdstr_unref(grpc_mdstr *gs) {
+  internal_string *s = (internal_string *)gs;
+  grpc_mdctx *ctx = s->context;
+  lock(ctx);
+  internal_string_unref(s);
+  unlock(ctx);
+}
+
+size_t grpc_mdctx_get_mdtab_capacity_test_only(grpc_mdctx *ctx) {
+  return ctx->mdtab_capacity;
+}
+
+size_t grpc_mdctx_get_mdtab_count_test_only(grpc_mdctx *ctx) {
+  return ctx->mdtab_count;
+}
+
+size_t grpc_mdctx_get_mdtab_free_test_only(grpc_mdctx *ctx) {
+  return ctx->mdtab_free;
+}
+
+void *grpc_mdelem_get_user_data(grpc_mdelem *md,
+                                void (*if_destroy_func)(void *)) {
+  internal_metadata *im = (internal_metadata *)md;
+  return im->destroy_user_data == if_destroy_func ? im->user_data : NULL;
+}
+
+void grpc_mdelem_set_user_data(grpc_mdelem *md, void (*destroy_func)(void *),
+                               void *user_data) {
+  internal_metadata *im = (internal_metadata *)md;
+  GPR_ASSERT((user_data == NULL) == (destroy_func == NULL));
+  if (im->destroy_user_data) {
+    im->destroy_user_data(im->user_data);
+  }
+  im->destroy_user_data = destroy_func;
+  im->user_data = user_data;
+}
diff --git a/src/core/transport/metadata.h b/src/core/transport/metadata.h
new file mode 100644
index 0000000..4b87f70
--- /dev/null
+++ b/src/core/transport/metadata.h
@@ -0,0 +1,132 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_METADATA_H__
+#define __GRPC_INTERNAL_TRANSPORT_METADATA_H__
+
+#include <grpc/support/slice.h>
+
+/* This file provides a mechanism for tracking metadata through the grpc stack.
+   It's not intended for consumption outside of the library.
+
+   Metadata is tracked in the context of a grpc_mdctx. For the time being there
+   is one of these per-channel, avoiding cross channel interference with memory
+   use and lock contention.
+
+   The context tracks unique strings (grpc_mdstr) and pairs of strings
+   (grpc_mdelem). Any of these objects can be checked for equality by comparing
+   their pointers. These objects are reference counted.
+
+   grpc_mdelem can additionally store a (non-NULL) user data pointer. This
+   pointer is intended to be used to cache semantic meaning of a metadata
+   element. For example, an OAuth token may cache the credentials it represents
+   and the time at which it expires in the mdelem user data.
+
+   Combining this metadata cache and the hpack compression table allows us to
+   simply lookup complete preparsed objects quickly, incurring a few atomic
+   ops per metadata element on the fast path.
+
+   grpc_mdelem instances MAY live longer than their refcount implies, and are
+   garbage collected periodically, meaning cached data can easily outlive a
+   single request. */
+
+/* Forward declarations */
+typedef struct grpc_mdctx grpc_mdctx;
+typedef struct grpc_mdstr grpc_mdstr;
+typedef struct grpc_mdelem grpc_mdelem;
+
+/* if changing this, make identical changes in internal_string in metadata.c */
+struct grpc_mdstr {
+  const gpr_slice slice;
+  const gpr_uint32 hash;
+  /* there is a private part to this in metadata.c */
+};
+
+/* if changing this, make identical changes in internal_metadata in
+   metadata.c */
+struct grpc_mdelem {
+  grpc_mdstr *const key;
+  grpc_mdstr *const value;
+  /* there is a private part to this in metadata.c */
+};
+
+/* Create/orphan a metadata context */
+grpc_mdctx *grpc_mdctx_create();
+grpc_mdctx *grpc_mdctx_create_with_seed(gpr_uint32 seed);
+void grpc_mdctx_orphan(grpc_mdctx *mdctx);
+
+/* Test only accessors to internal state - only for testing this code - do not
+   rely on it outside of metadata_test.c */
+size_t grpc_mdctx_get_mdtab_capacity_test_only(grpc_mdctx *mdctx);
+size_t grpc_mdctx_get_mdtab_count_test_only(grpc_mdctx *mdctx);
+size_t grpc_mdctx_get_mdtab_free_test_only(grpc_mdctx *mdctx);
+
+/* Constructors for grpc_mdstr instances; take a variety of data types that
+   clients may have handy */
+grpc_mdstr *grpc_mdstr_from_string(grpc_mdctx *ctx, const char *str);
+grpc_mdstr *grpc_mdstr_from_slice(grpc_mdctx *ctx, gpr_slice slice);
+grpc_mdstr *grpc_mdstr_from_buffer(grpc_mdctx *ctx, const gpr_uint8 *str,
+                                   size_t length);
+
+/* Constructors for grpc_mdelem instances; take a variety of data types that
+   clients may have handy */
+grpc_mdelem *grpc_mdelem_from_metadata_strings(grpc_mdctx *ctx, grpc_mdstr *key,
+                                               grpc_mdstr *value);
+grpc_mdelem *grpc_mdelem_from_strings(grpc_mdctx *ctx, const char *key,
+                                      const char *value);
+grpc_mdelem *grpc_mdelem_from_slices(grpc_mdctx *ctx, gpr_slice key,
+                                     gpr_slice value);
+grpc_mdelem *grpc_mdelem_from_string_and_buffer(grpc_mdctx *ctx,
+                                                const char *key,
+                                                const gpr_uint8 *value,
+                                                size_t value_length);
+
+/* Mutator and accessor for grpc_mdelem user data. The destructor function
+   is used as a type tag and is checked during user_data fetch. */
+void *grpc_mdelem_get_user_data(grpc_mdelem *md,
+                                void (*if_destroy_func)(void *));
+void grpc_mdelem_set_user_data(grpc_mdelem *md, void (*destroy_func)(void *),
+                               void *user_data);
+
+/* Reference counting */
+grpc_mdstr *grpc_mdstr_ref(grpc_mdstr *s);
+void grpc_mdstr_unref(grpc_mdstr *s);
+
+grpc_mdelem *grpc_mdelem_ref(grpc_mdelem *md);
+void grpc_mdelem_unref(grpc_mdelem *md);
+
+/* Recover a char* from a grpc_mdstr. The returned string is null terminated.
+   Does not promise that the returned string has no embedded nulls however. */
+const char *grpc_mdstr_as_c_string(grpc_mdstr *s);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_METADATA_H__ */
diff --git a/src/core/transport/stream_op.c b/src/core/transport/stream_op.c
new file mode 100644
index 0000000..c77c8cd
--- /dev/null
+++ b/src/core/transport/stream_op.c
@@ -0,0 +1,165 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/stream_op.h"
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+#include <string.h>
+
+/* Initial number of operations to allocate */
+#define INITIAL_SLOTS 8
+/* Exponential growth function: Given x, return a larger x.
+   Currently we grow by 1.5 times upon reallocation.
+   Assumes INITIAL_SLOTS > 1 */
+#define GROW(x) (3 * (x) / 2)
+
+void grpc_sopb_init(grpc_stream_op_buffer *sopb) {
+  sopb->ops = gpr_malloc(sizeof(grpc_stream_op) * INITIAL_SLOTS);
+  GPR_ASSERT(sopb->ops);
+  sopb->nops = 0;
+  sopb->capacity = INITIAL_SLOTS;
+}
+
+void grpc_sopb_destroy(grpc_stream_op_buffer *sopb) {
+  grpc_stream_ops_unref_owned_objects(sopb->ops, sopb->nops);
+  gpr_free(sopb->ops);
+}
+
+void grpc_sopb_reset(grpc_stream_op_buffer *sopb) {
+  grpc_stream_ops_unref_owned_objects(sopb->ops, sopb->nops);
+  sopb->nops = 0;
+}
+
+void grpc_stream_ops_unref_owned_objects(grpc_stream_op *ops, size_t nops) {
+  int i;
+  for (i = 0; i < nops; i++) {
+    switch (ops[i].type) {
+      case GRPC_OP_SLICE:
+        gpr_slice_unref(ops[i].data.slice);
+        break;
+      case GRPC_OP_METADATA:
+        grpc_mdelem_unref(ops[i].data.metadata);
+        break;
+      case GRPC_OP_FLOW_CTL_CB:
+        ops[i].data.flow_ctl_cb.cb(ops[i].data.flow_ctl_cb.arg, GRPC_OP_ERROR);
+        break;
+      case GRPC_NO_OP:
+      case GRPC_OP_DEADLINE:
+      case GRPC_OP_METADATA_BOUNDARY:
+      case GRPC_OP_BEGIN_MESSAGE:
+        break;
+    }
+  }
+}
+
+static void expand(grpc_stream_op_buffer *sopb) {
+  sopb->capacity = GROW(sopb->capacity);
+  sopb->ops = gpr_realloc(sopb->ops, sizeof(grpc_stream_op) * sopb->capacity);
+  GPR_ASSERT(sopb->ops);
+}
+
+static grpc_stream_op *add(grpc_stream_op_buffer *sopb) {
+  grpc_stream_op *out;
+
+  if (sopb->nops == sopb->capacity) {
+    expand(sopb);
+  }
+  out = sopb->ops + sopb->nops;
+  sopb->nops++;
+  return out;
+}
+
+void grpc_sopb_add_no_op(grpc_stream_op_buffer *sopb) {
+  add(sopb)->type = GRPC_NO_OP;
+}
+
+void grpc_sopb_add_begin_message(grpc_stream_op_buffer *sopb, gpr_uint32 length,
+                                 gpr_uint32 flags) {
+  grpc_stream_op *op = add(sopb);
+  op->type = GRPC_OP_BEGIN_MESSAGE;
+  op->data.begin_message.length = length;
+  op->data.begin_message.flags = flags;
+}
+
+void grpc_sopb_add_metadata_boundary(grpc_stream_op_buffer *sopb) {
+  grpc_stream_op *op = add(sopb);
+  op->type = GRPC_OP_METADATA_BOUNDARY;
+}
+
+void grpc_sopb_add_metadata(grpc_stream_op_buffer *sopb, grpc_mdelem *md) {
+  grpc_stream_op *op = add(sopb);
+  op->type = GRPC_OP_METADATA;
+  op->data.metadata = md;
+}
+
+void grpc_sopb_add_deadline(grpc_stream_op_buffer *sopb,
+                            gpr_timespec deadline) {
+  grpc_stream_op *op = add(sopb);
+  op->type = GRPC_OP_DEADLINE;
+  op->data.deadline = deadline;
+}
+
+void grpc_sopb_add_slice(grpc_stream_op_buffer *sopb, gpr_slice slice) {
+  grpc_stream_op *op = add(sopb);
+  op->type = GRPC_OP_SLICE;
+  op->data.slice = slice;
+}
+
+void grpc_sopb_add_flow_ctl_cb(grpc_stream_op_buffer *sopb,
+                               void (*cb)(void *arg, grpc_op_error error),
+                               void *arg) {
+  grpc_stream_op *op = add(sopb);
+  op->type = GRPC_OP_FLOW_CTL_CB;
+  op->data.flow_ctl_cb.cb = cb;
+  op->data.flow_ctl_cb.arg = arg;
+}
+
+void grpc_sopb_append(grpc_stream_op_buffer *sopb, grpc_stream_op *ops,
+                      size_t nops) {
+  size_t orig_nops = sopb->nops;
+  size_t new_nops = orig_nops + nops;
+
+  if (new_nops > sopb->capacity) {
+    size_t new_capacity = GROW(sopb->capacity);
+    if (new_capacity < new_nops) {
+      new_capacity = new_nops;
+    }
+    sopb->ops = gpr_realloc(sopb->ops, sizeof(grpc_stream_op) * new_capacity);
+    sopb->capacity = new_capacity;
+  }
+
+  memcpy(sopb->ops + orig_nops, ops, sizeof(grpc_stream_op) * nops);
+  sopb->nops = new_nops;
+}
diff --git a/src/core/transport/stream_op.h b/src/core/transport/stream_op.h
new file mode 100644
index 0000000..be60bc2
--- /dev/null
+++ b/src/core/transport/stream_op.h
@@ -0,0 +1,128 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_STREAM_OP_H__
+#define __GRPC_INTERNAL_TRANSPORT_STREAM_OP_H__
+
+#include <grpc/grpc.h>
+#include <grpc/support/port_platform.h>
+#include <grpc/support/slice.h>
+#include <grpc/support/time.h>
+#include "src/core/transport/metadata.h"
+
+/* Operations that can be performed on a stream.
+   Used by grpc_stream_op. */
+typedef enum grpc_stream_op_code {
+  /* Do nothing code. Useful if rewriting a batch to exclude some operations.
+     Must be ignored by receivers */
+  GRPC_NO_OP,
+  GRPC_OP_METADATA,
+  GRPC_OP_DEADLINE,
+  GRPC_OP_METADATA_BOUNDARY,
+  /* Begin a message/metadata element/status - as defined by
+     grpc_message_type. */
+  GRPC_OP_BEGIN_MESSAGE,
+  /* Add a slice of data to the current message/metadata element/status.
+     Must not overflow the forward declared length. */
+  GRPC_OP_SLICE,
+  /* Call some function once this operation has passed flow control. */
+  GRPC_OP_FLOW_CTL_CB
+} grpc_stream_op_code;
+
+/* Arguments for GRPC_OP_BEGIN */
+typedef struct grpc_begin_message {
+  /* How many bytes of data will this message contain */
+  gpr_uint32 length;
+  /* Write flags for the message: see grpc.h GRPC_WRITE_xxx */
+  gpr_uint32 flags;
+} grpc_begin_message;
+
+/* Arguments for GRPC_OP_FLOW_CTL_CB */
+typedef struct grpc_flow_ctl_cb {
+  void (*cb)(void *arg, grpc_op_error error);
+  void *arg;
+} grpc_flow_ctl_cb;
+
+/* Represents a single operation performed on a stream/transport */
+typedef struct grpc_stream_op {
+  /* the operation to be applied */
+  enum grpc_stream_op_code type;
+  /* the arguments to this operation. union fields are named according to the
+     associated op-code */
+  union {
+    grpc_begin_message begin_message;
+    grpc_mdelem *metadata;
+    gpr_timespec deadline;
+    gpr_slice slice;
+    grpc_flow_ctl_cb flow_ctl_cb;
+  } data;
+} grpc_stream_op;
+
+/* A stream op buffer is a wrapper around stream operations that is dynamically
+   extendable.
+   TODO(ctiller): inline a few elements into the struct, to avoid common case
+                  per-call allocations. */
+typedef struct grpc_stream_op_buffer {
+  grpc_stream_op *ops;
+  size_t nops;
+  size_t capacity;
+} grpc_stream_op_buffer;
+
+/* Initialize a stream op buffer */
+void grpc_sopb_init(grpc_stream_op_buffer *sopb);
+/* Destroy a stream op buffer */
+void grpc_sopb_destroy(grpc_stream_op_buffer *sopb);
+/* Reset a sopb to no elements */
+void grpc_sopb_reset(grpc_stream_op_buffer *sopb);
+
+void grpc_stream_ops_unref_owned_objects(grpc_stream_op *ops, size_t nops);
+
+/* Append a GRPC_NO_OP to a buffer */
+void grpc_sopb_add_no_op(grpc_stream_op_buffer *sopb);
+/* Append a GRPC_OP_BEGIN to a buffer */
+void grpc_sopb_add_begin_message(grpc_stream_op_buffer *sopb, gpr_uint32 length,
+                                 gpr_uint32 flags);
+void grpc_sopb_add_metadata(grpc_stream_op_buffer *sopb, grpc_mdelem *metadata);
+void grpc_sopb_add_deadline(grpc_stream_op_buffer *sopb, gpr_timespec deadline);
+void grpc_sopb_add_metadata_boundary(grpc_stream_op_buffer *sopb);
+/* Append a GRPC_SLICE to a buffer - does not ref/unref the slice */
+void grpc_sopb_add_slice(grpc_stream_op_buffer *sopb, gpr_slice slice);
+/* Append a GRPC_OP_FLOW_CTL_CB to a buffer */
+void grpc_sopb_add_flow_ctl_cb(grpc_stream_op_buffer *sopb,
+                               void (*cb)(void *arg, grpc_op_error error),
+                               void *arg);
+/* Append a buffer to a buffer - does not ref/unref any internal objects */
+void grpc_sopb_append(grpc_stream_op_buffer *sopb, grpc_stream_op *ops,
+                      size_t nops);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_STREAM_OP_H__ */
diff --git a/src/core/transport/transport.c b/src/core/transport/transport.c
new file mode 100644
index 0000000..d3291bb
--- /dev/null
+++ b/src/core/transport/transport.c
@@ -0,0 +1,85 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/transport.h"
+#include "src/core/transport/transport_impl.h"
+
+size_t grpc_transport_stream_size(grpc_transport *transport) {
+  return transport->vtable->sizeof_stream;
+}
+
+void grpc_transport_close(grpc_transport *transport) {
+  transport->vtable->close(transport);
+}
+
+void grpc_transport_destroy(grpc_transport *transport) {
+  transport->vtable->destroy(transport);
+}
+
+int grpc_transport_init_stream(grpc_transport *transport, grpc_stream *stream,
+                               const void *server_data) {
+  return transport->vtable->init_stream(transport, stream, server_data);
+}
+
+void grpc_transport_send_batch(grpc_transport *transport, grpc_stream *stream,
+                               grpc_stream_op *ops, size_t nops, int is_last) {
+  transport->vtable->send_batch(transport, stream, ops, nops, is_last);
+}
+
+void grpc_transport_set_allow_window_updates(grpc_transport *transport,
+                                             grpc_stream *stream, int allow) {
+  transport->vtable->set_allow_window_updates(transport, stream, allow);
+}
+
+void grpc_transport_destroy_stream(grpc_transport *transport,
+                                   grpc_stream *stream) {
+  transport->vtable->destroy_stream(transport, stream);
+}
+
+void grpc_transport_abort_stream(grpc_transport *transport, grpc_stream *stream,
+                                 grpc_status_code status) {
+  transport->vtable->abort_stream(transport, stream, status);
+}
+
+void grpc_transport_ping(grpc_transport *transport, void (*cb)(void *user_data),
+                         void *user_data) {
+  transport->vtable->ping(transport, cb, user_data);
+}
+
+void grpc_transport_setup_cancel(grpc_transport_setup *setup) {
+  setup->vtable->cancel(setup);
+}
+
+void grpc_transport_setup_initiate(grpc_transport_setup *setup) {
+  setup->vtable->initiate(setup);
+}
diff --git a/src/core/transport/transport.h b/src/core/transport/transport.h
new file mode 100644
index 0000000..1872947
--- /dev/null
+++ b/src/core/transport/transport.h
@@ -0,0 +1,245 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_TRANSPORT_H__
+#define __GRPC_INTERNAL_TRANSPORT_TRANSPORT_H__
+
+#include <stddef.h>
+
+#include "src/core/transport/stream_op.h"
+
+/* forward declarations */
+typedef struct grpc_transport grpc_transport;
+typedef struct grpc_transport_callbacks grpc_transport_callbacks;
+
+/* grpc_stream doesn't actually exist. It's used as a typesafe
+   opaque pointer for whatever data the transport wants to track
+   for a stream. */
+typedef struct grpc_stream grpc_stream;
+
+/* Represents the send/recv closed state of a stream. */
+typedef enum grpc_stream_state {
+  /* the stream is open for sends and receives */
+  GRPC_STREAM_OPEN,
+  /* the stream is closed for sends, but may still receive data */
+  GRPC_STREAM_SEND_CLOSED,
+  /* the stream is closed for receives, but may still send data */
+  GRPC_STREAM_RECV_CLOSED,
+  /* the stream is closed for both sends and receives */
+  GRPC_STREAM_CLOSED
+} grpc_stream_state;
+
+/* Callbacks made from the transport to the upper layers of grpc. */
+struct grpc_transport_callbacks {
+  /* Allocate a buffer to receive data into.
+     It's safe to call grpc_slice_new() to do this, but performance minded
+     proxies may want to carefully place data into optimal locations for
+     transports.
+     This function must return a valid, non-empty slice.
+
+     Arguments:
+       user_data - the transport user data set at transport creation time
+       transport - the grpc_transport instance making this call
+       stream    - the grpc_stream instance the buffer will be used for, or
+                   NULL if this is not known
+       size_hint - how big of a buffer would the transport optimally like?
+                   the actual returned buffer can be smaller or larger than
+                   size_hint as the implementation finds convenient */
+  struct gpr_slice (*alloc_recv_buffer)(void *user_data,
+                                        grpc_transport *transport,
+                                        grpc_stream *stream, size_t size_hint);
+
+  /* Initialize a new stream on behalf of the transport.
+     Must result in a call to
+     grpc_transport_init_stream(transport, ..., request) in the same call
+     stack.
+     Must not result in any other calls to the transport.
+
+     Arguments:
+       user_data     - the transport user data set at transport creation time
+       transport     - the grpc_transport instance making this call
+       request       - request parameters for this stream (owned by the caller)
+       server_data   - opaque transport dependent argument that should be passed
+                       to grpc_transport_init_stream
+     */
+  void (*accept_stream)(void *user_data, grpc_transport *transport,
+                        const void *server_data);
+
+  /* Process a set of stream ops that have been received by the transport.
+     Called by network threads, so must be careful not to block on network
+     activity.
+
+     If final_state == GRPC_STREAM_CLOSED, the upper layers should arrange to
+     call grpc_transport_destroy_stream.
+
+     Ownership of any objects contained in ops is transferred to the callee.
+
+     Arguments:
+       user_data   - the transport user data set at transport creation time
+       transport   - the grpc_transport instance making this call
+       stream      - the stream this data was received for
+       ops         - stream operations that are part of this batch
+       ops_count   - the number of stream operations in this batch
+       final_state - the state of the stream as of the final operation in this
+                     batch */
+  void (*recv_batch)(void *user_data, grpc_transport *transport,
+                     grpc_stream *stream, grpc_stream_op *ops, size_t ops_count,
+                     grpc_stream_state final_state);
+
+  /* The transport has been closed */
+  void (*closed)(void *user_data, grpc_transport *transport);
+};
+
+/* Returns the amount of memory required to store a grpc_stream for this
+   transport */
+size_t grpc_transport_stream_size(grpc_transport *transport);
+
+/* Initialize transport data for a stream.
+
+   Returns 0 on success, any other (transport-defined) value for failure.
+
+   Arguments:
+     transport   - the transport on which to create this stream
+     stream      - a pointer to uninitialized memory to initialize
+     server_data - either NULL for a client initiated stream, or a pointer
+                   supplied from the accept_stream callback function */
+int grpc_transport_init_stream(grpc_transport *transport, grpc_stream *stream,
+                               const void *server_data);
+
+/* Destroy transport data for a stream.
+
+   Requires: a recv_batch with final_state == GRPC_STREAM_CLOSED has been
+   received by the up-layer. Must not be called in the same call stack as
+   recv_frame.
+
+   Arguments:
+     transport - the transport on which to create this stream
+     stream    - the grpc_stream to destroy (memory is still owned by the
+                 caller, but any child memory must be cleaned up) */
+void grpc_transport_destroy_stream(grpc_transport *transport,
+                                   grpc_stream *stream);
+
+/* Enable/disable incoming data for a stream.
+
+   This effectively disables new window becoming available for a given stream,
+   but does not prevent existing window from being consumed by a sender: the
+   caller must still be prepared to receive some additional data after this
+   call.
+
+   Arguments:
+     transport - the transport on which to create this stream
+     stream    - the grpc_stream to destroy (memory is still owned by the
+                 caller, but any child memory must be cleaned up)
+     allow     - is it allowed that new window be opened up? */
+void grpc_transport_set_allow_window_updates(grpc_transport *transport,
+                                             grpc_stream *stream, int allow);
+
+/* Send a batch of operations on a transport
+
+   Takes ownership of any objects contained in ops.
+
+   Arguments:
+     transport - the transport on which to initiate the stream
+     stream    - the stream on which to send the operations. This must be
+                 non-NULL and previously initialized by the same transport.
+     ops       - an array of operations to apply to the stream - can be NULL
+                 if ops_count == 0.
+     ops_count - the number of elements in ops
+     is_last   - is this the last batch of operations to be sent out */
+void grpc_transport_send_batch(grpc_transport *transport, grpc_stream *stream,
+                               grpc_stream_op *ops, size_t ops_count,
+                               int is_last);
+
+/* Send a ping on a transport
+
+   Calls cb with user data when a response is received.
+   cb *MAY* be called with arbitrary transport level locks held. It is not safe
+   to call into the transport during cb. */
+void grpc_transport_ping(grpc_transport *transport, void (*cb)(void *user_data),
+                         void *user_data);
+
+/* Abort a stream
+
+   Terminate reading and writing for a stream. A final recv_batch with no
+   operations and final_state == GRPC_STREAM_CLOSED will be received locally,
+   and no more data will be presented to the up-layer.
+
+   TODO(ctiller): consider adding a HTTP/2 reason to this function. */
+void grpc_transport_abort_stream(grpc_transport *transport, grpc_stream *stream,
+                                 grpc_status_code status);
+
+/* Close a transport. Aborts all open streams. */
+void grpc_transport_close(struct grpc_transport *transport);
+
+/* Destroy the transport */
+void grpc_transport_destroy(struct grpc_transport *transport);
+
+/* Return type for grpc_transport_setup_callback */
+typedef struct grpc_transport_setup_result {
+  void *user_data;
+  const grpc_transport_callbacks *callbacks;
+} grpc_transport_setup_result;
+
+/* Given a transport, return callbacks for that transport. Used to finalize
+   setup as a transport is being created */
+typedef grpc_transport_setup_result (*grpc_transport_setup_callback)(
+    void *setup_arg, grpc_transport *transport, grpc_mdctx *mdctx);
+
+typedef struct grpc_transport_setup grpc_transport_setup;
+typedef struct grpc_transport_setup_vtable grpc_transport_setup_vtable;
+
+struct grpc_transport_setup_vtable {
+  void (*initiate)(grpc_transport_setup *setup);
+  void (*cancel)(grpc_transport_setup *setup);
+};
+
+/* Transport setup is an asynchronous utility interface for client channels to
+   establish connections. It's transport agnostic. */
+struct grpc_transport_setup {
+  const grpc_transport_setup_vtable *vtable;
+};
+
+/* Initiate transport setup: e.g. for TCP+DNS trigger a resolve of the name
+   given at transport construction time, create the tcp connection, perform
+   handshakes, and call some grpc_transport_setup_result function provided at
+   setup construction time.
+   This *may* be implemented as a no-op if the setup process monitors something
+   continuously. */
+void grpc_transport_setup_initiate(grpc_transport_setup *setup);
+/* Cancel transport setup. After this returns, no new transports should be
+   created, and all pending transport setup callbacks should be completed.
+   After this call completes, setup should be considered invalid (this can be
+   used as a destruction call by setup). */
+void grpc_transport_setup_cancel(grpc_transport_setup *setup);
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_TRANSPORT_H__ */
diff --git a/src/core/transport/transport_impl.h b/src/core/transport/transport_impl.h
new file mode 100644
index 0000000..6acdbf2
--- /dev/null
+++ b/src/core/transport/transport_impl.h
@@ -0,0 +1,80 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_TRANSPORT_IMPL_H__
+#define __GRPC_INTERNAL_TRANSPORT_TRANSPORT_IMPL_H__
+
+#include "src/core/transport/transport.h"
+
+typedef struct grpc_transport_vtable {
+  /* Memory required for a single stream element - this is allocated by upper
+     layers and initialized by the transport */
+  size_t sizeof_stream; /* = sizeof(transport stream) */
+
+  /* implementation of grpc_transport_init_stream */
+  int (*init_stream)(grpc_transport *self, grpc_stream *stream,
+                     const void *server_data);
+
+  /* implementation of grpc_transport_send_batch */
+  void (*send_batch)(grpc_transport *self, grpc_stream *stream,
+                     grpc_stream_op *ops, size_t ops_count, int is_last);
+
+  /* implementation of grpc_transport_set_allow_window_updates */
+  void (*set_allow_window_updates)(grpc_transport *self, grpc_stream *stream,
+                                   int allow);
+
+  /* implementation of grpc_transport_destroy_stream */
+  void (*destroy_stream)(grpc_transport *self, grpc_stream *stream);
+
+  /* implementation of grpc_transport_abort_stream */
+  void (*abort_stream)(grpc_transport *self, grpc_stream *stream,
+                       grpc_status_code status);
+
+  /* implementation of grpc_transport_close */
+  void (*close)(grpc_transport *self);
+
+  /* implementation of grpc_transport_ping */
+  void (*ping)(grpc_transport *self, void (*cb)(void *user_data),
+               void *user_data);
+
+  /* implementation of grpc_transport_destroy */
+  void (*destroy)(grpc_transport *self);
+} grpc_transport_vtable;
+
+/* an instance of a grpc transport */
+struct grpc_transport {
+  /* pointer to a vtable defining operations on this transport */
+  const grpc_transport_vtable *vtable;
+};
+
+#endif  /* __GRPC_INTERNAL_TRANSPORT_TRANSPORT_IMPL_H__ */