diff --git a/README.rst b/README.rst index 6460e8e5..73c79bc9 100644 --- a/README.rst +++ b/README.rst @@ -17,10 +17,12 @@ everything yet. Development Status ------------------ -We started to implement h2-13 -(http://tools.ietf.org/html/draft-ietf-httpbis-http2-13) and the -header compression -(http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08). +We started to implement h2-14 +(http://tools.ietf.org/html/draft-ietf-httpbis-http2-14), the header +compression +(http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09) +and HTTP Alternative Services +(http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-02). The nghttp2 code base was forked from spdylay project. @@ -30,15 +32,9 @@ HTTP/2 Features Support Core frames handling Yes Dependency Tree Yes Large header (CONTINUATION) Yes -ALTSVC extension Yes \*1 +ALTSVC extension Yes =========================== ======= -* \*1 As described in draft-12, but reserved byte was removed. ALTSVC - may be removed from nghttp2 public API since it is not stabilized - yet. - -BLOCKED frame, which once existed in h2-12, was removed in h2-13. - Public Test Server ------------------ @@ -47,7 +43,7 @@ implementation. * https://nghttp2.org/ (TLS + NPN) - NPN offer ``h2-13``, ``spdy/3.1`` and ``http/1.1``. + NPN offer ``h2-14``, ``spdy/3.1`` and ``http/1.1``. ALPN is currently disabled. This endpoint requires TLSv1.2 and DHE or EDCHE with GCM cipher @@ -186,10 +182,10 @@ output from ``nghttp`` client:: $ src/nghttp -nv https://nghttp2.org [ 0.033][NPN] server offers: - * h2-13 + * h2-14 * spdy/3.1 * http/1.1 - The negotiated protocol: h2-13 + The negotiated protocol: h2-14 [ 0.068] send SETTINGS frame (niv=3) [SETTINGS_MAX_CONCURRENT_STREAMS(3):100] @@ -281,7 +277,7 @@ The HTTP Upgrade is performed like this:: [SETTINGS_MAX_CONCURRENT_STREAMS(3):100] [SETTINGS_INITIAL_WINDOW_SIZE(4):65535] [ 0.024] recv ALTSVC frame - (max-age=86400, port=443, protocol_id=h2-13, host=nghttp2.org, origin=http://nghttp2.org) + (max-age=86400, port=443, protocol_id=h2-14, host=nghttp2.org, origin=http://nghttp2.org) [ 0.024] send SETTINGS frame ; ACK (niv=0) @@ -389,7 +385,7 @@ information. Here is sample output from ``nghttpd`` server:: nghttpx - proxy +++++++++++++++ -``nghttpx`` is a multi-threaded reverse proxy for ``h2-13``, SPDY and +``nghttpx`` is a multi-threaded reverse proxy for ``h2-14``, SPDY and HTTP/1.1 and powers nghttp2.org site. It has several operation modes: ================== ============================ ============== ============= @@ -403,7 +399,7 @@ default mode HTTP/2, SPDY, HTTP/1.1 (TLS) HTTP/1.1 Reverse proxy ================== ============================ ============== ============= The interesting mode at the moment is the default mode. It works like -a reverse proxy and listens for ``h2-13``, SPDY and HTTP/1.1 and can +a reverse proxy and listens for ``h2-14``, SPDY and HTTP/1.1 and can be deployed SSL/TLS terminator for existing web server. The default mode, ``--http2-proxy`` and ``--http2-bridge`` modes use diff --git a/doc/sources/index.rst b/doc/sources/index.rst index 47acaffd..312fee6b 100644 --- a/doc/sources/index.rst +++ b/doc/sources/index.rst @@ -46,5 +46,6 @@ Released Versions Resources --------- -* http://tools.ietf.org/html/draft-ietf-httpbis-http2-13 -* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08 +* http://tools.ietf.org/html/draft-ietf-httpbis-http2-14 +* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09 +* http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-02 diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index c06b4f6c..f551116b 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -41,7 +41,7 @@ extern "C" { * The protocol version identification string of this library * supports. This identifier is used if HTTP/2 is used over TLS. */ -#define NGHTTP2_PROTO_VERSION_ID "h2-13" +#define NGHTTP2_PROTO_VERSION_ID "h2-14" /** * @macro * @@ -56,7 +56,7 @@ extern "C" { * supports. This identifier is used if HTTP/2 is used over cleartext * TCP. */ -#define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "h2c-13" +#define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "h2c-14" /** * @macro @@ -476,10 +476,6 @@ typedef enum { * The ACK flag. */ NGHTTP2_FLAG_ACK = 0x01, - /** - * The END_SEGMENT flag. - */ - NGHTTP2_FLAG_END_SEGMENT = 0x02, /** * The PADDED flag. */ @@ -510,7 +506,15 @@ typedef enum { /** * SETTINGS_INITIAL_WINDOW_SIZE */ - NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 0x04 + NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 0x04, + /** + * SETTINGS_MAX_FRAME_SIZE + */ + NGHTTP2_SETTINGS_MAX_FRAME_SIZE = 0x05, + /** + * SETTINGS_MAX_HEADER_LIST_SIZE + */ + NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 0x06 } nghttp2_settings_id; /* Note: If we add SETTINGS, update the capacity of NGHTTP2_INBOUND_NUM_IV as well */ @@ -724,7 +728,9 @@ typedef enum { NGHTTP2_HCAT_PUSH_RESPONSE = 2, /** * The HEADERS frame which does not apply for the above categories, - * which is analogous to HEADERS in SPDY. + * which is analogous to HEADERS in SPDY. If non-final response + * (e.g., status 1xx) is used, final response HEADERS frame will be + * categorized here. */ NGHTTP2_HCAT_HEADERS = 3 } nghttp2_headers_category; @@ -1505,31 +1511,17 @@ int nghttp2_option_new(nghttp2_option **option_ptr); */ void nghttp2_option_del(nghttp2_option *option); -/** - * @function - * - * This option prevents the library from sending WINDOW_UPDATE for a - * stream automatically. If this option is set to nonzero, the - * library won't send WINDOW_UPDATE for a stream and the application - * is responsible for sending WINDOW_UPDATE using - * `nghttp2_submit_window_update`. By default, this option is set to - * zero. - */ -void nghttp2_option_set_no_auto_stream_window_update(nghttp2_option *option, - int val); - /** * @function * * This option prevents the library from sending WINDOW_UPDATE for a * connection automatically. If this option is set to nonzero, the - * library won't send WINDOW_UPDATE for a connection and the - * application is responsible for sending WINDOW_UPDATE with stream ID - * 0 using `nghttp2_submit_window_update`. By default, this option is - * set to zero. + * library won't send WINDOW_UPDATE for DATA until application calls + * `nghttp2_session_consume()` to indicate the consumed amount of + * data. Don't use `nghttp2_submit_window_update()` for this purpose. + * By default, this option is set to zero. */ -void nghttp2_option_set_no_auto_connection_window_update -(nghttp2_option *option, int val); +void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val); /** * @function @@ -2067,6 +2059,28 @@ int nghttp2_session_terminate_session2(nghttp2_session *session, uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session, nghttp2_settings_id id); +/** + * @function + * + * Tells the |session| that |size| bytes for a stream denoted by + * |stream_id| were consumed by application and are ready to + * WINDOW_UPDATE. This function is intended to be used without + * automatic window update (see + * `nghttp2_option_set_no_auto_window_update()`). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + * :enum:`NGHTTP2_ERR_INVALID_STATE` + * Automatic WINDOW_UPDATE is not disabled. + */ +int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id, + size_t size); + /** * @function * @@ -2366,8 +2380,7 @@ int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags, * Submits one or more DATA frames to the stream |stream_id|. The * data to be sent are provided by |data_prd|. If |flags| contains * :enum:`NGHTTP2_FLAG_END_STREAM`, the last DATA frame has END_STREAM - * flag set. If |flags| contains :enum:`NGHTTP2_FLAG_END_SEGMENT`, - * the last DATA frame has END_SEGMENT flag set. + * flag set. * * This function does not take ownership of the |data_prd|. The * function copies the members of the |data_prd|. @@ -2609,12 +2622,11 @@ int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags, * difference. * * If the |window_size_increment| is negative, the local window size - * is decreased by -|window_size_increment|. If - * :enum:`NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE` (or - * :enum:`NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE` if |stream_id| - * is 0) is not set and the library decided that the WINDOW_UPDATE - * should be submitted, then WINDOW_UPDATE is queued with the current - * received bytes count. + * is decreased by -|window_size_increment|. If automatic + * WINDOW_UPDATE is enabled + * (`nghttp2_option_set_no_auto_window_update()`), and the library + * decided that the WINDOW_UPDATE should be submitted, then + * WINDOW_UPDATE is queued with the current received bytes count. * * If the |window_size_increment| is 0, the function does nothing and * returns 0. @@ -2641,7 +2653,7 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags, * * Only the server can send the ALTSVC frame. If |session| is * initialized as client, this function fails and returns - * :enum:`NGHTTP2_ERR_INVALID_STATE`. + * :enum:`NGHTTP2_ERR_PROTO`. * * If the |protocol_id_len| is 0, the |protocol_id| could be ``NULL``. * @@ -2819,18 +2831,6 @@ int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, */ void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater); -/** - * @function - * - * Sets the availability of reference set in the |deflater|. If - * |no_refset| is nonzero, the deflater will first emit "Reference Set - * Emptying" in the each subsequent invocation of - * `nghttp2_hd_deflate_hd()` to clear up reference set. By default, - * the deflater uses reference set. - */ -void nghttp2_hd_deflate_set_no_refset(nghttp2_hd_deflater *deflater, - uint8_t no_refset); - /** * @function * diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index c48ba3e8..228284f7 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -40,21 +40,21 @@ int nghttp2_frame_is_data_frame(uint8_t *head) void nghttp2_frame_pack_frame_hd(uint8_t* buf, const nghttp2_frame_hd *hd) { - nghttp2_put_uint16be(&buf[0], hd->length); - buf[2]= hd->type; - buf[3] = hd->flags; - nghttp2_put_uint32be(&buf[4], hd->stream_id); + nghttp2_put_uint32be(&buf[0], (uint32_t)(hd->length << 8)); + buf[3]= hd->type; + buf[4] = hd->flags; + nghttp2_put_uint32be(&buf[5], hd->stream_id); } void nghttp2_frame_unpack_frame_hd(nghttp2_frame_hd *hd, const uint8_t* buf) { - hd->length = nghttp2_get_uint16(&buf[0]) & NGHTTP2_FRAME_LENGTH_MASK; - hd->type = buf[2]; - hd->flags = buf[3]; - hd->stream_id = nghttp2_get_uint32(&buf[4]) & NGHTTP2_STREAM_ID_MASK; + hd->length = nghttp2_get_uint32(&buf[0]) >> 8; + hd->type = buf[3]; + hd->flags = buf[4]; + hd->stream_id = nghttp2_get_uint32(&buf[5]) & NGHTTP2_STREAM_ID_MASK; } -static void frame_set_hd(nghttp2_frame_hd *hd, uint16_t length, +static void frame_set_hd(nghttp2_frame_hd *hd, size_t length, uint8_t type, uint8_t flags, int32_t stream_id) { @@ -231,11 +231,10 @@ void nghttp2_frame_data_init(nghttp2_data *frame, nghttp2_private_data *pdata) { frame->hd = pdata->hd; frame->padlen = pdata->padlen; - /* flags may have NGHTTP2_FLAG_END_STREAM or - NGHTTP2_FLAG_END_SEGMENT even if the sent chunk is not the end of - the stream */ + /* flags may have NGHTTP2_FLAG_END_STREAM even if the sent chunk is + not the end of the stream */ if(!pdata->eof) { - frame->hd.flags &= ~(NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT); + frame->hd.flags &= ~NGHTTP2_FLAG_END_STREAM; } } @@ -1037,8 +1036,14 @@ int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv) return 0; } break; - default: - return 0; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + if(iv[i].value < NGHTTP2_MAX_FRAME_SIZE_MIN || + iv[i].value > NGHTTP2_MAX_FRAME_SIZE_MAX) { + return 0; + } + break; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + break; } } return 1; @@ -1047,6 +1052,7 @@ int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv) static void frame_set_pad(nghttp2_buf *buf, size_t padlen) { size_t trail_padlen; + size_t newlen; DEBUGF(fprintf(stderr, "send: padlen=%zu, shift left 1 bytes\n", padlen)); @@ -1054,9 +1060,10 @@ static void frame_set_pad(nghttp2_buf *buf, size_t padlen) --buf->pos; - buf->pos[3] |= NGHTTP2_FLAG_PADDED; + buf->pos[4] |= NGHTTP2_FLAG_PADDED; - nghttp2_put_uint16be(buf->pos, nghttp2_get_uint16(buf->pos) + padlen); + newlen = (nghttp2_get_uint32(buf->pos) >> 8) + padlen; + nghttp2_put_uint32be(buf->pos, (uint32_t)((newlen << 8) + buf->pos[3])); trail_padlen = padlen - 1; buf->pos[NGHTTP2_FRAME_HDLEN] = trail_padlen; @@ -1087,12 +1094,12 @@ int nghttp2_frame_add_pad(nghttp2_bufs *bufs, nghttp2_frame_hd *hd, * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | |Frame header | Frame payload... : - * +-+---------------+---------------------------------------------+ - * | |Frame header | Frame payload... : - * +-+---------------+---------------------------------------------+ - * | |Frame header | Frame payload... : - * +-+---------------+---------------------------------------------+ + * | |Frame header | Frame payload... : + * +-+-----------------+-------------------------------------------+ + * | |Frame header | Frame payload... : + * +-+-----------------+-------------------------------------------+ + * | |Frame header | Frame payload... : + * +-+-----------------+-------------------------------------------+ * * We arranged padding so that it is included in the first frame * completely. For padded frame, we are going to adjust buf->pos of diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index 0bd2e77c..4361fb20 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -33,7 +33,6 @@ #include "nghttp2_hd.h" #include "nghttp2_buf.h" -#define NGHTTP2_FRAME_LENGTH_MASK ((1 << 14) - 1) #define NGHTTP2_STREAM_ID_MASK ((1u << 31) - 1) #define NGHTTP2_PRI_GROUP_ID_MASK ((1u << 31) - 1) #define NGHTTP2_PRIORITY_MASK ((1u << 31) - 1) @@ -41,9 +40,12 @@ #define NGHTTP2_SETTINGS_ID_MASK ((1 << 24) - 1) /* The number of bytes of frame header. */ -#define NGHTTP2_FRAME_HDLEN 8 +#define NGHTTP2_FRAME_HDLEN 9 -#define NGHTTP2_MAX_PAYLOADLEN 16383 +#define NGHTTP2_MAX_FRAME_SIZE_MAX ((1 << 24) - 1) +#define NGHTTP2_MAX_FRAME_SIZE_MIN (1 << 14) + +#define NGHTTP2_MAX_PAYLOADLEN 16384 /* The one frame buffer length for tranmission. We may use several of them to support CONTINUATION. To account for Pad Length field, we allocate extra 1 byte, which saves extra large memcopying. */ @@ -57,7 +59,7 @@ #define NGHTTP2_DATA_PAYLOADLEN 4096 /* Maximum headers payload length, calculated in compressed form. - This applies to both transmission and reception. */ + This applies to transmission only. */ #define NGHTTP2_MAX_HEADERSLEN 65536 /* The number of bytes for each SETTINGS entry */ diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c index 1a64b8b9..c4bcda9d 100644 --- a/lib/nghttp2_hd.c +++ b/lib/nghttp2_hd.c @@ -100,7 +100,7 @@ static nghttp2_hd_entry static_table[] = { MAKE_ENT("www-authenticate", "", 4051929931u, 0u), }; -static const size_t STATIC_TABLE_LENGTH = +const size_t NGHTTP2_STATIC_TABLE_LENGTH = sizeof(static_table)/sizeof(static_table[0]); static int memeq(const void *s1, const void *s2, size_t n) @@ -281,11 +281,9 @@ static void hd_ringbuf_pop_back(nghttp2_hd_ringbuf *ringbuf) --ringbuf->len; } -static int hd_context_init(nghttp2_hd_context *context, - nghttp2_hd_role role) +static int hd_context_init(nghttp2_hd_context *context) { int rv; - context->role = role; context->bad = 0; context->hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; rv = hd_ringbuf_init @@ -314,11 +312,10 @@ int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater, size_t deflate_hd_table_bufsize_max) { int rv; - rv = hd_context_init(&deflater->ctx, NGHTTP2_HD_ROLE_DEFLATE); + rv = hd_context_init(&deflater->ctx); if(rv != 0) { return rv; } - deflater->no_refset = 0; if(deflate_hd_table_bufsize_max < NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE) { deflater->notify_table_size_change = 1; @@ -328,6 +325,7 @@ int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater, } deflater->deflate_hd_table_bufsize_max = deflate_hd_table_bufsize_max; + deflater->min_hd_table_bufsize_max = UINT32_MAX; return 0; } @@ -336,7 +334,7 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater) { int rv; - rv = hd_context_init(&inflater->ctx, NGHTTP2_HD_ROLE_INFLATE); + rv = hd_context_init(&inflater->ctx); if(rv != 0) { goto fail; } @@ -346,7 +344,6 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater) inflater->ent_keep = NULL; inflater->nv_keep = NULL; - inflater->end_headers_index = 0; inflater->opcode = NGHTTP2_HD_OPCODE_NONE; inflater->state = NGHTTP2_HD_STATE_OPCODE; @@ -364,7 +361,6 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater) inflater->newnamelen = 0; inflater->index_required = 0; inflater->no_index = 0; - inflater->ent_name = NULL; return 0; @@ -400,12 +396,6 @@ void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater) hd_context_free(&inflater->ctx); } -void nghttp2_hd_deflate_set_no_refset(nghttp2_hd_deflater *deflater, - uint8_t no_refset) -{ - deflater->no_refset = no_refset; -} - static size_t entry_room(size_t namelen, size_t valuelen) { return NGHTTP2_HD_ENTRY_OVERHEAD + namelen + valuelen; @@ -418,10 +408,8 @@ static int emit_indexed_header(nghttp2_nv *nv_out, nghttp2_hd_entry *ent) DEBUGF(fprintf(stderr, ": ")); DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr)); DEBUGF(fprintf(stderr, "\n")); - /* ent->ref may be 0. This happens if the careless stupid encoder - emits literal block larger than header table capacity with - indexing. */ - ent->flags |= NGHTTP2_HD_FLAG_EMIT; + /* ent->ref may be 0. This happens if the encoder emits literal + block larger than header table capacity with indexing. */ *nv_out = ent->nv; return 0; } @@ -462,15 +450,19 @@ static size_t encode_length(uint8_t *buf, size_t n, size_t prefix) { size_t k = (1 << prefix) - 1; size_t len = 0; + *buf &= ~k; - if(n >= k) { - *buf++ |= k; - n -= k; - ++len; - } else { + + if(n < k) { *buf++ |= n; + return 1; } + + *buf++ |= k; + n -= k; + ++len; + do { ++len; if(n >= 128) { @@ -561,20 +553,6 @@ static ssize_t decode_length(uint32_t *res, size_t *shift_ptr, int *final, return in + 1 - start; } -static int emit_clear_refset(nghttp2_bufs *bufs) -{ - int rv; - - DEBUGF(fprintf(stderr, "deflatehd: emit clear refset\n")); - - rv = nghttp2_bufs_addb(bufs, 0x30u); - if(rv != 0) { - return rv; - } - - return 0; -} - static int emit_table_size(nghttp2_bufs *bufs, size_t table_size) { int rv; @@ -584,7 +562,7 @@ static int emit_table_size(nghttp2_bufs *bufs, size_t table_size) DEBUGF(fprintf(stderr, "deflatehd: emit table_size=%zu\n", table_size)); - blocklen = count_encoded_length(table_size, 4); + blocklen = count_encoded_length(table_size, 5); if(sizeof(sb) < blocklen) { return NGHTTP2_ERR_HEADER_COMP; @@ -594,7 +572,7 @@ static int emit_table_size(nghttp2_bufs *bufs, size_t table_size) *bufp = 0x20u; - encode_length(bufp, table_size, 4); + encode_length(bufp, table_size, 5); rv = nghttp2_bufs_add(bufs, sb, blocklen); if(rv != 0) { @@ -632,14 +610,22 @@ static int emit_indexed_block(nghttp2_bufs *bufs, size_t idx) return 0; } -static int emit_string(nghttp2_bufs *bufs, - size_t enclen, int huffman, - const uint8_t *str, size_t len) +static int emit_string(nghttp2_bufs *bufs, const uint8_t *str, size_t len) { int rv; uint8_t sb[16]; uint8_t *bufp; size_t blocklen; + size_t enclen; + int huffman = 0; + + enclen = nghttp2_hd_huff_encode_count(str, len); + + if(enclen < len) { + huffman = 1; + } else { + enclen = len; + } blocklen = count_encoded_length(enclen, 7); @@ -691,9 +677,7 @@ static int emit_indname_block(nghttp2_bufs *bufs, size_t idx, { int rv; uint8_t *bufp; - size_t encvallen; size_t blocklen; - int huffman; uint8_t sb[16]; size_t prefixlen; int no_index; @@ -711,13 +695,7 @@ static int emit_indname_block(nghttp2_bufs *bufs, size_t idx, "indexing=%d, no_index=%d\n", idx, nv->valuelen, inc_indexing, no_index)); - encvallen = nghttp2_hd_huff_encode_count(nv->value, nv->valuelen); blocklen = count_encoded_length(idx + 1, prefixlen); - huffman = encvallen < nv->valuelen; - - if(!huffman) { - encvallen = nv->valuelen; - } if(sizeof(sb) < blocklen) { return NGHTTP2_ERR_HEADER_COMP; @@ -734,7 +712,7 @@ static int emit_indname_block(nghttp2_bufs *bufs, size_t idx, return rv; } - rv = emit_string(bufs, encvallen, huffman, nv->value, nv->valuelen); + rv = emit_string(bufs, nv->value, nv->valuelen); if(rv != 0) { return rv; } @@ -746,10 +724,6 @@ static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv, int inc_indexing) { int rv; - size_t encnamelen; - size_t encvallen; - int name_huffman; - int value_huffman; int no_index; no_index = (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0; @@ -759,29 +733,17 @@ static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv, "indexing=%d, no_index=%d\n", nv->namelen, nv->valuelen, inc_indexing, no_index)); - encnamelen = nghttp2_hd_huff_encode_count(nv->name, nv->namelen); - encvallen = nghttp2_hd_huff_encode_count(nv->value, nv->valuelen); - name_huffman = encnamelen < nv->namelen; - value_huffman = encvallen < nv->valuelen; - - if(!name_huffman) { - encnamelen = nv->namelen; - } - if(!value_huffman) { - encvallen = nv->valuelen; - } - rv = nghttp2_bufs_addb(bufs, pack_first_byte(inc_indexing, no_index)); if(rv != 0) { return rv; } - rv = emit_string(bufs, encnamelen, name_huffman, nv->name, nv->namelen); + rv = emit_string(bufs, nv->name, nv->namelen); if(rv != 0) { return rv; } - rv = emit_string(bufs, encvallen, value_huffman, nv->value, nv->valuelen); + rv = emit_string(bufs, nv->value, nv->valuelen); if(rv != 0) { return rv; } @@ -789,23 +751,6 @@ static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv, return 0; } -/* - * Emit common header with |index| by toggle off and on (thus 2 - * indexed representation emissions). - */ -static int emit_implicit(nghttp2_bufs *bufs, size_t idx) -{ - int i, rv; - - for(i = 0; i < 2; ++i) { - rv = emit_indexed_block(bufs, idx); - if(rv != 0) { - return rv; - } - } - return 0; -} - static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context, nghttp2_bufs *bufs, const nghttp2_nv *nv, @@ -824,17 +769,7 @@ static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context, nghttp2_hd_entry* ent = hd_ringbuf_get(&context->hd_table, idx); context->hd_table_bufsize -= entry_room(ent->nv.namelen, ent->nv.valuelen); - if(context->role == NGHTTP2_HD_ROLE_DEFLATE) { - if(ent->flags & NGHTTP2_HD_FLAG_IMPLICIT_EMIT) { - /* Emit common header just before it slips away from the - table. If we don't do this, we have to emit it in literal - representation which hurts compression. */ - rv = emit_implicit(bufs, idx); - if(rv != 0) { - return NULL; - } - } - } + DEBUGF(fprintf(stderr, "hpack: remove item from header table: ")); DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr)); DEBUGF(fprintf(stderr, ": ")); @@ -882,8 +817,6 @@ static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context, } context->hd_table_bufsize += room; - - new_ent->flags |= NGHTTP2_HD_FLAG_REFSET; } return new_ent; } @@ -913,39 +846,42 @@ static search_result search_hd_table(nghttp2_hd_context *context, uint32_t value_hash = hash(nv->value, nv->valuelen); int use_index = (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) == 0; - if(use_index) { - for(i = 0; i < context->hd_table.len; ++i) { - nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, i); - if(ent->name_hash == name_hash && name_eq(&ent->nv, nv)) { - if(res.index == -1) { - res.index = (ssize_t)i; - } - if(ent->value_hash == value_hash && value_eq(&ent->nv, nv)) { - res.index = (ssize_t)i; - res.name_value_match = 1; - return res; - } - } - } - } - - for(i = 0; i < STATIC_TABLE_LENGTH; ++i) { + for(i = 0; i < NGHTTP2_STATIC_TABLE_LENGTH; ++i) { nghttp2_hd_entry *ent = &static_table[i]; if(ent->name_hash != name_hash || !name_eq(&ent->nv, nv)) { continue; } if(res.index == -1) { - res.index = (ssize_t)(context->hd_table.len + i); + res.index = (ssize_t)i; } if(use_index && ent->value_hash == value_hash && value_eq(&ent->nv, nv)) { - res.index = (ssize_t)(context->hd_table.len + i); + res.index = (ssize_t)i; res.name_value_match = 1; return res; } } + + if(!use_index) { + return res; + } + + for(i = 0; i < context->hd_table.len; ++i) { + nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, i); + if(ent->name_hash == name_hash && name_eq(&ent->nv, nv)) { + if(res.index == -1) { + res.index = (ssize_t)i + NGHTTP2_STATIC_TABLE_LENGTH; + } + if(ent->value_hash == value_hash && value_eq(&ent->nv, nv)) { + res.index = (ssize_t)i + NGHTTP2_STATIC_TABLE_LENGTH; + res.name_value_match = 1; + return res; + } + } + } + return res; } @@ -972,6 +908,9 @@ int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater, deflater->ctx.hd_table_bufsize_max = next_bufsize; + deflater->min_hd_table_bufsize_max = + nghttp2_min(deflater->min_hd_table_bufsize_max, next_bufsize); + deflater->notify_table_size_change = 1; hd_context_shrink_table_size(&deflater->ctx); @@ -987,31 +926,22 @@ int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater, return 0; } -static void clear_refset(nghttp2_hd_context *context) -{ - size_t i; - for(i = 0; i < context->hd_table.len; ++i) { - nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, i); - ent->flags &= ~NGHTTP2_HD_FLAG_REFSET; - } -} - #define INDEX_RANGE_VALID(context, idx) \ - ((idx) < (context)->hd_table.len + STATIC_TABLE_LENGTH) + ((idx) < (context)->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH) static size_t get_max_index(nghttp2_hd_context *context) { - return context->hd_table.len + STATIC_TABLE_LENGTH - 1; + return context->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH - 1; } nghttp2_hd_entry* nghttp2_hd_table_get(nghttp2_hd_context *context, size_t idx) { assert(INDEX_RANGE_VALID(context, idx)); - if(idx < context->hd_table.len) { - return hd_ringbuf_get(&context->hd_table, idx); + if(idx >= NGHTTP2_STATIC_TABLE_LENGTH) { + return hd_ringbuf_get(&context->hd_table, idx - NGHTTP2_STATIC_TABLE_LENGTH); } else { - return &static_table[idx - context->hd_table.len]; + return &static_table[idx]; } } @@ -1042,8 +972,9 @@ static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs, const nghttp2_nv *nv) { int rv; - nghttp2_hd_entry *ent; search_result res; + ssize_t idx = -1; + int incidx = 0; DEBUGF(fprintf(stderr, "deflatehd: deflating ")); DEBUGF(fwrite(nv->name, nv->namelen, 1, stderr)); @@ -1053,142 +984,55 @@ static int deflate_nv(nghttp2_hd_deflater *deflater, res = search_hd_table(&deflater->ctx, nv); - if(res.index != -1 && res.name_value_match) { - size_t idx = res.index; + idx = res.index; - DEBUGF(fprintf(stderr, "deflatehd: name/value match index=%zd\n", - res.index)); + if(res.name_value_match) { - ent = nghttp2_hd_table_get(&deflater->ctx, idx); - if(idx >= deflater->ctx.hd_table.len) { - nghttp2_hd_entry *new_ent; - - /* It is important to first add entry to the header table and - let eviction go. If NGHTTP2_HD_FLAG_IMPLICIT_EMIT entry is - evicted, it must be emitted before the |nv|. */ - new_ent = add_hd_table_incremental(&deflater->ctx, bufs, &ent->nv, - NGHTTP2_HD_FLAG_NONE); - if(!new_ent) { - return NGHTTP2_ERR_HEADER_COMP; - } - if(new_ent->ref == 0) { - nghttp2_hd_entry_free(new_ent); - free(new_ent); - new_ent = NULL; - } else { - /* new_ent->ref > 0 means that new_ent is in the reference - set */ - new_ent->flags |= NGHTTP2_HD_FLAG_EMIT; - } - rv = emit_indexed_block(bufs, idx); - if(rv != 0) { - return rv; - } - } else if((ent->flags & NGHTTP2_HD_FLAG_REFSET) == 0) { - ent->flags |= NGHTTP2_HD_FLAG_REFSET | NGHTTP2_HD_FLAG_EMIT; - rv = emit_indexed_block(bufs, idx); - if(rv != 0) { - return rv; - } - } else { - int num_emits = 0; - if(ent->flags & NGHTTP2_HD_FLAG_EMIT) { - /* occurrences of the same indexed representation. Emit index - twice. */ - num_emits = 2; - } else if(ent->flags & NGHTTP2_HD_FLAG_IMPLICIT_EMIT) { - /* ent was implicitly emitted because it is the common - header field. To support occurrences of the same indexed - representation, we have to emit 4 times. This is because - "implicitly emitted" means actually not emitted at - all. So first 2 emits performs 1st header appears in the - reference set. And another 2 emits are done for 2nd - (current) header. */ - ent->flags ^= NGHTTP2_HD_FLAG_IMPLICIT_EMIT; - ent->flags |= NGHTTP2_HD_FLAG_EMIT; - num_emits = 4; - } else { - /* This is common header and not emitted in the current - run. Just mark IMPLICIT_EMIT, in the hope that we are not - required to emit anything for this. We will emit toggle - off/on for this entry if it is removed from the header - table. */ - ent->flags |= NGHTTP2_HD_FLAG_IMPLICIT_EMIT; - } - for(; num_emits > 0; --num_emits) { - rv = emit_indexed_block(bufs, idx); - if(rv != 0) { - return rv; - } - } - } - } else { - ssize_t idx = -1; - int incidx = 0; - if(res.index != -1) { - DEBUGF(fprintf(stderr, "deflatehd: name match index=%zd\n", - res.index)); - - idx = res.index; - } - if(hd_deflate_should_indexing(deflater, nv)) { - nghttp2_hd_entry *new_ent; - if(idx >= (ssize_t)deflater->ctx.hd_table.len) { - nghttp2_nv nv_indname; - nv_indname = *nv; - nv_indname.name = nghttp2_hd_table_get(&deflater->ctx, idx)->nv.name; - new_ent = add_hd_table_incremental(&deflater->ctx, bufs, &nv_indname, - NGHTTP2_HD_FLAG_VALUE_ALLOC); - } else { - new_ent = add_hd_table_incremental(&deflater->ctx, bufs, nv, - NGHTTP2_HD_FLAG_NAME_ALLOC | - NGHTTP2_HD_FLAG_VALUE_ALLOC); - } - if(!new_ent) { - return NGHTTP2_ERR_HEADER_COMP; - } - if(new_ent->ref == 0) { - nghttp2_hd_entry_free(new_ent); - free(new_ent); - } else { - /* new_ent->ref > 0 means that new_ent is in the reference - set. */ - new_ent->flags |= NGHTTP2_HD_FLAG_EMIT; - } - incidx = 1; - } - if(idx == -1) { - rv = emit_newname_block(bufs, nv, incidx); - } else { - rv = emit_indname_block(bufs, idx, nv, incidx); - } - if(rv != 0) { - return rv; - } - } - return 0; -} - -static int deflate_post_process_hd_entry(nghttp2_hd_entry *ent, - size_t idx, - nghttp2_bufs *bufs) -{ - int rv; - - if((ent->flags & NGHTTP2_HD_FLAG_REFSET) && - (ent->flags & NGHTTP2_HD_FLAG_IMPLICIT_EMIT) == 0 && - (ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) { - /* This entry is not present in the current header set and must - be removed. */ - ent->flags ^= NGHTTP2_HD_FLAG_REFSET; + DEBUGF(fprintf(stderr, "deflatehd: name/value match index=%zd\n", idx)); rv = emit_indexed_block(bufs, idx); if(rv != 0) { return rv; } + + return 0; } - ent->flags &= ~(NGHTTP2_HD_FLAG_EMIT | NGHTTP2_HD_FLAG_IMPLICIT_EMIT); + if(res.index != -1) { + DEBUGF(fprintf(stderr, "deflatehd: name match index=%zd\n", + res.index)); + } + + if(hd_deflate_should_indexing(deflater, nv)) { + nghttp2_hd_entry *new_ent; + if(idx != -1 && idx < (ssize_t)NGHTTP2_STATIC_TABLE_LENGTH) { + nghttp2_nv nv_indname; + nv_indname = *nv; + nv_indname.name = nghttp2_hd_table_get(&deflater->ctx, idx)->nv.name; + new_ent = add_hd_table_incremental(&deflater->ctx, bufs, &nv_indname, + NGHTTP2_HD_FLAG_VALUE_ALLOC); + } else { + new_ent = add_hd_table_incremental(&deflater->ctx, bufs, nv, + NGHTTP2_HD_FLAG_NAME_ALLOC | + NGHTTP2_HD_FLAG_VALUE_ALLOC); + } + if(!new_ent) { + return NGHTTP2_ERR_HEADER_COMP; + } + if(new_ent->ref == 0) { + nghttp2_hd_entry_free(new_ent); + free(new_ent); + } + incidx = 1; + } + if(idx == -1) { + rv = emit_newname_block(bufs, nv, incidx); + } else { + rv = emit_indname_block(bufs, idx, nv, incidx); + } + if(rv != 0) { + return rv; + } return 0; } @@ -1205,8 +1049,21 @@ int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater, } if(deflater->notify_table_size_change) { + size_t min_hd_table_bufsize_max; + + min_hd_table_bufsize_max = deflater->min_hd_table_bufsize_max; deflater->notify_table_size_change = 0; + deflater->min_hd_table_bufsize_max = UINT32_MAX; + + if(deflater->ctx.hd_table_bufsize_max > min_hd_table_bufsize_max) { + + rv = emit_table_size(bufs, min_hd_table_bufsize_max); + + if(rv != 0) { + goto fail; + } + } rv = emit_table_size(bufs, deflater->ctx.hd_table_bufsize_max); @@ -1215,13 +1072,6 @@ int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater, } } - if(deflater->no_refset) { - rv = emit_clear_refset(bufs); - if(rv != 0) { - goto fail; - } - clear_refset(&deflater->ctx); - } for(i = 0; i < nvlen; ++i) { rv = deflate_nv(deflater, bufs, &nv[i]); if(rv != 0) { @@ -1232,15 +1082,6 @@ int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater, DEBUGF(fprintf(stderr, "deflatehd: all input name/value pairs were deflated\n")); - for(i = 0; i < deflater->ctx.hd_table.len; ++i) { - nghttp2_hd_entry *ent = hd_ringbuf_get(&deflater->ctx.hd_table, i); - - rv = deflate_post_process_hd_entry(ent, i, bufs); - if(rv != 0) { - goto fail; - } - } - return 0; fail: DEBUGF(fprintf(stderr, "deflatehd: error return %d\n", rv)); @@ -1282,15 +1123,13 @@ ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, const nghttp2_nv *nva, size_t nvlen) { - size_t n; + size_t n = 0; size_t i; - /* Possible Reference Set Emptying */ - n = 1; - /* Possible Maximum Header Table Size Change. Encoding (1u << 31) - - 1 using 4 bit prefix requires 6 bytes. */ - n += 6; + 1 using 4 bit prefix requires 6 bytes. We may emit this at most + twice. */ + n += 12; /* Use Literal Header Field without indexing - New Name, since it is most space consuming format. Also we choose the less one between @@ -1305,9 +1144,6 @@ size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, n += nva[i].namelen + nva[i].valuelen; } - /* Add possible reference set toggle off */ - n += deflater->ctx.hd_table.len; - return n; } @@ -1471,29 +1307,10 @@ static int hd_inflate_commit_indexed(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out) { nghttp2_hd_entry *ent = nghttp2_hd_table_get(&inflater->ctx, inflater->index); - if(inflater->index >= inflater->ctx.hd_table.len) { - nghttp2_hd_entry *new_ent; - new_ent = add_hd_table_incremental(&inflater->ctx, NULL, &ent->nv, - NGHTTP2_HD_FLAG_NONE); - if(!new_ent) { - return NGHTTP2_ERR_NOMEM; - } - /* new_ent->ref == 0 may be hold */ - emit_indexed_header(nv_out, new_ent); - inflater->ent_keep = new_ent; - return 0; - } - ent->flags ^= NGHTTP2_HD_FLAG_REFSET; - if(ent->flags & NGHTTP2_HD_FLAG_REFSET) { - emit_indexed_header(nv_out, ent); - return 0; - } - DEBUGF(fprintf(stderr, "inflatehd: toggle off item: ")); - DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr)); - DEBUGF(fprintf(stderr, ": ")); - DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr)); - DEBUGF(fprintf(stderr, "\n")); - return 1; + + emit_indexed_header(nv_out, ent); + + return 0; } static int hd_inflate_remove_bufs(nghttp2_hd_inflater *inflater, @@ -1599,6 +1416,7 @@ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater, { int rv; nghttp2_nv nv; + nghttp2_hd_entry *ent_name; rv = hd_inflate_remove_bufs(inflater, &nv, 1 /* value only */); if(rv != 0) { @@ -1611,8 +1429,10 @@ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater, nv.flags = NGHTTP2_NV_FLAG_NONE; } - nv.name = inflater->ent_name->nv.name; - nv.namelen = inflater->ent_name->nv.namelen; + ent_name = nghttp2_hd_table_get(&inflater->ctx, inflater->index); + + nv.name = ent_name->nv.name; + nv.namelen = ent_name->nv.namelen; if(inflater->index_required) { nghttp2_hd_entry *new_ent; @@ -1620,24 +1440,22 @@ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater, int static_name; ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC | NGHTTP2_HD_FLAG_VALUE_GIFT; - static_name = inflater->index >= inflater->ctx.hd_table.len; + static_name = inflater->index < NGHTTP2_STATIC_TABLE_LENGTH; if(!static_name) { ent_flags |= NGHTTP2_HD_FLAG_NAME_ALLOC; /* For entry in static table, we must not touch ref, because it is shared by threads */ - ++inflater->ent_name->ref; + ++ent_name->ref; } new_ent = add_hd_table_incremental(&inflater->ctx, NULL, &nv, ent_flags); - if(!static_name && --inflater->ent_name->ref == 0) { - nghttp2_hd_entry_free(inflater->ent_name); - free(inflater->ent_name); + if(!static_name && --ent_name->ref == 0) { + nghttp2_hd_entry_free(ent_name); + free(ent_name); } - inflater->ent_name = NULL; - if(new_ent) { emit_indexed_header(nv_out, new_ent); @@ -1678,20 +1496,10 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, for(; in != last;) { switch(inflater->state) { case NGHTTP2_HD_STATE_OPCODE: - if((*in & 0xf0u) == 0x20u) { + if((*in & 0xe0u) == 0x20u) { DEBUGF(fprintf(stderr, "inflatehd: header table size change\n")); inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED; inflater->state = NGHTTP2_HD_STATE_READ_TABLE_SIZE; - } else if((*in & 0xf0u) == 0x30u) { - if(*in != 0x30u) { - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - - DEBUGF(fprintf(stderr, "inflatehd: clearing reference set\n")); - inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED; - inflater->state = NGHTTP2_HD_STATE_CLEAR_REFSET; - ++in; } else if(*in & 0x80u) { DEBUGF(fprintf(stderr, "inflatehd: indexed repr\n")); inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED; @@ -1709,7 +1517,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, inflater->state = NGHTTP2_HD_STATE_READ_INDEX; } inflater->index_required = (*in & 0x40) != 0; - inflater->no_index = (*in & 0xf0u) == 0x10u; + inflater->no_index = (*in & 0xf0u) == 0x10u; DEBUGF(fprintf(stderr, "inflatehd: indexing required=%d, no_index=%d\n", inflater->index_required, @@ -1720,15 +1528,10 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, } inflater->left = 0; inflater->shift = 0; - break; - case NGHTTP2_HD_STATE_CLEAR_REFSET: - clear_refset(&inflater->ctx); - inflater->state = NGHTTP2_HD_STATE_OPCODE; - break; case NGHTTP2_HD_STATE_READ_TABLE_SIZE: rfin = 0; - rv = hd_inflate_read_len(inflater, &rfin, in, last, 4, + rv = hd_inflate_read_len(inflater, &rfin, in, last, 5, inflater->settings_hd_table_bufsize_max); if(rv < 0) { goto fail; @@ -1762,19 +1565,20 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, in += rv; + if(!rfin) { + goto almost_ok; + } + if(inflater->left == 0) { rv = NGHTTP2_ERR_HEADER_COMP; goto fail; } - if(!rfin) { - goto almost_ok; - } DEBUGF(fprintf(stderr, "inflatehd: index=%zu\n", inflater->left)); if(inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) { inflater->index = inflater->left; - assert(inflater->index > 0); --inflater->index; + rv = hd_inflate_commit_indexed(inflater, nv_out); if(rv < 0) { goto fail; @@ -1787,10 +1591,8 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, } } else { inflater->index = inflater->left; - assert(inflater->index > 0); --inflater->index; - inflater->ent_name = nghttp2_hd_table_get(&inflater->ctx, - inflater->index); + inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; } break; @@ -1995,20 +1797,6 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, goto fail; } - for(; inflater->end_headers_index < inflater->ctx.hd_table.len; - ++inflater->end_headers_index) { - nghttp2_hd_entry *ent; - ent = hd_ringbuf_get(&inflater->ctx.hd_table, - inflater->end_headers_index); - - if((ent->flags & NGHTTP2_HD_FLAG_REFSET) && - (ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) { - emit_indexed_header(nv_out, ent); - *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; - return (ssize_t)(in - first); - } - ent->flags &= ~NGHTTP2_HD_FLAG_EMIT; - } *inflate_flags |= NGHTTP2_HD_INFLATE_FINAL; } return (ssize_t)(in - first); @@ -2033,7 +1821,6 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater) { hd_inflate_keep_free(inflater); - inflater->end_headers_index = 0; return 0; } diff --git a/lib/nghttp2_hd.h b/lib/nghttp2_hd.h index 59affd49..907701d8 100644 --- a/lib/nghttp2_hd.h +++ b/lib/nghttp2_hd.h @@ -47,10 +47,8 @@ encoder only uses the memory up to this value. */ #define NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE (1 << 12) -typedef enum { - NGHTTP2_HD_ROLE_DEFLATE, - NGHTTP2_HD_ROLE_INFLATE -} nghttp2_hd_role; +/* Exported for unit test */ +extern const size_t NGHTTP2_STATIC_TABLE_LENGTH; typedef enum { NGHTTP2_HD_FLAG_NONE = 0, @@ -58,18 +56,12 @@ typedef enum { NGHTTP2_HD_FLAG_NAME_ALLOC = 1, /* Indicates value was dynamically allocated and must be freed */ NGHTTP2_HD_FLAG_VALUE_ALLOC = 1 << 1, - /* Indicates that the entry is in the reference set */ - NGHTTP2_HD_FLAG_REFSET = 1 << 2, - /* Indicates that the entry is emitted in the current header - processing. */ - NGHTTP2_HD_FLAG_EMIT = 1 << 3, - NGHTTP2_HD_FLAG_IMPLICIT_EMIT = 1 << 4, /* Indicates that the name was gifted to the entry and no copying necessary. */ - NGHTTP2_HD_FLAG_NAME_GIFT = 1 << 5, + NGHTTP2_HD_FLAG_NAME_GIFT = 1 << 2, /* Indicates that the value was gifted to the entry and no copying necessary. */ - NGHTTP2_HD_FLAG_VALUE_GIFT = 1 << 6 + NGHTTP2_HD_FLAG_VALUE_GIFT = 1 << 3 } nghttp2_hd_flags; typedef struct { @@ -97,7 +89,6 @@ typedef enum { typedef enum { NGHTTP2_HD_STATE_OPCODE, - NGHTTP2_HD_STATE_CLEAR_REFSET, NGHTTP2_HD_STATE_READ_TABLE_SIZE, NGHTTP2_HD_STATE_READ_INDEX, NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN, @@ -119,8 +110,6 @@ typedef struct { size_t hd_table_bufsize; /* The effective header table size. */ size_t hd_table_bufsize_max; - /* Role of this context; deflate or infalte */ - nghttp2_hd_role role; /* If inflate/deflate error occurred, this value is set to 1 and further invocation of inflate/deflate will fail with NGHTTP2_ERR_HEADER_COMP. */ @@ -131,9 +120,8 @@ struct nghttp2_hd_deflater { nghttp2_hd_context ctx; /* The upper limit of the header table size the deflater accepts. */ size_t deflate_hd_table_bufsize_max; - /* Set to this nonzero to clear reference set on each deflation each - time. */ - uint8_t no_refset; + /* Minimum header table size notified in the next context update */ + size_t min_hd_table_bufsize_max; /* If nonzero, send header table size using encoding context update in the next deflate process */ uint8_t notify_table_size_change; @@ -152,16 +140,10 @@ struct nghttp2_hd_inflater { /* Pointer to the name/value pair buffer which is used in the current header emission. */ uint8_t *nv_keep; - /* Pointers to the name/value pair which is referred as indexed - name. This entry must be in header table. */ - nghttp2_hd_entry *ent_name; /* The number of bytes to read */ size_t left; /* The index in indexed repr or indexed name */ size_t index; - /* The index of header table to toggle off the entry from reference - set at the end of decompression. */ - size_t end_headers_index; /* The length of new name encoded in literal. For huffman encoded string, this is the length after it is decoded. */ size_t newnamelen; diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c index 498227fd..adb59bc0 100644 --- a/lib/nghttp2_option.c +++ b/lib/nghttp2_option.c @@ -40,18 +40,10 @@ void nghttp2_option_del(nghttp2_option *option) free(option); } -void nghttp2_option_set_no_auto_stream_window_update(nghttp2_option *option, - int val) +void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val) { - option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE; - option->no_auto_stream_window_update = val; -} - -void nghttp2_option_set_no_auto_connection_window_update -(nghttp2_option *option, int val) -{ - option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE; - option->no_auto_connection_window_update = val; + option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE; + option->no_auto_window_update = val; } void nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option, diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h index 881d85de..d5bad011 100644 --- a/lib/nghttp2_option.h +++ b/lib/nghttp2_option.h @@ -37,22 +37,12 @@ typedef enum { /** * This option prevents the library from sending WINDOW_UPDATE for a - * stream automatically. If this option is set to nonzero, the - * library won't send WINDOW_UPDATE for a stream and the application - * is responsible for sending WINDOW_UPDATE using - * `nghttp2_submit_window_update`. By default, this option is set to - * zero. + * connection automatically. If this option is set to nonzero, the + * library won't send WINDOW_UPDATE for DATA until application calls + * nghttp2_session_consume() to indicate the amount of consumed + * DATA. By default, this option is set to zero. */ - NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE = 1, - /** - * This option prevents the library from sending WINDOW_UPDATE for a - * connection automatically. If this option is set to nonzero, the - * library won't send WINDOW_UPDATE for a connection and the - * application is responsible for sending WINDOW_UPDATE with stream - * ID 0 using `nghttp2_submit_window_update`. By default, this - * option is set to zero. - */ - NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE = 1 << 1, + NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE = 1, /** * This option sets the SETTINGS_MAX_CONCURRENT_STREAMS value of * remote endpoint as if it is received in SETTINGS frame. Without @@ -66,7 +56,7 @@ typedef enum { * will be overwritten if the local endpoint receives * SETTINGS_MAX_CONCURRENT_STREAMS from the remote endpoint. */ - NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 2 + NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1 } nghttp2_option_flag; /** @@ -83,13 +73,9 @@ struct nghttp2_option { */ uint32_t peer_max_concurrent_streams; /** - * NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE + * NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE */ - uint8_t no_auto_stream_window_update; - /** - * NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE - */ - uint8_t no_auto_connection_window_update; + uint8_t no_auto_window_update; }; #endif /* NGHTTP2_OPTION_H */ diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index b2a00a49..ed83b77d 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -281,7 +281,6 @@ static void session_inbound_frame_reset(nghttp2_session *session) iframe->niv = 0; iframe->payloadleft = 0; iframe->padlen = 0; - iframe->headers_payload_length = 0; iframe->iv[NGHTTP2_INBOUND_NUM_IV - 1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; iframe->iv[NGHTTP2_INBOUND_NUM_IV - 1].value = UINT32_MAX; @@ -293,6 +292,8 @@ static void init_settings(nghttp2_settings_storage *settings) settings->enable_push = 1; settings->max_concurrent_streams = NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS; settings->initial_window_size = NGHTTP2_INITIAL_WINDOW_SIZE; + settings->max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MIN; + settings->max_header_set_size = UINT32_MAX; } static void active_outbound_item_reset(nghttp2_active_outbound_item *aob) @@ -352,6 +353,7 @@ static int session_new(nghttp2_session **session_ptr, (*session_ptr)->remote_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE; (*session_ptr)->recv_window_size = 0; + (*session_ptr)->consumed_size = 0; (*session_ptr)->recv_reduction = 0; (*session_ptr)->local_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE; @@ -383,19 +385,10 @@ static int session_new(nghttp2_session **session_ptr, if(option) { - if((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE) && - option->no_auto_stream_window_update) { + if((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) && + option->no_auto_window_update) { - (*session_ptr)->opt_flags |= - NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE; - - } - - if((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE) && - option->no_auto_connection_window_update) { - - (*session_ptr)->opt_flags |= - NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE; + (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE; } @@ -2031,6 +2024,8 @@ static int session_after_frame_sent(nghttp2_session *session) /* Fall through */ case NGHTTP2_HCAT_RESPONSE: stream->state = NGHTTP2_STREAM_OPENED; + /* Fall through */ + case NGHTTP2_HCAT_HEADERS: if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); } @@ -2050,15 +2045,6 @@ static int session_after_frame_sent(nghttp2_session *session) DATA frame item. We might have to handle it here. */ } break; - case NGHTTP2_HCAT_HEADERS: - if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); - } - rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); - if(nghttp2_is_fatal(rv)) { - return rv; - } - break; } break; } @@ -3182,10 +3168,11 @@ static int update_local_initial_window_size_func return nghttp2_session_terminate_session(arg->session, NGHTTP2_FLOW_CONTROL_ERROR); } - if(!(arg->session->opt_flags & - NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE)) { + if(!(arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { + if(nghttp2_should_send_window_update(stream->local_window_size, stream->recv_window_size)) { + rv = nghttp2_session_add_window_update(arg->session, NGHTTP2_FLAG_NONE, stream->stream_id, @@ -3288,6 +3275,12 @@ int nghttp2_session_update_local_settings(nghttp2_session *session, case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: session->local_settings.initial_window_size = iv[i].value; break; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + session->local_settings.max_frame_size = iv[i].value; + break; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + session->local_settings.max_header_set_size = iv[i].value; + break; } } @@ -3408,6 +3401,23 @@ int nghttp2_session_on_settings_received(nghttp2_session *session, session->remote_settings.initial_window_size = entry->value; + break; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + + if(entry->value < NGHTTP2_MAX_FRAME_SIZE_MIN || + entry->value > NGHTTP2_MAX_FRAME_SIZE_MAX) { + return session_handle_invalid_connection + (session, frame, NGHTTP2_PROTOCOL_ERROR, + "SETTINGS: invalid SETTINGS_MAX_FRAME_SIZE"); + } + + session->remote_settings.max_frame_size = entry->value; + + break; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + + session->remote_settings.max_header_set_size = entry->value; + break; } } @@ -3722,7 +3732,8 @@ static int session_on_connection_window_update_received { int rv; /* Handle connection-level flow control */ - if(NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < + if(frame->window_update.window_size_increment == 0 || + NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < session->remote_window_size) { return session_handle_invalid_connection (session, frame, NGHTTP2_FLOW_CONTROL_ERROR, NULL); @@ -3761,7 +3772,8 @@ static int session_on_stream_window_update_received (session, frame, NGHTTP2_PROTOCOL_ERROR, "WINDOW_UPADATE to reserved stream"); } - if(NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < + if(frame->window_update.window_size_increment == 0 || + NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < stream->remote_window_size) { return session_handle_invalid_stream(session, frame, NGHTTP2_FLOW_CONTROL_ERROR); @@ -3883,9 +3895,9 @@ static int adjust_recv_window_size(int32_t *recv_window_size_ptr, /* * Accumulates received bytes |delta_size| for stream-level flow - * control and decides whether to send WINDOW_UPDATE to that - * stream. If NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE is set, - * WINDOW_UPDATE will not be sent. + * control and decides whether to send WINDOW_UPDATE to that stream. + * If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, WINDOW_UPDATE will not + * be sent. * * This function returns 0 if it succeeds, or one of the following * negative error codes: @@ -3909,7 +3921,7 @@ static int session_update_recv_stream_window_size /* We don't have to send WINDOW_UPDATE if the data received is the last chunk in the incoming stream. */ if(send_window_update && - !(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE)) { + !(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { /* We have to use local_settings here because it is the constraint the remote endpoint should honor. */ if(nghttp2_should_send_window_update(stream->local_window_size, @@ -3931,8 +3943,8 @@ static int session_update_recv_stream_window_size /* * Accumulates received bytes |delta_size| for connection-level flow * control and decides whether to send WINDOW_UPDATE to the - * connection. If NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE is - * set, WINDOW_UPDATE will not be sent. + * connection. If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, + * WINDOW_UPDATE will not be sent. * * This function returns 0 if it succeeds, or one of the following * negative error codes: @@ -3951,30 +3963,92 @@ static int session_update_recv_connection_window_size return nghttp2_session_terminate_session(session, NGHTTP2_FLOW_CONTROL_ERROR); } - if(!(session->opt_flags & - NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE)) { + if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { + if(nghttp2_should_send_window_update(session->local_window_size, session->recv_window_size)) { /* Use stream ID 0 to update connection-level flow control window */ - rv = nghttp2_session_add_window_update(session, - NGHTTP2_FLAG_NONE, - 0, - session->recv_window_size); - if(rv == 0) { - session->recv_window_size = 0; - /* recv_ign_window_size keeps track of ignored DATA bytes - before any connection-level WINDOW_UPDATE therefore, we can - reset it here. */ - session->recv_ign_window_size = 0; - } else { + rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, 0, + session->recv_window_size); + if(rv != 0) { return rv; } + + session->recv_window_size = 0; } } return 0; } +static int session_update_stream_consumed_size +(nghttp2_session *session, nghttp2_stream *stream, size_t delta_size) +{ + int32_t recv_size; + int rv; + + if((size_t)stream->consumed_size > NGHTTP2_MAX_WINDOW_SIZE - delta_size) { + return nghttp2_session_terminate_session(session, + NGHTTP2_FLOW_CONTROL_ERROR); + } + + stream->consumed_size += delta_size; + + /* recv_window_size may be smaller than consumed_size, because it + may be decreased by negative value with + nghttp2_submit_window_update(). */ + recv_size = nghttp2_min(stream->consumed_size, stream->recv_window_size); + + if(nghttp2_should_send_window_update(stream->local_window_size, recv_size)) { + rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, + stream->stream_id, recv_size); + + if(rv != 0) { + return rv; + } + + stream->recv_window_size -= recv_size; + stream->consumed_size -= recv_size; + } + + return 0; +} + +static int session_update_connection_consumed_size +(nghttp2_session *session, size_t delta_size) +{ + int32_t recv_size; + int rv; + + if((size_t)session->consumed_size > NGHTTP2_MAX_WINDOW_SIZE - delta_size) { + return nghttp2_session_terminate_session(session, + NGHTTP2_FLOW_CONTROL_ERROR); + } + + session->consumed_size += delta_size; + + /* recv_window_size may be smaller than consumed_size, because it + may be decreased by negative value with + nghttp2_submit_window_update(). */ + recv_size = nghttp2_min(session->consumed_size, session->recv_window_size); + + if(nghttp2_should_send_window_update(session->local_window_size, + recv_size)) { + + rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, 0, + recv_size); + + if(rv != 0) { + return rv; + } + + session->recv_window_size -= recv_size; + session->consumed_size -= recv_size; + } + + return 0; +} + /* * Checks that we can receive the DATA frame for stream, which is * indicated by |session->iframe.frame.hd.stream_id|. If it is a @@ -4094,6 +4168,8 @@ static void inbound_frame_set_settings_entry(nghttp2_inbound_frame *iframe) case NGHTTP2_SETTINGS_ENABLE_PUSH: case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: break; default: DEBUGF(fprintf(stderr, "recv: ignore unknown settings id=0x%02x\n", @@ -4226,12 +4302,31 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, iframe->frame.hd.flags, iframe->frame.hd.stream_id)); + if(iframe->frame.hd.length > session->local_settings.max_frame_size) { + DEBUGF(fprintf(stderr, + "recv: legnth is too large %u > %u\n", + iframe->frame.hd.length, + session->local_settings.max_frame_size)); + + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + rv = nghttp2_session_terminate_session_with_reason + (session, NGHTTP2_PROTOCOL_ERROR, "too large frame size"); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + + break; + } + switch(iframe->frame.hd.type) { case NGHTTP2_DATA: { DEBUGF(fprintf(stderr, "recv: DATA\n")); iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_STREAM | - NGHTTP2_FLAG_END_SEGMENT | NGHTTP2_FLAG_PADDED); /* Check stream is open. If it is not open or closing, ignore payload. */ @@ -4275,13 +4370,10 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, DEBUGF(fprintf(stderr, "recv: HEADERS\n")); iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_STREAM | - NGHTTP2_FLAG_END_SEGMENT | NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PADDED | NGHTTP2_FLAG_PRIORITY); - iframe->headers_payload_length = iframe->frame.hd.length; - rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); if(rv < 0) { busy = 1; @@ -4408,8 +4500,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PADDED); - iframe->headers_payload_length = iframe->frame.hd.length; - rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); if(rv < 0) { busy = 1; @@ -4988,27 +5078,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, break; } - iframe->headers_payload_length += cont_hd.length; - - if(iframe->headers_payload_length > NGHTTP2_MAX_HEADERSLEN) { - - DEBUGF(fprintf(stderr, - "recv: headers too large %zu\n", - iframe->headers_payload_length)); - - rv = nghttp2_session_terminate_session_with_reason - (session, NGHTTP2_INTERNAL_ERROR, "header is too large"); - if(nghttp2_is_fatal(rv)) { - return rv; - } - - busy = 1; - - iframe->state = NGHTTP2_IB_IGN_PAYLOAD; - - break; - } - /* CONTINUATION won't bear NGHTTP2_PADDED flag */ iframe->frame.hd.flags |= cont_hd.flags & NGHTTP2_FLAG_END_HEADERS; @@ -5040,6 +5109,14 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, return rv; } + /* Pad Length field is consumed immediately */ + rv = nghttp2_session_consume(session, iframe->frame.hd.stream_id, + readlen); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id); if(stream) { @@ -5105,6 +5182,18 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, data_readlen = inbound_frame_effective_readlen (iframe, iframe->payloadleft, readlen); + padlen = readlen - data_readlen; + + if(padlen > 0) { + /* Padding is considered as "consumed" immediately */ + rv = nghttp2_session_consume(session, iframe->frame.hd.stream_id, + padlen); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + } + DEBUGF(fprintf(stderr, "recv: data_readlen=%zu\n", data_readlen)); if(stream && data_readlen > 0 && @@ -5149,8 +5238,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, readlen, iframe->payloadleft)); if(readlen > 0) { - session->recv_ign_window_size += readlen; - /* Update connection-level flow control window for ignored DATA frame too */ rv = session_update_recv_connection_window_size(session, readlen); @@ -5158,20 +5245,14 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, return rv; } - if((session->opt_flags & - NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE) && - nghttp2_should_send_window_update - (session->local_window_size, session->recv_ign_window_size)) { + if(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { - rv = nghttp2_session_add_window_update - (session, NGHTTP2_FLAG_NONE, 0, session->recv_ign_window_size); + /* Ignored DATA is considered as "consumed" immediately. */ + rv = session_update_connection_consumed_size(session, readlen); if(nghttp2_is_fatal(rv)) { return rv; } - - session->recv_window_size -= session->recv_ign_window_size; - session->recv_ign_window_size = 0; } } @@ -5495,7 +5576,7 @@ int nghttp2_session_pack_data(nghttp2_session *session, /* Clear flags, because this may contain previous flags of previous DATA */ - frame->hd.flags &= (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT); + frame->hd.flags &= NGHTTP2_FLAG_END_STREAM; flags = NGHTTP2_FLAG_NONE; if(data_flags & NGHTTP2_DATA_FLAG_EOF) { @@ -5503,9 +5584,6 @@ int nghttp2_session_pack_data(nghttp2_session *session, if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { flags |= NGHTTP2_FLAG_END_STREAM; } - if(frame->hd.flags & NGHTTP2_FLAG_END_SEGMENT) { - flags |= NGHTTP2_FLAG_END_SEGMENT; - } } /* The primary reason of data_frame is pass to the user callback */ @@ -5654,6 +5732,10 @@ uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session, return session->remote_settings.max_concurrent_streams; case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: return session->remote_settings.initial_window_size; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + return session->remote_settings.max_frame_size; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + return session->remote_settings.max_header_set_size; } assert(0); @@ -5744,3 +5826,36 @@ int nghttp2_session_get_stream_remote_close(nghttp2_session* session, return (stream->shut_flags & NGHTTP2_SHUT_RD) != 0; } + +int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id, + size_t size) +{ + int rv; + nghttp2_stream *stream; + + if(stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { + return NGHTTP2_ERR_INVALID_STATE; + } + + rv = session_update_connection_consumed_size(session, size); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + + stream = nghttp2_session_get_stream(session, stream_id); + + if(stream) { + rv = session_update_stream_consumed_size(session, stream, size); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + } + + return 0; +} diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index d9d6094a..2b7aa38f 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -43,8 +43,7 @@ * Option flags. */ typedef enum { - NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE = 1 << 0, - NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE = 1 << 1 + NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0, } nghttp2_optmask; typedef enum { @@ -81,7 +80,7 @@ typedef enum { NGHTTP2_IB_IGN_DATA } nghttp2_inbound_state; -#define NGHTTP2_INBOUND_NUM_IV 5 +#define NGHTTP2_INBOUND_NUM_IV 7 typedef struct { nghttp2_frame frame; @@ -105,9 +104,6 @@ typedef struct { size_t payloadleft; /* padding length for the current frame */ size_t padlen; - /* Sum of payload of (HEADERS | PUSH_PROMISE) + possible - CONTINUATION received so far. */ - size_t headers_payload_length; nghttp2_inbound_state state; uint8_t raw_sbuf[8]; } nghttp2_inbound_frame; @@ -117,6 +113,8 @@ typedef struct { uint32_t enable_push; uint32_t max_concurrent_streams; uint32_t initial_window_size; + uint32_t max_frame_size; + uint32_t max_header_set_size; } nghttp2_settings_storage; typedef enum { @@ -206,15 +204,10 @@ struct nghttp2_session { WINDOW_UPDATE. This could be negative after submitting negative value to WINDOW_UPDATE. */ int32_t recv_window_size; - /* The number of bytes in ignored DATA frame received without - connection-level WINDOW_UPDATE. Since we do not call - on_data_chunk_recv_callback for ignored DATA chunk, if - nghttp2_option_set_no_auto_connection_window_update is used, - application may not have a chance to send connection - WINDOW_UPDATE. To fix this, we accumulate those received bytes, - and if it exceeds certain number, we automatically send - connection-level WINDOW_UPDATE. */ - int32_t recv_ign_window_size; + /* The number of bytes consumed by the application and now is + subject to WINDOW_UPDATE. This is only used when auto + WINDOW_UPDATE is turned off. */ + int32_t consumed_size; /* The amount of recv_window_size cut using submitting negative value to WINDOW_UPDATE */ int32_t recv_reduction; diff --git a/lib/nghttp2_stream.c b/lib/nghttp2_stream.c index 00bf6216..fc205b94 100644 --- a/lib/nghttp2_stream.c +++ b/lib/nghttp2_stream.c @@ -48,6 +48,7 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id, stream->remote_window_size = remote_initial_window_size; stream->local_window_size = local_initial_window_size; stream->recv_window_size = 0; + stream->consumed_size = 0; stream->recv_reduction = 0; stream->blocked_sent = 0; diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h index 5b2d0e45..5b33dcf9 100644 --- a/lib/nghttp2_stream.h +++ b/lib/nghttp2_stream.h @@ -151,6 +151,10 @@ struct nghttp2_stream { WINDOW_UPDATE. This could be negative after submitting negative value to WINDOW_UPDATE */ int32_t recv_window_size; + /* The number of bytes consumed by the application and now is + subject to WINDOW_UPDATE. This is only used when auto + WINDOW_UPDATE is turned off. */ + int32_t consumed_size; /* The amount of recv_window_size cut using submitting negative value to WINDOW_UPDATE */ int32_t recv_reduction; diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index 48a4b876..eea748b9 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -81,9 +81,7 @@ static int32_t submit_headers_shared } flags_copy = - (flags & (NGHTTP2_FLAG_END_STREAM | - NGHTTP2_FLAG_END_SEGMENT | - NGHTTP2_FLAG_PRIORITY)) | + (flags & (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PRIORITY)) | NGHTTP2_FLAG_END_HEADERS; if(stream_id == -1) { @@ -348,27 +346,30 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags, if(rv != 0) { return rv; } - - /* recv_ign_window_size keeps track of ignored DATA bytes before - any connection-level WINDOW_UPDATE therefore, we can reset it - here. */ - session->recv_ign_window_size = 0; } else { stream = nghttp2_session_get_stream(session, stream_id); - if(stream) { - rv = nghttp2_adjust_local_window_size(&stream->local_window_size, - &stream->recv_window_size, - &stream->recv_reduction, - &window_size_increment); - if(rv != 0) { - return rv; - } - } else { + if(!stream) { return 0; } + + rv = nghttp2_adjust_local_window_size(&stream->local_window_size, + &stream->recv_window_size, + &stream->recv_reduction, + &window_size_increment); + if(rv != 0) { + return rv; + } } if(window_size_increment > 0) { + if(stream_id == 0) { + session->consumed_size = + nghttp2_max(0, session->consumed_size - window_size_increment); + } else { + stream->consumed_size = + nghttp2_max(0, stream->consumed_size - window_size_increment); + } + return nghttp2_session_add_window_update(session, flags, stream_id, window_size_increment); } @@ -535,8 +536,7 @@ int nghttp2_submit_data(nghttp2_session *session, uint8_t flags, { int rv; nghttp2_private_data *data_frame; - uint8_t nflags = flags & (NGHTTP2_FLAG_END_STREAM | - NGHTTP2_FLAG_END_SEGMENT); + uint8_t nflags = flags & NGHTTP2_FLAG_END_STREAM; if(stream_id == 0) { return NGHTTP2_ERR_INVALID_ARGUMENT; diff --git a/python/cnghttp2.pxd b/python/cnghttp2.pxd index 12c5612e..f34abb36 100644 --- a/python/cnghttp2.pxd +++ b/python/cnghttp2.pxd @@ -252,9 +252,6 @@ cdef extern from 'nghttp2/nghttp2.h': void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater) - void nghttp2_hd_deflate_set_no_refset(nghttp2_hd_deflater *deflater, - uint8_t no_refset) - int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater, size_t hd_table_bufsize_max) @@ -291,9 +288,6 @@ cdef extern from 'nghttp2_hd.h': # This is macro int NGHTTP2_HD_ENTRY_OVERHEAD - ctypedef enum nghttp2_hd_flags: - NGHTTP2_HD_FLAG_REFSET - ctypedef enum nghttp2_hd_inflate_flag: NGHTTP2_HD_INFLATE_EMIT NGHTTP2_HD_INFLATE_FINAL diff --git a/python/hpackcheck.py b/python/hpackcheck.py index 00af0f6f..f1b08907 100755 --- a/python/hpackcheck.py +++ b/python/hpackcheck.py @@ -53,7 +53,7 @@ if __name__ == '__main__': testdata = json.loads(input) - if 'draft' not in testdata or testdata['draft'] != 8: + if 'draft' not in testdata or testdata['draft'] != 9: sys.stderr.write('Not supported\n') continue diff --git a/python/hpackmake.py b/python/hpackmake.py index ecc5609b..4ceea5ce 100755 --- a/python/hpackmake.py +++ b/python/hpackmake.py @@ -16,7 +16,7 @@ import nghttp2 def testsuite(testdata, filename, outdir, table_size, deflate_table_size, simulate_table_size_change): res = { - 'draft':8, + 'draft':9, 'description': '''\ Encoded by nghttp2. The basic encoding strategy is described in \ http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html \ diff --git a/python/nghttp2.pyx b/python/nghttp2.pyx index fad8e99b..fa7f6e8c 100644 --- a/python/nghttp2.pyx +++ b/python/nghttp2.pyx @@ -33,12 +33,11 @@ HD_ENTRY_OVERHEAD = cnghttp2.NGHTTP2_HD_ENTRY_OVERHEAD class HDTableEntry: - def __init__(self, name, namelen, value, valuelen, ref): + def __init__(self, name, namelen, value, valuelen): self.name = name self.namelen = namelen self.value = value self.valuelen = valuelen - self.ref = ref def space(self): return self.namelen + self.valuelen + HD_ENTRY_OVERHEAD @@ -52,8 +51,7 @@ cdef _get_hd_table(cnghttp2.nghttp2_hd_context *ctx): k = _get_pybytes(entry.nv.name, entry.nv.namelen) v = _get_pybytes(entry.nv.value, entry.nv.valuelen) res.append(HDTableEntry(k, entry.nv.namelen, - v, entry.nv.valuelen, - (entry.flags & cnghttp2.NGHTTP2_HD_FLAG_REFSET) != 0)) + v, entry.nv.valuelen)) return res cdef _get_pybytes(uint8_t *b, uint16_t blen): @@ -143,15 +141,6 @@ cdef class HDDeflater: return res - def set_no_refset(self, no_refset): - '''Tells the compressor not to use reference set if |no_refset| is - nonzero. If |no_refset| is nonzero, on each invocation of - deflate(), compressor first emits index=0 to clear up - reference set. - - ''' - cnghttp2.nghttp2_hd_deflate_set_no_refset(self._deflater, no_refset) - def change_table_size(self, hd_table_bufsize_max): '''Changes header table size to |hd_table_bufsize_max| byte. @@ -243,16 +232,14 @@ def print_hd_table(hdtable): function does not work if header name/value cannot be decoded using UTF-8 encoding. - s=N means the entry occupies N bytes in header table. if r=y, then - the entry is in the reference set. + s=N means the entry occupies N bytes in header table. ''' idx = 0 for entry in hdtable: idx += 1 - print('[{}] (s={}) (r={}) {}: {}'\ + print('[{}] (s={}) {}: {}'\ .format(idx, entry.space(), - 'y' if entry.ref else 'n', entry.name.decode('utf-8'), entry.value.decode('utf-8'))) diff --git a/src/HttpServer.cc b/src/HttpServer.cc index 13e3424f..9f4bbd8f 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -94,9 +94,9 @@ namespace { void append_nv(Stream *stream, const std::vector& nva) { for(auto& nv : nva) { - http2::split_add_header(stream->headers, - nv.name, nv.namelen, nv.value, nv.valuelen, - nv.flags & NGHTTP2_NV_FLAG_NO_INDEX); + http2::add_header(stream->headers, + nv.name, nv.namelen, nv.value, nv.valuelen, + nv.flags & NGHTTP2_NV_FLAG_NO_INDEX); } } } // namespace @@ -843,6 +843,16 @@ int Http2Handler::submit_response(const std::string& status, data_prd); } +int Http2Handler::submit_non_final_response(const std::string& status, + int32_t stream_id) +{ + auto nva = std::vector{ + http2::make_nv_ls(":status", status) + }; + return nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE, stream_id, + nullptr, nva.data(), nva.size(), nullptr); +} + int Http2Handler::submit_push_promise(Stream *stream, const std::string& push_path) { @@ -1158,8 +1168,8 @@ int on_header_callback(nghttp2_session *session, if(!http2::check_nv(name, namelen, value, valuelen)) { return 0; } - http2::split_add_header(stream->headers, name, namelen, value, valuelen, - flags & NGHTTP2_NV_FLAG_NO_INDEX); + http2::add_header(stream->headers, name, namelen, value, valuelen, + flags & NGHTTP2_NV_FLAG_NO_INDEX); return 0; } } // namespace @@ -1247,7 +1257,7 @@ int hd_on_frame_recv_callback if(frame->headers.cat == NGHTTP2_HCAT_REQUEST) { http2::normalize_headers(stream->headers); - if(!http2::check_http2_headers(stream->headers)) { + if(!http2::check_http2_request_headers(stream->headers)) { hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); return 0; } @@ -1266,6 +1276,12 @@ int hd_on_frame_recv_callback return 0; } + auto expect100 = http2::get_header(stream->headers, "expect"); + + if(expect100 && util::strieq("100-continue", expect100->value.c_str())) { + hd->submit_non_final_response("100", frame->hd.stream_id); + } + if(hd->get_config()->early_response) { prepare_response(stream, hd); } diff --git a/src/HttpServer.h b/src/HttpServer.h index 1d3fa456..23048c92 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -126,6 +126,8 @@ public: const std::vector>& headers, nghttp2_data_provider *data_prd); + int submit_non_final_response(const std::string& status, int32_t stream_id); + int submit_push_promise(Stream *stream, const std::string& push_path); int submit_rst_stream(Stream *stream, nghttp2_error_code error_code); diff --git a/src/app_helper.cc b/src/app_helper.cc index 756fa519..b82f8ec3 100644 --- a/src/app_helper.cc +++ b/src/app_helper.cc @@ -221,12 +221,6 @@ void print_flags(const nghttp2_frame_hd& hd) if(hd.flags & NGHTTP2_FLAG_END_STREAM) { s += "END_STREAM"; } - if(hd.flags & NGHTTP2_FLAG_END_SEGMENT) { - if(!s.empty()) { - s += " | "; - } - s += "END_SEGMENT"; - } if(hd.flags & NGHTTP2_FLAG_PADDED) { if(!s.empty()) { s += " | "; @@ -238,12 +232,6 @@ void print_flags(const nghttp2_frame_hd& hd) if(hd.flags & NGHTTP2_FLAG_END_STREAM) { s += "END_STREAM"; } - if(hd.flags & NGHTTP2_FLAG_END_SEGMENT) { - if(!s.empty()) { - s += " | "; - } - s += "END_SEGMENT"; - } if(hd.flags & NGHTTP2_FLAG_END_HEADERS) { if(!s.empty()) { s += " | "; diff --git a/src/comp_helper.c b/src/comp_helper.c index 25383d30..7c627dd1 100644 --- a/src/comp_helper.c +++ b/src/comp_helper.c @@ -43,8 +43,6 @@ json_t* dump_header_table(nghttp2_hd_context *context) json_object_set_new(outent, "index", json_integer(i + 1)); dump_val(outent, "name", ent->nv.name, ent->nv.namelen); dump_val(outent, "value", ent->nv.value, ent->nv.valuelen); - json_object_set_new(outent, "referenced", - json_boolean(ent->flags & NGHTTP2_HD_FLAG_REFSET)); json_object_set_new(outent, "size", json_integer(ent->nv.namelen + ent->nv.valuelen + NGHTTP2_HD_ENTRY_OVERHEAD)); diff --git a/src/deflatehd.cc b/src/deflatehd.cc index 755cb540..7a5b6d78 100644 --- a/src/deflatehd.cc +++ b/src/deflatehd.cc @@ -53,7 +53,6 @@ typedef struct { size_t deflate_table_size; int http1text; int dump_header_table; - int no_refset; } deflate_config; static deflate_config config; @@ -198,7 +197,6 @@ static int deflate_hd_json(json_t *obj, nghttp2_hd_deflater *deflater, int seq) static void init_deflater(nghttp2_hd_deflater *deflater) { nghttp2_hd_deflate_init2(deflater, config.deflate_table_size); - nghttp2_hd_deflate_set_no_refset(deflater, config.no_refset); nghttp2_hd_deflate_change_table_size(deflater, config.table_size); } @@ -387,8 +385,7 @@ OPTIONS: buffer. Default: 4096 -d, --dump-header-table - Output dynamic header table. - -c, --no-refset Don't use reference set.)" + Output dynamic header table.)" << std::endl; } @@ -397,7 +394,6 @@ static struct option long_options[] = { {"table-size", required_argument, nullptr, 's'}, {"deflate-table-size", required_argument, nullptr, 'S'}, {"dump-header-table", no_argument, nullptr, 'd'}, - {"no-refset", no_argument, nullptr, 'c'}, {nullptr, 0, nullptr, 0 } }; @@ -409,10 +405,9 @@ int main(int argc, char **argv) config.deflate_table_size = NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE; config.http1text = 0; config.dump_header_table = 0; - config.no_refset = 0; while(1) { int option_index = 0; - int c = getopt_long(argc, argv, "S:cdhs:t", long_options, &option_index); + int c = getopt_long(argc, argv, "S:dhs:t", long_options, &option_index); if(c == -1) { break; } @@ -446,10 +441,6 @@ int main(int argc, char **argv) // --dump-header-table config.dump_header_table = 1; break; - case 'c': - // --no-refset - config.no_refset = 1; - break; case '?': exit(EXIT_FAILURE); default: diff --git a/src/http2.cc b/src/http2.cc index 8bb09c7f..5920a6c5 100644 --- a/src/http2.cc +++ b/src/http2.cc @@ -162,10 +162,34 @@ namespace { size_t DISALLOWED_HDLEN = sizeof(DISALLOWED_HD)/sizeof(DISALLOWED_HD[0]); } // namespace +namespace { +const char *REQUEST_PSEUDO_HD[] = { + ":authority", + ":method", + ":path", + ":scheme", +}; +} // namespace + +namespace { +size_t REQUEST_PSEUDO_HDLEN = + sizeof(REQUEST_PSEUDO_HD) / sizeof(REQUEST_PSEUDO_HD[0]); +} // namespace + +namespace { +const char *RESPONSE_PSEUDO_HD[] = { + ":status", +}; +} // namespace + +namespace { +size_t RESPONSE_PSEUDO_HDLEN = + sizeof(RESPONSE_PSEUDO_HD) / sizeof(RESPONSE_PSEUDO_HD[0]); +} // namespace + namespace { const char *IGN_HD[] = { "connection", - "expect", "http2-settings", "keep-alive", "proxy-connection", @@ -186,7 +210,6 @@ namespace { const char *HTTP1_IGN_HD[] = { "connection", "cookie", - "expect", "http2-settings", "keep-alive", "proxy-connection", @@ -225,6 +248,61 @@ bool check_http2_headers(const Headers& nva) return true; } +namespace { +template +bool check_pseudo_headers(const Headers& nva, + InputIterator allowed_first, + InputIterator allowed_last) +{ + // strict checking for pseudo headers. + for(auto& hd : nva) { + auto c = hd.name.c_str()[0]; + + if(c < ':') { + continue; + } + + if(c > ':') { + break; + } + + auto i = allowed_first; + + for(; i != allowed_last; ++i) { + if(hd.name == *i) { + break; + } + } + + if(i == allowed_last) { + return false; + } + } + + return true; +} +} // namespace + +bool check_http2_request_headers(const Headers& nva) +{ + if(!check_http2_headers(nva)) { + return false; + } + + return check_pseudo_headers(nva, REQUEST_PSEUDO_HD, + REQUEST_PSEUDO_HD + REQUEST_PSEUDO_HDLEN); +} + +bool check_http2_response_headers(const Headers& nva) +{ + if(!check_http2_headers(nva)) { + return false; + } + + return check_pseudo_headers(nva, RESPONSE_PSEUDO_HD, + RESPONSE_PSEUDO_HD + RESPONSE_PSEUDO_HDLEN); +} + void normalize_headers(Headers& nva) { for(auto& kv : nva) { @@ -275,31 +353,12 @@ Headers::value_type to_header(const uint8_t *name, size_t namelen, no_index); } -void split_add_header(Headers& nva, - const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - bool no_index) +void add_header(Headers& nva, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + bool no_index) { - if(valuelen == 0) { - nva.push_back(to_header(name, namelen, value, valuelen, no_index)); - return; - } - auto j = value; - auto end = value + valuelen; - for(;;) { - // Skip 0 length value - j = std::find_if(j, end, - [](uint8_t c) - { - return c != '\0'; - }); - if(j == end) { - break; - } - auto l = std::find(j, end, '\0'); - nva.push_back(to_header(name, namelen, j, l-j, no_index)); - j = l; - } + nva.push_back(to_header(name, namelen, value, valuelen, no_index)); } const Headers::value_type* get_unique_header(const Headers& nva, @@ -355,29 +414,6 @@ nghttp2_nv make_nv(const std::string& name, const std::string& value, name.size(), value.size(), flags}; } -Headers concat_norm_headers(Headers headers) -{ - auto res = Headers(); - res.reserve(headers.size()); - for(auto& kv : headers) { - if(!res.empty() && res.back().name == kv.name && - kv.name != "cookie" && kv.name != "set-cookie") { - - auto& last = res.back(); - - if(!kv.value.empty()) { - last.value.append(1, '\0'); - last.value += kv.value; - } - // We do ORing nv flags. This is done even if value is empty. - last.no_index |= kv.no_index; - } else { - res.push_back(std::move(kv)); - } - } - return res; -} - void copy_norm_headers_to_nva (std::vector& nva, const Headers& headers) { diff --git a/src/http2.h b/src/http2.h index 39cb3013..a063d2ee 100644 --- a/src/http2.h +++ b/src/http2.h @@ -96,6 +96,16 @@ bool check_http2_allowed_header(const char *name); // contains such headers. bool check_http2_headers(const Headers& nva); +// Calls check_http2_headers() and also checks that |nva| only +// contains pseudo headers allowed in request. Returns true if all +// checks passed. +bool check_http2_request_headers(const Headers& nva); + +// Calls check_http2_headers() and also checks that |nva| only +// contains pseudo headers allowed in response. Returns true if all +// checks passed. +bool check_http2_response_headers(const Headers& nva); + bool name_less(const Headers::value_type& lhs, const Headers::value_type& rhs); void normalize_headers(Headers& nva); @@ -104,15 +114,13 @@ Headers::value_type to_header(const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, bool no_index); -// Add name/value pairs to |nva|. The name is given in the |name| with -// |namelen| bytes. This function inspects the |value| and split it -// using '\0' as delimiter. Each token is added to the |nva| with the -// name |name|. If |no_index| is true, this name/value pair won't be -// indexed when it is forwarded to the next hop. -void split_add_header(Headers& nva, - const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - bool no_index); +// Add name/value pairs to |nva|. If |no_index| is true, this +// name/value pair won't be indexed when it is forwarded to the next +// hop. +void add_header(Headers& nva, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + bool no_index); // Returns sorted |nva| with |nvlen| elements. The headers are sorted // by name only and not necessarily stable. In addition to the @@ -143,12 +151,6 @@ bool value_lws(const Headers::value_type *nv); // and not contain illegal characters. bool non_empty_value(const Headers::value_type *nv); -// Concatenates field with same value by NULL as delimiter and returns -// new vector containing the resulting header fields. cookie and -// set-cookie header fields won't be concatenated. This function -// assumes that the |headers| is sorted by name. -Headers concat_norm_headers(Headers headers); - // Creates nghttp2_nv using |name| and |value| and returns it. The // returned value only references the data pointer to name.c_str() and // value.c_str(). If |no_index| is true, nghttp2_nv flags member has diff --git a/src/http2_test.cc b/src/http2_test.cc index fa6dce8e..e59a4288 100644 --- a/src/http2_test.cc +++ b/src/http2_test.cc @@ -72,27 +72,19 @@ void test_http2_sort_nva(void) check_nv({"delta", "5"}, &nva[5]); } -void test_http2_split_add_header(void) +void test_http2_add_header(void) { - const uint8_t concatval[] = { '4', 0x00, 0x00, '6', 0x00, '5', '9', 0x00 }; auto nva = Headers(); - http2::split_add_header(nva, (const uint8_t*)"delta", 5, - concatval, sizeof(concatval), false); - CU_ASSERT(Headers::value_type("delta", "4") == nva[0]); - CU_ASSERT(Headers::value_type("delta", "6") == nva[1]); - CU_ASSERT(Headers::value_type("delta", "59") == nva[2]); - nva.clear(); - - http2::split_add_header(nva, (const uint8_t*)"alpha", 5, - (const uint8_t*)"123", 3, false); + http2::add_header(nva, (const uint8_t*)"alpha", 5, + (const uint8_t*)"123", 3, false); CU_ASSERT(Headers::value_type("alpha", "123") == nva[0]); CU_ASSERT(!nva[0].no_index); nva.clear(); - http2::split_add_header(nva, (const uint8_t*)"alpha", 5, - (const uint8_t*)"", 0, true); + http2::add_header(nva, (const uint8_t*)"alpha", 5, + (const uint8_t*)"", 0, true); CU_ASSERT(Headers::value_type("alpha", "") == nva[0]); CU_ASSERT(nva[0].no_index); } @@ -119,6 +111,21 @@ void test_http2_check_http2_headers(void) { "te2", "3" } }; CU_ASSERT(http2::check_http2_headers(nva3)); + + auto nva4 = Headers{ + { ":authority", "1" }, + { ":method", "2" }, + { ":path", "3" }, + { ":scheme", "4" } + }; + CU_ASSERT(http2::check_http2_request_headers(nva4)); + CU_ASSERT(!http2::check_http2_response_headers(nva4)); + + auto nva5 = Headers{ + { ":status", "1" } + }; + CU_ASSERT(!http2::check_http2_request_headers(nva5)); + CU_ASSERT(http2::check_http2_response_headers(nva5)); } void test_http2_get_unique_header(void) @@ -199,24 +206,12 @@ auto headers = Headers {"zulu", "12"}}; } // namespace -void test_http2_concat_norm_headers(void) -{ - auto hds = headers; - hds.emplace_back("cookie", "foo"); - hds.emplace_back("cookie", "bar"); - hds.emplace_back("set-cookie", "baz"); - hds.emplace_back("set-cookie", "buzz"); - auto res = http2::concat_norm_headers(hds); - CU_ASSERT(14 == res.size()); - CU_ASSERT(std::string("2") + '\0' + std::string("3") == res[2].value); -} - void test_http2_copy_norm_headers_to_nva(void) { std::vector nva; http2::copy_norm_headers_to_nva(nva, headers); - CU_ASSERT(6 == nva.size()); - auto ans = std::vector{0, 1, 4, 6, 7, 12}; + CU_ASSERT(7 == nva.size()); + auto ans = std::vector{0, 1, 4, 5, 6, 7, 12}; for(size_t i = 0; i < ans.size(); ++i) { check_nv(headers[ans[i]], &nva[i]); @@ -236,6 +231,7 @@ void test_http2_build_http1_headers_from_norm_headers(void) "Alpha: 0\r\n" "Bravo: 1\r\n" "Delta: 4\r\n" + "Expect: 5\r\n" "Foxtrot: 6\r\n" "Tango: 7\r\n" "Te: 8\r\n" diff --git a/src/http2_test.h b/src/http2_test.h index f2a2769f..b7a99c29 100644 --- a/src/http2_test.h +++ b/src/http2_test.h @@ -27,13 +27,12 @@ namespace shrpx { -void test_http2_split_add_header(void); +void test_http2_add_header(void); 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); void test_http2_value_lws(void); -void test_http2_concat_norm_headers(void); void test_http2_copy_norm_headers_to_nva(void); void test_http2_build_http1_headers_from_norm_headers(void); void test_http2_lws(void); diff --git a/src/nghttp.cc b/src/nghttp.cc index 58bb4ec1..7a7c2752 100644 --- a/src/nghttp.cc +++ b/src/nghttp.cc @@ -1048,8 +1048,6 @@ int submit_request return lhs.name < rhs.name; }); - build_headers = http2::concat_norm_headers(std::move(build_headers)); - auto nva = std::vector(); nva.reserve(build_headers.size()); @@ -1264,8 +1262,8 @@ int on_header_callback(nghttp2_session *session, if(!req) { break; } - http2::split_add_header(req->res_nva, name, namelen, value, valuelen, - flags & NGHTTP2_NV_FLAG_NO_INDEX); + http2::add_header(req->res_nva, name, namelen, value, valuelen, + flags & NGHTTP2_NV_FLAG_NO_INDEX); break; } case NGHTTP2_PUSH_PROMISE: { @@ -1274,8 +1272,8 @@ int on_header_callback(nghttp2_session *session, if(!req) { break; } - http2::split_add_header(req->push_req_nva, name, namelen, value, valuelen, - flags & NGHTTP2_NV_FLAG_NO_INDEX); + http2::add_header(req->push_req_nva, name, namelen, value, valuelen, + flags & NGHTTP2_NV_FLAG_NO_INDEX); break; } } diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index 6015ad4b..0d6f0a5f 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -71,8 +71,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_split_add_header", - shrpx::test_http2_split_add_header) || + !CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) || !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) || @@ -82,8 +81,6 @@ int main(int argc, char* argv[]) shrpx::test_http2_get_header) || !CU_add_test(pSuite, "http2_value_lws", shrpx::test_http2_value_lws) || - !CU_add_test(pSuite, "http2_concat_norm_headers", - shrpx::test_http2_concat_norm_headers) || !CU_add_test(pSuite, "http2_copy_norm_headers_to_nva", shrpx::test_http2_copy_norm_headers_to_nva) || !CU_add_test(pSuite, "http2_build_http1_headers_from_norm_headers", diff --git a/src/shrpx.cc b/src/shrpx.cc index 7d07952b..1e74a6bf 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -562,9 +562,7 @@ void fill_default_config() nghttp2_option_new(&mod_config()->http2_option); - nghttp2_option_set_no_auto_stream_window_update - (mod_config()->http2_option, 1); - nghttp2_option_set_no_auto_connection_window_update + nghttp2_option_set_no_auto_window_update (mod_config()->http2_option, 1); mod_config()->tls_proto_mask = 0; diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index 6c1c26b5..6a3f3ded 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -46,6 +46,8 @@ Downstream::Downstream(Upstream *upstream, int stream_id, int priority) response_body_buf_(nullptr), request_headers_sum_(0), response_headers_sum_(0), + request_datalen_(0), + response_datalen_(0), stream_id_(stream_id), priority_(priority), downstream_stream_id_(-1), @@ -63,12 +65,12 @@ Downstream::Downstream(Upstream *upstream, int stream_id, int priority) http2_settings_seen_(false), chunked_request_(false), request_connection_close_(false), - request_expect_100_continue_(false), request_header_key_prev_(false), request_http2_expect_body_(false), chunked_response_(false), response_connection_close_(false), - response_header_key_prev_(false) + response_header_key_prev_(false), + expect_final_response_(false) {} Downstream::~Downstream() @@ -230,11 +232,6 @@ Headers::const_iterator Downstream::get_norm_request_header return get_norm_header(request_headers_, name); } -void Downstream::concat_norm_request_headers() -{ - request_headers_ = http2::concat_norm_headers(std::move(request_headers_)); -} - void Downstream::add_request_header(std::string name, std::string value) { request_header_key_prev_ = true; @@ -256,8 +253,8 @@ void Downstream::split_add_request_header bool no_index) { request_headers_sum_ += namelen + valuelen; - http2::split_add_header(request_headers_, name, namelen, value, valuelen, - no_index); + http2::add_header(request_headers_, name, namelen, value, valuelen, + no_index); } bool Downstream::get_request_header_key_prev() const @@ -429,11 +426,6 @@ void Downstream::set_request_http2_expect_body(bool f) request_http2_expect_body_ = f; } -bool Downstream::get_expect_100_continue() const -{ - return request_expect_100_continue_; -} - bool Downstream::get_output_buffer_full() { if(dconn_) { @@ -463,7 +455,13 @@ int Downstream::push_upload_data_chunk(const uint8_t *data, size_t datalen) return -1; } request_bodylen_ += datalen; - return dconn_->push_upload_data_chunk(data, datalen); + if(dconn_->push_upload_data_chunk(data, datalen) != 0) { + return -1; + } + + request_datalen_ += datalen; + + return 0; } int Downstream::end_upload_data() @@ -485,11 +483,6 @@ void Downstream::normalize_response_headers() http2::normalize_headers(response_headers_); } -void Downstream::concat_norm_response_headers() -{ - response_headers_ = http2::concat_norm_headers(std::move(response_headers_)); -} - Headers::const_iterator Downstream::get_norm_response_header (const std::string& name) const { @@ -551,8 +544,8 @@ void Downstream::split_add_response_header bool no_index) { response_headers_sum_ += namelen + valuelen; - http2::split_add_header(response_headers_, name, namelen, value, valuelen, - no_index); + http2::add_header(response_headers_, name, namelen, value, valuelen, + no_index); } bool Downstream::get_response_header_key_prev() const @@ -754,11 +747,6 @@ void Downstream::inspect_http1_request() if(util::strifind(hd.value.c_str(), "chunked")) { chunked_request_ = true; } - } else if(!request_expect_100_continue_ && - util::strieq(hd.name.c_str(), "expect")) { - if(util::strifind(hd.value.c_str(), "100-continue")) { - request_expect_100_continue_ = true; - } } } } @@ -775,6 +763,18 @@ void Downstream::inspect_http1_response() } } +void Downstream::reset_response() +{ + response_http_status_ = 0; + response_major_ = 1; + response_minor_ = 1; +} + +bool Downstream::get_non_final_response() const +{ + return response_http_status_ / 100 == 1; +} + bool Downstream::get_upgraded() const { return upgraded_; @@ -816,4 +816,47 @@ void Downstream::set_response_rst_stream_error_code response_rst_stream_error_code_ = error_code; } +void Downstream::set_expect_final_response(bool f) +{ + expect_final_response_ = f; +} + +bool Downstream::get_expect_final_response() const +{ + return expect_final_response_; +} + +size_t Downstream::get_request_datalen() const +{ + return request_datalen_; +} + +void Downstream::reset_request_datalen() +{ + request_datalen_ = 0; +} + +void Downstream::add_response_datalen(size_t len) +{ + response_datalen_ += len; +} + +size_t Downstream::get_response_datalen() const +{ + return response_datalen_; +} + +void Downstream::reset_response_datalen() +{ + response_datalen_ = 0; +} + +bool Downstream::expect_response_body() const +{ + return request_method_ != "HEAD" && + response_http_status_ / 100 != 1 && + response_http_status_ != 304 && + response_http_status_ != 204; +} + } // namespace shrpx diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index c69c53d3..080f798a 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -99,10 +99,6 @@ public: // called after calling normalize_request_headers(). Headers::const_iterator get_norm_request_header (const std::string& name) const; - // Concatenates request header fields with same name by NULL as - // delimiter. See http2::concat_norm_headers(). This function must - // be called after calling normalize_request_headers(). - void concat_norm_request_headers(); void add_request_header(std::string name, std::string value); void set_last_request_header_value(std::string value); @@ -144,9 +140,11 @@ public: const std::string& get_request_user_agent() const; bool get_request_http2_expect_body() const; void set_request_http2_expect_body(bool f); - bool get_expect_100_continue() const; int push_upload_data_chunk(const uint8_t *data, size_t datalen); int end_upload_data(); + size_t get_request_datalen() const; + void reset_request_datalen(); + bool expect_response_body() const; enum { INITIAL, HEADER_COMPLETE, @@ -162,10 +160,6 @@ public: const Headers& get_response_headers() const; // Makes key lowercase and sort headers by name using < void normalize_response_headers(); - // Concatenates response header fields with same name by NULL as - // delimiter. See http2::concat_norm_headers(). This function must - // be called after calling normalize_response_headers(). - void concat_norm_response_headers(); // Returns iterator pointing to the response header with the name // |name|. If multiple header have |name| as name, return first // occurrence from the beginning. If no such header is found, @@ -215,6 +209,14 @@ public: void set_response_rst_stream_error_code(nghttp2_error_code error_code); // Inspects HTTP/1 response. This checks tranfer-encoding etc. void inspect_http1_response(); + // Clears some of member variables for response. + void reset_response(); + bool get_non_final_response() const; + void set_expect_final_response(bool f); + bool get_expect_final_response() const; + void add_response_datalen(size_t len); + size_t get_response_datalen() const; + void reset_response_datalen(); // Call this method when there is incoming data in downstream // connection. @@ -254,6 +256,10 @@ private: size_t request_headers_sum_; size_t response_headers_sum_; + // The number of bytes not consumed by the application yet. + size_t request_datalen_; + size_t response_datalen_; + int32_t stream_id_; int32_t priority_; // stream ID in backend connection @@ -282,13 +288,13 @@ private: bool chunked_request_; bool request_connection_close_; - bool request_expect_100_continue_; bool request_header_key_prev_; bool request_http2_expect_body_; bool chunked_response_; bool response_connection_close_; bool response_header_key_prev_; + bool expect_final_response_; }; } // namespace shrpx diff --git a/src/shrpx_http2_downstream_connection.cc b/src/shrpx_http2_downstream_connection.cc index 91dba637..549ecd13 100644 --- a/src/shrpx_http2_downstream_connection.cc +++ b/src/shrpx_http2_downstream_connection.cc @@ -67,6 +67,15 @@ Http2DownstreamConnection::~Http2DownstreamConnection() if(submit_rst_stream(downstream_) == 0) { http2session_->notify(); } + + if(downstream_->get_downstream_stream_id() != -1) { + http2session_->consume(downstream_->get_downstream_stream_id(), + downstream_->get_response_datalen()); + + downstream_->reset_response_datalen(); + + http2session_->notify(); + } } http2session_->remove_downstream_connection(this); // Downstream and DownstreamConnection may be deleted @@ -122,6 +131,16 @@ void Http2DownstreamConnection::detach_downstream(Downstream *downstream) if(submit_rst_stream(downstream) == 0) { http2session_->notify(); } + + if(downstream_->get_downstream_stream_id() != -1) { + http2session_->consume(downstream_->get_downstream_stream_id(), + downstream_->get_response_datalen()); + + downstream_->reset_response_datalen(); + + http2session_->notify(); + } + downstream->set_downstream_connection(nullptr); downstream_ = nullptr; @@ -244,7 +263,7 @@ int Http2DownstreamConnection::push_request_headers() downstream_->crumble_request_cookie(); } downstream_->normalize_request_headers(); - downstream_->concat_norm_request_headers(); + auto end_headers = std::end(downstream_->get_request_headers()); // 7 means: @@ -302,7 +321,12 @@ int Http2DownstreamConnection::push_request_headers() http2::copy_url_component(path, &u, UF_PATH, url); http2::copy_url_component(query, &u, UF_QUERY, url); if(path.empty()) { - path = "/"; + if(!authority.empty() && + downstream_->get_request_method() == "OPTIONS") { + path = "*"; + } else { + path = "/"; + } } if(!query.empty()) { path += "?"; @@ -469,34 +493,29 @@ int Http2DownstreamConnection::end_upload_data() int Http2DownstreamConnection::resume_read(IOCtrlReason reason) { - int rv1 = 0, rv2 = 0; - if(http2session_->get_state() == Http2Session::CONNECTED && - http2session_->get_flow_control()) { - int32_t window_size_increment; - window_size_increment = http2::determine_window_update_transmission - (http2session_->get_session(), 0); - if(window_size_increment != -1) { - rv1 = http2session_->submit_window_update(nullptr, window_size_increment); - if(rv1 == 0) { - http2session_->notify(); - } - } - if(downstream_ && downstream_->get_downstream_stream_id() != -1) { - window_size_increment = http2::determine_window_update_transmission - (http2session_->get_session(), downstream_->get_downstream_stream_id()); - if(window_size_increment != -1) { - rv2 = http2session_->submit_window_update(this, window_size_increment); - if(rv2 == 0) { - http2session_->notify(); - } - } - } - } - if(rv1 == 0 && rv2 == 0) { + int rv; + + if(http2session_->get_state() != Http2Session::CONNECTED || + !http2session_->get_flow_control()) { return 0; } - DLOG(WARNING, this) << "Sending WINDOW_UPDATE failed"; - return -1; + + if(!downstream_ || downstream_->get_downstream_stream_id() == -1) { + return 0; + } + + rv = http2session_->consume(downstream_->get_downstream_stream_id(), + downstream_->get_response_datalen()); + + if(rv != 0) { + return -1; + } + + downstream_->reset_response_datalen(); + + http2session_->notify(); + + return 0; } int Http2DownstreamConnection::on_read() diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index 41bb1e00..a7117248 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -58,7 +58,6 @@ Http2Session::Http2Session(event_base *evbase, SSL_CTX *ssl_ctx) wrbev_(nullptr), rdbev_(nullptr), settings_timerev_(nullptr), - recv_ign_window_size_(0), fd_(-1), state_(DISCONNECTED), notified_(false), @@ -661,28 +660,6 @@ int Http2Session::submit_rst_stream(int32_t stream_id, return 0; } -int Http2Session::submit_window_update(Http2DownstreamConnection *dconn, - int32_t amount) -{ - assert(state_ == CONNECTED); - int rv; - int32_t stream_id; - if(dconn) { - stream_id = dconn->get_downstream()->get_downstream_stream_id(); - } else { - stream_id = 0; - recv_ign_window_size_ = 0; - } - rv = nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE, - stream_id, amount); - if(rv < NGHTTP2_ERR_FATAL) { - SSLOG(FATAL, this) << "nghttp2_submit_window_update() failed: " - << nghttp2_strerror(rv); - return -1; - } - return 0; -} - int Http2Session::submit_priority(Http2DownstreamConnection *dconn, int32_t pri) { @@ -752,7 +729,6 @@ int on_stream_close_callback (nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code, void *user_data) { - int rv; auto http2session = static_cast(user_data); if(LOG_ENABLED(INFO)) { SSLOG(INFO, http2session) << "Stream stream_id=" << stream_id @@ -769,11 +745,14 @@ int on_stream_close_callback if(dconn) { auto downstream = dconn->get_downstream(); if(downstream && downstream->get_downstream_stream_id() == stream_id) { - auto upstream = downstream->get_upstream(); + + http2session->consume(downstream->get_downstream_stream_id(), + downstream->get_response_datalen()); + + downstream->reset_response_datalen(); + if(error_code == NGHTTP2_NO_ERROR) { - downstream->set_response_state(Downstream::MSG_COMPLETE); - rv = upstream->on_downstream_body_complete(downstream); - if(rv != 0) { + if(downstream->get_response_state() != Downstream::MSG_COMPLETE) { downstream->set_response_state(Downstream::MSG_RESET); } } else { @@ -841,10 +820,6 @@ int on_header_callback(nghttp2_session *session, uint8_t flags, void *user_data) { - if(frame->hd.type != NGHTTP2_HEADERS || - frame->headers.cat != NGHTTP2_HCAT_RESPONSE) { - return 0; - } auto sd = static_cast (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); if(!sd || !sd->dconn) { @@ -854,6 +829,13 @@ int on_header_callback(nghttp2_session *session, if(!downstream) { return 0; } + + if(frame->hd.type != NGHTTP2_HEADERS || + (frame->headers.cat != NGHTTP2_HCAT_RESPONSE && + !downstream->get_expect_final_response())) { + return 0; + } + if(downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) { if(LOG_ENABLED(INFO)) { DLOG(INFO, downstream) << "Too large header block size=" @@ -905,23 +887,20 @@ int on_begin_headers_callback(nghttp2_session *session, namespace { int on_response_headers(Http2Session *http2session, + Downstream *downstream, nghttp2_session *session, const nghttp2_frame *frame) { int rv; - auto sd = static_cast - (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); - if(!sd || !sd->dconn) { - return 0; - } - auto downstream = sd->dconn->get_downstream(); - if(!downstream) { - return 0; - } + + auto upstream = downstream->get_upstream(); + downstream->normalize_response_headers(); auto& nva = downstream->get_response_headers(); - if(!http2::check_http2_headers(nva)) { + downstream->set_expect_final_response(false); + + if(!http2::check_http2_response_headers(nva)) { http2session->submit_rst_stream(frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); downstream->set_response_state(Downstream::MSG_RESET); @@ -935,8 +914,10 @@ int on_response_headers(Http2Session *http2session, NGHTTP2_PROTOCOL_ERROR); downstream->set_response_state(Downstream::MSG_RESET); call_downstream_readcb(http2session, downstream); + return 0; } + downstream->set_response_http_status(strtoul(status->value.c_str(), nullptr, 10)); downstream->set_response_major(2); @@ -952,6 +933,26 @@ int on_response_headers(Http2Session *http2session, << "\n" << ss.str(); } + if(downstream->get_non_final_response()) { + + if(LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) << "This is non-final response."; + } + + downstream->set_expect_final_response(true); + rv = upstream->on_downstream_header_complete(downstream); + + // Now Dowstream's response headers are erased. + + if(rv != 0) { + http2session->submit_rst_stream(frame->hd.stream_id, + NGHTTP2_PROTOCOL_ERROR); + downstream->set_response_state(Downstream::MSG_RESET); + } + + return 0; + } + auto content_length = http2::get_header(nva, "content-length"); if(!content_length && downstream->get_request_method() != "HEAD" && downstream->get_request_method() != "CONNECT") { @@ -975,7 +976,6 @@ int on_response_headers(Http2Session *http2session, } } - auto upstream = downstream->get_upstream(); downstream->set_response_state(Downstream::HEADER_COMPLETE); downstream->check_upgrade_fulfilled(); if(downstream->get_upgraded()) { @@ -1033,19 +1033,72 @@ int on_frame_recv_callback http2session->submit_rst_stream(frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR); downstream->set_response_state(Downstream::MSG_RESET); + + } else if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + + if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { + + downstream->set_response_state(Downstream::MSG_COMPLETE); + + rv = upstream->on_downstream_body_complete(downstream); + + if(rv != 0) { + downstream->set_response_state(Downstream::MSG_RESET); + } + } } + call_downstream_readcb(http2session, downstream); break; } - case NGHTTP2_HEADERS: + case NGHTTP2_HEADERS: { + auto sd = static_cast + (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if(!sd || !sd->dconn) { + break; + } + auto downstream = sd->dconn->get_downstream(); + + if(!downstream) { + return 0; + } + if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE) { - rv = on_response_headers(http2session, session, frame); + rv = on_response_headers(http2session, downstream, session, frame); if(rv != 0) { return rv; } } + + if(frame->headers.cat == NGHTTP2_HCAT_HEADERS) { + if(downstream->get_expect_final_response()) { + + rv = on_response_headers(http2session, downstream, session, frame); + + if(rv != 0) { + return rv; + } + } else if((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + } + + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { + downstream->set_response_state(Downstream::MSG_COMPLETE); + + auto upstream = downstream->get_upstream(); + + rv = upstream->on_downstream_body_complete(downstream); + + if(rv != 0) { + downstream->set_response_state(Downstream::MSG_RESET); + } + } + } break; + } case NGHTTP2_RST_STREAM: { auto sd = static_cast (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); @@ -1055,7 +1108,7 @@ int on_frame_recv_callback downstream->get_downstream_stream_id() == frame->hd.stream_id) { if(downstream->get_upgraded() && downstream->get_response_state() == Downstream::HEADER_COMPLETE) { - // For tunneled connection, we has to submit RST_STREAM to + // For tunneled connection, we have to submit RST_STREAM to // upstream *after* whole response body is sent. We just set // MSG_COMPLETE here. Upstream will take care of that. if(LOG_ENABLED(INFO)) { @@ -1112,13 +1165,35 @@ int on_data_chunk_recv_callback(nghttp2_session *session, (nghttp2_session_get_stream_user_data(session, stream_id)); if(!sd || !sd->dconn) { http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR); - http2session->handle_ign_data_chunk(len); + + if(http2session->consume(stream_id, len) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; } auto downstream = sd->dconn->get_downstream(); - if(!downstream || downstream->get_downstream_stream_id() != stream_id) { + if(!downstream || downstream->get_downstream_stream_id() != stream_id || + !downstream->expect_response_body()) { + http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR); - http2session->handle_ign_data_chunk(len); + + if(http2session->consume(stream_id, len) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return 0; + } + + // We don't want DATA after non-final response, which is illegal in + // HTTP. + if(downstream->get_non_final_response()) { + http2session->submit_rst_stream(stream_id, NGHTTP2_PROTOCOL_ERROR); + + if(http2session->consume(stream_id, len) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; } @@ -1128,9 +1203,16 @@ int on_data_chunk_recv_callback(nghttp2_session *session, rv = upstream->on_downstream_body(downstream, data, len, false); if(rv != 0) { http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR); - http2session->handle_ign_data_chunk(len); + + if(http2session->consume(stream_id, len) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + downstream->set_response_state(Downstream::MSG_RESET); } + + downstream->add_response_datalen(len); + call_downstream_readcb(http2session, downstream); return 0; } @@ -1470,16 +1552,21 @@ SSL* Http2Session::get_ssl() const return ssl_; } -int Http2Session::handle_ign_data_chunk(size_t len) +int Http2Session::consume(int32_t stream_id, size_t len) { - int32_t window_size; + int rv; - recv_ign_window_size_ += len; + if(!session_) { + return 0; + } - window_size = nghttp2_session_get_effective_local_window_size(session_); + rv = nghttp2_session_consume(session_, stream_id, len); - if(recv_ign_window_size_ >= window_size / 2) { - submit_window_update(nullptr, recv_ign_window_size_); + if(rv != 0) { + SSLOG(WARNING, this) << "nghttp2_session_consume() returned error: " + << nghttp2_strerror(rv); + + return -1; } return 0; diff --git a/src/shrpx_http2_session.h b/src/shrpx_http2_session.h index 9a5d6a9a..be40c9bc 100644 --- a/src/shrpx_http2_session.h +++ b/src/shrpx_http2_session.h @@ -70,10 +70,6 @@ public: int submit_rst_stream(int32_t stream_id, nghttp2_error_code error_code); - // To send WINDOW_UPDATE for a connection, specify nullptr to - // |dconn|. - int submit_window_update(Http2DownstreamConnection *dconn, int32_t amount); - int submit_priority(Http2DownstreamConnection *dconn, int32_t pri); int terminate_session(nghttp2_error_code error_code); @@ -108,7 +104,7 @@ public: SSL* get_ssl() const; - int handle_ign_data_chunk(size_t len); + int consume(int32_t stream_id, size_t len); enum { // Disconnected @@ -140,7 +136,6 @@ private: bufferevent *wrbev_; bufferevent *rdbev_; event *settings_timerev_; - int32_t recv_ign_window_size_; // fd_ is used for proxy connection and no TLS connection. For // direct or TLS connection, it may be -1 even after connection is // established. Use bufferevent_getfd(bev_) to get file descriptor diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index a7c89a3b..b043b7ce 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -60,12 +60,17 @@ int on_stream_close_callback ULOG(INFO, upstream) << "Stream stream_id=" << stream_id << " is being closed"; } + auto downstream = upstream->find_downstream(stream_id); if(!downstream) { return 0; } + upstream->consume(stream_id, downstream->get_request_datalen()); + + downstream->reset_request_datalen(); + if(downstream->get_request_state() == Downstream::CONNECT_FAIL) { upstream->remove_downstream(downstream); @@ -297,7 +302,7 @@ int on_request_headers(Http2Upstream *upstream, http2::dump_nv(get_config()->http2_upstream_dump_request_header, nva); } - if(!http2::check_http2_headers(nva)) { + if(!http2::check_http2_request_headers(nva)) { if(upstream->error_reply(downstream, 400) != 0) { upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); } @@ -421,6 +426,8 @@ int on_frame_recv_callback if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { downstream->end_upload_data(); downstream->set_request_state(Downstream::MSG_COMPLETE); + } else { + return NGHTTP2_ERR_CALLBACK_FAILURE; } break; @@ -476,19 +483,21 @@ int on_data_chunk_recv_callback(nghttp2_session *session, auto upstream = static_cast(user_data); auto downstream = upstream->find_downstream(stream_id); - if(!downstream) { - upstream->handle_ign_data_chunk(len); - return 0; - } + if(!downstream || !downstream->get_downstream_connection()) { + if(upstream->consume(stream_id, len) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } - if(!downstream->get_downstream_connection()) { - upstream->handle_ign_data_chunk(len); return 0; } if(downstream->push_upload_data_chunk(data, len) != 0) { upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); - upstream->handle_ign_data_chunk(len); + + if(upstream->consume(stream_id, len) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; } @@ -586,8 +595,7 @@ nghttp2_error_code infer_upstream_rst_stream_error_code Http2Upstream::Http2Upstream(ClientHandler *handler) : handler_(handler), session_(nullptr), - settings_timerev_(nullptr), - recv_ign_window_size_(0) + settings_timerev_(nullptr) { handler->set_upstream_timeouts(&get_config()->http2_upstream_read_timeout, &get_config()->upstream_write_timeout); @@ -989,30 +997,6 @@ int Http2Upstream::rst_stream(Downstream *downstream, return 0; } -int Http2Upstream::window_update(Downstream *downstream, - int32_t window_size_increment) -{ - int rv; - int32_t stream_id; - - if(downstream) { - stream_id = downstream->get_stream_id(); - } else { - stream_id = 0; - recv_ign_window_size_ = 0; - } - - rv = nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE, - stream_id, window_size_increment); - - if(rv < NGHTTP2_ERR_FATAL) { - ULOG(FATAL, this) << "nghttp2_submit_window_update() failed: " - << nghttp2_strerror(rv); - DIE(); - } - return 0; -} - int Http2Upstream::terminate_session(nghttp2_error_code error_code) { int rv; @@ -1153,15 +1137,22 @@ nghttp2_session* Http2Upstream::get_http2_session() // nghttp2_session_recv. These calls may delete downstream. int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { + int rv; + if(LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "HTTP response header completed"; + if(downstream->get_non_final_response()) { + DLOG(INFO, downstream) << "HTTP non-final response header"; + } else { + DLOG(INFO, downstream) << "HTTP response header completed"; + } } + downstream->normalize_response_headers(); if(!get_config()->http2_proxy && !get_config()->client_proxy) { downstream->rewrite_norm_location_response_header (get_client_handler()->get_upstream_scheme(), get_config()->port); } - downstream->concat_norm_response_headers(); + auto end_headers = std::end(downstream->get_response_headers()); size_t nheader = downstream->get_response_headers().size(); auto nva = std::vector(); @@ -1172,6 +1163,26 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) nva.push_back(http2::make_nv_ls(":status", response_status)); http2::copy_norm_headers_to_nva(nva, downstream->get_response_headers()); + + if(downstream->get_non_final_response()) { + if(LOG_ENABLED(INFO)) { + log_response_headers(downstream, nva); + } + + rv = nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE, + downstream->get_stream_id(), nullptr, + nva.data(), nva.size(), nullptr); + + downstream->clear_response_headers(); + + if(rv != 0) { + ULOG(FATAL, this) << "nghttp2_submit_headers() failed"; + return -1; + } + + return 0; + } + auto via = downstream->get_norm_response_header("via"); if(get_config()->no_via) { if(via != end_headers) { @@ -1192,17 +1203,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) } if(LOG_ENABLED(INFO)) { - std::stringstream ss; - for(auto& nv : nva) { - ss << TTY_HTTP_HD; - ss.write(reinterpret_cast(nv.name), nv.namelen); - ss << TTY_RST << ": "; - ss.write(reinterpret_cast(nv.value), nv.valuelen); - ss << "\n"; - } - ULOG(INFO, this) << "HTTP response headers. stream_id=" - << downstream->get_stream_id() << "\n" - << ss.str(); + log_response_headers(downstream, nva); } if(get_config()->http2_upstream_dump_response_header) { @@ -1214,9 +1215,16 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) data_prd.source.ptr = downstream; data_prd.read_callback = downstream_data_read_callback; - int rv; + nghttp2_data_provider *data_prdptr; + + if(downstream->expect_response_body()) { + data_prdptr = &data_prd; + } else { + data_prdptr = nullptr; + } + rv = nghttp2_submit_response(session_, downstream->get_stream_id(), - nva.data(), nva.size(), &data_prd); + nva.data(), nva.size(),data_prdptr); if(rv != 0) { ULOG(FATAL, this) << "nghttp2_submit_response() failed"; return -1; @@ -1282,18 +1290,14 @@ void Http2Upstream::pause_read(IOCtrlReason reason) int Http2Upstream::resume_read(IOCtrlReason reason, Downstream *downstream) { if(get_flow_control()) { - int32_t window_size_increment; - window_size_increment = http2::determine_window_update_transmission - (session_, 0); - if(window_size_increment != -1) { - window_update(nullptr, window_size_increment); - } - window_size_increment = http2::determine_window_update_transmission - (session_, downstream->get_stream_id()); - if(window_size_increment != -1) { - window_update(downstream, window_size_increment); + if(consume(downstream->get_stream_id(), + downstream->get_request_datalen()) != 0) { + return -1; } + + downstream->reset_request_datalen(); } + return send(); } @@ -1311,19 +1315,35 @@ int Http2Upstream::on_downstream_abort_request(Downstream *downstream, return send(); } -int Http2Upstream::handle_ign_data_chunk(size_t len) +int Http2Upstream::consume(int32_t stream_id, size_t len) { - int32_t window_size; + int rv; - recv_ign_window_size_ += len; + rv = nghttp2_session_consume(session_, stream_id, len); - window_size = nghttp2_session_get_effective_local_window_size(session_); - - if(recv_ign_window_size_ >= window_size / 2) { - window_update(nullptr, recv_ign_window_size_); + if(rv != 0) { + ULOG(WARNING, this) << "nghttp2_session_consume() returned error: " + << nghttp2_strerror(rv); + return -1; } return 0; } +void Http2Upstream::log_response_headers +(Downstream *downstream, const std::vector& nva) const +{ + std::stringstream ss; + for(auto& nv : nva) { + ss << TTY_HTTP_HD; + ss.write(reinterpret_cast(nv.name), nv.namelen); + ss << TTY_RST << ": "; + ss.write(reinterpret_cast(nv.value), nv.valuelen); + ss << "\n"; + } + ULOG(INFO, this) << "HTTP response headers. stream_id=" + << downstream->get_stream_id() << "\n" + << ss.str(); +} + } // namespace shrpx diff --git a/src/shrpx_http2_upstream.h b/src/shrpx_http2_upstream.h index 87b42db8..34b0a3bb 100644 --- a/src/shrpx_http2_upstream.h +++ b/src/shrpx_http2_upstream.h @@ -60,9 +60,6 @@ public: nghttp2_session* get_http2_session(); int rst_stream(Downstream *downstream, nghttp2_error_code error_code); - // To send WINDOW_UPDATE for a connection, specify nullptr to - // |downstream|. - int window_update(Downstream *downstream, int32_t window_size_increment); int terminate_session(nghttp2_error_code error_code); int error_reply(Downstream *downstream, unsigned int status_code); @@ -81,16 +78,15 @@ public: int upgrade_upstream(HttpsUpstream *upstream); int start_settings_timer(); void stop_settings_timer(); - int handle_ign_data_chunk(size_t len); + int consume(int32_t stream_id, size_t len); + void log_response_headers(Downstream *downstream, + const std::vector& nva) const; private: DownstreamQueue downstream_queue_; std::unique_ptr pre_upstream_; ClientHandler *handler_; nghttp2_session *session_; event *settings_timerev_; - // Received DATA frame size while it is not sent to backend before - // any connection-level WINDOW_UPDATE - int32_t recv_ign_window_size_; bool flow_control_; }; diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index 9d748391..592dabcf 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -135,15 +135,25 @@ int HttpDownstreamConnection::push_request_headers() hdrs += downstream_->get_request_path(); } } else if(get_config()->http2_proxy && - !downstream_->get_request_http2_scheme().empty() && - !downstream_->get_request_http2_authority().empty() && - downstream_->get_request_path().c_str()[0] == '/') { + !downstream_->get_request_http2_scheme().empty() && + !downstream_->get_request_http2_authority().empty() && + (downstream_->get_request_path().c_str()[0] == '/' || + downstream_->get_request_path() == "*")) { // Construct absolute-form request target because we are going to // send a request to a HTTP/1 proxy. hdrs += downstream_->get_request_http2_scheme(); hdrs += "://"; hdrs += downstream_->get_request_http2_authority(); - hdrs += downstream_->get_request_path(); + + // Server-wide OPTIONS takes following form in proxy request: + // + // OPTIONS http://example.org HTTP/1.1 + // + // Notice that no slash after authority. See + // http://tools.ietf.org/html/rfc7230#section-5.3.4 + if(downstream_->get_request_path() != "*") { + hdrs += downstream_->get_request_path(); + } } else { // No proxy case. get_request_path() may be absolute-form but we // don't care. @@ -408,9 +418,26 @@ namespace { int htp_hdrs_completecb(http_parser *htp) { auto downstream = static_cast(htp->data); + auto upstream = downstream->get_upstream(); + int rv; + downstream->set_response_http_status(htp->status_code); downstream->set_response_major(htp->http_major); downstream->set_response_minor(htp->http_minor); + + if(downstream->get_non_final_response()) { + // For non-final response code, we just call + // on_downstream_header_complete() without changing response + // state. + rv = upstream->on_downstream_header_complete(downstream); + + if(rv != 0) { + return -1; + } + + return 0; + } + downstream->set_response_connection_close(!http_should_keep_alive(htp)); downstream->set_response_state(Downstream::HEADER_COMPLETE); downstream->inspect_http1_response(); @@ -418,15 +445,13 @@ int htp_hdrs_completecb(http_parser *htp) if(downstream->get_upgraded()) { downstream->set_response_connection_close(true); } - if(downstream->get_upstream()->on_downstream_header_complete(downstream) - != 0) { + if(upstream->on_downstream_header_complete(downstream) != 0) { return -1; } if(downstream->get_upgraded()) { // Upgrade complete, read until EOF in both ends - if(downstream->get_upstream()->resume_read(SHRPX_MSG_BLOCK, - downstream) != 0) { + if(upstream->resume_read(SHRPX_MSG_BLOCK, downstream) != 0) { return -1; } downstream->set_request_state(Downstream::HEADER_COMPLETE); @@ -443,6 +468,9 @@ int htp_hdrs_completecb(http_parser *htp) // 304 status code with nonzero Content-Length, but without response // body. See // http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-20#section-3.3 + + // TODO It seems that the cases other than HEAD are handled by + // http-parser. Need test. return downstream->get_request_method() == "HEAD" || (100 <= status && status <= 199) || status == 204 || status == 304 ? 1 : 0; @@ -505,6 +533,13 @@ namespace { int htp_msg_completecb(http_parser *htp) { auto downstream = static_cast(htp->data); + + if(downstream->get_non_final_response()) { + downstream->reset_response(); + + return 0; + } + downstream->set_response_state(Downstream::MSG_COMPLETE); // Block reading another response message from (broken?) // server. This callback is not called if the connection is diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index 0dd47b32..80b6981f 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -190,16 +190,6 @@ int htp_hdrs_completecb(http_parser *htp) auto dconn = upstream->get_client_handler()->get_downstream_connection(); - if(downstream->get_expect_100_continue()) { - static const char reply_100[] = "HTTP/1.1 100 Continue\r\n\r\n"; - if(bufferevent_write(upstream->get_client_handler()->get_bev(), - reply_100, sizeof(reply_100)-1) != 0) { - ULOG(FATAL, upstream) << "bufferevent_write() faild"; - delete dconn; - return -1; - } - } - rv = dconn->attach_downstream(downstream); if(rv != 0) { @@ -416,6 +406,23 @@ int HttpsUpstream::on_write() int rv = 0; auto downstream = get_downstream(); if(downstream) { + // We need to postpone detachment until all data are sent so that + // we can notify nghttp2 library all data consumed. + if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { + auto dconn = downstream->get_downstream_connection(); + + if(downstream->get_response_connection_close()) { + + // Connection close + downstream->set_downstream_connection(nullptr); + + delete dconn; + } else { + // Keep-alive + dconn->detach_downstream(downstream); + } + } + rv = downstream->resume_read(SHRPX_NO_BUFFER); } return rv; @@ -503,17 +510,20 @@ void https_downstream_readcb(bufferevent *bev, void *ptr) return; } + // If pending data exist, we defer detachment to correctly notify + // the all consumed data to nghttp2 library. + if(handler->get_outbuf_length() == 0) { + if(downstream->get_response_connection_close()) { + // Connection close + downstream->set_downstream_connection(nullptr); - if(downstream->get_response_connection_close()) { - // Connection close - downstream->set_downstream_connection(nullptr); + delete dconn; - delete dconn; - - dconn = nullptr; - } else { - // Keep-alive - dconn->detach_downstream(downstream); + dconn = nullptr; + } else { + // Keep-alive + dconn->detach_downstream(downstream); + } } if(downstream->get_request_state() == Downstream::MSG_COMPLETE) { @@ -740,7 +750,11 @@ Downstream* HttpsUpstream::pop_downstream() int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { if(LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "HTTP response header completed"; + if(downstream->get_non_final_response()) { + DLOG(INFO, downstream) << "HTTP non-final response header"; + } else { + DLOG(INFO, downstream) << "HTTP response header completed"; + } } std::string hdrs = "HTTP/"; @@ -759,6 +773,24 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) http2::build_http1_headers_from_norm_headers (hdrs, downstream->get_response_headers()); + if(downstream->get_non_final_response()) { + hdrs += "\r\n"; + + if(LOG_ENABLED(INFO)) { + log_response_headers(hdrs); + } + + auto output = bufferevent_get_output(handler_->get_bev()); + if(evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) { + ULOG(FATAL, this) << "evbuffer_add() failed"; + return -1; + } + + downstream->clear_response_headers(); + + return 0; + } + // We check downstream->get_response_connection_close() in case when // the Content-Length is not available. if(!downstream->get_request_connection_close() && @@ -818,17 +850,11 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) } hdrs += "\r\n"; + if(LOG_ENABLED(INFO)) { - const char *hdrp; - std::string nhdrs; - if(worker_config.errorlog_tty) { - nhdrs = http::colorizeHeaders(hdrs.c_str()); - hdrp = nhdrs.c_str(); - } else { - hdrp = hdrs.c_str(); - } - ULOG(INFO, this) << "HTTP response headers\n" << hdrp; + log_response_headers(hdrs); } + auto output = bufferevent_get_output(handler_->get_bev()); if(evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) { ULOG(FATAL, this) << "evbuffer_add() failed"; @@ -911,4 +937,17 @@ int HttpsUpstream::on_downstream_abort_request(Downstream *downstream, return error_reply(status_code); } +void HttpsUpstream::log_response_headers(const std::string& hdrs) const +{ + const char *hdrp; + std::string nhdrs; + if(worker_config.errorlog_tty) { + nhdrs = http::colorizeHeaders(hdrs.c_str()); + hdrp = nhdrs.c_str(); + } else { + hdrp = hdrs.c_str(); + } + ULOG(INFO, this) << "HTTP response headers\n" << hdrp; +} + } // namespace shrpx diff --git a/src/shrpx_https_upstream.h b/src/shrpx_https_upstream.h index 466334ef..53013831 100644 --- a/src/shrpx_https_upstream.h +++ b/src/shrpx_https_upstream.h @@ -65,6 +65,7 @@ public: virtual int on_downstream_body_complete(Downstream *downstream); void reset_current_header_length(); + void log_response_headers(const std::string& hdrs) const; private: ClientHandler *handler_; http_parser htp_; diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc index 4032004f..84302a05 100644 --- a/src/shrpx_spdy_upstream.cc +++ b/src/shrpx_spdy_upstream.cc @@ -886,6 +886,15 @@ spdylay_session* SpdyUpstream::get_http2_session() // spdylay_session_recv. These calls may delete downstream. int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) { + if(downstream->get_non_final_response()) { + // SPDY does not support non-final response. We could send it + // with HEADERS and final response in SYN_REPLY, but it is not + // official way. + downstream->clear_response_headers(); + + return 0; + } + if(LOG_ENABLED(INFO)) { DLOG(INFO, downstream) << "HTTP response header completed"; } diff --git a/tests/main.c b/tests/main.c index a44affec..6db64cbd 100644 --- a/tests/main.c +++ b/tests/main.c @@ -95,6 +95,8 @@ int main(int argc, char* argv[]) test_nghttp2_session_recv_unexpected_continuation) || !CU_add_test(pSuite, "session_recv_settings_header_table_size", test_nghttp2_session_recv_settings_header_table_size) || + !CU_add_test(pSuite, "session_recv_too_large_frame_length", + test_nghttp2_session_recv_too_large_frame_length) || !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) || !CU_add_test(pSuite, "session_add_frame", test_nghttp2_session_add_frame) || @@ -255,10 +257,6 @@ int main(int argc, char* argv[]) !CU_add_test(pSuite, "hd_deflate", test_nghttp2_hd_deflate) || !CU_add_test(pSuite, "hd_deflate_same_indexed_repr", test_nghttp2_hd_deflate_same_indexed_repr) || - !CU_add_test(pSuite, "hd_deflate_common_header_eviction", - test_nghttp2_hd_deflate_common_header_eviction) || - !CU_add_test(pSuite, "hd_deflate_clear_refset", - test_nghttp2_hd_deflate_clear_refset) || !CU_add_test(pSuite, "hd_inflate_indexed", test_nghttp2_hd_inflate_indexed) || !CU_add_test(pSuite, "hd_inflate_indname_noinc", diff --git a/tests/nghttp2_frame_test.c b/tests/nghttp2_frame_test.c index 0e96dbed..beb7baa7 100644 --- a/tests/nghttp2_frame_test.c +++ b/tests/nghttp2_frame_test.c @@ -61,7 +61,7 @@ static nghttp2_nv* headers(void) return nva; } -static void check_frame_header(uint16_t length, uint8_t type, uint8_t flags, +static void check_frame_header(size_t length, uint8_t type, uint8_t flags, int32_t stream_id, nghttp2_frame_hd *hd) { CU_ASSERT(length == hd->length); @@ -223,7 +223,7 @@ void test_nghttp2_frame_pack_priority(void) rv = nghttp2_frame_pack_priority(&bufs, &frame); CU_ASSERT(0 == rv); - CU_ASSERT(13 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 5 == nghttp2_bufs_len(&bufs)); CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs)); check_frame_header(5, NGHTTP2_PRIORITY, NGHTTP2_FLAG_NONE, 1000000007, &oframe.hd); @@ -251,7 +251,7 @@ void test_nghttp2_frame_pack_rst_stream(void) rv = nghttp2_frame_pack_rst_stream(&bufs, &frame); CU_ASSERT(0 == rv); - CU_ASSERT(12 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4 == nghttp2_bufs_len(&bufs)); CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs)); check_frame_header(4, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE, 1000000007, &oframe.hd); @@ -384,7 +384,7 @@ void test_nghttp2_frame_pack_ping(void) rv = nghttp2_frame_pack_ping(&bufs, &frame); CU_ASSERT(0 == rv); - CU_ASSERT(16 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 8 == nghttp2_bufs_len(&bufs)); CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs)); check_frame_header(8, NGHTTP2_PING, NGHTTP2_FLAG_ACK, 0, &oframe.hd); CU_ASSERT(memcmp(opaque_data, oframe.opaque_data, sizeof(opaque_data) - 1) @@ -411,7 +411,8 @@ void test_nghttp2_frame_pack_goaway() rv = nghttp2_frame_pack_goaway(&bufs, &frame); CU_ASSERT(0 == rv); - CU_ASSERT((ssize_t)(16 + opaque_data_len) == nghttp2_bufs_len(&bufs)); + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 8 + opaque_data_len) == + nghttp2_bufs_len(&bufs)); CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs)); check_frame_header(24, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, &oframe.hd); CU_ASSERT(1000000007 == oframe.last_stream_id); @@ -453,7 +454,7 @@ void test_nghttp2_frame_pack_window_update(void) rv = nghttp2_frame_pack_window_update(&bufs, &frame); CU_ASSERT(0 == rv); - CU_ASSERT(12 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4 == nghttp2_bufs_len(&bufs)); CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs)); check_frame_header(4, NGHTTP2_WINDOW_UPDATE, NGHTTP2_FLAG_NONE, 1000000007, &oframe.hd); @@ -541,7 +542,7 @@ void test_nghttp2_frame_pack_altsvc(void) /* Check no origin case */ payloadlen = NGHTTP2_ALTSVC_MINLEN + protocol_id_len + host_len; - nghttp2_put_uint16be(buf->pos, payloadlen); + nghttp2_put_uint32be(buf->pos, (uint32_t)((payloadlen << 8) + buf->pos[3])); oframe.payload = &oaltsvc; @@ -562,7 +563,7 @@ void test_nghttp2_frame_pack_altsvc(void) /* Check insufficient payload length for host */ payloadlen = NGHTTP2_ALTSVC_MINLEN + protocol_id_len + host_len - 1; - nghttp2_put_uint16be(buf->pos, payloadlen); + nghttp2_put_uint32be(buf->pos, (uint32_t)((payloadlen << 8) + buf->pos[3])); oframe.payload = &oaltsvc; @@ -579,7 +580,7 @@ void test_nghttp2_frame_pack_altsvc(void) /* Check no host case */ payloadlen = NGHTTP2_ALTSVC_MINLEN + protocol_id_len; - nghttp2_put_uint16be(buf->pos, payloadlen); + nghttp2_put_uint32be(buf->pos, (uint32_t)((payloadlen << 8) + buf->pos[3])); buf->pos[NGHTTP2_FRAME_HDLEN + NGHTTP2_ALTSVC_FIXED_PARTLEN + protocol_id_len] = 0; @@ -602,7 +603,7 @@ void test_nghttp2_frame_pack_altsvc(void) /* Check missing Host-Len */ payloadlen = NGHTTP2_ALTSVC_FIXED_PARTLEN + protocol_id_len; - nghttp2_put_uint16be(buf->pos, payloadlen); + nghttp2_put_uint32be(buf->pos, (uint32_t)((payloadlen << 8) + buf->pos[3])); oframe.payload = &oaltsvc; @@ -699,13 +700,30 @@ void test_nghttp2_iv_check(void) iv[1].value = 3; CU_ASSERT(!nghttp2_iv_check(iv, 2)); - /* Undefined SETTINGS ID */ + /* Undefined SETTINGS ID is allowed */ iv[1].settings_id = 1000000009; iv[1].value = 0; - CU_ASSERT(!nghttp2_iv_check(iv, 2)); + CU_ASSERT(nghttp2_iv_check(iv, 2)); /* Too large SETTINGS_HEADER_TABLE_SIZE */ iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; iv[1].value = UINT32_MAX; CU_ASSERT(!nghttp2_iv_check(iv, 2)); + + /* Too small SETTINGS_MAX_FRAME_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MIN - 1; + CU_ASSERT(!nghttp2_iv_check(iv, 1)); + + /* Too large SETTINGS_MAX_FRAME_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MAX + 1; + CU_ASSERT(!nghttp2_iv_check(iv, 1)); + + /* Max and min SETTINGS_MAX_FRAME_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MIN; + iv[1].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[1].value = NGHTTP2_MAX_FRAME_SIZE_MAX; + CU_ASSERT(nghttp2_iv_check(iv, 2)); } diff --git a/tests/nghttp2_hd_test.c b/tests/nghttp2_hd_test.c index 0ceb5bb6..c27783fd 100644 --- a/tests/nghttp2_hd_test.c +++ b/tests/nghttp2_hd_test.c @@ -159,8 +159,7 @@ void test_nghttp2_hd_deflate_same_indexed_repr(void) CU_ASSERT(0 == nghttp2_hd_deflate_init(&deflater)); CU_ASSERT(0 == nghttp2_hd_inflate_init(&inflater)); - /* Encode 2 same headers. cookie:alpha is not in the reference set, - so first emit literal repr and then 2 emits of indexed repr. */ + /* Encode 2 same headers. Emit 1 literal reprs and 1 index repr. */ rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva1, ARRLEN(nva1)); blocklen = nghttp2_bufs_len(&bufs); @@ -174,13 +173,12 @@ void test_nghttp2_hd_deflate_same_indexed_repr(void) nva_out_reset(&out); nghttp2_bufs_reset(&bufs); - /* Encode 3 same headers. This time, cookie:alpha is in the - reference set, so the encoder emits indexed repr 6 times */ + /* Encode 3 same headers. This time, emits 3 index reprs. */ rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, ARRLEN(nva2)); blocklen = nghttp2_bufs_len(&bufs); CU_ASSERT(0 == rv); - CU_ASSERT(blocklen == 6); + CU_ASSERT(blocklen == 3); CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0)); CU_ASSERT(3 == out.nvlen); @@ -195,120 +193,6 @@ void test_nghttp2_hd_deflate_same_indexed_repr(void) nghttp2_hd_deflate_free(&deflater); } -void test_nghttp2_hd_deflate_common_header_eviction(void) -{ - nghttp2_hd_deflater deflater; - nghttp2_hd_inflater inflater; - nghttp2_nv nva[] = {MAKE_NV("h1", ""), - MAKE_NV("h2", "")}; - nghttp2_bufs bufs; - ssize_t blocklen; - /* Default header table capacity is 4096. Adding 2 byte header name - and 4060 byte value, which is 4094 bytes including overhead, to - the table evicts first entry. */ - uint8_t value[3038]; - nva_out out; - size_t i; - int rv; - - frame_pack_bufs_init(&bufs); - - nva_out_init(&out); - memset(value, '0', sizeof(value)); - for(i = 0; i < 2; ++i) { - nva[i].value = value; - nva[i].valuelen = sizeof(value); - } - - nghttp2_hd_deflate_init(&deflater); - nghttp2_hd_inflate_init(&inflater); - - /* First emit "h1: ..." to put it in the reference set (index - = 0). */ - rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 1); - blocklen = nghttp2_bufs_len(&bufs); - - CU_ASSERT(0 == rv); - CU_ASSERT(blocklen > 0); - CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0)); - - CU_ASSERT(1 == out.nvlen); - nghttp2_nv_array_sort(nva, 1); - assert_nv_equal(nva, out.nva, 1); - - nva_out_reset(&out); - nghttp2_bufs_reset(&bufs); - - /* Encode with second header */ - - rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); - blocklen = nghttp2_bufs_len(&bufs); - - CU_ASSERT(0 == rv); - CU_ASSERT(blocklen > 0); - - /* Check common header "h1: ...:, which is removed from the - header table because of eviction, is still emitted by the - inflater */ - CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0)); - - CU_ASSERT(2 == out.nvlen); - nghttp2_nv_array_sort(nva, 2); - assert_nv_equal(nva, out.nva, 2); - - nva_out_reset(&out); - nghttp2_bufs_reset(&bufs); - - CU_ASSERT(1 == deflater.ctx.hd_table.len); - CU_ASSERT(1 == inflater.ctx.hd_table.len); - - nghttp2_bufs_free(&bufs); - nghttp2_hd_inflate_free(&inflater); - nghttp2_hd_deflate_free(&deflater); -} - -void test_nghttp2_hd_deflate_clear_refset(void) -{ - nghttp2_hd_deflater deflater; - nghttp2_hd_inflater inflater; - nghttp2_bufs bufs; - ssize_t blocklen; - nghttp2_nv nv[] = { - MAKE_NV(":path", "/"), - MAKE_NV(":scheme", "http") - }; - size_t i; - nva_out out; - int rv; - - frame_pack_bufs_init(&bufs); - - nva_out_init(&out); - nghttp2_hd_deflate_init2(&deflater, - NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE); - nghttp2_hd_deflate_set_no_refset(&deflater, 1); - nghttp2_hd_inflate_init(&inflater); - - for(i = 0; i < 2; ++i) { - rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nv, ARRLEN(nv)); - blocklen = nghttp2_bufs_len(&bufs); - - CU_ASSERT(0 == rv); - CU_ASSERT(blocklen > 1); - CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0)); - - CU_ASSERT(ARRLEN(nv) == out.nvlen); - assert_nv_equal(nv, out.nva, ARRLEN(nv)); - - nva_out_reset(&out); - nghttp2_bufs_reset(&bufs); - } - - nghttp2_bufs_free(&bufs); - nghttp2_hd_inflate_free(&inflater); - nghttp2_hd_deflate_free(&deflater); -} - void test_nghttp2_hd_inflate_indexed(void) { nghttp2_hd_inflater inflater; @@ -410,9 +294,11 @@ void test_nghttp2_hd_inflate_indname_inc(void) CU_ASSERT(1 == out.nvlen); assert_nv_equal(&nv, out.nva, 1); CU_ASSERT(1 == inflater.ctx.hd_table.len); - assert_nv_equal(&nv, - &GET_TABLE_ENT(&inflater.ctx, - inflater.ctx.hd_table.len-1)->nv, 1); + assert_nv_equal + (&nv, + &GET_TABLE_ENT + (&inflater.ctx, + NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len-1)->nv, 1); nva_out_reset(&out); nghttp2_bufs_free(&bufs); @@ -459,7 +345,6 @@ void test_nghttp2_hd_inflate_indname_inc_eviction(void) nghttp2_bufs_reset(&bufs); CU_ASSERT(3 == inflater.ctx.hd_table.len); - CU_ASSERT(GET_TABLE_ENT(&inflater.ctx, 0)->flags & NGHTTP2_HD_FLAG_REFSET); nghttp2_bufs_free(&bufs); nghttp2_hd_inflate_free(&inflater); @@ -530,9 +415,11 @@ void test_nghttp2_hd_inflate_newname_inc(void) CU_ASSERT(1 == out.nvlen); assert_nv_equal(&nv, out.nva, 1); CU_ASSERT(1 == inflater.ctx.hd_table.len); - assert_nv_equal(&nv, - &GET_TABLE_ENT(&inflater.ctx, - inflater.ctx.hd_table.len-1)->nv, 1); + assert_nv_equal + (&nv, + &GET_TABLE_ENT + (&inflater.ctx, + NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len-1)->nv, 1); nva_out_reset(&out); nghttp2_bufs_free(&bufs); @@ -696,8 +583,9 @@ void test_nghttp2_hd_change_table_size(void) { nghttp2_hd_deflater deflater; nghttp2_hd_inflater inflater; - nghttp2_nv nva[] = { MAKE_NV(":method", "GET"), - MAKE_NV(":path", "/") }; + nghttp2_nv nva[] = { MAKE_NV("alpha", "bravo"), + MAKE_NV("charlie", "delta") }; + nghttp2_nv nva2[] = { MAKE_NV(":path", "/") }; nghttp2_bufs bufs; ssize_t rv; nva_out out; @@ -909,6 +797,36 @@ void test_nghttp2_hd_change_table_size(void) nghttp2_hd_inflate_free(&inflater); nghttp2_hd_deflate_free(&deflater); + /* Check that context update emitted twice */ + nghttp2_hd_deflate_init2(&deflater, 4096); + nghttp2_hd_inflate_init(&inflater); + + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 0)); + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 3000)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 0)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 3000)); + + CU_ASSERT(0 == deflater.min_hd_table_bufsize_max); + CU_ASSERT(3000 == deflater.ctx.hd_table_bufsize_max); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, 1); + blocklen = nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(3 < blocklen); + CU_ASSERT(3000 == deflater.ctx.hd_table_bufsize_max); + CU_ASSERT(UINT32_MAX == deflater.min_hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0)); + CU_ASSERT(3000 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(3000 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out); + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + nghttp2_bufs_free(&bufs); } @@ -1163,7 +1081,7 @@ void test_nghttp2_hd_deflate_bound(void) bound = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva)); - CU_ASSERT(1 + 6 + 6 * 2 * 2 + + CU_ASSERT(12 + 6 * 2 * 2 + nva[0].namelen + nva[0].valuelen + nva[1].namelen + nva[1].valuelen == bound); @@ -1174,7 +1092,7 @@ void test_nghttp2_hd_deflate_bound(void) bound2 = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva)); - CU_ASSERT(bound + 2 == bound2); + CU_ASSERT(bound == bound2); nghttp2_bufs_free(&bufs); nghttp2_hd_deflate_free(&deflater); diff --git a/tests/nghttp2_hd_test.h b/tests/nghttp2_hd_test.h index 21dc5294..b726c95d 100644 --- a/tests/nghttp2_hd_test.h +++ b/tests/nghttp2_hd_test.h @@ -27,8 +27,6 @@ void test_nghttp2_hd_deflate(void); void test_nghttp2_hd_deflate_same_indexed_repr(void); -void test_nghttp2_hd_deflate_common_header_eviction(void); -void test_nghttp2_hd_deflate_clear_refset(void); void test_nghttp2_hd_inflate_indexed(void); void test_nghttp2_hd_inflate_indname_noinc(void); void test_nghttp2_hd_inflate_indname_inc(void); diff --git a/tests/nghttp2_npn_test.c b/tests/nghttp2_npn_test.c index fe42ba0a..56bbe219 100644 --- a/tests/nghttp2_npn_test.c +++ b/tests/nghttp2_npn_test.c @@ -33,7 +33,7 @@ static void http2(void) { const unsigned char p[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '1', - 5, 'h', '2', '-', '1', '3', + 5, 'h', '2', '-', '1', '4', 6, 's', 'p', 'd', 'y', '/', '3' }; unsigned char outlen; diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 88df64bf..567cd2ee 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -450,7 +450,9 @@ void test_nghttp2_session_recv(void) assert(nghttp2_buf_len(&bufs.cur->buf) >= 16); bufs.cur->buf.last += 16; - nghttp2_put_uint16be(bufs.cur->buf.pos, frame.hd.length + 16); + nghttp2_put_uint32be(bufs.cur->buf.pos, + (uint32_t)(((frame.hd.length + 16) << 8) + + bufs.cur->buf.pos[3])); nghttp2_frame_ping_free(&frame.ping); @@ -610,8 +612,8 @@ void test_nghttp2_session_recv_data(void) error. This is not mandated by the spec */ ud.data_chunk_recv_cb_called = 0; ud.frame_recv_cb_called = 0; - rv = nghttp2_session_mem_recv(session, data, 8+4096); - CU_ASSERT(8+4096 == rv); + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); CU_ASSERT(0 == ud.data_chunk_recv_cb_called); CU_ASSERT(0 == ud.frame_recv_cb_called); @@ -633,8 +635,8 @@ void test_nghttp2_session_recv_data(void) ud.data_chunk_recv_cb_called = 0; ud.frame_recv_cb_called = 0; - rv = nghttp2_session_mem_recv(session, data, 8+4096); - CU_ASSERT(8+4096 == rv); + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); CU_ASSERT(0 == ud.data_chunk_recv_cb_called); CU_ASSERT(0 == ud.frame_recv_cb_called); @@ -646,8 +648,8 @@ void test_nghttp2_session_recv_data(void) ud.data_chunk_recv_cb_called = 0; ud.frame_recv_cb_called = 0; - rv = nghttp2_session_mem_recv(session, data, 8+4096); - CU_ASSERT(8+4096 == rv); + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); CU_ASSERT(1 == ud.data_chunk_recv_cb_called); CU_ASSERT(1 == ud.frame_recv_cb_called); @@ -656,8 +658,8 @@ void test_nghttp2_session_recv_data(void) ud.data_chunk_recv_cb_called = 0; ud.frame_recv_cb_called = 0; - rv = nghttp2_session_mem_recv(session, data, 8+4096); - CU_ASSERT(8+4096 == rv); + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); /* Now we got data more than initial-window-size / 2, WINDOW_UPDATE must be queued */ @@ -676,8 +678,8 @@ void test_nghttp2_session_recv_data(void) DATA. Additional 4 DATA frames, connection flow control will kick in. */ for(i = 0; i < 5; ++i) { - rv = nghttp2_session_mem_recv(session, data, 8+4096); - CU_ASSERT(8+4096 == rv); + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); } item = nghttp2_session_get_next_ob_item(session); CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item)); @@ -693,8 +695,8 @@ void test_nghttp2_session_recv_data(void) ud.data_chunk_recv_cb_called = 0; ud.frame_recv_cb_called = 0; - rv = nghttp2_session_mem_recv(session, data, 8+4096); - CU_ASSERT(8+4096 == rv); + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); CU_ASSERT(0 == ud.data_chunk_recv_cb_called); CU_ASSERT(0 == ud.frame_recv_cb_called); @@ -753,11 +755,11 @@ void test_nghttp2_session_recv_continuation(void) nghttp2_frame_headers_free(&frame.headers); /* HEADERS's payload is 1 byte */ - memcpy(data, buf->pos, 9); - datalen = 9; - buf->pos += 9; + memcpy(data, buf->pos, NGHTTP2_FRAME_HDLEN + 1); + datalen = NGHTTP2_FRAME_HDLEN + 1; + buf->pos += NGHTTP2_FRAME_HDLEN + 1; - nghttp2_put_uint16be(data, 1); + nghttp2_put_uint32be(data, (1 << 8) + data[3]); /* First CONTINUATION, 2 bytes */ cont_hd.length = 2; @@ -925,14 +927,14 @@ void test_nghttp2_session_recv_headers_with_priority(void) rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); CU_ASSERT(0 == rv); - CU_ASSERT(13 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 5 + 2 == nghttp2_bufs_len(&bufs)); nghttp2_frame_headers_free(&frame.headers); buf = &bufs.head->buf; /* Make payload shorter than required length to store priroty group */ - nghttp2_put_uint16be(buf->pos, 4); + nghttp2_put_uint32be(buf->pos, (4 << 8) + buf->pos[3]); ud.frame_recv_cb_called = 0; @@ -1043,7 +1045,8 @@ void test_nghttp2_session_recv_premature_headers(void) assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); /* Intentionally feed payload cutting last 1 byte off */ - nghttp2_put_uint16be(buf->pos, frame.hd.length - 1); + nghttp2_put_uint32be(buf->pos, + (uint32_t)(((frame.hd.length - 1) << 8) + buf->pos[3])); rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf) - 1); CU_ASSERT((ssize_t)(nghttp2_buf_len(buf) - 1) == rv); @@ -1138,7 +1141,7 @@ void test_nghttp2_session_recv_altsvc(void) CU_ASSERT(NGHTTP2_EXT_ALTSVC == ud.recv_frame_type); /* premature payload */ - nghttp2_put_uint16be(buf->pos, 8); + nghttp2_put_uint32be(buf->pos, (8 << 8) + buf->pos[3]); ud.frame_recv_cb_called = 0; @@ -1407,6 +1410,36 @@ void test_nghttp2_session_recv_settings_header_table_size(void) nghttp2_session_del(session); } +void test_nghttp2_session_recv_too_large_frame_length(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t buf[NGHTTP2_FRAME_HDLEN]; + nghttp2_outbound_item *item; + nghttp2_frame_hd hd = { + /* Initial max frame size is NGHTTP2_MAX_FRAME_SIZE_MIN */ + NGHTTP2_MAX_FRAME_SIZE_MIN + 1, + 1, + NGHTTP2_HEADERS, + NGHTTP2_FLAG_NONE + }; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_frame_pack_frame_hd(buf, &hd); + + CU_ASSERT(sizeof(buf) == + nghttp2_session_mem_recv(session, buf, sizeof(buf))); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); + + nghttp2_session_del(session); +} + void test_nghttp2_session_continue(void) { nghttp2_session *session; @@ -1642,11 +1675,11 @@ void test_nghttp2_session_add_frame(void) aux_data)); CU_ASSERT(0 == nghttp2_pq_empty(&session->ob_ss_pq)); CU_ASSERT(0 == nghttp2_session_send(session)); - CU_ASSERT(NGHTTP2_HEADERS == acc.buf[2]); + CU_ASSERT(NGHTTP2_HEADERS == acc.buf[3]); CU_ASSERT((NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY) == - acc.buf[3]); + acc.buf[4]); /* check stream id */ - CU_ASSERT(1 == nghttp2_get_uint32(&acc.buf[4])); + CU_ASSERT(1 == nghttp2_get_uint32(&acc.buf[5])); nghttp2_session_del(session); } @@ -2159,6 +2192,25 @@ void test_nghttp2_session_on_settings_received(void) nghttp2_frame_settings_free(&frame.settings); nghttp2_session_del(session); + + /* Check too large SETTINGS_MAX_FRAME_SIZE */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MAX + 1; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, + dup_iv(iv, 1), 1); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); + + nghttp2_frame_settings_free(&frame.settings); + nghttp2_session_del(session); } void test_nghttp2_session_on_push_promise_received(void) @@ -2937,8 +2989,7 @@ void test_nghttp2_submit_data(void) &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL); CU_ASSERT(0 == nghttp2_submit_data(session, - NGHTTP2_FLAG_END_STREAM | - NGHTTP2_FLAG_END_SEGMENT, 1, &data_prd)); + NGHTTP2_FLAG_END_STREAM, 1, &data_prd)); ud.block_count = 0; CU_ASSERT(0 == nghttp2_session_send(session)); @@ -2949,8 +3000,7 @@ void test_nghttp2_submit_data(void) CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); /* frame->hd.flags has these flags */ - CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT) == - data_frame->hd.flags); + CU_ASSERT(NGHTTP2_FLAG_END_STREAM == data_frame->hd.flags); nghttp2_session_del(session); } @@ -3433,8 +3483,9 @@ void test_nghttp2_submit_settings(void) my_user_data ud; nghttp2_outbound_item *item; nghttp2_frame *frame; - nghttp2_settings_entry iv[6]; + nghttp2_settings_entry iv[7]; nghttp2_frame ack_frame; + const int32_t UNKNOWN_ID = 1000000007; iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; iv[0].value = 5; @@ -3448,8 +3499,11 @@ void test_nghttp2_submit_settings(void) iv[3].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; iv[3].value = 0; - iv[4].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; - iv[4].value = (uint32_t)NGHTTP2_MAX_WINDOW_SIZE + 1; + iv[4].settings_id = UNKNOWN_ID; + iv[4].value = 999; + + iv[5].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[5].value = (uint32_t)NGHTTP2_MAX_WINDOW_SIZE + 1; memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); callbacks.send_callback = null_send_callback; @@ -3457,7 +3511,7 @@ void test_nghttp2_submit_settings(void) nghttp2_session_server_new(&session, &callbacks, &ud); CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == - nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 5)); + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 6)); /* Make sure that local settings are not changed */ CU_ASSERT(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS == @@ -3465,15 +3519,15 @@ void test_nghttp2_submit_settings(void) CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == session->local_settings.initial_window_size); - /* Now sends without 5th one */ - CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 4)); + /* Now sends without 6th one */ + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 5)); item = nghttp2_session_get_next_ob_item(session); CU_ASSERT(NGHTTP2_SETTINGS == OB_CTRL_TYPE(item)); frame = item->frame; - CU_ASSERT(4 == frame->settings.niv); + CU_ASSERT(5 == frame->settings.niv); CU_ASSERT(5 == frame->settings.iv[0].value); CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS == frame->settings.iv[0].settings_id); @@ -3482,6 +3536,9 @@ void test_nghttp2_submit_settings(void) CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE == frame->settings.iv[1].settings_id); + CU_ASSERT(UNKNOWN_ID == frame->settings.iv[4].settings_id); + CU_ASSERT(999 == frame->settings.iv[4].value); + ud.frame_send_cb_called = 0; CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(1 == ud.frame_send_cb_called); @@ -4759,25 +4816,13 @@ void test_nghttp2_session_set_option(void) nghttp2_option_new(&option); - nghttp2_option_set_no_auto_stream_window_update(option, 1); + nghttp2_option_set_no_auto_window_update(option, 1); memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); nghttp2_session_client_new2(&session, &callbacks, NULL, option); - CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE); - CU_ASSERT(!(session->opt_flags & - NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE)); - nghttp2_session_del(session); + CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE); - nghttp2_option_set_no_auto_stream_window_update(option, 0); - nghttp2_option_set_no_auto_connection_window_update(option, 1); - - nghttp2_session_server_new2(&session, &callbacks, NULL, option); - - CU_ASSERT(!(session->opt_flags & - NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE)); - CU_ASSERT(session->opt_flags & - NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE); nghttp2_session_del(session); nghttp2_option_set_peer_max_concurrent_streams(option, 100); diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index 57fb9695..0cdd7394 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -37,6 +37,7 @@ void test_nghttp2_session_recv_altsvc(void); void test_nghttp2_session_recv_unknown_frame(void); void test_nghttp2_session_recv_unexpected_continuation(void); void test_nghttp2_session_recv_settings_header_table_size(void); +void test_nghttp2_session_recv_too_large_frame_length(void); void test_nghttp2_session_continue(void); void test_nghttp2_session_add_frame(void); void test_nghttp2_session_on_request_headers_received(void);