Built motion from commit 6a09e18b.|2.6.11
[motion2.git] / legacy-libs / grpc-cloned / deps / grpc / src / core / ext / transport / chttp2 / transport / parsing.cc
diff --git a/legacy-libs/grpc-cloned/deps/grpc/src/core/ext/transport/chttp2/transport/parsing.cc b/legacy-libs/grpc-cloned/deps/grpc/src/core/ext/transport/chttp2/transport/parsing.cc
new file mode 100644 (file)
index 0000000..e789006
--- /dev/null
@@ -0,0 +1,803 @@
+/*
+ *
+ * Copyright 2015 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <grpc/support/port_platform.h>
+
+#include "src/core/ext/transport/chttp2/transport/internal.h"
+
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
+
+#include "src/core/lib/profiling/timers.h"
+#include "src/core/lib/slice/slice_string_helpers.h"
+#include "src/core/lib/slice/slice_utils.h"
+#include "src/core/lib/transport/http2_errors.h"
+#include "src/core/lib/transport/static_metadata.h"
+#include "src/core/lib/transport/status_conversion.h"
+#include "src/core/lib/transport/timeout_encoding.h"
+
+static grpc_error* init_frame_parser(grpc_chttp2_transport* t);
+static grpc_error* init_header_frame_parser(grpc_chttp2_transport* t,
+                                            int is_continuation);
+static grpc_error* init_data_frame_parser(grpc_chttp2_transport* t);
+static grpc_error* init_rst_stream_parser(grpc_chttp2_transport* t);
+static grpc_error* init_settings_frame_parser(grpc_chttp2_transport* t);
+static grpc_error* init_window_update_frame_parser(grpc_chttp2_transport* t);
+static grpc_error* init_ping_parser(grpc_chttp2_transport* t);
+static grpc_error* init_goaway_parser(grpc_chttp2_transport* t);
+static grpc_error* init_skip_frame_parser(grpc_chttp2_transport* t,
+                                          int is_header);
+
+static grpc_error* parse_frame_slice(grpc_chttp2_transport* t,
+                                     const grpc_slice& slice, int is_last);
+
+grpc_error* grpc_chttp2_perform_read(grpc_chttp2_transport* t,
+                                     const grpc_slice& slice) {
+  const uint8_t* beg = GRPC_SLICE_START_PTR(slice);
+  const uint8_t* end = GRPC_SLICE_END_PTR(slice);
+  const uint8_t* cur = beg;
+  grpc_error* err;
+
+  if (cur == end) return GRPC_ERROR_NONE;
+
+  switch (t->deframe_state) {
+    case GRPC_DTS_CLIENT_PREFIX_0:
+    case GRPC_DTS_CLIENT_PREFIX_1:
+    case GRPC_DTS_CLIENT_PREFIX_2:
+    case GRPC_DTS_CLIENT_PREFIX_3:
+    case GRPC_DTS_CLIENT_PREFIX_4:
+    case GRPC_DTS_CLIENT_PREFIX_5:
+    case GRPC_DTS_CLIENT_PREFIX_6:
+    case GRPC_DTS_CLIENT_PREFIX_7:
+    case GRPC_DTS_CLIENT_PREFIX_8:
+    case GRPC_DTS_CLIENT_PREFIX_9:
+    case GRPC_DTS_CLIENT_PREFIX_10:
+    case GRPC_DTS_CLIENT_PREFIX_11:
+    case GRPC_DTS_CLIENT_PREFIX_12:
+    case GRPC_DTS_CLIENT_PREFIX_13:
+    case GRPC_DTS_CLIENT_PREFIX_14:
+    case GRPC_DTS_CLIENT_PREFIX_15:
+    case GRPC_DTS_CLIENT_PREFIX_16:
+    case GRPC_DTS_CLIENT_PREFIX_17:
+    case GRPC_DTS_CLIENT_PREFIX_18:
+    case GRPC_DTS_CLIENT_PREFIX_19:
+    case GRPC_DTS_CLIENT_PREFIX_20:
+    case GRPC_DTS_CLIENT_PREFIX_21:
+    case GRPC_DTS_CLIENT_PREFIX_22:
+    case GRPC_DTS_CLIENT_PREFIX_23:
+      while (cur != end && t->deframe_state != GRPC_DTS_FH_0) {
+        if (*cur != GRPC_CHTTP2_CLIENT_CONNECT_STRING[t->deframe_state]) {
+          char* msg;
+          gpr_asprintf(
+              &msg,
+              "Connect string mismatch: expected '%c' (%d) got '%c' (%d) "
+              "at byte %d",
+              GRPC_CHTTP2_CLIENT_CONNECT_STRING[t->deframe_state],
+              static_cast<int>(static_cast<uint8_t>(
+                  GRPC_CHTTP2_CLIENT_CONNECT_STRING[t->deframe_state])),
+              *cur, static_cast<int>(*cur), t->deframe_state);
+          err = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
+          gpr_free(msg);
+          return err;
+        }
+        ++cur;
+        t->deframe_state = static_cast<grpc_chttp2_deframe_transport_state>(
+            1 + static_cast<int>(t->deframe_state));
+      }
+      if (cur == end) {
+        return GRPC_ERROR_NONE;
+      }
+    /* fallthrough */
+    dts_fh_0:
+    case GRPC_DTS_FH_0:
+      GPR_DEBUG_ASSERT(cur < end);
+      t->incoming_frame_size = (static_cast<uint32_t>(*cur)) << 16;
+      if (++cur == end) {
+        t->deframe_state = GRPC_DTS_FH_1;
+        return GRPC_ERROR_NONE;
+      }
+    /* fallthrough */
+    case GRPC_DTS_FH_1:
+      GPR_DEBUG_ASSERT(cur < end);
+      t->incoming_frame_size |= (static_cast<uint32_t>(*cur)) << 8;
+      if (++cur == end) {
+        t->deframe_state = GRPC_DTS_FH_2;
+        return GRPC_ERROR_NONE;
+      }
+    /* fallthrough */
+    case GRPC_DTS_FH_2:
+      GPR_DEBUG_ASSERT(cur < end);
+      t->incoming_frame_size |= *cur;
+      if (++cur == end) {
+        t->deframe_state = GRPC_DTS_FH_3;
+        return GRPC_ERROR_NONE;
+      }
+    /* fallthrough */
+    case GRPC_DTS_FH_3:
+      GPR_DEBUG_ASSERT(cur < end);
+      t->incoming_frame_type = *cur;
+      if (++cur == end) {
+        t->deframe_state = GRPC_DTS_FH_4;
+        return GRPC_ERROR_NONE;
+      }
+    /* fallthrough */
+    case GRPC_DTS_FH_4:
+      GPR_DEBUG_ASSERT(cur < end);
+      t->incoming_frame_flags = *cur;
+      if (++cur == end) {
+        t->deframe_state = GRPC_DTS_FH_5;
+        return GRPC_ERROR_NONE;
+      }
+    /* fallthrough */
+    case GRPC_DTS_FH_5:
+      GPR_DEBUG_ASSERT(cur < end);
+      t->incoming_stream_id = ((static_cast<uint32_t>(*cur)) & 0x7f) << 24;
+      if (++cur == end) {
+        t->deframe_state = GRPC_DTS_FH_6;
+        return GRPC_ERROR_NONE;
+      }
+    /* fallthrough */
+    case GRPC_DTS_FH_6:
+      GPR_DEBUG_ASSERT(cur < end);
+      t->incoming_stream_id |= (static_cast<uint32_t>(*cur)) << 16;
+      if (++cur == end) {
+        t->deframe_state = GRPC_DTS_FH_7;
+        return GRPC_ERROR_NONE;
+      }
+    /* fallthrough */
+    case GRPC_DTS_FH_7:
+      GPR_DEBUG_ASSERT(cur < end);
+      t->incoming_stream_id |= (static_cast<uint32_t>(*cur)) << 8;
+      if (++cur == end) {
+        t->deframe_state = GRPC_DTS_FH_8;
+        return GRPC_ERROR_NONE;
+      }
+    /* fallthrough */
+    case GRPC_DTS_FH_8:
+      GPR_DEBUG_ASSERT(cur < end);
+      t->incoming_stream_id |= (static_cast<uint32_t>(*cur));
+      t->deframe_state = GRPC_DTS_FRAME;
+      err = init_frame_parser(t);
+      if (err != GRPC_ERROR_NONE) {
+        return err;
+      }
+      if (t->incoming_frame_size == 0) {
+        err = parse_frame_slice(t, grpc_empty_slice(), 1);
+        if (err != GRPC_ERROR_NONE) {
+          return err;
+        }
+        t->incoming_stream = nullptr;
+        if (++cur == end) {
+          t->deframe_state = GRPC_DTS_FH_0;
+          return GRPC_ERROR_NONE;
+        }
+        goto dts_fh_0; /* loop */
+      } else if (t->flow_control->flow_control_enabled() &&
+                 t->incoming_frame_size >
+                     t->settings[GRPC_ACKED_SETTINGS]
+                                [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE]) {
+        char* msg;
+        gpr_asprintf(&msg, "Frame size %d is larger than max frame size %d",
+                     t->incoming_frame_size,
+                     t->settings[GRPC_ACKED_SETTINGS]
+                                [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE]);
+        err = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
+        gpr_free(msg);
+        return err;
+      }
+      if (++cur == end) {
+        return GRPC_ERROR_NONE;
+      }
+    /* fallthrough */
+    case GRPC_DTS_FRAME:
+      GPR_DEBUG_ASSERT(cur < end);
+      if (static_cast<uint32_t>(end - cur) == t->incoming_frame_size) {
+        err = parse_frame_slice(
+            t,
+            grpc_slice_sub_no_ref(slice, static_cast<size_t>(cur - beg),
+                                  static_cast<size_t>(end - beg)),
+            1);
+        if (err != GRPC_ERROR_NONE) {
+          return err;
+        }
+        t->deframe_state = GRPC_DTS_FH_0;
+        t->incoming_stream = nullptr;
+        return GRPC_ERROR_NONE;
+      } else if (static_cast<uint32_t>(end - cur) > t->incoming_frame_size) {
+        size_t cur_offset = static_cast<size_t>(cur - beg);
+        err = parse_frame_slice(
+            t,
+            grpc_slice_sub_no_ref(slice, cur_offset,
+                                  cur_offset + t->incoming_frame_size),
+            1);
+        if (err != GRPC_ERROR_NONE) {
+          return err;
+        }
+        cur += t->incoming_frame_size;
+        t->incoming_stream = nullptr;
+        goto dts_fh_0; /* loop */
+      } else {
+        err = parse_frame_slice(
+            t,
+            grpc_slice_sub_no_ref(slice, static_cast<size_t>(cur - beg),
+                                  static_cast<size_t>(end - beg)),
+            0);
+        if (err != GRPC_ERROR_NONE) {
+          return err;
+        }
+        t->incoming_frame_size -= static_cast<uint32_t>(end - cur);
+        return GRPC_ERROR_NONE;
+      }
+      GPR_UNREACHABLE_CODE(return nullptr);
+  }
+
+  GPR_UNREACHABLE_CODE(return nullptr);
+}
+
+static grpc_error* init_frame_parser(grpc_chttp2_transport* t) {
+  if (t->is_first_frame &&
+      t->incoming_frame_type != GRPC_CHTTP2_FRAME_SETTINGS) {
+    char* msg;
+    gpr_asprintf(
+        &msg, "Expected SETTINGS frame as the first frame, got frame type %d",
+        t->incoming_frame_type);
+    grpc_error* err = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
+    gpr_free(msg);
+    return err;
+  }
+  t->is_first_frame = false;
+  if (t->expect_continuation_stream_id != 0) {
+    if (t->incoming_frame_type != GRPC_CHTTP2_FRAME_CONTINUATION) {
+      char* msg;
+      gpr_asprintf(&msg, "Expected CONTINUATION frame, got frame type %02x",
+                   t->incoming_frame_type);
+      grpc_error* err = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
+      gpr_free(msg);
+      return err;
+    }
+    if (t->expect_continuation_stream_id != t->incoming_stream_id) {
+      char* msg;
+      gpr_asprintf(
+          &msg,
+          "Expected CONTINUATION frame for grpc_chttp2_stream %08x, got "
+          "grpc_chttp2_stream %08x",
+          t->expect_continuation_stream_id, t->incoming_stream_id);
+      grpc_error* err = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
+      gpr_free(msg);
+      return err;
+    }
+    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:
+      return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+          "Unexpected CONTINUATION frame");
+    case GRPC_CHTTP2_FRAME_RST_STREAM:
+      return init_rst_stream_parser(t);
+    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);
+    case GRPC_CHTTP2_FRAME_GOAWAY:
+      return init_goaway_parser(t);
+    default:
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
+        gpr_log(GPR_ERROR, "Unknown frame type %02x", t->incoming_frame_type);
+      }
+      return init_skip_frame_parser(t, 0);
+  }
+}
+
+static grpc_error* skip_parser(void* parser, grpc_chttp2_transport* t,
+                               grpc_chttp2_stream* s, const grpc_slice& slice,
+                               int is_last) {
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* skip_header(void* tp, grpc_mdelem md) {
+  GRPC_MDELEM_UNREF(md);
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* init_skip_frame_parser(grpc_chttp2_transport* t,
+                                          int is_header) {
+  if (is_header) {
+    uint8_t 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 = nullptr;
+    t->hpack_parser.is_boundary = is_eoh;
+    t->hpack_parser.is_eof = static_cast<uint8_t>(is_eoh ? t->header_eof : 0);
+  } else {
+    t->parser = skip_parser;
+  }
+  return GRPC_ERROR_NONE;
+}
+
+void grpc_chttp2_parsing_become_skip_parser(grpc_chttp2_transport* t) {
+  init_skip_frame_parser(t, t->parser == grpc_chttp2_header_parser_parse);
+}
+
+static grpc_error* init_data_frame_parser(grpc_chttp2_transport* t) {
+  grpc_chttp2_stream* s =
+      grpc_chttp2_parsing_lookup_stream(t, t->incoming_stream_id);
+  grpc_error* err = GRPC_ERROR_NONE;
+  grpc_core::chttp2::FlowControlAction action;
+  if (s == nullptr) {
+    err = t->flow_control->RecvData(t->incoming_frame_size);
+    action = t->flow_control->MakeAction();
+  } else {
+    err = s->flow_control->RecvData(t->incoming_frame_size);
+    action = s->flow_control->MakeAction();
+  }
+  grpc_chttp2_act_on_flowctl_action(action, t, s);
+  if (err != GRPC_ERROR_NONE) {
+    goto error_handler;
+  }
+  if (s == nullptr) {
+    return init_skip_frame_parser(t, 0);
+  }
+  s->received_bytes += t->incoming_frame_size;
+  s->stats.incoming.framing_bytes += 9;
+  if (err == GRPC_ERROR_NONE && s->read_closed) {
+    return init_skip_frame_parser(t, 0);
+  }
+  if (err == GRPC_ERROR_NONE) {
+    err = grpc_chttp2_data_parser_begin_frame(
+        &s->data_parser, t->incoming_frame_flags, s->id, s);
+  }
+error_handler:
+  intptr_t unused;
+  if (err == GRPC_ERROR_NONE) {
+    t->incoming_stream = s;
+    /* t->parser = grpc_chttp2_data_parser_parse;*/
+    t->parser = grpc_chttp2_data_parser_parse;
+    t->parser_data = &s->data_parser;
+    t->ping_state.last_ping_sent_time = GRPC_MILLIS_INF_PAST;
+    return GRPC_ERROR_NONE;
+  } else if (grpc_error_get_int(err, GRPC_ERROR_INT_STREAM_ID, &unused)) {
+    /* handle stream errors by closing the stream */
+    if (s != nullptr) {
+      grpc_chttp2_mark_stream_closed(t, s, true, false, err);
+    }
+    grpc_chttp2_add_rst_stream_to_next_write(t, t->incoming_stream_id,
+                                             GRPC_HTTP2_PROTOCOL_ERROR,
+                                             &s->stats.outgoing);
+    return init_skip_frame_parser(t, 0);
+  } else {
+    return err;
+  }
+}
+
+static void free_timeout(void* p) { gpr_free(p); }
+
+static bool md_key_cmp(grpc_mdelem md, const grpc_slice& reference) {
+  GPR_DEBUG_ASSERT(grpc_slice_is_interned(GRPC_MDKEY(md)));
+  return GRPC_MDKEY(md).refcount == reference.refcount;
+}
+
+static bool md_cmp(grpc_mdelem md, grpc_mdelem ref_md,
+                   const grpc_slice& ref_key) {
+  if (GPR_LIKELY(GRPC_MDELEM_IS_INTERNED(md))) {
+    return md.payload == ref_md.payload;
+  }
+  if (md_key_cmp(md, ref_key)) {
+    return grpc_slice_eq_static_interned(GRPC_MDVALUE(md),
+                                         GRPC_MDVALUE(ref_md));
+  }
+  return false;
+}
+
+static bool is_nonzero_status(grpc_mdelem md) {
+  // If md.payload == GRPC_MDELEM_GRPC_STATUS_1 or GRPC_MDELEM_GRPC_STATUS_2,
+  // then we have seen an error. In fact, if it is a GRPC_STATUS and it's
+  // not equal to GRPC_MDELEM_GRPC_STATUS_0, then we have seen an error.
+  // TODO(ctiller): check for a status like " 0"
+  return md_key_cmp(md, GRPC_MDSTR_GRPC_STATUS) &&
+         !md_cmp(md, GRPC_MDELEM_GRPC_STATUS_0, GRPC_MDSTR_GRPC_STATUS);
+}
+
+static void GPR_ATTRIBUTE_NOINLINE on_initial_header_log(
+    grpc_chttp2_transport* t, grpc_chttp2_stream* s, grpc_mdelem md) {
+  char* key = grpc_slice_to_c_string(GRPC_MDKEY(md));
+  char* value =
+      grpc_dump_slice(GRPC_MDVALUE(md), GPR_DUMP_HEX | GPR_DUMP_ASCII);
+  gpr_log(GPR_INFO, "HTTP:%d:HDR:%s: %s: %s", s->id,
+          t->is_client ? "CLI" : "SVR", key, value);
+  gpr_free(key);
+  gpr_free(value);
+}
+
+static grpc_error* GPR_ATTRIBUTE_NOINLINE handle_timeout(grpc_chttp2_stream* s,
+                                                         grpc_mdelem md) {
+  grpc_millis* cached_timeout =
+      static_cast<grpc_millis*>(grpc_mdelem_get_user_data(md, free_timeout));
+  grpc_millis timeout;
+  if (cached_timeout != nullptr) {
+    timeout = *cached_timeout;
+  } else {
+    if (GPR_UNLIKELY(!grpc_http2_decode_timeout(GRPC_MDVALUE(md), &timeout))) {
+      char* val = grpc_slice_to_c_string(GRPC_MDVALUE(md));
+      gpr_log(GPR_ERROR, "Ignoring bad timeout value '%s'", val);
+      gpr_free(val);
+      timeout = GRPC_MILLIS_INF_FUTURE;
+    }
+    if (GRPC_MDELEM_IS_INTERNED(md)) {
+      /* store the result */
+      cached_timeout =
+          static_cast<grpc_millis*>(gpr_malloc(sizeof(grpc_millis)));
+      *cached_timeout = timeout;
+      grpc_mdelem_set_user_data(md, free_timeout, cached_timeout);
+    }
+  }
+  if (timeout != GRPC_MILLIS_INF_FUTURE) {
+    grpc_chttp2_incoming_metadata_buffer_set_deadline(
+        &s->metadata_buffer[0], grpc_core::ExecCtx::Get()->Now() + timeout);
+  }
+  GRPC_MDELEM_UNREF(md);
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* GPR_ATTRIBUTE_NOINLINE handle_metadata_size_limit_exceeded(
+    grpc_chttp2_transport* t, grpc_chttp2_stream* s, grpc_mdelem md,
+    size_t new_size, size_t metadata_size_limit) {
+  gpr_log(GPR_DEBUG,
+          "received initial metadata size exceeds limit (%" PRIuPTR
+          " vs. %" PRIuPTR ")",
+          new_size, metadata_size_limit);
+  grpc_chttp2_cancel_stream(
+      t, s,
+      grpc_error_set_int(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                             "received initial metadata size exceeds limit"),
+                         GRPC_ERROR_INT_GRPC_STATUS,
+                         GRPC_STATUS_RESOURCE_EXHAUSTED));
+  grpc_chttp2_parsing_become_skip_parser(t);
+  s->seen_error = true;
+  GRPC_MDELEM_UNREF(md);
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* GPR_ATTRIBUTE_NOINLINE
+handle_metadata_add_failure(grpc_chttp2_transport* t, grpc_chttp2_stream* s,
+                            grpc_mdelem md, grpc_error* error) {
+  grpc_chttp2_cancel_stream(t, s, error);
+  grpc_chttp2_parsing_become_skip_parser(t);
+  s->seen_error = true;
+  GRPC_MDELEM_UNREF(md);
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* on_initial_header(void* tp, grpc_mdelem md) {
+  GPR_TIMER_SCOPE("on_initial_header", 0);
+
+  grpc_chttp2_transport* t = static_cast<grpc_chttp2_transport*>(tp);
+  grpc_chttp2_stream* s = t->incoming_stream;
+  GPR_DEBUG_ASSERT(s != nullptr);
+
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
+    on_initial_header_log(t, s, md);
+  }
+
+  if (is_nonzero_status(md)) {  // not GRPC_MDELEM_GRPC_STATUS_0?
+    s->seen_error = true;
+  } else if (md_key_cmp(md, GRPC_MDSTR_GRPC_TIMEOUT)) {
+    return handle_timeout(s, md);
+  }
+
+  const size_t new_size = s->metadata_buffer[0].size + GRPC_MDELEM_LENGTH(md);
+  const size_t metadata_size_limit =
+      t->settings[GRPC_ACKED_SETTINGS]
+                 [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE];
+  if (GPR_UNLIKELY(new_size > metadata_size_limit)) {
+    return handle_metadata_size_limit_exceeded(t, s, md, new_size,
+                                               metadata_size_limit);
+  } else {
+    grpc_error* error =
+        grpc_chttp2_incoming_metadata_buffer_add(&s->metadata_buffer[0], md);
+    if (GPR_UNLIKELY(error != GRPC_ERROR_NONE)) {
+      return handle_metadata_add_failure(t, s, md, error);
+    }
+  }
+  // Not timeout-related metadata, and no error occurred.
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* on_trailing_header(void* tp, grpc_mdelem md) {
+  GPR_TIMER_SCOPE("on_trailing_header", 0);
+
+  grpc_chttp2_transport* t = static_cast<grpc_chttp2_transport*>(tp);
+  grpc_chttp2_stream* s = t->incoming_stream;
+  GPR_DEBUG_ASSERT(s != nullptr);
+
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
+    char* key = grpc_slice_to_c_string(GRPC_MDKEY(md));
+    char* value =
+        grpc_dump_slice(GRPC_MDVALUE(md), GPR_DUMP_HEX | GPR_DUMP_ASCII);
+    gpr_log(GPR_INFO, "HTTP:%d:TRL:%s: %s: %s", s->id,
+            t->is_client ? "CLI" : "SVR", key, value);
+    gpr_free(key);
+    gpr_free(value);
+  }
+
+  if (is_nonzero_status(md)) {  // not GRPC_MDELEM_GRPC_STATUS_0?
+    s->seen_error = true;
+  }
+
+  const size_t new_size = s->metadata_buffer[1].size + GRPC_MDELEM_LENGTH(md);
+  const size_t metadata_size_limit =
+      t->settings[GRPC_ACKED_SETTINGS]
+                 [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE];
+  if (new_size > metadata_size_limit) {
+    gpr_log(GPR_DEBUG,
+            "received trailing metadata size exceeds limit (%" PRIuPTR
+            " vs. %" PRIuPTR ")",
+            new_size, metadata_size_limit);
+    grpc_chttp2_cancel_stream(
+        t, s,
+        grpc_error_set_int(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                               "received trailing metadata size exceeds limit"),
+                           GRPC_ERROR_INT_GRPC_STATUS,
+                           GRPC_STATUS_RESOURCE_EXHAUSTED));
+    grpc_chttp2_parsing_become_skip_parser(t);
+    s->seen_error = true;
+    GRPC_MDELEM_UNREF(md);
+  } else {
+    grpc_error* error =
+        grpc_chttp2_incoming_metadata_buffer_add(&s->metadata_buffer[1], md);
+    if (error != GRPC_ERROR_NONE) {
+      grpc_chttp2_cancel_stream(t, s, error);
+      grpc_chttp2_parsing_become_skip_parser(t);
+      s->seen_error = true;
+      GRPC_MDELEM_UNREF(md);
+    }
+  }
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* init_header_frame_parser(grpc_chttp2_transport* t,
+                                            int is_continuation) {
+  uint8_t is_eoh =
+      (t->incoming_frame_flags & GRPC_CHTTP2_DATA_FLAG_END_HEADERS) != 0;
+  grpc_chttp2_stream* s;
+
+  /* TODO(ctiller): when to increment header_frames_received? */
+
+  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;
+  }
+
+  t->ping_state.last_ping_sent_time = GRPC_MILLIS_INF_PAST;
+
+  /* could be a new grpc_chttp2_stream or an existing grpc_chttp2_stream */
+  s = grpc_chttp2_parsing_lookup_stream(t, t->incoming_stream_id);
+  if (s == nullptr) {
+    if (GPR_UNLIKELY(is_continuation)) {
+      GRPC_CHTTP2_IF_TRACING(
+          gpr_log(GPR_ERROR,
+                  "grpc_chttp2_stream disbanded before CONTINUATION received"));
+      return init_skip_frame_parser(t, 1);
+    }
+    if (t->is_client) {
+      if (GPR_LIKELY((t->incoming_stream_id & 1) &&
+                     t->incoming_stream_id < t->next_stream_id)) {
+        /* this is an old (probably cancelled) grpc_chttp2_stream */
+      } else {
+        GRPC_CHTTP2_IF_TRACING(gpr_log(
+            GPR_ERROR, "ignoring new grpc_chttp2_stream creation on client"));
+      }
+      grpc_error* err = init_skip_frame_parser(t, 1);
+      if (t->incoming_frame_flags & GRPC_CHTTP2_FLAG_HAS_PRIORITY) {
+        grpc_chttp2_hpack_parser_set_has_priority(&t->hpack_parser);
+      }
+      return err;
+    } else if (GPR_UNLIKELY(t->last_new_stream_id >= t->incoming_stream_id)) {
+      GRPC_CHTTP2_IF_TRACING(gpr_log(
+          GPR_ERROR,
+          "ignoring out of order new grpc_chttp2_stream request on server; "
+          "last grpc_chttp2_stream "
+          "id=%d, new grpc_chttp2_stream id=%d",
+          t->last_new_stream_id, t->incoming_stream_id));
+      return init_skip_frame_parser(t, 1);
+    } else if (GPR_UNLIKELY((t->incoming_stream_id & 1) == 0)) {
+      GRPC_CHTTP2_IF_TRACING(gpr_log(
+          GPR_ERROR,
+          "ignoring grpc_chttp2_stream with non-client generated index %d",
+          t->incoming_stream_id));
+      return init_skip_frame_parser(t, 1);
+    } else if (GPR_UNLIKELY(
+                   grpc_chttp2_stream_map_size(&t->stream_map) >=
+                   t->settings[GRPC_ACKED_SETTINGS]
+                              [GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS])) {
+      return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Max stream count exceeded");
+    }
+    t->last_new_stream_id = t->incoming_stream_id;
+    s = t->incoming_stream =
+        grpc_chttp2_parsing_accept_stream(t, t->incoming_stream_id);
+    if (GPR_UNLIKELY(s == nullptr)) {
+      GRPC_CHTTP2_IF_TRACING(
+          gpr_log(GPR_ERROR, "grpc_chttp2_stream not accepted"));
+      return init_skip_frame_parser(t, 1);
+    }
+    if (t->channelz_socket != nullptr) {
+      t->channelz_socket->RecordStreamStartedFromRemote();
+    }
+  } else {
+    t->incoming_stream = s;
+  }
+  GPR_DEBUG_ASSERT(s != nullptr);
+  s->stats.incoming.framing_bytes += 9;
+  if (GPR_UNLIKELY(s->read_closed)) {
+    GRPC_CHTTP2_IF_TRACING(gpr_log(
+        GPR_ERROR, "skipping already closed grpc_chttp2_stream header"));
+    t->incoming_stream = nullptr;
+    return init_skip_frame_parser(t, 1);
+  }
+  t->parser = grpc_chttp2_header_parser_parse;
+  t->parser_data = &t->hpack_parser;
+  if (t->header_eof) {
+    s->eos_received = true;
+  }
+  switch (s->header_frames_received) {
+    case 0:
+      if (t->is_client && t->header_eof) {
+        GRPC_CHTTP2_IF_TRACING(gpr_log(GPR_INFO, "parsing Trailers-Only"));
+        if (s->trailing_metadata_available != nullptr) {
+          *s->trailing_metadata_available = true;
+        }
+        t->hpack_parser.on_header = on_trailing_header;
+      } else {
+        GRPC_CHTTP2_IF_TRACING(gpr_log(GPR_INFO, "parsing initial_metadata"));
+        t->hpack_parser.on_header = on_initial_header;
+      }
+      break;
+    case 1:
+      GRPC_CHTTP2_IF_TRACING(gpr_log(GPR_INFO, "parsing trailing_metadata"));
+      t->hpack_parser.on_header = on_trailing_header;
+      break;
+    case 2:
+      gpr_log(GPR_ERROR, "too many header frames received");
+      return init_skip_frame_parser(t, 1);
+  }
+  t->hpack_parser.on_header_user_data = t;
+  t->hpack_parser.is_boundary = is_eoh;
+  t->hpack_parser.is_eof = static_cast<uint8_t>(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 GRPC_ERROR_NONE;
+}
+
+static grpc_error* init_window_update_frame_parser(grpc_chttp2_transport* t) {
+  grpc_error* err = grpc_chttp2_window_update_parser_begin_frame(
+      &t->simple.window_update, t->incoming_frame_size,
+      t->incoming_frame_flags);
+  if (err != GRPC_ERROR_NONE) return err;
+  if (t->incoming_stream_id != 0) {
+    grpc_chttp2_stream* s = t->incoming_stream =
+        grpc_chttp2_parsing_lookup_stream(t, t->incoming_stream_id);
+    if (s == nullptr) {
+      return init_skip_frame_parser(t, 0);
+    }
+    s->stats.incoming.framing_bytes += 9;
+  }
+  t->parser = grpc_chttp2_window_update_parser_parse;
+  t->parser_data = &t->simple.window_update;
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* init_ping_parser(grpc_chttp2_transport* t) {
+  grpc_error* err = grpc_chttp2_ping_parser_begin_frame(
+      &t->simple.ping, t->incoming_frame_size, t->incoming_frame_flags);
+  if (err != GRPC_ERROR_NONE) return err;
+  t->parser = grpc_chttp2_ping_parser_parse;
+  t->parser_data = &t->simple.ping;
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* init_rst_stream_parser(grpc_chttp2_transport* t) {
+  grpc_error* err = grpc_chttp2_rst_stream_parser_begin_frame(
+      &t->simple.rst_stream, t->incoming_frame_size, t->incoming_frame_flags);
+  if (err != GRPC_ERROR_NONE) return err;
+  grpc_chttp2_stream* s = t->incoming_stream =
+      grpc_chttp2_parsing_lookup_stream(t, t->incoming_stream_id);
+  if (!t->incoming_stream) {
+    return init_skip_frame_parser(t, 0);
+  }
+  s->stats.incoming.framing_bytes += 9;
+  t->parser = grpc_chttp2_rst_stream_parser_parse;
+  t->parser_data = &t->simple.rst_stream;
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* init_goaway_parser(grpc_chttp2_transport* t) {
+  grpc_error* err = grpc_chttp2_goaway_parser_begin_frame(
+      &t->goaway_parser, t->incoming_frame_size, t->incoming_frame_flags);
+  if (err != GRPC_ERROR_NONE) return err;
+  t->parser = grpc_chttp2_goaway_parser_parse;
+  t->parser_data = &t->goaway_parser;
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* init_settings_frame_parser(grpc_chttp2_transport* t) {
+  if (t->incoming_stream_id != 0) {
+    return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "Settings frame received for grpc_chttp2_stream");
+  }
+
+  grpc_error* err = grpc_chttp2_settings_parser_begin_frame(
+      &t->simple.settings, t->incoming_frame_size, t->incoming_frame_flags,
+      t->settings[GRPC_PEER_SETTINGS]);
+  if (err != GRPC_ERROR_NONE) {
+    return err;
+  }
+  if (t->incoming_frame_flags & GRPC_CHTTP2_FLAG_ACK) {
+    memcpy(t->settings[GRPC_ACKED_SETTINGS], t->settings[GRPC_SENT_SETTINGS],
+           GRPC_CHTTP2_NUM_SETTINGS * sizeof(uint32_t));
+    grpc_chttp2_hptbl_set_max_bytes(
+        &t->hpack_parser.table,
+        t->settings[GRPC_ACKED_SETTINGS]
+                   [GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE]);
+    t->sent_local_settings = 0;
+  }
+  t->parser = grpc_chttp2_settings_parser_parse;
+  t->parser_data = &t->simple.settings;
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* parse_frame_slice(grpc_chttp2_transport* t,
+                                     const grpc_slice& slice, int is_last) {
+  grpc_chttp2_stream* s = t->incoming_stream;
+  grpc_error* err = t->parser(t->parser_data, t, s, slice, is_last);
+  intptr_t unused;
+  if (GPR_LIKELY(err == GRPC_ERROR_NONE)) {
+    return err;
+  } else if (grpc_error_get_int(err, GRPC_ERROR_INT_STREAM_ID, &unused)) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
+      const char* msg = grpc_error_string(err);
+      gpr_log(GPR_ERROR, "%s", msg);
+    }
+    grpc_chttp2_parsing_become_skip_parser(t);
+    if (s) {
+      s->forced_close_error = err;
+      grpc_chttp2_add_rst_stream_to_next_write(t, t->incoming_stream_id,
+                                               GRPC_HTTP2_PROTOCOL_ERROR,
+                                               &s->stats.outgoing);
+    } else {
+      GRPC_ERROR_UNREF(err);
+    }
+  }
+  return err;
+}