Merge branch 'h2-14'

This commit is contained in:
Tatsuhiro Tsujikawa 2014-07-31 23:39:04 +09:00
commit a5ec5c1a1d
48 changed files with 1323 additions and 1227 deletions

View File

@ -17,10 +17,12 @@ everything yet.
Development Status Development Status
------------------ ------------------
We started to implement h2-13 We started to implement h2-14
(http://tools.ietf.org/html/draft-ietf-httpbis-http2-13) and the (http://tools.ietf.org/html/draft-ietf-httpbis-http2-14), the header
header compression compression
(http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08). (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. The nghttp2 code base was forked from spdylay project.
@ -30,15 +32,9 @@ HTTP/2 Features Support
Core frames handling Yes Core frames handling Yes
Dependency Tree Yes Dependency Tree Yes
Large header (CONTINUATION) 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 Public Test Server
------------------ ------------------
@ -47,7 +43,7 @@ implementation.
* https://nghttp2.org/ (TLS + NPN) * 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. ALPN is currently disabled.
This endpoint requires TLSv1.2 and DHE or EDCHE with GCM cipher 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 $ src/nghttp -nv https://nghttp2.org
[ 0.033][NPN] server offers: [ 0.033][NPN] server offers:
* h2-13 * h2-14
* spdy/3.1 * spdy/3.1
* http/1.1 * http/1.1
The negotiated protocol: h2-13 The negotiated protocol: h2-14
[ 0.068] send SETTINGS frame <length=15, flags=0x00, stream_id=0> [ 0.068] send SETTINGS frame <length=15, flags=0x00, stream_id=0>
(niv=3) (niv=3)
[SETTINGS_MAX_CONCURRENT_STREAMS(3):100] [SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
@ -281,7 +277,7 @@ The HTTP Upgrade is performed like this::
[SETTINGS_MAX_CONCURRENT_STREAMS(3):100] [SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
[SETTINGS_INITIAL_WINDOW_SIZE(4):65535] [SETTINGS_INITIAL_WINDOW_SIZE(4):65535]
[ 0.024] recv ALTSVC frame <length=43, flags=0x00, stream_id=0> [ 0.024] recv ALTSVC frame <length=43, flags=0x00, stream_id=0>
(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 <length=0, flags=0x01, stream_id=0> [ 0.024] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK ; ACK
(niv=0) (niv=0)
@ -389,7 +385,7 @@ information. Here is sample output from ``nghttpd`` server::
nghttpx - proxy 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: 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 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. be deployed SSL/TLS terminator for existing web server.
The default mode, ``--http2-proxy`` and ``--http2-bridge`` modes use The default mode, ``--http2-proxy`` and ``--http2-bridge`` modes use

View File

@ -46,5 +46,6 @@ Released Versions
Resources Resources
--------- ---------
* http://tools.ietf.org/html/draft-ietf-httpbis-http2-13 * http://tools.ietf.org/html/draft-ietf-httpbis-http2-14
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09
* http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-02

View File

@ -41,7 +41,7 @@ extern "C" {
* The protocol version identification string of this library * The protocol version identification string of this library
* supports. This identifier is used if HTTP/2 is used over TLS. * 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 * @macro
* *
@ -56,7 +56,7 @@ extern "C" {
* supports. This identifier is used if HTTP/2 is used over cleartext * supports. This identifier is used if HTTP/2 is used over cleartext
* TCP. * TCP.
*/ */
#define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "h2c-13" #define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "h2c-14"
/** /**
* @macro * @macro
@ -476,10 +476,6 @@ typedef enum {
* The ACK flag. * The ACK flag.
*/ */
NGHTTP2_FLAG_ACK = 0x01, NGHTTP2_FLAG_ACK = 0x01,
/**
* The END_SEGMENT flag.
*/
NGHTTP2_FLAG_END_SEGMENT = 0x02,
/** /**
* The PADDED flag. * The PADDED flag.
*/ */
@ -510,7 +506,15 @@ typedef enum {
/** /**
* SETTINGS_INITIAL_WINDOW_SIZE * 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; } nghttp2_settings_id;
/* Note: If we add SETTINGS, update the capacity of /* Note: If we add SETTINGS, update the capacity of
NGHTTP2_INBOUND_NUM_IV as well */ NGHTTP2_INBOUND_NUM_IV as well */
@ -724,7 +728,9 @@ typedef enum {
NGHTTP2_HCAT_PUSH_RESPONSE = 2, NGHTTP2_HCAT_PUSH_RESPONSE = 2,
/** /**
* The HEADERS frame which does not apply for the above categories, * 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_HCAT_HEADERS = 3
} nghttp2_headers_category; } nghttp2_headers_category;
@ -1505,31 +1511,17 @@ int nghttp2_option_new(nghttp2_option **option_ptr);
*/ */
void nghttp2_option_del(nghttp2_option *option); 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 * @function
* *
* This option prevents the library from sending WINDOW_UPDATE for a * This option prevents the library from sending WINDOW_UPDATE for a
* connection automatically. If this option is set to nonzero, the * connection automatically. If this option is set to nonzero, the
* library won't send WINDOW_UPDATE for a connection and the * library won't send WINDOW_UPDATE for DATA until application calls
* application is responsible for sending WINDOW_UPDATE with stream ID * `nghttp2_session_consume()` to indicate the consumed amount of
* 0 using `nghttp2_submit_window_update`. By default, this option is * data. Don't use `nghttp2_submit_window_update()` for this purpose.
* set to zero. * By default, this option is set to zero.
*/ */
void nghttp2_option_set_no_auto_connection_window_update void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val);
(nghttp2_option *option, int val);
/** /**
* @function * @function
@ -2067,6 +2059,28 @@ int nghttp2_session_terminate_session2(nghttp2_session *session,
uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session, uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session,
nghttp2_settings_id id); 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 * @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 * Submits one or more DATA frames to the stream |stream_id|. The
* data to be sent are provided by |data_prd|. If |flags| contains * data to be sent are provided by |data_prd|. If |flags| contains
* :enum:`NGHTTP2_FLAG_END_STREAM`, the last DATA frame has END_STREAM * :enum:`NGHTTP2_FLAG_END_STREAM`, the last DATA frame has END_STREAM
* flag set. If |flags| contains :enum:`NGHTTP2_FLAG_END_SEGMENT`, * flag set.
* the last DATA frame has END_SEGMENT flag set.
* *
* This function does not take ownership of the |data_prd|. The * This function does not take ownership of the |data_prd|. The
* function copies the members of the |data_prd|. * function copies the members of the |data_prd|.
@ -2609,12 +2622,11 @@ int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags,
* difference. * difference.
* *
* If the |window_size_increment| is negative, the local window size * If the |window_size_increment| is negative, the local window size
* is decreased by -|window_size_increment|. If * is decreased by -|window_size_increment|. If automatic
* :enum:`NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE` (or * WINDOW_UPDATE is enabled
* :enum:`NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE` if |stream_id| * (`nghttp2_option_set_no_auto_window_update()`), and the library
* is 0) is not set and the library decided that the WINDOW_UPDATE * decided that the WINDOW_UPDATE should be submitted, then
* should be submitted, then WINDOW_UPDATE is queued with the current * WINDOW_UPDATE is queued with the current received bytes count.
* received bytes count.
* *
* If the |window_size_increment| is 0, the function does nothing and * If the |window_size_increment| is 0, the function does nothing and
* returns 0. * 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 * Only the server can send the ALTSVC frame. If |session| is
* initialized as client, this function fails and returns * 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``. * 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); 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 * @function
* *

View File

@ -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) void nghttp2_frame_pack_frame_hd(uint8_t* buf, const nghttp2_frame_hd *hd)
{ {
nghttp2_put_uint16be(&buf[0], hd->length); nghttp2_put_uint32be(&buf[0], (uint32_t)(hd->length << 8));
buf[2]= hd->type; buf[3]= hd->type;
buf[3] = hd->flags; buf[4] = hd->flags;
nghttp2_put_uint32be(&buf[4], hd->stream_id); nghttp2_put_uint32be(&buf[5], hd->stream_id);
} }
void nghttp2_frame_unpack_frame_hd(nghttp2_frame_hd *hd, const uint8_t* buf) 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->length = nghttp2_get_uint32(&buf[0]) >> 8;
hd->type = buf[2]; hd->type = buf[3];
hd->flags = buf[3]; hd->flags = buf[4];
hd->stream_id = nghttp2_get_uint32(&buf[4]) & NGHTTP2_STREAM_ID_MASK; 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, uint8_t type, uint8_t flags,
int32_t stream_id) 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->hd = pdata->hd;
frame->padlen = pdata->padlen; frame->padlen = pdata->padlen;
/* flags may have NGHTTP2_FLAG_END_STREAM or /* flags may have NGHTTP2_FLAG_END_STREAM even if the sent chunk is
NGHTTP2_FLAG_END_SEGMENT even if the sent chunk is not the end of not the end of the stream */
the stream */
if(!pdata->eof) { if(!pdata->eof) {
frame->hd.flags &= ~(NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT); frame->hd.flags &= ~NGHTTP2_FLAG_END_STREAM;
} }
} }
@ -1037,9 +1036,15 @@ int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv)
return 0; return 0;
} }
break; break;
default: 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; return 0;
} }
break;
case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
break;
}
} }
return 1; 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) static void frame_set_pad(nghttp2_buf *buf, size_t padlen)
{ {
size_t trail_padlen; size_t trail_padlen;
size_t newlen;
DEBUGF(fprintf(stderr, "send: padlen=%zu, shift left 1 bytes\n", padlen)); 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;
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; trail_padlen = padlen - 1;
buf->pos[NGHTTP2_FRAME_HDLEN] = trail_padlen; buf->pos[NGHTTP2_FRAME_HDLEN] = trail_padlen;
@ -1088,11 +1095,11 @@ int nghttp2_frame_add_pad(nghttp2_bufs *bufs, nghttp2_frame_hd *hd,
* 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 * 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 * We arranged padding so that it is included in the first frame
* completely. For padded frame, we are going to adjust buf->pos of * completely. For padded frame, we are going to adjust buf->pos of

View File

@ -33,7 +33,6 @@
#include "nghttp2_hd.h" #include "nghttp2_hd.h"
#include "nghttp2_buf.h" #include "nghttp2_buf.h"
#define NGHTTP2_FRAME_LENGTH_MASK ((1 << 14) - 1)
#define NGHTTP2_STREAM_ID_MASK ((1u << 31) - 1) #define NGHTTP2_STREAM_ID_MASK ((1u << 31) - 1)
#define NGHTTP2_PRI_GROUP_ID_MASK ((1u << 31) - 1) #define NGHTTP2_PRI_GROUP_ID_MASK ((1u << 31) - 1)
#define NGHTTP2_PRIORITY_MASK ((1u << 31) - 1) #define NGHTTP2_PRIORITY_MASK ((1u << 31) - 1)
@ -41,9 +40,12 @@
#define NGHTTP2_SETTINGS_ID_MASK ((1 << 24) - 1) #define NGHTTP2_SETTINGS_ID_MASK ((1 << 24) - 1)
/* The number of bytes of frame header. */ /* 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 /* The one frame buffer length for tranmission. We may use several of
them to support CONTINUATION. To account for Pad Length field, we them to support CONTINUATION. To account for Pad Length field, we
allocate extra 1 byte, which saves extra large memcopying. */ allocate extra 1 byte, which saves extra large memcopying. */
@ -57,7 +59,7 @@
#define NGHTTP2_DATA_PAYLOADLEN 4096 #define NGHTTP2_DATA_PAYLOADLEN 4096
/* Maximum headers payload length, calculated in compressed form. /* 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 #define NGHTTP2_MAX_HEADERSLEN 65536
/* The number of bytes for each SETTINGS entry */ /* The number of bytes for each SETTINGS entry */

View File

@ -100,7 +100,7 @@ static nghttp2_hd_entry static_table[] = {
MAKE_ENT("www-authenticate", "", 4051929931u, 0u), 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]); sizeof(static_table)/sizeof(static_table[0]);
static int memeq(const void *s1, const void *s2, size_t n) 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; --ringbuf->len;
} }
static int hd_context_init(nghttp2_hd_context *context, static int hd_context_init(nghttp2_hd_context *context)
nghttp2_hd_role role)
{ {
int rv; int rv;
context->role = role;
context->bad = 0; context->bad = 0;
context->hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; context->hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE;
rv = hd_ringbuf_init rv = hd_ringbuf_init
@ -314,11 +312,10 @@ int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater,
size_t deflate_hd_table_bufsize_max) size_t deflate_hd_table_bufsize_max)
{ {
int rv; int rv;
rv = hd_context_init(&deflater->ctx, NGHTTP2_HD_ROLE_DEFLATE); rv = hd_context_init(&deflater->ctx);
if(rv != 0) { if(rv != 0) {
return rv; return rv;
} }
deflater->no_refset = 0;
if(deflate_hd_table_bufsize_max < NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE) { if(deflate_hd_table_bufsize_max < NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE) {
deflater->notify_table_size_change = 1; 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->deflate_hd_table_bufsize_max = deflate_hd_table_bufsize_max;
deflater->min_hd_table_bufsize_max = UINT32_MAX;
return 0; return 0;
} }
@ -336,7 +334,7 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater)
{ {
int rv; int rv;
rv = hd_context_init(&inflater->ctx, NGHTTP2_HD_ROLE_INFLATE); rv = hd_context_init(&inflater->ctx);
if(rv != 0) { if(rv != 0) {
goto fail; goto fail;
} }
@ -346,7 +344,6 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater)
inflater->ent_keep = NULL; inflater->ent_keep = NULL;
inflater->nv_keep = NULL; inflater->nv_keep = NULL;
inflater->end_headers_index = 0;
inflater->opcode = NGHTTP2_HD_OPCODE_NONE; inflater->opcode = NGHTTP2_HD_OPCODE_NONE;
inflater->state = NGHTTP2_HD_STATE_OPCODE; inflater->state = NGHTTP2_HD_STATE_OPCODE;
@ -364,7 +361,6 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater)
inflater->newnamelen = 0; inflater->newnamelen = 0;
inflater->index_required = 0; inflater->index_required = 0;
inflater->no_index = 0; inflater->no_index = 0;
inflater->ent_name = NULL;
return 0; return 0;
@ -400,12 +396,6 @@ void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater)
hd_context_free(&inflater->ctx); 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) static size_t entry_room(size_t namelen, size_t valuelen)
{ {
return NGHTTP2_HD_ENTRY_OVERHEAD + namelen + 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(fprintf(stderr, ": "));
DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr)); DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr));
DEBUGF(fprintf(stderr, "\n")); DEBUGF(fprintf(stderr, "\n"));
/* ent->ref may be 0. This happens if the careless stupid encoder /* ent->ref may be 0. This happens if the encoder emits literal
emits literal block larger than header table capacity with block larger than header table capacity with indexing. */
indexing. */
ent->flags |= NGHTTP2_HD_FLAG_EMIT;
*nv_out = ent->nv; *nv_out = ent->nv;
return 0; 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 k = (1 << prefix) - 1;
size_t len = 0; size_t len = 0;
*buf &= ~k; *buf &= ~k;
if(n >= k) {
if(n < k) {
*buf++ |= n;
return 1;
}
*buf++ |= k; *buf++ |= k;
n -= k; n -= k;
++len; ++len;
} else {
*buf++ |= n;
return 1;
}
do { do {
++len; ++len;
if(n >= 128) { 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; 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) static int emit_table_size(nghttp2_bufs *bufs, size_t table_size)
{ {
int rv; 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)); 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) { if(sizeof(sb) < blocklen) {
return NGHTTP2_ERR_HEADER_COMP; return NGHTTP2_ERR_HEADER_COMP;
@ -594,7 +572,7 @@ static int emit_table_size(nghttp2_bufs *bufs, size_t table_size)
*bufp = 0x20u; *bufp = 0x20u;
encode_length(bufp, table_size, 4); encode_length(bufp, table_size, 5);
rv = nghttp2_bufs_add(bufs, sb, blocklen); rv = nghttp2_bufs_add(bufs, sb, blocklen);
if(rv != 0) { if(rv != 0) {
@ -632,14 +610,22 @@ static int emit_indexed_block(nghttp2_bufs *bufs, size_t idx)
return 0; return 0;
} }
static int emit_string(nghttp2_bufs *bufs, static int emit_string(nghttp2_bufs *bufs, const uint8_t *str, size_t len)
size_t enclen, int huffman,
const uint8_t *str, size_t len)
{ {
int rv; int rv;
uint8_t sb[16]; uint8_t sb[16];
uint8_t *bufp; uint8_t *bufp;
size_t blocklen; 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); blocklen = count_encoded_length(enclen, 7);
@ -691,9 +677,7 @@ static int emit_indname_block(nghttp2_bufs *bufs, size_t idx,
{ {
int rv; int rv;
uint8_t *bufp; uint8_t *bufp;
size_t encvallen;
size_t blocklen; size_t blocklen;
int huffman;
uint8_t sb[16]; uint8_t sb[16];
size_t prefixlen; size_t prefixlen;
int no_index; int no_index;
@ -711,13 +695,7 @@ static int emit_indname_block(nghttp2_bufs *bufs, size_t idx,
"indexing=%d, no_index=%d\n", "indexing=%d, no_index=%d\n",
idx, nv->valuelen, inc_indexing, no_index)); idx, nv->valuelen, inc_indexing, no_index));
encvallen = nghttp2_hd_huff_encode_count(nv->value, nv->valuelen);
blocklen = count_encoded_length(idx + 1, prefixlen); blocklen = count_encoded_length(idx + 1, prefixlen);
huffman = encvallen < nv->valuelen;
if(!huffman) {
encvallen = nv->valuelen;
}
if(sizeof(sb) < blocklen) { if(sizeof(sb) < blocklen) {
return NGHTTP2_ERR_HEADER_COMP; return NGHTTP2_ERR_HEADER_COMP;
@ -734,7 +712,7 @@ static int emit_indname_block(nghttp2_bufs *bufs, size_t idx,
return rv; return rv;
} }
rv = emit_string(bufs, encvallen, huffman, nv->value, nv->valuelen); rv = emit_string(bufs, nv->value, nv->valuelen);
if(rv != 0) { if(rv != 0) {
return rv; return rv;
} }
@ -746,10 +724,6 @@ static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv,
int inc_indexing) int inc_indexing)
{ {
int rv; int rv;
size_t encnamelen;
size_t encvallen;
int name_huffman;
int value_huffman;
int no_index; int no_index;
no_index = (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0; 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", "indexing=%d, no_index=%d\n",
nv->namelen, nv->valuelen, inc_indexing, no_index)); 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)); rv = nghttp2_bufs_addb(bufs, pack_first_byte(inc_indexing, no_index));
if(rv != 0) { if(rv != 0) {
return rv; return rv;
} }
rv = emit_string(bufs, encnamelen, name_huffman, nv->name, nv->namelen); rv = emit_string(bufs, nv->name, nv->namelen);
if(rv != 0) { if(rv != 0) {
return rv; return rv;
} }
rv = emit_string(bufs, encvallen, value_huffman, nv->value, nv->valuelen); rv = emit_string(bufs, nv->value, nv->valuelen);
if(rv != 0) { if(rv != 0) {
return rv; return rv;
} }
@ -789,23 +751,6 @@ static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv,
return 0; 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, static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context,
nghttp2_bufs *bufs, nghttp2_bufs *bufs,
const nghttp2_nv *nv, 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); nghttp2_hd_entry* ent = hd_ringbuf_get(&context->hd_table, idx);
context->hd_table_bufsize -= entry_room(ent->nv.namelen, ent->nv.valuelen); 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(fprintf(stderr, "hpack: remove item from header table: "));
DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr)); DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr));
DEBUGF(fprintf(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; context->hd_table_bufsize += room;
new_ent->flags |= NGHTTP2_HD_FLAG_REFSET;
} }
return new_ent; 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); uint32_t value_hash = hash(nv->value, nv->valuelen);
int use_index = (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) == 0; int use_index = (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) == 0;
if(use_index) { for(i = 0; i < NGHTTP2_STATIC_TABLE_LENGTH; ++i) {
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) {
nghttp2_hd_entry *ent = &static_table[i]; nghttp2_hd_entry *ent = &static_table[i];
if(ent->name_hash != name_hash || !name_eq(&ent->nv, nv)) { if(ent->name_hash != name_hash || !name_eq(&ent->nv, nv)) {
continue; continue;
} }
if(res.index == -1) { if(res.index == -1) {
res.index = (ssize_t)(context->hd_table.len + i); res.index = (ssize_t)i;
} }
if(use_index && if(use_index &&
ent->value_hash == value_hash && value_eq(&ent->nv, nv)) { 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; res.name_value_match = 1;
return res; 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; 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->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; deflater->notify_table_size_change = 1;
hd_context_shrink_table_size(&deflater->ctx); hd_context_shrink_table_size(&deflater->ctx);
@ -987,31 +926,22 @@ int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater,
return 0; 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) \ #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) 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, nghttp2_hd_entry* nghttp2_hd_table_get(nghttp2_hd_context *context,
size_t idx) size_t idx)
{ {
assert(INDEX_RANGE_VALID(context, idx)); assert(INDEX_RANGE_VALID(context, idx));
if(idx < context->hd_table.len) { if(idx >= NGHTTP2_STATIC_TABLE_LENGTH) {
return hd_ringbuf_get(&context->hd_table, idx); return hd_ringbuf_get(&context->hd_table, idx - NGHTTP2_STATIC_TABLE_LENGTH);
} else { } 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) nghttp2_bufs *bufs, const nghttp2_nv *nv)
{ {
int rv; int rv;
nghttp2_hd_entry *ent;
search_result res; search_result res;
ssize_t idx = -1;
int incidx = 0;
DEBUGF(fprintf(stderr, "deflatehd: deflating ")); DEBUGF(fprintf(stderr, "deflatehd: deflating "));
DEBUGF(fwrite(nv->name, nv->namelen, 1, stderr)); DEBUGF(fwrite(nv->name, nv->namelen, 1, stderr));
@ -1053,87 +984,28 @@ static int deflate_nv(nghttp2_hd_deflater *deflater,
res = search_hd_table(&deflater->ctx, nv); res = search_hd_table(&deflater->ctx, nv);
if(res.index != -1 && res.name_value_match) { idx = res.index;
size_t idx = res.index;
DEBUGF(fprintf(stderr, "deflatehd: name/value match index=%zd\n", if(res.name_value_match) {
res.index));
ent = nghttp2_hd_table_get(&deflater->ctx, idx); DEBUGF(fprintf(stderr, "deflatehd: name/value match index=%zd\n", 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); rv = emit_indexed_block(bufs, idx);
if(rv != 0) { if(rv != 0) {
return rv; return rv;
} }
} else if((ent->flags & NGHTTP2_HD_FLAG_REFSET) == 0) {
ent->flags |= NGHTTP2_HD_FLAG_REFSET | NGHTTP2_HD_FLAG_EMIT; return 0;
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) { if(res.index != -1) {
DEBUGF(fprintf(stderr, "deflatehd: name match index=%zd\n", DEBUGF(fprintf(stderr, "deflatehd: name match index=%zd\n",
res.index)); res.index));
idx = res.index;
} }
if(hd_deflate_should_indexing(deflater, nv)) { if(hd_deflate_should_indexing(deflater, nv)) {
nghttp2_hd_entry *new_ent; nghttp2_hd_entry *new_ent;
if(idx >= (ssize_t)deflater->ctx.hd_table.len) { if(idx != -1 && idx < (ssize_t)NGHTTP2_STATIC_TABLE_LENGTH) {
nghttp2_nv nv_indname; nghttp2_nv nv_indname;
nv_indname = *nv; nv_indname = *nv;
nv_indname.name = nghttp2_hd_table_get(&deflater->ctx, idx)->nv.name; nv_indname.name = nghttp2_hd_table_get(&deflater->ctx, idx)->nv.name;
@ -1150,10 +1022,6 @@ static int deflate_nv(nghttp2_hd_deflater *deflater,
if(new_ent->ref == 0) { if(new_ent->ref == 0) {
nghttp2_hd_entry_free(new_ent); nghttp2_hd_entry_free(new_ent);
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; incidx = 1;
} }
@ -1165,30 +1033,6 @@ static int deflate_nv(nghttp2_hd_deflater *deflater,
if(rv != 0) { if(rv != 0) {
return rv; 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;
rv = emit_indexed_block(bufs, idx);
if(rv != 0) {
return rv;
}
}
ent->flags &= ~(NGHTTP2_HD_FLAG_EMIT | NGHTTP2_HD_FLAG_IMPLICIT_EMIT);
return 0; return 0;
} }
@ -1205,8 +1049,21 @@ int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater,
} }
if(deflater->notify_table_size_change) { 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->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); 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) { for(i = 0; i < nvlen; ++i) {
rv = deflate_nv(deflater, bufs, &nv[i]); rv = deflate_nv(deflater, bufs, &nv[i]);
if(rv != 0) { if(rv != 0) {
@ -1232,15 +1082,6 @@ int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater,
DEBUGF(fprintf(stderr, DEBUGF(fprintf(stderr,
"deflatehd: all input name/value pairs were deflated\n")); "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; return 0;
fail: fail:
DEBUGF(fprintf(stderr, "deflatehd: error return %d\n", rv)); 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, size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater,
const nghttp2_nv *nva, size_t nvlen) const nghttp2_nv *nva, size_t nvlen)
{ {
size_t n; size_t n = 0;
size_t i; size_t i;
/* Possible Reference Set Emptying */
n = 1;
/* Possible Maximum Header Table Size Change. Encoding (1u << 31) - /* Possible Maximum Header Table Size Change. Encoding (1u << 31) -
1 using 4 bit prefix requires 6 bytes. */ 1 using 4 bit prefix requires 6 bytes. We may emit this at most
n += 6; twice. */
n += 12;
/* Use Literal Header Field without indexing - New Name, since it is /* Use Literal Header Field without indexing - New Name, since it is
most space consuming format. Also we choose the less one between 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; n += nva[i].namelen + nva[i].valuelen;
} }
/* Add possible reference set toggle off */
n += deflater->ctx.hd_table.len;
return n; return n;
} }
@ -1471,29 +1307,10 @@ static int hd_inflate_commit_indexed(nghttp2_hd_inflater *inflater,
nghttp2_nv *nv_out) nghttp2_nv *nv_out)
{ {
nghttp2_hd_entry *ent = nghttp2_hd_table_get(&inflater->ctx, inflater->index); 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); emit_indexed_header(nv_out, ent);
return 0; 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;
} }
static int hd_inflate_remove_bufs(nghttp2_hd_inflater *inflater, 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; int rv;
nghttp2_nv nv; nghttp2_nv nv;
nghttp2_hd_entry *ent_name;
rv = hd_inflate_remove_bufs(inflater, &nv, 1 /* value only */); rv = hd_inflate_remove_bufs(inflater, &nv, 1 /* value only */);
if(rv != 0) { if(rv != 0) {
@ -1611,8 +1429,10 @@ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater,
nv.flags = NGHTTP2_NV_FLAG_NONE; nv.flags = NGHTTP2_NV_FLAG_NONE;
} }
nv.name = inflater->ent_name->nv.name; ent_name = nghttp2_hd_table_get(&inflater->ctx, inflater->index);
nv.namelen = inflater->ent_name->nv.namelen;
nv.name = ent_name->nv.name;
nv.namelen = ent_name->nv.namelen;
if(inflater->index_required) { if(inflater->index_required) {
nghttp2_hd_entry *new_ent; nghttp2_hd_entry *new_ent;
@ -1620,24 +1440,22 @@ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater,
int static_name; int static_name;
ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC | NGHTTP2_HD_FLAG_VALUE_GIFT; 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) { if(!static_name) {
ent_flags |= NGHTTP2_HD_FLAG_NAME_ALLOC; ent_flags |= NGHTTP2_HD_FLAG_NAME_ALLOC;
/* For entry in static table, we must not touch ref, because it /* For entry in static table, we must not touch ref, because it
is shared by threads */ is shared by threads */
++inflater->ent_name->ref; ++ent_name->ref;
} }
new_ent = add_hd_table_incremental(&inflater->ctx, NULL, &nv, ent_flags); new_ent = add_hd_table_incremental(&inflater->ctx, NULL, &nv, ent_flags);
if(!static_name && --inflater->ent_name->ref == 0) { if(!static_name && --ent_name->ref == 0) {
nghttp2_hd_entry_free(inflater->ent_name); nghttp2_hd_entry_free(ent_name);
free(inflater->ent_name); free(ent_name);
} }
inflater->ent_name = NULL;
if(new_ent) { if(new_ent) {
emit_indexed_header(nv_out, 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;) { for(; in != last;) {
switch(inflater->state) { switch(inflater->state) {
case NGHTTP2_HD_STATE_OPCODE: case NGHTTP2_HD_STATE_OPCODE:
if((*in & 0xf0u) == 0x20u) { if((*in & 0xe0u) == 0x20u) {
DEBUGF(fprintf(stderr, "inflatehd: header table size change\n")); DEBUGF(fprintf(stderr, "inflatehd: header table size change\n"));
inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED; inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED;
inflater->state = NGHTTP2_HD_STATE_READ_TABLE_SIZE; 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) { } else if(*in & 0x80u) {
DEBUGF(fprintf(stderr, "inflatehd: indexed repr\n")); DEBUGF(fprintf(stderr, "inflatehd: indexed repr\n"));
inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED; inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED;
@ -1720,15 +1528,10 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
} }
inflater->left = 0; inflater->left = 0;
inflater->shift = 0; inflater->shift = 0;
break;
case NGHTTP2_HD_STATE_CLEAR_REFSET:
clear_refset(&inflater->ctx);
inflater->state = NGHTTP2_HD_STATE_OPCODE;
break; break;
case NGHTTP2_HD_STATE_READ_TABLE_SIZE: case NGHTTP2_HD_STATE_READ_TABLE_SIZE:
rfin = 0; 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); inflater->settings_hd_table_bufsize_max);
if(rv < 0) { if(rv < 0) {
goto fail; goto fail;
@ -1762,19 +1565,20 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
in += rv; in += rv;
if(!rfin) {
goto almost_ok;
}
if(inflater->left == 0) { if(inflater->left == 0) {
rv = NGHTTP2_ERR_HEADER_COMP; rv = NGHTTP2_ERR_HEADER_COMP;
goto fail; goto fail;
} }
if(!rfin) {
goto almost_ok;
}
DEBUGF(fprintf(stderr, "inflatehd: index=%zu\n", inflater->left)); DEBUGF(fprintf(stderr, "inflatehd: index=%zu\n", inflater->left));
if(inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) { if(inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) {
inflater->index = inflater->left; inflater->index = inflater->left;
assert(inflater->index > 0);
--inflater->index; --inflater->index;
rv = hd_inflate_commit_indexed(inflater, nv_out); rv = hd_inflate_commit_indexed(inflater, nv_out);
if(rv < 0) { if(rv < 0) {
goto fail; goto fail;
@ -1787,10 +1591,8 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
} }
} else { } else {
inflater->index = inflater->left; inflater->index = inflater->left;
assert(inflater->index > 0);
--inflater->index; --inflater->index;
inflater->ent_name = nghttp2_hd_table_get(&inflater->ctx,
inflater->index);
inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
} }
break; break;
@ -1995,20 +1797,6 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
goto fail; 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; *inflate_flags |= NGHTTP2_HD_INFLATE_FINAL;
} }
return (ssize_t)(in - first); 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) int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater)
{ {
hd_inflate_keep_free(inflater); hd_inflate_keep_free(inflater);
inflater->end_headers_index = 0;
return 0; return 0;
} }

View File

@ -47,10 +47,8 @@
encoder only uses the memory up to this value. */ encoder only uses the memory up to this value. */
#define NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE (1 << 12) #define NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE (1 << 12)
typedef enum { /* Exported for unit test */
NGHTTP2_HD_ROLE_DEFLATE, extern const size_t NGHTTP2_STATIC_TABLE_LENGTH;
NGHTTP2_HD_ROLE_INFLATE
} nghttp2_hd_role;
typedef enum { typedef enum {
NGHTTP2_HD_FLAG_NONE = 0, NGHTTP2_HD_FLAG_NONE = 0,
@ -58,18 +56,12 @@ typedef enum {
NGHTTP2_HD_FLAG_NAME_ALLOC = 1, NGHTTP2_HD_FLAG_NAME_ALLOC = 1,
/* Indicates value was dynamically allocated and must be freed */ /* Indicates value was dynamically allocated and must be freed */
NGHTTP2_HD_FLAG_VALUE_ALLOC = 1 << 1, 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 /* Indicates that the name was gifted to the entry and no copying
necessary. */ 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 /* Indicates that the value was gifted to the entry and no copying
necessary. */ necessary. */
NGHTTP2_HD_FLAG_VALUE_GIFT = 1 << 6 NGHTTP2_HD_FLAG_VALUE_GIFT = 1 << 3
} nghttp2_hd_flags; } nghttp2_hd_flags;
typedef struct { typedef struct {
@ -97,7 +89,6 @@ typedef enum {
typedef enum { typedef enum {
NGHTTP2_HD_STATE_OPCODE, NGHTTP2_HD_STATE_OPCODE,
NGHTTP2_HD_STATE_CLEAR_REFSET,
NGHTTP2_HD_STATE_READ_TABLE_SIZE, NGHTTP2_HD_STATE_READ_TABLE_SIZE,
NGHTTP2_HD_STATE_READ_INDEX, NGHTTP2_HD_STATE_READ_INDEX,
NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN, NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN,
@ -119,8 +110,6 @@ typedef struct {
size_t hd_table_bufsize; size_t hd_table_bufsize;
/* The effective header table size. */ /* The effective header table size. */
size_t hd_table_bufsize_max; 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 /* If inflate/deflate error occurred, this value is set to 1 and
further invocation of inflate/deflate will fail with further invocation of inflate/deflate will fail with
NGHTTP2_ERR_HEADER_COMP. */ NGHTTP2_ERR_HEADER_COMP. */
@ -131,9 +120,8 @@ struct nghttp2_hd_deflater {
nghttp2_hd_context ctx; nghttp2_hd_context ctx;
/* The upper limit of the header table size the deflater accepts. */ /* The upper limit of the header table size the deflater accepts. */
size_t deflate_hd_table_bufsize_max; size_t deflate_hd_table_bufsize_max;
/* Set to this nonzero to clear reference set on each deflation each /* Minimum header table size notified in the next context update */
time. */ size_t min_hd_table_bufsize_max;
uint8_t no_refset;
/* If nonzero, send header table size using encoding context update /* If nonzero, send header table size using encoding context update
in the next deflate process */ in the next deflate process */
uint8_t notify_table_size_change; 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 /* Pointer to the name/value pair buffer which is used in the
current header emission. */ current header emission. */
uint8_t *nv_keep; 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 */ /* The number of bytes to read */
size_t left; size_t left;
/* The index in indexed repr or indexed name */ /* The index in indexed repr or indexed name */
size_t index; 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 /* The length of new name encoded in literal. For huffman encoded
string, this is the length after it is decoded. */ string, this is the length after it is decoded. */
size_t newnamelen; size_t newnamelen;

View File

@ -40,18 +40,10 @@ void nghttp2_option_del(nghttp2_option *option)
free(option); free(option);
} }
void nghttp2_option_set_no_auto_stream_window_update(nghttp2_option *option, void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val)
int val)
{ {
option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE; option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE;
option->no_auto_stream_window_update = val; option->no_auto_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;
} }
void nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option, void nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option,

View File

@ -35,24 +35,14 @@
* Configuration options * Configuration options
*/ */
typedef enum { 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.
*/
NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE = 1,
/** /**
* This option prevents the library from sending WINDOW_UPDATE for a * This option prevents the library from sending WINDOW_UPDATE for a
* connection automatically. If this option is set to nonzero, the * connection automatically. If this option is set to nonzero, the
* library won't send WINDOW_UPDATE for a connection and the * library won't send WINDOW_UPDATE for DATA until application calls
* application is responsible for sending WINDOW_UPDATE with stream * nghttp2_session_consume() to indicate the amount of consumed
* ID 0 using `nghttp2_submit_window_update`. By default, this * DATA. By default, this option is set to zero.
* 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 * This option sets the SETTINGS_MAX_CONCURRENT_STREAMS value of
* remote endpoint as if it is received in SETTINGS frame. Without * 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 * will be overwritten if the local endpoint receives
* SETTINGS_MAX_CONCURRENT_STREAMS from the remote endpoint. * 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; } nghttp2_option_flag;
/** /**
@ -83,13 +73,9 @@ struct nghttp2_option {
*/ */
uint32_t peer_max_concurrent_streams; 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; uint8_t no_auto_window_update;
/**
* NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE
*/
uint8_t no_auto_connection_window_update;
}; };
#endif /* NGHTTP2_OPTION_H */ #endif /* NGHTTP2_OPTION_H */

View File

@ -281,7 +281,6 @@ static void session_inbound_frame_reset(nghttp2_session *session)
iframe->niv = 0; iframe->niv = 0;
iframe->payloadleft = 0; iframe->payloadleft = 0;
iframe->padlen = 0; iframe->padlen = 0;
iframe->headers_payload_length = 0;
iframe->iv[NGHTTP2_INBOUND_NUM_IV - 1].settings_id = iframe->iv[NGHTTP2_INBOUND_NUM_IV - 1].settings_id =
NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
iframe->iv[NGHTTP2_INBOUND_NUM_IV - 1].value = UINT32_MAX; 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->enable_push = 1;
settings->max_concurrent_streams = NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS; settings->max_concurrent_streams = NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
settings->initial_window_size = NGHTTP2_INITIAL_WINDOW_SIZE; 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) 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)->remote_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
(*session_ptr)->recv_window_size = 0; (*session_ptr)->recv_window_size = 0;
(*session_ptr)->consumed_size = 0;
(*session_ptr)->recv_reduction = 0; (*session_ptr)->recv_reduction = 0;
(*session_ptr)->local_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE; (*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) {
if((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE) && if((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
option->no_auto_stream_window_update) { option->no_auto_window_update) {
(*session_ptr)->opt_flags |= (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE;
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;
} }
@ -2031,6 +2024,8 @@ static int session_after_frame_sent(nghttp2_session *session)
/* Fall through */ /* Fall through */
case NGHTTP2_HCAT_RESPONSE: case NGHTTP2_HCAT_RESPONSE:
stream->state = NGHTTP2_STREAM_OPENED; stream->state = NGHTTP2_STREAM_OPENED;
/* Fall through */
case NGHTTP2_HCAT_HEADERS:
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); 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. */ DATA frame item. We might have to handle it here. */
} }
break; 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; break;
} }
@ -3182,10 +3168,11 @@ static int update_local_initial_window_size_func
return nghttp2_session_terminate_session(arg->session, return nghttp2_session_terminate_session(arg->session,
NGHTTP2_FLOW_CONTROL_ERROR); NGHTTP2_FLOW_CONTROL_ERROR);
} }
if(!(arg->session->opt_flags & if(!(arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE)) {
if(nghttp2_should_send_window_update(stream->local_window_size, if(nghttp2_should_send_window_update(stream->local_window_size,
stream->recv_window_size)) { stream->recv_window_size)) {
rv = nghttp2_session_add_window_update(arg->session, rv = nghttp2_session_add_window_update(arg->session,
NGHTTP2_FLAG_NONE, NGHTTP2_FLAG_NONE,
stream->stream_id, stream->stream_id,
@ -3288,6 +3275,12 @@ int nghttp2_session_update_local_settings(nghttp2_session *session,
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
session->local_settings.initial_window_size = iv[i].value; session->local_settings.initial_window_size = iv[i].value;
break; 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; 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; break;
} }
} }
@ -3722,7 +3732,8 @@ static int session_on_connection_window_update_received
{ {
int rv; int rv;
/* Handle connection-level flow control */ /* 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) { session->remote_window_size) {
return session_handle_invalid_connection return session_handle_invalid_connection
(session, frame, NGHTTP2_FLOW_CONTROL_ERROR, NULL); (session, frame, NGHTTP2_FLOW_CONTROL_ERROR, NULL);
@ -3761,7 +3772,8 @@ static int session_on_stream_window_update_received
(session, frame, NGHTTP2_PROTOCOL_ERROR, (session, frame, NGHTTP2_PROTOCOL_ERROR,
"WINDOW_UPADATE to reserved stream"); "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) { stream->remote_window_size) {
return session_handle_invalid_stream(session, frame, return session_handle_invalid_stream(session, frame,
NGHTTP2_FLOW_CONTROL_ERROR); 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 * Accumulates received bytes |delta_size| for stream-level flow
* control and decides whether to send WINDOW_UPDATE to that * control and decides whether to send WINDOW_UPDATE to that stream.
* stream. If NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE is set, * If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, WINDOW_UPDATE will not
* WINDOW_UPDATE will not be sent. * be sent.
* *
* This function returns 0 if it succeeds, or one of the following * This function returns 0 if it succeeds, or one of the following
* negative error codes: * 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 /* We don't have to send WINDOW_UPDATE if the data received is the
last chunk in the incoming stream. */ last chunk in the incoming stream. */
if(send_window_update && 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 /* We have to use local_settings here because it is the constraint
the remote endpoint should honor. */ the remote endpoint should honor. */
if(nghttp2_should_send_window_update(stream->local_window_size, 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 * Accumulates received bytes |delta_size| for connection-level flow
* control and decides whether to send WINDOW_UPDATE to the * control and decides whether to send WINDOW_UPDATE to the
* connection. If NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE is * connection. If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set,
* set, WINDOW_UPDATE will not be sent. * WINDOW_UPDATE will not be sent.
* *
* This function returns 0 if it succeeds, or one of the following * This function returns 0 if it succeeds, or one of the following
* negative error codes: * negative error codes:
@ -3951,30 +3963,92 @@ static int session_update_recv_connection_window_size
return nghttp2_session_terminate_session(session, return nghttp2_session_terminate_session(session,
NGHTTP2_FLOW_CONTROL_ERROR); NGHTTP2_FLOW_CONTROL_ERROR);
} }
if(!(session->opt_flags & if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE)) {
if(nghttp2_should_send_window_update(session->local_window_size, if(nghttp2_should_send_window_update(session->local_window_size,
session->recv_window_size)) { session->recv_window_size)) {
/* Use stream ID 0 to update connection-level flow control /* Use stream ID 0 to update connection-level flow control
window */ window */
rv = nghttp2_session_add_window_update(session, rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, 0,
NGHTTP2_FLAG_NONE,
0,
session->recv_window_size); session->recv_window_size);
if(rv == 0) { 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 {
return rv; return rv;
} }
session->recv_window_size = 0;
} }
} }
return 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 * Checks that we can receive the DATA frame for stream, which is
* indicated by |session->iframe.frame.hd.stream_id|. If it is a * 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_ENABLE_PUSH:
case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
break; break;
default: default:
DEBUGF(fprintf(stderr, "recv: ignore unknown settings id=0x%02x\n", 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.flags,
iframe->frame.hd.stream_id)); 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) { switch(iframe->frame.hd.type) {
case NGHTTP2_DATA: { case NGHTTP2_DATA: {
DEBUGF(fprintf(stderr, "recv: DATA\n")); DEBUGF(fprintf(stderr, "recv: DATA\n"));
iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_STREAM | iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_STREAM |
NGHTTP2_FLAG_END_SEGMENT |
NGHTTP2_FLAG_PADDED); NGHTTP2_FLAG_PADDED);
/* Check stream is open. If it is not open or closing, /* Check stream is open. If it is not open or closing,
ignore payload. */ ignore payload. */
@ -4275,13 +4370,10 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
DEBUGF(fprintf(stderr, "recv: HEADERS\n")); DEBUGF(fprintf(stderr, "recv: HEADERS\n"));
iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_STREAM | iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_STREAM |
NGHTTP2_FLAG_END_SEGMENT |
NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_HEADERS |
NGHTTP2_FLAG_PADDED | NGHTTP2_FLAG_PADDED |
NGHTTP2_FLAG_PRIORITY); NGHTTP2_FLAG_PRIORITY);
iframe->headers_payload_length = iframe->frame.hd.length;
rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd);
if(rv < 0) { if(rv < 0) {
busy = 1; busy = 1;
@ -4408,8 +4500,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_HEADERS | iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_HEADERS |
NGHTTP2_FLAG_PADDED); NGHTTP2_FLAG_PADDED);
iframe->headers_payload_length = iframe->frame.hd.length;
rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd);
if(rv < 0) { if(rv < 0) {
busy = 1; busy = 1;
@ -4988,27 +5078,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
break; 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 */ /* CONTINUATION won't bear NGHTTP2_PADDED flag */
iframe->frame.hd.flags |= cont_hd.flags & NGHTTP2_FLAG_END_HEADERS; 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; 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, stream = nghttp2_session_get_stream(session,
iframe->frame.hd.stream_id); iframe->frame.hd.stream_id);
if(stream) { if(stream) {
@ -5105,6 +5182,18 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
data_readlen = inbound_frame_effective_readlen data_readlen = inbound_frame_effective_readlen
(iframe, iframe->payloadleft, 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)); DEBUGF(fprintf(stderr, "recv: data_readlen=%zu\n", data_readlen));
if(stream && data_readlen > 0 && if(stream && data_readlen > 0 &&
@ -5149,8 +5238,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
readlen, iframe->payloadleft)); readlen, iframe->payloadleft));
if(readlen > 0) { if(readlen > 0) {
session->recv_ign_window_size += readlen;
/* Update connection-level flow control window for ignored /* Update connection-level flow control window for ignored
DATA frame too */ DATA frame too */
rv = session_update_recv_connection_window_size(session, readlen); rv = session_update_recv_connection_window_size(session, readlen);
@ -5158,20 +5245,14 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
return rv; return rv;
} }
if((session->opt_flags & if(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) {
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE) &&
nghttp2_should_send_window_update
(session->local_window_size, session->recv_ign_window_size)) {
rv = nghttp2_session_add_window_update /* Ignored DATA is considered as "consumed" immediately. */
(session, NGHTTP2_FLAG_NONE, 0, session->recv_ign_window_size); rv = session_update_connection_consumed_size(session, readlen);
if(nghttp2_is_fatal(rv)) { if(nghttp2_is_fatal(rv)) {
return 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 /* Clear flags, because this may contain previous flags of previous
DATA */ DATA */
frame->hd.flags &= (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT); frame->hd.flags &= NGHTTP2_FLAG_END_STREAM;
flags = NGHTTP2_FLAG_NONE; flags = NGHTTP2_FLAG_NONE;
if(data_flags & NGHTTP2_DATA_FLAG_EOF) { 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) { if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
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 */ /* 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; return session->remote_settings.max_concurrent_streams;
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
return session->remote_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); assert(0);
@ -5744,3 +5826,36 @@ int nghttp2_session_get_stream_remote_close(nghttp2_session* session,
return (stream->shut_flags & NGHTTP2_SHUT_RD) != 0; 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;
}

View File

@ -43,8 +43,7 @@
* Option flags. * Option flags.
*/ */
typedef enum { typedef enum {
NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE = 1 << 0, NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0,
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE = 1 << 1
} nghttp2_optmask; } nghttp2_optmask;
typedef enum { typedef enum {
@ -81,7 +80,7 @@ typedef enum {
NGHTTP2_IB_IGN_DATA NGHTTP2_IB_IGN_DATA
} nghttp2_inbound_state; } nghttp2_inbound_state;
#define NGHTTP2_INBOUND_NUM_IV 5 #define NGHTTP2_INBOUND_NUM_IV 7
typedef struct { typedef struct {
nghttp2_frame frame; nghttp2_frame frame;
@ -105,9 +104,6 @@ typedef struct {
size_t payloadleft; size_t payloadleft;
/* padding length for the current frame */ /* padding length for the current frame */
size_t padlen; size_t padlen;
/* Sum of payload of (HEADERS | PUSH_PROMISE) + possible
CONTINUATION received so far. */
size_t headers_payload_length;
nghttp2_inbound_state state; nghttp2_inbound_state state;
uint8_t raw_sbuf[8]; uint8_t raw_sbuf[8];
} nghttp2_inbound_frame; } nghttp2_inbound_frame;
@ -117,6 +113,8 @@ typedef struct {
uint32_t enable_push; uint32_t enable_push;
uint32_t max_concurrent_streams; uint32_t max_concurrent_streams;
uint32_t initial_window_size; uint32_t initial_window_size;
uint32_t max_frame_size;
uint32_t max_header_set_size;
} nghttp2_settings_storage; } nghttp2_settings_storage;
typedef enum { typedef enum {
@ -206,15 +204,10 @@ struct nghttp2_session {
WINDOW_UPDATE. This could be negative after submitting negative WINDOW_UPDATE. This could be negative after submitting negative
value to WINDOW_UPDATE. */ value to WINDOW_UPDATE. */
int32_t recv_window_size; int32_t recv_window_size;
/* The number of bytes in ignored DATA frame received without /* The number of bytes consumed by the application and now is
connection-level WINDOW_UPDATE. Since we do not call subject to WINDOW_UPDATE. This is only used when auto
on_data_chunk_recv_callback for ignored DATA chunk, if WINDOW_UPDATE is turned off. */
nghttp2_option_set_no_auto_connection_window_update is used, int32_t consumed_size;
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 amount of recv_window_size cut using submitting negative /* The amount of recv_window_size cut using submitting negative
value to WINDOW_UPDATE */ value to WINDOW_UPDATE */
int32_t recv_reduction; int32_t recv_reduction;

View File

@ -48,6 +48,7 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
stream->remote_window_size = remote_initial_window_size; stream->remote_window_size = remote_initial_window_size;
stream->local_window_size = local_initial_window_size; stream->local_window_size = local_initial_window_size;
stream->recv_window_size = 0; stream->recv_window_size = 0;
stream->consumed_size = 0;
stream->recv_reduction = 0; stream->recv_reduction = 0;
stream->blocked_sent = 0; stream->blocked_sent = 0;

View File

@ -151,6 +151,10 @@ struct nghttp2_stream {
WINDOW_UPDATE. This could be negative after submitting negative WINDOW_UPDATE. This could be negative after submitting negative
value to WINDOW_UPDATE */ value to WINDOW_UPDATE */
int32_t recv_window_size; 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 /* The amount of recv_window_size cut using submitting negative
value to WINDOW_UPDATE */ value to WINDOW_UPDATE */
int32_t recv_reduction; int32_t recv_reduction;

View File

@ -81,9 +81,7 @@ static int32_t submit_headers_shared
} }
flags_copy = flags_copy =
(flags & (NGHTTP2_FLAG_END_STREAM | (flags & (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PRIORITY)) |
NGHTTP2_FLAG_END_SEGMENT |
NGHTTP2_FLAG_PRIORITY)) |
NGHTTP2_FLAG_END_HEADERS; NGHTTP2_FLAG_END_HEADERS;
if(stream_id == -1) { if(stream_id == -1) {
@ -348,14 +346,12 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
if(rv != 0) { if(rv != 0) {
return rv; 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 { } else {
stream = nghttp2_session_get_stream(session, stream_id); stream = nghttp2_session_get_stream(session, stream_id);
if(stream) { if(!stream) {
return 0;
}
rv = nghttp2_adjust_local_window_size(&stream->local_window_size, rv = nghttp2_adjust_local_window_size(&stream->local_window_size,
&stream->recv_window_size, &stream->recv_window_size,
&stream->recv_reduction, &stream->recv_reduction,
@ -363,12 +359,17 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
if(rv != 0) { if(rv != 0) {
return rv; return rv;
} }
} else {
return 0;
}
} }
if(window_size_increment > 0) { 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, return nghttp2_session_add_window_update(session, flags, stream_id,
window_size_increment); window_size_increment);
} }
@ -535,8 +536,7 @@ int nghttp2_submit_data(nghttp2_session *session, uint8_t flags,
{ {
int rv; int rv;
nghttp2_private_data *data_frame; nghttp2_private_data *data_frame;
uint8_t nflags = flags & (NGHTTP2_FLAG_END_STREAM | uint8_t nflags = flags & NGHTTP2_FLAG_END_STREAM;
NGHTTP2_FLAG_END_SEGMENT);
if(stream_id == 0) { if(stream_id == 0) {
return NGHTTP2_ERR_INVALID_ARGUMENT; return NGHTTP2_ERR_INVALID_ARGUMENT;

View File

@ -252,9 +252,6 @@ cdef extern from 'nghttp2/nghttp2.h':
void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater) 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, int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater,
size_t hd_table_bufsize_max) size_t hd_table_bufsize_max)
@ -291,9 +288,6 @@ cdef extern from 'nghttp2_hd.h':
# This is macro # This is macro
int NGHTTP2_HD_ENTRY_OVERHEAD int NGHTTP2_HD_ENTRY_OVERHEAD
ctypedef enum nghttp2_hd_flags:
NGHTTP2_HD_FLAG_REFSET
ctypedef enum nghttp2_hd_inflate_flag: ctypedef enum nghttp2_hd_inflate_flag:
NGHTTP2_HD_INFLATE_EMIT NGHTTP2_HD_INFLATE_EMIT
NGHTTP2_HD_INFLATE_FINAL NGHTTP2_HD_INFLATE_FINAL

View File

@ -53,7 +53,7 @@ if __name__ == '__main__':
testdata = json.loads(input) 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') sys.stderr.write('Not supported\n')
continue continue

View File

@ -16,7 +16,7 @@ import nghttp2
def testsuite(testdata, filename, outdir, table_size, deflate_table_size, def testsuite(testdata, filename, outdir, table_size, deflate_table_size,
simulate_table_size_change): simulate_table_size_change):
res = { res = {
'draft':8, 'draft':9,
'description': '''\ 'description': '''\
Encoded by nghttp2. The basic encoding strategy is described in \ Encoded by nghttp2. The basic encoding strategy is described in \
http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html \ http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html \

View File

@ -33,12 +33,11 @@ HD_ENTRY_OVERHEAD = cnghttp2.NGHTTP2_HD_ENTRY_OVERHEAD
class HDTableEntry: class HDTableEntry:
def __init__(self, name, namelen, value, valuelen, ref): def __init__(self, name, namelen, value, valuelen):
self.name = name self.name = name
self.namelen = namelen self.namelen = namelen
self.value = value self.value = value
self.valuelen = valuelen self.valuelen = valuelen
self.ref = ref
def space(self): def space(self):
return self.namelen + self.valuelen + HD_ENTRY_OVERHEAD 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) k = _get_pybytes(entry.nv.name, entry.nv.namelen)
v = _get_pybytes(entry.nv.value, entry.nv.valuelen) v = _get_pybytes(entry.nv.value, entry.nv.valuelen)
res.append(HDTableEntry(k, entry.nv.namelen, res.append(HDTableEntry(k, entry.nv.namelen,
v, entry.nv.valuelen, v, entry.nv.valuelen))
(entry.flags & cnghttp2.NGHTTP2_HD_FLAG_REFSET) != 0))
return res return res
cdef _get_pybytes(uint8_t *b, uint16_t blen): cdef _get_pybytes(uint8_t *b, uint16_t blen):
@ -143,15 +141,6 @@ cdef class HDDeflater:
return res 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): def change_table_size(self, hd_table_bufsize_max):
'''Changes header table size to |hd_table_bufsize_max| byte. '''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 function does not work if header name/value cannot be decoded using
UTF-8 encoding. UTF-8 encoding.
s=N means the entry occupies N bytes in header table. if r=y, then s=N means the entry occupies N bytes in header table.
the entry is in the reference set.
''' '''
idx = 0 idx = 0
for entry in hdtable: for entry in hdtable:
idx += 1 idx += 1
print('[{}] (s={}) (r={}) {}: {}'\ print('[{}] (s={}) {}: {}'\
.format(idx, entry.space(), .format(idx, entry.space(),
'y' if entry.ref else 'n',
entry.name.decode('utf-8'), entry.name.decode('utf-8'),
entry.value.decode('utf-8'))) entry.value.decode('utf-8')))

View File

@ -94,7 +94,7 @@ namespace {
void append_nv(Stream *stream, const std::vector<nghttp2_nv>& nva) void append_nv(Stream *stream, const std::vector<nghttp2_nv>& nva)
{ {
for(auto& nv : nva) { for(auto& nv : nva) {
http2::split_add_header(stream->headers, http2::add_header(stream->headers,
nv.name, nv.namelen, nv.value, nv.valuelen, nv.name, nv.namelen, nv.value, nv.valuelen,
nv.flags & NGHTTP2_NV_FLAG_NO_INDEX); nv.flags & NGHTTP2_NV_FLAG_NO_INDEX);
} }
@ -843,6 +843,16 @@ int Http2Handler::submit_response(const std::string& status,
data_prd); data_prd);
} }
int Http2Handler::submit_non_final_response(const std::string& status,
int32_t stream_id)
{
auto nva = std::vector<nghttp2_nv>{
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, int Http2Handler::submit_push_promise(Stream *stream,
const std::string& push_path) const std::string& push_path)
{ {
@ -1158,7 +1168,7 @@ int on_header_callback(nghttp2_session *session,
if(!http2::check_nv(name, namelen, value, valuelen)) { if(!http2::check_nv(name, namelen, value, valuelen)) {
return 0; return 0;
} }
http2::split_add_header(stream->headers, name, namelen, value, valuelen, http2::add_header(stream->headers, name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX); flags & NGHTTP2_NV_FLAG_NO_INDEX);
return 0; return 0;
} }
@ -1247,7 +1257,7 @@ int hd_on_frame_recv_callback
if(frame->headers.cat == NGHTTP2_HCAT_REQUEST) { if(frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
http2::normalize_headers(stream->headers); 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); hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
return 0; return 0;
} }
@ -1266,6 +1276,12 @@ int hd_on_frame_recv_callback
return 0; 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) { if(hd->get_config()->early_response) {
prepare_response(stream, hd); prepare_response(stream, hd);
} }

View File

@ -126,6 +126,8 @@ public:
const std::vector<std::pair<std::string, std::string>>& headers, const std::vector<std::pair<std::string, std::string>>& headers,
nghttp2_data_provider *data_prd); 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_push_promise(Stream *stream, const std::string& push_path);
int submit_rst_stream(Stream *stream, nghttp2_error_code error_code); int submit_rst_stream(Stream *stream, nghttp2_error_code error_code);

View File

@ -221,12 +221,6 @@ void print_flags(const nghttp2_frame_hd& hd)
if(hd.flags & NGHTTP2_FLAG_END_STREAM) { if(hd.flags & NGHTTP2_FLAG_END_STREAM) {
s += "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(hd.flags & NGHTTP2_FLAG_PADDED) {
if(!s.empty()) { if(!s.empty()) {
s += " | "; s += " | ";
@ -238,12 +232,6 @@ void print_flags(const nghttp2_frame_hd& hd)
if(hd.flags & NGHTTP2_FLAG_END_STREAM) { if(hd.flags & NGHTTP2_FLAG_END_STREAM) {
s += "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(hd.flags & NGHTTP2_FLAG_END_HEADERS) {
if(!s.empty()) { if(!s.empty()) {
s += " | "; s += " | ";

View File

@ -43,8 +43,6 @@ json_t* dump_header_table(nghttp2_hd_context *context)
json_object_set_new(outent, "index", json_integer(i + 1)); json_object_set_new(outent, "index", json_integer(i + 1));
dump_val(outent, "name", ent->nv.name, ent->nv.namelen); dump_val(outent, "name", ent->nv.name, ent->nv.namelen);
dump_val(outent, "value", ent->nv.value, ent->nv.valuelen); 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_object_set_new(outent, "size",
json_integer(ent->nv.namelen + ent->nv.valuelen + json_integer(ent->nv.namelen + ent->nv.valuelen +
NGHTTP2_HD_ENTRY_OVERHEAD)); NGHTTP2_HD_ENTRY_OVERHEAD));

View File

@ -53,7 +53,6 @@ typedef struct {
size_t deflate_table_size; size_t deflate_table_size;
int http1text; int http1text;
int dump_header_table; int dump_header_table;
int no_refset;
} deflate_config; } deflate_config;
static deflate_config 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) static void init_deflater(nghttp2_hd_deflater *deflater)
{ {
nghttp2_hd_deflate_init2(deflater, config.deflate_table_size); 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); nghttp2_hd_deflate_change_table_size(deflater, config.table_size);
} }
@ -387,8 +385,7 @@ OPTIONS:
buffer. buffer.
Default: 4096 Default: 4096
-d, --dump-header-table -d, --dump-header-table
Output dynamic header table. Output dynamic header table.)"
-c, --no-refset Don't use reference set.)"
<< std::endl; << std::endl;
} }
@ -397,7 +394,6 @@ static struct option long_options[] = {
{"table-size", required_argument, nullptr, 's'}, {"table-size", required_argument, nullptr, 's'},
{"deflate-table-size", required_argument, nullptr, 'S'}, {"deflate-table-size", required_argument, nullptr, 'S'},
{"dump-header-table", no_argument, nullptr, 'd'}, {"dump-header-table", no_argument, nullptr, 'd'},
{"no-refset", no_argument, nullptr, 'c'},
{nullptr, 0, nullptr, 0 } {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.deflate_table_size = NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE;
config.http1text = 0; config.http1text = 0;
config.dump_header_table = 0; config.dump_header_table = 0;
config.no_refset = 0;
while(1) { while(1) {
int option_index = 0; 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) { if(c == -1) {
break; break;
} }
@ -446,10 +441,6 @@ int main(int argc, char **argv)
// --dump-header-table // --dump-header-table
config.dump_header_table = 1; config.dump_header_table = 1;
break; break;
case 'c':
// --no-refset
config.no_refset = 1;
break;
case '?': case '?':
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
default: default:

View File

@ -162,10 +162,34 @@ namespace {
size_t DISALLOWED_HDLEN = sizeof(DISALLOWED_HD)/sizeof(DISALLOWED_HD[0]); size_t DISALLOWED_HDLEN = sizeof(DISALLOWED_HD)/sizeof(DISALLOWED_HD[0]);
} // namespace } // 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 { namespace {
const char *IGN_HD[] = { const char *IGN_HD[] = {
"connection", "connection",
"expect",
"http2-settings", "http2-settings",
"keep-alive", "keep-alive",
"proxy-connection", "proxy-connection",
@ -186,7 +210,6 @@ namespace {
const char *HTTP1_IGN_HD[] = { const char *HTTP1_IGN_HD[] = {
"connection", "connection",
"cookie", "cookie",
"expect",
"http2-settings", "http2-settings",
"keep-alive", "keep-alive",
"proxy-connection", "proxy-connection",
@ -225,6 +248,61 @@ bool check_http2_headers(const Headers& nva)
return true; return true;
} }
namespace {
template<typename InputIterator>
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) void normalize_headers(Headers& nva)
{ {
for(auto& kv : nva) { for(auto& kv : nva) {
@ -275,31 +353,12 @@ Headers::value_type to_header(const uint8_t *name, size_t namelen,
no_index); no_index);
} }
void split_add_header(Headers& nva, void add_header(Headers& nva,
const uint8_t *name, size_t namelen, const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, const uint8_t *value, size_t valuelen,
bool no_index) bool no_index)
{ {
if(valuelen == 0) {
nva.push_back(to_header(name, namelen, value, valuelen, no_index)); 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;
}
} }
const Headers::value_type* get_unique_header(const Headers& nva, 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}; 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 void copy_norm_headers_to_nva
(std::vector<nghttp2_nv>& nva, const Headers& headers) (std::vector<nghttp2_nv>& nva, const Headers& headers)
{ {

View File

@ -96,6 +96,16 @@ bool check_http2_allowed_header(const char *name);
// contains such headers. // contains such headers.
bool check_http2_headers(const Headers& nva); 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); bool name_less(const Headers::value_type& lhs, const Headers::value_type& rhs);
void normalize_headers(Headers& nva); void normalize_headers(Headers& nva);
@ -104,12 +114,10 @@ Headers::value_type to_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, const uint8_t *value, size_t valuelen,
bool no_index); bool no_index);
// Add name/value pairs to |nva|. The name is given in the |name| with // Add name/value pairs to |nva|. If |no_index| is true, this
// |namelen| bytes. This function inspects the |value| and split it // name/value pair won't be indexed when it is forwarded to the next
// using '\0' as delimiter. Each token is added to the |nva| with the // hop.
// name |name|. If |no_index| is true, this name/value pair won't be void add_header(Headers& nva,
// 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 *name, size_t namelen,
const uint8_t *value, size_t valuelen, const uint8_t *value, size_t valuelen,
bool no_index); bool no_index);
@ -143,12 +151,6 @@ bool value_lws(const Headers::value_type *nv);
// and not contain illegal characters. // and not contain illegal characters.
bool non_empty_value(const Headers::value_type *nv); 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 // Creates nghttp2_nv using |name| and |value| and returns it. The
// returned value only references the data pointer to name.c_str() and // 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 // value.c_str(). If |no_index| is true, nghttp2_nv flags member has

View File

@ -72,26 +72,18 @@ void test_http2_sort_nva(void)
check_nv({"delta", "5"}, &nva[5]); 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(); 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::add_header(nva, (const uint8_t*)"alpha", 5,
http2::split_add_header(nva, (const uint8_t*)"alpha", 5,
(const uint8_t*)"123", 3, false); (const uint8_t*)"123", 3, false);
CU_ASSERT(Headers::value_type("alpha", "123") == nva[0]); CU_ASSERT(Headers::value_type("alpha", "123") == nva[0]);
CU_ASSERT(!nva[0].no_index); CU_ASSERT(!nva[0].no_index);
nva.clear(); nva.clear();
http2::split_add_header(nva, (const uint8_t*)"alpha", 5, http2::add_header(nva, (const uint8_t*)"alpha", 5,
(const uint8_t*)"", 0, true); (const uint8_t*)"", 0, true);
CU_ASSERT(Headers::value_type("alpha", "") == nva[0]); CU_ASSERT(Headers::value_type("alpha", "") == nva[0]);
CU_ASSERT(nva[0].no_index); CU_ASSERT(nva[0].no_index);
@ -119,6 +111,21 @@ void test_http2_check_http2_headers(void)
{ "te2", "3" } { "te2", "3" }
}; };
CU_ASSERT(http2::check_http2_headers(nva3)); 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) void test_http2_get_unique_header(void)
@ -199,24 +206,12 @@ auto headers = Headers
{"zulu", "12"}}; {"zulu", "12"}};
} // namespace } // 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) void test_http2_copy_norm_headers_to_nva(void)
{ {
std::vector<nghttp2_nv> nva; std::vector<nghttp2_nv> nva;
http2::copy_norm_headers_to_nva(nva, headers); http2::copy_norm_headers_to_nva(nva, headers);
CU_ASSERT(6 == nva.size()); CU_ASSERT(7 == nva.size());
auto ans = std::vector<int>{0, 1, 4, 6, 7, 12}; auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
for(size_t i = 0; i < ans.size(); ++i) { for(size_t i = 0; i < ans.size(); ++i) {
check_nv(headers[ans[i]], &nva[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" "Alpha: 0\r\n"
"Bravo: 1\r\n" "Bravo: 1\r\n"
"Delta: 4\r\n" "Delta: 4\r\n"
"Expect: 5\r\n"
"Foxtrot: 6\r\n" "Foxtrot: 6\r\n"
"Tango: 7\r\n" "Tango: 7\r\n"
"Te: 8\r\n" "Te: 8\r\n"

View File

@ -27,13 +27,12 @@
namespace shrpx { namespace shrpx {
void test_http2_split_add_header(void); void test_http2_add_header(void);
void test_http2_sort_nva(void); void test_http2_sort_nva(void);
void test_http2_check_http2_headers(void); void test_http2_check_http2_headers(void);
void test_http2_get_unique_header(void); void test_http2_get_unique_header(void);
void test_http2_get_header(void); void test_http2_get_header(void);
void test_http2_value_lws(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_copy_norm_headers_to_nva(void);
void test_http2_build_http1_headers_from_norm_headers(void); void test_http2_build_http1_headers_from_norm_headers(void);
void test_http2_lws(void); void test_http2_lws(void);

View File

@ -1048,8 +1048,6 @@ int submit_request
return lhs.name < rhs.name; return lhs.name < rhs.name;
}); });
build_headers = http2::concat_norm_headers(std::move(build_headers));
auto nva = std::vector<nghttp2_nv>(); auto nva = std::vector<nghttp2_nv>();
nva.reserve(build_headers.size()); nva.reserve(build_headers.size());
@ -1264,7 +1262,7 @@ int on_header_callback(nghttp2_session *session,
if(!req) { if(!req) {
break; break;
} }
http2::split_add_header(req->res_nva, name, namelen, value, valuelen, http2::add_header(req->res_nva, name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX); flags & NGHTTP2_NV_FLAG_NO_INDEX);
break; break;
} }
@ -1274,7 +1272,7 @@ int on_header_callback(nghttp2_session *session,
if(!req) { if(!req) {
break; break;
} }
http2::split_add_header(req->push_req_nva, name, namelen, value, valuelen, http2::add_header(req->push_req_nva, name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX); flags & NGHTTP2_NV_FLAG_NO_INDEX);
break; break;
} }

View File

@ -71,8 +71,7 @@ int main(int argc, char* argv[])
shrpx::test_shrpx_ssl_create_lookup_tree) || shrpx::test_shrpx_ssl_create_lookup_tree) ||
!CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file", !CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file",
shrpx::test_shrpx_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", !CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) ||
shrpx::test_http2_split_add_header) ||
!CU_add_test(pSuite, "http2_sort_nva", shrpx::test_http2_sort_nva) || !CU_add_test(pSuite, "http2_sort_nva", shrpx::test_http2_sort_nva) ||
!CU_add_test(pSuite, "http2_check_http2_headers", !CU_add_test(pSuite, "http2_check_http2_headers",
shrpx::test_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) || shrpx::test_http2_get_header) ||
!CU_add_test(pSuite, "http2_value_lws", !CU_add_test(pSuite, "http2_value_lws",
shrpx::test_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", !CU_add_test(pSuite, "http2_copy_norm_headers_to_nva",
shrpx::test_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", !CU_add_test(pSuite, "http2_build_http1_headers_from_norm_headers",

View File

@ -562,9 +562,7 @@ void fill_default_config()
nghttp2_option_new(&mod_config()->http2_option); nghttp2_option_new(&mod_config()->http2_option);
nghttp2_option_set_no_auto_stream_window_update nghttp2_option_set_no_auto_window_update
(mod_config()->http2_option, 1);
nghttp2_option_set_no_auto_connection_window_update
(mod_config()->http2_option, 1); (mod_config()->http2_option, 1);
mod_config()->tls_proto_mask = 0; mod_config()->tls_proto_mask = 0;

View File

@ -46,6 +46,8 @@ Downstream::Downstream(Upstream *upstream, int stream_id, int priority)
response_body_buf_(nullptr), response_body_buf_(nullptr),
request_headers_sum_(0), request_headers_sum_(0),
response_headers_sum_(0), response_headers_sum_(0),
request_datalen_(0),
response_datalen_(0),
stream_id_(stream_id), stream_id_(stream_id),
priority_(priority), priority_(priority),
downstream_stream_id_(-1), downstream_stream_id_(-1),
@ -63,12 +65,12 @@ Downstream::Downstream(Upstream *upstream, int stream_id, int priority)
http2_settings_seen_(false), http2_settings_seen_(false),
chunked_request_(false), chunked_request_(false),
request_connection_close_(false), request_connection_close_(false),
request_expect_100_continue_(false),
request_header_key_prev_(false), request_header_key_prev_(false),
request_http2_expect_body_(false), request_http2_expect_body_(false),
chunked_response_(false), chunked_response_(false),
response_connection_close_(false), response_connection_close_(false),
response_header_key_prev_(false) response_header_key_prev_(false),
expect_final_response_(false)
{} {}
Downstream::~Downstream() Downstream::~Downstream()
@ -230,11 +232,6 @@ Headers::const_iterator Downstream::get_norm_request_header
return get_norm_header(request_headers_, name); 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) void Downstream::add_request_header(std::string name, std::string value)
{ {
request_header_key_prev_ = true; request_header_key_prev_ = true;
@ -256,7 +253,7 @@ void Downstream::split_add_request_header
bool no_index) bool no_index)
{ {
request_headers_sum_ += namelen + valuelen; request_headers_sum_ += namelen + valuelen;
http2::split_add_header(request_headers_, name, namelen, value, valuelen, http2::add_header(request_headers_, name, namelen, value, valuelen,
no_index); no_index);
} }
@ -429,11 +426,6 @@ void Downstream::set_request_http2_expect_body(bool f)
request_http2_expect_body_ = f; request_http2_expect_body_ = f;
} }
bool Downstream::get_expect_100_continue() const
{
return request_expect_100_continue_;
}
bool Downstream::get_output_buffer_full() bool Downstream::get_output_buffer_full()
{ {
if(dconn_) { if(dconn_) {
@ -463,7 +455,13 @@ int Downstream::push_upload_data_chunk(const uint8_t *data, size_t datalen)
return -1; return -1;
} }
request_bodylen_ += datalen; 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() int Downstream::end_upload_data()
@ -485,11 +483,6 @@ void Downstream::normalize_response_headers()
http2::normalize_headers(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 Headers::const_iterator Downstream::get_norm_response_header
(const std::string& name) const (const std::string& name) const
{ {
@ -551,7 +544,7 @@ void Downstream::split_add_response_header
bool no_index) bool no_index)
{ {
response_headers_sum_ += namelen + valuelen; response_headers_sum_ += namelen + valuelen;
http2::split_add_header(response_headers_, name, namelen, value, valuelen, http2::add_header(response_headers_, name, namelen, value, valuelen,
no_index); no_index);
} }
@ -754,11 +747,6 @@ void Downstream::inspect_http1_request()
if(util::strifind(hd.value.c_str(), "chunked")) { if(util::strifind(hd.value.c_str(), "chunked")) {
chunked_request_ = true; 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 bool Downstream::get_upgraded() const
{ {
return upgraded_; return upgraded_;
@ -816,4 +816,47 @@ void Downstream::set_response_rst_stream_error_code
response_rst_stream_error_code_ = 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 } // namespace shrpx

View File

@ -99,10 +99,6 @@ public:
// called after calling normalize_request_headers(). // called after calling normalize_request_headers().
Headers::const_iterator get_norm_request_header Headers::const_iterator get_norm_request_header
(const std::string& name) const; (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 add_request_header(std::string name, std::string value);
void set_last_request_header_value(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; const std::string& get_request_user_agent() const;
bool get_request_http2_expect_body() const; bool get_request_http2_expect_body() const;
void set_request_http2_expect_body(bool f); 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 push_upload_data_chunk(const uint8_t *data, size_t datalen);
int end_upload_data(); int end_upload_data();
size_t get_request_datalen() const;
void reset_request_datalen();
bool expect_response_body() const;
enum { enum {
INITIAL, INITIAL,
HEADER_COMPLETE, HEADER_COMPLETE,
@ -162,10 +160,6 @@ public:
const Headers& get_response_headers() const; const Headers& get_response_headers() const;
// Makes key lowercase and sort headers by name using < // Makes key lowercase and sort headers by name using <
void normalize_response_headers(); 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 // Returns iterator pointing to the response header with the name
// |name|. If multiple header have |name| as name, return first // |name|. If multiple header have |name| as name, return first
// occurrence from the beginning. If no such header is found, // 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); void set_response_rst_stream_error_code(nghttp2_error_code error_code);
// Inspects HTTP/1 response. This checks tranfer-encoding etc. // Inspects HTTP/1 response. This checks tranfer-encoding etc.
void inspect_http1_response(); 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 // Call this method when there is incoming data in downstream
// connection. // connection.
@ -254,6 +256,10 @@ private:
size_t request_headers_sum_; size_t request_headers_sum_;
size_t response_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 stream_id_;
int32_t priority_; int32_t priority_;
// stream ID in backend connection // stream ID in backend connection
@ -282,13 +288,13 @@ private:
bool chunked_request_; bool chunked_request_;
bool request_connection_close_; bool request_connection_close_;
bool request_expect_100_continue_;
bool request_header_key_prev_; bool request_header_key_prev_;
bool request_http2_expect_body_; bool request_http2_expect_body_;
bool chunked_response_; bool chunked_response_;
bool response_connection_close_; bool response_connection_close_;
bool response_header_key_prev_; bool response_header_key_prev_;
bool expect_final_response_;
}; };
} // namespace shrpx } // namespace shrpx

View File

@ -67,6 +67,15 @@ Http2DownstreamConnection::~Http2DownstreamConnection()
if(submit_rst_stream(downstream_) == 0) { if(submit_rst_stream(downstream_) == 0) {
http2session_->notify(); 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); http2session_->remove_downstream_connection(this);
// Downstream and DownstreamConnection may be deleted // Downstream and DownstreamConnection may be deleted
@ -122,6 +131,16 @@ void Http2DownstreamConnection::detach_downstream(Downstream *downstream)
if(submit_rst_stream(downstream) == 0) { if(submit_rst_stream(downstream) == 0) {
http2session_->notify(); 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->set_downstream_connection(nullptr);
downstream_ = nullptr; downstream_ = nullptr;
@ -244,7 +263,7 @@ int Http2DownstreamConnection::push_request_headers()
downstream_->crumble_request_cookie(); downstream_->crumble_request_cookie();
} }
downstream_->normalize_request_headers(); downstream_->normalize_request_headers();
downstream_->concat_norm_request_headers();
auto end_headers = std::end(downstream_->get_request_headers()); auto end_headers = std::end(downstream_->get_request_headers());
// 7 means: // 7 means:
@ -302,8 +321,13 @@ int Http2DownstreamConnection::push_request_headers()
http2::copy_url_component(path, &u, UF_PATH, url); http2::copy_url_component(path, &u, UF_PATH, url);
http2::copy_url_component(query, &u, UF_QUERY, url); http2::copy_url_component(query, &u, UF_QUERY, url);
if(path.empty()) { if(path.empty()) {
if(!authority.empty() &&
downstream_->get_request_method() == "OPTIONS") {
path = "*";
} else {
path = "/"; path = "/";
} }
}
if(!query.empty()) { if(!query.empty()) {
path += "?"; path += "?";
path += query; path += query;
@ -469,34 +493,29 @@ int Http2DownstreamConnection::end_upload_data()
int Http2DownstreamConnection::resume_read(IOCtrlReason reason) int Http2DownstreamConnection::resume_read(IOCtrlReason reason)
{ {
int rv1 = 0, rv2 = 0; int rv;
if(http2session_->get_state() == Http2Session::CONNECTED &&
http2session_->get_flow_control()) { if(http2session_->get_state() != Http2Session::CONNECTED ||
int32_t window_size_increment; !http2session_->get_flow_control()) {
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) {
return 0; return 0;
} }
DLOG(WARNING, this) << "Sending WINDOW_UPDATE failed";
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; return -1;
}
downstream_->reset_response_datalen();
http2session_->notify();
return 0;
} }
int Http2DownstreamConnection::on_read() int Http2DownstreamConnection::on_read()

View File

@ -58,7 +58,6 @@ Http2Session::Http2Session(event_base *evbase, SSL_CTX *ssl_ctx)
wrbev_(nullptr), wrbev_(nullptr),
rdbev_(nullptr), rdbev_(nullptr),
settings_timerev_(nullptr), settings_timerev_(nullptr),
recv_ign_window_size_(0),
fd_(-1), fd_(-1),
state_(DISCONNECTED), state_(DISCONNECTED),
notified_(false), notified_(false),
@ -661,28 +660,6 @@ int Http2Session::submit_rst_stream(int32_t stream_id,
return 0; 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, int Http2Session::submit_priority(Http2DownstreamConnection *dconn,
int32_t pri) int32_t pri)
{ {
@ -752,7 +729,6 @@ int on_stream_close_callback
(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code, (nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code,
void *user_data) void *user_data)
{ {
int rv;
auto http2session = static_cast<Http2Session*>(user_data); auto http2session = static_cast<Http2Session*>(user_data);
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
SSLOG(INFO, http2session) << "Stream stream_id=" << stream_id SSLOG(INFO, http2session) << "Stream stream_id=" << stream_id
@ -769,11 +745,14 @@ int on_stream_close_callback
if(dconn) { if(dconn) {
auto downstream = dconn->get_downstream(); auto downstream = dconn->get_downstream();
if(downstream && downstream->get_downstream_stream_id() == stream_id) { 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) { if(error_code == NGHTTP2_NO_ERROR) {
downstream->set_response_state(Downstream::MSG_COMPLETE); if(downstream->get_response_state() != Downstream::MSG_COMPLETE) {
rv = upstream->on_downstream_body_complete(downstream);
if(rv != 0) {
downstream->set_response_state(Downstream::MSG_RESET); downstream->set_response_state(Downstream::MSG_RESET);
} }
} else { } else {
@ -841,10 +820,6 @@ int on_header_callback(nghttp2_session *session,
uint8_t flags, uint8_t flags,
void *user_data) void *user_data)
{ {
if(frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
return 0;
}
auto sd = static_cast<StreamData*> auto sd = static_cast<StreamData*>
(nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if(!sd || !sd->dconn) { if(!sd || !sd->dconn) {
@ -854,6 +829,13 @@ int on_header_callback(nghttp2_session *session,
if(!downstream) { if(!downstream) {
return 0; 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(downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large header block size=" DLOG(INFO, downstream) << "Too large header block size="
@ -905,23 +887,20 @@ int on_begin_headers_callback(nghttp2_session *session,
namespace { namespace {
int on_response_headers(Http2Session *http2session, int on_response_headers(Http2Session *http2session,
Downstream *downstream,
nghttp2_session *session, nghttp2_session *session,
const nghttp2_frame *frame) const nghttp2_frame *frame)
{ {
int rv; int rv;
auto sd = static_cast<StreamData*>
(nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); auto upstream = downstream->get_upstream();
if(!sd || !sd->dconn) {
return 0;
}
auto downstream = sd->dconn->get_downstream();
if(!downstream) {
return 0;
}
downstream->normalize_response_headers(); downstream->normalize_response_headers();
auto& nva = downstream->get_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, http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);
downstream->set_response_state(Downstream::MSG_RESET); downstream->set_response_state(Downstream::MSG_RESET);
@ -935,8 +914,10 @@ int on_response_headers(Http2Session *http2session,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);
downstream->set_response_state(Downstream::MSG_RESET); downstream->set_response_state(Downstream::MSG_RESET);
call_downstream_readcb(http2session, downstream); call_downstream_readcb(http2session, downstream);
return 0; return 0;
} }
downstream->set_response_http_status(strtoul(status->value.c_str(), downstream->set_response_http_status(strtoul(status->value.c_str(),
nullptr, 10)); nullptr, 10));
downstream->set_response_major(2); downstream->set_response_major(2);
@ -952,6 +933,26 @@ int on_response_headers(Http2Session *http2session,
<< "\n" << ss.str(); << "\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"); auto content_length = http2::get_header(nva, "content-length");
if(!content_length && downstream->get_request_method() != "HEAD" && if(!content_length && downstream->get_request_method() != "HEAD" &&
downstream->get_request_method() != "CONNECT") { 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->set_response_state(Downstream::HEADER_COMPLETE);
downstream->check_upgrade_fulfilled(); downstream->check_upgrade_fulfilled();
if(downstream->get_upgraded()) { if(downstream->get_upgraded()) {
@ -1033,19 +1033,72 @@ int on_frame_recv_callback
http2session->submit_rst_stream(frame->hd.stream_id, http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_INTERNAL_ERROR); NGHTTP2_INTERNAL_ERROR);
downstream->set_response_state(Downstream::MSG_RESET); 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); call_downstream_readcb(http2session, downstream);
break; break;
} }
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS: {
auto sd = static_cast<StreamData*>
(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) { 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) { if(rv != 0) {
return rv; 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; break;
}
case NGHTTP2_RST_STREAM: { case NGHTTP2_RST_STREAM: {
auto sd = static_cast<StreamData*> auto sd = static_cast<StreamData*>
(nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); (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) { downstream->get_downstream_stream_id() == frame->hd.stream_id) {
if(downstream->get_upgraded() && if(downstream->get_upgraded() &&
downstream->get_response_state() == Downstream::HEADER_COMPLETE) { 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 // upstream *after* whole response body is sent. We just set
// MSG_COMPLETE here. Upstream will take care of that. // MSG_COMPLETE here. Upstream will take care of that.
if(LOG_ENABLED(INFO)) { 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)); (nghttp2_session_get_stream_user_data(session, stream_id));
if(!sd || !sd->dconn) { if(!sd || !sd->dconn) {
http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR); 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; return 0;
} }
auto downstream = sd->dconn->get_downstream(); 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->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; return 0;
} }
@ -1128,9 +1203,16 @@ int on_data_chunk_recv_callback(nghttp2_session *session,
rv = upstream->on_downstream_body(downstream, data, len, false); rv = upstream->on_downstream_body(downstream, data, len, false);
if(rv != 0) { if(rv != 0) {
http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR); 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->set_response_state(Downstream::MSG_RESET);
} }
downstream->add_response_datalen(len);
call_downstream_readcb(http2session, downstream); call_downstream_readcb(http2session, downstream);
return 0; return 0;
} }
@ -1470,16 +1552,21 @@ SSL* Http2Session::get_ssl() const
return ssl_; 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) { if(rv != 0) {
submit_window_update(nullptr, recv_ign_window_size_); SSLOG(WARNING, this) << "nghttp2_session_consume() returned error: "
<< nghttp2_strerror(rv);
return -1;
} }
return 0; return 0;

View File

@ -70,10 +70,6 @@ public:
int submit_rst_stream(int32_t stream_id, nghttp2_error_code error_code); 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 submit_priority(Http2DownstreamConnection *dconn, int32_t pri);
int terminate_session(nghttp2_error_code error_code); int terminate_session(nghttp2_error_code error_code);
@ -108,7 +104,7 @@ public:
SSL* get_ssl() const; SSL* get_ssl() const;
int handle_ign_data_chunk(size_t len); int consume(int32_t stream_id, size_t len);
enum { enum {
// Disconnected // Disconnected
@ -140,7 +136,6 @@ private:
bufferevent *wrbev_; bufferevent *wrbev_;
bufferevent *rdbev_; bufferevent *rdbev_;
event *settings_timerev_; event *settings_timerev_;
int32_t recv_ign_window_size_;
// fd_ is used for proxy connection and no TLS connection. For // fd_ is used for proxy connection and no TLS connection. For
// direct or TLS connection, it may be -1 even after connection is // direct or TLS connection, it may be -1 even after connection is
// established. Use bufferevent_getfd(bev_) to get file descriptor // established. Use bufferevent_getfd(bev_) to get file descriptor

View File

@ -60,12 +60,17 @@ int on_stream_close_callback
ULOG(INFO, upstream) << "Stream stream_id=" << stream_id ULOG(INFO, upstream) << "Stream stream_id=" << stream_id
<< " is being closed"; << " is being closed";
} }
auto downstream = upstream->find_downstream(stream_id); auto downstream = upstream->find_downstream(stream_id);
if(!downstream) { if(!downstream) {
return 0; return 0;
} }
upstream->consume(stream_id, downstream->get_request_datalen());
downstream->reset_request_datalen();
if(downstream->get_request_state() == Downstream::CONNECT_FAIL) { if(downstream->get_request_state() == Downstream::CONNECT_FAIL) {
upstream->remove_downstream(downstream); 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); 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) { if(upstream->error_reply(downstream, 400) != 0) {
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
} }
@ -421,6 +426,8 @@ int on_frame_recv_callback
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
downstream->end_upload_data(); downstream->end_upload_data();
downstream->set_request_state(Downstream::MSG_COMPLETE); downstream->set_request_state(Downstream::MSG_COMPLETE);
} else {
return NGHTTP2_ERR_CALLBACK_FAILURE;
} }
break; break;
@ -476,19 +483,21 @@ int on_data_chunk_recv_callback(nghttp2_session *session,
auto upstream = static_cast<Http2Upstream*>(user_data); auto upstream = static_cast<Http2Upstream*>(user_data);
auto downstream = upstream->find_downstream(stream_id); auto downstream = upstream->find_downstream(stream_id);
if(!downstream) { if(!downstream || !downstream->get_downstream_connection()) {
upstream->handle_ign_data_chunk(len); if(upstream->consume(stream_id, len) != 0) {
return 0; return NGHTTP2_ERR_CALLBACK_FAILURE;
} }
if(!downstream->get_downstream_connection()) {
upstream->handle_ign_data_chunk(len);
return 0; return 0;
} }
if(downstream->push_upload_data_chunk(data, len) != 0) { if(downstream->push_upload_data_chunk(data, len) != 0) {
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); 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; return 0;
} }
@ -586,8 +595,7 @@ nghttp2_error_code infer_upstream_rst_stream_error_code
Http2Upstream::Http2Upstream(ClientHandler *handler) Http2Upstream::Http2Upstream(ClientHandler *handler)
: handler_(handler), : handler_(handler),
session_(nullptr), session_(nullptr),
settings_timerev_(nullptr), settings_timerev_(nullptr)
recv_ign_window_size_(0)
{ {
handler->set_upstream_timeouts(&get_config()->http2_upstream_read_timeout, handler->set_upstream_timeouts(&get_config()->http2_upstream_read_timeout,
&get_config()->upstream_write_timeout); &get_config()->upstream_write_timeout);
@ -989,30 +997,6 @@ int Http2Upstream::rst_stream(Downstream *downstream,
return 0; 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 Http2Upstream::terminate_session(nghttp2_error_code error_code)
{ {
int rv; int rv;
@ -1153,15 +1137,22 @@ nghttp2_session* Http2Upstream::get_http2_session()
// nghttp2_session_recv. These calls may delete downstream. // nghttp2_session_recv. These calls may delete downstream.
int Http2Upstream::on_downstream_header_complete(Downstream *downstream) int Http2Upstream::on_downstream_header_complete(Downstream *downstream)
{ {
int rv;
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
if(downstream->get_non_final_response()) {
DLOG(INFO, downstream) << "HTTP non-final response header";
} else {
DLOG(INFO, downstream) << "HTTP response header completed"; DLOG(INFO, downstream) << "HTTP response header completed";
} }
}
downstream->normalize_response_headers(); downstream->normalize_response_headers();
if(!get_config()->http2_proxy && !get_config()->client_proxy) { if(!get_config()->http2_proxy && !get_config()->client_proxy) {
downstream->rewrite_norm_location_response_header downstream->rewrite_norm_location_response_header
(get_client_handler()->get_upstream_scheme(), get_config()->port); (get_client_handler()->get_upstream_scheme(), get_config()->port);
} }
downstream->concat_norm_response_headers();
auto end_headers = std::end(downstream->get_response_headers()); auto end_headers = std::end(downstream->get_response_headers());
size_t nheader = downstream->get_response_headers().size(); size_t nheader = downstream->get_response_headers().size();
auto nva = std::vector<nghttp2_nv>(); auto nva = std::vector<nghttp2_nv>();
@ -1172,6 +1163,26 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream)
nva.push_back(http2::make_nv_ls(":status", response_status)); nva.push_back(http2::make_nv_ls(":status", response_status));
http2::copy_norm_headers_to_nva(nva, downstream->get_response_headers()); 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"); auto via = downstream->get_norm_response_header("via");
if(get_config()->no_via) { if(get_config()->no_via) {
if(via != end_headers) { if(via != end_headers) {
@ -1192,17 +1203,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream)
} }
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
std::stringstream ss; log_response_headers(downstream, nva);
for(auto& nv : nva) {
ss << TTY_HTTP_HD;
ss.write(reinterpret_cast<const char*>(nv.name), nv.namelen);
ss << TTY_RST << ": ";
ss.write(reinterpret_cast<const char*>(nv.value), nv.valuelen);
ss << "\n";
}
ULOG(INFO, this) << "HTTP response headers. stream_id="
<< downstream->get_stream_id() << "\n"
<< ss.str();
} }
if(get_config()->http2_upstream_dump_response_header) { 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.source.ptr = downstream;
data_prd.read_callback = downstream_data_read_callback; 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(), rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
nva.data(), nva.size(), &data_prd); nva.data(), nva.size(),data_prdptr);
if(rv != 0) { if(rv != 0) {
ULOG(FATAL, this) << "nghttp2_submit_response() failed"; ULOG(FATAL, this) << "nghttp2_submit_response() failed";
return -1; return -1;
@ -1282,18 +1290,14 @@ void Http2Upstream::pause_read(IOCtrlReason reason)
int Http2Upstream::resume_read(IOCtrlReason reason, Downstream *downstream) int Http2Upstream::resume_read(IOCtrlReason reason, Downstream *downstream)
{ {
if(get_flow_control()) { if(get_flow_control()) {
int32_t window_size_increment; if(consume(downstream->get_stream_id(),
window_size_increment = http2::determine_window_update_transmission downstream->get_request_datalen()) != 0) {
(session_, 0); return -1;
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);
} }
downstream->reset_request_datalen();
} }
return send(); return send();
} }
@ -1311,19 +1315,35 @@ int Http2Upstream::on_downstream_abort_request(Downstream *downstream,
return send(); 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(rv != 0) {
ULOG(WARNING, this) << "nghttp2_session_consume() returned error: "
if(recv_ign_window_size_ >= window_size / 2) { << nghttp2_strerror(rv);
window_update(nullptr, recv_ign_window_size_); return -1;
} }
return 0; return 0;
} }
void Http2Upstream::log_response_headers
(Downstream *downstream, const std::vector<nghttp2_nv>& nva) const
{
std::stringstream ss;
for(auto& nv : nva) {
ss << TTY_HTTP_HD;
ss.write(reinterpret_cast<const char*>(nv.name), nv.namelen);
ss << TTY_RST << ": ";
ss.write(reinterpret_cast<const char*>(nv.value), nv.valuelen);
ss << "\n";
}
ULOG(INFO, this) << "HTTP response headers. stream_id="
<< downstream->get_stream_id() << "\n"
<< ss.str();
}
} // namespace shrpx } // namespace shrpx

View File

@ -60,9 +60,6 @@ public:
nghttp2_session* get_http2_session(); nghttp2_session* get_http2_session();
int rst_stream(Downstream *downstream, nghttp2_error_code error_code); 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 terminate_session(nghttp2_error_code error_code);
int error_reply(Downstream *downstream, unsigned int status_code); int error_reply(Downstream *downstream, unsigned int status_code);
@ -81,16 +78,15 @@ public:
int upgrade_upstream(HttpsUpstream *upstream); int upgrade_upstream(HttpsUpstream *upstream);
int start_settings_timer(); int start_settings_timer();
void stop_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<nghttp2_nv>& nva) const;
private: private:
DownstreamQueue downstream_queue_; DownstreamQueue downstream_queue_;
std::unique_ptr<HttpsUpstream> pre_upstream_; std::unique_ptr<HttpsUpstream> pre_upstream_;
ClientHandler *handler_; ClientHandler *handler_;
nghttp2_session *session_; nghttp2_session *session_;
event *settings_timerev_; 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_; bool flow_control_;
}; };

View File

@ -137,13 +137,23 @@ int HttpDownstreamConnection::push_request_headers()
} else if(get_config()->http2_proxy && } else if(get_config()->http2_proxy &&
!downstream_->get_request_http2_scheme().empty() && !downstream_->get_request_http2_scheme().empty() &&
!downstream_->get_request_http2_authority().empty() && !downstream_->get_request_http2_authority().empty() &&
downstream_->get_request_path().c_str()[0] == '/') { (downstream_->get_request_path().c_str()[0] == '/' ||
downstream_->get_request_path() == "*")) {
// Construct absolute-form request target because we are going to // Construct absolute-form request target because we are going to
// send a request to a HTTP/1 proxy. // send a request to a HTTP/1 proxy.
hdrs += downstream_->get_request_http2_scheme(); hdrs += downstream_->get_request_http2_scheme();
hdrs += "://"; hdrs += "://";
hdrs += downstream_->get_request_http2_authority(); hdrs += downstream_->get_request_http2_authority();
// 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(); hdrs += downstream_->get_request_path();
}
} else { } else {
// No proxy case. get_request_path() may be absolute-form but we // No proxy case. get_request_path() may be absolute-form but we
// don't care. // don't care.
@ -408,9 +418,26 @@ namespace {
int htp_hdrs_completecb(http_parser *htp) int htp_hdrs_completecb(http_parser *htp)
{ {
auto downstream = static_cast<Downstream*>(htp->data); auto downstream = static_cast<Downstream*>(htp->data);
auto upstream = downstream->get_upstream();
int rv;
downstream->set_response_http_status(htp->status_code); downstream->set_response_http_status(htp->status_code);
downstream->set_response_major(htp->http_major); downstream->set_response_major(htp->http_major);
downstream->set_response_minor(htp->http_minor); 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_connection_close(!http_should_keep_alive(htp));
downstream->set_response_state(Downstream::HEADER_COMPLETE); downstream->set_response_state(Downstream::HEADER_COMPLETE);
downstream->inspect_http1_response(); downstream->inspect_http1_response();
@ -418,15 +445,13 @@ int htp_hdrs_completecb(http_parser *htp)
if(downstream->get_upgraded()) { if(downstream->get_upgraded()) {
downstream->set_response_connection_close(true); downstream->set_response_connection_close(true);
} }
if(downstream->get_upstream()->on_downstream_header_complete(downstream) if(upstream->on_downstream_header_complete(downstream) != 0) {
!= 0) {
return -1; return -1;
} }
if(downstream->get_upgraded()) { if(downstream->get_upgraded()) {
// Upgrade complete, read until EOF in both ends // Upgrade complete, read until EOF in both ends
if(downstream->get_upstream()->resume_read(SHRPX_MSG_BLOCK, if(upstream->resume_read(SHRPX_MSG_BLOCK, downstream) != 0) {
downstream) != 0) {
return -1; return -1;
} }
downstream->set_request_state(Downstream::HEADER_COMPLETE); 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 // 304 status code with nonzero Content-Length, but without response
// body. See // body. See
// http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-20#section-3.3 // 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" || return downstream->get_request_method() == "HEAD" ||
(100 <= status && status <= 199) || status == 204 || (100 <= status && status <= 199) || status == 204 ||
status == 304 ? 1 : 0; status == 304 ? 1 : 0;
@ -505,6 +533,13 @@ namespace {
int htp_msg_completecb(http_parser *htp) int htp_msg_completecb(http_parser *htp)
{ {
auto downstream = static_cast<Downstream*>(htp->data); auto downstream = static_cast<Downstream*>(htp->data);
if(downstream->get_non_final_response()) {
downstream->reset_response();
return 0;
}
downstream->set_response_state(Downstream::MSG_COMPLETE); downstream->set_response_state(Downstream::MSG_COMPLETE);
// Block reading another response message from (broken?) // Block reading another response message from (broken?)
// server. This callback is not called if the connection is // server. This callback is not called if the connection is

View File

@ -190,16 +190,6 @@ int htp_hdrs_completecb(http_parser *htp)
auto dconn = upstream->get_client_handler()->get_downstream_connection(); 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); rv = dconn->attach_downstream(downstream);
if(rv != 0) { if(rv != 0) {
@ -416,6 +406,23 @@ int HttpsUpstream::on_write()
int rv = 0; int rv = 0;
auto downstream = get_downstream(); auto downstream = get_downstream();
if(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); rv = downstream->resume_read(SHRPX_NO_BUFFER);
} }
return rv; return rv;
@ -503,7 +510,9 @@ void https_downstream_readcb(bufferevent *bev, void *ptr)
return; 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()) { if(downstream->get_response_connection_close()) {
// Connection close // Connection close
downstream->set_downstream_connection(nullptr); downstream->set_downstream_connection(nullptr);
@ -515,6 +524,7 @@ void https_downstream_readcb(bufferevent *bev, void *ptr)
// Keep-alive // Keep-alive
dconn->detach_downstream(downstream); dconn->detach_downstream(downstream);
} }
}
if(downstream->get_request_state() == Downstream::MSG_COMPLETE) { if(downstream->get_request_state() == Downstream::MSG_COMPLETE) {
if(handler->get_should_close_after_write() && if(handler->get_should_close_after_write() &&
@ -740,8 +750,12 @@ Downstream* HttpsUpstream::pop_downstream()
int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) int HttpsUpstream::on_downstream_header_complete(Downstream *downstream)
{ {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
if(downstream->get_non_final_response()) {
DLOG(INFO, downstream) << "HTTP non-final response header";
} else {
DLOG(INFO, downstream) << "HTTP response header completed"; DLOG(INFO, downstream) << "HTTP response header completed";
} }
}
std::string hdrs = "HTTP/"; std::string hdrs = "HTTP/";
hdrs += util::utos(downstream->get_request_major()); hdrs += util::utos(downstream->get_request_major());
@ -759,6 +773,24 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream)
http2::build_http1_headers_from_norm_headers http2::build_http1_headers_from_norm_headers
(hdrs, downstream->get_response_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 // We check downstream->get_response_connection_close() in case when
// the Content-Length is not available. // the Content-Length is not available.
if(!downstream->get_request_connection_close() && if(!downstream->get_request_connection_close() &&
@ -818,17 +850,11 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream)
} }
hdrs += "\r\n"; hdrs += "\r\n";
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
const char *hdrp; log_response_headers(hdrs);
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;
} }
auto output = bufferevent_get_output(handler_->get_bev()); auto output = bufferevent_get_output(handler_->get_bev());
if(evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) { if(evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) {
ULOG(FATAL, this) << "evbuffer_add() failed"; ULOG(FATAL, this) << "evbuffer_add() failed";
@ -911,4 +937,17 @@ int HttpsUpstream::on_downstream_abort_request(Downstream *downstream,
return error_reply(status_code); 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 } // namespace shrpx

View File

@ -65,6 +65,7 @@ public:
virtual int on_downstream_body_complete(Downstream *downstream); virtual int on_downstream_body_complete(Downstream *downstream);
void reset_current_header_length(); void reset_current_header_length();
void log_response_headers(const std::string& hdrs) const;
private: private:
ClientHandler *handler_; ClientHandler *handler_;
http_parser htp_; http_parser htp_;

View File

@ -886,6 +886,15 @@ spdylay_session* SpdyUpstream::get_http2_session()
// spdylay_session_recv. These calls may delete downstream. // spdylay_session_recv. These calls may delete downstream.
int SpdyUpstream::on_downstream_header_complete(Downstream *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)) { if(LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "HTTP response header completed"; DLOG(INFO, downstream) << "HTTP response header completed";
} }

View File

@ -95,6 +95,8 @@ int main(int argc, char* argv[])
test_nghttp2_session_recv_unexpected_continuation) || test_nghttp2_session_recv_unexpected_continuation) ||
!CU_add_test(pSuite, "session_recv_settings_header_table_size", !CU_add_test(pSuite, "session_recv_settings_header_table_size",
test_nghttp2_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_continue", test_nghttp2_session_continue) ||
!CU_add_test(pSuite, "session_add_frame", !CU_add_test(pSuite, "session_add_frame",
test_nghttp2_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", test_nghttp2_hd_deflate) ||
!CU_add_test(pSuite, "hd_deflate_same_indexed_repr", !CU_add_test(pSuite, "hd_deflate_same_indexed_repr",
test_nghttp2_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", !CU_add_test(pSuite, "hd_inflate_indexed",
test_nghttp2_hd_inflate_indexed) || test_nghttp2_hd_inflate_indexed) ||
!CU_add_test(pSuite, "hd_inflate_indname_noinc", !CU_add_test(pSuite, "hd_inflate_indname_noinc",

View File

@ -61,7 +61,7 @@ static nghttp2_nv* headers(void)
return nva; 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) int32_t stream_id, nghttp2_frame_hd *hd)
{ {
CU_ASSERT(length == hd->length); CU_ASSERT(length == hd->length);
@ -223,7 +223,7 @@ void test_nghttp2_frame_pack_priority(void)
rv = nghttp2_frame_pack_priority(&bufs, &frame); rv = nghttp2_frame_pack_priority(&bufs, &frame);
CU_ASSERT(0 == rv); 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)); CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs));
check_frame_header(5, NGHTTP2_PRIORITY, NGHTTP2_FLAG_NONE, check_frame_header(5, NGHTTP2_PRIORITY, NGHTTP2_FLAG_NONE,
1000000007, &oframe.hd); 1000000007, &oframe.hd);
@ -251,7 +251,7 @@ void test_nghttp2_frame_pack_rst_stream(void)
rv = nghttp2_frame_pack_rst_stream(&bufs, &frame); rv = nghttp2_frame_pack_rst_stream(&bufs, &frame);
CU_ASSERT(0 == rv); 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)); CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs));
check_frame_header(4, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE, 1000000007, check_frame_header(4, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE, 1000000007,
&oframe.hd); &oframe.hd);
@ -384,7 +384,7 @@ void test_nghttp2_frame_pack_ping(void)
rv = nghttp2_frame_pack_ping(&bufs, &frame); rv = nghttp2_frame_pack_ping(&bufs, &frame);
CU_ASSERT(0 == rv); 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)); CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs));
check_frame_header(8, NGHTTP2_PING, NGHTTP2_FLAG_ACK, 0, &oframe.hd); check_frame_header(8, NGHTTP2_PING, NGHTTP2_FLAG_ACK, 0, &oframe.hd);
CU_ASSERT(memcmp(opaque_data, oframe.opaque_data, sizeof(opaque_data) - 1) 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); rv = nghttp2_frame_pack_goaway(&bufs, &frame);
CU_ASSERT(0 == rv); 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)); CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs));
check_frame_header(24, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, &oframe.hd); check_frame_header(24, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, &oframe.hd);
CU_ASSERT(1000000007 == oframe.last_stream_id); 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); rv = nghttp2_frame_pack_window_update(&bufs, &frame);
CU_ASSERT(0 == rv); 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)); CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs));
check_frame_header(4, NGHTTP2_WINDOW_UPDATE, NGHTTP2_FLAG_NONE, check_frame_header(4, NGHTTP2_WINDOW_UPDATE, NGHTTP2_FLAG_NONE,
1000000007, &oframe.hd); 1000000007, &oframe.hd);
@ -541,7 +542,7 @@ void test_nghttp2_frame_pack_altsvc(void)
/* Check no origin case */ /* Check no origin case */
payloadlen = NGHTTP2_ALTSVC_MINLEN + protocol_id_len + host_len; 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; oframe.payload = &oaltsvc;
@ -562,7 +563,7 @@ void test_nghttp2_frame_pack_altsvc(void)
/* Check insufficient payload length for host */ /* Check insufficient payload length for host */
payloadlen = NGHTTP2_ALTSVC_MINLEN + protocol_id_len + host_len - 1; 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; oframe.payload = &oaltsvc;
@ -579,7 +580,7 @@ void test_nghttp2_frame_pack_altsvc(void)
/* Check no host case */ /* Check no host case */
payloadlen = NGHTTP2_ALTSVC_MINLEN + protocol_id_len; 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 buf->pos[NGHTTP2_FRAME_HDLEN + NGHTTP2_ALTSVC_FIXED_PARTLEN
+ protocol_id_len] = 0; + protocol_id_len] = 0;
@ -602,7 +603,7 @@ void test_nghttp2_frame_pack_altsvc(void)
/* Check missing Host-Len */ /* Check missing Host-Len */
payloadlen = NGHTTP2_ALTSVC_FIXED_PARTLEN + protocol_id_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; oframe.payload = &oaltsvc;
@ -699,13 +700,30 @@ void test_nghttp2_iv_check(void)
iv[1].value = 3; iv[1].value = 3;
CU_ASSERT(!nghttp2_iv_check(iv, 2)); CU_ASSERT(!nghttp2_iv_check(iv, 2));
/* Undefined SETTINGS ID */ /* Undefined SETTINGS ID is allowed */
iv[1].settings_id = 1000000009; iv[1].settings_id = 1000000009;
iv[1].value = 0; iv[1].value = 0;
CU_ASSERT(!nghttp2_iv_check(iv, 2)); CU_ASSERT(nghttp2_iv_check(iv, 2));
/* Too large SETTINGS_HEADER_TABLE_SIZE */ /* Too large SETTINGS_HEADER_TABLE_SIZE */
iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
iv[1].value = UINT32_MAX; iv[1].value = UINT32_MAX;
CU_ASSERT(!nghttp2_iv_check(iv, 2)); 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));
} }

View File

@ -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_deflate_init(&deflater));
CU_ASSERT(0 == nghttp2_hd_inflate_init(&inflater)); CU_ASSERT(0 == nghttp2_hd_inflate_init(&inflater));
/* Encode 2 same headers. cookie:alpha is not in the reference set, /* Encode 2 same headers. Emit 1 literal reprs and 1 index repr. */
so first emit literal repr and then 2 emits of indexed repr. */
rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva1, ARRLEN(nva1)); rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva1, ARRLEN(nva1));
blocklen = nghttp2_bufs_len(&bufs); blocklen = nghttp2_bufs_len(&bufs);
@ -174,13 +173,12 @@ void test_nghttp2_hd_deflate_same_indexed_repr(void)
nva_out_reset(&out); nva_out_reset(&out);
nghttp2_bufs_reset(&bufs); nghttp2_bufs_reset(&bufs);
/* Encode 3 same headers. This time, cookie:alpha is in the /* Encode 3 same headers. This time, emits 3 index reprs. */
reference set, so the encoder emits indexed repr 6 times */
rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, ARRLEN(nva2)); rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, ARRLEN(nva2));
blocklen = nghttp2_bufs_len(&bufs); blocklen = nghttp2_bufs_len(&bufs);
CU_ASSERT(0 == rv); CU_ASSERT(0 == rv);
CU_ASSERT(blocklen == 6); CU_ASSERT(blocklen == 3);
CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0)); CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
CU_ASSERT(3 == out.nvlen); CU_ASSERT(3 == out.nvlen);
@ -195,120 +193,6 @@ void test_nghttp2_hd_deflate_same_indexed_repr(void)
nghttp2_hd_deflate_free(&deflater); 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) void test_nghttp2_hd_inflate_indexed(void)
{ {
nghttp2_hd_inflater inflater; nghttp2_hd_inflater inflater;
@ -410,9 +294,11 @@ void test_nghttp2_hd_inflate_indname_inc(void)
CU_ASSERT(1 == out.nvlen); CU_ASSERT(1 == out.nvlen);
assert_nv_equal(&nv, out.nva, 1); assert_nv_equal(&nv, out.nva, 1);
CU_ASSERT(1 == inflater.ctx.hd_table.len); CU_ASSERT(1 == inflater.ctx.hd_table.len);
assert_nv_equal(&nv, assert_nv_equal
&GET_TABLE_ENT(&inflater.ctx, (&nv,
inflater.ctx.hd_table.len-1)->nv, 1); &GET_TABLE_ENT
(&inflater.ctx,
NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len-1)->nv, 1);
nva_out_reset(&out); nva_out_reset(&out);
nghttp2_bufs_free(&bufs); nghttp2_bufs_free(&bufs);
@ -459,7 +345,6 @@ void test_nghttp2_hd_inflate_indname_inc_eviction(void)
nghttp2_bufs_reset(&bufs); nghttp2_bufs_reset(&bufs);
CU_ASSERT(3 == inflater.ctx.hd_table.len); 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_bufs_free(&bufs);
nghttp2_hd_inflate_free(&inflater); nghttp2_hd_inflate_free(&inflater);
@ -530,9 +415,11 @@ void test_nghttp2_hd_inflate_newname_inc(void)
CU_ASSERT(1 == out.nvlen); CU_ASSERT(1 == out.nvlen);
assert_nv_equal(&nv, out.nva, 1); assert_nv_equal(&nv, out.nva, 1);
CU_ASSERT(1 == inflater.ctx.hd_table.len); CU_ASSERT(1 == inflater.ctx.hd_table.len);
assert_nv_equal(&nv, assert_nv_equal
&GET_TABLE_ENT(&inflater.ctx, (&nv,
inflater.ctx.hd_table.len-1)->nv, 1); &GET_TABLE_ENT
(&inflater.ctx,
NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len-1)->nv, 1);
nva_out_reset(&out); nva_out_reset(&out);
nghttp2_bufs_free(&bufs); nghttp2_bufs_free(&bufs);
@ -696,8 +583,9 @@ void test_nghttp2_hd_change_table_size(void)
{ {
nghttp2_hd_deflater deflater; nghttp2_hd_deflater deflater;
nghttp2_hd_inflater inflater; nghttp2_hd_inflater inflater;
nghttp2_nv nva[] = { MAKE_NV(":method", "GET"), nghttp2_nv nva[] = { MAKE_NV("alpha", "bravo"),
MAKE_NV(":path", "/") }; MAKE_NV("charlie", "delta") };
nghttp2_nv nva2[] = { MAKE_NV(":path", "/") };
nghttp2_bufs bufs; nghttp2_bufs bufs;
ssize_t rv; ssize_t rv;
nva_out out; nva_out out;
@ -909,6 +797,36 @@ void test_nghttp2_hd_change_table_size(void)
nghttp2_hd_inflate_free(&inflater); nghttp2_hd_inflate_free(&inflater);
nghttp2_hd_deflate_free(&deflater); 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); nghttp2_bufs_free(&bufs);
} }
@ -1163,7 +1081,7 @@ void test_nghttp2_hd_deflate_bound(void)
bound = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva)); 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[0].namelen + nva[0].valuelen +
nva[1].namelen + nva[1].valuelen nva[1].namelen + nva[1].valuelen
== bound); == bound);
@ -1174,7 +1092,7 @@ void test_nghttp2_hd_deflate_bound(void)
bound2 = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva)); bound2 = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva));
CU_ASSERT(bound + 2 == bound2); CU_ASSERT(bound == bound2);
nghttp2_bufs_free(&bufs); nghttp2_bufs_free(&bufs);
nghttp2_hd_deflate_free(&deflater); nghttp2_hd_deflate_free(&deflater);

View File

@ -27,8 +27,6 @@
void test_nghttp2_hd_deflate(void); void test_nghttp2_hd_deflate(void);
void test_nghttp2_hd_deflate_same_indexed_repr(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_indexed(void);
void test_nghttp2_hd_inflate_indname_noinc(void); void test_nghttp2_hd_inflate_indname_noinc(void);
void test_nghttp2_hd_inflate_indname_inc(void); void test_nghttp2_hd_inflate_indname_inc(void);

View File

@ -33,7 +33,7 @@ static void http2(void)
{ {
const unsigned char p[] = { const unsigned char p[] = {
8, 'h', 't', 't', 'p', '/', '1', '.', '1', 8, 'h', 't', 't', 'p', '/', '1', '.', '1',
5, 'h', '2', '-', '1', '3', 5, 'h', '2', '-', '1', '4',
6, 's', 'p', 'd', 'y', '/', '3' 6, 's', 'p', 'd', 'y', '/', '3'
}; };
unsigned char outlen; unsigned char outlen;

View File

@ -450,7 +450,9 @@ void test_nghttp2_session_recv(void)
assert(nghttp2_buf_len(&bufs.cur->buf) >= 16); assert(nghttp2_buf_len(&bufs.cur->buf) >= 16);
bufs.cur->buf.last += 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); 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 */ error. This is not mandated by the spec */
ud.data_chunk_recv_cb_called = 0; ud.data_chunk_recv_cb_called = 0;
ud.frame_recv_cb_called = 0; ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, 8+4096); rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
CU_ASSERT(8+4096 == rv); CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
CU_ASSERT(0 == ud.data_chunk_recv_cb_called); CU_ASSERT(0 == ud.data_chunk_recv_cb_called);
CU_ASSERT(0 == ud.frame_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.data_chunk_recv_cb_called = 0;
ud.frame_recv_cb_called = 0; ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, 8+4096); rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
CU_ASSERT(8+4096 == rv); CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
CU_ASSERT(0 == ud.data_chunk_recv_cb_called); CU_ASSERT(0 == ud.data_chunk_recv_cb_called);
CU_ASSERT(0 == ud.frame_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.data_chunk_recv_cb_called = 0;
ud.frame_recv_cb_called = 0; ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, 8+4096); rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
CU_ASSERT(8+4096 == rv); CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
CU_ASSERT(1 == ud.data_chunk_recv_cb_called); CU_ASSERT(1 == ud.data_chunk_recv_cb_called);
CU_ASSERT(1 == ud.frame_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.data_chunk_recv_cb_called = 0;
ud.frame_recv_cb_called = 0; ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, 8+4096); rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
CU_ASSERT(8+4096 == rv); CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
/* Now we got data more than initial-window-size / 2, WINDOW_UPDATE /* Now we got data more than initial-window-size / 2, WINDOW_UPDATE
must be queued */ must be queued */
@ -676,8 +678,8 @@ void test_nghttp2_session_recv_data(void)
DATA. Additional 4 DATA frames, connection flow control will kick DATA. Additional 4 DATA frames, connection flow control will kick
in. */ in. */
for(i = 0; i < 5; ++i) { for(i = 0; i < 5; ++i) {
rv = nghttp2_session_mem_recv(session, data, 8+4096); rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
CU_ASSERT(8+4096 == rv); CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
} }
item = nghttp2_session_get_next_ob_item(session); item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item)); 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.data_chunk_recv_cb_called = 0;
ud.frame_recv_cb_called = 0; ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, 8+4096); rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
CU_ASSERT(8+4096 == rv); CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
CU_ASSERT(0 == ud.data_chunk_recv_cb_called); CU_ASSERT(0 == ud.data_chunk_recv_cb_called);
CU_ASSERT(0 == ud.frame_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); nghttp2_frame_headers_free(&frame.headers);
/* HEADERS's payload is 1 byte */ /* HEADERS's payload is 1 byte */
memcpy(data, buf->pos, 9); memcpy(data, buf->pos, NGHTTP2_FRAME_HDLEN + 1);
datalen = 9; datalen = NGHTTP2_FRAME_HDLEN + 1;
buf->pos += 9; buf->pos += NGHTTP2_FRAME_HDLEN + 1;
nghttp2_put_uint16be(data, 1); nghttp2_put_uint32be(data, (1 << 8) + data[3]);
/* First CONTINUATION, 2 bytes */ /* First CONTINUATION, 2 bytes */
cont_hd.length = 2; 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); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
CU_ASSERT(0 == rv); 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); nghttp2_frame_headers_free(&frame.headers);
buf = &bufs.head->buf; buf = &bufs.head->buf;
/* Make payload shorter than required length to store priroty /* Make payload shorter than required length to store priroty
group */ group */
nghttp2_put_uint16be(buf->pos, 4); nghttp2_put_uint32be(buf->pos, (4 << 8) + buf->pos[3]);
ud.frame_recv_cb_called = 0; 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)); assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
/* Intentionally feed payload cutting last 1 byte off */ /* 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); rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf) - 1);
CU_ASSERT((ssize_t)(nghttp2_buf_len(buf) - 1) == rv); 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); CU_ASSERT(NGHTTP2_EXT_ALTSVC == ud.recv_frame_type);
/* premature payload */ /* premature payload */
nghttp2_put_uint16be(buf->pos, 8); nghttp2_put_uint32be(buf->pos, (8 << 8) + buf->pos[3]);
ud.frame_recv_cb_called = 0; ud.frame_recv_cb_called = 0;
@ -1407,6 +1410,36 @@ void test_nghttp2_session_recv_settings_header_table_size(void)
nghttp2_session_del(session); 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) void test_nghttp2_session_continue(void)
{ {
nghttp2_session *session; nghttp2_session *session;
@ -1642,11 +1675,11 @@ void test_nghttp2_session_add_frame(void)
aux_data)); aux_data));
CU_ASSERT(0 == nghttp2_pq_empty(&session->ob_ss_pq)); CU_ASSERT(0 == nghttp2_pq_empty(&session->ob_ss_pq));
CU_ASSERT(0 == nghttp2_session_send(session)); 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) == CU_ASSERT((NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY) ==
acc.buf[3]); acc.buf[4]);
/* check stream id */ /* 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); nghttp2_session_del(session);
} }
@ -2159,6 +2192,25 @@ void test_nghttp2_session_on_settings_received(void)
nghttp2_frame_settings_free(&frame.settings); nghttp2_frame_settings_free(&frame.settings);
nghttp2_session_del(session); 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) 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, &pri_spec_default, NGHTTP2_STREAM_OPENING,
NULL); NULL);
CU_ASSERT(0 == nghttp2_submit_data(session, CU_ASSERT(0 == nghttp2_submit_data(session,
NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_STREAM, 1, &data_prd));
NGHTTP2_FLAG_END_SEGMENT, 1, &data_prd));
ud.block_count = 0; ud.block_count = 0;
CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(0 == nghttp2_session_send(session));
@ -2949,8 +3000,7 @@ void test_nghttp2_submit_data(void)
CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags);
/* frame->hd.flags has these flags */ /* frame->hd.flags has these flags */
CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT) == CU_ASSERT(NGHTTP2_FLAG_END_STREAM == data_frame->hd.flags);
data_frame->hd.flags);
nghttp2_session_del(session); nghttp2_session_del(session);
} }
@ -3433,8 +3483,9 @@ void test_nghttp2_submit_settings(void)
my_user_data ud; my_user_data ud;
nghttp2_outbound_item *item; nghttp2_outbound_item *item;
nghttp2_frame *frame; nghttp2_frame *frame;
nghttp2_settings_entry iv[6]; nghttp2_settings_entry iv[7];
nghttp2_frame ack_frame; nghttp2_frame ack_frame;
const int32_t UNKNOWN_ID = 1000000007;
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
iv[0].value = 5; 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].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
iv[3].value = 0; iv[3].value = 0;
iv[4].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; iv[4].settings_id = UNKNOWN_ID;
iv[4].value = (uint32_t)NGHTTP2_MAX_WINDOW_SIZE + 1; 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)); memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback; callbacks.send_callback = null_send_callback;
@ -3457,7 +3511,7 @@ void test_nghttp2_submit_settings(void)
nghttp2_session_server_new(&session, &callbacks, &ud); nghttp2_session_server_new(&session, &callbacks, &ud);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == 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 */ /* Make sure that local settings are not changed */
CU_ASSERT(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS == CU_ASSERT(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS ==
@ -3465,15 +3519,15 @@ void test_nghttp2_submit_settings(void)
CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE ==
session->local_settings.initial_window_size); session->local_settings.initial_window_size);
/* Now sends without 5th one */ /* Now sends without 6th one */
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 4)); CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 5));
item = nghttp2_session_get_next_ob_item(session); item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_SETTINGS == OB_CTRL_TYPE(item)); CU_ASSERT(NGHTTP2_SETTINGS == OB_CTRL_TYPE(item));
frame = item->frame; 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(5 == frame->settings.iv[0].value);
CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS == CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS ==
frame->settings.iv[0].settings_id); frame->settings.iv[0].settings_id);
@ -3482,6 +3536,9 @@ void test_nghttp2_submit_settings(void)
CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE == CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE ==
frame->settings.iv[1].settings_id); 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; ud.frame_send_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == ud.frame_send_cb_called); 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_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)); memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
nghttp2_session_client_new2(&session, &callbacks, NULL, option); 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_WINDOW_UPDATE);
CU_ASSERT(!(session->opt_flags &
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE));
nghttp2_session_del(session);
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_session_del(session);
nghttp2_option_set_peer_max_concurrent_streams(option, 100); nghttp2_option_set_peer_max_concurrent_streams(option, 100);

View File

@ -37,6 +37,7 @@ void test_nghttp2_session_recv_altsvc(void);
void test_nghttp2_session_recv_unknown_frame(void); void test_nghttp2_session_recv_unknown_frame(void);
void test_nghttp2_session_recv_unexpected_continuation(void); void test_nghttp2_session_recv_unexpected_continuation(void);
void test_nghttp2_session_recv_settings_header_table_size(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_continue(void);
void test_nghttp2_session_add_frame(void); void test_nghttp2_session_add_frame(void);
void test_nghttp2_session_on_request_headers_received(void); void test_nghttp2_session_on_request_headers_received(void);