From 781bab5399e91c99b70af3ec242c321585d22c07 Mon Sep 17 00:00:00 2001
From: Craig Tiller <ctiller@google.com>
Date: Thu, 5 May 2016 08:15:28 -0700
Subject: [PATCH] Error handling progress in chttp2 transport

---
 .../chttp2/transport/chttp2_transport.c       |  85 ++-
 .../transport/chttp2/transport/frame_data.c   |  58 +-
 .../transport/chttp2/transport/frame_data.h   |  11 +-
 .../transport/chttp2/transport/frame_goaway.c |  37 +-
 .../transport/chttp2/transport/frame_goaway.h |   4 +-
 .../transport/chttp2/transport/frame_ping.c   |  19 +-
 .../transport/chttp2/transport/frame_ping.h   |   6 +-
 .../chttp2/transport/frame_rst_stream.c       |  19 +-
 .../chttp2/transport/frame_rst_stream.h       |   4 +-
 .../chttp2/transport/frame_settings.c         |  44 +-
 .../chttp2/transport/frame_settings.h         |   4 +-
 .../chttp2/transport/frame_window_update.c    |  26 +-
 .../chttp2/transport/frame_window_update.h    |   4 +-
 .../transport/chttp2/transport/hpack_parser.c | 632 ++++++++++--------
 .../transport/chttp2/transport/hpack_parser.h |  14 +-
 .../transport/chttp2/transport/hpack_table.c  |  44 +-
 .../transport/chttp2/transport/hpack_table.h  |   9 +-
 .../ext/transport/chttp2/transport/internal.h |   7 +-
 src/core/lib/iomgr/closure.c                  |  14 +-
 src/core/lib/iomgr/closure.h                  |   8 +-
 src/core/lib/iomgr/error.c                    |  16 +
 src/core/lib/iomgr/error.h                    |   7 +
 src/core/lib/iomgr/exec_ctx.c                 |   4 +-
 23 files changed, 615 insertions(+), 461 deletions(-)

diff --git a/src/core/ext/transport/chttp2/transport/chttp2_transport.c b/src/core/ext/transport/chttp2/transport/chttp2_transport.c
index d49c56ab63..25d37711e5 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_transport.c
+++ b/src/core/ext/transport/chttp2/transport/chttp2_transport.c
@@ -142,7 +142,8 @@ static void incoming_byte_stream_destroy_locked(grpc_exec_ctx *exec_ctx,
                                                 grpc_chttp2_stream *s,
                                                 void *byte_stream);
 static void fail_pending_writes(grpc_exec_ctx *exec_ctx,
-                                grpc_chttp2_stream_global *stream_global);
+                                grpc_chttp2_stream_global *stream_global,
+                                grpc_error *error);
 
 /*******************************************************************************
  * CONSTRUCTION/DESTRUCTION/REFCOUNTING
@@ -746,7 +747,7 @@ static void terminate_writing_with_lock(grpc_exec_ctx *exec_ctx,
   grpc_chttp2_stream_global *stream_global;
   while (grpc_chttp2_list_pop_closed_waiting_for_writing(&t->global,
                                                          &stream_global)) {
-    fail_pending_writes(exec_ctx, stream_global);
+    fail_pending_writes(exec_ctx, stream_global, grpc_error_ref(error));
     GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "finish_writes");
   }
 
@@ -844,34 +845,36 @@ static void maybe_start_some_streams(
 }
 
 #define CLOSURE_BARRIER_STATS_BIT (1 << 0)
-#define CLOSURE_BARRIER_FAILURE_BIT (1 << 1)
 #define CLOSURE_BARRIER_FIRST_REF_BIT (1 << 16)
 
 static grpc_closure *add_closure_barrier(grpc_closure *closure) {
-  closure->final_data.scratch += CLOSURE_BARRIER_FIRST_REF_BIT;
+  closure->next_data.scratch += CLOSURE_BARRIER_FIRST_REF_BIT;
   return closure;
 }
 
 void grpc_chttp2_complete_closure_step(grpc_exec_ctx *exec_ctx,
                                        grpc_chttp2_stream_global *stream_global,
-                                       grpc_closure **pclosure, int success) {
+                                       grpc_closure **pclosure,
+                                       grpc_error *error) {
   grpc_closure *closure = *pclosure;
   if (closure == NULL) {
     return;
   }
-  closure->final_data.scratch -= CLOSURE_BARRIER_FIRST_REF_BIT;
-  if (!success) {
-    closure->final_data.scratch |= CLOSURE_BARRIER_FAILURE_BIT;
+  closure->next_data.scratch -= CLOSURE_BARRIER_FIRST_REF_BIT;
+  if (error != GRPC_ERROR_NONE) {
+    if (closure->error == GRPC_ERROR_NONE) {
+      closure->error =
+          GRPC_ERROR_CREATE("Error in HTTP transport completing operation");
+    }
+    closure->error = grpc_error_add_child(closure->error, error);
   }
-  if (closure->final_data.scratch < CLOSURE_BARRIER_FIRST_REF_BIT) {
-    if (closure->final_data.scratch & CLOSURE_BARRIER_STATS_BIT) {
+  if (closure->next_data.scratch < CLOSURE_BARRIER_FIRST_REF_BIT) {
+    if (closure->next_data.scratch & CLOSURE_BARRIER_STATS_BIT) {
       grpc_transport_move_stats(&stream_global->stats,
                                 stream_global->collecting_stats);
       stream_global->collecting_stats = NULL;
     }
-    grpc_exec_ctx_push(
-        exec_ctx, closure,
-        (closure->final_data.scratch & CLOSURE_BARRIER_FAILURE_BIT) == 0, NULL);
+    grpc_exec_ctx_push(exec_ctx, closure, closure->error, NULL);
   }
   *pclosure = NULL;
 }
@@ -906,12 +909,12 @@ static void perform_stream_op_locked(grpc_exec_ctx *exec_ctx,
   }
   /* use final_data as a barrier until enqueue time; the inital counter is
      dropped at the end of this function */
-  on_complete->final_data.scratch = CLOSURE_BARRIER_FIRST_REF_BIT;
+  on_complete->next_data.scratch = CLOSURE_BARRIER_FIRST_REF_BIT;
 
   if (op->collect_stats != NULL) {
     GPR_ASSERT(stream_global->collecting_stats == NULL);
     stream_global->collecting_stats = op->collect_stats;
-    on_complete->final_data.scratch |= CLOSURE_BARRIER_STATS_BIT;
+    on_complete->next_data.scratch |= CLOSURE_BARRIER_STATS_BIT;
   }
 
   if (op->cancel_with_status != GRPC_STATUS_OK) {
@@ -946,7 +949,9 @@ static void perform_stream_op_locked(grpc_exec_ctx *exec_ctx,
     } else {
       grpc_chttp2_complete_closure_step(
           exec_ctx, stream_global,
-          &stream_global->send_initial_metadata_finished, 0);
+          &stream_global->send_initial_metadata_finished,
+          GRPC_ERROR_CREATE(
+              "Attempt to send initial metadata after stream was closed"));
     }
   }
 
@@ -956,7 +961,8 @@ static void perform_stream_op_locked(grpc_exec_ctx *exec_ctx,
     stream_global->send_message_finished = add_closure_barrier(on_complete);
     if (stream_global->write_closed) {
       grpc_chttp2_complete_closure_step(
-          exec_ctx, stream_global, &stream_global->send_message_finished, 0);
+          exec_ctx, stream_global, &stream_global->send_message_finished,
+          GRPC_ERROR_CREATE("Attempt to send message after stream was closed"));
     } else {
       stream_global->send_message = op->send_message;
       if (stream_global->id != 0) {
@@ -978,7 +984,10 @@ static void perform_stream_op_locked(grpc_exec_ctx *exec_ctx,
       grpc_chttp2_complete_closure_step(
           exec_ctx, stream_global,
           &stream_global->send_trailing_metadata_finished,
-          grpc_metadata_batch_is_empty(op->send_trailing_metadata));
+          grpc_metadata_batch_is_empty(op->send_trailing_metadata)
+              ? GRPC_ERROR_NONE
+              : GRPC_ERROR_CREATE("Attempt to send trailing metadata after "
+                                  "stream was closed"));
     } else if (stream_global->id != 0) {
       /* TODO(ctiller): check if there's flow control for any outstanding
          bytes before going writable */
@@ -1016,7 +1025,8 @@ static void perform_stream_op_locked(grpc_exec_ctx *exec_ctx,
     grpc_chttp2_list_add_check_read_ops(transport_global, stream_global);
   }
 
-  grpc_chttp2_complete_closure_step(exec_ctx, stream_global, &on_complete, 1);
+  grpc_chttp2_complete_closure_step(exec_ctx, stream_global, &on_complete,
+                                    GRPC_ERROR_NONE);
 
   GPR_TIMER_END("perform_stream_op_locked", 0);
 }
@@ -1185,7 +1195,7 @@ static void check_read_ops(grpc_exec_ctx *exec_ctx,
             stream_global->recv_trailing_metadata);
         grpc_chttp2_complete_closure_step(
             exec_ctx, stream_global,
-            &stream_global->recv_trailing_metadata_finished, 1);
+            &stream_global->recv_trailing_metadata_finished, GRPC_ERROR_NONE);
       }
     }
   }
@@ -1256,8 +1266,10 @@ static void cancel_from_api(grpc_exec_ctx *exec_ctx,
     stream_global->seen_error = 1;
     grpc_chttp2_list_add_check_read_ops(transport_global, stream_global);
   }
-  grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global, 1,
-                                 1);
+  grpc_chttp2_mark_stream_closed(
+      exec_ctx, transport_global, stream_global, 1, 1,
+      grpc_error_set_int(GRPC_ERROR_CREATE("Cancelled"),
+                         GRPC_ERROR_INT_GRPC_STATUS, status));
 }
 
 void grpc_chttp2_fake_status(grpc_exec_ctx *exec_ctx,
@@ -1298,23 +1310,25 @@ void grpc_chttp2_fake_status(grpc_exec_ctx *exec_ctx,
 }
 
 static void fail_pending_writes(grpc_exec_ctx *exec_ctx,
-                                grpc_chttp2_stream_global *stream_global) {
+                                grpc_chttp2_stream_global *stream_global,
+                                grpc_error *error) {
   grpc_chttp2_complete_closure_step(
       exec_ctx, stream_global, &stream_global->send_initial_metadata_finished,
-      0);
+      grpc_error_ref(error));
   grpc_chttp2_complete_closure_step(
       exec_ctx, stream_global, &stream_global->send_trailing_metadata_finished,
-      0);
-  grpc_chttp2_complete_closure_step(exec_ctx, stream_global,
-                                    &stream_global->send_message_finished, 0);
+      grpc_error_ref(error));
+  grpc_chttp2_complete_closure_step(
+      exec_ctx, stream_global, &stream_global->send_message_finished, error);
 }
 
 void grpc_chttp2_mark_stream_closed(
     grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global, int close_reads,
-    int close_writes) {
+    grpc_chttp2_stream_global *stream_global, int close_reads, int close_writes,
+    grpc_error *error) {
   if (stream_global->read_closed && stream_global->write_closed) {
     /* already closed */
+    grpc_error_unref(error);
     return;
   }
   grpc_chttp2_list_add_check_read_ops(transport_global, stream_global);
@@ -1331,7 +1345,7 @@ void grpc_chttp2_mark_stream_closed(
       grpc_chttp2_list_add_closed_waiting_for_writing(transport_global,
                                                       stream_global);
     } else {
-      fail_pending_writes(exec_ctx, stream_global);
+      fail_pending_writes(exec_ctx, stream_global, grpc_error_ref(error));
     }
   }
   if (stream_global->read_closed && stream_global->write_closed) {
@@ -1347,6 +1361,7 @@ void grpc_chttp2_mark_stream_closed(
       GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2");
     }
   }
+  grpc_error_unref(error);
 }
 
 static void close_from_api(grpc_exec_ctx *exec_ctx,
@@ -1451,8 +1466,16 @@ static void close_from_api(grpc_exec_ctx *exec_ctx,
   }
   grpc_chttp2_fake_status(exec_ctx, transport_global, stream_global, status,
                           optional_message);
+  grpc_error *err = GRPC_ERROR_CREATE("Stream closed");
+  err = grpc_error_set_int(err, GRPC_ERROR_INT_GRPC_STATUS, status);
+  if (optional_message) {
+    char *str =
+        gpr_dump_slice(*optional_message, GPR_DUMP_HEX | GPR_DUMP_ASCII);
+    err = grpc_error_set_str(err, GRPC_ERROR_STR_GRPC_MESSAGE, str);
+    gpr_free(str);
+  }
   grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global, 1,
-                                 1);
+                                 1, err);
 }
 
 static void cancel_stream_cb(grpc_chttp2_transport_global *transport_global,
diff --git a/src/core/ext/transport/chttp2/transport/frame_data.c b/src/core/ext/transport/chttp2/transport/frame_data.c
index 3a6d80e0a3..828d0427ac 100644
--- a/src/core/ext/transport/chttp2/transport/frame_data.c
+++ b/src/core/ext/transport/chttp2/transport/frame_data.c
@@ -37,16 +37,16 @@
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
 #include <grpc/support/useful.h>
 #include "src/core/ext/transport/chttp2/transport/internal.h"
 #include "src/core/lib/support/string.h"
 #include "src/core/lib/transport/transport.h"
 
-grpc_chttp2_parse_error grpc_chttp2_data_parser_init(
-    grpc_chttp2_data_parser *parser) {
+grpc_error *grpc_chttp2_data_parser_init(grpc_chttp2_data_parser *parser) {
   parser->state = GRPC_CHTTP2_DATA_FH_0;
   parser->parsing_frame = NULL;
-  return GRPC_CHTTP2_PARSE_OK;
+  return GRPC_ERROR_NONE;
 }
 
 void grpc_chttp2_data_parser_destroy(grpc_exec_ctx *exec_ctx,
@@ -62,11 +62,16 @@ void grpc_chttp2_data_parser_destroy(grpc_exec_ctx *exec_ctx,
   }
 }
 
-grpc_chttp2_parse_error grpc_chttp2_data_parser_begin_frame(
-    grpc_chttp2_data_parser *parser, uint8_t flags) {
+grpc_error *grpc_chttp2_data_parser_begin_frame(grpc_chttp2_data_parser *parser,
+                                                uint8_t flags,
+                                                uint32_t stream_id) {
   if (flags & ~GRPC_CHTTP2_DATA_FLAG_END_STREAM) {
-    gpr_log(GPR_ERROR, "unsupported data flags: 0x%02x", flags);
-    return GRPC_CHTTP2_STREAM_ERROR;
+    char *msg;
+    gpr_asprintf(&msg, "unsupported data flags: 0x%02x", flags);
+    grpc_error *err = grpc_error_set_int(GRPC_ERROR_CREATE(msg),
+                                         GRPC_ERROR_INT_STREAM_ID, stream_id);
+    gpr_free(msg);
+    return err;
   }
 
   if (flags & GRPC_CHTTP2_DATA_FLAG_END_STREAM) {
@@ -75,7 +80,7 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_begin_frame(
     parser->is_last_frame = 0;
   }
 
-  return GRPC_CHTTP2_PARSE_OK;
+  return GRPC_ERROR_NONE;
 }
 
 void grpc_chttp2_incoming_frame_queue_merge(
@@ -139,7 +144,7 @@ void grpc_chttp2_encode_data(uint32_t id, gpr_slice_buffer *inbuf,
   stats->data_bytes += write_bytes;
 }
 
-grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
+grpc_error *grpc_chttp2_data_parser_parse(
     grpc_exec_ctx *exec_ctx, void *parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
@@ -149,19 +154,20 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
   grpc_chttp2_data_parser *p = parser;
   uint32_t message_flags;
   grpc_chttp2_incoming_byte_stream *incoming_byte_stream;
+  char *msg;
 
   if (is_last && p->is_last_frame) {
     stream_parsing->received_close = 1;
   }
 
   if (cur == end) {
-    return GRPC_CHTTP2_PARSE_OK;
+    return GRPC_ERROR_NONE;
   }
 
   switch (p->state) {
     case GRPC_CHTTP2_DATA_ERROR:
       p->state = GRPC_CHTTP2_DATA_ERROR;
-      return GRPC_CHTTP2_STREAM_ERROR;
+      return grpc_error_ref(p->error);
     fh_0:
     case GRPC_CHTTP2_DATA_FH_0:
       stream_parsing->stats.incoming.framing_bytes++;
@@ -174,13 +180,23 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
           p->is_frame_compressed = 1; /* GPR_TRUE */
           break;
         default:
-          gpr_log(GPR_ERROR, "Bad GRPC frame type 0x%02x", p->frame_type);
+          gpr_asprintf(&msg, "Bad GRPC frame type 0x%02x", p->frame_type);
+          p->error = GRPC_ERROR_CREATE(msg);
+          p->error = grpc_error_set_int(p->error, GRPC_ERROR_INT_STREAM_ID,
+                                        stream_parsing->id);
+          gpr_free(msg);
+          msg = gpr_dump_slice(slice, GPR_DUMP_HEX | GPR_DUMP_ASCII);
+          p->error =
+              grpc_error_set_str(p->error, GRPC_ERROR_STR_RAW_BYTES, msg);
+          gpr_free(msg);
+          p->error =
+              grpc_error_set_int(p->error, GRPC_ERROR_INT_OFFSET, cur - beg);
           p->state = GRPC_CHTTP2_DATA_ERROR;
-          return GRPC_CHTTP2_STREAM_ERROR;
+          return grpc_error_ref(p->error);
       }
       if (++cur == end) {
         p->state = GRPC_CHTTP2_DATA_FH_1;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
     /* fallthrough */
     case GRPC_CHTTP2_DATA_FH_1:
@@ -188,7 +204,7 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
       p->frame_size = ((uint32_t)*cur) << 24;
       if (++cur == end) {
         p->state = GRPC_CHTTP2_DATA_FH_2;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
     /* fallthrough */
     case GRPC_CHTTP2_DATA_FH_2:
@@ -196,7 +212,7 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
       p->frame_size |= ((uint32_t)*cur) << 16;
       if (++cur == end) {
         p->state = GRPC_CHTTP2_DATA_FH_3;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
     /* fallthrough */
     case GRPC_CHTTP2_DATA_FH_3:
@@ -204,7 +220,7 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
       p->frame_size |= ((uint32_t)*cur) << 8;
       if (++cur == end) {
         p->state = GRPC_CHTTP2_DATA_FH_4;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
     /* fallthrough */
     case GRPC_CHTTP2_DATA_FH_4:
@@ -225,7 +241,7 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
       grpc_chttp2_list_add_parsing_seen_stream(transport_parsing,
                                                stream_parsing);
       if (cur == end) {
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
       uint32_t remaining = (uint32_t)(end - cur);
       if (remaining == p->frame_size) {
@@ -237,7 +253,7 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
                                                   1);
         p->parsing_frame = NULL;
         p->state = GRPC_CHTTP2_DATA_FH_0;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       } else if (remaining > p->frame_size) {
         stream_parsing->stats.incoming.data_bytes += p->frame_size;
         grpc_chttp2_incoming_byte_stream_push(
@@ -256,9 +272,9 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
             gpr_slice_sub(slice, (size_t)(cur - beg), (size_t)(end - beg)));
         p->frame_size -= remaining;
         stream_parsing->stats.incoming.data_bytes += remaining;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
   }
 
-  GPR_UNREACHABLE_CODE(return GRPC_CHTTP2_CONNECTION_ERROR);
+  GPR_UNREACHABLE_CODE(return GRPC_ERROR_CREATE("Should never reach here"));
 }
diff --git a/src/core/ext/transport/chttp2/transport/frame_data.h b/src/core/ext/transport/chttp2/transport/frame_data.h
index af71f483a2..a21a7942b9 100644
--- a/src/core/ext/transport/chttp2/transport/frame_data.h
+++ b/src/core/ext/transport/chttp2/transport/frame_data.h
@@ -66,6 +66,7 @@ typedef struct {
   uint8_t is_last_frame;
   uint8_t frame_type;
   uint32_t frame_size;
+  grpc_error *error;
 
   int is_frame_compressed;
   grpc_chttp2_incoming_frame_queue incoming_frames;
@@ -79,19 +80,19 @@ grpc_byte_stream *grpc_chttp2_incoming_frame_queue_pop(
     grpc_chttp2_incoming_frame_queue *q);
 
 /* initialize per-stream state for data frame parsing */
-grpc_chttp2_parse_error grpc_chttp2_data_parser_init(
-    grpc_chttp2_data_parser *parser);
+grpc_error *grpc_chttp2_data_parser_init(grpc_chttp2_data_parser *parser);
 
 void grpc_chttp2_data_parser_destroy(grpc_exec_ctx *exec_ctx,
                                      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, uint8_t flags);
+grpc_error *grpc_chttp2_data_parser_begin_frame(grpc_chttp2_data_parser *parser,
+                                                uint8_t flags,
+                                                uint32_t stream_id);
 
 /* 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(
+grpc_error *grpc_chttp2_data_parser_parse(
     grpc_exec_ctx *exec_ctx, void *parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
diff --git a/src/core/ext/transport/chttp2/transport/frame_goaway.c b/src/core/ext/transport/chttp2/transport/frame_goaway.c
index 69accb7696..c985169f51 100644
--- a/src/core/ext/transport/chttp2/transport/frame_goaway.c
+++ b/src/core/ext/transport/chttp2/transport/frame_goaway.c
@@ -38,6 +38,7 @@
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
 
 void grpc_chttp2_goaway_parser_init(grpc_chttp2_goaway_parser *p) {
   p->debug_data = NULL;
@@ -47,11 +48,15 @@ void grpc_chttp2_goaway_parser_destroy(grpc_chttp2_goaway_parser *p) {
   gpr_free(p->debug_data);
 }
 
-grpc_chttp2_parse_error grpc_chttp2_goaway_parser_begin_frame(
-    grpc_chttp2_goaway_parser *p, uint32_t length, uint8_t flags) {
+grpc_error *grpc_chttp2_goaway_parser_begin_frame(grpc_chttp2_goaway_parser *p,
+                                                  uint32_t length,
+                                                  uint8_t flags) {
   if (length < 8) {
-    gpr_log(GPR_ERROR, "goaway frame too short (%d bytes)", length);
-    return GRPC_CHTTP2_CONNECTION_ERROR;
+    char *msg;
+    gpr_asprintf(&msg, "goaway frame too short (%d bytes)", length);
+    grpc_error *err = GRPC_ERROR_CREATE(msg);
+    gpr_free(msg);
+    return err;
   }
 
   gpr_free(p->debug_data);
@@ -59,10 +64,10 @@ grpc_chttp2_parse_error grpc_chttp2_goaway_parser_begin_frame(
   p->debug_data = gpr_malloc(p->debug_length);
   p->debug_pos = 0;
   p->state = GRPC_CHTTP2_GOAWAY_LSI0;
-  return GRPC_CHTTP2_PARSE_OK;
+  return GRPC_ERROR_NONE;
 }
 
-grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
+grpc_error *grpc_chttp2_goaway_parser_parse(
     grpc_exec_ctx *exec_ctx, void *parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
@@ -75,7 +80,7 @@ grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
     case GRPC_CHTTP2_GOAWAY_LSI0:
       if (cur == end) {
         p->state = GRPC_CHTTP2_GOAWAY_LSI0;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
       p->last_stream_id = ((uint32_t)*cur) << 24;
       ++cur;
@@ -83,7 +88,7 @@ grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
     case GRPC_CHTTP2_GOAWAY_LSI1:
       if (cur == end) {
         p->state = GRPC_CHTTP2_GOAWAY_LSI1;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
       p->last_stream_id |= ((uint32_t)*cur) << 16;
       ++cur;
@@ -91,7 +96,7 @@ grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
     case GRPC_CHTTP2_GOAWAY_LSI2:
       if (cur == end) {
         p->state = GRPC_CHTTP2_GOAWAY_LSI2;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
       p->last_stream_id |= ((uint32_t)*cur) << 8;
       ++cur;
@@ -99,7 +104,7 @@ grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
     case GRPC_CHTTP2_GOAWAY_LSI3:
       if (cur == end) {
         p->state = GRPC_CHTTP2_GOAWAY_LSI3;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
       p->last_stream_id |= ((uint32_t)*cur);
       ++cur;
@@ -107,7 +112,7 @@ grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
     case GRPC_CHTTP2_GOAWAY_ERR0:
       if (cur == end) {
         p->state = GRPC_CHTTP2_GOAWAY_ERR0;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
       p->error_code = ((uint32_t)*cur) << 24;
       ++cur;
@@ -115,7 +120,7 @@ grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
     case GRPC_CHTTP2_GOAWAY_ERR1:
       if (cur == end) {
         p->state = GRPC_CHTTP2_GOAWAY_ERR1;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
       p->error_code |= ((uint32_t)*cur) << 16;
       ++cur;
@@ -123,7 +128,7 @@ grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
     case GRPC_CHTTP2_GOAWAY_ERR2:
       if (cur == end) {
         p->state = GRPC_CHTTP2_GOAWAY_ERR2;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
       p->error_code |= ((uint32_t)*cur) << 8;
       ++cur;
@@ -131,7 +136,7 @@ grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
     case GRPC_CHTTP2_GOAWAY_ERR3:
       if (cur == end) {
         p->state = GRPC_CHTTP2_GOAWAY_ERR3;
-        return GRPC_CHTTP2_PARSE_OK;
+        return GRPC_ERROR_NONE;
       }
       p->error_code |= ((uint32_t)*cur);
       ++cur;
@@ -150,9 +155,9 @@ grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
             gpr_slice_new(p->debug_data, p->debug_length, gpr_free);
         p->debug_data = NULL;
       }
-      return GRPC_CHTTP2_PARSE_OK;
+      return GRPC_ERROR_NONE;
   }
-  GPR_UNREACHABLE_CODE(return GRPC_CHTTP2_CONNECTION_ERROR);
+  GPR_UNREACHABLE_CODE(return GRPC_ERROR_CREATE("Should never reach here"));
 }
 
 void grpc_chttp2_goaway_append(uint32_t last_stream_id, uint32_t error_code,
diff --git a/src/core/ext/transport/chttp2/transport/frame_goaway.h b/src/core/ext/transport/chttp2/transport/frame_goaway.h
index 7c38b26a39..eb4303405a 100644
--- a/src/core/ext/transport/chttp2/transport/frame_goaway.h
+++ b/src/core/ext/transport/chttp2/transport/frame_goaway.h
@@ -63,9 +63,9 @@ typedef struct {
 
 void grpc_chttp2_goaway_parser_init(grpc_chttp2_goaway_parser *p);
 void grpc_chttp2_goaway_parser_destroy(grpc_chttp2_goaway_parser *p);
-grpc_chttp2_parse_error grpc_chttp2_goaway_parser_begin_frame(
+grpc_error *grpc_chttp2_goaway_parser_begin_frame(
     grpc_chttp2_goaway_parser *parser, uint32_t length, uint8_t flags);
-grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
+grpc_error *grpc_chttp2_goaway_parser_parse(
     grpc_exec_ctx *exec_ctx, void *parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
diff --git a/src/core/ext/transport/chttp2/transport/frame_ping.c b/src/core/ext/transport/chttp2/transport/frame_ping.c
index 7e1815f0fe..1f814ab1bd 100644
--- a/src/core/ext/transport/chttp2/transport/frame_ping.c
+++ b/src/core/ext/transport/chttp2/transport/frame_ping.c
@@ -38,6 +38,7 @@
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
 
 gpr_slice grpc_chttp2_ping_create(uint8_t ack, uint8_t *opaque_8bytes) {
   gpr_slice slice = gpr_slice_malloc(9 + 8);
@@ -57,18 +58,22 @@ gpr_slice grpc_chttp2_ping_create(uint8_t ack, uint8_t *opaque_8bytes) {
   return slice;
 }
 
-grpc_chttp2_parse_error grpc_chttp2_ping_parser_begin_frame(
-    grpc_chttp2_ping_parser *parser, uint32_t length, uint8_t flags) {
+grpc_error *grpc_chttp2_ping_parser_begin_frame(grpc_chttp2_ping_parser *parser,
+                                                uint32_t length,
+                                                uint8_t flags) {
   if (flags & 0xfe || length != 8) {
-    gpr_log(GPR_ERROR, "invalid ping: length=%d, flags=%02x", length, flags);
-    return GRPC_CHTTP2_CONNECTION_ERROR;
+    char *msg;
+    gpr_asprintf(&msg, "invalid ping: length=%d, flags=%02x", length, flags);
+    grpc_error *error = GRPC_ERROR_CREATE(msg);
+    gpr_free(msg);
+    return error;
   }
   parser->byte = 0;
   parser->is_ack = flags;
-  return GRPC_CHTTP2_PARSE_OK;
+  return GRPC_ERROR_NONE;
 }
 
-grpc_chttp2_parse_error grpc_chttp2_ping_parser_parse(
+grpc_error *grpc_chttp2_ping_parser_parse(
     grpc_exec_ctx *exec_ctx, void *parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
@@ -93,5 +98,5 @@ grpc_chttp2_parse_error grpc_chttp2_ping_parser_parse(
     }
   }
 
-  return GRPC_CHTTP2_PARSE_OK;
+  return GRPC_ERROR_NONE;
 }
diff --git a/src/core/ext/transport/chttp2/transport/frame_ping.h b/src/core/ext/transport/chttp2/transport/frame_ping.h
index 4f7fcc1305..5a8723421c 100644
--- a/src/core/ext/transport/chttp2/transport/frame_ping.h
+++ b/src/core/ext/transport/chttp2/transport/frame_ping.h
@@ -46,9 +46,9 @@ typedef struct {
 
 gpr_slice grpc_chttp2_ping_create(uint8_t ack, uint8_t *opaque_8bytes);
 
-grpc_chttp2_parse_error grpc_chttp2_ping_parser_begin_frame(
-    grpc_chttp2_ping_parser *parser, uint32_t length, uint8_t flags);
-grpc_chttp2_parse_error grpc_chttp2_ping_parser_parse(
+grpc_error *grpc_chttp2_ping_parser_begin_frame(grpc_chttp2_ping_parser *parser,
+                                                uint32_t length, uint8_t flags);
+grpc_error *grpc_chttp2_ping_parser_parse(
     grpc_exec_ctx *exec_ctx, void *parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
diff --git a/src/core/ext/transport/chttp2/transport/frame_rst_stream.c b/src/core/ext/transport/chttp2/transport/frame_rst_stream.c
index 22467e9ddd..d49df0fcc2 100644
--- a/src/core/ext/transport/chttp2/transport/frame_rst_stream.c
+++ b/src/core/ext/transport/chttp2/transport/frame_rst_stream.c
@@ -34,7 +34,9 @@
 #include "src/core/ext/transport/chttp2/transport/frame_rst_stream.h"
 #include "src/core/ext/transport/chttp2/transport/internal.h"
 
+#include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
 
 #include "src/core/ext/transport/chttp2/transport/frame.h"
 
@@ -62,18 +64,21 @@ gpr_slice grpc_chttp2_rst_stream_create(uint32_t id, uint32_t code,
   return slice;
 }
 
-grpc_chttp2_parse_error grpc_chttp2_rst_stream_parser_begin_frame(
+grpc_error *grpc_chttp2_rst_stream_parser_begin_frame(
     grpc_chttp2_rst_stream_parser *parser, uint32_t length, uint8_t flags) {
   if (length != 4) {
-    gpr_log(GPR_ERROR, "invalid rst_stream: length=%d, flags=%02x", length,
-            flags);
-    return GRPC_CHTTP2_CONNECTION_ERROR;
+    char *msg;
+    gpr_asprintf(&msg, "invalid rst_stream: length=%d, flags=%02x", length,
+                 flags);
+    grpc_error *err = GRPC_ERROR_CREATE(msg);
+    gpr_free(msg);
+    return err;
   }
   parser->byte = 0;
-  return GRPC_CHTTP2_PARSE_OK;
+  return GRPC_ERROR_NONE;
 }
 
-grpc_chttp2_parse_error grpc_chttp2_rst_stream_parser_parse(
+grpc_error *grpc_chttp2_rst_stream_parser_parse(
     grpc_exec_ctx *exec_ctx, void *parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
@@ -99,5 +104,5 @@ grpc_chttp2_parse_error grpc_chttp2_rst_stream_parser_parse(
                                         (((uint32_t)p->reason_bytes[3]));
   }
 
-  return GRPC_CHTTP2_PARSE_OK;
+  return GRPC_ERROR_NONE;
 }
diff --git a/src/core/ext/transport/chttp2/transport/frame_rst_stream.h b/src/core/ext/transport/chttp2/transport/frame_rst_stream.h
index 9c1e756a94..11cf94f3ea 100644
--- a/src/core/ext/transport/chttp2/transport/frame_rst_stream.h
+++ b/src/core/ext/transport/chttp2/transport/frame_rst_stream.h
@@ -47,9 +47,9 @@ typedef struct {
 gpr_slice grpc_chttp2_rst_stream_create(uint32_t stream_id, uint32_t code,
                                         grpc_transport_one_way_stats *stats);
 
-grpc_chttp2_parse_error grpc_chttp2_rst_stream_parser_begin_frame(
+grpc_error *grpc_chttp2_rst_stream_parser_begin_frame(
     grpc_chttp2_rst_stream_parser *parser, uint32_t length, uint8_t flags);
-grpc_chttp2_parse_error grpc_chttp2_rst_stream_parser_parse(
+grpc_error *grpc_chttp2_rst_stream_parser_parse(
     grpc_exec_ctx *exec_ctx, void *parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
diff --git a/src/core/ext/transport/chttp2/transport/frame_settings.c b/src/core/ext/transport/chttp2/transport/frame_settings.c
index a3c1e15f35..04b96c4cd9 100644
--- a/src/core/ext/transport/chttp2/transport/frame_settings.c
+++ b/src/core/ext/transport/chttp2/transport/frame_settings.c
@@ -36,7 +36,9 @@
 
 #include <string.h>
 
+#include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
 #include <grpc/support/useful.h>
 
 #include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
@@ -118,7 +120,7 @@ gpr_slice grpc_chttp2_settings_ack_create(void) {
   return output;
 }
 
-grpc_chttp2_parse_error grpc_chttp2_settings_parser_begin_frame(
+grpc_error *grpc_chttp2_settings_parser_begin_frame(
     grpc_chttp2_settings_parser *parser, uint32_t length, uint8_t flags,
     uint32_t *settings) {
   parser->target_settings = settings;
@@ -129,31 +131,29 @@ grpc_chttp2_parse_error grpc_chttp2_settings_parser_begin_frame(
   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_ERROR_CREATE("non-empty settings ack frame received");
     }
-    return GRPC_CHTTP2_PARSE_OK;
+    return GRPC_ERROR_NONE;
   } else if (flags != 0) {
-    gpr_log(GPR_ERROR, "invalid flags on settings frame");
-    return GRPC_CHTTP2_CONNECTION_ERROR;
+    return GRPC_ERROR_CREATE("invalid flags on settings frame");
   } else if (length % 6 != 0) {
-    gpr_log(GPR_ERROR, "settings frames must be a multiple of six bytes");
-    return GRPC_CHTTP2_CONNECTION_ERROR;
+    return GRPC_ERROR_CREATE("settings frames must be a multiple of six bytes");
   } else {
-    return GRPC_CHTTP2_PARSE_OK;
+    return GRPC_ERROR_NONE;
   }
 }
 
-grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse(
+grpc_error *grpc_chttp2_settings_parser_parse(
     grpc_exec_ctx *exec_ctx, void *p,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
   grpc_chttp2_settings_parser *parser = p;
   const uint8_t *cur = GPR_SLICE_START_PTR(slice);
   const uint8_t *end = GPR_SLICE_END_PTR(slice);
+  char *msg;
 
   if (parser->is_ack) {
-    return GRPC_CHTTP2_PARSE_OK;
+    return GRPC_ERROR_NONE;
   }
 
   for (;;) {
@@ -168,7 +168,7 @@ grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse(
             gpr_slice_buffer_add(&transport_parsing->qbuf,
                                  grpc_chttp2_settings_ack_create());
           }
-          return GRPC_CHTTP2_PARSE_OK;
+          return GRPC_ERROR_NONE;
         }
         parser->id = (uint16_t)(((uint16_t)*cur) << 8);
         cur++;
@@ -176,7 +176,7 @@ grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse(
       case GRPC_CHTTP2_SPS_ID1:
         if (cur == end) {
           parser->state = GRPC_CHTTP2_SPS_ID1;
-          return GRPC_CHTTP2_PARSE_OK;
+          return GRPC_ERROR_NONE;
         }
         parser->id = (uint16_t)(parser->id | (*cur));
         cur++;
@@ -184,7 +184,7 @@ grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse(
       case GRPC_CHTTP2_SPS_VAL0:
         if (cur == end) {
           parser->state = GRPC_CHTTP2_SPS_VAL0;
-          return GRPC_CHTTP2_PARSE_OK;
+          return GRPC_ERROR_NONE;
         }
         parser->value = ((uint32_t)*cur) << 24;
         cur++;
@@ -192,7 +192,7 @@ grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse(
       case GRPC_CHTTP2_SPS_VAL1:
         if (cur == end) {
           parser->state = GRPC_CHTTP2_SPS_VAL1;
-          return GRPC_CHTTP2_PARSE_OK;
+          return GRPC_ERROR_NONE;
         }
         parser->value |= ((uint32_t)*cur) << 16;
         cur++;
@@ -200,7 +200,7 @@ grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse(
       case GRPC_CHTTP2_SPS_VAL2:
         if (cur == end) {
           parser->state = GRPC_CHTTP2_SPS_VAL2;
-          return GRPC_CHTTP2_PARSE_OK;
+          return GRPC_ERROR_NONE;
         }
         parser->value |= ((uint32_t)*cur) << 8;
         cur++;
@@ -208,7 +208,7 @@ grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse(
       case GRPC_CHTTP2_SPS_VAL3:
         if (cur == end) {
           parser->state = GRPC_CHTTP2_SPS_VAL3;
-          return GRPC_CHTTP2_PARSE_OK;
+          return GRPC_ERROR_NONE;
         } else {
           parser->state = GRPC_CHTTP2_SPS_ID0;
         }
@@ -229,9 +229,11 @@ grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse(
                     transport_parsing->last_incoming_stream_id, sp->error_value,
                     gpr_slice_from_static_string("HTTP2 settings error"),
                     &transport_parsing->qbuf);
-                gpr_log(GPR_ERROR, "invalid value %u passed for %s",
-                        parser->value, sp->name);
-                return GRPC_CHTTP2_CONNECTION_ERROR;
+                gpr_asprintf(&msg, "invalid value %u passed for %s",
+                             parser->value, sp->name);
+                grpc_error *err = GRPC_ERROR_CREATE(msg);
+                gpr_free(msg);
+                return err;
             }
           }
           if (parser->id == GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE &&
@@ -249,7 +251,7 @@ grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse(
                     transport_parsing->is_client ? "CLI" : "SVR", parser->id,
                     parser->value);
           }
-        } else {
+        } else if (grpc_http_trace) {
           gpr_log(GPR_ERROR, "CHTTP2: Ignoring unknown setting %d (value %d)",
                   parser->id, parser->value);
         }
diff --git a/src/core/ext/transport/chttp2/transport/frame_settings.h b/src/core/ext/transport/chttp2/transport/frame_settings.h
index d9e30f1ed0..f654c598c8 100644
--- a/src/core/ext/transport/chttp2/transport/frame_settings.h
+++ b/src/core/ext/transport/chttp2/transport/frame_settings.h
@@ -92,10 +92,10 @@ gpr_slice grpc_chttp2_settings_create(uint32_t *old, const uint32_t *new,
 /* Create an ack settings frame */
 gpr_slice grpc_chttp2_settings_ack_create(void);
 
-grpc_chttp2_parse_error grpc_chttp2_settings_parser_begin_frame(
+grpc_error *grpc_chttp2_settings_parser_begin_frame(
     grpc_chttp2_settings_parser *parser, uint32_t length, uint8_t flags,
     uint32_t *settings);
-grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse(
+grpc_error *grpc_chttp2_settings_parser_parse(
     grpc_exec_ctx *exec_ctx, void *parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
diff --git a/src/core/ext/transport/chttp2/transport/frame_window_update.c b/src/core/ext/transport/chttp2/transport/frame_window_update.c
index 90243418bd..3cf848fd5c 100644
--- a/src/core/ext/transport/chttp2/transport/frame_window_update.c
+++ b/src/core/ext/transport/chttp2/transport/frame_window_update.c
@@ -34,7 +34,9 @@
 #include "src/core/ext/transport/chttp2/transport/frame_window_update.h"
 #include "src/core/ext/transport/chttp2/transport/internal.h"
 
+#include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
 
 gpr_slice grpc_chttp2_window_update_create(
     uint32_t id, uint32_t window_update, grpc_transport_one_way_stats *stats) {
@@ -62,19 +64,22 @@ gpr_slice grpc_chttp2_window_update_create(
   return slice;
 }
 
-grpc_chttp2_parse_error grpc_chttp2_window_update_parser_begin_frame(
+grpc_error *grpc_chttp2_window_update_parser_begin_frame(
     grpc_chttp2_window_update_parser *parser, uint32_t length, uint8_t flags) {
   if (flags || length != 4) {
-    gpr_log(GPR_ERROR, "invalid window update: length=%d, flags=%02x", length,
-            flags);
-    return GRPC_CHTTP2_CONNECTION_ERROR;
+    char *msg;
+    gpr_asprintf(&msg, "invalid window update: length=%d, flags=%02x", length,
+                 flags);
+    grpc_error *err = GRPC_ERROR_CREATE(msg);
+    gpr_free(msg);
+    return err;
   }
   parser->byte = 0;
   parser->amount = 0;
-  return GRPC_CHTTP2_PARSE_OK;
+  return GRPC_ERROR_NONE;
 }
 
-grpc_chttp2_parse_error grpc_chttp2_window_update_parser_parse(
+grpc_error *grpc_chttp2_window_update_parser_parse(
     grpc_exec_ctx *exec_ctx, void *parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
@@ -96,8 +101,11 @@ grpc_chttp2_parse_error grpc_chttp2_window_update_parser_parse(
   if (p->byte == 4) {
     uint32_t received_update = p->amount;
     if (received_update == 0 || (received_update & 0x80000000u)) {
-      gpr_log(GPR_ERROR, "invalid window update bytes: %d", p->amount);
-      return GRPC_CHTTP2_CONNECTION_ERROR;
+      char *msg;
+      gpr_asprintf(&msg, "invalid window update bytes: %d", p->amount);
+      grpc_error *err = GRPC_ERROR_CREATE(msg);
+      gpr_free(msg);
+      return err;
     }
     GPR_ASSERT(is_last);
 
@@ -115,5 +123,5 @@ grpc_chttp2_parse_error grpc_chttp2_window_update_parser_parse(
     }
   }
 
-  return GRPC_CHTTP2_PARSE_OK;
+  return GRPC_ERROR_NONE;
 }
diff --git a/src/core/ext/transport/chttp2/transport/frame_window_update.h b/src/core/ext/transport/chttp2/transport/frame_window_update.h
index d6e87b9329..1bcbbf9247 100644
--- a/src/core/ext/transport/chttp2/transport/frame_window_update.h
+++ b/src/core/ext/transport/chttp2/transport/frame_window_update.h
@@ -48,9 +48,9 @@ typedef struct {
 gpr_slice grpc_chttp2_window_update_create(uint32_t id, uint32_t window_delta,
                                            grpc_transport_one_way_stats *stats);
 
-grpc_chttp2_parse_error grpc_chttp2_window_update_parser_begin_frame(
+grpc_error *grpc_chttp2_window_update_parser_begin_frame(
     grpc_chttp2_window_update_parser *parser, uint32_t length, uint8_t flags);
-grpc_chttp2_parse_error grpc_chttp2_window_update_parser_parse(
+grpc_error *grpc_chttp2_window_update_parser_parse(
     grpc_exec_ctx *exec_ctx, void *parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
diff --git a/src/core/ext/transport/chttp2/transport/hpack_parser.c b/src/core/ext/transport/chttp2/transport/hpack_parser.c
index 687936bfd3..993c6d0204 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_parser.c
+++ b/src/core/ext/transport/chttp2/transport/hpack_parser.c
@@ -46,6 +46,7 @@
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/port_platform.h>
+#include <grpc/support/string_util.h>
 #include <grpc/support/useful.h>
 
 #include "src/core/ext/transport/chttp2/transport/bin_encoder.h"
@@ -77,63 +78,70 @@ typedef enum {
    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 uint8_t *cur,
-                       const uint8_t *end);
-static int parse_error(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                       const uint8_t *end);
-static int parse_illegal_op(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                            const uint8_t *end);
-
-static int parse_string_prefix(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_begin(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                const uint8_t *end);
-static int parse_key_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                            const uint8_t *end);
-static int parse_value_string_with_indexed_key(grpc_chttp2_hpack_parser *p,
-                                               const uint8_t *cur,
-                                               const uint8_t *end);
-static int parse_value_string_with_literal_key(grpc_chttp2_hpack_parser *p,
-                                               const uint8_t *cur,
-                                               const uint8_t *end);
-
-static int parse_value0(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                        const uint8_t *end);
-static int parse_value1(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                        const uint8_t *end);
-static int parse_value2(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                        const uint8_t *end);
-static int parse_value3(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                        const uint8_t *end);
-static int parse_value4(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                        const uint8_t *end);
-static int parse_value5up(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                          const uint8_t *end);
-
-static int parse_indexed_field(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                               const uint8_t *end);
-static int parse_indexed_field_x(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end);
-static int parse_lithdr_incidx(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                               const uint8_t *end);
-static int parse_lithdr_incidx_x(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end);
-static int parse_lithdr_incidx_v(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end);
-static int parse_lithdr_notidx(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                               const uint8_t *end);
-static int parse_lithdr_notidx_x(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end);
-static int parse_lithdr_notidx_v(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end);
-static int parse_lithdr_nvridx(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                               const uint8_t *end);
-static int parse_lithdr_nvridx_x(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end);
-static int parse_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end);
-static int parse_max_tbl_size(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                              const uint8_t *end);
-static int parse_max_tbl_size_x(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_error(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                               const uint8_t *end, grpc_error *error);
+static grpc_error *still_parse_error(grpc_chttp2_hpack_parser *p,
+                                     const uint8_t *cur, const uint8_t *end);
+static grpc_error *parse_illegal_op(grpc_chttp2_hpack_parser *p,
+                                    const uint8_t *cur, const uint8_t *end);
+
+static grpc_error *parse_string_prefix(grpc_chttp2_hpack_parser *p,
+                                       const uint8_t *cur, const uint8_t *end);
+static grpc_error *parse_key_string(grpc_chttp2_hpack_parser *p,
+                                    const uint8_t *cur, const uint8_t *end);
+static grpc_error *parse_value_string_with_indexed_key(
+    grpc_chttp2_hpack_parser *p, const uint8_t *cur, const uint8_t *end);
+static grpc_error *parse_value_string_with_literal_key(
+    grpc_chttp2_hpack_parser *p, const uint8_t *cur, const uint8_t *end);
+
+static grpc_error *parse_value0(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                                const uint8_t *end);
+static grpc_error *parse_value1(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                                const uint8_t *end);
+static grpc_error *parse_value2(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                                const uint8_t *end);
+static grpc_error *parse_value3(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                 const uint8_t *end);
+static grpc_error *parse_value4(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                                const uint8_t *end);
+static grpc_error *parse_value5up(grpc_chttp2_hpack_parser *p,
+                                  const uint8_t *cur, const uint8_t *end);
+
+static grpc_error *parse_indexed_field(grpc_chttp2_hpack_parser *p,
+                                       const uint8_t *cur, const uint8_t *end);
+static grpc_error *parse_indexed_field_x(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end);
+static grpc_error *parse_lithdr_incidx(grpc_chttp2_hpack_parser *p,
+                                       const uint8_t *cur, const uint8_t *end);
+static grpc_error *parse_lithdr_incidx_x(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end);
+static grpc_error *parse_lithdr_incidx_v(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end);
+static grpc_error *parse_lithdr_notidx(grpc_chttp2_hpack_parser *p,
+                                       const uint8_t *cur, const uint8_t *end);
+static grpc_error *parse_lithdr_notidx_x(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end);
+static grpc_error *parse_lithdr_notidx_v(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end);
+static grpc_error *parse_lithdr_nvridx(grpc_chttp2_hpack_parser *p,
+                                       const uint8_t *cur, const uint8_t *end);
+static grpc_error *parse_lithdr_nvridx_x(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end);
+static grpc_error *parse_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end);
+static grpc_error *parse_max_tbl_size(grpc_chttp2_hpack_parser *p,
+                                      const uint8_t *cur, const uint8_t *end);
+static grpc_error *parse_max_tbl_size_x(grpc_chttp2_hpack_parser *p,
+                                        const uint8_t *cur, const uint8_t *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.
@@ -631,19 +639,18 @@ static const uint8_t inverse_base64[256] = {
 };
 
 /* emission helpers */
-static int on_hdr(grpc_chttp2_hpack_parser *p, grpc_mdelem *md,
-                  int add_to_table) {
+static grpc_error *on_hdr(grpc_chttp2_hpack_parser *p, grpc_mdelem *md,
+                          int add_to_table) {
   if (add_to_table) {
-    if (!grpc_chttp2_hptbl_add(&p->table, md)) {
-      return 0;
-    }
+    grpc_error *err = grpc_chttp2_hptbl_add(&p->table, md);
+    if (err != GRPC_ERROR_NONE) return err;
   }
   if (p->on_header == NULL) {
     GRPC_MDELEM_UNREF(md);
-    return 0;
+    return GRPC_ERROR_CREATE("on_header callback not set");
   }
   p->on_header(p->on_header_user_data, md);
-  return 1;
+  return GRPC_ERROR_NONE;
 }
 
 static grpc_mdstr *take_string(grpc_chttp2_hpack_parser *p,
@@ -654,70 +661,70 @@ static grpc_mdstr *take_string(grpc_chttp2_hpack_parser *p,
 }
 
 /* jump to the next state */
-static int parse_next(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                      const uint8_t *end) {
+static grpc_error *parse_next(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                              const uint8_t *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 uint8_t *cur,
-                       const uint8_t *end) {
+static grpc_error *parse_begin(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                               const uint8_t *end) {
   if (cur == end) {
     p->state = parse_begin;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   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 uint8_t *cur,
-                               const uint8_t *end) {
+static grpc_error *parse_stream_weight(grpc_chttp2_hpack_parser *p,
+                                       const uint8_t *cur, const uint8_t *end) {
   if (cur == end) {
     p->state = parse_stream_weight;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   return p->after_prioritization(p, cur + 1, end);
 }
 
-static int parse_stream_dep3(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                             const uint8_t *end) {
+static grpc_error *parse_stream_dep3(grpc_chttp2_hpack_parser *p,
+                                     const uint8_t *cur, const uint8_t *end) {
   if (cur == end) {
     p->state = parse_stream_dep3;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   return parse_stream_weight(p, cur + 1, end);
 }
 
-static int parse_stream_dep2(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                             const uint8_t *end) {
+static grpc_error *parse_stream_dep2(grpc_chttp2_hpack_parser *p,
+                                     const uint8_t *cur, const uint8_t *end) {
   if (cur == end) {
     p->state = parse_stream_dep2;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   return parse_stream_dep3(p, cur + 1, end);
 }
 
-static int parse_stream_dep1(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                             const uint8_t *end) {
+static grpc_error *parse_stream_dep1(grpc_chttp2_hpack_parser *p,
+                                     const uint8_t *cur, const uint8_t *end) {
   if (cur == end) {
     p->state = parse_stream_dep1;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   return parse_stream_dep2(p, cur + 1, end);
 }
 
-static int parse_stream_dep0(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                             const uint8_t *end) {
+static grpc_error *parse_stream_dep0(grpc_chttp2_hpack_parser *p,
+                                     const uint8_t *cur, const uint8_t *end) {
   if (cur == end) {
     p->state = parse_stream_dep0;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   return parse_stream_dep1(p, cur + 1, end);
@@ -725,30 +732,34 @@ static int parse_stream_dep0(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
 
 /* 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 uint8_t *cur,
-                                const uint8_t *end) {
+static grpc_error *finish_indexed_field(grpc_chttp2_hpack_parser *p,
+                                        const uint8_t *cur,
+                                        const uint8_t *end) {
   grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
   if (md == NULL) {
-    if (grpc_http_trace) {
-      gpr_log(GPR_ERROR, "Invalid HPACK index received: %d", p->index);
-    }
-    return 0;
+    return grpc_error_set_int(
+        grpc_error_set_int(GRPC_ERROR_CREATE("Invalid HPACK index received"),
+                           GRPC_ERROR_INT_INDEX, p->index),
+        GRPC_ERROR_INT_SIZE, p->table.num_ents);
   }
   GRPC_MDELEM_REF(md);
-  return on_hdr(p, md, 0) && parse_begin(p, cur, end);
+  grpc_error *err = on_hdr(p, md, 0);
+  if (err != GRPC_ERROR_NONE) return err;
+  return parse_begin(p, cur, end);
 }
 
 /* parse an indexed field with index < 127 */
-static int parse_indexed_field(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                               const uint8_t *end) {
+static grpc_error *parse_indexed_field(grpc_chttp2_hpack_parser *p,
+                                       const uint8_t *cur, const uint8_t *end) {
   p->dynamic_table_update_allowed = 0;
   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 uint8_t *cur, const uint8_t *end) {
+static grpc_error *parse_indexed_field_x(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       finish_indexed_field};
   p->dynamic_table_update_allowed = 0;
@@ -760,28 +771,34 @@ static int parse_indexed_field_x(grpc_chttp2_hpack_parser *p,
 
 /* finish a literal header with incremental indexing: just log, and jump to '
    begin */
-static int finish_lithdr_incidx(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                                const uint8_t *end) {
+static grpc_error *finish_lithdr_incidx(grpc_chttp2_hpack_parser *p,
+                                        const uint8_t *cur,
+                                        const uint8_t *end) {
   grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
   GPR_ASSERT(md != NULL); /* handled in string parsing */
-  return on_hdr(p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key),
-                                                     take_string(p, &p->value)),
-                1) &&
-         parse_begin(p, cur, end);
+  grpc_error *err =
+      on_hdr(p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key),
+                                                  take_string(p, &p->value)),
+             1);
+  if (err != GRPC_ERROR_NONE) return err;
+  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 uint8_t *cur, const uint8_t *end) {
-  return on_hdr(p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key),
-                                                     take_string(p, &p->value)),
-                1) &&
-         parse_begin(p, cur, end);
+static grpc_error *finish_lithdr_incidx_v(grpc_chttp2_hpack_parser *p,
+                                          const uint8_t *cur,
+                                          const uint8_t *end) {
+  grpc_error *err =
+      on_hdr(p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key),
+                                                  take_string(p, &p->value)),
+             1);
+  if (err != GRPC_ERROR_NONE) return err;
+  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 uint8_t *cur,
-                               const uint8_t *end) {
+static grpc_error *parse_lithdr_incidx(grpc_chttp2_hpack_parser *p,
+                                       const uint8_t *cur, const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       parse_value_string_with_indexed_key, finish_lithdr_incidx};
   p->dynamic_table_update_allowed = 0;
@@ -791,8 +808,9 @@ static int parse_lithdr_incidx(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
 }
 
 /* parse a literal header with incremental indexing; index >= 63 */
-static int parse_lithdr_incidx_x(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end) {
+static grpc_error *parse_lithdr_incidx_x(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       parse_string_prefix, parse_value_string_with_indexed_key,
       finish_lithdr_incidx};
@@ -804,8 +822,9 @@ static int parse_lithdr_incidx_x(grpc_chttp2_hpack_parser *p,
 }
 
 /* parse a literal header with incremental indexing; index = 0 */
-static int parse_lithdr_incidx_v(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end) {
+static grpc_error *parse_lithdr_incidx_v(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       parse_key_string, parse_string_prefix,
       parse_value_string_with_literal_key, finish_lithdr_incidx_v};
@@ -815,28 +834,34 @@ static int parse_lithdr_incidx_v(grpc_chttp2_hpack_parser *p,
 }
 
 /* finish a literal header without incremental indexing */
-static int finish_lithdr_notidx(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                                const uint8_t *end) {
+static grpc_error *finish_lithdr_notidx(grpc_chttp2_hpack_parser *p,
+                                        const uint8_t *cur,
+                                        const uint8_t *end) {
   grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
   GPR_ASSERT(md != NULL); /* handled in string parsing */
-  return on_hdr(p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key),
-                                                     take_string(p, &p->value)),
-                0) &&
-         parse_begin(p, cur, end);
+  grpc_error *err =
+      on_hdr(p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key),
+                                                  take_string(p, &p->value)),
+             0);
+  if (err != GRPC_ERROR_NONE) return err;
+  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 uint8_t *cur, const uint8_t *end) {
-  return on_hdr(p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key),
-                                                     take_string(p, &p->value)),
-                0) &&
-         parse_begin(p, cur, end);
+static grpc_error *finish_lithdr_notidx_v(grpc_chttp2_hpack_parser *p,
+                                          const uint8_t *cur,
+                                          const uint8_t *end) {
+  grpc_error *err =
+      on_hdr(p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key),
+                                                  take_string(p, &p->value)),
+             0);
+  if (err != GRPC_ERROR_NONE) return err;
+  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 uint8_t *cur,
-                               const uint8_t *end) {
+static grpc_error *parse_lithdr_notidx(grpc_chttp2_hpack_parser *p,
+                                       const uint8_t *cur, const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       parse_value_string_with_indexed_key, finish_lithdr_notidx};
   p->dynamic_table_update_allowed = 0;
@@ -846,8 +871,9 @@ static int parse_lithdr_notidx(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
 }
 
 /* parse a literal header without incremental indexing; index >= 15 */
-static int parse_lithdr_notidx_x(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end) {
+static grpc_error *parse_lithdr_notidx_x(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       parse_string_prefix, parse_value_string_with_indexed_key,
       finish_lithdr_notidx};
@@ -859,8 +885,9 @@ static int parse_lithdr_notidx_x(grpc_chttp2_hpack_parser *p,
 }
 
 /* parse a literal header without incremental indexing; index == 0 */
-static int parse_lithdr_notidx_v(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end) {
+static grpc_error *parse_lithdr_notidx_v(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       parse_key_string, parse_string_prefix,
       parse_value_string_with_literal_key, finish_lithdr_notidx_v};
@@ -870,28 +897,34 @@ static int parse_lithdr_notidx_v(grpc_chttp2_hpack_parser *p,
 }
 
 /* finish a literal header that is never indexed */
-static int finish_lithdr_nvridx(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                                const uint8_t *end) {
+static grpc_error *finish_lithdr_nvridx(grpc_chttp2_hpack_parser *p,
+                                        const uint8_t *cur,
+                                        const uint8_t *end) {
   grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
   GPR_ASSERT(md != NULL); /* handled in string parsing */
-  return on_hdr(p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key),
-                                                     take_string(p, &p->value)),
-                0) &&
-         parse_begin(p, cur, end);
+  grpc_error *err =
+      on_hdr(p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key),
+                                                  take_string(p, &p->value)),
+             0);
+  if (err != GRPC_ERROR_NONE) return err;
+  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 uint8_t *cur, const uint8_t *end) {
-  return on_hdr(p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key),
-                                                     take_string(p, &p->value)),
-                0) &&
-         parse_begin(p, cur, end);
+static grpc_error *finish_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p,
+                                          const uint8_t *cur,
+                                          const uint8_t *end) {
+  grpc_error *err =
+      on_hdr(p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key),
+                                                  take_string(p, &p->value)),
+             0);
+  if (err != GRPC_ERROR_NONE) return err;
+  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 uint8_t *cur,
-                               const uint8_t *end) {
+static grpc_error *parse_lithdr_nvridx(grpc_chttp2_hpack_parser *p,
+                                       const uint8_t *cur, const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       parse_value_string_with_indexed_key, finish_lithdr_nvridx};
   p->dynamic_table_update_allowed = 0;
@@ -901,8 +934,9 @@ static int parse_lithdr_nvridx(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
 }
 
 /* parse a literal header that is never indexed; index >= 15 */
-static int parse_lithdr_nvridx_x(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end) {
+static grpc_error *parse_lithdr_nvridx_x(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       parse_string_prefix, parse_value_string_with_indexed_key,
       finish_lithdr_nvridx};
@@ -914,8 +948,9 @@ static int parse_lithdr_nvridx_x(grpc_chttp2_hpack_parser *p,
 }
 
 /* parse a literal header that is never indexed; index == 0 */
-static int parse_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p,
-                                 const uint8_t *cur, const uint8_t *end) {
+static grpc_error *parse_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p,
+                                         const uint8_t *cur,
+                                         const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       parse_key_string, parse_string_prefix,
       parse_value_string_with_literal_key, finish_lithdr_nvridx_v};
@@ -925,20 +960,23 @@ static int parse_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p,
 }
 
 /* finish parsing a max table size change */
-static int finish_max_tbl_size(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                               const uint8_t *end) {
+static grpc_error *finish_max_tbl_size(grpc_chttp2_hpack_parser *p,
+                                       const uint8_t *cur, const uint8_t *end) {
   if (grpc_http_trace) {
     gpr_log(GPR_INFO, "MAX TABLE SIZE: %d", p->index);
   }
-  return grpc_chttp2_hptbl_set_current_table_size(&p->table, p->index) &&
-         parse_begin(p, cur, end);
+  grpc_error *err =
+      grpc_chttp2_hptbl_set_current_table_size(&p->table, p->index);
+  if (err != GRPC_ERROR_NONE) return err;
+  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 uint8_t *cur,
-                              const uint8_t *end) {
+static grpc_error *parse_max_tbl_size(grpc_chttp2_hpack_parser *p,
+                                      const uint8_t *cur, const uint8_t *end) {
   if (p->dynamic_table_update_allowed == 0) {
-    return 0;
+    return GRPC_ERROR_CREATE(
+        "More than two max table size changes in a single frame");
   }
   p->dynamic_table_update_allowed--;
   p->index = (*cur) & 0x1f;
@@ -946,12 +984,14 @@ static int parse_max_tbl_size(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
 }
 
 /* parse a max table size change, max size >= 15 */
-static int parse_max_tbl_size_x(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                                const uint8_t *end) {
+static grpc_error *parse_max_tbl_size_x(grpc_chttp2_hpack_parser *p,
+                                        const uint8_t *cur,
+                                        const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       finish_max_tbl_size};
   if (p->dynamic_table_update_allowed == 0) {
-    return 0;
+    return GRPC_ERROR_CREATE(
+        "More than two max table size changes in a single frame");
   }
   p->dynamic_table_update_allowed--;
   p->next_state = and_then;
@@ -961,28 +1001,36 @@ static int parse_max_tbl_size_x(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
 }
 
 /* a parse error: jam the parse state into parse_error, and return error */
-static int parse_error(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                       const uint8_t *end) {
-  p->state = parse_error;
-  return 0;
+static grpc_error *parse_error(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                               const uint8_t *end, grpc_error *err) {
+  GPR_ASSERT(err != GRPC_ERROR_NONE);
+  p->last_error = grpc_error_ref(err);
+  p->state = still_parse_error;
+  return err;
+}
+
+static grpc_error *still_parse_error(grpc_chttp2_hpack_parser *p,
+                                     const uint8_t *cur, const uint8_t *end) {
+  return grpc_error_ref(p->last_error);
 }
 
-static int parse_illegal_op(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                            const uint8_t *end) {
+static grpc_error *parse_illegal_op(grpc_chttp2_hpack_parser *p,
+                                    const uint8_t *cur, const uint8_t *end) {
   GPR_ASSERT(cur != end);
-  if (grpc_http_trace) {
-    gpr_log(GPR_DEBUG, "Illegal hpack op code %d", *cur);
-  }
-  return parse_error(p, cur, end);
+  char *msg;
+  gpr_asprintf(&msg, "Illegal hpack op code %d", *cur);
+  grpc_error *err = GRPC_ERROR_CREATE(msg);
+  gpr_free(msg);
+  return parse_error(p, cur, end, err);
 }
 
 /* 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 uint8_t *cur,
-                        const uint8_t *end) {
+static grpc_error *parse_value0(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                                const uint8_t *end) {
   if (cur == end) {
     p->state = parse_value0;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   *p->parsing.value += (*cur) & 0x7f;
@@ -996,11 +1044,11 @@ static int parse_value0(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
 
 /* 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 uint8_t *cur,
-                        const uint8_t *end) {
+static grpc_error *parse_value1(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                                const uint8_t *end) {
   if (cur == end) {
     p->state = parse_value1;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   *p->parsing.value += (((uint32_t)*cur) & 0x7f) << 7;
@@ -1014,11 +1062,11 @@ static int parse_value1(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
 
 /* 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 uint8_t *cur,
-                        const uint8_t *end) {
+static grpc_error *parse_value2(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                                const uint8_t *end) {
   if (cur == end) {
     p->state = parse_value2;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   *p->parsing.value += (((uint32_t)*cur) & 0x7f) << 14;
@@ -1032,11 +1080,11 @@ static int parse_value2(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
 
 /* 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 uint8_t *cur,
-                        const uint8_t *end) {
+static grpc_error *parse_value3(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                                const uint8_t *end) {
   if (cur == end) {
     p->state = parse_value3;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   *p->parsing.value += (((uint32_t)*cur) & 0x7f) << 21;
@@ -1050,15 +1098,16 @@ static int parse_value3(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
 
 /* 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 uint8_t *cur,
-                        const uint8_t *end) {
+static grpc_error *parse_value4(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                                const uint8_t *end) {
   uint8_t c;
   uint32_t cur_value;
   uint32_t add_value;
+  char *msg;
 
   if (cur == end) {
     p->state = parse_value4;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   c = (*cur) & 0x7f;
@@ -1081,48 +1130,49 @@ static int parse_value4(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
   }
 
 error:
-  if (grpc_http_trace) {
-    gpr_log(GPR_ERROR,
-            "integer overflow in hpack integer decoding: have 0x%08x, "
-            "got byte 0x%02x on byte 5",
-            *p->parsing.value, *cur);
-  }
-  return parse_error(p, cur, end);
+  gpr_asprintf(&msg,
+               "integer overflow in hpack integer decoding: have 0x%08x, "
+               "got byte 0x%02x on byte 5",
+               *p->parsing.value, *cur);
+  grpc_error *err = GRPC_ERROR_CREATE(msg);
+  gpr_free(msg);
+  return parse_error(p, cur, end, err);
 }
 
 /* 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 uint8_t *cur,
-                          const uint8_t *end) {
+static grpc_error *parse_value5up(grpc_chttp2_hpack_parser *p,
+                                  const uint8_t *cur, const uint8_t *end) {
   while (cur != end && *cur == 0x80) {
     ++cur;
   }
 
   if (cur == end) {
     p->state = parse_value5up;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   if (*cur == 0) {
     return parse_next(p, cur + 1, end);
   }
 
-  if (grpc_http_trace) {
-    gpr_log(GPR_ERROR,
-            "integer overflow in hpack integer decoding: have 0x%08x, "
-            "got byte 0x%02x sometime after byte 5",
-            *p->parsing.value, *cur);
-  }
-  return parse_error(p, cur, end);
+  char *msg;
+  gpr_asprintf(&msg,
+               "integer overflow in hpack integer decoding: have 0x%08x, "
+               "got byte 0x%02x sometime after byte 5",
+               *p->parsing.value, *cur);
+  grpc_error *err = GRPC_ERROR_CREATE(msg);
+  gpr_free(msg);
+  return parse_error(p, cur, end, err);
 }
 
 /* parse a string prefix */
-static int parse_string_prefix(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                               const uint8_t *end) {
+static grpc_error *parse_string_prefix(grpc_chttp2_hpack_parser *p,
+                                       const uint8_t *cur, const uint8_t *end) {
   if (cur == end) {
     p->state = parse_string_prefix;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   p->strlen = (*cur) & 0x7f;
@@ -1148,25 +1198,25 @@ static void append_bytes(grpc_chttp2_hpack_parser_string *str,
   str->length += (uint32_t)length;
 }
 
-static int append_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                         const uint8_t *end) {
+static grpc_error *append_string(grpc_chttp2_hpack_parser *p,
+                                 const uint8_t *cur, const uint8_t *end) {
   grpc_chttp2_hpack_parser_string *str = p->parsing.str;
   uint32_t bits;
   uint8_t decoded[3];
   switch ((binary_state)p->binary) {
     case NOT_BINARY:
       append_bytes(str, cur, (size_t)(end - cur));
-      return 1;
+      return GRPC_ERROR_NONE;
     b64_byte0:
     case B64_BYTE0:
       if (cur == end) {
         p->binary = B64_BYTE0;
-        return 1;
+        return GRPC_ERROR_NONE;
       }
       bits = inverse_base64[*cur];
       ++cur;
       if (bits == 255)
-        return 0;
+        return GRPC_ERROR_CREATE("Illegal base64 character");
       else if (bits == 64)
         goto b64_byte0;
       p->base64_buffer = bits << 18;
@@ -1175,12 +1225,12 @@ static int append_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
     case B64_BYTE1:
       if (cur == end) {
         p->binary = B64_BYTE1;
-        return 1;
+        return GRPC_ERROR_NONE;
       }
       bits = inverse_base64[*cur];
       ++cur;
       if (bits == 255)
-        return 0;
+        return GRPC_ERROR_CREATE("Illegal base64 character");
       else if (bits == 64)
         goto b64_byte1;
       p->base64_buffer |= bits << 12;
@@ -1189,12 +1239,12 @@ static int append_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
     case B64_BYTE2:
       if (cur == end) {
         p->binary = B64_BYTE2;
-        return 1;
+        return GRPC_ERROR_NONE;
       }
       bits = inverse_base64[*cur];
       ++cur;
       if (bits == 255)
-        return 0;
+        return GRPC_ERROR_CREATE("Illegal base64 character");
       else if (bits == 64)
         goto b64_byte2;
       p->base64_buffer |= bits << 6;
@@ -1203,12 +1253,12 @@ static int append_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
     case B64_BYTE3:
       if (cur == end) {
         p->binary = B64_BYTE3;
-        return 1;
+        return GRPC_ERROR_NONE;
       }
       bits = inverse_base64[*cur];
       ++cur;
       if (bits == 255)
-        return 0;
+        return GRPC_ERROR_CREATE("Illegal base64 character");
       else if (bits == 64)
         goto b64_byte3;
       p->base64_buffer |= bits;
@@ -1219,11 +1269,11 @@ static int append_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
       append_bytes(str, decoded, 3);
       goto b64_byte0;
   }
-  GPR_UNREACHABLE_CODE(return 1);
+  GPR_UNREACHABLE_CODE(return GRPC_ERROR_CREATE("Should never reach here"));
 }
 
 /* append a null terminator to a string */
-static int finish_str(grpc_chttp2_hpack_parser *p) {
+static grpc_error *finish_str(grpc_chttp2_hpack_parser *p) {
   uint8_t terminator = 0;
   uint8_t decoded[2];
   uint32_t bits;
@@ -1234,14 +1284,17 @@ static int finish_str(grpc_chttp2_hpack_parser *p) {
     case B64_BYTE0:
       break;
     case B64_BYTE1:
-      gpr_log(GPR_ERROR, "illegal base64 encoding");
-      return 0; /* illegal encoding */
+      return GRPC_ERROR_CREATE(
+          "illegal base64 encoding"); /* illegal encoding */
     case B64_BYTE2:
       bits = p->base64_buffer;
       if (bits & 0xffff) {
-        gpr_log(GPR_ERROR, "trailing bits in base64 encoding: 0x%04x",
-                bits & 0xffff);
-        return 0;
+        char *msg;
+        gpr_asprintf(&msg, "trailing bits in base64 encoding: 0x%04x",
+                     bits & 0xffff);
+        grpc_error *err = GRPC_ERROR_CREATE(msg);
+        gpr_free(msg);
+        return err;
       }
       decoded[0] = (uint8_t)(bits >> 16);
       append_bytes(str, decoded, 1);
@@ -1249,9 +1302,12 @@ static int finish_str(grpc_chttp2_hpack_parser *p) {
     case B64_BYTE3:
       bits = p->base64_buffer;
       if (bits & 0xff) {
-        gpr_log(GPR_ERROR, "trailing bits in base64 encoding: 0x%02x",
-                bits & 0xff);
-        return 0;
+        char *msg;
+        gpr_asprintf(&msg, "trailing bits in base64 encoding: 0x%02x",
+                     bits & 0xff);
+        grpc_error *err = GRPC_ERROR_CREATE(msg);
+        gpr_free(msg);
+        return err;
       }
       decoded[0] = (uint8_t)(bits >> 16);
       decoded[1] = (uint8_t)(bits >> 8);
@@ -1260,38 +1316,42 @@ static int finish_str(grpc_chttp2_hpack_parser *p) {
   }
   append_bytes(str, &terminator, 1);
   p->parsing.str->length--; /* don't actually count the null terminator */
-  return 1;
+  return GRPC_ERROR_NONE;
 }
 
 /* decode a nibble from a huffman encoded stream */
-static int huff_nibble(grpc_chttp2_hpack_parser *p, uint8_t nibble) {
+static grpc_error *huff_nibble(grpc_chttp2_hpack_parser *p, uint8_t nibble) {
   int16_t emit = emit_sub_tbl[16 * emit_tbl[p->huff_state] + nibble];
   int16_t next = next_sub_tbl[16 * next_tbl[p->huff_state] + nibble];
   if (emit != -1) {
     if (emit >= 0 && emit < 256) {
       uint8_t c = (uint8_t)emit;
-      if (!append_string(p, &c, (&c) + 1)) return 0;
+      grpc_error *err = append_string(p, &c, (&c) + 1);
+      if (err != GRPC_ERROR_NONE) return err;
     } else {
       assert(emit == 256);
     }
   }
   p->huff_state = next;
-  return 1;
+  return GRPC_ERROR_NONE;
 }
 
 /* decode full bytes from a huffman encoded stream */
-static int add_huff_bytes(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                          const uint8_t *end) {
+static grpc_error *add_huff_bytes(grpc_chttp2_hpack_parser *p,
+                                  const uint8_t *cur, const uint8_t *end) {
   for (; cur != end; ++cur) {
-    if (!huff_nibble(p, *cur >> 4) || !huff_nibble(p, *cur & 0xf)) return 0;
+    grpc_error *err = huff_nibble(p, *cur >> 4);
+    if (err != GRPC_ERROR_NONE) return err;
+    err = huff_nibble(p, *cur & 0xf);
+    if (err != GRPC_ERROR_NONE) return err;
   }
-  return 1;
+  return GRPC_ERROR_NONE;
 }
 
 /* decode some string bytes based on the current decoding mode
    (huffman or not) */
-static int add_str_bytes(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                         const uint8_t *end) {
+static grpc_error *add_str_bytes(grpc_chttp2_hpack_parser *p,
+                                 const uint8_t *cur, const uint8_t *end) {
   if (p->huff) {
     return add_huff_bytes(p, cur, end);
   } else {
@@ -1300,26 +1360,31 @@ static int add_str_bytes(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
 }
 
 /* parse a string - tries to do large chunks at a time */
-static int parse_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                        const uint8_t *end) {
+static grpc_error *parse_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+                                const uint8_t *end) {
   size_t remaining = p->strlen - p->strgot;
   size_t given = (size_t)(end - cur);
   if (remaining <= given) {
-    return add_str_bytes(p, cur, cur + remaining) && finish_str(p) &&
-           parse_next(p, cur + remaining, end);
+    grpc_error *err = add_str_bytes(p, cur, cur + remaining);
+    if (err != GRPC_ERROR_NONE) return err;
+    err = finish_str(p);
+    if (err != GRPC_ERROR_NONE) return err;
+    return parse_next(p, cur + remaining, end);
   } else {
-    if (!add_str_bytes(p, cur, cur + given)) return 0;
+    grpc_error *err = add_str_bytes(p, cur, cur + given);
+    if (err != GRPC_ERROR_NONE) return err;
     GPR_ASSERT(given <= UINT32_MAX - p->strgot);
     p->strgot += (uint32_t)given;
     p->state = parse_string;
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 }
 
 /* begin parsing a string - performs setup, calls parse_string */
-static int begin_parse_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                              const uint8_t *end, uint8_t binary,
-                              grpc_chttp2_hpack_parser_string *str) {
+static grpc_error *begin_parse_string(grpc_chttp2_hpack_parser *p,
+                                      const uint8_t *cur, const uint8_t *end,
+                                      uint8_t binary,
+                                      grpc_chttp2_hpack_parser_string *str) {
   p->strgot = 0;
   str->length = 0;
   p->parsing.str = str;
@@ -1329,58 +1394,50 @@ static int begin_parse_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
 }
 
 /* parse the key string */
-static int parse_key_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                            const uint8_t *end) {
+static grpc_error *parse_key_string(grpc_chttp2_hpack_parser *p,
+                                    const uint8_t *cur, const uint8_t *end) {
   return begin_parse_string(p, cur, end, NOT_BINARY, &p->key);
 }
 
 /* check if a key represents a binary header or not */
-typedef enum { BINARY_HEADER, PLAINTEXT_HEADER, ERROR_HEADER } is_binary_header;
 
-static is_binary_header is_binary_literal_header(grpc_chttp2_hpack_parser *p) {
-  return grpc_is_binary_header(p->key.str, p->key.length) ? BINARY_HEADER
-                                                          : PLAINTEXT_HEADER;
+static bool is_binary_literal_header(grpc_chttp2_hpack_parser *p) {
+  return grpc_is_binary_header(p->key.str, p->key.length);
 }
 
-static is_binary_header is_binary_indexed_header(grpc_chttp2_hpack_parser *p) {
+static grpc_error *is_binary_indexed_header(grpc_chttp2_hpack_parser *p,
+                                            bool *is) {
   grpc_mdelem *elem = grpc_chttp2_hptbl_lookup(&p->table, p->index);
   if (!elem) {
-    if (grpc_http_trace) {
-      gpr_log(GPR_ERROR, "Invalid HPACK index received: %d", p->index);
-    }
-    return ERROR_HEADER;
+    return grpc_error_set_int(
+        grpc_error_set_int(GRPC_ERROR_CREATE("Invalid HPACK index received"),
+                           GRPC_ERROR_INT_INDEX, p->index),
+        GRPC_ERROR_INT_SIZE, p->table.num_ents);
   }
-  return grpc_is_binary_header(
-             (const char *)GPR_SLICE_START_PTR(elem->key->slice),
-             GPR_SLICE_LENGTH(elem->key->slice))
-             ? BINARY_HEADER
-             : PLAINTEXT_HEADER;
+  *is =
+      grpc_is_binary_header((const char *)GPR_SLICE_START_PTR(elem->key->slice),
+                            GPR_SLICE_LENGTH(elem->key->slice));
+  return GRPC_ERROR_NONE;
 }
 
 /* parse the value string */
-static int parse_value_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
-                              const uint8_t *end, is_binary_header type) {
-  switch (type) {
-    case BINARY_HEADER:
-      return begin_parse_string(p, cur, end, B64_BYTE0, &p->value);
-    case PLAINTEXT_HEADER:
-      return begin_parse_string(p, cur, end, NOT_BINARY, &p->value);
-    case ERROR_HEADER:
-      return 0;
-  }
-  /* Add code to prevent return without value error */
-  GPR_UNREACHABLE_CODE(return 0);
+static grpc_error *parse_value_string(grpc_chttp2_hpack_parser *p,
+                                      const uint8_t *cur, const uint8_t *end,
+                                      bool is_binary) {
+  return begin_parse_string(p, cur, end, is_binary ? B64_BYTE0 : NOT_BINARY,
+                            &p->value);
 }
 
-static int parse_value_string_with_indexed_key(grpc_chttp2_hpack_parser *p,
-                                               const uint8_t *cur,
-                                               const uint8_t *end) {
-  return parse_value_string(p, cur, end, is_binary_indexed_header(p));
+static grpc_error *parse_value_string_with_indexed_key(
+    grpc_chttp2_hpack_parser *p, const uint8_t *cur, const uint8_t *end) {
+  bool is_binary;
+  grpc_error *err = is_binary_indexed_header(p, &is_binary);
+  if (err != GRPC_ERROR_NONE) return err;
+  return parse_value_string(p, cur, end, is_binary);
 }
 
-static int parse_value_string_with_literal_key(grpc_chttp2_hpack_parser *p,
-                                               const uint8_t *cur,
-                                               const uint8_t *end) {
+static grpc_error *parse_value_string_with_literal_key(
+    grpc_chttp2_hpack_parser *p, const uint8_t *cur, const uint8_t *end) {
   return parse_value_string(p, cur, end, is_binary_literal_header(p));
 }
 
@@ -1411,8 +1468,9 @@ void grpc_chttp2_hpack_parser_destroy(grpc_chttp2_hpack_parser *p) {
   gpr_free(p->value.str);
 }
 
-int grpc_chttp2_hpack_parser_parse(grpc_chttp2_hpack_parser *p,
-                                   const uint8_t *beg, const uint8_t *end) {
+grpc_error *grpc_chttp2_hpack_parser_parse(grpc_chttp2_hpack_parser *p,
+                                           const uint8_t *beg,
+                                           const uint8_t *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
@@ -1420,7 +1478,7 @@ int grpc_chttp2_hpack_parser_parse(grpc_chttp2_hpack_parser *p,
   return p->state(p, beg, end);
 }
 
-grpc_chttp2_parse_error grpc_chttp2_header_parser_parse(
+grpc_error *grpc_chttp2_header_parser_parse(
     grpc_exec_ctx *exec_ctx, void *hpack_parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
@@ -1429,17 +1487,17 @@ grpc_chttp2_parse_error grpc_chttp2_header_parser_parse(
   if (stream_parsing != NULL) {
     stream_parsing->stats.incoming.header_bytes += GPR_SLICE_LENGTH(slice);
   }
-  if (!grpc_chttp2_hpack_parser_parse(parser, GPR_SLICE_START_PTR(slice),
-                                      GPR_SLICE_END_PTR(slice))) {
+  grpc_error *error = grpc_chttp2_hpack_parser_parse(
+      parser, GPR_SLICE_START_PTR(slice), GPR_SLICE_END_PTR(slice));
+  if (error != GRPC_ERROR_NONE) {
     GPR_TIMER_END("grpc_chttp2_hpack_parser_parse", 0);
-    return GRPC_CHTTP2_CONNECTION_ERROR;
+    return 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");
       GPR_TIMER_END("grpc_chttp2_hpack_parser_parse", 0);
-      return GRPC_CHTTP2_CONNECTION_ERROR;
+      return GRPC_ERROR_CREATE(
+          "end of header frame not aligned with a hpack record boundary");
     }
     /* need to check for null stream: this can occur if we receive an invalid
        stream id on a header */
@@ -1462,5 +1520,5 @@ grpc_chttp2_parse_error grpc_chttp2_header_parser_parse(
     parser->dynamic_table_update_allowed = 2;
   }
   GPR_TIMER_END("grpc_chttp2_hpack_parser_parse", 0);
-  return GRPC_CHTTP2_PARSE_OK;
+  return GRPC_ERROR_NONE;
 }
diff --git a/src/core/ext/transport/chttp2/transport/hpack_parser.h b/src/core/ext/transport/chttp2/transport/hpack_parser.h
index 855d6c5d52..78eb38db5e 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_parser.h
+++ b/src/core/ext/transport/chttp2/transport/hpack_parser.h
@@ -44,9 +44,8 @@
 
 typedef struct grpc_chttp2_hpack_parser grpc_chttp2_hpack_parser;
 
-typedef int (*grpc_chttp2_hpack_parser_state)(grpc_chttp2_hpack_parser *p,
-                                              const uint8_t *beg,
-                                              const uint8_t *end);
+typedef grpc_error *(*grpc_chttp2_hpack_parser_state)(
+    grpc_chttp2_hpack_parser *p, const uint8_t *beg, const uint8_t *end);
 
 typedef struct {
   char *str;
@@ -59,6 +58,8 @@ struct grpc_chttp2_hpack_parser {
   void (*on_header)(void *user_data, grpc_mdelem *md);
   void *on_header_user_data;
 
+  grpc_error *last_error;
+
   /* current parse state - or a function that implements it */
   grpc_chttp2_hpack_parser_state state;
   /* future states dependent on the opening op code */
@@ -103,12 +104,13 @@ 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 uint8_t *beg, const uint8_t *end);
+grpc_error *grpc_chttp2_hpack_parser_parse(grpc_chttp2_hpack_parser *p,
+                                           const uint8_t *beg,
+                                           const uint8_t *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(
+grpc_error *grpc_chttp2_header_parser_parse(
     grpc_exec_ctx *exec_ctx, void *hpack_parser,
     grpc_chttp2_transport_parsing *transport_parsing,
     grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
diff --git a/src/core/ext/transport/chttp2/transport/hpack_table.c b/src/core/ext/transport/chttp2/transport/hpack_table.c
index 4d64506de2..0684d49899 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_table.c
+++ b/src/core/ext/transport/chttp2/transport/hpack_table.c
@@ -38,6 +38,7 @@
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
 
 #include "src/core/lib/support/murmur_hash.h"
 
@@ -260,18 +261,19 @@ void grpc_chttp2_hptbl_set_max_bytes(grpc_chttp2_hptbl *tbl,
   tbl->max_bytes = max_bytes;
 }
 
-int grpc_chttp2_hptbl_set_current_table_size(grpc_chttp2_hptbl *tbl,
-                                             uint32_t bytes) {
+grpc_error *grpc_chttp2_hptbl_set_current_table_size(grpc_chttp2_hptbl *tbl,
+                                                     uint32_t bytes) {
   if (tbl->current_table_bytes == bytes) {
-    return 1;
+    return GRPC_ERROR_NONE;
   }
   if (bytes > tbl->max_bytes) {
-    if (grpc_http_trace) {
-      gpr_log(GPR_ERROR,
-              "Attempt to make hpack table %d bytes when max is %d bytes",
-              bytes, tbl->max_bytes);
-    }
-    return 0;
+    char *msg;
+    gpr_asprintf(&msg,
+                 "Attempt to make hpack table %d bytes when max is %d bytes",
+                 bytes, tbl->max_bytes);
+    grpc_error *err = GRPC_ERROR_CREATE(msg);
+    gpr_free(msg);
+    return err;
   }
   if (grpc_http_trace) {
     gpr_log(GPR_DEBUG, "Update hpack parser table size to %d", bytes);
@@ -289,23 +291,25 @@ int grpc_chttp2_hptbl_set_current_table_size(grpc_chttp2_hptbl *tbl,
       rebuild_ents(tbl, new_cap);
     }
   }
-  return 1;
+  return GRPC_ERROR_NONE;
 }
 
-int grpc_chttp2_hptbl_add(grpc_chttp2_hptbl *tbl, grpc_mdelem *md) {
+grpc_error *grpc_chttp2_hptbl_add(grpc_chttp2_hptbl *tbl, grpc_mdelem *md) {
   /* determine how many bytes of buffer this entry represents */
   size_t elem_bytes = GPR_SLICE_LENGTH(md->key->slice) +
                       GPR_SLICE_LENGTH(md->value->slice) +
                       GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD;
 
   if (tbl->current_table_bytes > tbl->max_bytes) {
-    if (grpc_http_trace) {
-      gpr_log(GPR_ERROR,
-              "HPACK max table size reduced to %d but not reflected by hpack "
-              "stream (still at %d)",
-              tbl->max_bytes, tbl->current_table_bytes);
-    }
-    return 0;
+    char *msg;
+    gpr_asprintf(
+        &msg,
+        "HPACK max table size reduced to %d but not reflected by hpack "
+        "stream (still at %d)",
+        tbl->max_bytes, tbl->current_table_bytes);
+    grpc_error *err = GRPC_ERROR_CREATE(msg);
+    gpr_free(msg);
+    return err;
   }
 
   /* we can't add elements bigger than the max table size */
@@ -322,7 +326,7 @@ int grpc_chttp2_hptbl_add(grpc_chttp2_hptbl *tbl, grpc_mdelem *md) {
     while (tbl->num_ents) {
       evict1(tbl);
     }
-    return 1;
+    return GRPC_ERROR_NONE;
   }
 
   /* evict entries to ensure no overflow */
@@ -337,7 +341,7 @@ int grpc_chttp2_hptbl_add(grpc_chttp2_hptbl *tbl, grpc_mdelem *md) {
   /* update accounting values */
   tbl->num_ents++;
   tbl->mem_used += (uint32_t)elem_bytes;
-  return 1;
+  return GRPC_ERROR_NONE;
 }
 
 grpc_chttp2_hptbl_find_result grpc_chttp2_hptbl_find(
diff --git a/src/core/ext/transport/chttp2/transport/hpack_table.h b/src/core/ext/transport/chttp2/transport/hpack_table.h
index 074fea36d8..45bd9255bf 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_table.h
+++ b/src/core/ext/transport/chttp2/transport/hpack_table.h
@@ -36,6 +36,7 @@
 
 #include <grpc/support/port_platform.h>
 #include <grpc/support/slice.h>
+#include "src/core/lib/iomgr/error.h"
 #include "src/core/lib/transport/metadata.h"
 
 /* HPACK header table */
@@ -87,15 +88,15 @@ void grpc_chttp2_hptbl_init(grpc_chttp2_hptbl *tbl);
 void grpc_chttp2_hptbl_destroy(grpc_chttp2_hptbl *tbl);
 void grpc_chttp2_hptbl_set_max_bytes(grpc_chttp2_hptbl *tbl,
                                      uint32_t max_bytes);
-int grpc_chttp2_hptbl_set_current_table_size(grpc_chttp2_hptbl *tbl,
-                                             uint32_t bytes);
+grpc_error *grpc_chttp2_hptbl_set_current_table_size(grpc_chttp2_hptbl *tbl,
+                                                     uint32_t bytes);
 
 /* lookup a table entry based on its hpack index */
 grpc_mdelem *grpc_chttp2_hptbl_lookup(const grpc_chttp2_hptbl *tbl,
                                       uint32_t index);
 /* add a table entry to the index */
-int grpc_chttp2_hptbl_add(grpc_chttp2_hptbl *tbl,
-                          grpc_mdelem *md) GRPC_MUST_USE_RESULT;
+grpc_error *grpc_chttp2_hptbl_add(grpc_chttp2_hptbl *tbl,
+                                  grpc_mdelem *md) GRPC_MUST_USE_RESULT;
 /* 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 {
diff --git a/src/core/ext/transport/chttp2/transport/internal.h b/src/core/ext/transport/chttp2/transport/internal.h
index cb5ed9fc25..ae35d58117 100644
--- a/src/core/ext/transport/chttp2/transport/internal.h
+++ b/src/core/ext/transport/chttp2/transport/internal.h
@@ -673,7 +673,8 @@ void grpc_chttp2_parsing_become_skip_parser(
 
 void grpc_chttp2_complete_closure_step(grpc_exec_ctx *exec_ctx,
                                        grpc_chttp2_stream_global *stream_global,
-                                       grpc_closure **pclosure, int success);
+                                       grpc_closure **pclosure,
+                                       grpc_error *error);
 
 void grpc_chttp2_run_with_global_lock(grpc_exec_ctx *exec_ctx,
                                       grpc_chttp2_transport *transport,
@@ -776,8 +777,8 @@ void grpc_chttp2_fake_status(grpc_exec_ctx *exec_ctx,
                              grpc_status_code status, gpr_slice *details);
 void grpc_chttp2_mark_stream_closed(
     grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global, int close_reads,
-    int close_writes);
+    grpc_chttp2_stream_global *stream_global, int close_reads, int close_writes,
+    grpc_error *error);
 void grpc_chttp2_start_writing(grpc_exec_ctx *exec_ctx,
                                grpc_chttp2_transport_global *transport_global);
 
diff --git a/src/core/lib/iomgr/closure.c b/src/core/lib/iomgr/closure.c
index 885637efe9..0e84d13c45 100644
--- a/src/core/lib/iomgr/closure.c
+++ b/src/core/lib/iomgr/closure.c
@@ -44,21 +44,21 @@ void grpc_closure_init(grpc_closure *closure, grpc_iomgr_cb_func cb,
 void grpc_closure_list_append(grpc_closure_list *closure_list,
                               grpc_closure *closure, grpc_error *error) {
   if (closure == NULL) return;
-  closure->final_data.error = error;
-  closure->next = NULL;
+  closure->error = error;
+  closure->next_data.next = NULL;
   if (closure_list->head == NULL) {
     closure_list->head = closure;
   } else {
-    closure_list->tail->next = closure;
+    closure_list->tail->next_data.next = closure;
   }
   closure_list->tail = closure;
 }
 
 void grpc_closure_list_fail_all(grpc_closure_list *list,
                                 grpc_error *forced_failure) {
-  for (grpc_closure *c = list->head; c != NULL; c = c->next) {
-    if (c->final_data.error == NULL) {
-      c->final_data.error = grpc_error_ref(forced_failure);
+  for (grpc_closure *c = list->head; c != NULL; c = c->next_data.next) {
+    if (c->error == GRPC_ERROR_NONE) {
+      c->error = grpc_error_ref(forced_failure);
     }
   }
   grpc_error_unref(forced_failure);
@@ -75,7 +75,7 @@ void grpc_closure_list_move(grpc_closure_list *src, grpc_closure_list *dst) {
   if (dst->head == NULL) {
     *dst = *src;
   } else {
-    dst->tail->next = src->head;
+    dst->tail->next_data.next = src->head;
     dst->tail = src->tail;
   }
   src->head = src->tail = NULL;
diff --git a/src/core/lib/iomgr/closure.h b/src/core/lib/iomgr/closure.h
index 14132d926e..344b7d3c3e 100644
--- a/src/core/lib/iomgr/closure.h
+++ b/src/core/lib/iomgr/closure.h
@@ -66,12 +66,12 @@ struct grpc_closure {
   /** Arguments to be passed to "cb". */
   void *cb_arg;
 
+  grpc_error *error;
+
   union {
-    grpc_error *error;
+    grpc_closure *next;
     uintptr_t scratch;
-  } final_data;
-
-  grpc_closure *next;
+  } next_data;
 };
 
 /** Initializes \a closure with \a cb and \a cb_arg. */
diff --git a/src/core/lib/iomgr/error.c b/src/core/lib/iomgr/error.c
index d0e4301722..55bb1b5660 100644
--- a/src/core/lib/iomgr/error.c
+++ b/src/core/lib/iomgr/error.c
@@ -90,6 +90,18 @@ static const char *error_int_name(grpc_error_ints key) {
       return "errno";
     case GRPC_ERROR_INT_FILE_LINE:
       return "file_line";
+    case GRPC_ERROR_INT_WARNING:
+      return "warning";
+    case GRPC_ERROR_INT_STREAM_ID:
+      return "stream_id";
+    case GRPC_ERROR_INT_GRPC_STATUS:
+      return "grpc_status";
+    case GRPC_ERROR_INT_OFFSET:
+      return "offset";
+    case GRPC_ERROR_INT_INDEX:
+      return "index";
+    case GRPC_ERROR_INT_SIZE:
+      return "size";
   }
   GPR_UNREACHABLE_CODE(return "unknown");
 }
@@ -106,6 +118,10 @@ static const char *error_str_name(grpc_error_strs key) {
       return "syscall";
     case GRPC_ERROR_STR_FILE:
       return "file";
+    case GRPC_ERROR_STR_GRPC_MESSAGE:
+      return "grpc_message";
+    case GRPC_ERROR_STR_RAW_BYTES:
+      return "raw_bytes";
   }
   GPR_UNREACHABLE_CODE(return "unknown");
 }
diff --git a/src/core/lib/iomgr/error.h b/src/core/lib/iomgr/error.h
index 1429631b67..db5e378e77 100644
--- a/src/core/lib/iomgr/error.h
+++ b/src/core/lib/iomgr/error.h
@@ -45,6 +45,11 @@ typedef enum {
   GRPC_ERROR_INT_FILE_LINE,
   GRPC_ERROR_INT_STATUS_CODE,
   GRPC_ERROR_INT_WARNING,
+  GRPC_ERROR_INT_STREAM_ID,
+  GRPC_ERROR_INT_GRPC_STATUS,
+  GRPC_ERROR_INT_OFFSET,
+  GRPC_ERROR_INT_INDEX,
+  GRPC_ERROR_INT_SIZE,
 } grpc_error_ints;
 
 typedef enum {
@@ -53,6 +58,8 @@ typedef enum {
   GRPC_ERROR_STR_OS_ERROR,
   GRPC_ERROR_STR_SYSCALL,
   GRPC_ERROR_STR_TARGET_ADDRESS,
+  GRPC_ERROR_STR_GRPC_MESSAGE,
+  GRPC_ERROR_STR_RAW_BYTES,
 } grpc_error_strs;
 
 typedef enum {
diff --git a/src/core/lib/iomgr/exec_ctx.c b/src/core/lib/iomgr/exec_ctx.c
index 1111462132..cc52c16bce 100644
--- a/src/core/lib/iomgr/exec_ctx.c
+++ b/src/core/lib/iomgr/exec_ctx.c
@@ -47,8 +47,8 @@ bool grpc_exec_ctx_flush(grpc_exec_ctx *exec_ctx) {
     grpc_closure *c = exec_ctx->closure_list.head;
     exec_ctx->closure_list.head = exec_ctx->closure_list.tail = NULL;
     while (c != NULL) {
-      grpc_closure *next = c->next;
-      grpc_error *error = c->final_data.error;
+      grpc_closure *next = c->next_data.next;
+      grpc_error *error = c->error;
       did_something = true;
       GPR_TIMER_BEGIN("grpc_exec_ctx_flush.cb", 0);
       c->cb(exec_ctx, c->cb_arg, error);
-- 
GitLab