Repository: trafficserver Updated Branches: refs/heads/master 6fcff147b -> 00ce2f111
TS-3752: Problem with larger headers and HTTP/2 Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/00ce2f11 Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/00ce2f11 Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/00ce2f11 Branch: refs/heads/master Commit: 00ce2f1113baa9485262695a66ee67a08fc5d121 Parents: 6fcff14 Author: Masaori Koshiba <[email protected]> Authored: Fri Aug 14 15:37:38 2015 -0700 Committer: Bryan Call <[email protected]> Committed: Fri Aug 14 15:40:23 2015 -0700 ---------------------------------------------------------------------- proxy/http2/HPACK.cc | 14 +- proxy/http2/HTTP2.cc | 71 +++----- proxy/http2/HTTP2.h | 23 ++- proxy/http2/Http2ClientSession.cc | 24 ++- proxy/http2/Http2ConnectionState.cc | 292 +++++++++++++++---------------- proxy/http2/Http2ConnectionState.h | 63 ++++--- 6 files changed, 244 insertions(+), 243 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/00ce2f11/proxy/http2/HPACK.cc ---------------------------------------------------------------------- diff --git a/proxy/http2/HPACK.cc b/proxy/http2/HPACK.cc index b37eef6..d65a6b1 100644 --- a/proxy/http2/HPACK.cc +++ b/proxy/http2/HPACK.cc @@ -43,8 +43,6 @@ const unsigned HPACK_LEN_STATUS = countof(":status") - 1; // Section 6.2), plus 32. const static unsigned ADDITIONAL_OCTETS = 32; -const static uint32_t HEADER_FIELD_LIMIT_LENGTH = 4096; - typedef enum { TS_HPACK_STATIC_TABLE_0 = 0, TS_HPACK_STATIC_TABLE_AUTHORITY, @@ -241,8 +239,10 @@ Http2DynamicTable::add_header_field(const MIMEField *field) uint32_t header_size = ADDITIONAL_OCTETS + name_len + value_len; if (header_size > _settings_dynamic_table_size) { - // 5.3. It is not an error to attempt to add an entry that is larger than the maximum size; an - // attempt to add an entry larger than the entire table causes the table to be emptied of all existing entries. + // 5.3. It is not an error to attempt to add an entry that is larger than + // the maximum size; an + // attempt to add an entry larger than the entire table causes the table to + // be emptied of all existing entries. _headers.clear(); _mhdr->fields_clear(); } else { @@ -538,7 +538,7 @@ decode_string(Arena &arena, char **str, uint32_t &str_length, const uint8_t *buf return HPACK_ERROR_COMPRESSION_ERROR; p += len; - if (encoded_string_len > HEADER_FIELD_LIMIT_LENGTH || (p + encoded_string_len) > buf_end) { + if ((p + encoded_string_len) > buf_end) { return HPACK_ERROR_COMPRESSION_ERROR; } @@ -603,7 +603,8 @@ decode_literal_header_field(MIMEFieldWrapper &header, const uint8_t *buf_start, HpackFieldType ftype = hpack_parse_field_type(*p); if (ftype == HPACK_FIELD_INDEXED_LITERAL) { - // 7.2.1. index extraction based on Literal Header Field with Incremental Indexing + // 7.2.1. index extraction based on Literal Header Field with Incremental + // Indexing len = decode_integer(index, p, buf_end, 6); isIncremental = true; } else if (ftype == HPACK_FIELD_NEVERINDEX_LITERAL) { @@ -655,7 +656,6 @@ decode_literal_header_field(MIMEFieldWrapper &header, const uint8_t *buf_start, p += len; header.value_set(value_str, value_str_len); - // Incremental Indexing adds header to header table as new entry if (isIncremental) { dynamic_table.add_header_field(header.field_get()); http://git-wip-us.apache.org/repos/asf/trafficserver/blob/00ce2f11/proxy/http2/HTTP2.cc ---------------------------------------------------------------------- diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc index 3a84a59..d77242c 100644 --- a/proxy/http2/HTTP2.cc +++ b/proxy/http2/HTTP2.cc @@ -111,7 +111,8 @@ http2_are_frame_flags_valid(uint8_t ftype, uint8_t fflags) HTTP2_FLAGS_WINDOW_UPDATE_MASK, HTTP2_FLAGS_CONTINUATION_MASK, }; - // The frame flags are valid for this frame if nothing outside the defined bits is set. + // The frame flags are valid for this frame if nothing outside the defined + // bits is set. return (fflags & ~mask[ftype]) == 0; } @@ -325,7 +326,6 @@ http2_parse_headers_parameter(IOVec iov, Http2HeadersParameter ¶ms) return true; } - // 6.3. PRIORITY // // 0 1 2 3 @@ -401,7 +401,6 @@ http2_parse_settings_parameter(IOVec iov, Http2SettingsParameter ¶m) return true; } - // 6.8. GOAWAY // // 0 1 2 3 @@ -429,7 +428,6 @@ http2_parse_goaway(IOVec iov, Http2Goaway &goaway) return true; } - // 6.9. WINDOW_UPDATE // // 0 1 2 3 @@ -584,8 +582,10 @@ http2_write_header_fragment(HTTPHdr *in, MIMEFieldIter &field_iter, uint8_t *out ink_assert(http_hdr_type_get(in->m_http) != HTTP_TYPE_UNKNOWN); ink_assert(in); - // TODO Get a index value from the tables for the header field, and then choose a representation type. - // TODO Each indexing types per field should be passed by a caller, HTTP/2 implementation. + // TODO Get a index value from the tables for the header field, and then + // choose a representation type. + // TODO Each indexing types per field should be passed by a caller, HTTP/2 + // implementation. // Get first header field which is required encoding MIMEField *field; @@ -629,17 +629,17 @@ http2_write_header_fragment(HTTPHdr *in, MIMEFieldIter &field_iter, uint8_t *out return p - out; } +/* + * Decode Header Blocks to Header List. + */ int64_t -http2_parse_header_fragment(HTTPHdr *hdr, IOVec iov, Http2DynamicTable &dynamic_table, bool cont) +http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint8_t *buf_end, Http2DynamicTable &dynamic_table) { - const uint8_t *buf_start = (uint8_t *)iov.iov_base; - const uint8_t *buf_end = buf_start + iov.iov_len; - - uint8_t *cursor = (uint8_t *)iov.iov_base; // place the cursor at the start + const uint8_t *cursor = buf_start; HdrHeap *heap = hdr->m_heap; HTTPHdrImpl *hh = hdr->m_http; - do { + while (cursor < buf_end) { int64_t read_bytes = 0; // decode a header field encoded by HPACK @@ -651,13 +651,7 @@ http2_parse_header_fragment(HTTPHdr *hdr, IOVec iov, Http2DynamicTable &dynamic_ case HPACK_FIELD_INDEX: read_bytes = decode_indexed_header_field(header, cursor, buf_end, dynamic_table); if (read_bytes == HPACK_ERROR_COMPRESSION_ERROR) { - if (cont) { - // Parsing a part of headers is done - return cursor - buf_start; - } else { - // Parse error - return HPACK_ERROR_COMPRESSION_ERROR; - } + return HPACK_ERROR_COMPRESSION_ERROR; } cursor += read_bytes; break; @@ -666,26 +660,14 @@ http2_parse_header_fragment(HTTPHdr *hdr, IOVec iov, Http2DynamicTable &dynamic_ case HPACK_FIELD_NEVERINDEX_LITERAL: read_bytes = decode_literal_header_field(header, cursor, buf_end, dynamic_table); if (read_bytes == HPACK_ERROR_COMPRESSION_ERROR) { - if (cont) { - // Parsing a part of headers is done - return cursor - buf_start; - } else { - // Parse error - return HPACK_ERROR_COMPRESSION_ERROR; - } + return HPACK_ERROR_COMPRESSION_ERROR; } cursor += read_bytes; break; case HPACK_FIELD_TABLESIZE_UPDATE: read_bytes = update_dynamic_table_size(cursor, buf_end, dynamic_table); if (read_bytes == HPACK_ERROR_COMPRESSION_ERROR) { - if (cont) { - // Parsing a part of headers is done - return cursor - buf_start; - } else { - // Parse error - return HPACK_ERROR_COMPRESSION_ERROR; - } + return HPACK_ERROR_COMPRESSION_ERROR; } cursor += read_bytes; continue; @@ -700,7 +682,8 @@ http2_parse_header_fragment(HTTPHdr *hdr, IOVec iov, Http2DynamicTable &dynamic_ return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; } - // rfc7540,sec8.1.2.2: Any message containing connection-specific header fields MUST be treated as malformed + // rfc7540,sec8.1.2.2: Any message containing connection-specific header + // fields MUST be treated as malformed if (name == MIME_FIELD_CONNECTION) { return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; } @@ -738,10 +721,10 @@ http2_parse_header_fragment(HTTPHdr *hdr, IOVec iov, Http2DynamicTable &dynamic_ return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; } } - } while (cursor < buf_end); + } // Psuedo headers is insufficient - if (hdr->fields_count() < 4 && !cont) { + if (hdr->fields_count() < 4) { return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; } @@ -788,7 +771,6 @@ Http2::init() static_cast<int>(HTTP2_STAT_TOTAL_CLIENT_CONNECTION_COUNT), RecRawStatSyncSum); } - #if TS_HAS_TESTS #include "ts/TestBox.h" @@ -799,10 +781,11 @@ const static int MAX_TEST_FIELD_NUM = 8; /*********************************************************************************** * * - * Test cases for regression test * + * Test cases for regression test * * * - * Some test cases are based on examples of specification. * - * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09#appendix-D * + * Some test cases are based on examples of specification. * + * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09#appendix-D + ** * * ***********************************************************************************/ @@ -909,7 +892,7 @@ const static struct { /*********************************************************************************** * * - * Regression test codes * + * Regression test codes * * * ***********************************************************************************/ @@ -1153,9 +1136,9 @@ REGRESSION_TEST(HPACK_Decode)(RegressionTest *t, int, int *pstatus) ats_scoped_obj<HTTPHdr> headers(new HTTPHdr); headers->create(HTTP_TYPE_REQUEST); - http2_parse_header_fragment(headers, - make_iovec(encoded_field_test_case[i].encoded_field, encoded_field_test_case[i].encoded_field_len), - dynamic_table, false); + http2_decode_header_blocks(headers, encoded_field_test_case[i].encoded_field, + encoded_field_test_case[i].encoded_field + encoded_field_test_case[i].encoded_field_len, + dynamic_table); for (unsigned int j = 0; j < sizeof(raw_field_test_case[i]) / sizeof(raw_field_test_case[i][0]); j++) { const char *expected_name = raw_field_test_case[i][j].raw_name; http://git-wip-us.apache.org/repos/asf/trafficserver/blob/00ce2f11/proxy/http2/HTTP2.h ---------------------------------------------------------------------- diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h index 3183ec0..29f31fe 100644 --- a/proxy/http2/HTTP2.h +++ b/proxy/http2/HTTP2.h @@ -34,7 +34,8 @@ class HTTPHdr; typedef unsigned Http2StreamId; -// 6.9.2 Initial Flow Control Window Size - the flow control window can be come negative +// 6.9.2 Initial Flow Control Window Size - the flow control window can be come +// negative // so we need to track it with a signed type. typedef int32_t Http2WindowSize; @@ -62,7 +63,8 @@ const uint32_t HTTP2_MAX_HEADER_LIST_SIZE = UINT_MAX; // Statistics enum { - HTTP2_STAT_CURRENT_CLIENT_SESSION_COUNT, // Current # of active HTTP2 sessions. + HTTP2_STAT_CURRENT_CLIENT_SESSION_COUNT, // Current # of active HTTP2 + // sessions. HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT, // Current # of active HTTP2 streams. HTTP2_STAT_TOTAL_TRANSACTIONS_TIME, // Total stream time and streams HTTP2_STAT_TOTAL_CLIENT_CONNECTION_COUNT, // Total connections running http2 @@ -247,6 +249,8 @@ struct Http2Priority { // 6.2 HEADERS Format struct Http2HeadersParameter { + Http2HeadersParameter() : pad_length(0) {} + uint8_t pad_length; Http2Priority priority; }; @@ -258,8 +262,10 @@ struct Http2Goaway { Http2StreamId last_streamid; uint32_t error_code; - // NOTE: we don't (de)serialize the variable length debug data at this layer because there's - // really nothing we can do with it without some out of band agreement. Trying to deal with it + // NOTE: we don't (de)serialize the variable length debug data at this layer + // because there's + // really nothing we can do with it without some out of band agreement. Trying + // to deal with it // just complicates memory management. }; @@ -314,7 +320,7 @@ bool http2_parse_goaway(IOVec, Http2Goaway &); bool http2_parse_window_update(IOVec, uint32_t &); -int64_t http2_parse_header_fragment(HTTPHdr *, IOVec, Http2DynamicTable &, bool); +int64_t http2_decode_header_blocks(HTTPHdr *, const uint8_t *, const uint8_t *, Http2DynamicTable &); MIMEParseResult convert_from_2_to_1_1_header(HTTPHdr *); @@ -322,9 +328,10 @@ int64_t http2_write_psuedo_headers(HTTPHdr *, uint8_t *, uint64_t, Http2DynamicT int64_t http2_write_header_fragment(HTTPHdr *, MIMEFieldIter &, uint8_t *, uint64_t, Http2DynamicTable &, bool &); - -// Not sure where else to put this, but figure this is as good of a start as anything else. -// Right now, only the static init() is available, which sets up some basic librecords +// Not sure where else to put this, but figure this is as good of a start as +// anything else. +// Right now, only the static init() is available, which sets up some basic +// librecords // dependencies. class Http2 { http://git-wip-us.apache.org/repos/asf/trafficserver/blob/00ce2f11/proxy/http2/Http2ClientSession.cc ---------------------------------------------------------------------- diff --git a/proxy/http2/Http2ClientSession.cc b/proxy/http2/Http2ClientSession.cc index 386195b..ac990c2 100644 --- a/proxy/http2/Http2ClientSession.cc +++ b/proxy/http2/Http2ClientSession.cc @@ -42,7 +42,8 @@ ClassAllocator<Http2ClientSession> http2ClientSessionAllocator("http2ClientSessionAllocator"); -// memcpy the requested bytes from the IOBufferReader, returning how many were actually copied. +// memcpy the requested bytes from the IOBufferReader, returning how many were +// actually copied. static inline unsigned copy_from_buffer_reader(void *dst, IOBufferReader *reader, unsigned nbytes) { @@ -95,7 +96,8 @@ Http2ClientSession::start() // 3.5 HTTP/2 Connection Preface. Upon establishment of a TCP connection and // determination that HTTP/2 will be used by both peers, each endpoint MUST // send a connection preface as a final confirmation ... - // this->write_buffer->write(HTTP2_CONNECTION_PREFACE, HTTP2_CONNECTION_PREFACE_LEN); + // this->write_buffer->write(HTTP2_CONNECTION_PREFACE, + // HTTP2_CONNECTION_PREFACE_LEN); this->connection_state.init(); send_connection_event(&this->connection_state, HTTP2_SESSION_EVENT_INIT, this); @@ -151,7 +153,8 @@ Http2ClientSession::set_upgrade_context(HTTPHdr *h) Http2SettingsParameter param; if (!http2_parse_settings_parameter(make_iovec(out_buf + nbytes, HTTP2_SETTINGS_PARAMETER_LEN), param) || !http2_settings_parameter_is_valid(param)) { - // TODO ignore incoming invalid parameters and send suitable SETTINGS frame. + // TODO ignore incoming invalid parameters and send suitable SETTINGS + // frame. } upgrade_context.client_settings.set((Http2SettingsIdentifier)param.id, param.value); } @@ -187,7 +190,8 @@ Http2ClientSession::do_io_shutdown(ShutdownHowTo_t howto) this->client_vc->do_io_shutdown(howto); } -// XXX Currently, we don't have a half-closed state, but we will need to implement that. After we send a GOAWAY, there +// XXX Currently, we don't have a half-closed state, but we will need to +// implement that. After we send a GOAWAY, there // are scenarios where we would like to complete the outstanding streams. void @@ -295,8 +299,10 @@ Http2ClientSession::state_read_connection_preface(int event, void *edata) } } - // XXX We don't have enough data to check the connection preface. We should reset the accept inactivity - // timeout. We should have a maximum timeout to get the session started though. + // XXX We don't have enough data to check the connection preface. We should + // reset the accept inactivity + // timeout. We should have a maximum timeout to get the session started + // though. vio->reenable(); return 0; @@ -352,9 +358,11 @@ Http2ClientSession::state_start_frame_read(int event, void *edata) } // CONTINUATIONs MUST follow behind HEADERS which doesn't have END_HEADERS - if (this->connection_state.get_continued_id() != 0 && this->current_hdr.type != HTTP2_FRAME_TYPE_CONTINUATION) { + Http2StreamId continued_stream_id = this->connection_state.get_continued_stream_id(); + + if (continued_stream_id != 0 && this->current_hdr.type != HTTP2_FRAME_TYPE_CONTINUATION) { SCOPED_MUTEX_LOCK(lock, this->connection_state.mutex, this_ethread()); - if (!this->connection_state.is_state_closed()) { + if (!this->connection_state.is_state_closed() || continued_stream_id != this->current_hdr.streamid) { this->connection_state.send_goaway_frame(this->current_hdr.streamid, HTTP2_ERROR_PROTOCOL_ERROR); } return 0; http://git-wip-us.apache.org/repos/asf/trafficserver/blob/00ce2f11/proxy/http2/Http2ConnectionState.cc ---------------------------------------------------------------------- diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc index 7cae2fd..2e00220 100644 --- a/proxy/http2/Http2ConnectionState.cc +++ b/proxy/http2/Http2ConnectionState.cc @@ -71,7 +71,8 @@ rcv_data_frame(Http2ClientSession &cs, Http2ConnectionState &cstate, const Http2 DebugSsn(&cs, "http2_cs", "[%" PRId64 "] Received DATA frame.", cs.connection_id()); - // If a DATA frame is received whose stream identifier field is 0x0, the recipient MUST + // If a DATA frame is received whose stream identifier field is 0x0, the + // recipient MUST // respond with a connection error of type PROTOCOL_ERROR. if (!http2_is_client_streamid(id)) { return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); @@ -86,7 +87,8 @@ rcv_data_frame(Http2ClientSession &cs, Http2ConnectionState &cstate, const Http2 } } - // If a DATA frame is received whose stream is not in "open" or "half closed (local)" state, + // If a DATA frame is received whose stream is not in "open" or "half closed + // (local)" state, // the recipient MUST respond with a stream error of type STREAM_CLOSED. if (stream->get_state() != HTTP2_STREAM_STATE_OPEN && stream->get_state() != HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL) { return Http2Error(HTTP2_ERROR_CLASS_STREAM, HTTP2_ERROR_STREAM_CLOSED); @@ -158,28 +160,33 @@ rcv_data_frame(Http2ClientSession &cs, Http2ConnectionState &cstate, const Http2 return Http2Error(HTTP2_ERROR_CLASS_NONE); } +/* + * [RFC 7540] 6.2 HEADERS Frame + * + * NOTE: HEADERS Frame and CONTINUATION Frame + * 1. A HEADERS frame with the END_STREAM flag set can be followed by + *CONTINUATION frames on the same stream. + * 2. A HEADERS frame without the END_HEADERS flag set MUST be followed by a + *CONTINUATION frame + */ static Http2Error rcv_headers_frame(Http2ClientSession &cs, Http2ConnectionState &cstate, const Http2Frame &frame) { - char buf[BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS])]; - unsigned nbytes = 0; - Http2StreamId id = frame.header().streamid; - Http2HeadersParameter params; + const Http2StreamId stream_id = frame.header().streamid; const uint32_t payload_length = frame.header().length; DebugSsn(&cs, "http2_cs", "[%" PRId64 "] Received HEADERS frame.", cs.connection_id()); - if (!http2_is_client_streamid(id)) { + if (!http2_is_client_streamid(stream_id)) { return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } - if (id <= cstate.get_latest_stream_id()) { + if (stream_id <= cstate.get_latest_stream_id()) { return Http2Error(HTTP2_ERROR_CLASS_STREAM, HTTP2_ERROR_STREAM_CLOSED); } // Create new stream - Http2Stream *stream = cstate.create_stream(id); - + Http2Stream *stream = cstate.create_stream(stream_id); if (!stream) { return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } @@ -192,83 +199,71 @@ rcv_headers_frame(Http2ClientSession &cs, Http2ConnectionState &cstate, const Ht return Http2Error(HTTP2_ERROR_CLASS_STREAM, HTTP2_ERROR_PROTOCOL_ERROR); } - // A receiver MUST treat the receipt of any other type of frame or - // a frame on a different stream as a connection error of type PROTOCOL_ERROR. - if (cstate.get_continued_id() != 0) { - return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); - } + Http2HeadersParameter params; + uint32_t header_block_fragment_offset = 0; + uint32_t header_block_fragment_length = payload_length; - // Change state. If changing is invalid, raise PROTOCOL_ERROR - if (!stream->change_state(frame.header().type, frame.header().flags)) { - return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); + if (frame.header().flags & HTTP2_FLAGS_HEADERS_END_STREAM) { + stream->end_stream = true; } - // Check whether padding exists or not. + // NOTE: Strip padding if exists if (frame.header().flags & HTTP2_FLAGS_HEADERS_PADDED) { - frame.reader()->memcpy(buf, HTTP2_HEADERS_PADLEN_LEN, nbytes); - nbytes += HTTP2_HEADERS_PADLEN_LEN; + uint8_t buf[HTTP2_HEADERS_PADLEN_LEN] = {0}; + frame.reader()->memcpy(buf, HTTP2_HEADERS_PADLEN_LEN); + if (!http2_parse_headers_parameter(make_iovec(buf, HTTP2_HEADERS_PADLEN_LEN), params)) { return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } if (params.pad_length > payload_length) { - // If the length of the padding is the length of the - // frame payload or greater, the recipient MUST treat this as a - // connection error of type PROTOCOL_ERROR. return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } - } else { - params.pad_length = 0; + + header_block_fragment_offset += HTTP2_HEADERS_PADLEN_LEN; + header_block_fragment_length -= (HTTP2_HEADERS_PADLEN_LEN + params.pad_length); } - // Check whether parameters of priority exist or not. - // TODO Currently priority is NOT supported. + // NOTE: Parse priority parameters if exists + // TODO: Currently priority is NOT supported. TS-3535 will fix this. if (frame.header().flags & HTTP2_FLAGS_HEADERS_PRIORITY) { - frame.reader()->memcpy(buf, HTTP2_PRIORITY_LEN, nbytes); - nbytes += HTTP2_PRIORITY_LEN; + uint8_t buf[HTTP2_PRIORITY_LEN] = {0}; + + frame.reader()->memcpy(buf, HTTP2_PRIORITY_LEN, header_block_fragment_offset); if (!http2_parse_priority_parameter(make_iovec(buf, HTTP2_PRIORITY_LEN), params.priority)) { return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } - } - // Parse request headers encoded by HPACK - const uint32_t unpadded_length = payload_length - params.pad_length; - uint32_t remaining_bytes = 0; - for (;;) { - size_t read_len = sizeof(buf) - remaining_bytes; - if (nbytes + read_len > unpadded_length) - read_len -= nbytes + read_len - unpadded_length; - unsigned read_bytes = read_rcv_buffer(buf + remaining_bytes, read_len, nbytes, frame); - IOVec header_block_fragment = make_iovec(buf, read_bytes + remaining_bytes); + header_block_fragment_offset += HTTP2_PRIORITY_LEN; + header_block_fragment_length -= HTTP2_PRIORITY_LEN; + } - bool cont = nbytes < payload_length || !(frame.header().flags & HTTP2_FLAGS_HEADERS_END_HEADERS); - int64_t decoded_bytes = stream->decode_request_header(header_block_fragment, *cstate.local_dynamic_table, cont); + stream->header_blocks = static_cast<uint8_t *>(ats_malloc(header_block_fragment_length)); + frame.reader()->memcpy(stream->header_blocks, header_block_fragment_length, header_block_fragment_offset); - // 4.3. A receiver MUST terminate the connection with a - // connection error of type COMPRESSION_ERROR if it does - // not decompress a header block. - if (decoded_bytes == 0 || decoded_bytes == HPACK_ERROR_COMPRESSION_ERROR) { - return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_COMPRESSION_ERROR); - } + stream->header_blocks_length = header_block_fragment_length; - if (decoded_bytes == HPACK_ERROR_HTTP2_PROTOCOL_ERROR) { - return Http2Error(HTTP2_ERROR_CLASS_STREAM, HTTP2_ERROR_PROTOCOL_ERROR); + if (frame.header().flags & HTTP2_FLAGS_HEADERS_END_HEADERS) { + // NOTE: If there are END_HEADERS flag, decode stored Header Blocks. + if (!stream->change_state(HTTP2_FRAME_TYPE_HEADERS, frame.header().flags)) { + return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } - remaining_bytes = header_block_fragment.iov_len - decoded_bytes; - memmove(buf, buf + header_block_fragment.iov_len - remaining_bytes, remaining_bytes); + const int64_t decoded_bytes = stream->decode_header_blocks(*cstate.local_dynamic_table); - if (nbytes >= payload_length - params.pad_length) { - if (!(frame.header().flags & HTTP2_FLAGS_HEADERS_END_HEADERS)) { - cstate.set_continued_headers(buf, remaining_bytes, id); - } - break; + if (decoded_bytes == 0 || decoded_bytes == HPACK_ERROR_COMPRESSION_ERROR) { + return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_COMPRESSION_ERROR); + } else if (decoded_bytes == HPACK_ERROR_HTTP2_PROTOCOL_ERROR) { + return Http2Error(HTTP2_ERROR_CLASS_STREAM, HTTP2_ERROR_PROTOCOL_ERROR); } - } - // backposting - if (frame.header().flags & HTTP2_FLAGS_HEADERS_END_HEADERS) { stream->init_fetcher(cstate); + } else { + // NOTE: Expect CONTINUATION Frame. Do NOT change state of stream or decode + // Header Blocks. + DebugSsn(&cs, "http2_cs", "[%" PRId64 "] No END_HEADERS flag, expecting CONTINUATION frame.", cs.connection_id()); + + cstate.set_continued_stream_id(stream_id); } return Http2Error(HTTP2_ERROR_CLASS_NONE); @@ -415,7 +410,8 @@ rcv_settings_frame(Http2ClientSession &cs, Http2ConnectionState &cstate, const H cstate.client_settings.set((Http2SettingsIdentifier)param.id, param.value); } - // 6.5 Once all values have been applied, the recipient MUST immediately emit a + // 6.5 Once all values have been applied, the recipient MUST immediately emit + // a // SETTINGS frame with the ACK flag set. Http2Frame ackFrame(HTTP2_FRAME_TYPE_SETTINGS, 0, HTTP2_FLAGS_SETTINGS_ACK); cstate.ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &ackFrame); @@ -441,8 +437,10 @@ rcv_ping_frame(Http2ClientSession &cs, Http2ConnectionState &cstate, const Http2 DebugSsn(&cs, "http2_cs", "[%" PRId64 "] Received PING frame.", cs.connection_id()); - // If a PING frame is received with a stream identifier field value other than - // 0x0, the recipient MUST respond with a connection error of type PROTOCOL_ERROR. + // If a PING frame is received with a stream identifier field value other + // than + // 0x0, the recipient MUST respond with a connection error of type + // PROTOCOL_ERROR. if (frame.header().streamid != 0x0) { return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } @@ -518,8 +516,10 @@ rcv_window_update_frame(Http2ClientSession &cs, Http2ConnectionState &cstate, co frame.reader()->memcpy(buf, sizeof(buf), 0); http2_parse_window_update(make_iovec(buf, sizeof(buf)), size); - // A receiver MUST treat the receipt of a WINDOW_UPDATE frame with a connection - // flow control window increment of 0 as a connection error of type PROTOCOL_ERROR; + // A receiver MUST treat the receipt of a WINDOW_UPDATE frame with a + // connection + // flow control window increment of 0 as a connection error of type + // PROTOCOL_ERROR; if (size == 0) { return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } @@ -553,7 +553,8 @@ rcv_window_update_frame(Http2ClientSession &cs, Http2ConnectionState &cstate, co http2_parse_window_update(make_iovec(buf, sizeof(buf)), size); // A receiver MUST treat the receipt of a WINDOW_UPDATE frame with an - // flow control window increment of 0 as a stream error of type PROTOCOL_ERROR; + // flow control window increment of 0 as a stream error of type + // PROTOCOL_ERROR; if (size == 0) { return Http2Error(HTTP2_ERROR_CLASS_STREAM, HTTP2_ERROR_PROTOCOL_ERROR); } @@ -579,19 +580,30 @@ rcv_window_update_frame(Http2ClientSession &cs, Http2ConnectionState &cstate, co return Http2Error(HTTP2_ERROR_CLASS_NONE); } +/* + * [RFC 7540] 6.10 CONTINUATION + * + * NOTE: Logically, the CONTINUATION frames are part of the HEADERS frame. ([RFC + *7540] 6.2 HEADERS) + * + */ static Http2Error rcv_continuation_frame(Http2ClientSession &cs, Http2ConnectionState &cstate, const Http2Frame &frame) { - char buf[BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_CONTINUATION])]; - unsigned nbytes = 0; const Http2StreamId stream_id = frame.header().streamid; + const uint32_t payload_length = frame.header().length; DebugSsn(&cs, "http2_cs", "[%" PRId64 "] Received CONTINUATION frame.", cs.connection_id()); + if (!http2_is_client_streamid(stream_id)) { + return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); + } + // Find opened stream // CONTINUATION frames MUST be associated with a stream. If a // CONTINUATION frame is received whose stream identifier field is 0x0, - // the recipient MUST respond with a connection error (Section 5.4.1) of + // the recipient MUST respond with a connection error ([RFC 7540] Section + // 5.4.1) of // type PROTOCOL_ERROR. Http2Stream *stream = cstate.find_stream(stream_id); if (stream == NULL) { @@ -600,70 +612,54 @@ rcv_continuation_frame(Http2ClientSession &cs, Http2ConnectionState &cstate, con } else { return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } + } else { + switch (stream->get_state()) { + case HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE: + return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_STREAM_CLOSED); + case HTTP2_STREAM_STATE_IDLE: + break; + default: + return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); + } } - // A CONTINUATION frame MUST be preceded by a HEADERS, PUSH_PROMISE or - // CONTINUATION frame without the END_HEADERS flag set. A recipient - // that observes violation of this rule MUST respond with a connection - // error (Section 5.4.1) of type PROTOCOL_ERROR. - if (stream->get_state() != HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE && stream->get_state() != HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL) { + // keep track of how many bytes we get in the frame + stream->request_header_length += payload_length; + if (stream->request_header_length > Http2::max_request_header_size) { + Error("HTTP/2 payload for headers exceeded: %u", stream->request_header_length); return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } - // A receiver MUST treat the receipt of any other type of frame or - // a frame on a different stream as a connection error of type PROTOCOL_ERROR. - if (stream->get_id() != cstate.get_continued_id()) { + if (!stream->header_blocks) { return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } - const IOVec remaining_data = cstate.get_continued_headers(); - uint32_t remaining_bytes = remaining_data.iov_len; - if (remaining_bytes && remaining_data.iov_base) { - memcpy(buf, remaining_data.iov_base, remaining_data.iov_len); - } - - // Parse request headers encoded by HPACK - for (;;) { - unsigned read_bytes = read_rcv_buffer(buf + remaining_bytes, sizeof(buf) - remaining_bytes, nbytes, frame); - IOVec header_block_fragment = make_iovec(buf, read_bytes + remaining_bytes); - - // keep track of how many bytes we get in the frame - stream->request_header_length += frame.header().length; - if (stream->request_header_length > Http2::max_request_header_size) { - Error("HTTP/2 payload for headers exceeded: %u", stream->request_header_length); - // XXX Should we respond with 431 (Request Header Fields Too Large) ? - return Http2Error(HTTP2_ERROR_CLASS_STREAM, HTTP2_ERROR_PROTOCOL_ERROR); - } + uint32_t header_blocks_offset = stream->header_blocks_length; + stream->header_blocks_length += payload_length; - bool cont = nbytes < frame.header().length || !(frame.header().flags & HTTP2_FLAGS_HEADERS_END_HEADERS); - int64_t decoded_bytes = stream->decode_request_header(header_block_fragment, *cstate.local_dynamic_table, cont); + stream->header_blocks = static_cast<uint8_t *>(ats_realloc(stream->header_blocks, stream->header_blocks_length)); + frame.reader()->memcpy(stream->header_blocks + header_blocks_offset, payload_length); - // A receiver MUST terminate the connection with a - // connection error of type COMPRESSION_ERROR if it does - // not decompress a header block. - if (decoded_bytes == 0 || decoded_bytes == HPACK_ERROR_COMPRESSION_ERROR) { - return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_COMPRESSION_ERROR); - } + if (frame.header().flags & HTTP2_FLAGS_HEADERS_END_HEADERS) { + // NOTE: If there are END_HEADERS flag, decode stored Header Blocks. + cstate.clear_continued_stream_id(); - if (decoded_bytes == HPACK_ERROR_HTTP2_PROTOCOL_ERROR) { + if (!stream->change_state(HTTP2_FRAME_TYPE_CONTINUATION, frame.header().flags)) { return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } - remaining_bytes = header_block_fragment.iov_len - decoded_bytes; - memmove(buf, buf + header_block_fragment.iov_len - remaining_bytes, remaining_bytes); + const int64_t decoded_bytes = stream->decode_header_blocks(*cstate.local_dynamic_table); - if (nbytes >= frame.header().length) { - if (!(frame.header().flags & HTTP2_FLAGS_HEADERS_END_HEADERS)) { - cstate.set_continued_headers(buf, remaining_bytes, stream_id); - } - break; + if (decoded_bytes == 0 || decoded_bytes == HPACK_ERROR_COMPRESSION_ERROR) { + return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_COMPRESSION_ERROR); + } else if (decoded_bytes == HPACK_ERROR_HTTP2_PROTOCOL_ERROR) { + return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } - } - // backposting - if (frame.header().flags & HTTP2_FLAGS_HEADERS_END_HEADERS) { - cstate.finish_continued_headers(); stream->init_fetcher(cstate); + } else { + // NOTE: Expect another CONTINUATION Frame. Do nothing. + DebugSsn(&cs, "http2_cs", "[%" PRId64 "] No END_HEADERS flag, expecting CONTINUATION frame.", cs.connection_id()); } return Http2Error(HTTP2_ERROR_CLASS_NONE); @@ -693,10 +689,12 @@ Http2ConnectionState::main_event_handler(int event, void *edata) // 3.5 HTTP/2 Connection Preface. Upon establishment of a TCP connection and // determination that HTTP/2 will be used by both peers, each endpoint MUST - // send a connection preface as a final confirmation ... The server connection + // send a connection preface as a final confirmation ... The server + // connection // preface consists of a potentially empty SETTINGS frame. - // Load the server settings from the records.config / RecordsConfig.cc settings. + // Load the server settings from the records.config / RecordsConfig.cc + // settings. Http2ConnectionSettings configured_settings; configured_settings.settings_from_configs(); send_settings_frame(configured_settings); @@ -723,7 +721,8 @@ Http2ConnectionState::main_event_handler(int event, void *edata) Http2Error error; // 5.5 Extending HTTP/2 - // Implementations MUST discard frames that have unknown or unsupported types. + // Implementations MUST discard frames that have unknown or unsupported + // types. if (frame->header().type >= HTTP2_FRAME_TYPE_MAX) { DebugSsn(this->ua_session, "http2_cs", "[%" PRId64 "] Discard a frame which has unknown type, type=%x", this->ua_session->connection_id(), frame->header().type); @@ -740,9 +739,12 @@ Http2ConnectionState::main_event_handler(int event, void *edata) if (error.cls == HTTP2_ERROR_CLASS_CONNECTION) { this->send_goaway_frame(last_streamid, error.code); cleanup_streams(); - // XXX We need to think a bit harder about how to coordinate the client session and the - // protocol connection. At this point, the protocol is shutting down, but there's no way - // to tell that to the client session. Perhaps this could be solved by implementing the + // XXX We need to think a bit harder about how to coordinate the client + // session and the + // protocol connection. At this point, the protocol is shutting down, + // but there's no way + // to tell that to the client session. Perhaps this could be solved by + // implementing the // half-closed state ... SET_HANDLER(&Http2ConnectionState::state_closed); } else if (error.cls == HTTP2_ERROR_CLASS_STREAM) { @@ -857,34 +859,6 @@ Http2ConnectionState::cleanup_streams() } void -Http2ConnectionState::set_continued_headers(const char *buf, uint32_t len, Http2StreamId id) -{ - DebugSsn(this->ua_session, "http2_cs", "[%" PRId64 "] Send CONTINUATION frame.", this->ua_session->connection_id()); - - if (buf && len > 0) { - if (!continued_buffer.iov_base) { - continued_buffer.iov_base = static_cast<uint8_t *>(ats_malloc(len)); - } else if (continued_buffer.iov_len < len) { - continued_buffer.iov_base = ats_realloc(continued_buffer.iov_base, len); - } - continued_buffer.iov_len = len; - - memcpy(continued_buffer.iov_base, buf, continued_buffer.iov_len); - } - - continued_id = id; -} - -void -Http2ConnectionState::finish_continued_headers() -{ - continued_id = 0; - ats_free(continued_buffer.iov_base); - continued_buffer.iov_base = NULL; - continued_buffer.iov_len = 0; -} - -void Http2ConnectionState::delete_stream(Http2Stream *stream) { stream_list.remove(stream); @@ -958,8 +932,10 @@ Http2ConnectionState::send_data_frame(FetchSM *fetch_sm) if (flags & HTTP2_FLAGS_DATA_END_STREAM) { // Delete a stream immediately - // TODO its should not be deleted for a several time to handling RST_STREAM and WINDOW_UPDATE. - // See 'closed' state written at https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.1 + // TODO its should not be deleted for a several time to handling + // RST_STREAM and WINDOW_UPDATE. + // See 'closed' state written at + // https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.1 this->delete_stream(stream); break; } @@ -983,7 +959,8 @@ Http2ConnectionState::send_headers_frame(FetchSM *fetch_sm) payload_length += http2_write_psuedo_headers(resp_header, payload_buffer, buf_len, *(this->remote_dynamic_table)); // If response body is empty, set END_STREAM flag to HEADERS frame - // Must check to ensure content-length is there. Otherwise the value defaults to 0 + // Must check to ensure content-length is there. Otherwise the value defaults + // to 0 if (resp_header->presence(MIME_PRESENCE_CONTENT_LENGTH) && resp_header->get_content_length() == 0) { flags |= HTTP2_FLAGS_HEADERS_END_STREAM; } @@ -1168,7 +1145,7 @@ Http2Stream::set_body_to_fetcher(const void *data, size_t len) } /* - * 5.1. Stream States + * 5.1. Stream States * * +--------+ * PP | | PP @@ -1202,8 +1179,13 @@ Http2Stream::change_state(uint8_t type, uint8_t flags) switch (_state) { case HTTP2_STREAM_STATE_IDLE: if (type == HTTP2_FRAME_TYPE_HEADERS) { - if (flags & HTTP2_FLAGS_HEADERS_END_STREAM) { - // Skip OPEN _state + if (end_stream && flags & HTTP2_FLAGS_HEADERS_END_HEADERS) { + _state = HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; + } else { + _state = HTTP2_STREAM_STATE_OPEN; + } + } else if (type == HTTP2_FRAME_TYPE_CONTINUATION) { + if (end_stream && flags & HTTP2_FLAGS_CONTINUATION_END_HEADERS) { _state = HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; } else { _state = HTTP2_STREAM_STATE_OPEN; http://git-wip-us.apache.org/repos/asf/trafficserver/blob/00ce2f11/proxy/http2/Http2ConnectionState.h ---------------------------------------------------------------------- diff --git a/proxy/http2/Http2ConnectionState.h b/proxy/http2/Http2ConnectionState.h index 14d7f01..41297f0 100644 --- a/proxy/http2/Http2ConnectionState.h +++ b/proxy/http2/Http2ConnectionState.h @@ -35,7 +35,8 @@ class Http2ConnectionSettings public: Http2ConnectionSettings() { - // 6.5.2. Defined SETTINGS Parameters. These should generally not be modified, + // 6.5.2. Defined SETTINGS Parameters. These should generally not be + // modified, // only if the protocol changes should these change. settings[indexof(HTTP2_SETTINGS_ENABLE_PUSH)] = 0; // Disabled for now @@ -99,14 +100,14 @@ class Http2Stream { public: Http2Stream(Http2StreamId sid = 0, ssize_t initial_rwnd = Http2::initial_window_size) - : client_rwnd(initial_rwnd), server_rwnd(initial_rwnd), _id(sid), _state(HTTP2_STREAM_STATE_IDLE), _fetch_sm(NULL), - body_done(false), data_length(0) + : client_rwnd(initial_rwnd), server_rwnd(initial_rwnd), header_blocks(NULL), header_blocks_length(0), request_header_length(0), + end_stream(false), _id(sid), _state(HTTP2_STREAM_STATE_IDLE), _fetch_sm(NULL), body_done(false), data_length(0) { _thread = this_ethread(); HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT, _thread); _start_time = ink_hrtime(); + // FIXME: Are you sure? every "stream" needs _req_header? _req_header.create(HTTP_TYPE_REQUEST); - request_header_length = 0; } ~Http2Stream() @@ -120,6 +121,9 @@ public: _fetch_sm->ext_destroy(); _fetch_sm = NULL; } + if (header_blocks) { + ats_free(header_blocks); + } } // Operate FetchSM @@ -154,9 +158,10 @@ public: bool change_state(uint8_t type, uint8_t flags); int64_t - decode_request_header(const IOVec &iov, Http2DynamicTable &dynamic_table, bool cont) + decode_header_blocks(Http2DynamicTable &dynamic_table) { - return http2_parse_header_fragment(&_req_header, iov, dynamic_table, cont); + return http2_decode_header_blocks(&_req_header, (const uint8_t *)header_blocks, + (const uint8_t *)header_blocks + header_blocks_length, dynamic_table); } // Check entire DATA payload length if content-length: header is exist @@ -177,7 +182,12 @@ public: LINK(Http2Stream, link); - uint32_t request_header_length; + uint8_t *header_blocks; + uint32_t header_blocks_length; // total length of header blocks (not include + // Padding or other fields) + uint32_t request_header_length; // total length of payload (include Padding + // and other fields) + bool end_stream; private: ink_hrtime _start_time; @@ -191,10 +201,10 @@ private: uint64_t data_length; }; - // Http2ConnectionState // -// Capture the semantics of a HTTP/2 connection. The client session captures the frame layer, and the +// Capture the semantics of a HTTP/2 connection. The client session captures the +// frame layer, and the // connection state captures the connection-wide state. class Http2ConnectionState : public Continuation @@ -202,7 +212,7 @@ class Http2ConnectionState : public Continuation public: Http2ConnectionState() : Continuation(NULL), ua_session(NULL), client_rwnd(Http2::initial_window_size), server_rwnd(Http2::initial_window_size), - stream_list(), latest_streamid(0), client_streams_count(0), continued_id(0) + stream_list(), latest_streamid(0), client_streams_count(0), continued_stream_id(0) { SET_HANDLER(&Http2ConnectionState::main_event_handler); } @@ -257,17 +267,20 @@ public: // Continuated header decoding Http2StreamId - get_continued_id() const + get_continued_stream_id() const + { + return continued_stream_id; + } + void + set_continued_stream_id(Http2StreamId stream_id) { - return continued_id; + continued_stream_id = stream_id; } - const IOVec & - get_continued_headers() const + void + clear_continued_stream_id() { - return continued_buffer; + continued_stream_id = 0; } - void set_continued_headers(const char *buf, uint32_t len, Http2StreamId id); - void finish_continued_headers(); // Connection level window size ssize_t client_rwnd, server_rwnd; @@ -292,17 +305,25 @@ private: Http2ConnectionState &operator=(const Http2ConnectionState &); // noncopyable // NOTE: 'stream_list' has only active streams. - // If given Stream Identifier is not found in stream_list and it is less than or equal to latest_streamid, the state of Stream + // If given Stream Identifier is not found in stream_list and it is less + // than or equal to latest_streamid, the state of Stream // is CLOSED. - // If given Stream Identifier is not found in stream_list and it is greater than latest_streamid, the state of Stream is IDLE. + // If given Stream Identifier is not found in stream_list and it is greater + // than latest_streamid, the state of Stream is IDLE. DLL<Http2Stream> stream_list; Http2StreamId latest_streamid; // Counter for current acive streams which is started by client uint32_t client_streams_count; - // The buffer used for storing incomplete fragments of a header field which consists of multiple frames. - Http2StreamId continued_id; + // NOTE: Id of stream which MUST receive CONTINUATION frame. + // - [RFC 7540] 6.2 HEADERS + // "A HEADERS frame without the END_HEADERS flag set MUST be followed by a + // CONTINUATION frame for the same stream." + // - [RFC 7540] 6.10 CONTINUATION + // "If the END_HEADERS bit is not set, this frame MUST be followed by + // another CONTINUATION frame." + Http2StreamId continued_stream_id; IOVec continued_buffer; };
