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
------------------
We started to implement h2-13
(http://tools.ietf.org/html/draft-ietf-httpbis-http2-13) and the
header compression
(http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08).
We started to implement h2-14
(http://tools.ietf.org/html/draft-ietf-httpbis-http2-14), the header
compression
(http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09)
and HTTP Alternative Services
(http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-02).
The nghttp2 code base was forked from spdylay project.
@ -30,15 +32,9 @@ HTTP/2 Features Support
Core frames handling Yes
Dependency Tree Yes
Large header (CONTINUATION) Yes
ALTSVC extension Yes \*1
ALTSVC extension Yes
=========================== =======
* \*1 As described in draft-12, but reserved byte was removed. ALTSVC
may be removed from nghttp2 public API since it is not stabilized
yet.
BLOCKED frame, which once existed in h2-12, was removed in h2-13.
Public Test Server
------------------
@ -47,7 +43,7 @@ implementation.
* https://nghttp2.org/ (TLS + NPN)
NPN offer ``h2-13``, ``spdy/3.1`` and ``http/1.1``.
NPN offer ``h2-14``, ``spdy/3.1`` and ``http/1.1``.
ALPN is currently disabled.
This endpoint requires TLSv1.2 and DHE or EDCHE with GCM cipher
@ -186,10 +182,10 @@ output from ``nghttp`` client::
$ src/nghttp -nv https://nghttp2.org
[ 0.033][NPN] server offers:
* h2-13
* h2-14
* spdy/3.1
* http/1.1
The negotiated protocol: h2-13
The negotiated protocol: h2-14
[ 0.068] send SETTINGS frame <length=15, flags=0x00, stream_id=0>
(niv=3)
[SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
@ -281,7 +277,7 @@ The HTTP Upgrade is performed like this::
[SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
[SETTINGS_INITIAL_WINDOW_SIZE(4):65535]
[ 0.024] recv ALTSVC frame <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>
; ACK
(niv=0)
@ -389,7 +385,7 @@ information. Here is sample output from ``nghttpd`` server::
nghttpx - proxy
+++++++++++++++
``nghttpx`` is a multi-threaded reverse proxy for ``h2-13``, SPDY and
``nghttpx`` is a multi-threaded reverse proxy for ``h2-14``, SPDY and
HTTP/1.1 and powers nghttp2.org site. It has several operation modes:
================== ============================ ============== =============
@ -403,7 +399,7 @@ default mode HTTP/2, SPDY, HTTP/1.1 (TLS) HTTP/1.1 Reverse proxy
================== ============================ ============== =============
The interesting mode at the moment is the default mode. It works like
a reverse proxy and listens for ``h2-13``, SPDY and HTTP/1.1 and can
a reverse proxy and listens for ``h2-14``, SPDY and HTTP/1.1 and can
be deployed SSL/TLS terminator for existing web server.
The default mode, ``--http2-proxy`` and ``--http2-bridge`` modes use

View File

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

View File

@ -41,7 +41,7 @@ extern "C" {
* The protocol version identification string of this library
* supports. This identifier is used if HTTP/2 is used over TLS.
*/
#define NGHTTP2_PROTO_VERSION_ID "h2-13"
#define NGHTTP2_PROTO_VERSION_ID "h2-14"
/**
* @macro
*
@ -56,7 +56,7 @@ extern "C" {
* supports. This identifier is used if HTTP/2 is used over cleartext
* TCP.
*/
#define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "h2c-13"
#define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "h2c-14"
/**
* @macro
@ -476,10 +476,6 @@ typedef enum {
* The ACK flag.
*/
NGHTTP2_FLAG_ACK = 0x01,
/**
* The END_SEGMENT flag.
*/
NGHTTP2_FLAG_END_SEGMENT = 0x02,
/**
* The PADDED flag.
*/
@ -510,7 +506,15 @@ typedef enum {
/**
* SETTINGS_INITIAL_WINDOW_SIZE
*/
NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 0x04
NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 0x04,
/**
* SETTINGS_MAX_FRAME_SIZE
*/
NGHTTP2_SETTINGS_MAX_FRAME_SIZE = 0x05,
/**
* SETTINGS_MAX_HEADER_LIST_SIZE
*/
NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 0x06
} nghttp2_settings_id;
/* Note: If we add SETTINGS, update the capacity of
NGHTTP2_INBOUND_NUM_IV as well */
@ -724,7 +728,9 @@ typedef enum {
NGHTTP2_HCAT_PUSH_RESPONSE = 2,
/**
* The HEADERS frame which does not apply for the above categories,
* which is analogous to HEADERS in SPDY.
* which is analogous to HEADERS in SPDY. If non-final response
* (e.g., status 1xx) is used, final response HEADERS frame will be
* categorized here.
*/
NGHTTP2_HCAT_HEADERS = 3
} nghttp2_headers_category;
@ -1505,31 +1511,17 @@ int nghttp2_option_new(nghttp2_option **option_ptr);
*/
void nghttp2_option_del(nghttp2_option *option);
/**
* @function
*
* This option prevents the library from sending WINDOW_UPDATE for a
* stream automatically. If this option is set to nonzero, the
* library won't send WINDOW_UPDATE for a stream and the application
* is responsible for sending WINDOW_UPDATE using
* `nghttp2_submit_window_update`. By default, this option is set to
* zero.
*/
void nghttp2_option_set_no_auto_stream_window_update(nghttp2_option *option,
int val);
/**
* @function
*
* This option prevents the library from sending WINDOW_UPDATE for a
* connection automatically. If this option is set to nonzero, the
* library won't send WINDOW_UPDATE for a connection and the
* application is responsible for sending WINDOW_UPDATE with stream ID
* 0 using `nghttp2_submit_window_update`. By default, this option is
* set to zero.
* library won't send WINDOW_UPDATE for DATA until application calls
* `nghttp2_session_consume()` to indicate the consumed amount of
* data. Don't use `nghttp2_submit_window_update()` for this purpose.
* By default, this option is set to zero.
*/
void nghttp2_option_set_no_auto_connection_window_update
(nghttp2_option *option, int val);
void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val);
/**
* @function
@ -2067,6 +2059,28 @@ int nghttp2_session_terminate_session2(nghttp2_session *session,
uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session,
nghttp2_settings_id id);
/**
* @function
*
* Tells the |session| that |size| bytes for a stream denoted by
* |stream_id| were consumed by application and are ready to
* WINDOW_UPDATE. This function is intended to be used without
* automatic window update (see
* `nghttp2_option_set_no_auto_window_update()`).
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* :enum:`NGHTTP2_ERR_NOMEM`
* Out of memory.
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* The |stream_id| is 0.
* :enum:`NGHTTP2_ERR_INVALID_STATE`
* Automatic WINDOW_UPDATE is not disabled.
*/
int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id,
size_t size);
/**
* @function
*
@ -2366,8 +2380,7 @@ int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
* Submits one or more DATA frames to the stream |stream_id|. The
* data to be sent are provided by |data_prd|. If |flags| contains
* :enum:`NGHTTP2_FLAG_END_STREAM`, the last DATA frame has END_STREAM
* flag set. If |flags| contains :enum:`NGHTTP2_FLAG_END_SEGMENT`,
* the last DATA frame has END_SEGMENT flag set.
* flag set.
*
* This function does not take ownership of the |data_prd|. The
* function copies the members of the |data_prd|.
@ -2609,12 +2622,11 @@ int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags,
* difference.
*
* If the |window_size_increment| is negative, the local window size
* is decreased by -|window_size_increment|. If
* :enum:`NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE` (or
* :enum:`NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE` if |stream_id|
* is 0) is not set and the library decided that the WINDOW_UPDATE
* should be submitted, then WINDOW_UPDATE is queued with the current
* received bytes count.
* is decreased by -|window_size_increment|. If automatic
* WINDOW_UPDATE is enabled
* (`nghttp2_option_set_no_auto_window_update()`), and the library
* decided that the WINDOW_UPDATE should be submitted, then
* WINDOW_UPDATE is queued with the current received bytes count.
*
* If the |window_size_increment| is 0, the function does nothing and
* returns 0.
@ -2641,7 +2653,7 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
*
* Only the server can send the ALTSVC frame. If |session| is
* initialized as client, this function fails and returns
* :enum:`NGHTTP2_ERR_INVALID_STATE`.
* :enum:`NGHTTP2_ERR_PROTO`.
*
* If the |protocol_id_len| is 0, the |protocol_id| could be ``NULL``.
*
@ -2819,18 +2831,6 @@ int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr,
*/
void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater);
/**
* @function
*
* Sets the availability of reference set in the |deflater|. If
* |no_refset| is nonzero, the deflater will first emit "Reference Set
* Emptying" in the each subsequent invocation of
* `nghttp2_hd_deflate_hd()` to clear up reference set. By default,
* the deflater uses reference set.
*/
void nghttp2_hd_deflate_set_no_refset(nghttp2_hd_deflater *deflater,
uint8_t no_refset);
/**
* @function
*

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)
{
nghttp2_put_uint16be(&buf[0], hd->length);
buf[2]= hd->type;
buf[3] = hd->flags;
nghttp2_put_uint32be(&buf[4], hd->stream_id);
nghttp2_put_uint32be(&buf[0], (uint32_t)(hd->length << 8));
buf[3]= hd->type;
buf[4] = hd->flags;
nghttp2_put_uint32be(&buf[5], hd->stream_id);
}
void nghttp2_frame_unpack_frame_hd(nghttp2_frame_hd *hd, const uint8_t* buf)
{
hd->length = nghttp2_get_uint16(&buf[0]) & NGHTTP2_FRAME_LENGTH_MASK;
hd->type = buf[2];
hd->flags = buf[3];
hd->stream_id = nghttp2_get_uint32(&buf[4]) & NGHTTP2_STREAM_ID_MASK;
hd->length = nghttp2_get_uint32(&buf[0]) >> 8;
hd->type = buf[3];
hd->flags = buf[4];
hd->stream_id = nghttp2_get_uint32(&buf[5]) & NGHTTP2_STREAM_ID_MASK;
}
static void frame_set_hd(nghttp2_frame_hd *hd, uint16_t length,
static void frame_set_hd(nghttp2_frame_hd *hd, size_t length,
uint8_t type, uint8_t flags,
int32_t stream_id)
{
@ -231,11 +231,10 @@ void nghttp2_frame_data_init(nghttp2_data *frame, nghttp2_private_data *pdata)
{
frame->hd = pdata->hd;
frame->padlen = pdata->padlen;
/* flags may have NGHTTP2_FLAG_END_STREAM or
NGHTTP2_FLAG_END_SEGMENT even if the sent chunk is not the end of
the stream */
/* flags may have NGHTTP2_FLAG_END_STREAM even if the sent chunk is
not the end of the stream */
if(!pdata->eof) {
frame->hd.flags &= ~(NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT);
frame->hd.flags &= ~NGHTTP2_FLAG_END_STREAM;
}
}
@ -1037,8 +1036,14 @@ int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv)
return 0;
}
break;
default:
return 0;
case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
if(iv[i].value < NGHTTP2_MAX_FRAME_SIZE_MIN ||
iv[i].value > NGHTTP2_MAX_FRAME_SIZE_MAX) {
return 0;
}
break;
case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
break;
}
}
return 1;
@ -1047,6 +1052,7 @@ int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv)
static void frame_set_pad(nghttp2_buf *buf, size_t padlen)
{
size_t trail_padlen;
size_t newlen;
DEBUGF(fprintf(stderr, "send: padlen=%zu, shift left 1 bytes\n", padlen));
@ -1054,9 +1060,10 @@ static void frame_set_pad(nghttp2_buf *buf, size_t padlen)
--buf->pos;
buf->pos[3] |= NGHTTP2_FLAG_PADDED;
buf->pos[4] |= NGHTTP2_FLAG_PADDED;
nghttp2_put_uint16be(buf->pos, nghttp2_get_uint16(buf->pos) + padlen);
newlen = (nghttp2_get_uint32(buf->pos) >> 8) + padlen;
nghttp2_put_uint32be(buf->pos, (uint32_t)((newlen << 8) + buf->pos[3]));
trail_padlen = padlen - 1;
buf->pos[NGHTTP2_FRAME_HDLEN] = trail_padlen;
@ -1087,12 +1094,12 @@ int nghttp2_frame_add_pad(nghttp2_bufs *bufs, nghttp2_frame_hd *hd,
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |Frame header | Frame payload... :
* +-+---------------+---------------------------------------------+
* | |Frame header | Frame payload... :
* +-+---------------+---------------------------------------------+
* | |Frame header | Frame payload... :
* +-+---------------+---------------------------------------------+
* | |Frame header | Frame payload... :
* +-+-----------------+-------------------------------------------+
* | |Frame header | Frame payload... :
* +-+-----------------+-------------------------------------------+
* | |Frame header | Frame payload... :
* +-+-----------------+-------------------------------------------+
*
* We arranged padding so that it is included in the first frame
* completely. For padded frame, we are going to adjust buf->pos of

View File

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

View File

@ -100,7 +100,7 @@ static nghttp2_hd_entry static_table[] = {
MAKE_ENT("www-authenticate", "", 4051929931u, 0u),
};
static const size_t STATIC_TABLE_LENGTH =
const size_t NGHTTP2_STATIC_TABLE_LENGTH =
sizeof(static_table)/sizeof(static_table[0]);
static int memeq(const void *s1, const void *s2, size_t n)
@ -281,11 +281,9 @@ static void hd_ringbuf_pop_back(nghttp2_hd_ringbuf *ringbuf)
--ringbuf->len;
}
static int hd_context_init(nghttp2_hd_context *context,
nghttp2_hd_role role)
static int hd_context_init(nghttp2_hd_context *context)
{
int rv;
context->role = role;
context->bad = 0;
context->hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE;
rv = hd_ringbuf_init
@ -314,11 +312,10 @@ int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater,
size_t deflate_hd_table_bufsize_max)
{
int rv;
rv = hd_context_init(&deflater->ctx, NGHTTP2_HD_ROLE_DEFLATE);
rv = hd_context_init(&deflater->ctx);
if(rv != 0) {
return rv;
}
deflater->no_refset = 0;
if(deflate_hd_table_bufsize_max < NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE) {
deflater->notify_table_size_change = 1;
@ -328,6 +325,7 @@ int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater,
}
deflater->deflate_hd_table_bufsize_max = deflate_hd_table_bufsize_max;
deflater->min_hd_table_bufsize_max = UINT32_MAX;
return 0;
}
@ -336,7 +334,7 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater)
{
int rv;
rv = hd_context_init(&inflater->ctx, NGHTTP2_HD_ROLE_INFLATE);
rv = hd_context_init(&inflater->ctx);
if(rv != 0) {
goto fail;
}
@ -346,7 +344,6 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater)
inflater->ent_keep = NULL;
inflater->nv_keep = NULL;
inflater->end_headers_index = 0;
inflater->opcode = NGHTTP2_HD_OPCODE_NONE;
inflater->state = NGHTTP2_HD_STATE_OPCODE;
@ -364,7 +361,6 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater)
inflater->newnamelen = 0;
inflater->index_required = 0;
inflater->no_index = 0;
inflater->ent_name = NULL;
return 0;
@ -400,12 +396,6 @@ void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater)
hd_context_free(&inflater->ctx);
}
void nghttp2_hd_deflate_set_no_refset(nghttp2_hd_deflater *deflater,
uint8_t no_refset)
{
deflater->no_refset = no_refset;
}
static size_t entry_room(size_t namelen, size_t valuelen)
{
return NGHTTP2_HD_ENTRY_OVERHEAD + namelen + valuelen;
@ -418,10 +408,8 @@ static int emit_indexed_header(nghttp2_nv *nv_out, nghttp2_hd_entry *ent)
DEBUGF(fprintf(stderr, ": "));
DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr));
DEBUGF(fprintf(stderr, "\n"));
/* ent->ref may be 0. This happens if the careless stupid encoder
emits literal block larger than header table capacity with
indexing. */
ent->flags |= NGHTTP2_HD_FLAG_EMIT;
/* ent->ref may be 0. This happens if the encoder emits literal
block larger than header table capacity with indexing. */
*nv_out = ent->nv;
return 0;
}
@ -462,15 +450,19 @@ static size_t encode_length(uint8_t *buf, size_t n, size_t prefix)
{
size_t k = (1 << prefix) - 1;
size_t len = 0;
*buf &= ~k;
if(n >= k) {
*buf++ |= k;
n -= k;
++len;
} else {
if(n < k) {
*buf++ |= n;
return 1;
}
*buf++ |= k;
n -= k;
++len;
do {
++len;
if(n >= 128) {
@ -561,20 +553,6 @@ static ssize_t decode_length(uint32_t *res, size_t *shift_ptr, int *final,
return in + 1 - start;
}
static int emit_clear_refset(nghttp2_bufs *bufs)
{
int rv;
DEBUGF(fprintf(stderr, "deflatehd: emit clear refset\n"));
rv = nghttp2_bufs_addb(bufs, 0x30u);
if(rv != 0) {
return rv;
}
return 0;
}
static int emit_table_size(nghttp2_bufs *bufs, size_t table_size)
{
int rv;
@ -584,7 +562,7 @@ static int emit_table_size(nghttp2_bufs *bufs, size_t table_size)
DEBUGF(fprintf(stderr, "deflatehd: emit table_size=%zu\n", table_size));
blocklen = count_encoded_length(table_size, 4);
blocklen = count_encoded_length(table_size, 5);
if(sizeof(sb) < blocklen) {
return NGHTTP2_ERR_HEADER_COMP;
@ -594,7 +572,7 @@ static int emit_table_size(nghttp2_bufs *bufs, size_t table_size)
*bufp = 0x20u;
encode_length(bufp, table_size, 4);
encode_length(bufp, table_size, 5);
rv = nghttp2_bufs_add(bufs, sb, blocklen);
if(rv != 0) {
@ -632,14 +610,22 @@ static int emit_indexed_block(nghttp2_bufs *bufs, size_t idx)
return 0;
}
static int emit_string(nghttp2_bufs *bufs,
size_t enclen, int huffman,
const uint8_t *str, size_t len)
static int emit_string(nghttp2_bufs *bufs, const uint8_t *str, size_t len)
{
int rv;
uint8_t sb[16];
uint8_t *bufp;
size_t blocklen;
size_t enclen;
int huffman = 0;
enclen = nghttp2_hd_huff_encode_count(str, len);
if(enclen < len) {
huffman = 1;
} else {
enclen = len;
}
blocklen = count_encoded_length(enclen, 7);
@ -691,9 +677,7 @@ static int emit_indname_block(nghttp2_bufs *bufs, size_t idx,
{
int rv;
uint8_t *bufp;
size_t encvallen;
size_t blocklen;
int huffman;
uint8_t sb[16];
size_t prefixlen;
int no_index;
@ -711,13 +695,7 @@ static int emit_indname_block(nghttp2_bufs *bufs, size_t idx,
"indexing=%d, no_index=%d\n",
idx, nv->valuelen, inc_indexing, no_index));
encvallen = nghttp2_hd_huff_encode_count(nv->value, nv->valuelen);
blocklen = count_encoded_length(idx + 1, prefixlen);
huffman = encvallen < nv->valuelen;
if(!huffman) {
encvallen = nv->valuelen;
}
if(sizeof(sb) < blocklen) {
return NGHTTP2_ERR_HEADER_COMP;
@ -734,7 +712,7 @@ static int emit_indname_block(nghttp2_bufs *bufs, size_t idx,
return rv;
}
rv = emit_string(bufs, encvallen, huffman, nv->value, nv->valuelen);
rv = emit_string(bufs, nv->value, nv->valuelen);
if(rv != 0) {
return rv;
}
@ -746,10 +724,6 @@ static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv,
int inc_indexing)
{
int rv;
size_t encnamelen;
size_t encvallen;
int name_huffman;
int value_huffman;
int no_index;
no_index = (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0;
@ -759,29 +733,17 @@ static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv,
"indexing=%d, no_index=%d\n",
nv->namelen, nv->valuelen, inc_indexing, no_index));
encnamelen = nghttp2_hd_huff_encode_count(nv->name, nv->namelen);
encvallen = nghttp2_hd_huff_encode_count(nv->value, nv->valuelen);
name_huffman = encnamelen < nv->namelen;
value_huffman = encvallen < nv->valuelen;
if(!name_huffman) {
encnamelen = nv->namelen;
}
if(!value_huffman) {
encvallen = nv->valuelen;
}
rv = nghttp2_bufs_addb(bufs, pack_first_byte(inc_indexing, no_index));
if(rv != 0) {
return rv;
}
rv = emit_string(bufs, encnamelen, name_huffman, nv->name, nv->namelen);
rv = emit_string(bufs, nv->name, nv->namelen);
if(rv != 0) {
return rv;
}
rv = emit_string(bufs, encvallen, value_huffman, nv->value, nv->valuelen);
rv = emit_string(bufs, nv->value, nv->valuelen);
if(rv != 0) {
return rv;
}
@ -789,23 +751,6 @@ static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv,
return 0;
}
/*
* Emit common header with |index| by toggle off and on (thus 2
* indexed representation emissions).
*/
static int emit_implicit(nghttp2_bufs *bufs, size_t idx)
{
int i, rv;
for(i = 0; i < 2; ++i) {
rv = emit_indexed_block(bufs, idx);
if(rv != 0) {
return rv;
}
}
return 0;
}
static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context,
nghttp2_bufs *bufs,
const nghttp2_nv *nv,
@ -824,17 +769,7 @@ static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context,
nghttp2_hd_entry* ent = hd_ringbuf_get(&context->hd_table, idx);
context->hd_table_bufsize -= entry_room(ent->nv.namelen, ent->nv.valuelen);
if(context->role == NGHTTP2_HD_ROLE_DEFLATE) {
if(ent->flags & NGHTTP2_HD_FLAG_IMPLICIT_EMIT) {
/* Emit common header just before it slips away from the
table. If we don't do this, we have to emit it in literal
representation which hurts compression. */
rv = emit_implicit(bufs, idx);
if(rv != 0) {
return NULL;
}
}
}
DEBUGF(fprintf(stderr, "hpack: remove item from header table: "));
DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr));
DEBUGF(fprintf(stderr, ": "));
@ -882,8 +817,6 @@ static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context,
}
context->hd_table_bufsize += room;
new_ent->flags |= NGHTTP2_HD_FLAG_REFSET;
}
return new_ent;
}
@ -913,39 +846,42 @@ static search_result search_hd_table(nghttp2_hd_context *context,
uint32_t value_hash = hash(nv->value, nv->valuelen);
int use_index = (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) == 0;
if(use_index) {
for(i = 0; i < context->hd_table.len; ++i) {
nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, i);
if(ent->name_hash == name_hash && name_eq(&ent->nv, nv)) {
if(res.index == -1) {
res.index = (ssize_t)i;
}
if(ent->value_hash == value_hash && value_eq(&ent->nv, nv)) {
res.index = (ssize_t)i;
res.name_value_match = 1;
return res;
}
}
}
}
for(i = 0; i < STATIC_TABLE_LENGTH; ++i) {
for(i = 0; i < NGHTTP2_STATIC_TABLE_LENGTH; ++i) {
nghttp2_hd_entry *ent = &static_table[i];
if(ent->name_hash != name_hash || !name_eq(&ent->nv, nv)) {
continue;
}
if(res.index == -1) {
res.index = (ssize_t)(context->hd_table.len + i);
res.index = (ssize_t)i;
}
if(use_index &&
ent->value_hash == value_hash && value_eq(&ent->nv, nv)) {
res.index = (ssize_t)(context->hd_table.len + i);
res.index = (ssize_t)i;
res.name_value_match = 1;
return res;
}
}
if(!use_index) {
return res;
}
for(i = 0; i < context->hd_table.len; ++i) {
nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, i);
if(ent->name_hash == name_hash && name_eq(&ent->nv, nv)) {
if(res.index == -1) {
res.index = (ssize_t)i + NGHTTP2_STATIC_TABLE_LENGTH;
}
if(ent->value_hash == value_hash && value_eq(&ent->nv, nv)) {
res.index = (ssize_t)i + NGHTTP2_STATIC_TABLE_LENGTH;
res.name_value_match = 1;
return res;
}
}
}
return res;
}
@ -972,6 +908,9 @@ int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater,
deflater->ctx.hd_table_bufsize_max = next_bufsize;
deflater->min_hd_table_bufsize_max =
nghttp2_min(deflater->min_hd_table_bufsize_max, next_bufsize);
deflater->notify_table_size_change = 1;
hd_context_shrink_table_size(&deflater->ctx);
@ -987,31 +926,22 @@ int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater,
return 0;
}
static void clear_refset(nghttp2_hd_context *context)
{
size_t i;
for(i = 0; i < context->hd_table.len; ++i) {
nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, i);
ent->flags &= ~NGHTTP2_HD_FLAG_REFSET;
}
}
#define INDEX_RANGE_VALID(context, idx) \
((idx) < (context)->hd_table.len + STATIC_TABLE_LENGTH)
((idx) < (context)->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH)
static size_t get_max_index(nghttp2_hd_context *context)
{
return context->hd_table.len + STATIC_TABLE_LENGTH - 1;
return context->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH - 1;
}
nghttp2_hd_entry* nghttp2_hd_table_get(nghttp2_hd_context *context,
size_t idx)
{
assert(INDEX_RANGE_VALID(context, idx));
if(idx < context->hd_table.len) {
return hd_ringbuf_get(&context->hd_table, idx);
if(idx >= NGHTTP2_STATIC_TABLE_LENGTH) {
return hd_ringbuf_get(&context->hd_table, idx - NGHTTP2_STATIC_TABLE_LENGTH);
} else {
return &static_table[idx - context->hd_table.len];
return &static_table[idx];
}
}
@ -1042,8 +972,9 @@ static int deflate_nv(nghttp2_hd_deflater *deflater,
nghttp2_bufs *bufs, const nghttp2_nv *nv)
{
int rv;
nghttp2_hd_entry *ent;
search_result res;
ssize_t idx = -1;
int incidx = 0;
DEBUGF(fprintf(stderr, "deflatehd: deflating "));
DEBUGF(fwrite(nv->name, nv->namelen, 1, stderr));
@ -1053,142 +984,55 @@ static int deflate_nv(nghttp2_hd_deflater *deflater,
res = search_hd_table(&deflater->ctx, nv);
if(res.index != -1 && res.name_value_match) {
size_t idx = res.index;
idx = res.index;
DEBUGF(fprintf(stderr, "deflatehd: name/value match index=%zd\n",
res.index));
if(res.name_value_match) {
ent = nghttp2_hd_table_get(&deflater->ctx, idx);
if(idx >= deflater->ctx.hd_table.len) {
nghttp2_hd_entry *new_ent;
/* It is important to first add entry to the header table and
let eviction go. If NGHTTP2_HD_FLAG_IMPLICIT_EMIT entry is
evicted, it must be emitted before the |nv|. */
new_ent = add_hd_table_incremental(&deflater->ctx, bufs, &ent->nv,
NGHTTP2_HD_FLAG_NONE);
if(!new_ent) {
return NGHTTP2_ERR_HEADER_COMP;
}
if(new_ent->ref == 0) {
nghttp2_hd_entry_free(new_ent);
free(new_ent);
new_ent = NULL;
} else {
/* new_ent->ref > 0 means that new_ent is in the reference
set */
new_ent->flags |= NGHTTP2_HD_FLAG_EMIT;
}
rv = emit_indexed_block(bufs, idx);
if(rv != 0) {
return rv;
}
} else if((ent->flags & NGHTTP2_HD_FLAG_REFSET) == 0) {
ent->flags |= NGHTTP2_HD_FLAG_REFSET | NGHTTP2_HD_FLAG_EMIT;
rv = emit_indexed_block(bufs, idx);
if(rv != 0) {
return rv;
}
} else {
int num_emits = 0;
if(ent->flags & NGHTTP2_HD_FLAG_EMIT) {
/* occurrences of the same indexed representation. Emit index
twice. */
num_emits = 2;
} else if(ent->flags & NGHTTP2_HD_FLAG_IMPLICIT_EMIT) {
/* ent was implicitly emitted because it is the common
header field. To support occurrences of the same indexed
representation, we have to emit 4 times. This is because
"implicitly emitted" means actually not emitted at
all. So first 2 emits performs 1st header appears in the
reference set. And another 2 emits are done for 2nd
(current) header. */
ent->flags ^= NGHTTP2_HD_FLAG_IMPLICIT_EMIT;
ent->flags |= NGHTTP2_HD_FLAG_EMIT;
num_emits = 4;
} else {
/* This is common header and not emitted in the current
run. Just mark IMPLICIT_EMIT, in the hope that we are not
required to emit anything for this. We will emit toggle
off/on for this entry if it is removed from the header
table. */
ent->flags |= NGHTTP2_HD_FLAG_IMPLICIT_EMIT;
}
for(; num_emits > 0; --num_emits) {
rv = emit_indexed_block(bufs, idx);
if(rv != 0) {
return rv;
}
}
}
} else {
ssize_t idx = -1;
int incidx = 0;
if(res.index != -1) {
DEBUGF(fprintf(stderr, "deflatehd: name match index=%zd\n",
res.index));
idx = res.index;
}
if(hd_deflate_should_indexing(deflater, nv)) {
nghttp2_hd_entry *new_ent;
if(idx >= (ssize_t)deflater->ctx.hd_table.len) {
nghttp2_nv nv_indname;
nv_indname = *nv;
nv_indname.name = nghttp2_hd_table_get(&deflater->ctx, idx)->nv.name;
new_ent = add_hd_table_incremental(&deflater->ctx, bufs, &nv_indname,
NGHTTP2_HD_FLAG_VALUE_ALLOC);
} else {
new_ent = add_hd_table_incremental(&deflater->ctx, bufs, nv,
NGHTTP2_HD_FLAG_NAME_ALLOC |
NGHTTP2_HD_FLAG_VALUE_ALLOC);
}
if(!new_ent) {
return NGHTTP2_ERR_HEADER_COMP;
}
if(new_ent->ref == 0) {
nghttp2_hd_entry_free(new_ent);
free(new_ent);
} else {
/* new_ent->ref > 0 means that new_ent is in the reference
set. */
new_ent->flags |= NGHTTP2_HD_FLAG_EMIT;
}
incidx = 1;
}
if(idx == -1) {
rv = emit_newname_block(bufs, nv, incidx);
} else {
rv = emit_indname_block(bufs, idx, nv, incidx);
}
if(rv != 0) {
return rv;
}
}
return 0;
}
static int deflate_post_process_hd_entry(nghttp2_hd_entry *ent,
size_t idx,
nghttp2_bufs *bufs)
{
int rv;
if((ent->flags & NGHTTP2_HD_FLAG_REFSET) &&
(ent->flags & NGHTTP2_HD_FLAG_IMPLICIT_EMIT) == 0 &&
(ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) {
/* This entry is not present in the current header set and must
be removed. */
ent->flags ^= NGHTTP2_HD_FLAG_REFSET;
DEBUGF(fprintf(stderr, "deflatehd: name/value match index=%zd\n", idx));
rv = emit_indexed_block(bufs, idx);
if(rv != 0) {
return rv;
}
return 0;
}
ent->flags &= ~(NGHTTP2_HD_FLAG_EMIT | NGHTTP2_HD_FLAG_IMPLICIT_EMIT);
if(res.index != -1) {
DEBUGF(fprintf(stderr, "deflatehd: name match index=%zd\n",
res.index));
}
if(hd_deflate_should_indexing(deflater, nv)) {
nghttp2_hd_entry *new_ent;
if(idx != -1 && idx < (ssize_t)NGHTTP2_STATIC_TABLE_LENGTH) {
nghttp2_nv nv_indname;
nv_indname = *nv;
nv_indname.name = nghttp2_hd_table_get(&deflater->ctx, idx)->nv.name;
new_ent = add_hd_table_incremental(&deflater->ctx, bufs, &nv_indname,
NGHTTP2_HD_FLAG_VALUE_ALLOC);
} else {
new_ent = add_hd_table_incremental(&deflater->ctx, bufs, nv,
NGHTTP2_HD_FLAG_NAME_ALLOC |
NGHTTP2_HD_FLAG_VALUE_ALLOC);
}
if(!new_ent) {
return NGHTTP2_ERR_HEADER_COMP;
}
if(new_ent->ref == 0) {
nghttp2_hd_entry_free(new_ent);
free(new_ent);
}
incidx = 1;
}
if(idx == -1) {
rv = emit_newname_block(bufs, nv, incidx);
} else {
rv = emit_indname_block(bufs, idx, nv, incidx);
}
if(rv != 0) {
return rv;
}
return 0;
}
@ -1205,8 +1049,21 @@ int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater,
}
if(deflater->notify_table_size_change) {
size_t min_hd_table_bufsize_max;
min_hd_table_bufsize_max = deflater->min_hd_table_bufsize_max;
deflater->notify_table_size_change = 0;
deflater->min_hd_table_bufsize_max = UINT32_MAX;
if(deflater->ctx.hd_table_bufsize_max > min_hd_table_bufsize_max) {
rv = emit_table_size(bufs, min_hd_table_bufsize_max);
if(rv != 0) {
goto fail;
}
}
rv = emit_table_size(bufs, deflater->ctx.hd_table_bufsize_max);
@ -1215,13 +1072,6 @@ int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater,
}
}
if(deflater->no_refset) {
rv = emit_clear_refset(bufs);
if(rv != 0) {
goto fail;
}
clear_refset(&deflater->ctx);
}
for(i = 0; i < nvlen; ++i) {
rv = deflate_nv(deflater, bufs, &nv[i]);
if(rv != 0) {
@ -1232,15 +1082,6 @@ int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater,
DEBUGF(fprintf(stderr,
"deflatehd: all input name/value pairs were deflated\n"));
for(i = 0; i < deflater->ctx.hd_table.len; ++i) {
nghttp2_hd_entry *ent = hd_ringbuf_get(&deflater->ctx.hd_table, i);
rv = deflate_post_process_hd_entry(ent, i, bufs);
if(rv != 0) {
goto fail;
}
}
return 0;
fail:
DEBUGF(fprintf(stderr, "deflatehd: error return %d\n", rv));
@ -1282,15 +1123,13 @@ ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater,
size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater,
const nghttp2_nv *nva, size_t nvlen)
{
size_t n;
size_t n = 0;
size_t i;
/* Possible Reference Set Emptying */
n = 1;
/* Possible Maximum Header Table Size Change. Encoding (1u << 31) -
1 using 4 bit prefix requires 6 bytes. */
n += 6;
1 using 4 bit prefix requires 6 bytes. We may emit this at most
twice. */
n += 12;
/* Use Literal Header Field without indexing - New Name, since it is
most space consuming format. Also we choose the less one between
@ -1305,9 +1144,6 @@ size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater,
n += nva[i].namelen + nva[i].valuelen;
}
/* Add possible reference set toggle off */
n += deflater->ctx.hd_table.len;
return n;
}
@ -1471,29 +1307,10 @@ static int hd_inflate_commit_indexed(nghttp2_hd_inflater *inflater,
nghttp2_nv *nv_out)
{
nghttp2_hd_entry *ent = nghttp2_hd_table_get(&inflater->ctx, inflater->index);
if(inflater->index >= inflater->ctx.hd_table.len) {
nghttp2_hd_entry *new_ent;
new_ent = add_hd_table_incremental(&inflater->ctx, NULL, &ent->nv,
NGHTTP2_HD_FLAG_NONE);
if(!new_ent) {
return NGHTTP2_ERR_NOMEM;
}
/* new_ent->ref == 0 may be hold */
emit_indexed_header(nv_out, new_ent);
inflater->ent_keep = new_ent;
return 0;
}
ent->flags ^= NGHTTP2_HD_FLAG_REFSET;
if(ent->flags & NGHTTP2_HD_FLAG_REFSET) {
emit_indexed_header(nv_out, ent);
return 0;
}
DEBUGF(fprintf(stderr, "inflatehd: toggle off item: "));
DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr));
DEBUGF(fprintf(stderr, ": "));
DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr));
DEBUGF(fprintf(stderr, "\n"));
return 1;
emit_indexed_header(nv_out, ent);
return 0;
}
static int hd_inflate_remove_bufs(nghttp2_hd_inflater *inflater,
@ -1599,6 +1416,7 @@ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater,
{
int rv;
nghttp2_nv nv;
nghttp2_hd_entry *ent_name;
rv = hd_inflate_remove_bufs(inflater, &nv, 1 /* value only */);
if(rv != 0) {
@ -1611,8 +1429,10 @@ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater,
nv.flags = NGHTTP2_NV_FLAG_NONE;
}
nv.name = inflater->ent_name->nv.name;
nv.namelen = inflater->ent_name->nv.namelen;
ent_name = nghttp2_hd_table_get(&inflater->ctx, inflater->index);
nv.name = ent_name->nv.name;
nv.namelen = ent_name->nv.namelen;
if(inflater->index_required) {
nghttp2_hd_entry *new_ent;
@ -1620,24 +1440,22 @@ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater,
int static_name;
ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC | NGHTTP2_HD_FLAG_VALUE_GIFT;
static_name = inflater->index >= inflater->ctx.hd_table.len;
static_name = inflater->index < NGHTTP2_STATIC_TABLE_LENGTH;
if(!static_name) {
ent_flags |= NGHTTP2_HD_FLAG_NAME_ALLOC;
/* For entry in static table, we must not touch ref, because it
is shared by threads */
++inflater->ent_name->ref;
++ent_name->ref;
}
new_ent = add_hd_table_incremental(&inflater->ctx, NULL, &nv, ent_flags);
if(!static_name && --inflater->ent_name->ref == 0) {
nghttp2_hd_entry_free(inflater->ent_name);
free(inflater->ent_name);
if(!static_name && --ent_name->ref == 0) {
nghttp2_hd_entry_free(ent_name);
free(ent_name);
}
inflater->ent_name = NULL;
if(new_ent) {
emit_indexed_header(nv_out, new_ent);
@ -1678,20 +1496,10 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
for(; in != last;) {
switch(inflater->state) {
case NGHTTP2_HD_STATE_OPCODE:
if((*in & 0xf0u) == 0x20u) {
if((*in & 0xe0u) == 0x20u) {
DEBUGF(fprintf(stderr, "inflatehd: header table size change\n"));
inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED;
inflater->state = NGHTTP2_HD_STATE_READ_TABLE_SIZE;
} else if((*in & 0xf0u) == 0x30u) {
if(*in != 0x30u) {
rv = NGHTTP2_ERR_HEADER_COMP;
goto fail;
}
DEBUGF(fprintf(stderr, "inflatehd: clearing reference set\n"));
inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED;
inflater->state = NGHTTP2_HD_STATE_CLEAR_REFSET;
++in;
} else if(*in & 0x80u) {
DEBUGF(fprintf(stderr, "inflatehd: indexed repr\n"));
inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED;
@ -1709,7 +1517,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
inflater->state = NGHTTP2_HD_STATE_READ_INDEX;
}
inflater->index_required = (*in & 0x40) != 0;
inflater->no_index = (*in & 0xf0u) == 0x10u;
inflater->no_index = (*in & 0xf0u) == 0x10u;
DEBUGF(fprintf(stderr,
"inflatehd: indexing required=%d, no_index=%d\n",
inflater->index_required,
@ -1720,15 +1528,10 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
}
inflater->left = 0;
inflater->shift = 0;
break;
case NGHTTP2_HD_STATE_CLEAR_REFSET:
clear_refset(&inflater->ctx);
inflater->state = NGHTTP2_HD_STATE_OPCODE;
break;
case NGHTTP2_HD_STATE_READ_TABLE_SIZE:
rfin = 0;
rv = hd_inflate_read_len(inflater, &rfin, in, last, 4,
rv = hd_inflate_read_len(inflater, &rfin, in, last, 5,
inflater->settings_hd_table_bufsize_max);
if(rv < 0) {
goto fail;
@ -1762,19 +1565,20 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
in += rv;
if(!rfin) {
goto almost_ok;
}
if(inflater->left == 0) {
rv = NGHTTP2_ERR_HEADER_COMP;
goto fail;
}
if(!rfin) {
goto almost_ok;
}
DEBUGF(fprintf(stderr, "inflatehd: index=%zu\n", inflater->left));
if(inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) {
inflater->index = inflater->left;
assert(inflater->index > 0);
--inflater->index;
rv = hd_inflate_commit_indexed(inflater, nv_out);
if(rv < 0) {
goto fail;
@ -1787,10 +1591,8 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
}
} else {
inflater->index = inflater->left;
assert(inflater->index > 0);
--inflater->index;
inflater->ent_name = nghttp2_hd_table_get(&inflater->ctx,
inflater->index);
inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
}
break;
@ -1995,20 +1797,6 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
goto fail;
}
for(; inflater->end_headers_index < inflater->ctx.hd_table.len;
++inflater->end_headers_index) {
nghttp2_hd_entry *ent;
ent = hd_ringbuf_get(&inflater->ctx.hd_table,
inflater->end_headers_index);
if((ent->flags & NGHTTP2_HD_FLAG_REFSET) &&
(ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) {
emit_indexed_header(nv_out, ent);
*inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;
return (ssize_t)(in - first);
}
ent->flags &= ~NGHTTP2_HD_FLAG_EMIT;
}
*inflate_flags |= NGHTTP2_HD_INFLATE_FINAL;
}
return (ssize_t)(in - first);
@ -2033,7 +1821,6 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater)
{
hd_inflate_keep_free(inflater);
inflater->end_headers_index = 0;
return 0;
}

View File

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

View File

@ -40,18 +40,10 @@ void nghttp2_option_del(nghttp2_option *option)
free(option);
}
void nghttp2_option_set_no_auto_stream_window_update(nghttp2_option *option,
int val)
void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val)
{
option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE;
option->no_auto_stream_window_update = val;
}
void nghttp2_option_set_no_auto_connection_window_update
(nghttp2_option *option, int val)
{
option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE;
option->no_auto_connection_window_update = val;
option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE;
option->no_auto_window_update = val;
}
void nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option,

View File

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

View File

@ -281,7 +281,6 @@ static void session_inbound_frame_reset(nghttp2_session *session)
iframe->niv = 0;
iframe->payloadleft = 0;
iframe->padlen = 0;
iframe->headers_payload_length = 0;
iframe->iv[NGHTTP2_INBOUND_NUM_IV - 1].settings_id =
NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
iframe->iv[NGHTTP2_INBOUND_NUM_IV - 1].value = UINT32_MAX;
@ -293,6 +292,8 @@ static void init_settings(nghttp2_settings_storage *settings)
settings->enable_push = 1;
settings->max_concurrent_streams = NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
settings->initial_window_size = NGHTTP2_INITIAL_WINDOW_SIZE;
settings->max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MIN;
settings->max_header_set_size = UINT32_MAX;
}
static void active_outbound_item_reset(nghttp2_active_outbound_item *aob)
@ -352,6 +353,7 @@ static int session_new(nghttp2_session **session_ptr,
(*session_ptr)->remote_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
(*session_ptr)->recv_window_size = 0;
(*session_ptr)->consumed_size = 0;
(*session_ptr)->recv_reduction = 0;
(*session_ptr)->local_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
@ -383,19 +385,10 @@ static int session_new(nghttp2_session **session_ptr,
if(option) {
if((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE) &&
option->no_auto_stream_window_update) {
if((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
option->no_auto_window_update) {
(*session_ptr)->opt_flags |=
NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE;
}
if((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE) &&
option->no_auto_connection_window_update) {
(*session_ptr)->opt_flags |=
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE;
(*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE;
}
@ -2031,6 +2024,8 @@ static int session_after_frame_sent(nghttp2_session *session)
/* Fall through */
case NGHTTP2_HCAT_RESPONSE:
stream->state = NGHTTP2_STREAM_OPENED;
/* Fall through */
case NGHTTP2_HCAT_HEADERS:
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
}
@ -2050,15 +2045,6 @@ static int session_after_frame_sent(nghttp2_session *session)
DATA frame item. We might have to handle it here. */
}
break;
case NGHTTP2_HCAT_HEADERS:
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
}
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
if(nghttp2_is_fatal(rv)) {
return rv;
}
break;
}
break;
}
@ -3182,10 +3168,11 @@ static int update_local_initial_window_size_func
return nghttp2_session_terminate_session(arg->session,
NGHTTP2_FLOW_CONTROL_ERROR);
}
if(!(arg->session->opt_flags &
NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE)) {
if(!(arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
if(nghttp2_should_send_window_update(stream->local_window_size,
stream->recv_window_size)) {
rv = nghttp2_session_add_window_update(arg->session,
NGHTTP2_FLAG_NONE,
stream->stream_id,
@ -3288,6 +3275,12 @@ int nghttp2_session_update_local_settings(nghttp2_session *session,
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
session->local_settings.initial_window_size = iv[i].value;
break;
case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
session->local_settings.max_frame_size = iv[i].value;
break;
case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
session->local_settings.max_header_set_size = iv[i].value;
break;
}
}
@ -3408,6 +3401,23 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
session->remote_settings.initial_window_size = entry->value;
break;
case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
if(entry->value < NGHTTP2_MAX_FRAME_SIZE_MIN ||
entry->value > NGHTTP2_MAX_FRAME_SIZE_MAX) {
return session_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR,
"SETTINGS: invalid SETTINGS_MAX_FRAME_SIZE");
}
session->remote_settings.max_frame_size = entry->value;
break;
case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
session->remote_settings.max_header_set_size = entry->value;
break;
}
}
@ -3722,7 +3732,8 @@ static int session_on_connection_window_update_received
{
int rv;
/* Handle connection-level flow control */
if(NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
if(frame->window_update.window_size_increment == 0 ||
NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
session->remote_window_size) {
return session_handle_invalid_connection
(session, frame, NGHTTP2_FLOW_CONTROL_ERROR, NULL);
@ -3761,7 +3772,8 @@ static int session_on_stream_window_update_received
(session, frame, NGHTTP2_PROTOCOL_ERROR,
"WINDOW_UPADATE to reserved stream");
}
if(NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
if(frame->window_update.window_size_increment == 0 ||
NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
stream->remote_window_size) {
return session_handle_invalid_stream(session, frame,
NGHTTP2_FLOW_CONTROL_ERROR);
@ -3883,9 +3895,9 @@ static int adjust_recv_window_size(int32_t *recv_window_size_ptr,
/*
* Accumulates received bytes |delta_size| for stream-level flow
* control and decides whether to send WINDOW_UPDATE to that
* stream. If NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE is set,
* WINDOW_UPDATE will not be sent.
* control and decides whether to send WINDOW_UPDATE to that stream.
* If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, WINDOW_UPDATE will not
* be sent.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
@ -3909,7 +3921,7 @@ static int session_update_recv_stream_window_size
/* We don't have to send WINDOW_UPDATE if the data received is the
last chunk in the incoming stream. */
if(send_window_update &&
!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE)) {
!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
/* We have to use local_settings here because it is the constraint
the remote endpoint should honor. */
if(nghttp2_should_send_window_update(stream->local_window_size,
@ -3931,8 +3943,8 @@ static int session_update_recv_stream_window_size
/*
* Accumulates received bytes |delta_size| for connection-level flow
* control and decides whether to send WINDOW_UPDATE to the
* connection. If NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE is
* set, WINDOW_UPDATE will not be sent.
* connection. If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set,
* WINDOW_UPDATE will not be sent.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
@ -3951,30 +3963,92 @@ static int session_update_recv_connection_window_size
return nghttp2_session_terminate_session(session,
NGHTTP2_FLOW_CONTROL_ERROR);
}
if(!(session->opt_flags &
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE)) {
if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
if(nghttp2_should_send_window_update(session->local_window_size,
session->recv_window_size)) {
/* Use stream ID 0 to update connection-level flow control
window */
rv = nghttp2_session_add_window_update(session,
NGHTTP2_FLAG_NONE,
0,
session->recv_window_size);
if(rv == 0) {
session->recv_window_size = 0;
/* recv_ign_window_size keeps track of ignored DATA bytes
before any connection-level WINDOW_UPDATE therefore, we can
reset it here. */
session->recv_ign_window_size = 0;
} else {
rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, 0,
session->recv_window_size);
if(rv != 0) {
return rv;
}
session->recv_window_size = 0;
}
}
return 0;
}
static int session_update_stream_consumed_size
(nghttp2_session *session, nghttp2_stream *stream, size_t delta_size)
{
int32_t recv_size;
int rv;
if((size_t)stream->consumed_size > NGHTTP2_MAX_WINDOW_SIZE - delta_size) {
return nghttp2_session_terminate_session(session,
NGHTTP2_FLOW_CONTROL_ERROR);
}
stream->consumed_size += delta_size;
/* recv_window_size may be smaller than consumed_size, because it
may be decreased by negative value with
nghttp2_submit_window_update(). */
recv_size = nghttp2_min(stream->consumed_size, stream->recv_window_size);
if(nghttp2_should_send_window_update(stream->local_window_size, recv_size)) {
rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE,
stream->stream_id, recv_size);
if(rv != 0) {
return rv;
}
stream->recv_window_size -= recv_size;
stream->consumed_size -= recv_size;
}
return 0;
}
static int session_update_connection_consumed_size
(nghttp2_session *session, size_t delta_size)
{
int32_t recv_size;
int rv;
if((size_t)session->consumed_size > NGHTTP2_MAX_WINDOW_SIZE - delta_size) {
return nghttp2_session_terminate_session(session,
NGHTTP2_FLOW_CONTROL_ERROR);
}
session->consumed_size += delta_size;
/* recv_window_size may be smaller than consumed_size, because it
may be decreased by negative value with
nghttp2_submit_window_update(). */
recv_size = nghttp2_min(session->consumed_size, session->recv_window_size);
if(nghttp2_should_send_window_update(session->local_window_size,
recv_size)) {
rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, 0,
recv_size);
if(rv != 0) {
return rv;
}
session->recv_window_size -= recv_size;
session->consumed_size -= recv_size;
}
return 0;
}
/*
* Checks that we can receive the DATA frame for stream, which is
* indicated by |session->iframe.frame.hd.stream_id|. If it is a
@ -4094,6 +4168,8 @@ static void inbound_frame_set_settings_entry(nghttp2_inbound_frame *iframe)
case NGHTTP2_SETTINGS_ENABLE_PUSH:
case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
break;
default:
DEBUGF(fprintf(stderr, "recv: ignore unknown settings id=0x%02x\n",
@ -4226,12 +4302,31 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
iframe->frame.hd.flags,
iframe->frame.hd.stream_id));
if(iframe->frame.hd.length > session->local_settings.max_frame_size) {
DEBUGF(fprintf(stderr,
"recv: legnth is too large %u > %u\n",
iframe->frame.hd.length,
session->local_settings.max_frame_size));
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
rv = nghttp2_session_terminate_session_with_reason
(session, NGHTTP2_PROTOCOL_ERROR, "too large frame size");
if(nghttp2_is_fatal(rv)) {
return rv;
}
break;
}
switch(iframe->frame.hd.type) {
case NGHTTP2_DATA: {
DEBUGF(fprintf(stderr, "recv: DATA\n"));
iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_STREAM |
NGHTTP2_FLAG_END_SEGMENT |
NGHTTP2_FLAG_PADDED);
/* Check stream is open. If it is not open or closing,
ignore payload. */
@ -4275,13 +4370,10 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
DEBUGF(fprintf(stderr, "recv: HEADERS\n"));
iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_STREAM |
NGHTTP2_FLAG_END_SEGMENT |
NGHTTP2_FLAG_END_HEADERS |
NGHTTP2_FLAG_PADDED |
NGHTTP2_FLAG_PRIORITY);
iframe->headers_payload_length = iframe->frame.hd.length;
rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd);
if(rv < 0) {
busy = 1;
@ -4408,8 +4500,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_HEADERS |
NGHTTP2_FLAG_PADDED);
iframe->headers_payload_length = iframe->frame.hd.length;
rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd);
if(rv < 0) {
busy = 1;
@ -4988,27 +5078,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
break;
}
iframe->headers_payload_length += cont_hd.length;
if(iframe->headers_payload_length > NGHTTP2_MAX_HEADERSLEN) {
DEBUGF(fprintf(stderr,
"recv: headers too large %zu\n",
iframe->headers_payload_length));
rv = nghttp2_session_terminate_session_with_reason
(session, NGHTTP2_INTERNAL_ERROR, "header is too large");
if(nghttp2_is_fatal(rv)) {
return rv;
}
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
break;
}
/* CONTINUATION won't bear NGHTTP2_PADDED flag */
iframe->frame.hd.flags |= cont_hd.flags & NGHTTP2_FLAG_END_HEADERS;
@ -5040,6 +5109,14 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
return rv;
}
/* Pad Length field is consumed immediately */
rv = nghttp2_session_consume(session, iframe->frame.hd.stream_id,
readlen);
if(nghttp2_is_fatal(rv)) {
return rv;
}
stream = nghttp2_session_get_stream(session,
iframe->frame.hd.stream_id);
if(stream) {
@ -5105,6 +5182,18 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
data_readlen = inbound_frame_effective_readlen
(iframe, iframe->payloadleft, readlen);
padlen = readlen - data_readlen;
if(padlen > 0) {
/* Padding is considered as "consumed" immediately */
rv = nghttp2_session_consume(session, iframe->frame.hd.stream_id,
padlen);
if(nghttp2_is_fatal(rv)) {
return rv;
}
}
DEBUGF(fprintf(stderr, "recv: data_readlen=%zu\n", data_readlen));
if(stream && data_readlen > 0 &&
@ -5149,8 +5238,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
readlen, iframe->payloadleft));
if(readlen > 0) {
session->recv_ign_window_size += readlen;
/* Update connection-level flow control window for ignored
DATA frame too */
rv = session_update_recv_connection_window_size(session, readlen);
@ -5158,20 +5245,14 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
return rv;
}
if((session->opt_flags &
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE) &&
nghttp2_should_send_window_update
(session->local_window_size, session->recv_ign_window_size)) {
if(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) {
rv = nghttp2_session_add_window_update
(session, NGHTTP2_FLAG_NONE, 0, session->recv_ign_window_size);
/* Ignored DATA is considered as "consumed" immediately. */
rv = session_update_connection_consumed_size(session, readlen);
if(nghttp2_is_fatal(rv)) {
return rv;
}
session->recv_window_size -= session->recv_ign_window_size;
session->recv_ign_window_size = 0;
}
}
@ -5495,7 +5576,7 @@ int nghttp2_session_pack_data(nghttp2_session *session,
/* Clear flags, because this may contain previous flags of previous
DATA */
frame->hd.flags &= (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT);
frame->hd.flags &= NGHTTP2_FLAG_END_STREAM;
flags = NGHTTP2_FLAG_NONE;
if(data_flags & NGHTTP2_DATA_FLAG_EOF) {
@ -5503,9 +5584,6 @@ int nghttp2_session_pack_data(nghttp2_session *session,
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
flags |= NGHTTP2_FLAG_END_STREAM;
}
if(frame->hd.flags & NGHTTP2_FLAG_END_SEGMENT) {
flags |= NGHTTP2_FLAG_END_SEGMENT;
}
}
/* The primary reason of data_frame is pass to the user callback */
@ -5654,6 +5732,10 @@ uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session,
return session->remote_settings.max_concurrent_streams;
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
return session->remote_settings.initial_window_size;
case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
return session->remote_settings.max_frame_size;
case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
return session->remote_settings.max_header_set_size;
}
assert(0);
@ -5744,3 +5826,36 @@ int nghttp2_session_get_stream_remote_close(nghttp2_session* session,
return (stream->shut_flags & NGHTTP2_SHUT_RD) != 0;
}
int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id,
size_t size)
{
int rv;
nghttp2_stream *stream;
if(stream_id == 0) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
return NGHTTP2_ERR_INVALID_STATE;
}
rv = session_update_connection_consumed_size(session, size);
if(nghttp2_is_fatal(rv)) {
return rv;
}
stream = nghttp2_session_get_stream(session, stream_id);
if(stream) {
rv = session_update_stream_consumed_size(session, stream, size);
if(nghttp2_is_fatal(rv)) {
return rv;
}
}
return 0;
}

View File

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

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->local_window_size = local_initial_window_size;
stream->recv_window_size = 0;
stream->consumed_size = 0;
stream->recv_reduction = 0;
stream->blocked_sent = 0;

View File

@ -151,6 +151,10 @@ struct nghttp2_stream {
WINDOW_UPDATE. This could be negative after submitting negative
value to WINDOW_UPDATE */
int32_t recv_window_size;
/* The number of bytes consumed by the application and now is
subject to WINDOW_UPDATE. This is only used when auto
WINDOW_UPDATE is turned off. */
int32_t consumed_size;
/* The amount of recv_window_size cut using submitting negative
value to WINDOW_UPDATE */
int32_t recv_reduction;

View File

@ -81,9 +81,7 @@ static int32_t submit_headers_shared
}
flags_copy =
(flags & (NGHTTP2_FLAG_END_STREAM |
NGHTTP2_FLAG_END_SEGMENT |
NGHTTP2_FLAG_PRIORITY)) |
(flags & (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PRIORITY)) |
NGHTTP2_FLAG_END_HEADERS;
if(stream_id == -1) {
@ -348,27 +346,30 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
if(rv != 0) {
return rv;
}
/* recv_ign_window_size keeps track of ignored DATA bytes before
any connection-level WINDOW_UPDATE therefore, we can reset it
here. */
session->recv_ign_window_size = 0;
} else {
stream = nghttp2_session_get_stream(session, stream_id);
if(stream) {
rv = nghttp2_adjust_local_window_size(&stream->local_window_size,
&stream->recv_window_size,
&stream->recv_reduction,
&window_size_increment);
if(rv != 0) {
return rv;
}
} else {
if(!stream) {
return 0;
}
rv = nghttp2_adjust_local_window_size(&stream->local_window_size,
&stream->recv_window_size,
&stream->recv_reduction,
&window_size_increment);
if(rv != 0) {
return rv;
}
}
if(window_size_increment > 0) {
if(stream_id == 0) {
session->consumed_size =
nghttp2_max(0, session->consumed_size - window_size_increment);
} else {
stream->consumed_size =
nghttp2_max(0, stream->consumed_size - window_size_increment);
}
return nghttp2_session_add_window_update(session, flags, stream_id,
window_size_increment);
}
@ -535,8 +536,7 @@ int nghttp2_submit_data(nghttp2_session *session, uint8_t flags,
{
int rv;
nghttp2_private_data *data_frame;
uint8_t nflags = flags & (NGHTTP2_FLAG_END_STREAM |
NGHTTP2_FLAG_END_SEGMENT);
uint8_t nflags = flags & NGHTTP2_FLAG_END_STREAM;
if(stream_id == 0) {
return NGHTTP2_ERR_INVALID_ARGUMENT;

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

View File

@ -53,7 +53,7 @@ if __name__ == '__main__':
testdata = json.loads(input)
if 'draft' not in testdata or testdata['draft'] != 8:
if 'draft' not in testdata or testdata['draft'] != 9:
sys.stderr.write('Not supported\n')
continue

View File

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

View File

@ -33,12 +33,11 @@ HD_ENTRY_OVERHEAD = cnghttp2.NGHTTP2_HD_ENTRY_OVERHEAD
class HDTableEntry:
def __init__(self, name, namelen, value, valuelen, ref):
def __init__(self, name, namelen, value, valuelen):
self.name = name
self.namelen = namelen
self.value = value
self.valuelen = valuelen
self.ref = ref
def space(self):
return self.namelen + self.valuelen + HD_ENTRY_OVERHEAD
@ -52,8 +51,7 @@ cdef _get_hd_table(cnghttp2.nghttp2_hd_context *ctx):
k = _get_pybytes(entry.nv.name, entry.nv.namelen)
v = _get_pybytes(entry.nv.value, entry.nv.valuelen)
res.append(HDTableEntry(k, entry.nv.namelen,
v, entry.nv.valuelen,
(entry.flags & cnghttp2.NGHTTP2_HD_FLAG_REFSET) != 0))
v, entry.nv.valuelen))
return res
cdef _get_pybytes(uint8_t *b, uint16_t blen):
@ -143,15 +141,6 @@ cdef class HDDeflater:
return res
def set_no_refset(self, no_refset):
'''Tells the compressor not to use reference set if |no_refset| is
nonzero. If |no_refset| is nonzero, on each invocation of
deflate(), compressor first emits index=0 to clear up
reference set.
'''
cnghttp2.nghttp2_hd_deflate_set_no_refset(self._deflater, no_refset)
def change_table_size(self, hd_table_bufsize_max):
'''Changes header table size to |hd_table_bufsize_max| byte.
@ -243,16 +232,14 @@ def print_hd_table(hdtable):
function does not work if header name/value cannot be decoded using
UTF-8 encoding.
s=N means the entry occupies N bytes in header table. if r=y, then
the entry is in the reference set.
s=N means the entry occupies N bytes in header table.
'''
idx = 0
for entry in hdtable:
idx += 1
print('[{}] (s={}) (r={}) {}: {}'\
print('[{}] (s={}) {}: {}'\
.format(idx, entry.space(),
'y' if entry.ref else 'n',
entry.name.decode('utf-8'),
entry.value.decode('utf-8')))

View File

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

View File

@ -126,6 +126,8 @@ public:
const std::vector<std::pair<std::string, std::string>>& headers,
nghttp2_data_provider *data_prd);
int submit_non_final_response(const std::string& status, int32_t stream_id);
int submit_push_promise(Stream *stream, const std::string& push_path);
int submit_rst_stream(Stream *stream, nghttp2_error_code error_code);

View File

@ -221,12 +221,6 @@ void print_flags(const nghttp2_frame_hd& hd)
if(hd.flags & NGHTTP2_FLAG_END_STREAM) {
s += "END_STREAM";
}
if(hd.flags & NGHTTP2_FLAG_END_SEGMENT) {
if(!s.empty()) {
s += " | ";
}
s += "END_SEGMENT";
}
if(hd.flags & NGHTTP2_FLAG_PADDED) {
if(!s.empty()) {
s += " | ";
@ -238,12 +232,6 @@ void print_flags(const nghttp2_frame_hd& hd)
if(hd.flags & NGHTTP2_FLAG_END_STREAM) {
s += "END_STREAM";
}
if(hd.flags & NGHTTP2_FLAG_END_SEGMENT) {
if(!s.empty()) {
s += " | ";
}
s += "END_SEGMENT";
}
if(hd.flags & NGHTTP2_FLAG_END_HEADERS) {
if(!s.empty()) {
s += " | ";

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));
dump_val(outent, "name", ent->nv.name, ent->nv.namelen);
dump_val(outent, "value", ent->nv.value, ent->nv.valuelen);
json_object_set_new(outent, "referenced",
json_boolean(ent->flags & NGHTTP2_HD_FLAG_REFSET));
json_object_set_new(outent, "size",
json_integer(ent->nv.namelen + ent->nv.valuelen +
NGHTTP2_HD_ENTRY_OVERHEAD));

View File

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

View File

@ -162,10 +162,34 @@ namespace {
size_t DISALLOWED_HDLEN = sizeof(DISALLOWED_HD)/sizeof(DISALLOWED_HD[0]);
} // namespace
namespace {
const char *REQUEST_PSEUDO_HD[] = {
":authority",
":method",
":path",
":scheme",
};
} // namespace
namespace {
size_t REQUEST_PSEUDO_HDLEN =
sizeof(REQUEST_PSEUDO_HD) / sizeof(REQUEST_PSEUDO_HD[0]);
} // namespace
namespace {
const char *RESPONSE_PSEUDO_HD[] = {
":status",
};
} // namespace
namespace {
size_t RESPONSE_PSEUDO_HDLEN =
sizeof(RESPONSE_PSEUDO_HD) / sizeof(RESPONSE_PSEUDO_HD[0]);
} // namespace
namespace {
const char *IGN_HD[] = {
"connection",
"expect",
"http2-settings",
"keep-alive",
"proxy-connection",
@ -186,7 +210,6 @@ namespace {
const char *HTTP1_IGN_HD[] = {
"connection",
"cookie",
"expect",
"http2-settings",
"keep-alive",
"proxy-connection",
@ -225,6 +248,61 @@ bool check_http2_headers(const Headers& nva)
return true;
}
namespace {
template<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)
{
for(auto& kv : nva) {
@ -275,31 +353,12 @@ Headers::value_type to_header(const uint8_t *name, size_t namelen,
no_index);
}
void split_add_header(Headers& nva,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index)
void add_header(Headers& nva,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index)
{
if(valuelen == 0) {
nva.push_back(to_header(name, namelen, value, valuelen, no_index));
return;
}
auto j = value;
auto end = value + valuelen;
for(;;) {
// Skip 0 length value
j = std::find_if(j, end,
[](uint8_t c)
{
return c != '\0';
});
if(j == end) {
break;
}
auto l = std::find(j, end, '\0');
nva.push_back(to_header(name, namelen, j, l-j, no_index));
j = l;
}
nva.push_back(to_header(name, namelen, value, valuelen, no_index));
}
const Headers::value_type* get_unique_header(const Headers& nva,
@ -355,29 +414,6 @@ nghttp2_nv make_nv(const std::string& name, const std::string& value,
name.size(), value.size(), flags};
}
Headers concat_norm_headers(Headers headers)
{
auto res = Headers();
res.reserve(headers.size());
for(auto& kv : headers) {
if(!res.empty() && res.back().name == kv.name &&
kv.name != "cookie" && kv.name != "set-cookie") {
auto& last = res.back();
if(!kv.value.empty()) {
last.value.append(1, '\0');
last.value += kv.value;
}
// We do ORing nv flags. This is done even if value is empty.
last.no_index |= kv.no_index;
} else {
res.push_back(std::move(kv));
}
}
return res;
}
void copy_norm_headers_to_nva
(std::vector<nghttp2_nv>& nva, const Headers& headers)
{

View File

@ -96,6 +96,16 @@ bool check_http2_allowed_header(const char *name);
// contains such headers.
bool check_http2_headers(const Headers& nva);
// Calls check_http2_headers() and also checks that |nva| only
// contains pseudo headers allowed in request. Returns true if all
// checks passed.
bool check_http2_request_headers(const Headers& nva);
// Calls check_http2_headers() and also checks that |nva| only
// contains pseudo headers allowed in response. Returns true if all
// checks passed.
bool check_http2_response_headers(const Headers& nva);
bool name_less(const Headers::value_type& lhs, const Headers::value_type& rhs);
void normalize_headers(Headers& nva);
@ -104,15 +114,13 @@ Headers::value_type to_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index);
// Add name/value pairs to |nva|. The name is given in the |name| with
// |namelen| bytes. This function inspects the |value| and split it
// using '\0' as delimiter. Each token is added to the |nva| with the
// name |name|. If |no_index| is true, this name/value pair won't be
// indexed when it is forwarded to the next hop.
void split_add_header(Headers& nva,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index);
// Add name/value pairs to |nva|. If |no_index| is true, this
// name/value pair won't be indexed when it is forwarded to the next
// hop.
void add_header(Headers& nva,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index);
// Returns sorted |nva| with |nvlen| elements. The headers are sorted
// by name only and not necessarily stable. In addition to the
@ -143,12 +151,6 @@ bool value_lws(const Headers::value_type *nv);
// and not contain illegal characters.
bool non_empty_value(const Headers::value_type *nv);
// Concatenates field with same value by NULL as delimiter and returns
// new vector containing the resulting header fields. cookie and
// set-cookie header fields won't be concatenated. This function
// assumes that the |headers| is sorted by name.
Headers concat_norm_headers(Headers headers);
// Creates nghttp2_nv using |name| and |value| and returns it. The
// returned value only references the data pointer to name.c_str() and
// value.c_str(). If |no_index| is true, nghttp2_nv flags member has

View File

@ -72,27 +72,19 @@ void test_http2_sort_nva(void)
check_nv({"delta", "5"}, &nva[5]);
}
void test_http2_split_add_header(void)
void test_http2_add_header(void)
{
const uint8_t concatval[] = { '4', 0x00, 0x00, '6', 0x00, '5', '9', 0x00 };
auto nva = Headers();
http2::split_add_header(nva, (const uint8_t*)"delta", 5,
concatval, sizeof(concatval), false);
CU_ASSERT(Headers::value_type("delta", "4") == nva[0]);
CU_ASSERT(Headers::value_type("delta", "6") == nva[1]);
CU_ASSERT(Headers::value_type("delta", "59") == nva[2]);
nva.clear();
http2::split_add_header(nva, (const uint8_t*)"alpha", 5,
(const uint8_t*)"123", 3, false);
http2::add_header(nva, (const uint8_t*)"alpha", 5,
(const uint8_t*)"123", 3, false);
CU_ASSERT(Headers::value_type("alpha", "123") == nva[0]);
CU_ASSERT(!nva[0].no_index);
nva.clear();
http2::split_add_header(nva, (const uint8_t*)"alpha", 5,
(const uint8_t*)"", 0, true);
http2::add_header(nva, (const uint8_t*)"alpha", 5,
(const uint8_t*)"", 0, true);
CU_ASSERT(Headers::value_type("alpha", "") == nva[0]);
CU_ASSERT(nva[0].no_index);
}
@ -119,6 +111,21 @@ void test_http2_check_http2_headers(void)
{ "te2", "3" }
};
CU_ASSERT(http2::check_http2_headers(nva3));
auto nva4 = Headers{
{ ":authority", "1" },
{ ":method", "2" },
{ ":path", "3" },
{ ":scheme", "4" }
};
CU_ASSERT(http2::check_http2_request_headers(nva4));
CU_ASSERT(!http2::check_http2_response_headers(nva4));
auto nva5 = Headers{
{ ":status", "1" }
};
CU_ASSERT(!http2::check_http2_request_headers(nva5));
CU_ASSERT(http2::check_http2_response_headers(nva5));
}
void test_http2_get_unique_header(void)
@ -199,24 +206,12 @@ auto headers = Headers
{"zulu", "12"}};
} // namespace
void test_http2_concat_norm_headers(void)
{
auto hds = headers;
hds.emplace_back("cookie", "foo");
hds.emplace_back("cookie", "bar");
hds.emplace_back("set-cookie", "baz");
hds.emplace_back("set-cookie", "buzz");
auto res = http2::concat_norm_headers(hds);
CU_ASSERT(14 == res.size());
CU_ASSERT(std::string("2") + '\0' + std::string("3") == res[2].value);
}
void test_http2_copy_norm_headers_to_nva(void)
{
std::vector<nghttp2_nv> nva;
http2::copy_norm_headers_to_nva(nva, headers);
CU_ASSERT(6 == nva.size());
auto ans = std::vector<int>{0, 1, 4, 6, 7, 12};
CU_ASSERT(7 == nva.size());
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
for(size_t i = 0; i < ans.size(); ++i) {
check_nv(headers[ans[i]], &nva[i]);
@ -236,6 +231,7 @@ void test_http2_build_http1_headers_from_norm_headers(void)
"Alpha: 0\r\n"
"Bravo: 1\r\n"
"Delta: 4\r\n"
"Expect: 5\r\n"
"Foxtrot: 6\r\n"
"Tango: 7\r\n"
"Te: 8\r\n"

View File

@ -27,13 +27,12 @@
namespace shrpx {
void test_http2_split_add_header(void);
void test_http2_add_header(void);
void test_http2_sort_nva(void);
void test_http2_check_http2_headers(void);
void test_http2_get_unique_header(void);
void test_http2_get_header(void);
void test_http2_value_lws(void);
void test_http2_concat_norm_headers(void);
void test_http2_copy_norm_headers_to_nva(void);
void test_http2_build_http1_headers_from_norm_headers(void);
void test_http2_lws(void);

View File

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

View File

@ -71,8 +71,7 @@ int main(int argc, char* argv[])
shrpx::test_shrpx_ssl_create_lookup_tree) ||
!CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file",
shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) ||
!CU_add_test(pSuite, "http2_split_add_header",
shrpx::test_http2_split_add_header) ||
!CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) ||
!CU_add_test(pSuite, "http2_sort_nva", shrpx::test_http2_sort_nva) ||
!CU_add_test(pSuite, "http2_check_http2_headers",
shrpx::test_http2_check_http2_headers) ||
@ -82,8 +81,6 @@ int main(int argc, char* argv[])
shrpx::test_http2_get_header) ||
!CU_add_test(pSuite, "http2_value_lws",
shrpx::test_http2_value_lws) ||
!CU_add_test(pSuite, "http2_concat_norm_headers",
shrpx::test_http2_concat_norm_headers) ||
!CU_add_test(pSuite, "http2_copy_norm_headers_to_nva",
shrpx::test_http2_copy_norm_headers_to_nva) ||
!CU_add_test(pSuite, "http2_build_http1_headers_from_norm_headers",

View File

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

View File

@ -46,6 +46,8 @@ Downstream::Downstream(Upstream *upstream, int stream_id, int priority)
response_body_buf_(nullptr),
request_headers_sum_(0),
response_headers_sum_(0),
request_datalen_(0),
response_datalen_(0),
stream_id_(stream_id),
priority_(priority),
downstream_stream_id_(-1),
@ -63,12 +65,12 @@ Downstream::Downstream(Upstream *upstream, int stream_id, int priority)
http2_settings_seen_(false),
chunked_request_(false),
request_connection_close_(false),
request_expect_100_continue_(false),
request_header_key_prev_(false),
request_http2_expect_body_(false),
chunked_response_(false),
response_connection_close_(false),
response_header_key_prev_(false)
response_header_key_prev_(false),
expect_final_response_(false)
{}
Downstream::~Downstream()
@ -230,11 +232,6 @@ Headers::const_iterator Downstream::get_norm_request_header
return get_norm_header(request_headers_, name);
}
void Downstream::concat_norm_request_headers()
{
request_headers_ = http2::concat_norm_headers(std::move(request_headers_));
}
void Downstream::add_request_header(std::string name, std::string value)
{
request_header_key_prev_ = true;
@ -256,8 +253,8 @@ void Downstream::split_add_request_header
bool no_index)
{
request_headers_sum_ += namelen + valuelen;
http2::split_add_header(request_headers_, name, namelen, value, valuelen,
no_index);
http2::add_header(request_headers_, name, namelen, value, valuelen,
no_index);
}
bool Downstream::get_request_header_key_prev() const
@ -429,11 +426,6 @@ void Downstream::set_request_http2_expect_body(bool f)
request_http2_expect_body_ = f;
}
bool Downstream::get_expect_100_continue() const
{
return request_expect_100_continue_;
}
bool Downstream::get_output_buffer_full()
{
if(dconn_) {
@ -463,7 +455,13 @@ int Downstream::push_upload_data_chunk(const uint8_t *data, size_t datalen)
return -1;
}
request_bodylen_ += datalen;
return dconn_->push_upload_data_chunk(data, datalen);
if(dconn_->push_upload_data_chunk(data, datalen) != 0) {
return -1;
}
request_datalen_ += datalen;
return 0;
}
int Downstream::end_upload_data()
@ -485,11 +483,6 @@ void Downstream::normalize_response_headers()
http2::normalize_headers(response_headers_);
}
void Downstream::concat_norm_response_headers()
{
response_headers_ = http2::concat_norm_headers(std::move(response_headers_));
}
Headers::const_iterator Downstream::get_norm_response_header
(const std::string& name) const
{
@ -551,8 +544,8 @@ void Downstream::split_add_response_header
bool no_index)
{
response_headers_sum_ += namelen + valuelen;
http2::split_add_header(response_headers_, name, namelen, value, valuelen,
no_index);
http2::add_header(response_headers_, name, namelen, value, valuelen,
no_index);
}
bool Downstream::get_response_header_key_prev() const
@ -754,11 +747,6 @@ void Downstream::inspect_http1_request()
if(util::strifind(hd.value.c_str(), "chunked")) {
chunked_request_ = true;
}
} else if(!request_expect_100_continue_ &&
util::strieq(hd.name.c_str(), "expect")) {
if(util::strifind(hd.value.c_str(), "100-continue")) {
request_expect_100_continue_ = true;
}
}
}
}
@ -775,6 +763,18 @@ void Downstream::inspect_http1_response()
}
}
void Downstream::reset_response()
{
response_http_status_ = 0;
response_major_ = 1;
response_minor_ = 1;
}
bool Downstream::get_non_final_response() const
{
return response_http_status_ / 100 == 1;
}
bool Downstream::get_upgraded() const
{
return upgraded_;
@ -816,4 +816,47 @@ void Downstream::set_response_rst_stream_error_code
response_rst_stream_error_code_ = error_code;
}
void Downstream::set_expect_final_response(bool f)
{
expect_final_response_ = f;
}
bool Downstream::get_expect_final_response() const
{
return expect_final_response_;
}
size_t Downstream::get_request_datalen() const
{
return request_datalen_;
}
void Downstream::reset_request_datalen()
{
request_datalen_ = 0;
}
void Downstream::add_response_datalen(size_t len)
{
response_datalen_ += len;
}
size_t Downstream::get_response_datalen() const
{
return response_datalen_;
}
void Downstream::reset_response_datalen()
{
response_datalen_ = 0;
}
bool Downstream::expect_response_body() const
{
return request_method_ != "HEAD" &&
response_http_status_ / 100 != 1 &&
response_http_status_ != 304 &&
response_http_status_ != 204;
}
} // namespace shrpx

View File

@ -99,10 +99,6 @@ public:
// called after calling normalize_request_headers().
Headers::const_iterator get_norm_request_header
(const std::string& name) const;
// Concatenates request header fields with same name by NULL as
// delimiter. See http2::concat_norm_headers(). This function must
// be called after calling normalize_request_headers().
void concat_norm_request_headers();
void add_request_header(std::string name, std::string value);
void set_last_request_header_value(std::string value);
@ -144,9 +140,11 @@ public:
const std::string& get_request_user_agent() const;
bool get_request_http2_expect_body() const;
void set_request_http2_expect_body(bool f);
bool get_expect_100_continue() const;
int push_upload_data_chunk(const uint8_t *data, size_t datalen);
int end_upload_data();
size_t get_request_datalen() const;
void reset_request_datalen();
bool expect_response_body() const;
enum {
INITIAL,
HEADER_COMPLETE,
@ -162,10 +160,6 @@ public:
const Headers& get_response_headers() const;
// Makes key lowercase and sort headers by name using <
void normalize_response_headers();
// Concatenates response header fields with same name by NULL as
// delimiter. See http2::concat_norm_headers(). This function must
// be called after calling normalize_response_headers().
void concat_norm_response_headers();
// Returns iterator pointing to the response header with the name
// |name|. If multiple header have |name| as name, return first
// occurrence from the beginning. If no such header is found,
@ -215,6 +209,14 @@ public:
void set_response_rst_stream_error_code(nghttp2_error_code error_code);
// Inspects HTTP/1 response. This checks tranfer-encoding etc.
void inspect_http1_response();
// Clears some of member variables for response.
void reset_response();
bool get_non_final_response() const;
void set_expect_final_response(bool f);
bool get_expect_final_response() const;
void add_response_datalen(size_t len);
size_t get_response_datalen() const;
void reset_response_datalen();
// Call this method when there is incoming data in downstream
// connection.
@ -254,6 +256,10 @@ private:
size_t request_headers_sum_;
size_t response_headers_sum_;
// The number of bytes not consumed by the application yet.
size_t request_datalen_;
size_t response_datalen_;
int32_t stream_id_;
int32_t priority_;
// stream ID in backend connection
@ -282,13 +288,13 @@ private:
bool chunked_request_;
bool request_connection_close_;
bool request_expect_100_continue_;
bool request_header_key_prev_;
bool request_http2_expect_body_;
bool chunked_response_;
bool response_connection_close_;
bool response_header_key_prev_;
bool expect_final_response_;
};
} // namespace shrpx

View File

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

View File

@ -58,7 +58,6 @@ Http2Session::Http2Session(event_base *evbase, SSL_CTX *ssl_ctx)
wrbev_(nullptr),
rdbev_(nullptr),
settings_timerev_(nullptr),
recv_ign_window_size_(0),
fd_(-1),
state_(DISCONNECTED),
notified_(false),
@ -661,28 +660,6 @@ int Http2Session::submit_rst_stream(int32_t stream_id,
return 0;
}
int Http2Session::submit_window_update(Http2DownstreamConnection *dconn,
int32_t amount)
{
assert(state_ == CONNECTED);
int rv;
int32_t stream_id;
if(dconn) {
stream_id = dconn->get_downstream()->get_downstream_stream_id();
} else {
stream_id = 0;
recv_ign_window_size_ = 0;
}
rv = nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE,
stream_id, amount);
if(rv < NGHTTP2_ERR_FATAL) {
SSLOG(FATAL, this) << "nghttp2_submit_window_update() failed: "
<< nghttp2_strerror(rv);
return -1;
}
return 0;
}
int Http2Session::submit_priority(Http2DownstreamConnection *dconn,
int32_t pri)
{
@ -752,7 +729,6 @@ int on_stream_close_callback
(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code,
void *user_data)
{
int rv;
auto http2session = static_cast<Http2Session*>(user_data);
if(LOG_ENABLED(INFO)) {
SSLOG(INFO, http2session) << "Stream stream_id=" << stream_id
@ -769,11 +745,14 @@ int on_stream_close_callback
if(dconn) {
auto downstream = dconn->get_downstream();
if(downstream && downstream->get_downstream_stream_id() == stream_id) {
auto upstream = downstream->get_upstream();
http2session->consume(downstream->get_downstream_stream_id(),
downstream->get_response_datalen());
downstream->reset_response_datalen();
if(error_code == NGHTTP2_NO_ERROR) {
downstream->set_response_state(Downstream::MSG_COMPLETE);
rv = upstream->on_downstream_body_complete(downstream);
if(rv != 0) {
if(downstream->get_response_state() != Downstream::MSG_COMPLETE) {
downstream->set_response_state(Downstream::MSG_RESET);
}
} else {
@ -841,10 +820,6 @@ int on_header_callback(nghttp2_session *session,
uint8_t flags,
void *user_data)
{
if(frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
return 0;
}
auto sd = static_cast<StreamData*>
(nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if(!sd || !sd->dconn) {
@ -854,6 +829,13 @@ int on_header_callback(nghttp2_session *session,
if(!downstream) {
return 0;
}
if(frame->hd.type != NGHTTP2_HEADERS ||
(frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
!downstream->get_expect_final_response())) {
return 0;
}
if(downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if(LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large header block size="
@ -905,23 +887,20 @@ int on_begin_headers_callback(nghttp2_session *session,
namespace {
int on_response_headers(Http2Session *http2session,
Downstream *downstream,
nghttp2_session *session,
const nghttp2_frame *frame)
{
int rv;
auto sd = static_cast<StreamData*>
(nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if(!sd || !sd->dconn) {
return 0;
}
auto downstream = sd->dconn->get_downstream();
if(!downstream) {
return 0;
}
auto upstream = downstream->get_upstream();
downstream->normalize_response_headers();
auto& nva = downstream->get_response_headers();
if(!http2::check_http2_headers(nva)) {
downstream->set_expect_final_response(false);
if(!http2::check_http2_response_headers(nva)) {
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
downstream->set_response_state(Downstream::MSG_RESET);
@ -935,8 +914,10 @@ int on_response_headers(Http2Session *http2session,
NGHTTP2_PROTOCOL_ERROR);
downstream->set_response_state(Downstream::MSG_RESET);
call_downstream_readcb(http2session, downstream);
return 0;
}
downstream->set_response_http_status(strtoul(status->value.c_str(),
nullptr, 10));
downstream->set_response_major(2);
@ -952,6 +933,26 @@ int on_response_headers(Http2Session *http2session,
<< "\n" << ss.str();
}
if(downstream->get_non_final_response()) {
if(LOG_ENABLED(INFO)) {
SSLOG(INFO, http2session) << "This is non-final response.";
}
downstream->set_expect_final_response(true);
rv = upstream->on_downstream_header_complete(downstream);
// Now Dowstream's response headers are erased.
if(rv != 0) {
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
downstream->set_response_state(Downstream::MSG_RESET);
}
return 0;
}
auto content_length = http2::get_header(nva, "content-length");
if(!content_length && downstream->get_request_method() != "HEAD" &&
downstream->get_request_method() != "CONNECT") {
@ -975,7 +976,6 @@ int on_response_headers(Http2Session *http2session,
}
}
auto upstream = downstream->get_upstream();
downstream->set_response_state(Downstream::HEADER_COMPLETE);
downstream->check_upgrade_fulfilled();
if(downstream->get_upgraded()) {
@ -1033,19 +1033,72 @@ int on_frame_recv_callback
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_INTERNAL_ERROR);
downstream->set_response_state(Downstream::MSG_RESET);
} else if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
downstream->set_response_state(Downstream::MSG_COMPLETE);
rv = upstream->on_downstream_body_complete(downstream);
if(rv != 0) {
downstream->set_response_state(Downstream::MSG_RESET);
}
}
}
call_downstream_readcb(http2session, downstream);
break;
}
case NGHTTP2_HEADERS:
case NGHTTP2_HEADERS: {
auto sd = static_cast<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) {
rv = on_response_headers(http2session, session, frame);
rv = on_response_headers(http2session, downstream, session, frame);
if(rv != 0) {
return rv;
}
}
if(frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
if(downstream->get_expect_final_response()) {
rv = on_response_headers(http2session, downstream, session, frame);
if(rv != 0) {
return rv;
}
} else if((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
}
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
downstream->set_response_state(Downstream::MSG_COMPLETE);
auto upstream = downstream->get_upstream();
rv = upstream->on_downstream_body_complete(downstream);
if(rv != 0) {
downstream->set_response_state(Downstream::MSG_RESET);
}
}
}
break;
}
case NGHTTP2_RST_STREAM: {
auto sd = static_cast<StreamData*>
(nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
@ -1055,7 +1108,7 @@ int on_frame_recv_callback
downstream->get_downstream_stream_id() == frame->hd.stream_id) {
if(downstream->get_upgraded() &&
downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// For tunneled connection, we has to submit RST_STREAM to
// For tunneled connection, we have to submit RST_STREAM to
// upstream *after* whole response body is sent. We just set
// MSG_COMPLETE here. Upstream will take care of that.
if(LOG_ENABLED(INFO)) {
@ -1112,13 +1165,35 @@ int on_data_chunk_recv_callback(nghttp2_session *session,
(nghttp2_session_get_stream_user_data(session, stream_id));
if(!sd || !sd->dconn) {
http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR);
http2session->handle_ign_data_chunk(len);
if(http2session->consume(stream_id, len) != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
auto downstream = sd->dconn->get_downstream();
if(!downstream || downstream->get_downstream_stream_id() != stream_id) {
if(!downstream || downstream->get_downstream_stream_id() != stream_id ||
!downstream->expect_response_body()) {
http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR);
http2session->handle_ign_data_chunk(len);
if(http2session->consume(stream_id, len) != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
// We don't want DATA after non-final response, which is illegal in
// HTTP.
if(downstream->get_non_final_response()) {
http2session->submit_rst_stream(stream_id, NGHTTP2_PROTOCOL_ERROR);
if(http2session->consume(stream_id, len) != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
@ -1128,9 +1203,16 @@ int on_data_chunk_recv_callback(nghttp2_session *session,
rv = upstream->on_downstream_body(downstream, data, len, false);
if(rv != 0) {
http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR);
http2session->handle_ign_data_chunk(len);
if(http2session->consume(stream_id, len) != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
downstream->set_response_state(Downstream::MSG_RESET);
}
downstream->add_response_datalen(len);
call_downstream_readcb(http2session, downstream);
return 0;
}
@ -1470,16 +1552,21 @@ SSL* Http2Session::get_ssl() const
return ssl_;
}
int Http2Session::handle_ign_data_chunk(size_t len)
int Http2Session::consume(int32_t stream_id, size_t len)
{
int32_t window_size;
int rv;
recv_ign_window_size_ += len;
if(!session_) {
return 0;
}
window_size = nghttp2_session_get_effective_local_window_size(session_);
rv = nghttp2_session_consume(session_, stream_id, len);
if(recv_ign_window_size_ >= window_size / 2) {
submit_window_update(nullptr, recv_ign_window_size_);
if(rv != 0) {
SSLOG(WARNING, this) << "nghttp2_session_consume() returned error: "
<< nghttp2_strerror(rv);
return -1;
}
return 0;

View File

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

View File

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

View File

@ -60,9 +60,6 @@ public:
nghttp2_session* get_http2_session();
int rst_stream(Downstream *downstream, nghttp2_error_code error_code);
// To send WINDOW_UPDATE for a connection, specify nullptr to
// |downstream|.
int window_update(Downstream *downstream, int32_t window_size_increment);
int terminate_session(nghttp2_error_code error_code);
int error_reply(Downstream *downstream, unsigned int status_code);
@ -81,16 +78,15 @@ public:
int upgrade_upstream(HttpsUpstream *upstream);
int start_settings_timer();
void stop_settings_timer();
int handle_ign_data_chunk(size_t len);
int consume(int32_t stream_id, size_t len);
void log_response_headers(Downstream *downstream,
const std::vector<nghttp2_nv>& nva) const;
private:
DownstreamQueue downstream_queue_;
std::unique_ptr<HttpsUpstream> pre_upstream_;
ClientHandler *handler_;
nghttp2_session *session_;
event *settings_timerev_;
// Received DATA frame size while it is not sent to backend before
// any connection-level WINDOW_UPDATE
int32_t recv_ign_window_size_;
bool flow_control_;
};

View File

@ -135,15 +135,25 @@ int HttpDownstreamConnection::push_request_headers()
hdrs += downstream_->get_request_path();
}
} else if(get_config()->http2_proxy &&
!downstream_->get_request_http2_scheme().empty() &&
!downstream_->get_request_http2_authority().empty() &&
downstream_->get_request_path().c_str()[0] == '/') {
!downstream_->get_request_http2_scheme().empty() &&
!downstream_->get_request_http2_authority().empty() &&
(downstream_->get_request_path().c_str()[0] == '/' ||
downstream_->get_request_path() == "*")) {
// Construct absolute-form request target because we are going to
// send a request to a HTTP/1 proxy.
hdrs += downstream_->get_request_http2_scheme();
hdrs += "://";
hdrs += downstream_->get_request_http2_authority();
hdrs += downstream_->get_request_path();
// Server-wide OPTIONS takes following form in proxy request:
//
// OPTIONS http://example.org HTTP/1.1
//
// Notice that no slash after authority. See
// http://tools.ietf.org/html/rfc7230#section-5.3.4
if(downstream_->get_request_path() != "*") {
hdrs += downstream_->get_request_path();
}
} else {
// No proxy case. get_request_path() may be absolute-form but we
// don't care.
@ -408,9 +418,26 @@ namespace {
int htp_hdrs_completecb(http_parser *htp)
{
auto downstream = static_cast<Downstream*>(htp->data);
auto upstream = downstream->get_upstream();
int rv;
downstream->set_response_http_status(htp->status_code);
downstream->set_response_major(htp->http_major);
downstream->set_response_minor(htp->http_minor);
if(downstream->get_non_final_response()) {
// For non-final response code, we just call
// on_downstream_header_complete() without changing response
// state.
rv = upstream->on_downstream_header_complete(downstream);
if(rv != 0) {
return -1;
}
return 0;
}
downstream->set_response_connection_close(!http_should_keep_alive(htp));
downstream->set_response_state(Downstream::HEADER_COMPLETE);
downstream->inspect_http1_response();
@ -418,15 +445,13 @@ int htp_hdrs_completecb(http_parser *htp)
if(downstream->get_upgraded()) {
downstream->set_response_connection_close(true);
}
if(downstream->get_upstream()->on_downstream_header_complete(downstream)
!= 0) {
if(upstream->on_downstream_header_complete(downstream) != 0) {
return -1;
}
if(downstream->get_upgraded()) {
// Upgrade complete, read until EOF in both ends
if(downstream->get_upstream()->resume_read(SHRPX_MSG_BLOCK,
downstream) != 0) {
if(upstream->resume_read(SHRPX_MSG_BLOCK, downstream) != 0) {
return -1;
}
downstream->set_request_state(Downstream::HEADER_COMPLETE);
@ -443,6 +468,9 @@ int htp_hdrs_completecb(http_parser *htp)
// 304 status code with nonzero Content-Length, but without response
// body. See
// http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-20#section-3.3
// TODO It seems that the cases other than HEAD are handled by
// http-parser. Need test.
return downstream->get_request_method() == "HEAD" ||
(100 <= status && status <= 199) || status == 204 ||
status == 304 ? 1 : 0;
@ -505,6 +533,13 @@ namespace {
int htp_msg_completecb(http_parser *htp)
{
auto downstream = static_cast<Downstream*>(htp->data);
if(downstream->get_non_final_response()) {
downstream->reset_response();
return 0;
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
// Block reading another response message from (broken?)
// server. This callback is not called if the connection is

View File

@ -190,16 +190,6 @@ int htp_hdrs_completecb(http_parser *htp)
auto dconn = upstream->get_client_handler()->get_downstream_connection();
if(downstream->get_expect_100_continue()) {
static const char reply_100[] = "HTTP/1.1 100 Continue\r\n\r\n";
if(bufferevent_write(upstream->get_client_handler()->get_bev(),
reply_100, sizeof(reply_100)-1) != 0) {
ULOG(FATAL, upstream) << "bufferevent_write() faild";
delete dconn;
return -1;
}
}
rv = dconn->attach_downstream(downstream);
if(rv != 0) {
@ -416,6 +406,23 @@ int HttpsUpstream::on_write()
int rv = 0;
auto downstream = get_downstream();
if(downstream) {
// We need to postpone detachment until all data are sent so that
// we can notify nghttp2 library all data consumed.
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
auto dconn = downstream->get_downstream_connection();
if(downstream->get_response_connection_close()) {
// Connection close
downstream->set_downstream_connection(nullptr);
delete dconn;
} else {
// Keep-alive
dconn->detach_downstream(downstream);
}
}
rv = downstream->resume_read(SHRPX_NO_BUFFER);
}
return rv;
@ -503,17 +510,20 @@ void https_downstream_readcb(bufferevent *bev, void *ptr)
return;
}
// If pending data exist, we defer detachment to correctly notify
// the all consumed data to nghttp2 library.
if(handler->get_outbuf_length() == 0) {
if(downstream->get_response_connection_close()) {
// Connection close
downstream->set_downstream_connection(nullptr);
if(downstream->get_response_connection_close()) {
// Connection close
downstream->set_downstream_connection(nullptr);
delete dconn;
delete dconn;
dconn = nullptr;
} else {
// Keep-alive
dconn->detach_downstream(downstream);
dconn = nullptr;
} else {
// Keep-alive
dconn->detach_downstream(downstream);
}
}
if(downstream->get_request_state() == Downstream::MSG_COMPLETE) {
@ -740,7 +750,11 @@ Downstream* HttpsUpstream::pop_downstream()
int HttpsUpstream::on_downstream_header_complete(Downstream *downstream)
{
if(LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "HTTP response header completed";
if(downstream->get_non_final_response()) {
DLOG(INFO, downstream) << "HTTP non-final response header";
} else {
DLOG(INFO, downstream) << "HTTP response header completed";
}
}
std::string hdrs = "HTTP/";
@ -759,6 +773,24 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream)
http2::build_http1_headers_from_norm_headers
(hdrs, downstream->get_response_headers());
if(downstream->get_non_final_response()) {
hdrs += "\r\n";
if(LOG_ENABLED(INFO)) {
log_response_headers(hdrs);
}
auto output = bufferevent_get_output(handler_->get_bev());
if(evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) {
ULOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
downstream->clear_response_headers();
return 0;
}
// We check downstream->get_response_connection_close() in case when
// the Content-Length is not available.
if(!downstream->get_request_connection_close() &&
@ -818,17 +850,11 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream)
}
hdrs += "\r\n";
if(LOG_ENABLED(INFO)) {
const char *hdrp;
std::string nhdrs;
if(worker_config.errorlog_tty) {
nhdrs = http::colorizeHeaders(hdrs.c_str());
hdrp = nhdrs.c_str();
} else {
hdrp = hdrs.c_str();
}
ULOG(INFO, this) << "HTTP response headers\n" << hdrp;
log_response_headers(hdrs);
}
auto output = bufferevent_get_output(handler_->get_bev());
if(evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) {
ULOG(FATAL, this) << "evbuffer_add() failed";
@ -911,4 +937,17 @@ int HttpsUpstream::on_downstream_abort_request(Downstream *downstream,
return error_reply(status_code);
}
void HttpsUpstream::log_response_headers(const std::string& hdrs) const
{
const char *hdrp;
std::string nhdrs;
if(worker_config.errorlog_tty) {
nhdrs = http::colorizeHeaders(hdrs.c_str());
hdrp = nhdrs.c_str();
} else {
hdrp = hdrs.c_str();
}
ULOG(INFO, this) << "HTTP response headers\n" << hdrp;
}
} // namespace shrpx

View File

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

View File

@ -886,6 +886,15 @@ spdylay_session* SpdyUpstream::get_http2_session()
// spdylay_session_recv. These calls may delete downstream.
int SpdyUpstream::on_downstream_header_complete(Downstream *downstream)
{
if(downstream->get_non_final_response()) {
// SPDY does not support non-final response. We could send it
// with HEADERS and final response in SYN_REPLY, but it is not
// official way.
downstream->clear_response_headers();
return 0;
}
if(LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "HTTP response header completed";
}

View File

@ -95,6 +95,8 @@ int main(int argc, char* argv[])
test_nghttp2_session_recv_unexpected_continuation) ||
!CU_add_test(pSuite, "session_recv_settings_header_table_size",
test_nghttp2_session_recv_settings_header_table_size) ||
!CU_add_test(pSuite, "session_recv_too_large_frame_length",
test_nghttp2_session_recv_too_large_frame_length) ||
!CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) ||
!CU_add_test(pSuite, "session_add_frame",
test_nghttp2_session_add_frame) ||
@ -255,10 +257,6 @@ int main(int argc, char* argv[])
!CU_add_test(pSuite, "hd_deflate", test_nghttp2_hd_deflate) ||
!CU_add_test(pSuite, "hd_deflate_same_indexed_repr",
test_nghttp2_hd_deflate_same_indexed_repr) ||
!CU_add_test(pSuite, "hd_deflate_common_header_eviction",
test_nghttp2_hd_deflate_common_header_eviction) ||
!CU_add_test(pSuite, "hd_deflate_clear_refset",
test_nghttp2_hd_deflate_clear_refset) ||
!CU_add_test(pSuite, "hd_inflate_indexed",
test_nghttp2_hd_inflate_indexed) ||
!CU_add_test(pSuite, "hd_inflate_indname_noinc",

View File

@ -61,7 +61,7 @@ static nghttp2_nv* headers(void)
return nva;
}
static void check_frame_header(uint16_t length, uint8_t type, uint8_t flags,
static void check_frame_header(size_t length, uint8_t type, uint8_t flags,
int32_t stream_id, nghttp2_frame_hd *hd)
{
CU_ASSERT(length == hd->length);
@ -223,7 +223,7 @@ void test_nghttp2_frame_pack_priority(void)
rv = nghttp2_frame_pack_priority(&bufs, &frame);
CU_ASSERT(0 == rv);
CU_ASSERT(13 == nghttp2_bufs_len(&bufs));
CU_ASSERT(NGHTTP2_FRAME_HDLEN + 5 == nghttp2_bufs_len(&bufs));
CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs));
check_frame_header(5, NGHTTP2_PRIORITY, NGHTTP2_FLAG_NONE,
1000000007, &oframe.hd);
@ -251,7 +251,7 @@ void test_nghttp2_frame_pack_rst_stream(void)
rv = nghttp2_frame_pack_rst_stream(&bufs, &frame);
CU_ASSERT(0 == rv);
CU_ASSERT(12 == nghttp2_bufs_len(&bufs));
CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4 == nghttp2_bufs_len(&bufs));
CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs));
check_frame_header(4, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE, 1000000007,
&oframe.hd);
@ -384,7 +384,7 @@ void test_nghttp2_frame_pack_ping(void)
rv = nghttp2_frame_pack_ping(&bufs, &frame);
CU_ASSERT(0 == rv);
CU_ASSERT(16 == nghttp2_bufs_len(&bufs));
CU_ASSERT(NGHTTP2_FRAME_HDLEN + 8 == nghttp2_bufs_len(&bufs));
CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs));
check_frame_header(8, NGHTTP2_PING, NGHTTP2_FLAG_ACK, 0, &oframe.hd);
CU_ASSERT(memcmp(opaque_data, oframe.opaque_data, sizeof(opaque_data) - 1)
@ -411,7 +411,8 @@ void test_nghttp2_frame_pack_goaway()
rv = nghttp2_frame_pack_goaway(&bufs, &frame);
CU_ASSERT(0 == rv);
CU_ASSERT((ssize_t)(16 + opaque_data_len) == nghttp2_bufs_len(&bufs));
CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 8 + opaque_data_len) ==
nghttp2_bufs_len(&bufs));
CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs));
check_frame_header(24, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, &oframe.hd);
CU_ASSERT(1000000007 == oframe.last_stream_id);
@ -453,7 +454,7 @@ void test_nghttp2_frame_pack_window_update(void)
rv = nghttp2_frame_pack_window_update(&bufs, &frame);
CU_ASSERT(0 == rv);
CU_ASSERT(12 == nghttp2_bufs_len(&bufs));
CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4 == nghttp2_bufs_len(&bufs));
CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs));
check_frame_header(4, NGHTTP2_WINDOW_UPDATE, NGHTTP2_FLAG_NONE,
1000000007, &oframe.hd);
@ -541,7 +542,7 @@ void test_nghttp2_frame_pack_altsvc(void)
/* Check no origin case */
payloadlen = NGHTTP2_ALTSVC_MINLEN + protocol_id_len + host_len;
nghttp2_put_uint16be(buf->pos, payloadlen);
nghttp2_put_uint32be(buf->pos, (uint32_t)((payloadlen << 8) + buf->pos[3]));
oframe.payload = &oaltsvc;
@ -562,7 +563,7 @@ void test_nghttp2_frame_pack_altsvc(void)
/* Check insufficient payload length for host */
payloadlen = NGHTTP2_ALTSVC_MINLEN + protocol_id_len + host_len - 1;
nghttp2_put_uint16be(buf->pos, payloadlen);
nghttp2_put_uint32be(buf->pos, (uint32_t)((payloadlen << 8) + buf->pos[3]));
oframe.payload = &oaltsvc;
@ -579,7 +580,7 @@ void test_nghttp2_frame_pack_altsvc(void)
/* Check no host case */
payloadlen = NGHTTP2_ALTSVC_MINLEN + protocol_id_len;
nghttp2_put_uint16be(buf->pos, payloadlen);
nghttp2_put_uint32be(buf->pos, (uint32_t)((payloadlen << 8) + buf->pos[3]));
buf->pos[NGHTTP2_FRAME_HDLEN + NGHTTP2_ALTSVC_FIXED_PARTLEN
+ protocol_id_len] = 0;
@ -602,7 +603,7 @@ void test_nghttp2_frame_pack_altsvc(void)
/* Check missing Host-Len */
payloadlen = NGHTTP2_ALTSVC_FIXED_PARTLEN + protocol_id_len;
nghttp2_put_uint16be(buf->pos, payloadlen);
nghttp2_put_uint32be(buf->pos, (uint32_t)((payloadlen << 8) + buf->pos[3]));
oframe.payload = &oaltsvc;
@ -699,13 +700,30 @@ void test_nghttp2_iv_check(void)
iv[1].value = 3;
CU_ASSERT(!nghttp2_iv_check(iv, 2));
/* Undefined SETTINGS ID */
/* Undefined SETTINGS ID is allowed */
iv[1].settings_id = 1000000009;
iv[1].value = 0;
CU_ASSERT(!nghttp2_iv_check(iv, 2));
CU_ASSERT(nghttp2_iv_check(iv, 2));
/* Too large SETTINGS_HEADER_TABLE_SIZE */
iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
iv[1].value = UINT32_MAX;
CU_ASSERT(!nghttp2_iv_check(iv, 2));
/* Too small SETTINGS_MAX_FRAME_SIZE */
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MIN - 1;
CU_ASSERT(!nghttp2_iv_check(iv, 1));
/* Too large SETTINGS_MAX_FRAME_SIZE */
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MAX + 1;
CU_ASSERT(!nghttp2_iv_check(iv, 1));
/* Max and min SETTINGS_MAX_FRAME_SIZE */
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MIN;
iv[1].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
iv[1].value = NGHTTP2_MAX_FRAME_SIZE_MAX;
CU_ASSERT(nghttp2_iv_check(iv, 2));
}

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_inflate_init(&inflater));
/* Encode 2 same headers. cookie:alpha is not in the reference set,
so first emit literal repr and then 2 emits of indexed repr. */
/* Encode 2 same headers. Emit 1 literal reprs and 1 index repr. */
rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva1, ARRLEN(nva1));
blocklen = nghttp2_bufs_len(&bufs);
@ -174,13 +173,12 @@ void test_nghttp2_hd_deflate_same_indexed_repr(void)
nva_out_reset(&out);
nghttp2_bufs_reset(&bufs);
/* Encode 3 same headers. This time, cookie:alpha is in the
reference set, so the encoder emits indexed repr 6 times */
/* Encode 3 same headers. This time, emits 3 index reprs. */
rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, ARRLEN(nva2));
blocklen = nghttp2_bufs_len(&bufs);
CU_ASSERT(0 == rv);
CU_ASSERT(blocklen == 6);
CU_ASSERT(blocklen == 3);
CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
CU_ASSERT(3 == out.nvlen);
@ -195,120 +193,6 @@ void test_nghttp2_hd_deflate_same_indexed_repr(void)
nghttp2_hd_deflate_free(&deflater);
}
void test_nghttp2_hd_deflate_common_header_eviction(void)
{
nghttp2_hd_deflater deflater;
nghttp2_hd_inflater inflater;
nghttp2_nv nva[] = {MAKE_NV("h1", ""),
MAKE_NV("h2", "")};
nghttp2_bufs bufs;
ssize_t blocklen;
/* Default header table capacity is 4096. Adding 2 byte header name
and 4060 byte value, which is 4094 bytes including overhead, to
the table evicts first entry. */
uint8_t value[3038];
nva_out out;
size_t i;
int rv;
frame_pack_bufs_init(&bufs);
nva_out_init(&out);
memset(value, '0', sizeof(value));
for(i = 0; i < 2; ++i) {
nva[i].value = value;
nva[i].valuelen = sizeof(value);
}
nghttp2_hd_deflate_init(&deflater);
nghttp2_hd_inflate_init(&inflater);
/* First emit "h1: ..." to put it in the reference set (index
= 0). */
rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 1);
blocklen = nghttp2_bufs_len(&bufs);
CU_ASSERT(0 == rv);
CU_ASSERT(blocklen > 0);
CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
CU_ASSERT(1 == out.nvlen);
nghttp2_nv_array_sort(nva, 1);
assert_nv_equal(nva, out.nva, 1);
nva_out_reset(&out);
nghttp2_bufs_reset(&bufs);
/* Encode with second header */
rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2);
blocklen = nghttp2_bufs_len(&bufs);
CU_ASSERT(0 == rv);
CU_ASSERT(blocklen > 0);
/* Check common header "h1: ...:, which is removed from the
header table because of eviction, is still emitted by the
inflater */
CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
CU_ASSERT(2 == out.nvlen);
nghttp2_nv_array_sort(nva, 2);
assert_nv_equal(nva, out.nva, 2);
nva_out_reset(&out);
nghttp2_bufs_reset(&bufs);
CU_ASSERT(1 == deflater.ctx.hd_table.len);
CU_ASSERT(1 == inflater.ctx.hd_table.len);
nghttp2_bufs_free(&bufs);
nghttp2_hd_inflate_free(&inflater);
nghttp2_hd_deflate_free(&deflater);
}
void test_nghttp2_hd_deflate_clear_refset(void)
{
nghttp2_hd_deflater deflater;
nghttp2_hd_inflater inflater;
nghttp2_bufs bufs;
ssize_t blocklen;
nghttp2_nv nv[] = {
MAKE_NV(":path", "/"),
MAKE_NV(":scheme", "http")
};
size_t i;
nva_out out;
int rv;
frame_pack_bufs_init(&bufs);
nva_out_init(&out);
nghttp2_hd_deflate_init2(&deflater,
NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE);
nghttp2_hd_deflate_set_no_refset(&deflater, 1);
nghttp2_hd_inflate_init(&inflater);
for(i = 0; i < 2; ++i) {
rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nv, ARRLEN(nv));
blocklen = nghttp2_bufs_len(&bufs);
CU_ASSERT(0 == rv);
CU_ASSERT(blocklen > 1);
CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
CU_ASSERT(ARRLEN(nv) == out.nvlen);
assert_nv_equal(nv, out.nva, ARRLEN(nv));
nva_out_reset(&out);
nghttp2_bufs_reset(&bufs);
}
nghttp2_bufs_free(&bufs);
nghttp2_hd_inflate_free(&inflater);
nghttp2_hd_deflate_free(&deflater);
}
void test_nghttp2_hd_inflate_indexed(void)
{
nghttp2_hd_inflater inflater;
@ -410,9 +294,11 @@ void test_nghttp2_hd_inflate_indname_inc(void)
CU_ASSERT(1 == out.nvlen);
assert_nv_equal(&nv, out.nva, 1);
CU_ASSERT(1 == inflater.ctx.hd_table.len);
assert_nv_equal(&nv,
&GET_TABLE_ENT(&inflater.ctx,
inflater.ctx.hd_table.len-1)->nv, 1);
assert_nv_equal
(&nv,
&GET_TABLE_ENT
(&inflater.ctx,
NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len-1)->nv, 1);
nva_out_reset(&out);
nghttp2_bufs_free(&bufs);
@ -459,7 +345,6 @@ void test_nghttp2_hd_inflate_indname_inc_eviction(void)
nghttp2_bufs_reset(&bufs);
CU_ASSERT(3 == inflater.ctx.hd_table.len);
CU_ASSERT(GET_TABLE_ENT(&inflater.ctx, 0)->flags & NGHTTP2_HD_FLAG_REFSET);
nghttp2_bufs_free(&bufs);
nghttp2_hd_inflate_free(&inflater);
@ -530,9 +415,11 @@ void test_nghttp2_hd_inflate_newname_inc(void)
CU_ASSERT(1 == out.nvlen);
assert_nv_equal(&nv, out.nva, 1);
CU_ASSERT(1 == inflater.ctx.hd_table.len);
assert_nv_equal(&nv,
&GET_TABLE_ENT(&inflater.ctx,
inflater.ctx.hd_table.len-1)->nv, 1);
assert_nv_equal
(&nv,
&GET_TABLE_ENT
(&inflater.ctx,
NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len-1)->nv, 1);
nva_out_reset(&out);
nghttp2_bufs_free(&bufs);
@ -696,8 +583,9 @@ void test_nghttp2_hd_change_table_size(void)
{
nghttp2_hd_deflater deflater;
nghttp2_hd_inflater inflater;
nghttp2_nv nva[] = { MAKE_NV(":method", "GET"),
MAKE_NV(":path", "/") };
nghttp2_nv nva[] = { MAKE_NV("alpha", "bravo"),
MAKE_NV("charlie", "delta") };
nghttp2_nv nva2[] = { MAKE_NV(":path", "/") };
nghttp2_bufs bufs;
ssize_t rv;
nva_out out;
@ -909,6 +797,36 @@ void test_nghttp2_hd_change_table_size(void)
nghttp2_hd_inflate_free(&inflater);
nghttp2_hd_deflate_free(&deflater);
/* Check that context update emitted twice */
nghttp2_hd_deflate_init2(&deflater, 4096);
nghttp2_hd_inflate_init(&inflater);
CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 0));
CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 3000));
CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 0));
CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 3000));
CU_ASSERT(0 == deflater.min_hd_table_bufsize_max);
CU_ASSERT(3000 == deflater.ctx.hd_table_bufsize_max);
rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, 1);
blocklen = nghttp2_bufs_len(&bufs);
CU_ASSERT(0 == rv);
CU_ASSERT(3 < blocklen);
CU_ASSERT(3000 == deflater.ctx.hd_table_bufsize_max);
CU_ASSERT(UINT32_MAX == deflater.min_hd_table_bufsize_max);
CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
CU_ASSERT(3000 == inflater.ctx.hd_table_bufsize_max);
CU_ASSERT(3000 == inflater.settings_hd_table_bufsize_max);
nva_out_reset(&out);
nghttp2_bufs_reset(&bufs);
nghttp2_hd_inflate_free(&inflater);
nghttp2_hd_deflate_free(&deflater);
nghttp2_bufs_free(&bufs);
}
@ -1163,7 +1081,7 @@ void test_nghttp2_hd_deflate_bound(void)
bound = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva));
CU_ASSERT(1 + 6 + 6 * 2 * 2 +
CU_ASSERT(12 + 6 * 2 * 2 +
nva[0].namelen + nva[0].valuelen +
nva[1].namelen + nva[1].valuelen
== bound);
@ -1174,7 +1092,7 @@ void test_nghttp2_hd_deflate_bound(void)
bound2 = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva));
CU_ASSERT(bound + 2 == bound2);
CU_ASSERT(bound == bound2);
nghttp2_bufs_free(&bufs);
nghttp2_hd_deflate_free(&deflater);

View File

@ -27,8 +27,6 @@
void test_nghttp2_hd_deflate(void);
void test_nghttp2_hd_deflate_same_indexed_repr(void);
void test_nghttp2_hd_deflate_common_header_eviction(void);
void test_nghttp2_hd_deflate_clear_refset(void);
void test_nghttp2_hd_inflate_indexed(void);
void test_nghttp2_hd_inflate_indname_noinc(void);
void test_nghttp2_hd_inflate_indname_inc(void);

View File

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

View File

@ -450,7 +450,9 @@ void test_nghttp2_session_recv(void)
assert(nghttp2_buf_len(&bufs.cur->buf) >= 16);
bufs.cur->buf.last += 16;
nghttp2_put_uint16be(bufs.cur->buf.pos, frame.hd.length + 16);
nghttp2_put_uint32be(bufs.cur->buf.pos,
(uint32_t)(((frame.hd.length + 16) << 8) +
bufs.cur->buf.pos[3]));
nghttp2_frame_ping_free(&frame.ping);
@ -610,8 +612,8 @@ void test_nghttp2_session_recv_data(void)
error. This is not mandated by the spec */
ud.data_chunk_recv_cb_called = 0;
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, 8+4096);
CU_ASSERT(8+4096 == rv);
rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
CU_ASSERT(0 == ud.data_chunk_recv_cb_called);
CU_ASSERT(0 == ud.frame_recv_cb_called);
@ -633,8 +635,8 @@ void test_nghttp2_session_recv_data(void)
ud.data_chunk_recv_cb_called = 0;
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, 8+4096);
CU_ASSERT(8+4096 == rv);
rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
CU_ASSERT(0 == ud.data_chunk_recv_cb_called);
CU_ASSERT(0 == ud.frame_recv_cb_called);
@ -646,8 +648,8 @@ void test_nghttp2_session_recv_data(void)
ud.data_chunk_recv_cb_called = 0;
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, 8+4096);
CU_ASSERT(8+4096 == rv);
rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
CU_ASSERT(1 == ud.data_chunk_recv_cb_called);
CU_ASSERT(1 == ud.frame_recv_cb_called);
@ -656,8 +658,8 @@ void test_nghttp2_session_recv_data(void)
ud.data_chunk_recv_cb_called = 0;
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, 8+4096);
CU_ASSERT(8+4096 == rv);
rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
/* Now we got data more than initial-window-size / 2, WINDOW_UPDATE
must be queued */
@ -676,8 +678,8 @@ void test_nghttp2_session_recv_data(void)
DATA. Additional 4 DATA frames, connection flow control will kick
in. */
for(i = 0; i < 5; ++i) {
rv = nghttp2_session_mem_recv(session, data, 8+4096);
CU_ASSERT(8+4096 == rv);
rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
}
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item));
@ -693,8 +695,8 @@ void test_nghttp2_session_recv_data(void)
ud.data_chunk_recv_cb_called = 0;
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, 8+4096);
CU_ASSERT(8+4096 == rv);
rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
CU_ASSERT(0 == ud.data_chunk_recv_cb_called);
CU_ASSERT(0 == ud.frame_recv_cb_called);
@ -753,11 +755,11 @@ void test_nghttp2_session_recv_continuation(void)
nghttp2_frame_headers_free(&frame.headers);
/* HEADERS's payload is 1 byte */
memcpy(data, buf->pos, 9);
datalen = 9;
buf->pos += 9;
memcpy(data, buf->pos, NGHTTP2_FRAME_HDLEN + 1);
datalen = NGHTTP2_FRAME_HDLEN + 1;
buf->pos += NGHTTP2_FRAME_HDLEN + 1;
nghttp2_put_uint16be(data, 1);
nghttp2_put_uint32be(data, (1 << 8) + data[3]);
/* First CONTINUATION, 2 bytes */
cont_hd.length = 2;
@ -925,14 +927,14 @@ void test_nghttp2_session_recv_headers_with_priority(void)
rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
CU_ASSERT(0 == rv);
CU_ASSERT(13 == nghttp2_bufs_len(&bufs));
CU_ASSERT(NGHTTP2_FRAME_HDLEN + 5 + 2 == nghttp2_bufs_len(&bufs));
nghttp2_frame_headers_free(&frame.headers);
buf = &bufs.head->buf;
/* Make payload shorter than required length to store priroty
group */
nghttp2_put_uint16be(buf->pos, 4);
nghttp2_put_uint32be(buf->pos, (4 << 8) + buf->pos[3]);
ud.frame_recv_cb_called = 0;
@ -1043,7 +1045,8 @@ void test_nghttp2_session_recv_premature_headers(void)
assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
/* Intentionally feed payload cutting last 1 byte off */
nghttp2_put_uint16be(buf->pos, frame.hd.length - 1);
nghttp2_put_uint32be(buf->pos,
(uint32_t)(((frame.hd.length - 1) << 8) + buf->pos[3]));
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf) - 1);
CU_ASSERT((ssize_t)(nghttp2_buf_len(buf) - 1) == rv);
@ -1138,7 +1141,7 @@ void test_nghttp2_session_recv_altsvc(void)
CU_ASSERT(NGHTTP2_EXT_ALTSVC == ud.recv_frame_type);
/* premature payload */
nghttp2_put_uint16be(buf->pos, 8);
nghttp2_put_uint32be(buf->pos, (8 << 8) + buf->pos[3]);
ud.frame_recv_cb_called = 0;
@ -1407,6 +1410,36 @@ void test_nghttp2_session_recv_settings_header_table_size(void)
nghttp2_session_del(session);
}
void test_nghttp2_session_recv_too_large_frame_length(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
uint8_t buf[NGHTTP2_FRAME_HDLEN];
nghttp2_outbound_item *item;
nghttp2_frame_hd hd = {
/* Initial max frame size is NGHTTP2_MAX_FRAME_SIZE_MIN */
NGHTTP2_MAX_FRAME_SIZE_MIN + 1,
1,
NGHTTP2_HEADERS,
NGHTTP2_FLAG_NONE
};
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
nghttp2_session_server_new(&session, &callbacks, NULL);
nghttp2_frame_pack_frame_hd(buf, &hd);
CU_ASSERT(sizeof(buf) ==
nghttp2_session_mem_recv(session, buf, sizeof(buf)));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(item != NULL);
CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item));
nghttp2_session_del(session);
}
void test_nghttp2_session_continue(void)
{
nghttp2_session *session;
@ -1642,11 +1675,11 @@ void test_nghttp2_session_add_frame(void)
aux_data));
CU_ASSERT(0 == nghttp2_pq_empty(&session->ob_ss_pq));
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(NGHTTP2_HEADERS == acc.buf[2]);
CU_ASSERT(NGHTTP2_HEADERS == acc.buf[3]);
CU_ASSERT((NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY) ==
acc.buf[3]);
acc.buf[4]);
/* check stream id */
CU_ASSERT(1 == nghttp2_get_uint32(&acc.buf[4]));
CU_ASSERT(1 == nghttp2_get_uint32(&acc.buf[5]));
nghttp2_session_del(session);
}
@ -2159,6 +2192,25 @@ void test_nghttp2_session_on_settings_received(void)
nghttp2_frame_settings_free(&frame.settings);
nghttp2_session_del(session);
/* Check too large SETTINGS_MAX_FRAME_SIZE */
nghttp2_session_server_new(&session, &callbacks, NULL);
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MAX + 1;
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE,
dup_iv(iv, 1), 1);
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(item != NULL);
CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item));
nghttp2_frame_settings_free(&frame.settings);
nghttp2_session_del(session);
}
void test_nghttp2_session_on_push_promise_received(void)
@ -2937,8 +2989,7 @@ void test_nghttp2_submit_data(void)
&pri_spec_default, NGHTTP2_STREAM_OPENING,
NULL);
CU_ASSERT(0 == nghttp2_submit_data(session,
NGHTTP2_FLAG_END_STREAM |
NGHTTP2_FLAG_END_SEGMENT, 1, &data_prd));
NGHTTP2_FLAG_END_STREAM, 1, &data_prd));
ud.block_count = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
@ -2949,8 +3000,7 @@ void test_nghttp2_submit_data(void)
CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags);
/* frame->hd.flags has these flags */
CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT) ==
data_frame->hd.flags);
CU_ASSERT(NGHTTP2_FLAG_END_STREAM == data_frame->hd.flags);
nghttp2_session_del(session);
}
@ -3433,8 +3483,9 @@ void test_nghttp2_submit_settings(void)
my_user_data ud;
nghttp2_outbound_item *item;
nghttp2_frame *frame;
nghttp2_settings_entry iv[6];
nghttp2_settings_entry iv[7];
nghttp2_frame ack_frame;
const int32_t UNKNOWN_ID = 1000000007;
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
iv[0].value = 5;
@ -3448,8 +3499,11 @@ void test_nghttp2_submit_settings(void)
iv[3].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
iv[3].value = 0;
iv[4].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
iv[4].value = (uint32_t)NGHTTP2_MAX_WINDOW_SIZE + 1;
iv[4].settings_id = UNKNOWN_ID;
iv[4].value = 999;
iv[5].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
iv[5].value = (uint32_t)NGHTTP2_MAX_WINDOW_SIZE + 1;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
@ -3457,7 +3511,7 @@ void test_nghttp2_submit_settings(void)
nghttp2_session_server_new(&session, &callbacks, &ud);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 5));
nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 6));
/* Make sure that local settings are not changed */
CU_ASSERT(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS ==
@ -3465,15 +3519,15 @@ void test_nghttp2_submit_settings(void)
CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE ==
session->local_settings.initial_window_size);
/* Now sends without 5th one */
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 4));
/* Now sends without 6th one */
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 5));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_SETTINGS == OB_CTRL_TYPE(item));
frame = item->frame;
CU_ASSERT(4 == frame->settings.niv);
CU_ASSERT(5 == frame->settings.niv);
CU_ASSERT(5 == frame->settings.iv[0].value);
CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS ==
frame->settings.iv[0].settings_id);
@ -3482,6 +3536,9 @@ void test_nghttp2_submit_settings(void)
CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE ==
frame->settings.iv[1].settings_id);
CU_ASSERT(UNKNOWN_ID == frame->settings.iv[4].settings_id);
CU_ASSERT(999 == frame->settings.iv[4].value);
ud.frame_send_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == ud.frame_send_cb_called);
@ -4759,25 +4816,13 @@ void test_nghttp2_session_set_option(void)
nghttp2_option_new(&option);
nghttp2_option_set_no_auto_stream_window_update(option, 1);
nghttp2_option_set_no_auto_window_update(option, 1);
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
nghttp2_session_client_new2(&session, &callbacks, NULL, option);
CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE);
CU_ASSERT(!(session->opt_flags &
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE));
nghttp2_session_del(session);
CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE);
nghttp2_option_set_no_auto_stream_window_update(option, 0);
nghttp2_option_set_no_auto_connection_window_update(option, 1);
nghttp2_session_server_new2(&session, &callbacks, NULL, option);
CU_ASSERT(!(session->opt_flags &
NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE));
CU_ASSERT(session->opt_flags &
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE);
nghttp2_session_del(session);
nghttp2_option_set_peer_max_concurrent_streams(option, 100);

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_unexpected_continuation(void);
void test_nghttp2_session_recv_settings_header_table_size(void);
void test_nghttp2_session_recv_too_large_frame_length(void);
void test_nghttp2_session_continue(void);
void test_nghttp2_session_add_frame(void);
void test_nghttp2_session_on_request_headers_received(void);