diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 656da22a..eaf80299 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -621,20 +621,20 @@ void nghttp2_nv_array_del(nghttp2_nv *nva) free(nva); } -static int nghttp2_nv_name_compar(const void *lhs, const void *rhs) +static int bytes_compar(const uint8_t *a, size_t alen, + const uint8_t *b, size_t blen) { - nghttp2_nv *a = (nghttp2_nv*)lhs, *b = (nghttp2_nv*)rhs; - if(a->namelen == b->namelen) { - return memcmp(a->name, b->name, a->namelen); - } else if(a->namelen < b->namelen) { - int rv = memcmp(a->name, b->name, a->namelen); + if(alen == blen) { + return memcmp(a, b, alen); + } else if(alen < blen) { + int rv = memcmp(a, b, alen); if(rv == 0) { return -1; } else { return rv; } } else { - int rv = memcmp(a->name, b->name, b->namelen); + int rv = memcmp(a, b, blen); if(rv == 0) { return 1; } else { @@ -645,12 +645,24 @@ static int nghttp2_nv_name_compar(const void *lhs, const void *rhs) int nghttp2_nv_compare_name(const nghttp2_nv *lhs, const nghttp2_nv *rhs) { - return nghttp2_nv_name_compar(lhs, rhs); + return bytes_compar(lhs->name, lhs->namelen, rhs->name, rhs->namelen); +} + +static int nv_compar(const void *lhs, const void *rhs) +{ + const nghttp2_nv *a = (const nghttp2_nv*)lhs; + const nghttp2_nv *b = (const nghttp2_nv*)rhs; + int rv; + rv = bytes_compar(a->name, a->namelen, b->name, b->namelen); + if(rv == 0) { + return bytes_compar(a->value, a->valuelen, b->value, b->valuelen); + } + return rv; } void nghttp2_nv_array_sort(nghttp2_nv *nva, size_t nvlen) { - qsort(nva, nvlen, sizeof(nghttp2_nv), nghttp2_nv_name_compar); + qsort(nva, nvlen, sizeof(nghttp2_nv), nv_compar); } ssize_t nghttp2_nv_array_from_cstr(nghttp2_nv **nva_ptr, const char **nv) @@ -694,7 +706,6 @@ ssize_t nghttp2_nv_array_from_cstr(nghttp2_nv **nva_ptr, const char **nv) data += len; ++p; } - nghttp2_nv_array_sort(*nva_ptr, nvlen); return nvlen; } @@ -737,7 +748,6 @@ ssize_t nghttp2_nv_array_copy(nghttp2_nv **nva_ptr, data += nva[i].valuelen; ++p; } - nghttp2_nv_array_sort(*nva_ptr, nvlen); return nvlen; } diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index 02532666..5f953337 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -523,8 +523,8 @@ nghttp2_settings_entry* nghttp2_frame_iv_copy(const nghttp2_settings_entry *iv, int nghttp2_frame_nv_check_null(const char **nv); /* - * Sorts the |nva| in ascending order of name. The relative order of - * the same name pair is undefined. + * Sorts the |nva| in ascending order of name and value. If names are + * equivalent, sort them by value. */ void nghttp2_nv_array_sort(nghttp2_nv *nva, size_t nvlen); diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c index ed16c475..0076c484 100644 --- a/lib/nghttp2_hd.c +++ b/lib/nghttp2_hd.c @@ -1352,7 +1352,6 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, goto fail; } } - nghttp2_nv_array_sort(nva_out.nva, nva_out.nvlen); *nva_ptr = nva_out.nva; return nva_out.nvlen; fail: diff --git a/src/HttpServer.cc b/src/HttpServer.cc index e6db6946..cc88e7f6 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -707,13 +707,12 @@ void prepare_response(Request *req, Http2Handler *hd) } // namespace namespace { -void append_nv(Request *req, nghttp2_nv *nva, size_t nvlen) +void append_nv(Request *req, const std::vector& nva) { - for(size_t i = 0; i < nvlen; ++i) { - req->headers.push_back({ - std::string(nva[i].name, nva[i].name + nva[i].namelen), - std::string(nva[i].value, nva[i].value + nva[i].valuelen) - }); + for(auto nv : nva) { + req->headers.push_back(std::make_pair + (std::string(nv->name, nv->name + nv->namelen), + std::string(nv->value, nv->value + nv->valuelen))); } } } // namespace @@ -738,16 +737,14 @@ int hd_on_frame_recv_callback switch(frame->headers.cat) { case NGHTTP2_HCAT_REQUEST: { int32_t stream_id = frame->hd.stream_id; - if(!http2::check_http2_headers(frame->headers.nva, - frame->headers.nvlen)) { + auto nva = http2::sort_nva(frame->headers.nva, frame->headers.nvlen); + if(!http2::check_http2_headers(nva)) { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_PROTOCOL_ERROR); return 0; } for(size_t i = 0; REQUIRED_HEADERS[i]; ++i) { - if(!http2::get_unique_header(frame->headers.nva, - frame->headers.nvlen, - REQUIRED_HEADERS[i])) { + if(!http2::get_unique_header(nva, REQUIRED_HEADERS[i])) { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_PROTOCOL_ERROR); return 0; @@ -756,18 +753,14 @@ int hd_on_frame_recv_callback // intermediary translating from HTTP/1 request to HTTP/2 may // not produce :authority header field. In this case, it should // provide host HTTP/1.1 header field. - if(!http2::get_unique_header(frame->headers.nva, - frame->headers.nvlen, - ":authority") && - !http2::get_unique_header(frame->headers.nva, - frame->headers.nvlen, - "host")) { + if(!http2::get_unique_header(nva, ":authority") && + !http2::get_unique_header(nva, "host")) { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_PROTOCOL_ERROR); return 0; } auto req = util::make_unique(stream_id); - append_nv(req.get(), frame->headers.nva, frame->headers.nvlen); + append_nv(req.get(), nva); hd->add_stream(stream_id, std::move(req)); break; } diff --git a/src/http2.cc b/src/http2.cc index 434484b2..0fe82678 100644 --- a/src/http2.cc +++ b/src/http2.cc @@ -195,50 +195,69 @@ size_t HTTP1_IGN_HDLEN = sizeof(HTTP1_IGN_HD)/sizeof(HTTP1_IGN_HD[0]); } // namespace namespace { -auto nv_name_less = [](const nghttp2_nv& lhs, const nghttp2_nv& rhs) +auto nv_name_less = [](const nghttp2_nv *lhs, const nghttp2_nv *rhs) { - return nghttp2_nv_compare_name(&lhs, &rhs) < 0; + return nghttp2_nv_compare_name(lhs, rhs) < 0; }; } // namespace -bool check_http2_headers(const nghttp2_nv *nva, size_t nvlen) +bool check_http2_headers(const std::vector& nva) { for(size_t i = 0; i < DISALLOWED_HDLEN; ++i) { nghttp2_nv nv = {(uint8_t*)DISALLOWED_HD[i], nullptr, (uint16_t)strlen(DISALLOWED_HD[i]), 0}; - if(std::binary_search(&nva[0], &nva[nvlen], nv, nv_name_less)) { + if(std::binary_search(std::begin(nva), std::end(nva), &nv, nv_name_less)) { return false; } } return true; } -const nghttp2_nv* get_unique_header(const nghttp2_nv *nva, size_t nvlen, +std::vector sort_nva(const nghttp2_nv *nva, size_t nvlen) +{ + auto res = std::vector(); + res.reserve(nvlen); + for(size_t i = 0; i < nvlen; ++i) { + res.push_back(&nva[i]); + } + std::sort(std::begin(res), std::end(res), + [](const nghttp2_nv *lhs, const nghttp2_nv *rhs) + { + auto rv = nghttp2_nv_compare_name(lhs, rhs); + if(rv == 0) { + return lhs < rhs; + } + return rv < 0; + }); + return res; +} + +const nghttp2_nv* get_unique_header(const std::vector& nva, const char *name) { size_t namelen = strlen(name); nghttp2_nv nv = {(uint8_t*)name, nullptr, (uint16_t)namelen, 0}; - auto i = std::lower_bound(&nva[0], &nva[nvlen], nv, nv_name_less); - if(i != &nva[nvlen] && util::streq(i->name, i->namelen, - (const uint8_t*)name, namelen)) { + auto i = std::lower_bound(std::begin(nva), std::end(nva), &nv, nv_name_less); + if(i != std::end(nva) && util::streq((*i)->name, (*i)->namelen, + (const uint8_t*)name, namelen)) { auto j = i + 1; - if(j == &nva[nvlen] || !util::streq(j->name, j->namelen, - (const uint8_t*)name, namelen)) { - return i; + if(j == std::end(nva) || !util::streq((*j)->name, (*j)->namelen, + (const uint8_t*)name, namelen)) { + return *i; } } return nullptr; } -const nghttp2_nv* get_header(const nghttp2_nv *nva, size_t nvlen, +const nghttp2_nv* get_header(const std::vector& nva, const char *name) { size_t namelen = strlen(name); nghttp2_nv nv = {(uint8_t*)name, nullptr, (uint16_t)namelen, 0}; - auto i = std::lower_bound(&nva[0], &nva[nvlen], nv, nv_name_less); - if(i != &nva[nvlen] && util::streq(i->name, i->namelen, - (const uint8_t*)name, namelen)) { - return i; + auto i = std::lower_bound(std::begin(nva), std::end(nva), &nv, nv_name_less); + if(i != std::end(nva) && util::streq((*i)->name, (*i)->namelen, + (const uint8_t*)name, namelen)) { + return *i; } return nullptr; } diff --git a/src/http2.h b/src/http2.h index 9f117f7c..8713d22d 100644 --- a/src/http2.h +++ b/src/http2.h @@ -67,18 +67,22 @@ bool check_http2_allowed_header(const char *name); // Checks that headers |nva| including |nvlen| entries do not contain // disallowed header fields in HTTP/2.0 spec. This function returns // true if |nva| does not contains such headers. -bool check_http2_headers(const nghttp2_nv *nva, size_t nvlen); +bool check_http2_headers(const std::vector& nva); + +// Returns sorted |nva| with |nvlen| elements. This sort is stable +// sort. +std::vector sort_nva(const nghttp2_nv *nva, size_t nvlen); // Returns the pointer to the entry in |nva| which has name |name| and // the |name| is uinque in the |nva|. If no such entry exist, returns // nullptr. -const nghttp2_nv* get_unique_header(const nghttp2_nv *nva, size_t nvlen, +const nghttp2_nv* get_unique_header(const std::vector& nva, const char *name); // Returns the poiter to the entry in |nva| which has name |name|. If // more than one entries which have the name |name|, first occurrence // in |nva| is returned. If no such entry exist, returns nullptr. -const nghttp2_nv* get_header(const nghttp2_nv *nva, size_t nvlen, +const nghttp2_nv* get_header(const std::vector& nva, const char *name); // Returns std::string version of nv->name with nv->namelen bytes. diff --git a/src/http2_test.cc b/src/http2_test.cc index bd1f7c20..bf6129af 100644 --- a/src/http2_test.cc +++ b/src/http2_test.cc @@ -24,6 +24,7 @@ */ #include "http2_test.h" +#include #include #include @@ -38,22 +39,53 @@ using namespace nghttp2; namespace shrpx { +namespace { +template +int bstrcmp(cstr1 *a, cstr2 *b) +{ + return strcmp((const char*)a, (const char*)b); +} +} // namespace + +void test_http2_sort_nva(void) +{ + nghttp2_nv nv[] = {MAKE_NV("alpha", "1"), + MAKE_NV("bravo", "9"), + MAKE_NV("bravo", "8"), + MAKE_NV("charlie", "5"), + MAKE_NV("bravo", "3"), + MAKE_NV("bravo", "4")}; + auto nvlen = sizeof(nv)/sizeof(nv[0]); + auto nva = http2::sort_nva(nv, nvlen); + CU_ASSERT(nvlen == nva.size()); + CU_ASSERT(0 == bstrcmp("alpha", nva[0]->name)); + CU_ASSERT(0 == bstrcmp("bravo", nva[1]->name)); + CU_ASSERT(0 == bstrcmp("9", nva[1]->value)); + CU_ASSERT(0 == bstrcmp("bravo", nva[2]->name)); + CU_ASSERT(0 == bstrcmp("8", nva[2]->value)); + CU_ASSERT(0 == bstrcmp("bravo", nva[3]->name)); + CU_ASSERT(0 == bstrcmp("3", nva[3]->value)); + CU_ASSERT(0 == bstrcmp("bravo", nva[4]->name)); + CU_ASSERT(0 == bstrcmp("4", nva[4]->value)); + CU_ASSERT(0 == bstrcmp("charlie", nva[5]->name)); +} + void test_http2_check_http2_headers(void) { nghttp2_nv nv1[] = {MAKE_NV("alpha", "1"), MAKE_NV("bravo", "2"), MAKE_NV("upgrade", "http2")}; - CU_ASSERT(!http2::check_http2_headers(nv1, 3)); + CU_ASSERT(!http2::check_http2_headers(http2::sort_nva(nv1, 3))); nghttp2_nv nv2[] = {MAKE_NV("connection", "1"), MAKE_NV("delta", "2"), MAKE_NV("echo", "3")}; - CU_ASSERT(!http2::check_http2_headers(nv2, 3)); + CU_ASSERT(!http2::check_http2_headers(http2::sort_nva(nv2, 3))); nghttp2_nv nv3[] = {MAKE_NV("alpha", "1"), MAKE_NV("bravo", "2"), MAKE_NV("te2", "3")}; - CU_ASSERT(http2::check_http2_headers(nv3, 3)); + CU_ASSERT(http2::check_http2_headers(http2::sort_nva(nv3, 3))); } void test_http2_get_unique_header(void) @@ -65,15 +97,16 @@ void test_http2_get_unique_header(void) MAKE_NV("delta", "5"), MAKE_NV("echo", "6"),}; size_t nvlen = sizeof(nv)/sizeof(nv[0]); + auto nva = http2::sort_nva(nv, nvlen); const nghttp2_nv *rv; - rv = http2::get_unique_header(nv, nvlen, "delta"); + rv = http2::get_unique_header(nva, "delta"); CU_ASSERT(rv != nullptr); CU_ASSERT(util::streq("delta", rv->name, rv->namelen)); - rv = http2::get_unique_header(nv, nvlen, "bravo"); + rv = http2::get_unique_header(nva, "bravo"); CU_ASSERT(rv == nullptr); - rv = http2::get_unique_header(nv, nvlen, "foxtrot"); + rv = http2::get_unique_header(nva, "foxtrot"); CU_ASSERT(rv == nullptr); } @@ -86,16 +119,17 @@ void test_http2_get_header(void) MAKE_NV("delta", "5"), MAKE_NV("echo", "6"),}; size_t nvlen = sizeof(nv)/sizeof(nv[0]); + auto nva = http2::sort_nva(nv, nvlen); const nghttp2_nv *rv; - rv = http2::get_header(nv, nvlen, "delta"); + rv = http2::get_header(nva, "delta"); CU_ASSERT(rv != nullptr); CU_ASSERT(util::streq("delta", rv->name, rv->namelen)); - rv = http2::get_header(nv, nvlen, "bravo"); + rv = http2::get_header(nva, "bravo"); CU_ASSERT(rv != nullptr); CU_ASSERT(util::streq("bravo", rv->name, rv->namelen)); - rv = http2::get_header(nv, nvlen, "foxtrot"); + rv = http2::get_header(nva, "foxtrot"); CU_ASSERT(rv == nullptr); } diff --git a/src/http2_test.h b/src/http2_test.h index 34cc5560..f62ef957 100644 --- a/src/http2_test.h +++ b/src/http2_test.h @@ -27,6 +27,7 @@ namespace shrpx { +void test_http2_sort_nva(void); void test_http2_check_http2_headers(void); void test_http2_get_unique_header(void); void test_http2_get_header(void); diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index ee87c78a..77108dc6 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -69,6 +69,7 @@ int main(int argc, char* argv[]) shrpx::test_shrpx_ssl_create_lookup_tree) || !CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file", shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) || + !CU_add_test(pSuite, "http2_sort_nva", shrpx::test_http2_sort_nva) || !CU_add_test(pSuite, "http2_check_http2_headers", shrpx::test_http2_check_http2_headers) || !CU_add_test(pSuite, "http2_get_unique_header", diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index 6efb8308..ce2e4fd6 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -156,7 +156,7 @@ void normalize_headers(Headers& headers) for(auto& kv : headers) { util::inp_strlower(kv.first); } - std::sort(std::begin(headers), std::end(headers), name_less); + std::stable_sort(std::begin(headers), std::end(headers), name_less); } } // namespace diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index 22477c6a..94bab748 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -820,11 +820,10 @@ int on_frame_recv_callback NGHTTP2_INTERNAL_ERROR); break; } - auto nva = frame->headers.nva; - auto nvlen = frame->headers.nvlen; + // nva is no longer sorted + auto nva = http2::sort_nva(frame->headers.nva, frame->headers.nvlen); - // Assuming that nva is sorted by name. - if(!http2::check_http2_headers(nva, nvlen)) { + if(!http2::check_http2_headers(nva)) { http2session->submit_rst_stream(frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); downstream->set_response_state(Downstream::MSG_RESET); @@ -832,14 +831,14 @@ int on_frame_recv_callback return 0; } - for(size_t i = 0; i < nvlen; ++i) { - if(nva[i].namelen > 0 && nva[i].name[0] != ':') { - downstream->add_response_header(http2::name_to_str(&nva[i]), - http2::value_to_str(&nva[i])); + for(auto nv : nva) { + if(nv->namelen > 0 && nv->name[0] != ':') { + downstream->add_response_header(http2::name_to_str(nv), + http2::value_to_str(nv)); } } - auto status = http2::get_unique_header(nva, nvlen, ":status"); + auto status = http2::get_unique_header(nva, ":status"); if(!status || http2::value_lws(status)) { http2session->submit_rst_stream(frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); @@ -855,7 +854,7 @@ int on_frame_recv_callback downstream->set_response_major(1); downstream->set_response_minor(1); - auto content_length = http2::get_header(nva, nvlen, "content-length"); + auto content_length = http2::get_header(nva, "content-length"); if(!content_length && downstream->get_request_method() != "HEAD" && downstream->get_request_method() != "CONNECT") { unsigned int status; @@ -879,11 +878,11 @@ int on_frame_recv_callback if(LOG_ENABLED(INFO)) { std::stringstream ss; - for(size_t i = 0; i < frame->headers.nvlen; ++i) { + for(auto nv : nva) { ss << TTY_HTTP_HD; - ss.write(reinterpret_cast(nva[i].name), nva[i].namelen); + ss.write(reinterpret_cast(nv->name), nv->namelen); ss << TTY_RST << ": "; - ss.write(reinterpret_cast(nva[i].value), nva[i].valuelen); + ss.write(reinterpret_cast(nv->value), nv->valuelen); ss << "\n"; } SSLOG(INFO, http2session) << "HTTP response headers. stream_id=" diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index cef83f0d..95018165 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -237,16 +237,16 @@ int on_frame_recv_callback upstream->add_downstream(downstream); downstream->init_response_body_buf(); - auto nva = frame->headers.nva; - auto nvlen = frame->headers.nvlen; + // nva is no longer sorted + auto nva = http2::sort_nva(frame->headers.nva, frame->headers.nvlen); if(LOG_ENABLED(INFO)) { std::stringstream ss; - for(size_t i = 0; i < frame->headers.nvlen; ++i) { + for(auto nv : nva) { ss << TTY_HTTP_HD; - ss.write(reinterpret_cast(nva[i].name), nva[i].namelen); + ss.write(reinterpret_cast(nv->name), nv->namelen); ss << TTY_RST << ": "; - ss.write(reinterpret_cast(nva[i].value), nva[i].valuelen); + ss.write(reinterpret_cast(nv->value), nv->valuelen); ss << "\n"; } ULOG(INFO, upstream) << "HTTP request headers. stream_id=" @@ -254,24 +254,23 @@ int on_frame_recv_callback << "\n" << ss.str(); } - // Assuming that nva is sorted by name. - if(!http2::check_http2_headers(nva, nvlen)) { + if(!http2::check_http2_headers(nva)) { upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); return 0; } - for(size_t i = 0; i < nvlen; ++i) { - if(nva[i].namelen > 0 && nva[i].name[0] != ':') { - downstream->add_request_header(http2::name_to_str(&nva[i]), - http2::value_to_str(&nva[i])); + for(auto nv : nva) { + if(nv->namelen > 0 && nv->name[0] != ':') { + downstream->add_request_header(http2::name_to_str(nv), + http2::value_to_str(nv)); } } - auto host = http2::get_unique_header(nva, nvlen, "host"); - auto authority = http2::get_unique_header(nva, nvlen, ":authority"); - auto path = http2::get_unique_header(nva, nvlen, ":path"); - auto method = http2::get_unique_header(nva, nvlen, ":method"); - auto scheme = http2::get_unique_header(nva, nvlen, ":scheme"); + auto host = http2::get_unique_header(nva, "host"); + auto authority = http2::get_unique_header(nva, ":authority"); + auto path = http2::get_unique_header(nva, ":path"); + auto method = http2::get_unique_header(nva, ":method"); + auto scheme = http2::get_unique_header(nva, ":scheme"); bool is_connect = method && util::streq("CONNECT", method->value, method->valuelen); bool having_host = http2::non_empty_value(host); @@ -297,7 +296,7 @@ int on_frame_recv_callback } if(!is_connect && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { - auto content_length = http2::get_header(nva, nvlen, "content-length"); + auto content_length = http2::get_header(nva, "content-length"); if(!content_length || http2::value_lws(content_length)) { // If content-length is missing, // Downstream::push_upload_data_chunk will fail and diff --git a/tests/nghttp2_frame_test.c b/tests/nghttp2_frame_test.c index 24aa99d8..e8ddb831 100644 --- a/tests/nghttp2_frame_test.c +++ b/tests/nghttp2_frame_test.c @@ -118,6 +118,7 @@ void test_nghttp2_frame_pack_headers() NGHTTP2_FLAG_PRIORITY, 1000000007, &oframe.hd); CU_ASSERT(1 << 20 == oframe.pri); + nghttp2_nv_array_sort(oframe.nva, oframe.nvlen); CU_ASSERT(nvnameeq("method", &oframe.nva[0])); free(buf); diff --git a/tests/nghttp2_hd_test.c b/tests/nghttp2_hd_test.c index c1ecbf61..7dd5819f 100644 --- a/tests/nghttp2_hd_test.c +++ b/tests/nghttp2_hd_test.c @@ -38,6 +38,7 @@ static void assert_nv_equal(nghttp2_nv *a, nghttp2_nv *b, size_t len) { size_t i; + nghttp2_nv_array_sort(b, len); for(i = 0; i < len; ++i, ++a, ++b) { CU_ASSERT(nghttp2_nv_equal(a, b)); }