From f7162ab702454f36ca858ced08e4c622762226f2 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 26 Mar 2014 02:04:24 +0900 Subject: [PATCH] Implement dependency based priority --- examples/client.c | 3 +- examples/libevent-client.c | 2 +- lib/Makefile.am | 6 +- lib/includes/nghttp2/nghttp2.h | 182 ++++- lib/nghttp2_frame.c | 116 ++- lib/nghttp2_frame.h | 31 +- lib/nghttp2_outbound_item.h | 17 +- lib/nghttp2_priority_spec.c | 41 + lib/nghttp2_priority_spec.h | 34 + lib/nghttp2_session.c | 737 ++++++++++++++---- lib/nghttp2_session.h | 50 +- lib/nghttp2_stream.c | 556 +++++++++++++- lib/nghttp2_stream.h | 196 ++++- lib/nghttp2_submit.c | 117 ++- src/HtmlParser.cc | 2 +- src/app_helper.cc | 53 +- src/h2load_http2_session.cc | 2 +- src/nghttp.cc | 191 ++++- src/shrpx_http2_session.cc | 15 +- src/shrpx_http2_upstream.cc | 14 +- src/shrpx_https_upstream.cc | 3 +- tests/main.c | 14 + tests/nghttp2_frame_test.c | 122 ++- tests/nghttp2_session_test.c | 1273 +++++++++++++++++++++++++++----- tests/nghttp2_session_test.h | 7 + tests/nghttp2_test_helper.c | 45 ++ tests/nghttp2_test_helper.h | 11 + 27 files changed, 3319 insertions(+), 521 deletions(-) create mode 100644 lib/nghttp2_priority_spec.c create mode 100644 lib/nghttp2_priority_spec.h diff --git a/examples/client.c b/examples/client.c index f7497f52..c1c77f62 100644 --- a/examples/client.c +++ b/examples/client.c @@ -521,7 +521,6 @@ static void ctl_poll(struct pollfd *pollfd, struct Connection *connection) */ static void submit_request(struct Connection *connection, struct Request *req) { - int pri = 0; int rv; const nghttp2_nv nva[] = { /* Make sure that the last item is NULL */ @@ -532,7 +531,7 @@ static void submit_request(struct Connection *connection, struct Request *req) MAKE_NV("accept", "*/*"), MAKE_NV("user-agent", "nghttp2/"NGHTTP2_VERSION) }; - rv = nghttp2_submit_request(connection->session, pri, + rv = nghttp2_submit_request(connection->session, NULL, nva, sizeof(nva)/sizeof(nva[0]), NULL, req); if(rv != 0) { diec("nghttp2_submit_request", rv); diff --git a/examples/libevent-client.c b/examples/libevent-client.c index 8c319fda..5699a048 100644 --- a/examples/libevent-client.c +++ b/examples/libevent-client.c @@ -402,7 +402,7 @@ static void submit_request(http2_session_data *session_data) }; fprintf(stderr, "Request headers:\n"); print_headers(stderr, hdrs, ARRLEN(hdrs)); - rv = nghttp2_submit_request(session_data->session, NGHTTP2_PRI_DEFAULT, + rv = nghttp2_submit_request(session_data->session, NULL, hdrs, ARRLEN(hdrs), NULL, stream_data); if(rv != 0) { errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(rv)); diff --git a/lib/Makefile.am b/lib/Makefile.am index 933e5453..cd419557 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -39,7 +39,8 @@ OBJECTS = nghttp2_pq.c nghttp2_map.c nghttp2_queue.c \ nghttp2_helper.c \ nghttp2_npn.c nghttp2_gzip.c \ nghttp2_hd.c nghttp2_hd_huffman.c nghttp2_hd_huffman_data.c \ - nghttp2_version.c + nghttp2_version.c \ + nghttp2_priority_spec.c HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \ nghttp2_frame.h \ @@ -48,7 +49,8 @@ HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \ nghttp2_npn.h nghttp2_gzip.h \ nghttp2_submit.h nghttp2_outbound_item.h \ nghttp2_net.h \ - nghttp2_hd.h nghttp2_hd_huffman.h + nghttp2_hd.h nghttp2_hd_huffman.h \ + nghttp2_priority_spec.h libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS) libnghttp2_la_LDFLAGS = -no-undefined \ diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index e8ccd159..94713dbf 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -97,15 +97,16 @@ typedef struct { /** * @macro * - * The default priority value + * The default weight of priority group. */ -#define NGHTTP2_PRI_DEFAULT (1 << 30) +#define NGHTTP2_DEFAULT_WEIGHT 16 + /** * @macro * - * The lowest priority value + * The maximum weight of priority group. */ -#define NGHTTP2_PRI_LOWEST ((1U << 31) - 1) +#define NGHTTP2_MAX_WEIGHT 255 /** * @macro @@ -278,6 +279,11 @@ typedef enum { * The server push is disabled. */ NGHTTP2_ERR_PUSH_DISABLED = -528, + /** + * DATA frame for a given stream has been already submitted and has + * not been fully processed yet. + */ + NGHTTP2_ERR_DATA_EXIST = -529, /** * The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is * under unexpected condition and cannot process any further data @@ -390,10 +396,6 @@ typedef enum { * The END_HEADERS flag. */ NGHTTP2_FLAG_END_HEADERS = 0x4, - /** - * The PRIORITY flag. - */ - NGHTTP2_FLAG_PRIORITY = 0x8, /** * The ACK flag. */ @@ -405,11 +407,19 @@ typedef enum { /** * The PAD_LOW flag. */ - NGHTTP2_FLAG_PAD_LOW = 0x10, + NGHTTP2_FLAG_PAD_LOW = 0x08, /** * The PAD_HIGH flag. */ - NGHTTP2_FLAG_PAD_HIGH = 0x20 + NGHTTP2_FLAG_PAD_HIGH = 0x10, + /** + * The PRIORITY_GROUP flag. + */ + NGHTTP2_FLAG_PRIORITY_GROUP = 0x20, + /** + * The PRIORITY_DEPENDENCY flag. + */ + NGHTTP2_FLAG_PRIORITY_DEPENDENCY = 0x40 } nghttp2_flag; /** @@ -634,6 +644,85 @@ typedef enum { NGHTTP2_HCAT_HEADERS = 3 } nghttp2_headers_category; +/** + * @enum + * + * The type of priority specified in :type:`nghttp2_priority_spec`. + */ +typedef enum { + /** + * No priority is given. + */ + NGHTTP2_PRIORITY_TYPE_NONE, + /** + * Priority group ID and its weight are specified. + */ + NGHTTP2_PRIORITY_TYPE_GROUP, + /** + * The stream ID of a stream to depend on and its exclusive flag is + * specified. + */ + NGHTTP2_PRIORITY_TYPE_DEP +} nghttp2_priority_type; + +/** + * @struct + * + * This structure stores priority group ID and its weight. + */ +typedef struct { + /** + * The priority group ID + */ + int32_t pri_group_id; + /** + * The weight of the priority group + */ + uint8_t weight; +} nghttp2_priority_group; + +/** + * @struct + * + * This structure stores stream ID of the stream to depend on and its + * dependency is exclusive or not. + */ +typedef struct { + /** + * The stream ID of the stream to depend on. + */ + int32_t stream_id; + /** + * nonzero means exclusive dependency + */ + uint8_t exclusive; +} nghttp2_priority_dep; + +/** + * @struct + * + * The structure to specify stream dependency. To specify stream + * dependency, specify |pri_type| and fill the |group| or |dep| member + * according to |pri_type|. + */ +typedef struct { + /** + * Type of priority specification. If |pri_type| is + * :enum:`NGHTTP2_PRIORITY_TYPE_GROUP`, fill |group|. If |pri_type| + * is :enum:`NGHTTP2_PRIORITY_TYPE_DEP`, fill |dep|. If |pri_type| + * is :enum:`NGHTTP2_PRIORITY_TYPE_NONE`, the other data members are + * ignored and it means that default priority group ID (which is + * same as the stream ID) and default weight + * :macro:`NGHTTP2_DEFAULT_WEIGHT` are specified. + */ + nghttp2_priority_type pri_type; + + union { + nghttp2_priority_group group; + nghttp2_priority_dep dep; + }; +} nghttp2_priority_spec; + /** * @struct * The HEADERS frame. It has the following members: @@ -648,6 +737,10 @@ typedef struct { * and PAD_LOW. */ size_t padlen; + /** + * The priority specification + */ + nghttp2_priority_spec pri_spec; /** * The name/value pairs. */ @@ -660,10 +753,6 @@ typedef struct { * The category of this HEADERS frame. */ nghttp2_headers_category cat; - /** - * The priority. - */ - int32_t pri; } nghttp2_headers; /** @@ -676,9 +765,9 @@ typedef struct { */ nghttp2_frame_hd hd; /** - * The priority. + * The priority specification. */ - int32_t pri; + nghttp2_priority_spec pri_spec; } nghttp2_priority; /** @@ -1883,13 +1972,39 @@ ssize_t nghttp2_pack_settings_payload(uint8_t *buf, */ const char* nghttp2_strerror(int lib_error_code); +/** + * @function + * + * Initializes |pri_spec| with priority group ID |pri_group_id| and + * its weight |weight|. To specify weight for the default priority + * group (which is the same as the stream ID of the stream) in + * `nghttp2_submit_request()` and `nghttp2_submit_headers()` and its + * stream ID is not known in advance, specify -1 to |pri_group_id|. + */ +void nghttp2_priority_spec_group_init(nghttp2_priority_spec *pri_spec, + int32_t pri_group_id, uint8_t weight); + +/** + * @function + * + * Initializes |pri_spec| with the |stream_id| of the stream to depend + * on and its exclusive flag. If |exclusive| is nonzero, exclusive + * flag is set. + */ +void nghttp2_priority_spec_dep_init(nghttp2_priority_spec *pri_spec, + int32_t stream_id, int exclusive); + /** * @function * * Submits HEADERS frame and optionally one or more DATA frames. * - * The |pri| is priority of this request. 0 is the highest priority - * value and :macro:`NGHTTP2_PRI_LOWEST` is the lowest value. + * The |pri_spec| is priority specification of this request. ``NULL`` + * means the default priority (priority group ID becomes its stream ID + * and weight is :macro:`NGHTTP2_DEFAULT_WEIGHT). To specify the + * priority, use either `nghttp2_priority_spec_group_init()` or + * `nghttp2_priority_spec_dep_init()`. If |pri_spec| is not ``NULL``, + * this function will copy its data members. * * The |nva| is an array of name/value pair :type:`nghttp2_nv` with * |nvlen| elements. The value is opaque sequence of bytes and @@ -1931,12 +2046,11 @@ const char* nghttp2_strerror(int lib_error_code); * This function returns 0 if it succeeds, or one of the following * negative error codes: * - * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` - * The |pri| is invalid * :enum:`NGHTTP2_ERR_NOMEM` * Out of memory. */ -int nghttp2_submit_request(nghttp2_session *session, int32_t pri, +int nghttp2_submit_request(nghttp2_session *session, + const nghttp2_priority_spec *pri_spec, const nghttp2_nv *nva, size_t nvlen, const nghttp2_data_provider *data_prd, void *stream_user_data); @@ -1990,7 +2104,6 @@ int nghttp2_submit_response(nghttp2_session *session, * following values: * * * :enum:`NGHTTP2_FLAG_END_STREAM` - * * :enum:`NGHTTP2_FLAG_PRIORITY` * * If |flags| includes :enum:`NGHTTP2_FLAG_END_STREAM`, this frame has * END_STREAM flag set. @@ -2004,7 +2117,12 @@ int nghttp2_submit_response(nghttp2_session *session, * actual stream ID is assigned just before the frame is sent. For * response, specify stream ID in |stream_id|. * - * The |pri| is priority of this request. + * The |pri_spec| is priority specification of this request. ``NULL`` + * means the default priority (priority group ID becomes its stream ID + * and weight is :macro:`NGHTTP2_DEFAULT_WEIGHT). To specify the + * priority, use either `nghttp2_priority_spec_group_init()` or + * `nghttp2_priority_spec_dep_init()`. If |pri_spec| is not ``NULL``, + * this function will copy its data members. * * The |nva| is an array of name/value pair :type:`nghttp2_nv` with * |nvlen| elements. The value is opaque sequence of bytes and @@ -2028,13 +2146,12 @@ int nghttp2_submit_response(nghttp2_session *session, * This function returns 0 if it succeeds, or one of the following * negative error codes: * - * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` - * The |pri| is invalid * :enum:`NGHTTP2_ERR_NOMEM` * Out of memory. */ int nghttp2_submit_headers(nghttp2_session *session, uint8_t flags, - int32_t stream_id, int32_t pri, + int32_t stream_id, + const nghttp2_priority_spec *pri_spec, const nghttp2_nv *nva, size_t nvlen, void *stream_user_data); @@ -2064,21 +2181,28 @@ int nghttp2_submit_data(nghttp2_session *session, uint8_t flags, * @function * * Submits PRIORITY frame to change the priority of stream |stream_id| - * to the priority value |pri|. + * to the priority specification |pri_spec|. * * The |flags| is currently ignored and should be * :enum:`NGHTTP2_FLAG_NONE`. * + * The |pri_spec| is priority specification of this request. ``NULL`` + * is not allowed for this function. To specify the priority, use + * either `nghttp2_priority_spec_group_init()` or + * `nghttp2_priority_spec_dep_init()`. This function will copy its + * data members. + * * 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 |pri| is negative. + * The |pri_spec| is NULL; or trying to depend on itself. */ int nghttp2_submit_priority(nghttp2_session *session, uint8_t flags, - int32_t stream_id, int32_t pri); + int32_t stream_id, + const nghttp2_priority_spec *pri_spec); /** * @function diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 81c760b9..390e598b 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -31,6 +31,7 @@ #include "nghttp2_helper.h" #include "nghttp2_net.h" +#include "nghttp2_priority_spec.h" int nghttp2_frame_is_data_frame(uint8_t *head) { @@ -64,7 +65,8 @@ static void nghttp2_frame_set_hd(nghttp2_frame_hd *hd, uint16_t length, } void nghttp2_frame_headers_init(nghttp2_headers *frame, - uint8_t flags, int32_t stream_id, int32_t pri, + uint8_t flags, int32_t stream_id, + const nghttp2_priority_spec *pri_spec, nghttp2_nv *nva, size_t nvlen) { nghttp2_frame_set_hd(&frame->hd, 0, NGHTTP2_HEADERS, flags, stream_id); @@ -72,7 +74,7 @@ void nghttp2_frame_headers_init(nghttp2_headers *frame, frame->nva = nva; frame->nvlen = nvlen; frame->cat = NGHTTP2_HCAT_REQUEST; - frame->pri = pri; + frame->pri_spec = *pri_spec; } void nghttp2_frame_headers_free(nghttp2_headers *frame) @@ -81,11 +83,25 @@ void nghttp2_frame_headers_free(nghttp2_headers *frame) } void nghttp2_frame_priority_init(nghttp2_priority *frame, int32_t stream_id, - int32_t pri) + const nghttp2_priority_spec *pri_spec) { - nghttp2_frame_set_hd(&frame->hd, 4, NGHTTP2_PRIORITY, NGHTTP2_FLAG_NONE, - stream_id); - frame->pri = pri; + uint8_t flags; + + switch(pri_spec->pri_type) { + case NGHTTP2_PRIORITY_TYPE_GROUP: + flags = NGHTTP2_FLAG_PRIORITY_GROUP; + + break; + case NGHTTP2_PRIORITY_TYPE_DEP: + flags = NGHTTP2_FLAG_PRIORITY_DEPENDENCY; + + break; + default: + assert(0); + } + + nghttp2_frame_set_hd(&frame->hd, 4, NGHTTP2_PRIORITY, flags, stream_id); + frame->pri_spec = *pri_spec; } void nghttp2_frame_priority_free(nghttp2_priority *frame) @@ -212,13 +228,22 @@ void nghttp2_frame_private_data_init(nghttp2_private_data *frame, void nghttp2_frame_private_data_free(nghttp2_private_data *frame) {} +size_t nghttp2_frame_priority_len(uint8_t flags) +{ + if(flags & NGHTTP2_FLAG_PRIORITY_GROUP) { + return 5; + } + + if(flags & NGHTTP2_FLAG_PRIORITY_DEPENDENCY) { + return 4; + } + + return 0; +} + size_t nghttp2_frame_headers_payload_nv_offset(nghttp2_headers *frame) { - if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY) { - return 4; - } else { - return 0; - } + return nghttp2_frame_priority_len(frame->hd.flags); } /* @@ -319,9 +344,7 @@ int nghttp2_frame_pack_headers(nghttp2_bufs *bufs, return rv; } - if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY) { - nghttp2_put_uint32be(buf->pos, frame->pri); - } + nghttp2_frame_pack_priority_spec(buf->pos, &frame->pri_spec); frame->padlen = 0; frame->hd.length = nghttp2_bufs_len(bufs); @@ -329,17 +352,64 @@ int nghttp2_frame_pack_headers(nghttp2_bufs *bufs, return frame_pack_headers_shared(bufs, &frame->hd); } +void nghttp2_frame_pack_priority_spec(uint8_t *buf, + const nghttp2_priority_spec *pri_spec) +{ + switch(pri_spec->pri_type) { + case NGHTTP2_PRIORITY_TYPE_GROUP: + + nghttp2_put_uint32be(buf, pri_spec->group.pri_group_id); + buf[4] = pri_spec->group.weight; + + return; + case NGHTTP2_PRIORITY_TYPE_DEP: + + nghttp2_put_uint32be(buf, pri_spec->dep.stream_id); + if(pri_spec->dep.exclusive) { + buf[0] |= 0x80; + } + + return; + default: + return; + } +} + +void nghttp2_frame_unpack_priority_spec(nghttp2_priority_spec *pri_spec, + uint8_t flags, + const uint8_t *payload, + size_t payloadlen) +{ + if(flags & NGHTTP2_FLAG_PRIORITY_GROUP) { + int32_t pri_group_id; + uint8_t weight; + + pri_group_id = nghttp2_get_uint32(payload) & NGHTTP2_PRI_GROUP_ID_MASK; + weight = payload[4]; + + nghttp2_priority_spec_group_init(pri_spec, pri_group_id, weight); + } else if(flags & NGHTTP2_FLAG_PRIORITY_DEPENDENCY) { + int32_t dep_stream_id; + uint8_t exclusive; + + dep_stream_id = nghttp2_get_uint32(payload) & NGHTTP2_STREAM_ID_MASK; + exclusive = (payload[0] & 0x80) > 0; + + nghttp2_priority_spec_dep_init(pri_spec, dep_stream_id, exclusive); + } else { + pri_spec->pri_type = NGHTTP2_PRIORITY_TYPE_NONE; + } +} + int nghttp2_frame_unpack_headers_payload(nghttp2_headers *frame, const uint8_t *payload, size_t payloadlen) { - if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY) { - frame->pri = nghttp2_get_uint32(payload) & NGHTTP2_PRIORITY_MASK; - } else { - frame->pri = NGHTTP2_PRI_DEFAULT; - } + nghttp2_frame_unpack_priority_spec(&frame->pri_spec, frame->hd.flags, + payload, payloadlen); frame->nva = NULL; frame->nvlen = 0; + return 0; } @@ -354,8 +424,9 @@ int nghttp2_frame_pack_priority(nghttp2_bufs *bufs, nghttp2_priority *frame) nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); - nghttp2_put_uint32be(buf->last, frame->pri); - buf->last += 4; + nghttp2_frame_pack_priority_spec(buf->last, &frame->pri_spec); + + buf->last += nghttp2_frame_priority_len(frame->hd.flags); return 0; } @@ -364,7 +435,8 @@ void nghttp2_frame_unpack_priority_payload(nghttp2_priority *frame, const uint8_t *payload, size_t payloadlen) { - frame->pri = nghttp2_get_uint32(payload) & NGHTTP2_PRIORITY_MASK; + nghttp2_frame_unpack_priority_spec(&frame->pri_spec, frame->hd.flags, + payload, payloadlen); } int nghttp2_frame_pack_rst_stream(nghttp2_bufs *bufs, diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index 04321720..df9a9ac7 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -35,6 +35,7 @@ #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) #define NGHTTP2_WINDOW_SIZE_INCREMENT_MASK ((1u << 31) - 1) #define NGHTTP2_SETTINGS_ID_MASK ((1 << 24) - 1) @@ -97,6 +98,31 @@ void nghttp2_frame_pack_frame_hd(uint8_t *buf, const nghttp2_frame_hd *hd); void nghttp2_frame_unpack_frame_hd(nghttp2_frame_hd *hd, const uint8_t* buf); +/** + * Returns the number of priority field depending on the |flags|. If + * |flags| has neither NGHTTP2_FLAG_PRIORITY_GROUP nor + * NGHTTP2_FLAG_PRIORITY_DEPENDENCY set, return 0. + */ +size_t nghttp2_frame_priority_len(uint8_t flags); + +/** + * Packs the |pri_spec| in |buf|. This function assumes |buf| has + * enough space for serialization. + */ +void nghttp2_frame_pack_priority_spec(uint8_t *buf, + const nghttp2_priority_spec *pri_spec); + +/** + * Unpacks the priority specification from payload |payload| of length + * |payloadlen| to |pri_spec|. The |flags| is used to determine what + * kind of priority specification is in |payload|. This function + * assumes the |payload| contains whole priority specification. + */ +void nghttp2_frame_unpack_priority_spec(nghttp2_priority_spec *pri_spec, + uint8_t flags, + const uint8_t *payload, + size_t payloadlen); + /* * Returns the offset from the HEADERS frame payload where the * compressed header block starts. The frame payload does not include @@ -387,14 +413,15 @@ void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame, * not assigned yet, it must be -1. */ void nghttp2_frame_headers_init(nghttp2_headers *frame, - uint8_t flags, int32_t stream_id, int32_t pri, + uint8_t flags, int32_t stream_id, + const nghttp2_priority_spec *pri_spec, nghttp2_nv *nva, size_t nvlen); void nghttp2_frame_headers_free(nghttp2_headers *frame); void nghttp2_frame_priority_init(nghttp2_priority *frame, int32_t stream_id, - int32_t pri); + const nghttp2_priority_spec *pri_spec); void nghttp2_frame_priority_free(nghttp2_priority *frame); diff --git a/lib/nghttp2_outbound_item.h b/lib/nghttp2_outbound_item.h index d0b53b49..8a6da1c4 100644 --- a/lib/nghttp2_outbound_item.h +++ b/lib/nghttp2_outbound_item.h @@ -32,10 +32,12 @@ #include #include "nghttp2_frame.h" -/* Priority for PING */ -#define NGHTTP2_OB_PRI_PING -10 -/* Priority for SETTINGS */ -#define NGHTTP2_OB_PRI_SETTINGS -9 +/* A bit higher weight for non-DATA frames */ +#define NGHTTP2_OB_EX_WEIGHT 256 +/* Higher weight for SETTINGS */ +#define NGHTTP2_OB_SETTINGS_WEIGHT 257 +/* Highest weight for PING */ +#define NGHTTP2_OB_PING_WEIGHT 258 typedef struct { nghttp2_data_provider *data_prd; @@ -49,8 +51,11 @@ typedef struct { /* Type of |frame|. NGHTTP2_CTRL: nghttp2_frame*, NGHTTP2_DATA: nghttp2_private_data* */ nghttp2_frame_category frame_cat; - /* The priority used in priority comparion */ - int32_t pri; + /* The priority used in priority comparion. Larger is served + ealier. */ + int32_t weight; + /* nonzero if this object is queued. */ + uint8_t queued; } nghttp2_outbound_item; /* diff --git a/lib/nghttp2_priority_spec.c b/lib/nghttp2_priority_spec.c new file mode 100644 index 00000000..416a7f19 --- /dev/null +++ b/lib/nghttp2_priority_spec.c @@ -0,0 +1,41 @@ +/* + * nghttp2 - HTTP/2.0 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_priority_spec.h" + +void nghttp2_priority_spec_group_init(nghttp2_priority_spec *pri_spec, + int32_t pri_group_id, uint8_t weight) +{ + pri_spec->pri_type = NGHTTP2_PRIORITY_TYPE_GROUP; + pri_spec->group.pri_group_id = pri_group_id; + pri_spec->group.weight = weight; +} + +void nghttp2_priority_spec_dep_init(nghttp2_priority_spec *pri_spec, + int32_t stream_id, int exclusive) +{ + pri_spec->pri_type = NGHTTP2_PRIORITY_TYPE_DEP; + pri_spec->dep.stream_id = stream_id; + pri_spec->dep.exclusive = exclusive != 0; +} diff --git a/lib/nghttp2_priority_spec.h b/lib/nghttp2_priority_spec.h new file mode 100644 index 00000000..c2ea55a0 --- /dev/null +++ b/lib/nghttp2_priority_spec.h @@ -0,0 +1,34 @@ +/* + * nghttp2 - HTTP/2.0 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_PRIORITY_SPEC_H +#define NGHTTP2_PRIORITY_SPEC_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#endif /* NGHTTP2_PRIORITY_SPEC_H */ diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 4eb2beb0..2f318457 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -31,6 +31,7 @@ #include "nghttp2_helper.h" #include "nghttp2_net.h" +#include "nghttp2_priority_spec.h" /* * Returns non-zero if the number of outgoing opened streams is larger @@ -81,14 +82,6 @@ int nghttp2_is_fatal(int lib_error) return lib_error < NGHTTP2_ERR_FATAL; } -/* Returns the pushed stream's priority based on the associated stream - |stream|. */ -static int32_t nghttp2_pushed_stream_pri(nghttp2_stream *stream) -{ - return stream->pri == NGHTTP2_PRI_LOWEST ? - (int32_t)NGHTTP2_PRI_LOWEST : stream->pri + 1; -} - /* Returns nonzero if the |stream| is in reserved(remote) state */ static int state_reserved_remote(nghttp2_session *session, nghttp2_stream *stream) @@ -139,16 +132,26 @@ nghttp2_stream* nghttp2_session_get_stream(nghttp2_session *session, return (nghttp2_stream*)nghttp2_map_find(&session->streams, stream_id); } +nghttp2_stream_group* nghttp2_session_get_stream_group +(nghttp2_session *session, int32_t pri_group_id) +{ + return (nghttp2_stream_group*)nghttp2_map_find(&session->stream_groups, + pri_group_id); +} + static int nghttp2_outbound_item_compar(const void *lhsx, const void *rhsx) { const nghttp2_outbound_item *lhs, *rhs; + lhs = (const nghttp2_outbound_item*)lhsx; rhs = (const nghttp2_outbound_item*)rhsx; - if(lhs->pri == rhs->pri) { + + if(lhs->weight == rhs->weight) { return (lhs->seq < rhs->seq) ? -1 : ((lhs->seq > rhs->seq) ? 1 : 0); - } else { - return lhs->pri - rhs->pri; } + + /* Larger weight has higher precedence */ + return rhs->weight - lhs->weight; } static void nghttp2_inbound_frame_reset(nghttp2_session *session) @@ -267,6 +270,11 @@ static int nghttp2_session_new(nghttp2_session **session_ptr, goto fail_map; } + rv = nghttp2_map_init(&(*session_ptr)->stream_groups); + if(rv != 0) { + goto fail_group_map; + } + (*session_ptr)->next_seq = 0; if((opt_set_mask & NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE) && @@ -325,6 +333,8 @@ static int nghttp2_session_new(nghttp2_session **session_ptr, return 0; fail_aob_framebuf: + nghttp2_map_free(&(*session_ptr)->stream_groups); + fail_group_map: nghttp2_map_free(&(*session_ptr)->streams); fail_map: nghttp2_hd_inflate_free(&(*session_ptr)->hd_inflater); @@ -396,6 +406,15 @@ static int nghttp2_free_streams(nghttp2_map_entry *entry, void *ptr) { nghttp2_stream_free((nghttp2_stream*)entry); free(entry); + + return 0; +} + +static int nghttp2_free_stream_groups(nghttp2_map_entry *entry, void *ptr) +{ + nghttp2_stream_group_free((nghttp2_stream_group*)entry); + free(entry); + return 0; } @@ -416,65 +435,119 @@ void nghttp2_session_del(nghttp2_session *session) return; } free(session->inflight_iv); - nghttp2_inbound_frame_reset(session); + + /* Have to free streams first, so that we can check + stream->data->queued */ nghttp2_map_each_free(&session->streams, nghttp2_free_streams, NULL); nghttp2_map_free(&session->streams); + + nghttp2_map_each_free(&session->stream_groups, nghttp2_free_stream_groups, + NULL); + nghttp2_map_free(&session->stream_groups); + nghttp2_session_ob_pq_free(&session->ob_pq); nghttp2_session_ob_pq_free(&session->ob_ss_pq); + nghttp2_active_outbound_item_reset(&session->aob); + nghttp2_inbound_frame_reset(session); nghttp2_hd_deflate_free(&session->hd_deflater); nghttp2_hd_inflate_free(&session->hd_inflater); - nghttp2_active_outbound_item_reset(&session->aob); nghttp2_bufs_free(&session->aob.framebufs); free(session); } -static int outbound_item_update_pri -(nghttp2_outbound_item *item, nghttp2_stream *stream) +int nghttp2_session_reprioritize_stream +(nghttp2_session *session, nghttp2_stream *stream, + const nghttp2_priority_spec *pri_spec) { - if(item->frame_cat == NGHTTP2_CAT_CTRL) { - if(((nghttp2_frame*)item->frame)->hd.stream_id != stream->stream_id) { - return 0; - } - switch(((nghttp2_frame*)item->frame)->hd.type) { - case NGHTTP2_HEADERS: - case NGHTTP2_PUSH_PROMISE: - break; - default: - return 0; - } - } else { - if(((nghttp2_private_data*)item->frame)->hd.stream_id != stream->stream_id) { - return 0; - } - } - item->pri = stream->pri; - return 1; -} + int rv; + nghttp2_stream_group *stream_group; + nghttp2_stream_group *old_stream_group; + nghttp2_stream *dep_stream; + const nghttp2_priority_group *group; + const nghttp2_priority_dep *dep; -static int update_stream_pri(void *ptr, void *arg) -{ - nghttp2_outbound_item *item = (nghttp2_outbound_item*)ptr; - nghttp2_stream *stream = (nghttp2_stream*)arg; - return outbound_item_update_pri(item, stream); -} + switch(pri_spec->pri_type) { + case NGHTTP2_PRIORITY_TYPE_GROUP: + group = &pri_spec->group; -void nghttp2_session_reprioritize_stream -(nghttp2_session *session, nghttp2_stream *stream, int32_t pri) -{ - if(stream->pri == pri) { - return; - } - stream->pri = pri; - /* For submitted frames, we only update initial priority, so the - structure of the queue will remain unchanged. */ - nghttp2_pq_update(&session->ob_pq, update_stream_pri, stream); - nghttp2_pq_update(&session->ob_ss_pq, update_stream_pri, stream); - if(stream->deferred_data) { - stream->deferred_data->pri = pri; - } - if(session->aob.item) { - outbound_item_update_pri(session->aob.item, stream); + old_stream_group = stream->stream_group; + + nghttp2_stream_dep_remove_subtree(stream); + + stream_group = nghttp2_session_get_stream_group(session, + group->pri_group_id); + + if(stream_group == NULL) { + stream_group = nghttp2_session_open_stream_group(session, + group->pri_group_id, + group->weight); + + if(stream_group == NULL) { + return NGHTTP2_ERR_NOMEM; + } + } else { + stream_group->weight = group->weight; + } + + rv = nghttp2_stream_dep_make_root(stream_group, stream, &session->ob_pq); + + if(rv != 0) { + return rv; + } + + nghttp2_session_close_stream_group_if_empty(session, old_stream_group); + + return 0; + + case NGHTTP2_PRIORITY_TYPE_DEP: + dep = &pri_spec->dep; + + if(dep->stream_id == stream->stream_id || dep->stream_id == 0) { + return nghttp2_session_terminate_session(session, + NGHTTP2_PROTOCOL_ERROR); + } + + old_stream_group = stream->stream_group; + + dep_stream = nghttp2_session_get_stream(session, dep->stream_id); + + if(dep_stream == NULL) { + return 0; + } + + /* Ignore priority request if resultant tree has cycle */ + if(nghttp2_stream_dep_subtree_find(stream, dep_stream)) { + DEBUGF(fprintf(stderr, + "stream: future cycle detected, dep_stream(%p)=%d " + "stream(%p)=%d\n", + dep_stream, dep_stream->stream_id, + stream, stream->stream_id)); + + return nghttp2_session_terminate_session(session, + NGHTTP2_PROTOCOL_ERROR); + } + + nghttp2_stream_dep_remove_subtree(stream); + + if(dep->exclusive) { + rv = nghttp2_stream_dep_insert_subtree(dep_stream, stream, + &session->ob_pq); + } else { + rv = nghttp2_stream_dep_add_subtree(dep_stream, stream, &session->ob_pq); + } + + if(rv != 0) { + return rv; + } + + nghttp2_session_close_stream_group_if_empty(session, old_stream_group); + + return 0; + default: + assert(0); } + + return 0; } int nghttp2_session_add_frame(nghttp2_session *session, @@ -486,38 +559,55 @@ int nghttp2_session_add_frame(nghttp2_session *session, stream presence. */ int rv = 0; nghttp2_outbound_item *item; + item = malloc(sizeof(nghttp2_outbound_item)); if(item == NULL) { return NGHTTP2_ERR_NOMEM; } + item->frame_cat = frame_cat; item->frame = abs_frame; item->aux_data = aux_data; item->seq = session->next_seq++; - /* Set priority to the default value at the moment. */ - item->pri = NGHTTP2_PRI_DEFAULT; + + item->weight = NGHTTP2_OB_EX_WEIGHT; + item->queued = 0; + if(frame_cat == NGHTTP2_CAT_CTRL) { nghttp2_frame *frame = (nghttp2_frame*)abs_frame; - nghttp2_stream *stream = NULL; + nghttp2_stream *stream; + nghttp2_stream *dep_stream; + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + switch(frame->hd.type) { case NGHTTP2_HEADERS: if(frame->hd.stream_id == -1) { /* Initial HEADERS, which will open stream */ - item->pri = frame->headers.pri; - } else { + + if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY_GROUP) { + item->weight = frame->headers.pri_spec.group.weight; + } else if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY_DEPENDENCY) { + dep_stream = nghttp2_session_get_stream + (session, frame->headers.pri_spec.dep.stream_id); + if(dep_stream) { + item->weight = dep_stream->stream_group->weight; + } else { + item->weight = NGHTTP2_DEFAULT_WEIGHT; + } + } else { + item->weight = NGHTTP2_DEFAULT_WEIGHT; + } + + } else if(stream) { /* Otherwise, the frame must have stream ID. We use its priority value. */ - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - if(stream) { - item->pri = stream->pri; - } + item->weight = stream->stream_group->weight; } break; case NGHTTP2_PRIORITY: - item->pri = -1; break; case NGHTTP2_RST_STREAM: - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); if(stream) { /* We rely on the stream state to decide whether number of streams should be decremented or not. For purly reserved @@ -527,29 +617,31 @@ int nghttp2_session_add_frame(nghttp2_session *session, stream->state = NGHTTP2_STREAM_CLOSING; } } - item->pri = -1; + break; case NGHTTP2_SETTINGS: - item->pri = NGHTTP2_OB_PRI_SETTINGS; + item->weight = NGHTTP2_OB_SETTINGS_WEIGHT; + break; case NGHTTP2_PUSH_PROMISE: /* Use priority of associated stream */ - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); if(stream) { - item->pri = stream->pri; + item->weight = stream->stream_group->weight; } + break; case NGHTTP2_PING: /* Ping has highest priority. */ - item->pri = NGHTTP2_OB_PRI_PING; + item->weight = NGHTTP2_OB_PING_WEIGHT; + break; case NGHTTP2_GOAWAY: /* Should GOAWAY have higher priority? */ break; case NGHTTP2_WINDOW_UPDATE: - item->pri = -1; break; } + if(frame->hd.type == NGHTTP2_HEADERS && (frame->hd.stream_id == -1 || (stream && stream->state == NGHTTP2_STREAM_RESERVED))) { @@ -563,22 +655,30 @@ int nghttp2_session_add_frame(nghttp2_session *session, } else { rv = nghttp2_pq_push(&session->ob_pq, item); } + + item->queued = 1; + } else if(frame_cat == NGHTTP2_CAT_DATA) { nghttp2_private_data *data_frame = (nghttp2_private_data*)abs_frame; nghttp2_stream *stream; + stream = nghttp2_session_get_stream(session, data_frame->hd.stream_id); if(stream) { - item->pri = stream->pri; + item->weight = stream->stream_group->weight; + + rv = nghttp2_stream_attach_data(stream, item, &session->ob_pq); } - rv = nghttp2_pq_push(&session->ob_pq, item); + } else { /* Unreachable */ assert(0); } + if(rv != 0) { free(item); return rv; } + return 0; } @@ -611,26 +711,74 @@ int nghttp2_session_add_rst_stream(nghttp2_session *session, nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session, int32_t stream_id, - uint8_t flags, int32_t pri, + uint8_t flags, + nghttp2_priority_spec *pri_spec, nghttp2_stream_state initial_state, void *stream_user_data) { int rv; - nghttp2_stream *stream = malloc(sizeof(nghttp2_stream)); + nghttp2_stream *stream; + nghttp2_stream *dep_stream; + int32_t pri_group_id; + uint8_t weight; + nghttp2_stream_group *stream_group; + + dep_stream = NULL; + + switch(pri_spec->pri_type) { + case NGHTTP2_PRIORITY_TYPE_GROUP: + pri_group_id = pri_spec->group.pri_group_id; + weight = pri_spec->group.weight; + + break; + case NGHTTP2_PRIORITY_TYPE_DEP: + dep_stream = nghttp2_session_get_stream(session, pri_spec->dep.stream_id); + + if(dep_stream) { + pri_group_id = dep_stream->stream_group->pri_group_id; + weight = dep_stream->stream_group->weight; + } else { + pri_group_id = stream_id; + weight = NGHTTP2_DEFAULT_WEIGHT; + } + + break; + default: + pri_group_id = stream_id; + weight = NGHTTP2_DEFAULT_WEIGHT; + }; + + stream_group = nghttp2_session_get_stream_group(session, pri_group_id); + + if(stream_group == NULL) { + stream_group = nghttp2_session_open_stream_group(session, pri_group_id, + weight); + + if(stream_group == NULL) { + return NULL; + } + } + + stream = malloc(sizeof(nghttp2_stream)); if(stream == NULL) { return NULL; } - nghttp2_stream_init(stream, stream_id, flags, pri, initial_state, + + nghttp2_stream_init(stream, stream_id, flags, initial_state, session->remote_settings [NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE], session->local_settings [NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE], stream_user_data); + rv = nghttp2_map_insert(&session->streams, &stream->map_entry); if(rv != 0) { free(stream); return NULL; } + + nghttp2_stream_group_add_stream(stream_group, stream); + if(initial_state == NGHTTP2_STREAM_RESERVED) { if(nghttp2_session_is_my_stream_id(session, stream_id)) { /* half closed (remote) */ @@ -648,28 +796,64 @@ nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session, ++session->num_incoming_streams; } } + + /* Possibly update weight of priority group */ + stream_group->weight = weight; + + /* We don't have to track dependency of received reserved stream */ + if(stream->shut_flags & NGHTTP2_SHUT_WR) { + return stream; + } + + if(!dep_stream) { + return stream; + } + + if(pri_spec->dep.exclusive) { + nghttp2_stream_dep_insert(dep_stream, stream); + } else { + nghttp2_stream_dep_add(dep_stream, stream); + } + return stream; } -/* - * Closes stream with stream ID |stream_id|. The |error_code| - * indicates the reason of the closure. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_INVALID_ARGUMENT - * The stream is not found. - * NGHTTP2_ERR_CALLBACK_FAILURE - * The callback function failed. - */ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code) { - nghttp2_stream *stream = nghttp2_session_get_stream(session, stream_id); + int rv; + nghttp2_stream *stream; + nghttp2_stream_group *stream_group; + + stream = nghttp2_session_get_stream(session, stream_id); + if(!stream) { return NGHTTP2_ERR_INVALID_ARGUMENT; } + + DEBUGF(fprintf(stderr, "stream: stream(%p)=%d close\n", + stream, stream->stream_id)); + + if(stream->data) { + nghttp2_outbound_item *item; + + item = stream->data; + + rv = nghttp2_stream_detach_data(stream, &session->ob_pq); + + if(rv != 0) { + return rv; + } + + /* If item is queued, it will be deleted when it is popped + (nghttp2_session_prep_frame() will fail). If session->aob.item + points to this item, let nghttp2_active_outbound_item_reset() + free the item. */ + if(!item->queued && item != session->aob.item) { + free(item); + } + } + /* We call on_stream_close_callback even if stream->state is NGHTTP2_STREAM_INITIAL. This will happen while sending request HEADERS, a local endpoint receives RST_STREAM for that stream. It @@ -677,7 +861,9 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id, hang the stream in a local endpoint. */ /* TODO Should on_stream_close_callback be called against - NGHTTP2_STREAM_RESERVED? It is actually not opened yet. */ + NGHTTP2_STREAM_RESERVED? It is actually not opened yet. + + Maybe we should call callback in this case. */ if(stream->state != NGHTTP2_STREAM_RESERVED) { if(session->callbacks.on_stream_close_callback) { if(session->callbacks.on_stream_close_callback @@ -693,9 +879,18 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id, --session->num_incoming_streams; } } + + nghttp2_stream_dep_remove(stream); + + stream_group = stream->stream_group; + + nghttp2_stream_group_remove_stream(stream_group, stream); + nghttp2_session_close_stream_group_if_empty(session, stream_group); + nghttp2_map_remove(&session->streams, stream_id); nghttp2_stream_free(stream); free(stream); + return 0; } @@ -1211,6 +1406,15 @@ static int nghttp2_session_prep_frame(nghttp2_session *session, } frame->hd.stream_id = session->next_stream_id; session->next_stream_id += 2; + + if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY_GROUP) { + if(frame->headers.pri_spec.group.pri_group_id == -1) { + /* We now know stream_id. Assign stream_id to + pri_group_id if it is -1. */ + frame->headers.pri_spec.group.pri_group_id = frame->hd.stream_id; + } + } + } else if(nghttp2_session_predicate_push_response_headers_send (session, frame->hd.stream_id) == 0) { frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE; @@ -1245,7 +1449,7 @@ static int nghttp2_session_prep_frame(nghttp2_session *session, case NGHTTP2_HCAT_REQUEST: if(!nghttp2_session_open_stream(session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE, - frame->headers.pri, + &frame->headers.pri_spec, NGHTTP2_STREAM_INITIAL, aux_data ? aux_data->stream_user_data : NULL)) { @@ -1303,6 +1507,8 @@ static int nghttp2_session_prep_frame(nghttp2_session *session, case NGHTTP2_PUSH_PROMISE: { nghttp2_stream *stream; nghttp2_headers_aux_data *aux_data; + nghttp2_priority_spec pri_spec; + aux_data = (nghttp2_headers_aux_data*)item->aux_data; rv = nghttp2_session_predicate_push_promise_send(session, @@ -1325,10 +1531,15 @@ static int nghttp2_session_prep_frame(nghttp2_session *session, stream = nghttp2_session_get_stream(session, frame->hd.stream_id); assert(stream); + + /* TODO It is unclear reserved stream dpeneds on associated + stream with or without exclusive flag set */ + nghttp2_priority_spec_dep_init(&pri_spec, stream->stream_id, 0); + if(!nghttp2_session_open_stream (session, frame->push_promise.promised_stream_id, NGHTTP2_STREAM_FLAG_PUSH, - nghttp2_pushed_stream_pri(stream), + &pri_spec, NGHTTP2_STREAM_RESERVED, aux_data ? aux_data->stream_user_data : NULL)) { @@ -1378,15 +1589,36 @@ static int nghttp2_session_prep_frame(nghttp2_session *session, size_t next_readmax; nghttp2_stream *stream; nghttp2_private_data *data_frame; + data_frame = nghttp2_outbound_item_get_data_frame(item); + stream = nghttp2_session_get_stream(session, data_frame->hd.stream_id); + + if(stream && stream->data && stream->data != item) { + /* We don't allow multiple DATA for a stream at the same + time. */ + return NGHTTP2_ERR_DATA_EXIST; + } + rv = nghttp2_session_predicate_data_send(session, data_frame->hd.stream_id); if(rv != 0) { + int rv2; + + stream = nghttp2_session_get_stream(session, data_frame->hd.stream_id); + + if(stream) { + rv2 = nghttp2_stream_detach_data(stream, &session->ob_pq); + + if(nghttp2_is_fatal(rv2)) { + return rv2; + } + } + return rv; } - stream = nghttp2_session_get_stream(session, data_frame->hd.stream_id); /* Assuming stream is not NULL */ assert(stream); next_readmax = nghttp2_session_next_data_read(session, stream); + if(next_readmax == 0) { nghttp2_stream_defer_data(stream, item, NGHTTP2_DEFERRED_FLOW_CONTROL); session->aob.item = NULL; @@ -1404,6 +1636,12 @@ static int nghttp2_session_prep_frame(nghttp2_session *session, return NGHTTP2_ERR_DEFERRED; } if(framebuflen == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + rv = nghttp2_stream_detach_data(stream, &session->ob_pq); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + rv = nghttp2_session_add_rst_stream(session, data_frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR); if(rv != 0) { @@ -1450,8 +1688,8 @@ nghttp2_outbound_item* nghttp2_session_get_next_ob_item item = nghttp2_pq_top(&session->ob_pq); headers_item = nghttp2_pq_top(&session->ob_ss_pq); if(nghttp2_session_is_outgoing_concurrent_streams_max(session) || - item->pri < headers_item->pri || - (item->pri == headers_item->pri && + item->weight > headers_item->weight || + (item->weight == headers_item->weight && item->seq < headers_item->seq)) { return item; } else { @@ -1476,6 +1714,9 @@ nghttp2_outbound_item* nghttp2_session_pop_next_ob_item nghttp2_outbound_item *item; item = nghttp2_pq_top(&session->ob_ss_pq); nghttp2_pq_pop(&session->ob_ss_pq); + + item->queued = 0; + return item; } } @@ -1484,19 +1725,28 @@ nghttp2_outbound_item* nghttp2_session_pop_next_ob_item nghttp2_outbound_item *item; item = nghttp2_pq_top(&session->ob_pq); nghttp2_pq_pop(&session->ob_pq); + + item->queued = 0; + return item; } else { nghttp2_outbound_item *item, *headers_item; item = nghttp2_pq_top(&session->ob_pq); headers_item = nghttp2_pq_top(&session->ob_ss_pq); if(nghttp2_session_is_outgoing_concurrent_streams_max(session) || - item->pri < headers_item->pri || - (item->pri == headers_item->pri && + item->weight > headers_item->weight || + (item->weight == headers_item->weight && item->seq < headers_item->seq)) { nghttp2_pq_pop(&session->ob_pq); + + item->queued = 0; + return item; } else { nghttp2_pq_pop(&session->ob_ss_pq); + + headers_item->queued = 0; + return headers_item; } } @@ -1531,6 +1781,16 @@ static int session_call_on_frame_send(nghttp2_session *session, return 0; } +static void outbound_item_cycle_weight(nghttp2_outbound_item *item, + int32_t ini_weight) +{ + if(item->weight == 0 || item->weight > ini_weight) { + item->weight = ini_weight; + } else { + --item->weight; + } +} + /* * Called after a frame is sent. * @@ -1651,8 +1911,12 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session) nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) || ((stream->flags & NGHTTP2_STREAM_FLAG_PUSH) && !nghttp2_session_is_my_stream_id(session, frame->hd.stream_id))) { - nghttp2_session_reprioritize_stream(session, stream, - frame->priority.pri); + rv = nghttp2_session_reprioritize_stream(session, stream, + &frame->priority.pri_spec); + + if(nghttp2_is_fatal(rv)) { + return rv; + } } break; } @@ -1724,31 +1988,50 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session) } } - if(stream && data_frame->eof && - (data_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(stream && data_frame->eof) { + rv = nghttp2_stream_detach_data(stream, &session->ob_pq); + if(nghttp2_is_fatal(rv)) { return rv; } + + if(data_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; + } + /* stream may be NULL if it was closed */ + } } /* If session is closed or RST_STREAM was queued, we won't send further data. */ if(data_frame->eof || nghttp2_session_predicate_data_send(session, data_frame->hd.stream_id) != 0) { + nghttp2_active_outbound_item_reset(aob); + return 0; } + /* Assuming stream is not NULL */ assert(stream); next_item = nghttp2_session_get_next_ob_item(session); + + outbound_item_cycle_weight(aob->item, stream->stream_group->weight); + /* If priority of this stream is higher or equal to other stream waiting at the top of the queue, we continue to send this data. */ - if(next_item == NULL || aob->item->pri < next_item->pri) { + if(stream->dpri == NGHTTP2_STREAM_DPRI_TOP && + (next_item == NULL || aob->item->weight > next_item->weight)) { size_t next_readmax; + next_readmax = nghttp2_session_next_data_read(session, stream); + if(next_readmax == 0) { nghttp2_stream_defer_data(stream, aob->item, NGHTTP2_DEFERRED_FLOW_CONTROL); @@ -1779,11 +2062,19 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session) rv = nghttp2_session_add_rst_stream(session, data_frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR); - nghttp2_active_outbound_item_reset(aob); + if(nghttp2_is_fatal(rv)) { return rv; } + rv = nghttp2_stream_detach_data(stream, &session->ob_pq); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + + nghttp2_active_outbound_item_reset(aob); + return 0; } assert(rv >= 0); @@ -1793,10 +2084,17 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session) /* Update seq to interleave other streams with the same priority. */ aob->item->seq = session->next_seq++; - rv = nghttp2_pq_push(&session->ob_pq, aob->item); - if(nghttp2_is_fatal(rv)) { - return rv; + + if(stream->dpri == NGHTTP2_STREAM_DPRI_TOP) { + rv = nghttp2_pq_push(&session->ob_pq, aob->item); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + + aob->item->queued = 1; } + aob->item = NULL; nghttp2_active_outbound_item_reset(&session->aob); return 0; @@ -1825,6 +2123,22 @@ ssize_t nghttp2_session_mem_send(nghttp2_session *session, if(item == NULL) { return 0; } + + if(item->frame_cat == NGHTTP2_CAT_DATA) { + nghttp2_private_data *data; + nghttp2_stream *stream; + + data = nghttp2_outbound_item_get_data_frame(item); + + stream = nghttp2_session_get_stream(session, data->hd.stream_id); + + if(stream && stream->dpri != NGHTTP2_STREAM_DPRI_TOP) { + /* We have DATA with higher priority in queue within the + same dependency tree. */ + break; + } + } + rv = nghttp2_session_prep_frame(session, item); if(rv == NGHTTP2_ERR_DEFERRED) { DEBUGF(fprintf(stderr, "send: frame transmission deferred\n")); @@ -1846,6 +2160,10 @@ ssize_t nghttp2_session_mem_send(nghttp2_session *session, if(frame->hd.type != NGHTTP2_WINDOW_UPDATE) { if(session->callbacks.on_frame_not_send_callback (session, frame, rv, session->user_data) != 0) { + + nghttp2_outbound_item_free(item); + free(item); + return NGHTTP2_ERR_CALLBACK_FAILURE; } } @@ -1866,6 +2184,7 @@ ssize_t nghttp2_session_mem_send(nghttp2_session *session, rv = 0; break; } + aob->item = item; nghttp2_bufs_rewind(framebufs); @@ -2383,6 +2702,14 @@ int nghttp2_session_on_request_headers_received(nghttp2_session *session, return nghttp2_session_inflate_handle_invalid_connection (session, frame, NGHTTP2_ENHANCE_YOUR_CALM); } + + if(frame->headers.pri_spec.pri_type == NGHTTP2_PRIORITY_TYPE_DEP && + (frame->headers.pri_spec.dep.stream_id == frame->hd.stream_id || + frame->headers.pri_spec.dep.stream_id == 0)) { + return nghttp2_session_inflate_handle_invalid_connection + (session, frame, NGHTTP2_PROTOCOL_ERROR); + } + if(nghttp2_session_is_incoming_concurrent_streams_pending_max(session)) { return nghttp2_session_inflate_handle_invalid_stream (session, frame, NGHTTP2_REFUSED_STREAM); @@ -2391,7 +2718,7 @@ int nghttp2_session_on_request_headers_received(nghttp2_session *session, stream = nghttp2_session_open_stream(session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE, - frame->headers.pri, + &frame->headers.pri_spec, NGHTTP2_STREAM_OPENING, NULL); if(!stream) { @@ -2568,7 +2895,9 @@ static int session_process_headers_frame(nghttp2_session *session) int nghttp2_session_on_priority_received(nghttp2_session *session, nghttp2_frame *frame) { + int rv; nghttp2_stream *stream; + if(frame->hd.stream_id == 0) { return nghttp2_session_handle_invalid_connection(session, frame, NGHTTP2_PROTOCOL_ERROR); @@ -2592,8 +2921,13 @@ int nghttp2_session_on_priority_received(nghttp2_session *session, !nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) || ((stream->flags & NGHTTP2_STREAM_FLAG_PUSH) && nghttp2_session_is_my_stream_id(session, frame->hd.stream_id))) { - nghttp2_session_reprioritize_stream(session, stream, - frame->priority.pri); + + rv = nghttp2_session_reprioritize_stream(session, stream, + &frame->priority.pri_spec); + + if(nghttp2_is_fatal(rv)) { + return rv; + } } return nghttp2_session_call_on_frame_received(session, frame); } @@ -2633,7 +2967,7 @@ int nghttp2_session_on_rst_stream_received(nghttp2_session *session, } rv = nghttp2_session_close_stream(session, frame->hd.stream_id, frame->rst_stream.error_code); - if(rv != 0 && nghttp2_is_fatal(rv)) { + if(nghttp2_is_fatal(rv)) { return rv; } return 0; @@ -2652,14 +2986,15 @@ static int session_process_rst_stream_frame(nghttp2_session *session) } static int nghttp2_update_remote_initial_window_size_func -(nghttp2_map_entry *entry, - void *ptr) +(nghttp2_map_entry *entry, void *ptr) { int rv; nghttp2_update_window_size_arg *arg; nghttp2_stream *stream; + arg = (nghttp2_update_window_size_arg*)ptr; stream = (nghttp2_stream*)entry; + rv = nghttp2_stream_update_remote_initial_window_size(stream, arg->new_window_size, arg->old_window_size); @@ -2673,13 +3008,12 @@ static int nghttp2_update_remote_initial_window_size_func (stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL) && stream->remote_window_size > 0 && arg->session->remote_window_size > 0) { - rv = nghttp2_pq_push(&arg->session->ob_pq, stream->deferred_data); - if(rv != 0) { - /* FATAL */ - assert(rv < NGHTTP2_ERR_FATAL); + + rv = nghttp2_stream_detach_deferred_data(stream, &arg->session->ob_pq); + + if(nghttp2_is_fatal(rv)) { return rv; } - nghttp2_stream_detach_deferred_data(stream); } return 0; } @@ -2956,6 +3290,8 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session, int rv; nghttp2_stream *stream; nghttp2_stream *promised_stream; + nghttp2_priority_spec pri_spec; + if(frame->hd.stream_id == 0) { return nghttp2_session_inflate_handle_invalid_connection (session, frame, NGHTTP2_PROTOCOL_ERROR); @@ -3014,16 +3350,23 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session, } return NGHTTP2_ERR_IGN_HEADER_BLOCK; } + + /* TODO It is unclear reserved stream dpeneds on associated + stream with or without exclusive flag set */ + nghttp2_priority_spec_dep_init(&pri_spec, stream->stream_id, 0); + promised_stream = nghttp2_session_open_stream (session, frame->push_promise.promised_stream_id, NGHTTP2_STREAM_FLAG_PUSH, - nghttp2_pushed_stream_pri(stream), + &pri_spec, NGHTTP2_STREAM_RESERVED, NULL); + if(!promised_stream) { return NGHTTP2_ERR_NOMEM; } + session->last_proc_stream_id = session->last_recv_stream_id; rv = session_call_on_begin_headers(session, frame); if(rv != 0) { @@ -3111,22 +3454,22 @@ static int session_process_goaway_frame(nghttp2_session *session) static int nghttp2_push_back_deferred_data_func(nghttp2_map_entry *entry, void *ptr) { + int rv; nghttp2_session *session; nghttp2_stream *stream; + session = (nghttp2_session*)ptr; stream = (nghttp2_stream*)entry; + /* If DATA frame is deferred due to flow control, push it back to outbound queue. */ if(stream->deferred_data && (stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL) && stream->remote_window_size > 0) { - int rv; - rv = nghttp2_pq_push(&session->ob_pq, stream->deferred_data); - if(rv == 0) { - nghttp2_stream_detach_deferred_data(stream); - } else { - /* FATAL */ - assert(rv < NGHTTP2_ERR_FATAL); + + rv = nghttp2_stream_detach_deferred_data(stream, &session->ob_pq); + + if(nghttp2_is_fatal(rv)) { return rv; } } @@ -3194,13 +3537,12 @@ static int session_on_stream_window_update_received session->remote_window_size > 0 && stream->deferred_data != NULL && (stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL)) { - rv = nghttp2_pq_push(&session->ob_pq, stream->deferred_data); - if(rv != 0) { - /* FATAL */ - assert(rv < NGHTTP2_ERR_FATAL); + + rv = nghttp2_stream_detach_deferred_data(stream, &session->ob_pq); + + if(nghttp2_is_fatal(rv)) { return rv; } - nghttp2_stream_detach_deferred_data(stream); } return nghttp2_session_call_on_frame_received(session, frame); } @@ -3615,6 +3957,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, int busy = 0; nghttp2_frame_hd cont_hd; nghttp2_stream *stream; + size_t pri_fieldlen; DEBUGF(fprintf(stderr, "recv: connection recv_window_size=%d, local_window=%d\n", @@ -3688,17 +4031,24 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, break; } case NGHTTP2_HEADERS: + pri_fieldlen = 0; + DEBUGF(fprintf(stderr, "recv: HEADERS\n")); iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT | NGHTTP2_FLAG_END_HEADERS | - NGHTTP2_FLAG_PRIORITY | NGHTTP2_FLAG_PAD_LOW | - NGHTTP2_FLAG_PAD_HIGH); + NGHTTP2_FLAG_PAD_HIGH | + NGHTTP2_FLAG_PRIORITY_GROUP | + NGHTTP2_FLAG_PRIORITY_DEPENDENCY); rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); - if(rv < 0) { + if(rv < 0 || + (iframe->frame.hd.flags & (NGHTTP2_FLAG_PRIORITY_GROUP | + NGHTTP2_FLAG_PRIORITY_DEPENDENCY)) == + (NGHTTP2_FLAG_PRIORITY_GROUP | NGHTTP2_FLAG_PRIORITY_DEPENDENCY)) { + busy = 1; iframe->state = NGHTTP2_IB_IGN_PAYLOAD; @@ -3716,8 +4066,10 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, break; } - if(iframe->frame.hd.flags & NGHTTP2_FLAG_PRIORITY) { - if(iframe->payloadleft < 4) { + pri_fieldlen = nghttp2_frame_priority_len(iframe->frame.hd.flags); + + if(pri_fieldlen > 0) { + if(iframe->payloadleft < pri_fieldlen) { busy = 1; iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; break; @@ -3725,7 +4077,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, iframe->state = NGHTTP2_IB_READ_NBYTE; - inbound_frame_set_mark(iframe, 4); + inbound_frame_set_mark(iframe, pri_fieldlen); break; } @@ -3746,13 +4098,50 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, break; case NGHTTP2_PRIORITY: + pri_fieldlen = 0; + + DEBUGF(fprintf(stderr, "recv: PRIORITY\n")); + + iframe->frame.hd.flags &= (NGHTTP2_FLAG_PRIORITY_GROUP | + NGHTTP2_FLAG_PRIORITY_DEPENDENCY); + + pri_fieldlen = nghttp2_frame_priority_len(iframe->frame.hd.flags); + + if(iframe->frame.hd.flags == (NGHTTP2_FLAG_PRIORITY_GROUP | + NGHTTP2_FLAG_PRIORITY_DEPENDENCY) || + pri_fieldlen == 0) { + + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + rv = nghttp2_session_terminate_session(session, + NGHTTP2_PROTOCOL_ERROR); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + + break; + } + + if(pri_fieldlen != iframe->payloadleft) { + busy = 1; + + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + + inbound_frame_set_mark(iframe, pri_fieldlen); + + break; case NGHTTP2_RST_STREAM: case NGHTTP2_WINDOW_UPDATE: #ifdef DEBUGBUILD switch(iframe->frame.hd.type) { - case NGHTTP2_PRIORITY: - DEBUGF(fprintf(stderr, "recv: PRIORITY\n")); - break; case NGHTTP2_RST_STREAM: DEBUGF(fprintf(stderr, "recv: RST_STREAM\n")); break; @@ -3914,14 +4303,17 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, break; } iframe->frame.headers.padlen = rv; - if(iframe->frame.hd.flags & NGHTTP2_FLAG_PRIORITY) { - if(iframe->payloadleft < 4) { + + pri_fieldlen = nghttp2_frame_priority_len(iframe->frame.hd.flags); + + if(pri_fieldlen > 0) { + if(iframe->payloadleft < pri_fieldlen) { busy = 1; iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; break; } iframe->state = NGHTTP2_IB_READ_NBYTE; - inbound_frame_set_mark(iframe, 4); + inbound_frame_set_mark(iframe, pri_fieldlen); break; } } @@ -4858,10 +5250,13 @@ int nghttp2_session_resume_data(nghttp2_session *session, int32_t stream_id) (stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL)) { return NGHTTP2_ERR_INVALID_ARGUMENT; } - rv = nghttp2_pq_push(&session->ob_pq, stream->deferred_data); - if(rv == 0) { - nghttp2_stream_detach_deferred_data(stream); + + rv = nghttp2_stream_detach_deferred_data(stream, &session->ob_pq); + + if(nghttp2_is_fatal(rv)) { + return rv; } + return rv; } @@ -4930,6 +5325,7 @@ int nghttp2_session_upgrade(nghttp2_session *session, int max_conn_val_seen = 0; int ini_win_size_seen = 0; size_t i; + nghttp2_priority_spec pri_spec; if((!session->server && session->next_stream_id != 1) || (session->server && session->last_recv_stream_id >= 1)) { @@ -4966,8 +5362,11 @@ int nghttp2_session_upgrade(nghttp2_session *session, if(rv != 0) { return rv; } + + nghttp2_priority_spec_group_init(&pri_spec, 1, NGHTTP2_DEFAULT_WEIGHT); + stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - 0, NGHTTP2_STREAM_OPENING, + &pri_spec, NGHTTP2_STREAM_OPENING, session->server ? NULL : stream_user_data); if(stream == NULL) { @@ -4983,3 +5382,41 @@ int nghttp2_session_upgrade(nghttp2_session *session, } return 0; } + +nghttp2_stream_group* nghttp2_session_open_stream_group +(nghttp2_session *session, int32_t pri_group_id, int32_t weight) +{ + int rv; + nghttp2_stream_group *stream_group; + + stream_group = malloc(sizeof(nghttp2_stream_group)); + + if(stream_group == NULL) { + return NULL; + } + + nghttp2_stream_group_init(stream_group, pri_group_id, weight); + + rv = nghttp2_map_insert(&session->stream_groups, &stream_group->map_entry); + + if(rv != 0) { + free(stream_group); + + return NULL; + } + + return stream_group; +} + +void nghttp2_session_close_stream_group_if_empty +(nghttp2_session *session, nghttp2_stream_group *stream_group) +{ + if(stream_group->num_streams == 0) { + DEBUGF(fprintf(stderr, "stream: stream_group(%p)=%d closed\n", + stream_group, stream_group->pri_group_id)); + + nghttp2_map_remove(&session->stream_groups, stream_group->pri_group_id); + nghttp2_stream_group_free(stream_group); + free(stream_group); + } +} diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index 3d5dd1b3..b74d481c 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -116,6 +116,7 @@ typedef enum { struct nghttp2_session { nghttp2_map /* */ streams; + nghttp2_map /* */ stream_groups; /* Queue for outbound frames other than stream-creating HEADERS */ nghttp2_pq /* */ ob_pq; /* Queue for outbound stream-creating HEADERS frame */ @@ -307,9 +308,8 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags, /* * Creates new stream in |session| with stream ID |stream_id|, - * priority |pri| and flags |flags|. NGHTTP2_FLAG_END_STREAM flag is - * set in |flags|, the sender of HEADERS will not send any further - * data in this stream. Since this function is called when initial + * priority |pri_spec| and flags |flags|. The |flags| is bitwise OR + * of nghttp2_stream_flag. Since this function is called when initial * HEADERS is sent or received, these flags are taken from it. The * state of stream is set to |initial_state|. The |stream_user_data| * is a pointer to the arbitrary user supplied data to be associated @@ -320,7 +320,8 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags, */ nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session, int32_t stream_id, - uint8_t flags, int32_t pri, + uint8_t flags, + nghttp2_priority_spec *pri_spec, nghttp2_stream_state initial_state, void *stream_user_data); @@ -332,8 +333,12 @@ nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session, * This function returns 0 if it succeeds, or one the following * negative error codes: * + * NGHTTP2_ERR_NOMEM + * Out of memory * NGHTTP2_ERR_INVALID_ARGUMENT * The specified stream does not exist. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. */ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code); @@ -506,6 +511,14 @@ int nghttp2_session_on_data_received(nghttp2_session *session, nghttp2_stream* nghttp2_session_get_stream(nghttp2_session *session, int32_t stream_id); +/* + * Returns nghttp2_stream_group* object whose priority group ID is + * |pri_group_id|. It could be NULL if such priority group does not + * exist. + */ +nghttp2_stream_group* nghttp2_session_get_stream_group +(nghttp2_session *session, int32_t pri_group_id); + /* * Packs DATA frame |frame| in wire frame format and stores it in * |*buf_ptr|. The capacity of |*buf_ptr| is |*buflen_ptr| @@ -580,9 +593,32 @@ int nghttp2_session_update_local_settings(nghttp2_session *session, size_t niv); /* - * Re-prioritize |stream|. The new priority is |pri|. + * Re-prioritize |stream|. The new priority specification is + * |pri_spec|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory */ -void nghttp2_session_reprioritize_stream -(nghttp2_session *session, nghttp2_stream *stream, int32_t pri); +int nghttp2_session_reprioritize_stream +(nghttp2_session *session, nghttp2_stream *stream, + const nghttp2_priority_spec *pri_spec); + +/* + * Creates new priority group using given values. + * + * This function returns created priority group if it succeeds, or + * NULL. + */ +nghttp2_stream_group* nghttp2_session_open_stream_group +(nghttp2_session *session, int32_t pri_group_id, int32_t weight); + +/* + * Closes priority group if it does not include any streams. + */ +void nghttp2_session_close_stream_group_if_empty +(nghttp2_session *session, nghttp2_stream_group *stream_group); #endif /* NGHTTP2_SESSION_H */ diff --git a/lib/nghttp2_stream.c b/lib/nghttp2_stream.c index f674657c..5373b972 100644 --- a/lib/nghttp2_stream.c +++ b/lib/nghttp2_stream.c @@ -25,9 +25,10 @@ #include "nghttp2_stream.h" #include +#include void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id, - uint8_t flags, int32_t pri, + uint8_t flags, nghttp2_stream_state initial_state, int32_t remote_initial_window_size, int32_t local_initial_window_size, @@ -36,22 +37,33 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id, nghttp2_map_entry_init(&stream->map_entry, stream_id); stream->stream_id = stream_id; stream->flags = flags; - stream->pri = pri; stream->state = initial_state; stream->shut_flags = NGHTTP2_SHUT_NONE; stream->stream_user_data = stream_user_data; + stream->data = NULL; stream->deferred_data = NULL; stream->deferred_flags = NGHTTP2_DEFERRED_NONE; stream->remote_window_size = remote_initial_window_size; stream->local_window_size = local_initial_window_size; stream->recv_window_size = 0; stream->recv_reduction = 0; + + stream->dep_prev = NULL; + stream->dep_next = NULL; + stream->sib_prev = NULL; + stream->sib_next = NULL; + + stream->stream_group = NULL; + stream->dpri = NGHTTP2_STREAM_DPRI_NO_DATA; + stream->num_substreams = 1; } void nghttp2_stream_free(nghttp2_stream *stream) { nghttp2_outbound_item_free(stream->deferred_data); free(stream->deferred_data); + + /* We don't free stream->data. */ } void nghttp2_stream_shutdown(nghttp2_stream *stream, nghttp2_shut_flag flag) @@ -59,19 +71,216 @@ void nghttp2_stream_shutdown(nghttp2_stream *stream, nghttp2_shut_flag flag) stream->shut_flags |= flag; } +static nghttp2_stream* stream_first_sib(nghttp2_stream *stream) +{ + for(; stream->sib_prev; stream = stream->sib_prev); + + return stream; +} + +static nghttp2_stream* stream_last_sib(nghttp2_stream *stream) +{ + for(; stream->sib_next; stream = stream->sib_next); + + return stream; +} + +static nghttp2_stream* stream_update_dep_length(nghttp2_stream *stream, + ssize_t delta) +{ + stream->num_substreams += delta; + + stream = stream_first_sib(stream); + + if(stream->dep_prev) { + return stream_update_dep_length(stream->dep_prev, delta); + } + + return stream; +} + +static void stream_update_dep_set_rest_stream_group +(nghttp2_stream *stream, nghttp2_stream_group *stream_group) +{ + if(stream == NULL) { + return; + } + + nghttp2_stream_group_remove_stream(stream->stream_group, stream); + nghttp2_stream_group_add_stream(stream_group, stream); + + if(stream->dpri == NGHTTP2_STREAM_DPRI_TOP) { + stream->dpri = NGHTTP2_STREAM_DPRI_REST; + } + + stream_update_dep_set_rest_stream_group(stream->sib_next, stream_group); + stream_update_dep_set_rest_stream_group(stream->dep_next, stream_group); +} + +static void stream_update_dep_set_rest(nghttp2_stream *stream) +{ + if(stream == NULL) { + return; + } + + if(stream->dpri == NGHTTP2_STREAM_DPRI_REST) { + return; + } + + if(stream->dpri == NGHTTP2_STREAM_DPRI_TOP) { + stream->dpri = NGHTTP2_STREAM_DPRI_REST; + + stream_update_dep_set_rest(stream->sib_next); + + return; + } + + stream_update_dep_set_rest(stream->sib_next); + stream_update_dep_set_rest(stream->dep_next); +} + +/* + * Performs dfs starting |stream|, search stream which can become + * NGHTTP2_STREAM_DPRI_TOP and queues its data. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +static int stream_update_dep_set_top(nghttp2_stream *stream, nghttp2_pq *pq) +{ + int rv; + + if(stream == NULL) { + return 0; + } + + if(stream->dpri == NGHTTP2_STREAM_DPRI_TOP) { + return stream_update_dep_set_top(stream->sib_next, pq); + } + + if(stream->dpri == NGHTTP2_STREAM_DPRI_REST) { + + DEBUGF(fprintf(stderr, "stream: stream=%d data is top\n", + stream->stream_id)); + + if(!stream->data->queued) { + rv = nghttp2_pq_push(pq, stream->data); + + if(rv != 0) { + return rv; + } + + stream->data->queued = 1; + } + + stream->dpri = NGHTTP2_STREAM_DPRI_TOP; + + return stream_update_dep_set_top(stream->sib_next, pq); + } + + assert(stream->dpri == NGHTTP2_STREAM_DPRI_NO_DATA); + + rv = stream_update_dep_set_top(stream->sib_next, pq); + + if(rv != 0) { + return rv; + } + + return stream_update_dep_set_top(stream->dep_next, pq); +} + +static int stream_update_dep_on_attach_data(nghttp2_stream *stream, + nghttp2_pq *pq) +{ + int rv; + nghttp2_stream *root_stream; + + stream->dpri = NGHTTP2_STREAM_DPRI_REST; + + stream_update_dep_set_rest(stream->dep_next); + + root_stream = nghttp2_stream_get_dep_root(stream); + + DEBUGF(fprintf(stderr, "root=%p, stream=%p\n", root_stream, stream)); + + rv = stream_update_dep_set_top(root_stream, pq); + + if(rv != 0) { + return rv; + } + + return 0; +} + +static int stream_update_dep_on_detach_data(nghttp2_stream *stream, + nghttp2_pq *pq) +{ + if(stream->dpri != NGHTTP2_STREAM_DPRI_TOP) { + stream->dpri = NGHTTP2_STREAM_DPRI_NO_DATA; + + return 0; + } + + stream->dpri = NGHTTP2_STREAM_DPRI_NO_DATA; + + return stream_update_dep_set_top(stream->dep_next, pq); +} + +int nghttp2_stream_attach_data(nghttp2_stream *stream, + nghttp2_outbound_item *data, + nghttp2_pq *pq) +{ + assert(stream->data == NULL); + assert(stream->deferred_data == NULL); + + stream->data = data; + + DEBUGF(fprintf(stderr, "stream: stream=%d attach data=%p\n", + stream->stream_id, data)); + + return stream_update_dep_on_attach_data(stream, pq); +} + +int nghttp2_stream_detach_data(nghttp2_stream *stream, nghttp2_pq *pq) +{ + DEBUGF(fprintf(stderr, "stream: stream=%d detach data=%p\n", + stream->stream_id, stream->data)); + + stream->data = NULL; + + return stream_update_dep_on_detach_data(stream, pq); +} + void nghttp2_stream_defer_data(nghttp2_stream *stream, nghttp2_outbound_item *data, uint8_t flags) { + assert(stream->data); + assert(stream->data == data); assert(stream->deferred_data == NULL); + stream->deferred_data = data; stream->deferred_flags = flags; + + stream->data = NULL; } -void nghttp2_stream_detach_deferred_data(nghttp2_stream *stream) +int nghttp2_stream_detach_deferred_data(nghttp2_stream *stream, + nghttp2_pq *pq) { + nghttp2_outbound_item *data; + assert(stream->data == NULL); + assert(stream->deferred_data); + + data = stream->deferred_data; + stream->deferred_data = NULL; stream->deferred_flags = NGHTTP2_DEFERRED_NONE; + + return nghttp2_stream_attach_data(stream, data, pq); } static int update_initial_window_size @@ -113,3 +322,344 @@ void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream) { stream->state = NGHTTP2_STREAM_OPENED; } + +nghttp2_stream* nghttp2_stream_get_dep_root(nghttp2_stream *stream) +{ + for(;;) { + if(stream->sib_prev) { + stream = stream->sib_prev; + + continue; + } + + if(stream->dep_prev) { + stream = stream->dep_prev; + + continue; + } + + break; + } + + return stream; +} + +int nghttp2_stream_dep_subtree_find(nghttp2_stream *stream, + nghttp2_stream *target) +{ + if(stream == NULL) { + return 0; + } + + if(stream == target) { + return 1; + } + + if(nghttp2_stream_dep_subtree_find(stream->sib_next, target)) { + return 1; + } + + return nghttp2_stream_dep_subtree_find(stream->dep_next, target); +} + +void nghttp2_stream_dep_insert(nghttp2_stream *dep_stream, + nghttp2_stream *stream) +{ + nghttp2_stream *si; + + assert(stream->data == NULL); + + DEBUGF(fprintf(stderr, + "stream: dep_insert dep_stream(%p)=%d, stream(%p)=%d\n", + dep_stream, dep_stream->stream_id, + stream, stream->stream_id)); + + if(dep_stream->dep_next) { + for(si = dep_stream->dep_next; si; si = si->sib_next) { + stream->num_substreams += si->num_substreams; + } + + stream->dep_next = dep_stream->dep_next; + stream->dep_next->dep_prev = stream; + } + + dep_stream->dep_next = stream; + stream->dep_prev = dep_stream; + + stream_update_dep_length(dep_stream, 1); +} + +void nghttp2_stream_dep_add(nghttp2_stream *dep_stream, + nghttp2_stream *stream) +{ + nghttp2_stream *last_sib; + + assert(stream->data == NULL); + + DEBUGF(fprintf(stderr, + "stream: dep_add dep_stream(%p)=%d, stream(%p)=%d\n", + dep_stream, dep_stream->stream_id, + stream, stream->stream_id)); + + stream_update_dep_length(dep_stream, 1); + + if(dep_stream->dep_next == NULL) { + dep_stream->dep_next = stream; + stream->dep_prev = dep_stream; + + return; + } + + last_sib = stream_last_sib(dep_stream->dep_next); + last_sib->sib_next = stream; + stream->sib_prev = last_sib; +} + +void nghttp2_stream_dep_remove(nghttp2_stream *stream) +{ + nghttp2_stream *prev, *next, *dep_next; + + DEBUGF(fprintf(stderr, "stream: dep_remove stream(%p)=%d\n", + stream, stream->stream_id)); + + prev = stream_first_sib(stream); + + if(prev->dep_prev) { + stream_update_dep_length(prev->dep_prev, -1); + } + + if(stream->sib_prev) { + prev = stream->sib_prev; + dep_next = stream->dep_next; + + if(dep_next) { + dep_next->dep_prev = NULL; + + prev->sib_next = dep_next; + dep_next->sib_prev = prev; + } else { + next = stream->sib_next; + + prev->sib_next = next; + + if(next) { + next->sib_prev = prev; + } + } + } else if(stream->dep_prev) { + prev = stream->dep_prev; + dep_next = stream->dep_next; + + if(dep_next) { + prev->dep_next = dep_next; + dep_next->dep_prev = prev; + } else if(stream->sib_next) { + next = stream->sib_next; + + prev->dep_next = next; + next->dep_prev = prev; + + next->sib_prev = NULL; + } else { + prev->dep_next = NULL; + dep_next = NULL; + } + } else { + nghttp2_stream *si; + + dep_next = NULL; + + /* stream is a root of tree. Removing stream makes its + descendants a root of its own subtree. */ + + for(si = stream->dep_next; si;) { + next = si->sib_next; + + si->dep_prev = NULL; + si->sib_prev = NULL; + si->sib_next = NULL; + + si = next; + } + } + + if(dep_next && stream->sib_next) { + prev = stream_last_sib(dep_next); + next = stream->sib_next; + + prev->sib_next = next; + next->sib_prev = prev; + } + + stream->num_substreams = 1; + stream->dep_prev = NULL; + stream->dep_next = NULL; + stream->sib_prev = NULL; + stream->sib_next = NULL; +} + +int nghttp2_stream_dep_insert_subtree(nghttp2_stream *dep_stream, + nghttp2_stream *stream, + nghttp2_pq *pq) +{ + nghttp2_stream *last_sib; + nghttp2_stream *dep_next; + nghttp2_stream *root_stream; + nghttp2_stream *si; + size_t delta_substreams; + + DEBUGF(fprintf(stderr, "stream: dep_insert_subtree dep_stream(%p)=%d " + "stream(%p)=%d\n", + dep_stream, dep_stream->stream_id, + stream, stream->stream_id)); + + delta_substreams = stream->num_substreams; + + stream_update_dep_set_rest_stream_group(stream, dep_stream->stream_group); + + if(dep_stream->dep_next) { + dep_next = dep_stream->dep_next; + + for(si = dep_stream->dep_next; si; si = si->sib_next) { + stream->num_substreams += si->num_substreams; + } + + stream_update_dep_set_rest(dep_next); + + dep_stream->dep_next = stream; + stream->dep_prev = dep_stream; + + if(stream->dep_next) { + last_sib = stream_last_sib(stream->dep_next); + + last_sib->sib_next = dep_next; + dep_next->sib_prev = last_sib; + + dep_next->dep_prev = NULL; + } else { + stream->dep_next = dep_next; + dep_next->dep_prev = stream; + } + } else { + dep_stream->dep_next = stream; + stream->dep_prev = dep_stream; + } + + root_stream = stream_update_dep_length(dep_stream, delta_substreams); + + return stream_update_dep_set_top(root_stream, pq); +} + +int nghttp2_stream_dep_add_subtree(nghttp2_stream *dep_stream, + nghttp2_stream *stream, + nghttp2_pq *pq) +{ + nghttp2_stream *last_sib; + nghttp2_stream *root_stream; + + DEBUGF(fprintf(stderr, "stream: dep_add_subtree dep_stream(%p)=%d " + "stream(%p)=%d\n", + dep_stream, dep_stream->stream_id, + stream, stream->stream_id)); + + stream_update_dep_set_rest_stream_group(stream, dep_stream->stream_group); + + if(dep_stream->dep_next) { + last_sib = stream_last_sib(dep_stream->dep_next); + + last_sib->sib_next = stream; + stream->sib_prev = last_sib; + } else { + dep_stream->dep_next = stream; + stream->dep_prev = dep_stream; + } + + root_stream = stream_update_dep_length(dep_stream, stream->num_substreams); + + return stream_update_dep_set_top(root_stream, pq); +} + +void nghttp2_stream_dep_remove_subtree(nghttp2_stream *stream) +{ + nghttp2_stream *prev, *next; + + if(stream->sib_prev) { + prev = stream->sib_prev; + + prev->sib_next = stream->sib_next; + if(prev->sib_next) { + prev->sib_next->sib_prev = prev; + } + + prev = stream_first_sib(prev); + if(prev->dep_prev) { + stream_update_dep_length(prev->dep_prev, -stream->num_substreams); + } + } else if(stream->dep_prev) { + prev = stream->dep_prev; + next = stream->sib_next; + + prev->dep_next = next; + + if(next) { + next->dep_prev = prev; + + next->sib_prev = NULL; + } + + stream_update_dep_length(prev, -stream->num_substreams); + } + + stream->sib_prev = NULL; + stream->sib_next = NULL; + stream->dep_prev = NULL; +} + +int nghttp2_stream_dep_make_root(nghttp2_stream_group *stream_group, + nghttp2_stream *stream, + nghttp2_pq *pq) +{ + stream_update_dep_set_rest_stream_group(stream, stream_group); + + return stream_update_dep_set_top(stream, pq); +} + +void nghttp2_stream_group_init(nghttp2_stream_group *stream_group, + int32_t pri_group_id, + int32_t weight) +{ + nghttp2_map_entry_init(&stream_group->map_entry, pri_group_id); + + stream_group->num_streams = 0; + stream_group->pri_group_id = pri_group_id; + stream_group->weight = weight; +} + +void nghttp2_stream_group_free(nghttp2_stream_group *stream_group) +{} + +void nghttp2_stream_group_add_stream(nghttp2_stream_group *stream_group, + nghttp2_stream *stream) +{ + DEBUGF(fprintf(stderr, "stream_group: stream_group(%p)=%d " + "add stream(%p)=%d\n", + stream_group, stream_group->pri_group_id, + stream, stream->stream_id)); + + stream->stream_group = stream_group; + + ++stream_group->num_streams; +} + +void nghttp2_stream_group_remove_stream(nghttp2_stream_group *stream_group, + nghttp2_stream *stream) +{ + DEBUGF(fprintf(stderr, "stream_group: stream_group(%p)=%d " + "remove stream(%p)=%d\n", + stream_group, stream_group->pri_group_id, + stream, stream->stream_id)); + + stream->stream_group = NULL; + + --stream_group->num_streams; +} diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h index 021bb800..9d352919 100644 --- a/lib/nghttp2_stream.h +++ b/lib/nghttp2_stream.h @@ -32,6 +32,8 @@ #include #include "nghttp2_outbound_item.h" #include "nghttp2_map.h" +#include "nghttp2_pq.h" +#include "nghttp2_int.h" /* * If local peer is stream initiator: @@ -78,23 +80,54 @@ typedef enum { NGHTTP2_STREAM_FLAG_PUSH = 0x01 } nghttp2_stream_flag; +typedef enum { + NGHTTP2_STREAM_DPRI_NONE = 0, + NGHTTP2_STREAM_DPRI_NO_DATA = 0x01, + NGHTTP2_STREAM_DPRI_TOP = 0x02, + NGHTTP2_STREAM_DPRI_REST = 0x04 +} nghttp2_stream_dpri; + typedef enum { NGHTTP2_DEFERRED_NONE = 0, /* Indicates the DATA is deferred due to flow control. */ NGHTTP2_DEFERRED_FLOW_CONTROL = 0x01 } nghttp2_deferred_flag; -typedef struct { +struct nghttp2_stream_group; + +typedef struct nghttp2_stream_group nghttp2_stream_group; + +struct nghttp2_stream; + +typedef struct nghttp2_stream nghttp2_stream; + +struct nghttp2_stream { /* Intrusive Map */ nghttp2_map_entry map_entry; + /* pointers to form dependency tree. If multiple streams depend on + a stream, only one stream (left most) has non-NULL dep_prev which + points to the stream it depends on. The remaining streams are + linked using sib_prev and sib_next. The stream which has + non-NULL dep_prev always NULL sib_prev. The right most stream + has NULL sib_next. If this stream is a root of dependency tree, + dep_prev and sib_prev are NULL. */ + nghttp2_stream *dep_prev, *dep_next; + nghttp2_stream *sib_prev, *sib_next; /* The arbitrary data provided by user for this stream. */ void *stream_user_data; + /* Active DATA frame */ + nghttp2_outbound_item *data; /* Deferred DATA frame */ nghttp2_outbound_item *deferred_data; /* stream ID */ int32_t stream_id; - /* Use same value in request HEADERS frame */ - int32_t pri; + /* priority group this stream belongs to */ + nghttp2_stream_group *stream_group; + /* categorized priority of this stream. Only stream bearing + NGHTTP2_STREAM_DPRI_TOP can send DATA frame. */ + nghttp2_stream_dpri dpri; + /* the number of nodes in subtree */ + size_t num_substreams; /* Current remote window size. This value is computed against the current initial window size of remote endpoint. */ int32_t remote_window_size; @@ -117,10 +150,10 @@ typedef struct { /* The flags for defered DATA. Bitwise OR of zero or more nghttp2_deferred_flag values */ uint8_t deferred_flags; -} nghttp2_stream; +}; void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id, - uint8_t flags, int32_t pri, + uint8_t flags, nghttp2_stream_state initial_state, int32_t remote_initial_window_size, int32_t local_initial_window_size, @@ -147,7 +180,8 @@ void nghttp2_stream_defer_data(nghttp2_stream *stream, * Detaches deferred data from this stream. This function does not * free deferred data. */ -void nghttp2_stream_detach_deferred_data(nghttp2_stream *stream); +int nghttp2_stream_detach_deferred_data(nghttp2_stream *stream, + nghttp2_pq *pq); /* * Updates the remote window size with the new value @@ -182,4 +216,154 @@ int nghttp2_stream_update_local_initial_window_size */ void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream); +/* + * Returns the stream positioned in root of the dependency tree the + * |stream| belongs to. + */ +nghttp2_stream* nghttp2_stream_get_dep_root(nghttp2_stream *stream); + +/* + * Returns nonzero if |target| is found in subtree of |stream|. + */ +int nghttp2_stream_dep_subtree_find(nghttp2_stream *stream, + nghttp2_stream *target); + +/* + * Makes the |stream| depend on the |dep_stream|. This dependency is + * exclusive. All existing direct descendants of |dep_stream| become + * the descendants of the |stream|. This function assumes + * |stream->data| is NULL and no dpri members are changed in this + * dependency tree. It also does not change stream->stream_group. + */ +void nghttp2_stream_dep_insert(nghttp2_stream *dep_stream, + nghttp2_stream *stream); + +/* + * Makes the |stream| depend on the |dep_stream|. This dependency is + * not exclusive. This function assumes |stream->data| is NULL and no + * dpri members are changed in this dependency tree. It also does not + * change stream->stream_group. + */ +void nghttp2_stream_dep_add(nghttp2_stream *dep_stream, + nghttp2_stream *stream); + +/* + * Removes the |stream| from the current dependency tree. This + * function assumes |stream->data| is NULL. + */ +void nghttp2_stream_dep_remove(nghttp2_stream *stream); + +/* + * Attaches |data| to |stream|. Updates dpri members in this + * dependency tree. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_stream_attach_data(nghttp2_stream *stream, + nghttp2_outbound_item *data, + nghttp2_pq *pq); + +/* + * Detaches |data| from |stream|. Updates dpri members in this + * dependency tree. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_stream_detach_data(nghttp2_stream *stream, nghttp2_pq *pq); + + +/* + * Makes the |stream| depend on the |dep_stream|. This dependency is + * exclusive. Updates dpri members in this dependency tree. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_stream_dep_insert_subtree(nghttp2_stream *dep_stream, + nghttp2_stream *stream, + nghttp2_pq *pq); + +/* + * Makes the |stream| depend on the |dep_stream|. This dependency is + * not exclusive. Updates dpri members in this dependency tree. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_stream_dep_add_subtree(nghttp2_stream *dep_stream, + nghttp2_stream *stream, + nghttp2_pq *pq); + +/* + * Removes subtree whose root stream is |stream|. Removing subtree + * does not change dpri values. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +void nghttp2_stream_dep_remove_subtree(nghttp2_stream *stream); + +/* + * Makes the |stream| as root for |stream_group|. Updates dpri + * members in this dependency tree. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_stream_dep_make_root(nghttp2_stream_group *stream_group, + nghttp2_stream *stream, + nghttp2_pq *pq); + +/* + * Priority group of streams. + */ +struct nghttp2_stream_group { + /* Intrusive Map */ + nghttp2_map_entry map_entry; + /* The number of streams this priority group contains */ + size_t num_streams; + /* The priority group ID */ + int32_t pri_group_id; + /* The weight of this group */ + int32_t weight; +}; + +void nghttp2_stream_group_init(nghttp2_stream_group *stream_group, + int32_t pri_group_id, + int32_t weight); + +void nghttp2_stream_group_free(nghttp2_stream_group *stream_group); + +/* + * Adds |stream| to |stream_group|. + */ +void nghttp2_stream_group_add_stream(nghttp2_stream_group *stream_group, + nghttp2_stream *stream); + +/* + * Removes |stream| from |stream_group|. + */ +void nghttp2_stream_group_remove_stream(nghttp2_stream_group *stream_group, + nghttp2_stream *stream); + #endif /* NGHTTP2_STREAM */ diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index 9836fa5f..7fc70b35 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -30,6 +30,7 @@ #include "nghttp2_session.h" #include "nghttp2_frame.h" #include "nghttp2_helper.h" +#include "nghttp2_priority_spec.h" /* This function takes ownership of |nva_copy|. Regardless of the return value, the caller must not free |nva_copy| after this @@ -38,7 +39,7 @@ static int nghttp2_submit_headers_shared (nghttp2_session *session, uint8_t flags, int32_t stream_id, - int32_t pri, + const nghttp2_priority_spec *pri_spec, nghttp2_nv *nva_copy, size_t nvlen, const nghttp2_data_provider *data_prd, @@ -49,10 +50,7 @@ static int nghttp2_submit_headers_shared nghttp2_frame *frame = NULL; nghttp2_data_provider *data_prd_copy = NULL; nghttp2_headers_aux_data *aux_data = NULL; - if(pri < 0) { - rv = NGHTTP2_ERR_INVALID_ARGUMENT; - goto fail; - } + if(data_prd != NULL && data_prd->read_callback != NULL) { data_prd_copy = malloc(sizeof(nghttp2_data_provider)); if(data_prd_copy == NULL) { @@ -75,15 +73,20 @@ static int nghttp2_submit_headers_shared rv = NGHTTP2_ERR_NOMEM; goto fail; } + flags_copy = - (flags & (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PRIORITY | - NGHTTP2_FLAG_END_SEGMENT)) | + (flags & (NGHTTP2_FLAG_END_STREAM | + NGHTTP2_FLAG_END_SEGMENT | + NGHTTP2_FLAG_PRIORITY_GROUP | + NGHTTP2_FLAG_PRIORITY_DEPENDENCY)) | NGHTTP2_FLAG_END_HEADERS; - nghttp2_frame_headers_init(&frame->headers, flags_copy, stream_id, pri, + nghttp2_frame_headers_init(&frame->headers, flags_copy, stream_id, pri_spec, nva_copy, nvlen); + rv = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, aux_data); + if(rv != 0) { nghttp2_frame_headers_free(&frame->headers); goto fail2; @@ -103,7 +106,7 @@ static int nghttp2_submit_headers_shared_nva (nghttp2_session *session, uint8_t flags, int32_t stream_id, - int32_t pri, + const nghttp2_priority_spec *pri_spec, const nghttp2_nv *nva, size_t nvlen, const nghttp2_data_provider *data_prd, @@ -111,21 +114,52 @@ static int nghttp2_submit_headers_shared_nva { ssize_t rv; nghttp2_nv *nva_copy; + nghttp2_priority_spec pri_spec_none = { + NGHTTP2_PRIORITY_TYPE_NONE + }; + const nghttp2_priority_spec *pri_spec_ptr; + rv = nghttp2_nv_array_copy(&nva_copy, nva, nvlen); if(rv < 0) { return rv; } + + if(pri_spec == NULL) { + pri_spec_ptr = &pri_spec_none; + } else { + pri_spec_ptr = pri_spec; + } + return nghttp2_submit_headers_shared(session, flags, stream_id, - pri, nva_copy, rv, data_prd, + pri_spec_ptr, nva_copy, rv, data_prd, stream_user_data); } int nghttp2_submit_headers(nghttp2_session *session, uint8_t flags, - int32_t stream_id, int32_t pri, + int32_t stream_id, + const nghttp2_priority_spec *pri_spec, const nghttp2_nv *nva, size_t nvlen, void *stream_user_data) { - return nghttp2_submit_headers_shared_nva(session, flags, stream_id, pri, + flags &= NGHTTP2_FLAG_END_STREAM; + + if(pri_spec) { + switch(pri_spec->pri_type) { + case NGHTTP2_PRIORITY_TYPE_GROUP: + flags |= NGHTTP2_FLAG_PRIORITY_GROUP; + break; + case NGHTTP2_PRIORITY_TYPE_DEP: + flags |= NGHTTP2_FLAG_PRIORITY_DEPENDENCY; + break; + default: + /* Default weight */ + pri_spec = NULL; + + break; + } + } + + return nghttp2_submit_headers_shared_nva(session, flags, stream_id, pri_spec, nva, nvlen, NULL, stream_user_data); } @@ -137,24 +171,46 @@ int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags, } int nghttp2_submit_priority(nghttp2_session *session, uint8_t flags, - int32_t stream_id, int32_t pri) + int32_t stream_id, + const nghttp2_priority_spec *pri_spec) { int rv; nghttp2_frame *frame; - if(pri < 0) { + + if(pri_spec == NULL) { return NGHTTP2_ERR_INVALID_ARGUMENT; } + + switch(pri_spec->pri_type) { + case NGHTTP2_PRIORITY_TYPE_GROUP: + break; + case NGHTTP2_PRIORITY_TYPE_DEP: + if(stream_id == pri_spec->dep.stream_id) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + break; + default: + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + frame = malloc(sizeof(nghttp2_frame)); + if(frame == NULL) { return NGHTTP2_ERR_NOMEM; } - nghttp2_frame_priority_init(&frame->priority, stream_id, pri); + + nghttp2_frame_priority_init(&frame->priority, stream_id, pri_spec); + rv = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL); + if(rv != 0) { nghttp2_frame_priority_free(&frame->priority); free(frame); + return rv; } + return 0; } @@ -260,26 +316,43 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags, return 0; } -static uint8_t set_request_flags(int32_t pri, +static uint8_t set_request_flags(const nghttp2_priority_spec *pri_spec, const nghttp2_data_provider *data_prd) { uint8_t flags = NGHTTP2_FLAG_NONE; if(data_prd == NULL || data_prd->read_callback == NULL) { flags |= NGHTTP2_FLAG_END_STREAM; } - if(pri != NGHTTP2_PRI_DEFAULT) { - flags |= NGHTTP2_FLAG_PRIORITY; + + if(pri_spec) { + switch(pri_spec->pri_type) { + case NGHTTP2_PRIORITY_TYPE_GROUP: + flags |= NGHTTP2_FLAG_PRIORITY_GROUP; + break; + case NGHTTP2_PRIORITY_TYPE_DEP: + flags |= NGHTTP2_FLAG_PRIORITY_DEPENDENCY; + break; + default: + /* Default weight */ + break; + } } + return flags; } -int nghttp2_submit_request(nghttp2_session *session, int32_t pri, +int nghttp2_submit_request(nghttp2_session *session, + const nghttp2_priority_spec *pri_spec, const nghttp2_nv *nva, size_t nvlen, const nghttp2_data_provider *data_prd, void *stream_user_data) { - uint8_t flags = set_request_flags(pri, data_prd); - return nghttp2_submit_headers_shared_nva(session, flags, -1, pri, nva, nvlen, + uint8_t flags; + + flags = set_request_flags(pri_spec, data_prd); + + return nghttp2_submit_headers_shared_nva(session, flags, -1, pri_spec, + nva, nvlen, data_prd, stream_user_data); } @@ -299,7 +372,7 @@ int nghttp2_submit_response(nghttp2_session *session, { uint8_t flags = set_response_flags(data_prd); return nghttp2_submit_headers_shared_nva(session, flags, stream_id, - NGHTTP2_PRI_DEFAULT, nva, nvlen, + NULL, nva, nvlen, data_prd, NULL); } diff --git a/src/HtmlParser.cc b/src/HtmlParser.cc index 2c4398e4..a805b1f9 100644 --- a/src/HtmlParser.cc +++ b/src/HtmlParser.cc @@ -103,7 +103,7 @@ void start_element_func if(!src_attr) { return; } - add_link(parser_data, src_attr, REQ_PRI_MEDIUM); + add_link(parser_data, src_attr, REQ_PRI_LOW); } } } // namespace diff --git a/src/app_helper.cc b/src/app_helper.cc index 2516c465..9aae3119 100644 --- a/src/app_helper.cc +++ b/src/app_helper.cc @@ -247,12 +247,6 @@ void print_flags(const nghttp2_frame_hd& hd) } s += "END_HEADERS"; } - if(hd.flags & NGHTTP2_FLAG_PRIORITY) { - if(!s.empty()) { - s += " | "; - } - s += "PRIORITY"; - } if(hd.flags & NGHTTP2_FLAG_PAD_LOW) { if(!s.empty()) { s += " | "; @@ -265,6 +259,27 @@ void print_flags(const nghttp2_frame_hd& hd) } s += "PAD_HIGH"; } + if(hd.flags & NGHTTP2_FLAG_PRIORITY_GROUP) { + if(!s.empty()) { + s += " | "; + } + s += "PRIORITY_GROUP"; + } + if(hd.flags & NGHTTP2_FLAG_PRIORITY_DEPENDENCY) { + if(!s.empty()) { + s += " | "; + } + s += "PRIORITY_DEPENDENCY"; + } + + break; + case NGHTTP2_PRIORITY: + if(hd.flags & NGHTTP2_FLAG_PRIORITY_GROUP) { + s += "PRIORITY_GROUP"; + } else if(hd.flags & NGHTTP2_FLAG_PRIORITY_DEPENDENCY) { + s += "PRIORITY_DEPENDENCY"; + } + break; case NGHTTP2_SETTINGS: if(hd.flags & NGHTTP2_FLAG_ACK) { @@ -332,8 +347,14 @@ void print_frame(print_type ptype, const nghttp2_frame *frame) case NGHTTP2_HEADERS: print_frame_attr_indent(); fprintf(outfile, "("); - if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY) { - fprintf(outfile, "pri=%d, ", frame->headers.pri); + if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY_GROUP) { + fprintf(outfile, "pri_group_id=%d, weight=%u, ", + frame->headers.pri_spec.group.pri_group_id, + frame->headers.pri_spec.group.weight); + } else if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY_DEPENDENCY) { + fprintf(outfile, "stream_id=%d, exclusive=%d, ", + frame->headers.pri_spec.dep.stream_id, + frame->headers.pri_spec.dep.exclusive); } fprintf(outfile, "padlen=%zu)\n", frame->headers.padlen); switch(frame->headers.cat) { @@ -356,7 +377,21 @@ void print_frame(print_type ptype, const nghttp2_frame *frame) break; case NGHTTP2_PRIORITY: print_frame_attr_indent(); - fprintf(outfile, "(pri=%d)\n", frame->priority.pri); + + fprintf(outfile, "("); + + if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY_GROUP) { + fprintf(outfile, "pri_group_id=%d, weight=%u", + frame->priority.pri_spec.group.pri_group_id, + frame->priority.pri_spec.group.weight); + } else if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY_DEPENDENCY) { + fprintf(outfile, "stream_id=%d, exclusive=%d", + frame->priority.pri_spec.dep.stream_id, + frame->priority.pri_spec.dep.exclusive); + } + + fprintf(outfile, ")\n"); + break; case NGHTTP2_RST_STREAM: print_frame_attr_indent(); diff --git a/src/h2load_http2_session.cc b/src/h2load_http2_session.cc index e2425a3b..824c4ad5 100644 --- a/src/h2load_http2_session.cc +++ b/src/h2load_http2_session.cc @@ -148,7 +148,7 @@ void Http2Session::submit_request() client_->reqidx = 0; } - nghttp2_submit_request(session_, 0, nva.data(), nva.size(), + nghttp2_submit_request(session_, nullptr, nva.data(), nva.size(), nullptr, nullptr); } diff --git a/src/nghttp.cc b/src/nghttp.cc index b860686e..0c0366c7 100644 --- a/src/nghttp.cc +++ b/src/nghttp.cc @@ -85,7 +85,7 @@ struct Config { size_t padding; ssize_t peer_max_concurrent_streams; ssize_t header_table_size; - int32_t pri; + int32_t weight; int multiply; // milliseconds int timeout; @@ -103,7 +103,7 @@ struct Config { padding(0), peer_max_concurrent_streams(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS), header_table_size(-1), - pri(NGHTTP2_PRI_DEFAULT), + weight(NGHTTP2_DEFAULT_WEIGHT), multiply(1), timeout(-1), window_bits(-1), @@ -146,6 +146,16 @@ std::string strip_fragment(const char *raw_uri) } } // namespace +namespace { +struct Request; +} // namespace + +namespace { +struct Dependency { + std::vector> deps; +}; +} // namespace + namespace { struct Request { Headers res_nva; @@ -154,28 +164,37 @@ struct Request { std::string uri; std::string status; http_parser_url u; + std::shared_ptr dep; + nghttp2_priority_spec pri_spec; RequestStat stat; int64_t data_length; int64_t data_offset; nghttp2_gzip *inflater; HtmlParser *html_parser; const nghttp2_data_provider *data_prd; - int32_t pri; + int32_t stream_id; // Recursion level: 0: first entity, 1: entity linked from first entity int level; + // RequestPriority value defined in HtmlParser.h + int pri; + // For pushed request, |uri| is empty and |u| is zero-cleared. Request(const std::string& uri, const http_parser_url &u, const nghttp2_data_provider *data_prd, int64_t data_length, - int32_t pri, int level = 0) + const nghttp2_priority_spec& pri_spec, + std::shared_ptr dep, int level = 0) : uri(uri), u(u), + dep(std::move(dep)), + pri_spec(pri_spec), data_length(data_length), data_offset(0), inflater(nullptr), html_parser(nullptr), data_prd(data_prd), - pri(pri), - level(level) + stream_id(-1), + level(level), + pri(0) {} ~Request() @@ -219,6 +238,55 @@ struct Request { return path; } + int32_t find_dep_stream_id(int start) + { + for(auto i = start; i >= 0; --i) { + for(auto req : dep->deps[i]) { + if(req->stat.stage != STAT_ON_COMPLETE) { + return req->stream_id; + } + } + } + return -1; + } + + nghttp2_priority_spec resolve_dep(int32_t pri) + { + nghttp2_priority_spec pri_spec = { NGHTTP2_PRIORITY_TYPE_NONE }; + int exclusive = 0; + int32_t stream_id = -1; + + if(pri == 0) { + return pri_spec; + } + + auto start = std::min(pri, (int)dep->deps.size() - 1); + + for(auto i = start; i >= 0; --i) { + if(dep->deps[i][0]->pri < pri) { + stream_id = find_dep_stream_id(i); + + if(i != (int)dep->deps.size() - 1) { + exclusive = 1; + } + + break; + } else if(dep->deps[i][0]->pri == pri) { + stream_id = find_dep_stream_id(i - 1); + + break; + } + } + + if(stream_id == -1) { + return pri_spec; + } + + nghttp2_priority_spec_dep_init(&pri_spec, stream_id, exclusive); + + return pri_spec; + } + bool is_ipv6_literal_addr() const { if(util::has_uri_field(u, UF_HOST)) { @@ -773,7 +841,8 @@ struct HttpClient { bool add_request(const std::string& uri, const nghttp2_data_provider *data_prd, int64_t data_length, - int32_t pri, + const nghttp2_priority_spec& pri_spec, + std::shared_ptr dep, int level = 0) { http_parser_url u; @@ -787,8 +856,11 @@ struct HttpClient { if(config.multiply == 1) { path_cache.insert(uri); } + reqvec.push_back(util::make_unique(uri, u, data_prd, - data_length, pri, level)); + data_length, + pri_spec, std::move(dep), + level)); return true; } } @@ -892,8 +964,9 @@ int submit_request for(auto& kv : build_headers) { nva.push_back(http2::make_nv(kv.first, kv.second)); } - int rv = nghttp2_submit_request(client->session, req->pri, - nva.data(), nva.size(), req->data_prd, req); + + auto rv = nghttp2_submit_request(client->session, &req->pri_spec, + nva.data(), nva.size(), req->data_prd, req); if(rv != 0) { std::cerr << "nghttp2_submit_request() returned error: " << nghttp2_strerror(rv) << std::endl; @@ -903,17 +976,6 @@ int submit_request } } // namespace -namespace { -int32_t adjust_pri(int32_t base_pri, int32_t rel_pri) -{ - if((int32_t)NGHTTP2_PRI_LOWEST - rel_pri < base_pri) { - return NGHTTP2_PRI_LOWEST; - } else { - return base_pri + rel_pri; - } -} -} // namespace - namespace { void update_html_parser(HttpClient *client, Request *req, const uint8_t *data, size_t len, int fin) @@ -931,9 +993,23 @@ void update_html_parser(HttpClient *client, Request *req, util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_SCHEMA) && util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_HOST) && util::porteq(uri.c_str(), u, req->uri.c_str(), req->u)) { - int32_t pri = adjust_pri(req->pri, p.second); // No POST data for assets - if ( client->add_request(uri, nullptr, 0, pri, req->level+1) ) { + + nghttp2_priority_spec pri_spec; + + // We always specify priority group of parent stream, so that + // even if the parent stream is closed, the dependent stream is + // in the same priority group. We adjust the priority using + // separate PRIORITY frame after stream ID becomes known. + nghttp2_priority_spec_group_init(&pri_spec, req->stream_id, + config.weight); + + if ( client->add_request(uri, nullptr, 0, pri_spec, req->dep, + req->level+1) ) { + auto& req = client->reqvec.back(); + + req->pri = p.second; + submit_request(client, config.headers, client->reqvec.back().get()); } @@ -997,8 +1073,48 @@ void check_stream_id(nghttp2_session *session, int32_t stream_id, auto req = (Request*)nghttp2_session_get_stream_user_data(session, stream_id); assert(req); + req->stream_id = stream_id; client->streams[stream_id] = req; req->record_request_time(); + + if(req->pri == 0) { + assert(req->dep->deps.empty()); + + req->dep->deps.push_back(std::vector{req}); + + return; + } + + if(stream_id % 2 == 0) { + return; + } + + auto pri_spec = req->resolve_dep(req->pri); + + if(pri_spec.pri_type == NGHTTP2_PRIORITY_TYPE_DEP) { + nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, stream_id, + &pri_spec); + } + + auto itr = std::begin(req->dep->deps); + for(; itr != std::end(req->dep->deps); ++itr) { + if((*itr)[0]->pri == req->pri) { + (*itr).push_back(req); + + break; + } + + if((*itr)[0]->pri > req->pri) { + auto v = std::vector{req}; + req->dep->deps.insert(itr, std::move(v)); + + break; + } + } + + if(itr == std::end(req->dep->deps)) { + req->dep->deps.push_back(std::vector{req}); + } } } // namespace @@ -1075,7 +1191,10 @@ int on_begin_headers_callback(nghttp2_session *session, http_parser_url u; memset(&u, 0, sizeof(u)); // TODO Set pri and level - auto req = util::make_unique("", u, nullptr, 0, 0, 0); + nghttp2_priority_spec pri_spec = { NGHTTP2_PRIORITY_TYPE_NONE }; + + auto req = util::make_unique("", u, nullptr, 0, pri_spec, + nullptr); nghttp2_session_set_stream_user_data(session, stream_id, req.get()); client->reqvec.push_back(std::move(req)); check_stream_id(session, stream_id, user_data); @@ -1484,10 +1603,18 @@ int communicate(const std::string& scheme, const std::string& host, } { HttpClient client{callbacks, evbase, ssl_ctx}; + + nghttp2_priority_spec pri_spec = { NGHTTP2_PRIORITY_TYPE_NONE }; + + if(config.weight != NGHTTP2_DEFAULT_WEIGHT) { + nghttp2_priority_spec_group_init(&pri_spec, -1, config.weight); + } + for(auto req : requests) { for(int i = 0; i < config.multiply; ++i) { + auto dep = std::make_shared(); client.add_request(std::get<0>(req), std::get<1>(req), - std::get<2>(req), config.pri); + std::get<2>(req), pri_spec, std::move(dep)); } } client.update_hostport(); @@ -1682,9 +1809,9 @@ Options: is ignored if the request URI has https scheme. If -d is used, the HTTP upgrade request is performed with OPTIONS method. - -p, --pri= - Sets stream priority. Default: )" - << NGHTTP2_PRI_DEFAULT << R"( + -p, --weight= + Sets priority group weight. Default: )" + << NGHTTP2_DEFAULT_WEIGHT << R"( -M, --peer-max-concurrent-streams= Use as SETTINGS_MAX_CONCURRENT_STREAMS value of remote endpoint as if it is received in @@ -1721,7 +1848,7 @@ int main(int argc, char **argv) {"data", required_argument, nullptr, 'd'}, {"multiply", required_argument, nullptr, 'm'}, {"upgrade", no_argument, nullptr, 'u'}, - {"pri", required_argument, nullptr, 'p'}, + {"weight", required_argument, nullptr, 'p'}, {"peer-max-concurrent-streams", required_argument, nullptr, 'M'}, {"header-table-size", required_argument, nullptr, 'c'}, {"padding", required_argument, nullptr, 'b'}, @@ -1758,11 +1885,11 @@ int main(int argc, char **argv) break; case 'p': { auto n = strtoul(optarg, nullptr, 10); - if(n <= NGHTTP2_PRI_LOWEST) { - config.pri = n; + if(n <= NGHTTP2_MAX_WEIGHT) { + config.weight = n; } else { std::cerr << "-p: specify the integer in the range [0, " - << NGHTTP2_PRI_LOWEST << "], inclusive" + << NGHTTP2_MAX_WEIGHT << "], inclusive" << std::endl; exit(EXIT_FAILURE); } diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index 72f59006..843d7904 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -578,7 +578,8 @@ int Http2Session::submit_request(Http2DownstreamConnection *dconn, { assert(state_ == CONNECTED); auto sd = util::make_unique(); - int rv = nghttp2_submit_request(session_, pri, nva, nvlen, + // TODO Specify nullptr to pri_spec for now + int rv = nghttp2_submit_request(session_, nullptr, nva, nvlen, data_prd, sd.get()); if(rv == 0) { dconn->attach_stream_data(sd.get()); @@ -640,9 +641,15 @@ int Http2Session::submit_priority(Http2DownstreamConnection *dconn, return 0; } int rv; - rv = nghttp2_submit_priority(session_, NGHTTP2_FLAG_NONE, - dconn->get_downstream()-> - get_downstream_stream_id(), pri); + + // TODO Disabled temporarily + + // rv = nghttp2_submit_priority(session_, NGHTTP2_FLAG_NONE, + // dconn->get_downstream()-> + // get_downstream_stream_id(), pri); + + rv = 0; + if(rv < NGHTTP2_ERR_FATAL) { SSLOG(FATAL, this) << "nghttp2_submit_priority() failed: " << nghttp2_strerror(rv); diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index d309cd4b..47e506c0 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -223,9 +223,12 @@ int on_begin_headers_callback(nghttp2_session *session, ULOG(INFO, upstream) << "Received upstream request HEADERS stream_id=" << frame->hd.stream_id; } + + // TODO Use priority 0 for now auto downstream = new Downstream(upstream, frame->hd.stream_id, - frame->headers.pri); + 0); + upstream->add_downstream(downstream); downstream->init_response_body_buf(); @@ -367,10 +370,11 @@ int on_frame_recv_callback if(!downstream) { break; } - rv = downstream->change_priority(frame->priority.pri); - if(rv != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } + // TODO comment out for now + // rv = downstream->change_priority(frame->priority.pri); + // if(rv != 0) { + // return NGHTTP2_ERR_CALLBACK_FAILURE; + // } break; } case NGHTTP2_SETTINGS: diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index ddfcf122..15b75950 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -75,7 +75,8 @@ int htp_msg_begin(http_parser *htp) ULOG(INFO, upstream) << "HTTP request started"; } upstream->reset_current_header_length(); - auto downstream = new Downstream(upstream, 0, NGHTTP2_PRI_DEFAULT); + // TODO specify 0 as priority for now + auto downstream = new Downstream(upstream, 0, 0); upstream->attach_downstream(downstream); return 0; } diff --git a/tests/main.c b/tests/main.c index dc2c8202..03076584 100644 --- a/tests/main.c +++ b/tests/main.c @@ -84,6 +84,8 @@ int main(int argc, char* argv[]) test_nghttp2_session_recv_data) || !CU_add_test(pSuite, "session_recv_continuation", test_nghttp2_session_recv_continuation) || + !CU_add_test(pSuite, "session_recv_headers_with_priority", + test_nghttp2_session_recv_headers_with_priority) || !CU_add_test(pSuite, "session_recv_premature_headers", test_nghttp2_session_recv_premature_headers) || !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) || @@ -209,6 +211,18 @@ int main(int argc, char* argv[]) test_nghttp2_session_pack_headers_with_padding4) || !CU_add_test(pSuite, "pack_settings_payload", test_nghttp2_pack_settings_payload) || + !CU_add_test(pSuite, "session_stream_dep_add", + test_nghttp2_session_stream_dep_add) || + !CU_add_test(pSuite, "session_stream_dep_remove", + test_nghttp2_session_stream_dep_remove) || + !CU_add_test(pSuite, "session_stream_dep_add_subtree", + test_nghttp2_session_stream_dep_add_subtree) || + !CU_add_test(pSuite, "session_stream_dep_remove_subtree", + test_nghttp2_session_stream_dep_remove_subtree) || + !CU_add_test(pSuite, "session_stream_attach_data", + test_nghttp2_session_stream_attach_data) || + !CU_add_test(pSuite, "session_stream_attach_data_subtree", + test_nghttp2_session_stream_attach_data_subtree) || !CU_add_test(pSuite, "frame_pack_headers", test_nghttp2_frame_pack_headers) || !CU_add_test(pSuite, "frame_pack_headers_frame_too_large", diff --git a/tests/nghttp2_frame_test.c b/tests/nghttp2_frame_test.c index 476268e8..57be1d8b 100644 --- a/tests/nghttp2_frame_test.c +++ b/tests/nghttp2_frame_test.c @@ -32,6 +32,7 @@ #include "nghttp2_frame.h" #include "nghttp2_helper.h" #include "nghttp2_test_helper.h" +#include "nghttp2_priority_spec.h" static nghttp2_nv make_nv(const char *name, const char *value) { @@ -74,6 +75,7 @@ void test_nghttp2_frame_pack_headers() nghttp2_headers frame, oframe; nghttp2_bufs bufs; nghttp2_nv *nva; + nghttp2_priority_spec pri_spec; ssize_t nvlen; nva_out out; ssize_t hdblocklen; @@ -87,10 +89,13 @@ void test_nghttp2_frame_pack_headers() nva = headers(); nvlen = HEADERS_LENGTH; + + pri_spec.pri_type = NGHTTP2_PRIORITY_TYPE_NONE; + nghttp2_frame_headers_init(&frame, - NGHTTP2_FLAG_END_STREAM|NGHTTP2_FLAG_END_HEADERS, - 1000000007, - 1 << 20, nva, nvlen); + NGHTTP2_FLAG_END_STREAM | + NGHTTP2_FLAG_END_HEADERS, + 1000000007, &pri_spec, nva, nvlen); rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater); nghttp2_bufs_rewind(&bufs); @@ -103,8 +108,8 @@ void test_nghttp2_frame_pack_headers() NGHTTP2_HEADERS, NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS, 1000000007, &oframe.hd); - /* We didn't include PRIORITY flag so priority is not packed */ - CU_ASSERT(1 << 30 == oframe.pri); + /* We include NGHTTP2_PRIORITY_TYPE_NONE */ + CU_ASSERT(NGHTTP2_PRIORITY_TYPE_NONE == oframe.pri_spec.pri_type); hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN; CU_ASSERT(hdblocklen == @@ -119,8 +124,10 @@ void test_nghttp2_frame_pack_headers() nghttp2_bufs_reset(&bufs); memset(&oframe, 0, sizeof(oframe)); - /* Next, include PRIORITY flag */ - frame.hd.flags |= NGHTTP2_FLAG_PRIORITY; + /* Next, include NGHTTP2_PRIORITY_TYPE_GROUP */ + nghttp2_priority_spec_group_init(&frame.pri_spec, 1000000009, 12); + frame.hd.flags |= NGHTTP2_FLAG_PRIORITY_GROUP; + rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater); CU_ASSERT(0 == rv); @@ -130,20 +137,64 @@ void test_nghttp2_frame_pack_headers() check_frame_header(nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN, NGHTTP2_HEADERS, NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS | - NGHTTP2_FLAG_PRIORITY, + NGHTTP2_FLAG_PRIORITY_GROUP, 1000000007, &oframe.hd); - CU_ASSERT(1 << 20 == oframe.pri); - hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN - 4; + CU_ASSERT(NGHTTP2_PRIORITY_TYPE_GROUP == oframe.pri_spec.pri_type); + CU_ASSERT(1000000009 == oframe.pri_spec.group.pri_group_id); + CU_ASSERT(12 == oframe.pri_spec.group.weight); + + hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN + - nghttp2_frame_priority_len(oframe.hd.flags); CU_ASSERT(hdblocklen == - inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN + 4)); + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN + + nghttp2_frame_priority_len(oframe.hd.flags))); nghttp2_nv_array_sort(out.nva, out.nvlen); CU_ASSERT(nvnameeq("method", &out.nva[0])); + nghttp2_frame_headers_free(&oframe); + nva_out_reset(&out); + nghttp2_bufs_reset(&bufs); + + memset(&oframe, 0, sizeof(oframe)); + + /* Next, include NGHTTP2_PRIORITY_TYPE_DEP */ + nghttp2_priority_spec_dep_init(&frame.pri_spec, 123, 1); + + frame.hd.flags = + NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY_DEPENDENCY; + + rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs)); + + check_frame_header(nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN, + NGHTTP2_HEADERS, + NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY_DEPENDENCY, + 1000000007, &oframe.hd); + + CU_ASSERT(NGHTTP2_PRIORITY_TYPE_DEP == oframe.pri_spec.pri_type); + CU_ASSERT(123 == oframe.pri_spec.dep.stream_id); + CU_ASSERT(1 == oframe.pri_spec.dep.exclusive); + + hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN + - nghttp2_frame_priority_len(oframe.hd.flags); + CU_ASSERT(hdblocklen == + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN + + nghttp2_frame_priority_len(oframe.hd.flags))); + + nghttp2_nv_array_sort(out.nva, out.nvlen); + CU_ASSERT(nvnameeq("method", &out.nva[0])); + + nghttp2_frame_headers_free(&oframe); nva_out_reset(&out); nghttp2_bufs_free(&bufs); - nghttp2_frame_headers_free(&oframe); + nghttp2_frame_headers_free(&frame); nghttp2_hd_inflate_free(&inflater); nghttp2_hd_deflate_free(&deflater); @@ -155,6 +206,7 @@ void test_nghttp2_frame_pack_headers_frame_too_large(void) nghttp2_headers frame; nghttp2_bufs bufs; nghttp2_nv *nva; + nghttp2_priority_spec pri_spec; ssize_t nvlen; size_t big_vallen = NGHTTP2_HD_MAX_VALUE; nghttp2_nv big_hds[16]; @@ -173,12 +225,13 @@ void test_nghttp2_frame_pack_headers_frame_too_large(void) big_hds[i].valuelen = big_vallen; } + pri_spec.pri_type = NGHTTP2_PRIORITY_TYPE_NONE; + nvlen = nghttp2_nv_array_copy(&nva, big_hds, big_hdslen); nghttp2_hd_deflate_init(&deflater); nghttp2_frame_headers_init(&frame, NGHTTP2_FLAG_END_STREAM|NGHTTP2_FLAG_END_HEADERS, - 1000000007, - 0, nva, nvlen); + 1000000007, &pri_spec, nva, nvlen); rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater); CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == rv); @@ -194,22 +247,53 @@ void test_nghttp2_frame_pack_priority(void) { nghttp2_priority frame, oframe; nghttp2_bufs bufs; + nghttp2_priority_spec pri_spec; int rv; frame_pack_bufs_init(&bufs); - nghttp2_frame_priority_init(&frame, 1000000007, 1 << 30); + /* First, pack priority with priority group and weight */ + nghttp2_priority_spec_group_init(&pri_spec, 1000000009, 12); + + nghttp2_frame_priority_init(&frame, 1000000007, &pri_spec); + rv = nghttp2_frame_pack_priority(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(13 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs)); + check_frame_header(4, NGHTTP2_PRIORITY, NGHTTP2_FLAG_PRIORITY_GROUP, + 1000000007, &oframe.hd); + + CU_ASSERT(NGHTTP2_PRIORITY_TYPE_GROUP == oframe.pri_spec.pri_type); + CU_ASSERT(1000000009 == oframe.pri_spec.group.pri_group_id); + CU_ASSERT(12 == oframe.pri_spec.group.weight); + + nghttp2_frame_priority_free(&oframe); + nghttp2_bufs_reset(&bufs); + + memset(&oframe, 0, sizeof(oframe)); + + /* Next, pack priority with stream dependency */ + nghttp2_priority_spec_dep_init(&pri_spec, 79, 1); + + nghttp2_frame_priority_init(&frame, 1000000007, &pri_spec); rv = nghttp2_frame_pack_priority(&bufs, &frame); CU_ASSERT(0 == rv); CU_ASSERT(12 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs)); - check_frame_header(4, NGHTTP2_PRIORITY, NGHTTP2_FLAG_NONE, 1000000007, - &oframe.hd); - CU_ASSERT(1 << 30 == oframe.pri); + check_frame_header(4, NGHTTP2_PRIORITY, NGHTTP2_FLAG_PRIORITY_DEPENDENCY, + 1000000007, &oframe.hd); + + CU_ASSERT(NGHTTP2_PRIORITY_TYPE_DEP == oframe.pri_spec.pri_type); + CU_ASSERT(79 == oframe.pri_spec.dep.stream_id); + CU_ASSERT(1 == oframe.pri_spec.dep.exclusive); + + nghttp2_frame_priority_free(&oframe); + nghttp2_bufs_reset(&bufs); nghttp2_bufs_free(&bufs); - nghttp2_frame_priority_free(&oframe); nghttp2_frame_priority_free(&frame); } diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 1ce7fd54..da9c7108 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -34,6 +34,7 @@ #include "nghttp2_net.h" #include "nghttp2_helper.h" #include "nghttp2_test_helper.h" +#include "nghttp2_priority_spec.h" #define OB_CTRL(ITEM) nghttp2_outbound_item_get_ctrl_frame(ITEM) #define OB_CTRL_TYPE(ITEM) nghttp2_outbound_item_get_ctrl_frame_type(ITEM) @@ -350,6 +351,10 @@ static nghttp2_settings_entry* dup_iv(const nghttp2_settings_entry *iv, return nghttp2_frame_iv_copy(iv, niv); } +static nghttp2_priority_spec pri_spec_none = { + NGHTTP2_PRIORITY_TYPE_NONE +}; + void test_nghttp2_session_recv(void) { nghttp2_session *session; @@ -383,7 +388,7 @@ void test_nghttp2_session_recv(void) nvlen = nghttp2_nv_array_copy(&nva, nv, ARRLEN(nv)); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, - 1, NGHTTP2_PRI_DEFAULT, nva, nvlen); + 1, &pri_spec_none, nva, nvlen); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); CU_ASSERT(0 == rv); @@ -409,7 +414,7 @@ void test_nghttp2_session_recv(void) /* Received HEADERS without header block, which is valid */ nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, - 5, NGHTTP2_PRI_DEFAULT, NULL, 0); + 5, &pri_spec_none, NULL, 0); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); CU_ASSERT(0 == rv); @@ -482,7 +487,7 @@ void test_nghttp2_session_recv_invalid_stream_id(void) nghttp2_hd_deflate_init(&deflater); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, - NGHTTP2_PRI_DEFAULT, NULL, 0); + &pri_spec_none, NULL, 0); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); CU_ASSERT(0 == rv); @@ -528,7 +533,7 @@ void test_nghttp2_session_recv_invalid_frame(void) nghttp2_hd_deflate_init(&deflater); nvlen = nghttp2_nv_array_copy(&nva, nv, ARRLEN(nv)); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, - NGHTTP2_PRI_DEFAULT, nva, nvlen); + &pri_spec_none, nva, nvlen); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); CU_ASSERT(0 == rv); @@ -616,7 +621,7 @@ void test_nghttp2_session_recv_data(void) /* Create stream 1 with CLOSING state. DATA is ignored. */ stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_CLOSING, NULL); /* Set initial window size 16383 to check stream flow control, isolating it from the conneciton flow control */ @@ -715,6 +720,7 @@ void test_nghttp2_session_recv_continuation(void) uint8_t data[1024]; size_t datalen; nghttp2_frame_hd cont_hd; + nghttp2_priority_spec pri_spec; frame_pack_bufs_init(&bufs); @@ -728,8 +734,8 @@ void test_nghttp2_session_recv_continuation(void) /* Make 1 HEADERS and insert CONTINUATION header */ nvlen = nghttp2_nv_array_copy(&nva, nv1, ARRLEN(nv1)); - nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, - 1, NGHTTP2_PRI_DEFAULT, nva, nvlen); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, 1, + &pri_spec_none, nva, nvlen); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); CU_ASSERT(0 == rv); @@ -790,8 +796,8 @@ void test_nghttp2_session_recv_continuation(void) /* HEADERS without END_HEADERS flag */ nvlen = nghttp2_nv_array_copy(&nva, nv1, ARRLEN(nv1)); - nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, - 1, NGHTTP2_PRI_DEFAULT, nva, nvlen); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, 1, + &pri_spec_none, nva, nvlen); nghttp2_bufs_reset(&bufs); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); @@ -808,7 +814,9 @@ void test_nghttp2_session_recv_continuation(void) datalen = nghttp2_buf_len(buf); /* Followed by PRIORITY */ - nghttp2_frame_priority_init(&frame.priority, 1, 0); + nghttp2_priority_spec_group_init(&pri_spec, 1, 0); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); nghttp2_bufs_reset(&bufs); rv = nghttp2_frame_pack_priority(&bufs, &frame.priority); @@ -832,6 +840,195 @@ void test_nghttp2_session_recv_continuation(void) nghttp2_session_del(session); } +void test_nghttp2_session_recv_headers_with_priority(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const nghttp2_nv nv1[] = { + MAKE_NV("method", "GET"), + MAKE_NV("path", "/") + }; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_hd_deflater deflater; + nghttp2_outbound_item *item; + nghttp2_priority_spec pri_spec; + nghttp2_stream *stream; + + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater); + + /* With NGHTTP2_FLAG_PRIORITY_GROUP */ + nvlen = nghttp2_nv_array_copy(&nva, nv1, ARRLEN(nv1)); + + nghttp2_priority_spec_group_init(&pri_spec, 0, 99); + + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY_GROUP, + 1, &pri_spec, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(0 == stream->stream_group->pri_group_id); + CU_ASSERT(99 == stream->stream_group->weight); + + nghttp2_bufs_reset(&bufs); + + /* With NGHTTP2_FLAG_PRIORITY_DEP without exclusive flag set */ + nvlen = nghttp2_nv_array_copy(&nva, nv1, ARRLEN(nv1)); + + nghttp2_priority_spec_dep_init(&pri_spec, 1, 0); + + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY_DEPENDENCY, + 3, &pri_spec, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream(session, 3); + + CU_ASSERT(0 == stream->stream_group->pri_group_id); + CU_ASSERT(99 == stream->stream_group->weight); + CU_ASSERT(1 == stream->dep_prev->stream_id); + + nghttp2_bufs_reset(&bufs); + + /* With NGHTTP2_FLAG_PRIORITY_GROUP, but cut last 1 byte to make it + invalid. */ + nvlen = nghttp2_nv_array_copy(&nva, nv1, ARRLEN(nv1)); + + nghttp2_priority_spec_group_init(&pri_spec, 0, 99); + + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY_GROUP, + 5, &pri_spec, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(13 == 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); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream(session, 5); + + CU_ASSERT(NULL == stream); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); + CU_ASSERT(NGHTTP2_FRAME_SIZE_ERROR == OB_CTRL(item)->goaway.error_code); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Check dep_stream_id == stream_id */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater); + + nvlen = nghttp2_nv_array_copy(&nva, nv1, ARRLEN(nv1)); + + nghttp2_priority_spec_dep_init(&pri_spec, 1, 0); + + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY_DEPENDENCY, + 1, &pri_spec, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NULL == stream); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == OB_CTRL(item)->goaway.error_code); + + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + void test_nghttp2_session_recv_premature_headers(void) { nghttp2_session *session; @@ -859,8 +1056,8 @@ void test_nghttp2_session_recv_premature_headers(void) nghttp2_hd_deflate_init(&deflater); nvlen = nghttp2_nv_array_copy(&nva, nv1, ARRLEN(nv1)); - nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, - 1, NGHTTP2_PRI_DEFAULT, nva, nvlen); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + &pri_spec_none, nva, nvlen); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); CU_ASSERT(0 == rv); @@ -929,8 +1126,8 @@ void test_nghttp2_session_continue(void) /* Make 2 HEADERS frames */ nvlen = nghttp2_nv_array_copy(&nva, nv1, ARRLEN(nv1)); - nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, - 1, NGHTTP2_PRI_DEFAULT, nva, nvlen); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + &pri_spec_none, nva, nvlen); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); CU_ASSERT(0 == rv); @@ -945,8 +1142,8 @@ void test_nghttp2_session_continue(void) databuf.last = nghttp2_cpymem(databuf.last, buf->pos, nghttp2_buf_len(buf)); nvlen = nghttp2_nv_array_copy(&nva, nv2, ARRLEN(nv2)); - nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, - 3, NGHTTP2_PRI_DEFAULT, nva, nvlen); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3, + &pri_spec_none, nva, nvlen); nghttp2_bufs_reset(&bufs); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); @@ -1095,6 +1292,7 @@ void test_nghttp2_session_add_frame(void) malloc(sizeof(nghttp2_headers_aux_data)); nghttp2_nv *nva; ssize_t nvlen; + nghttp2_priority_spec pri_spec; memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); callbacks.send_callback = accumulator_send_callback; @@ -1108,16 +1306,20 @@ void test_nghttp2_session_add_frame(void) frame = malloc(sizeof(nghttp2_frame)); nvlen = nghttp2_nv_array_copy(&nva, nv, ARRLEN(nv)); + nghttp2_priority_spec_group_init(&pri_spec, 1, 12); + nghttp2_frame_headers_init(&frame->headers, - NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, - -1, NGHTTP2_PRI_DEFAULT, nva, nvlen); + NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY_GROUP, + -1, &pri_spec, nva, nvlen); CU_ASSERT(0 == nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, 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_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY) == acc.buf[3]); + CU_ASSERT((NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY_GROUP) == + acc.buf[3]); /* check stream id */ CU_ASSERT(1 == nghttp2_get_uint32(&acc.buf[4])); @@ -1135,6 +1337,7 @@ void test_nghttp2_session_on_request_headers_received(void) nghttp2_nv malformed_nva[] = { MAKE_NV(":path", "\x01") }; nghttp2_nv *nva; size_t nvlen; + nghttp2_priority_spec pri_spec; memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); callbacks.on_begin_headers_callback = on_begin_headers_callback; @@ -1142,9 +1345,12 @@ void test_nghttp2_session_on_request_headers_received(void) nghttp2_session_server_new(&session, &callbacks, &user_data); + nghttp2_priority_spec_group_init(&pri_spec, 1, 12); + nghttp2_frame_headers_init(&frame.headers, - NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, - stream_id, 1 << 20, NULL, 0); + NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY_GROUP, + stream_id, &pri_spec, NULL, 0); user_data.begin_headers_cb_called = 0; user_data.invalid_frame_recv_cb_called = 0; @@ -1153,15 +1359,16 @@ void test_nghttp2_session_on_request_headers_received(void) CU_ASSERT(1 == user_data.begin_headers_cb_called); stream = nghttp2_session_get_stream(session, stream_id); CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); - CU_ASSERT(1 << 20 == stream->pri); + CU_ASSERT(12 == stream->stream_group->weight); nghttp2_frame_headers_free(&frame.headers); /* More than un-ACKed max concurrent streams leads REFUSED_STREAM */ session->pending_local_max_concurrent_stream = 1; nghttp2_frame_headers_init(&frame.headers, - NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, - 3, NGHTTP2_PRI_DEFAULT, NULL, 0); + NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY_GROUP, + 3, &pri_spec, NULL, 0); user_data.invalid_frame_recv_cb_called = 0; CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == nghttp2_session_on_request_headers_received(session, &frame)); @@ -1175,8 +1382,9 @@ void test_nghttp2_session_on_request_headers_received(void) /* Stream ID less than or equal to the previouly received request HEADERS leads to connection error */ nghttp2_frame_headers_init(&frame.headers, - NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, - 3, NGHTTP2_PRI_DEFAULT, NULL, 0); + NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY_GROUP, + 3, &pri_spec, NULL, 0); user_data.invalid_frame_recv_cb_called = 0; CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == nghttp2_session_on_request_headers_received(session, &frame)); @@ -1192,8 +1400,9 @@ void test_nghttp2_session_on_request_headers_received(void) nvlen = nghttp2_nv_array_copy(&nva, malformed_nva, ARRLEN(malformed_nva)); nghttp2_frame_headers_init(&frame.headers, - NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, - 1, 1 << 20, nva, nvlen); + NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY_GROUP, + 1, &pri_spec, nva, nvlen); user_data.begin_headers_cb_called = 0; user_data.invalid_frame_recv_cb_called = 0; CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session, &frame)); @@ -1219,10 +1428,10 @@ void test_nghttp2_session_on_response_headers_received(void) nghttp2_session_client_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, - NGHTTP2_PRI_DEFAULT, NULL, 0); + &pri_spec_none, NULL, 0); user_data.begin_headers_cb_called = 0; user_data.invalid_frame_recv_cb_called = 0; @@ -1250,11 +1459,11 @@ void test_nghttp2_session_on_headers_received(void) nghttp2_session_client_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, NULL); nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, - NGHTTP2_PRI_DEFAULT, NULL, 0); + &pri_spec_none, NULL, 0); user_data.begin_headers_cb_called = 0; user_data.invalid_frame_recv_cb_called = 0; @@ -1272,7 +1481,7 @@ void test_nghttp2_session_on_headers_received(void) /* Check to see when NGHTTP2_STREAM_CLOSING, incoming HEADERS is discarded. */ stream = nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_CLOSING, NULL); frame.hd.stream_id = 3; frame.hd.flags = NGHTTP2_FLAG_END_HEADERS; @@ -1284,7 +1493,7 @@ void test_nghttp2_session_on_headers_received(void) /* Server initiated stream */ stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); /* half closed (remote) */ @@ -1323,10 +1532,10 @@ void test_nghttp2_session_on_push_response_headers_received(void) nghttp2_session_client_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, - NGHTTP2_PRI_DEFAULT, NULL, 0); + &pri_spec_none, NULL, 0); /* nghttp2_session_on_push_response_headers_received assumes stream's state is NGHTTP2_STREAM_RESERVED and session->server is 0. */ @@ -1344,7 +1553,7 @@ void test_nghttp2_session_on_push_response_headers_received(void) RST_STREAMed */ session->pending_local_max_concurrent_stream = 1; stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); frame.hd.stream_id = 4; CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == @@ -1363,7 +1572,7 @@ void test_nghttp2_session_on_push_response_headers_received(void) session->local_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = 1; stream = nghttp2_session_open_stream(session, 6, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); frame.hd.stream_id = 6; @@ -1385,7 +1594,9 @@ void test_nghttp2_session_on_priority_received(void) nghttp2_session_callbacks callbacks; my_user_data user_data; nghttp2_frame frame; - nghttp2_stream *stream; + nghttp2_stream *stream, *dep_stream; + nghttp2_priority_spec pri_spec; + nghttp2_outbound_item *item; memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); callbacks.on_frame_recv_callback = on_frame_recv_callback; @@ -1393,34 +1604,51 @@ void test_nghttp2_session_on_priority_received(void) nghttp2_session_server_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); - nghttp2_frame_priority_init(&frame.priority, 1, 1000000007); + nghttp2_priority_spec_group_init(&pri_spec, 999, 2); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); /* non-push and initiated by remote peer */ CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); - CU_ASSERT(1000000007 == stream->pri); + + CU_ASSERT(999 == stream->stream_group->pri_group_id); + CU_ASSERT(2 == stream->stream_group->weight); /* push and initiated by remote peer: no update */ stream->flags = NGHTTP2_STREAM_FLAG_PUSH; - stream->pri = NGHTTP2_PRI_DEFAULT; + + frame.priority.pri_spec.group.weight = 3; + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); - CU_ASSERT(NGHTTP2_PRI_DEFAULT == stream->pri); + + CU_ASSERT(2 == stream->stream_group->weight); stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); + dep_stream = nghttp2_session_open_stream(session, 3, + NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_none, + NGHTTP2_STREAM_OPENING, NULL); + frame.hd.stream_id = 2; /* non-push and initiated by local peer: no update */ CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); - CU_ASSERT(NGHTTP2_PRI_DEFAULT == stream->pri); + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->stream_group->weight); /* push and initiated by local peer */ stream->flags = NGHTTP2_STREAM_FLAG_PUSH; + + nghttp2_priority_spec_dep_init(&frame.priority.pri_spec, 3, 0); + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); - CU_ASSERT(1000000007 == stream->pri); + CU_ASSERT(3 == stream->stream_group->pri_group_id); + CU_ASSERT(dep_stream == stream->dep_prev); nghttp2_frame_priority_free(&frame.priority); nghttp2_session_del(session); @@ -1428,10 +1656,10 @@ void test_nghttp2_session_on_priority_received(void) /* Check that receiving PRIORITY in reserved(remote) is error */ nghttp2_session_server_new(&session, &callbacks, &user_data); nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); - nghttp2_frame_priority_init(&frame.priority, 3, 123); + nghttp2_frame_priority_init(&frame.priority, 3, &pri_spec); user_data.frame_recv_cb_called = 0; user_data.invalid_frame_recv_cb_called = 0; @@ -1441,6 +1669,25 @@ void test_nghttp2_session_on_priority_received(void) nghttp2_frame_priority_free(&frame.priority); nghttp2_session_del(session); + + /* Check dep_stream_id == stream_id case */ + nghttp2_session_server_new(&session, &callbacks, &user_data); + nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_none, + NGHTTP2_STREAM_OPENED, NULL); + + nghttp2_priority_spec_dep_init(&pri_spec, 1, 0); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); + + nghttp2_frame_priority_free(&frame.priority); + nghttp2_session_del(session); } void test_nghttp2_session_on_rst_stream_received(void) @@ -1452,7 +1699,7 @@ void test_nghttp2_session_on_rst_stream_received(void) memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); nghttp2_session_server_new(&session, &callbacks, &user_data); nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_PROTOCOL_ERROR); @@ -1495,10 +1742,10 @@ void test_nghttp2_session_on_settings_received(void) session->remote_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE] = 16*1024; stream1 = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); stream2 = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); /* Set window size for each streams and will see how settings updates these values */ @@ -1584,7 +1831,7 @@ void test_nghttp2_session_on_push_promise_received(void) nghttp2_session_client_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, 1, 2, @@ -1659,7 +1906,7 @@ void test_nghttp2_session_on_push_promise_received(void) nghttp2_session_client_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); /* Same ID twice */ @@ -1695,7 +1942,7 @@ void test_nghttp2_session_on_push_promise_received(void) nghttp2_session_client_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); /* Attempt to PUSH_PROMISE against reserved (remote) stream */ nghttp2_frame_push_promise_init(&frame.push_promise, @@ -1717,7 +1964,7 @@ void test_nghttp2_session_on_push_promise_received(void) nghttp2_session_client_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); session->local_settings[NGHTTP2_SETTINGS_ENABLE_PUSH] = 0; @@ -1741,7 +1988,7 @@ void test_nghttp2_session_on_push_promise_received(void) nghttp2_session_client_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); nvlen = nghttp2_nv_array_copy(&nva, malformed_nva, ARRLEN(malformed_nva)); nghttp2_frame_push_promise_init(&frame.push_promise, @@ -1839,8 +2086,16 @@ void test_nghttp2_session_on_window_update_received(void) nghttp2_session_client_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, NULL); + + data_item = malloc(sizeof(nghttp2_outbound_item)); + memset(data_item, 0, sizeof(nghttp2_outbound_item)); + data_item->frame_cat = NGHTTP2_CAT_DATA; + + CU_ASSERT(0 == nghttp2_stream_attach_data(stream, data_item, + &session->ob_pq)); + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1, 16*1024); @@ -1848,9 +2103,6 @@ void test_nghttp2_session_on_window_update_received(void) CU_ASSERT(1 == user_data.frame_recv_cb_called); CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE+16*1024 == stream->remote_window_size); - data_item = malloc(sizeof(nghttp2_outbound_item)); - memset(data_item, 0, sizeof(nghttp2_outbound_item)); - data_item->frame_cat = NGHTTP2_CAT_DATA; nghttp2_stream_defer_data(stream, data_item, NGHTTP2_DEFERRED_FLOW_CONTROL); CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); @@ -1863,7 +2115,7 @@ void test_nghttp2_session_on_window_update_received(void) /* WINDOW_UPDATE against reserved stream is a connection error */ stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, @@ -1891,7 +2143,7 @@ void test_nghttp2_session_on_data_received(void) nghttp2_session_client_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); frame.hd.length = 4096; @@ -1908,7 +2160,7 @@ void test_nghttp2_session_on_data_received(void) /* If NGHTTP2_STREAM_CLOSING state, DATA frame is discarded. */ stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_CLOSING, NULL); frame.hd.flags = NGHTTP2_FLAG_NONE; @@ -1947,7 +2199,7 @@ void test_nghttp2_session_send_headers_start_stream(void) nghttp2_session_client_new(&session, &callbacks, NULL); nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, -1, - NGHTTP2_PRI_DEFAULT, NULL, 0); + &pri_spec_none, NULL, 0); nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, aux_data); CU_ASSERT(0 == nghttp2_session_send(session)); stream = nghttp2_session_get_stream(session, 1); @@ -1968,10 +2220,10 @@ void test_nghttp2_session_send_headers_reply(void) CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, 2, - NGHTTP2_PRI_DEFAULT, NULL, 0); + &pri_spec_none, NULL, 0); nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL); CU_ASSERT(0 == nghttp2_session_send(session)); stream = nghttp2_session_get_stream(session, 2); @@ -2008,7 +2260,7 @@ void test_nghttp2_session_send_headers_header_comp_error(void) nvlen = nghttp2_nv_array_copy(&nva, nv, nnv); nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, -1, - NGHTTP2_PRI_DEFAULT, nva, nvlen); + &pri_spec_none, nva, nvlen); nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL); CU_ASSERT(0 == nghttp2_session_send(session)); @@ -2034,10 +2286,10 @@ void test_nghttp2_session_send_headers_push_reply(void) CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL)); nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, 2, - NGHTTP2_PRI_DEFAULT, NULL, 0); + &pri_spec_none, NULL, 0); nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL); CU_ASSERT(0 == session->num_outgoing_streams); CU_ASSERT(0 == nghttp2_session_send(session)); @@ -2058,7 +2310,7 @@ void test_nghttp2_session_send_rst_stream(void) callbacks.send_callback = null_send_callback; nghttp2_session_client_new(&session, &callbacks, &user_data); nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); frame = malloc(sizeof(nghttp2_frame)); @@ -2086,7 +2338,7 @@ void test_nghttp2_session_send_push_promise(void) nghttp2_session_server_new(&session, &callbacks, &ud); nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, NGHTTP2_STREAM_OPENING, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); nghttp2_frame_push_promise_init(&frame->push_promise, NGHTTP2_FLAG_END_HEADERS, 1, -1, @@ -2124,7 +2376,7 @@ void test_nghttp2_session_send_push_promise(void) /* PUSH_PROMISE from client is error */ nghttp2_session_client_new(&session, &callbacks, &ud); nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, NGHTTP2_STREAM_OPENING, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); frame = malloc(sizeof(nghttp2_frame)); nghttp2_frame_push_promise_init(&frame->push_promise, @@ -2257,48 +2509,24 @@ void test_nghttp2_session_reprioritize_stream(void) nghttp2_session_callbacks callbacks; my_user_data ud; nghttp2_stream *stream; - nghttp2_outbound_item *item; + nghttp2_priority_spec pri_spec; memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); callbacks.send_callback = block_count_send_callback; nghttp2_session_server_new(&session, &callbacks, &ud); - nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - 5000, - NGHTTP2_STREAM_OPENING, NULL); - stream = nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, - NGHTTP2_STREAM_OPENING, NULL); - CU_ASSERT(0 == nghttp2_submit_push_promise(session, - NGHTTP2_FLAG_END_HEADERS, - 3, NULL, 0, NULL)); - ud.block_count = 0; - CU_ASSERT(0 == nghttp2_session_send(session)); - /* Now PUSH_PROMISE is in aob */ - - CU_ASSERT(0 == nghttp2_submit_response(session, 1, NULL, 0, NULL)); - CU_ASSERT(0 == nghttp2_submit_response(session, 3, NULL, 0, NULL)); - - nghttp2_session_reprioritize_stream(session, stream, 120); - - CU_ASSERT(session->aob.item != NULL); - CU_ASSERT(120 == session->aob.item->pri); - CU_ASSERT(120 == stream->pri); - CU_ASSERT(5000 == nghttp2_session_get_stream(session, 1)->pri); - item = nghttp2_session_get_next_ob_item(session); - CU_ASSERT(120 == item->pri); - CU_ASSERT(NGHTTP2_HEADERS == OB_CTRL_TYPE(item)); - CU_ASSERT(3 == OB_CTRL(item)->hd.stream_id); - - nghttp2_session_del(session); - - /* Check aob.item == NULL case */ - nghttp2_session_server_new(&session, &callbacks, &ud); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); - nghttp2_session_reprioritize_stream(session, stream, 120); + + nghttp2_priority_spec_group_init(&pri_spec, 2, 10); + + nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(2 == stream->stream_group->pri_group_id); + CU_ASSERT(10 == stream->stream_group->weight); + nghttp2_session_del(session); } @@ -2324,7 +2552,7 @@ void test_nghttp2_submit_data(void) framebufs = &aob->framebufs; nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, NGHTTP2_STREAM_OPENING, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM | @@ -2373,7 +2601,7 @@ void test_nghttp2_submit_request_with_data(void) data_prd.read_callback = fixed_length_data_source_read_callback; ud.data_source_length = 64*1024 - 1; CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); - CU_ASSERT(0 == nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, + CU_ASSERT(0 == nghttp2_submit_request(session, NULL, nva, ARRLEN(nva), &data_prd, NULL)); item = nghttp2_session_get_next_ob_item(session); CU_ASSERT(nvnameeq(":version", &OB_CTRL(item)->headers.nva[0])); @@ -2409,7 +2637,7 @@ void test_nghttp2_submit_request_without_data(void) CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); nghttp2_hd_inflate_init(&inflater); - CU_ASSERT(0 == nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, + CU_ASSERT(0 == nghttp2_submit_request(session, NULL, nva, ARRLEN(nva), &data_prd, NULL)); item = nghttp2_session_get_next_ob_item(session); CU_ASSERT(nvnameeq(":version", &OB_CTRL(item)->headers.nva[0])); @@ -2448,7 +2676,7 @@ void test_nghttp2_submit_response_with_data(void) ud.data_source_length = 64*1024 - 1; CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_END_STREAM, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); CU_ASSERT(0 == nghttp2_submit_response(session, 1, nva, ARRLEN(nva), &data_prd)); @@ -2487,7 +2715,7 @@ void test_nghttp2_submit_response_without_data(void) nghttp2_hd_inflate_init(&inflater); nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_END_STREAM, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); CU_ASSERT(0 == nghttp2_submit_response(session, 1, nva, ARRLEN(nva), &data_prd)); @@ -2523,13 +2751,14 @@ void test_nghttp2_submit_headers_start_stream(void) CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, - -1, NGHTTP2_PRI_DEFAULT, + -1, NULL, nv, ARRLEN(nv), NULL)); item = nghttp2_session_get_next_ob_item(session); CU_ASSERT(nvnameeq(":version", &OB_CTRL(item)->headers.nva[0])); CU_ASSERT((NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM) == OB_CTRL(item)->hd.flags); - CU_ASSERT(NGHTTP2_PRI_DEFAULT == OB_CTRL(item)->headers.pri); + CU_ASSERT(NGHTTP2_PRIORITY_TYPE_NONE == + OB_CTRL(item)->headers.pri_spec.pri_type); nghttp2_session_del(session); } @@ -2552,7 +2781,7 @@ void test_nghttp2_submit_headers_reply(void) CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, - 1, NGHTTP2_PRI_DEFAULT, + 1, NULL, nv, ARRLEN(nv), NULL)); item = nghttp2_session_get_next_ob_item(session); CU_ASSERT(nvnameeq(":version", &OB_CTRL(item)->headers.nva[0])); @@ -2567,12 +2796,12 @@ void test_nghttp2_submit_headers_reply(void) CU_ASSERT(0 == ud.frame_send_cb_called); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, - 1, NGHTTP2_PRI_DEFAULT, + 1, NULL, nv, ARRLEN(nv), NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(1 == ud.frame_send_cb_called); @@ -2599,11 +2828,11 @@ void test_nghttp2_submit_headers_push_reply(void) CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, - 2, NGHTTP2_PRI_DEFAULT, + 2, NULL, nv, ARRLEN(nv), &foo)); ud.frame_send_cb_called = 0; @@ -2620,11 +2849,11 @@ void test_nghttp2_submit_headers_push_reply(void) error */ CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, - 2, NGHTTP2_PRI_DEFAULT, + 2, NULL, nv, ARRLEN(nv), NULL)); ud.frame_send_cb_called = 0; @@ -2665,7 +2894,7 @@ void test_nghttp2_submit_headers(void) nghttp2_hd_inflate_init(&inflater); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, - 1, NGHTTP2_PRI_DEFAULT, + 1, NULL, nv, ARRLEN(nv), NULL)); item = nghttp2_session_get_next_ob_item(session); CU_ASSERT(nvnameeq(":version", &OB_CTRL(item)->headers.nva[0])); @@ -2680,12 +2909,12 @@ void test_nghttp2_submit_headers(void) CU_ASSERT(0 == ud.frame_send_cb_called); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, - 1, NGHTTP2_PRI_DEFAULT, + 1, NULL, nv, ARRLEN(nv), NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(1 == ud.frame_send_cb_called); @@ -2738,13 +2967,14 @@ void test_nghttp2_submit_headers_continuation(void) CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, - -1, NGHTTP2_PRI_DEFAULT, + -1, NULL, nv, ARRLEN(nv), NULL)); item = nghttp2_session_get_next_ob_item(session); CU_ASSERT(NGHTTP2_HEADERS == OB_CTRL_TYPE(item)); CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) == OB_CTRL(item)->hd.flags); - CU_ASSERT(NGHTTP2_PRI_DEFAULT == OB_CTRL(item)->headers.pri); + CU_ASSERT(NGHTTP2_PRIORITY_TYPE_NONE == + OB_CTRL(item)->headers.pri_spec.pri_type); ud.frame_send_cb_called = 0; CU_ASSERT(0 == nghttp2_session_send(session)); @@ -2759,6 +2989,7 @@ void test_nghttp2_submit_priority(void) nghttp2_session_callbacks callbacks; nghttp2_stream *stream; my_user_data ud; + nghttp2_priority_spec pri_spec; memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); callbacks.send_callback = null_send_callback; @@ -2767,41 +2998,48 @@ void test_nghttp2_submit_priority(void) nghttp2_session_client_new(&session, &callbacks, &ud); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); + pri_spec.pri_type = 999999; + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == - nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, -1)); + nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec)); + + nghttp2_priority_spec_group_init(&pri_spec, 1, 3); + /* non-push stream and initiated by local peer */ CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, - 1000000007)); + &pri_spec)); CU_ASSERT(0 == nghttp2_session_send(session)); - CU_ASSERT(1000000007 == stream->pri); + CU_ASSERT(3 == stream->stream_group->weight); /* push stream and initiated by local peer: no update */ stream->flags = NGHTTP2_STREAM_FLAG_PUSH; - stream->pri = NGHTTP2_PRI_DEFAULT; + + nghttp2_priority_spec_group_init(&pri_spec, 1, 2); + CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, - 1000000007)); + &pri_spec)); CU_ASSERT(0 == nghttp2_session_send(session)); - CU_ASSERT(NGHTTP2_PRI_DEFAULT == stream->pri); + + CU_ASSERT(3 == stream->stream_group->weight); /* non-push stream and initiated by remote peer: no update */ stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 2, - 1000000007)); + &pri_spec)); CU_ASSERT(0 == nghttp2_session_send(session)); - CU_ASSERT(NGHTTP2_PRI_DEFAULT == stream->pri); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->stream_group->weight); /* push stream and initiated by remote peer */ stream->flags = NGHTTP2_STREAM_FLAG_PUSH; CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 2, - 1000000007)); + &pri_spec)); CU_ASSERT(0 == nghttp2_session_send(session)); - CU_ASSERT(1000000007 == stream->pri); - + CU_ASSERT(2 == stream->stream_group->weight); nghttp2_session_del(session); @@ -2809,10 +3047,11 @@ void test_nghttp2_submit_priority(void) error */ nghttp2_session_server_new(&session, &callbacks, &ud); stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); - CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 2, 123)); + CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 2, + &pri_spec)); ud.frame_send_cb_called = 0; ud.frame_not_send_cb_called = 0; @@ -2920,13 +3159,13 @@ void test_nghttp2_submit_settings_update_local_window_size(void) nghttp2_session_server_new(&session, &callbacks, NULL); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, NULL); stream->local_window_size = NGHTTP2_INITIAL_WINDOW_SIZE + 100; stream->recv_window_size = 32768; stream = nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, NULL); CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); @@ -2950,7 +3189,7 @@ void test_nghttp2_submit_settings_update_local_window_size(void) iv[0].value = 128*1024; nghttp2_session_server_new(&session, &callbacks, NULL); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, NULL); stream->local_window_size = NGHTTP2_MAX_WINDOW_SIZE; @@ -2983,7 +3222,7 @@ void test_nghttp2_submit_push_promise(void) CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); CU_ASSERT(0 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, nv, ARRLEN(nv), &ud)); @@ -3014,7 +3253,7 @@ void test_nghttp2_submit_window_update(void) nghttp2_session_client_new(&session, &callbacks, &ud); stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, NULL); stream->recv_window_size = 4096; @@ -3065,7 +3304,7 @@ void test_nghttp2_submit_window_update_local_window_size(void) nghttp2_session_client_new(&session, &callbacks, NULL); stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, NULL); stream->recv_window_size = 4096; @@ -3160,7 +3399,7 @@ void test_nghttp2_submit_invalid_nv(void) /* nghttp2_submit_request */ CU_ASSERT(0 == - nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, + nghttp2_submit_request(session, NULL, empty_name_nv, ARRLEN(empty_name_nv), NULL, NULL)); @@ -3173,7 +3412,7 @@ void test_nghttp2_submit_invalid_nv(void) /* nghttp2_submit_headers */ CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, - NGHTTP2_PRI_DEFAULT, + NULL, empty_name_nv, ARRLEN(empty_name_nv), NULL)); @@ -3191,44 +3430,54 @@ void test_nghttp2_session_open_stream(void) nghttp2_session *session; nghttp2_session_callbacks callbacks; nghttp2_stream *stream; + nghttp2_priority_spec pri_spec; memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); nghttp2_session_server_new(&session, &callbacks, NULL); + nghttp2_priority_spec_group_init(&pri_spec, 100, 245); + stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - 1000000007, NGHTTP2_STREAM_OPENED, + &pri_spec, NGHTTP2_STREAM_OPENED, NULL); CU_ASSERT(1 == session->num_incoming_streams); CU_ASSERT(0 == session->num_outgoing_streams); CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); - CU_ASSERT(1000000007 == stream->pri); + CU_ASSERT(100 == stream->stream_group->pri_group_id); + CU_ASSERT(245 == stream->stream_group->weight); CU_ASSERT(NGHTTP2_SHUT_NONE == stream->shut_flags); stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); CU_ASSERT(1 == session->num_incoming_streams); CU_ASSERT(1 == session->num_outgoing_streams); + CU_ASSERT(2 == stream->stream_group->pri_group_id); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->stream_group->weight); CU_ASSERT(NGHTTP2_SHUT_NONE == stream->shut_flags); stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); CU_ASSERT(1 == session->num_incoming_streams); CU_ASSERT(1 == session->num_outgoing_streams); + CU_ASSERT(4 == stream->stream_group->pri_group_id); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->stream_group->weight); CU_ASSERT(NGHTTP2_SHUT_RD == stream->shut_flags); nghttp2_session_del(session); nghttp2_session_client_new(&session, &callbacks, NULL); stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); CU_ASSERT(0 == session->num_incoming_streams); CU_ASSERT(0 == session->num_outgoing_streams); + CU_ASSERT(4 == stream->stream_group->pri_group_id); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->stream_group->weight); CU_ASSERT(NGHTTP2_SHUT_WR == stream->shut_flags); nghttp2_session_del(session); @@ -3238,6 +3487,8 @@ void test_nghttp2_session_get_next_ob_item(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; + nghttp2_priority_spec pri_spec; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); callbacks.send_callback = null_send_callback; @@ -3249,7 +3500,7 @@ void test_nghttp2_session_get_next_ob_item(void) CU_ASSERT(NGHTTP2_PING == OB_CTRL_TYPE(nghttp2_session_get_next_ob_item(session))); - nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, NULL, 0, NULL, NULL); + nghttp2_submit_request(session, NULL, NULL, 0, NULL, NULL); CU_ASSERT(NGHTTP2_PING == OB_CTRL_TYPE(nghttp2_session_get_next_ob_item(session))); @@ -3259,15 +3510,17 @@ void test_nghttp2_session_get_next_ob_item(void) /* Incoming stream does not affect the number of outgoing max concurrent streams. */ nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); - nghttp2_submit_request(session, 0, NULL, 0, NULL, NULL); + nghttp2_priority_spec_group_init(&pri_spec, -1, NGHTTP2_MAX_WEIGHT); + + nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL); CU_ASSERT(NGHTTP2_HEADERS == OB_CTRL_TYPE(nghttp2_session_get_next_ob_item(session))); CU_ASSERT(0 == nghttp2_session_send(session)); - nghttp2_submit_request(session, 0, NULL, 0, NULL, NULL); + nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL); CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); session->remote_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = 3; @@ -3283,6 +3536,8 @@ void test_nghttp2_session_pop_next_ob_item(void) nghttp2_session *session; nghttp2_session_callbacks callbacks; nghttp2_outbound_item *item; + nghttp2_priority_spec pri_spec; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); callbacks.send_callback = null_send_callback; @@ -3290,8 +3545,12 @@ void test_nghttp2_session_pop_next_ob_item(void) session->remote_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = 1; CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session)); + nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL); - nghttp2_submit_request(session, 1, NULL, 0, NULL, NULL); + + nghttp2_priority_spec_group_init(&pri_spec, -1, 254); + + nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL); item = nghttp2_session_pop_next_ob_item(session); CU_ASSERT(NGHTTP2_PING == OB_CTRL_TYPE(item)); @@ -3308,12 +3567,14 @@ void test_nghttp2_session_pop_next_ob_item(void) /* Incoming stream does not affect the number of outgoing max concurrent streams. */ nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - 3, NGHTTP2_STREAM_OPENING, NULL); + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); /* In-flight outgoing stream */ nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE, - 3, NGHTTP2_STREAM_OPENING, NULL); + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); - nghttp2_submit_request(session, 0, NULL, 0, NULL, NULL); + nghttp2_priority_spec_group_init(&pri_spec, -1, NGHTTP2_MAX_WEIGHT); + + nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL); nghttp2_submit_response(session, 1, NULL, 0, NULL); item = nghttp2_session_pop_next_ob_item(session); @@ -3337,10 +3598,10 @@ void test_nghttp2_session_pop_next_ob_item(void) nghttp2_session_server_new(&session, &callbacks, NULL); session->remote_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = 0; nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, NGHTTP2_STREAM_RESERVED, + &pri_spec_none, NGHTTP2_STREAM_RESERVED, NULL); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, - 2, NGHTTP2_PRI_DEFAULT, NULL, 0, + 2, NULL, NULL, 0, NULL)); CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session)); CU_ASSERT(1 == nghttp2_pq_size(&session->ob_ss_pq)); @@ -3361,7 +3622,7 @@ void test_nghttp2_session_reply_fail(void) ud.data_source_length = 4*1024; CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); CU_ASSERT(0 == nghttp2_submit_response(session, 1, NULL, 0, &data_prd)); CU_ASSERT(NGHTTP2_ERR_CALLBACK_FAILURE == nghttp2_session_send(session)); @@ -3380,12 +3641,12 @@ void test_nghttp2_session_max_concurrent_streams(void) nghttp2_session_server_new(&session, &callbacks, NULL); nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, NULL); /* Check un-ACKed SETTINGS_MAX_CONCURRENT_STREAMS */ nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3, - NGHTTP2_PRI_DEFAULT, NULL, 0); + &pri_spec_none, NULL, 0); session->pending_local_max_concurrent_stream = 1; CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == @@ -3461,7 +3722,7 @@ void test_nghttp2_session_stop_data_with_rst_stream(void) nghttp2_session_server_new(&session, &callbacks, &ud); nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); nghttp2_submit_response(session, 1, NULL, 0, &data_prd); @@ -3507,7 +3768,7 @@ void test_nghttp2_session_defer_data(void) nghttp2_session_server_new(&session, &callbacks, &ud); nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); nghttp2_submit_response(session, 1, NULL, 0, &data_prd); @@ -3585,8 +3846,7 @@ void test_nghttp2_session_flow_control(void) session->remote_window_size = 64*1024; session->remote_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE] = 64*1024; - nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, NULL, 0, - &data_prd, NULL); + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); /* Sends 64KiB - 1 data */ CU_ASSERT(0 == nghttp2_session_send(session)); @@ -3688,9 +3948,13 @@ void test_nghttp2_session_flow_control_data_recv(void) nghttp2_session_client_new(&session, &callbacks, NULL); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, NULL); + session->next_stream_id = 3; + + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); + session->local_window_size = NGHTTP2_MAX_PAYLOADLEN; stream->local_window_size = NGHTTP2_MAX_PAYLOADLEN; @@ -3708,7 +3972,7 @@ void test_nghttp2_session_flow_control_data_recv(void) item = nghttp2_session_get_next_ob_item(session); /* Since this is the last frame, stream-level WINDOW_UPDATE is not - issued, but connection-level does. */ + issued, but connection-level is. */ CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item)); CU_ASSERT(0 == OB_CTRL(item)->hd.stream_id); CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN == @@ -3750,7 +4014,7 @@ void test_nghttp2_session_flow_control_data_with_padding_recv(void) nghttp2_session_client_new(&session, &callbacks, NULL); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, NULL); /* Create DATA frame */ @@ -3795,7 +4059,7 @@ void test_nghttp2_session_data_read_temporal_failure(void) /* Initial window size is 64KiB - 1 */ nghttp2_session_client_new(&session, &callbacks, &ud); - nghttp2_submit_request(session, 3, NULL, 0, &data_prd, NULL); + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); /* Sends NGHTTP2_INITIAL_WINDOW_SIZE data, assuming, it is equal to or smaller than NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE */ @@ -3827,7 +4091,7 @@ void test_nghttp2_session_data_read_temporal_failure(void) CU_ASSERT(NGHTTP2_RST_STREAM == ud.sent_frame_type); data_prd.read_callback = fail_data_source_read_callback; - nghttp2_submit_request(session, 3, NULL, 0, &data_prd, NULL); + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); /* Sending data will fail (hard fail) and session tear down */ CU_ASSERT(NGHTTP2_ERR_CALLBACK_FAILURE == nghttp2_session_send(session)); @@ -3847,7 +4111,7 @@ void test_nghttp2_session_on_stream_close(void) nghttp2_session_client_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, &user_data); CU_ASSERT(stream != NULL); CU_ASSERT(nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR) == 0); @@ -3871,14 +4135,14 @@ void test_nghttp2_session_on_ctrl_not_send(void) nghttp2_session_server_new(&session, &callbacks, &user_data); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENING, &user_data); /* Check response HEADERS */ /* Send bogus stream ID */ CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 3, - NGHTTP2_PRI_DEFAULT, NULL, 0, NULL)); + NULL, NULL, 0, NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(1 == user_data.frame_not_send_cb_called); CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type); @@ -3889,7 +4153,7 @@ void test_nghttp2_session_on_ctrl_not_send(void) stream->shut_flags |= NGHTTP2_SHUT_WR; CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, - NGHTTP2_PRI_DEFAULT, NULL, 0, NULL)); + NULL, NULL, 0, NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(1 == user_data.frame_not_send_cb_called); CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type); @@ -3900,7 +4164,7 @@ void test_nghttp2_session_on_ctrl_not_send(void) /* Queue RST_STREAM */ CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, - NGHTTP2_PRI_DEFAULT, NULL, 0, NULL)); + NULL, NULL, 0, NULL)); CU_ASSERT(0 == nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1, NGHTTP2_INTERNAL_ERROR)); CU_ASSERT(0 == nghttp2_session_send(session)); @@ -3909,7 +4173,7 @@ void test_nghttp2_session_on_ctrl_not_send(void) CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED == user_data.not_sent_error); stream = nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, &user_data); /* Check HEADERS */ @@ -3917,7 +4181,7 @@ void test_nghttp2_session_on_ctrl_not_send(void) /* Queue RST_STREAM */ CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 3, - NGHTTP2_PRI_DEFAULT, NULL, 0, NULL)); + NULL, NULL, 0, NULL)); CU_ASSERT(0 == nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 3, NGHTTP2_INTERNAL_ERROR)); CU_ASSERT(0 == nghttp2_session_send(session)); @@ -3933,7 +4197,7 @@ void test_nghttp2_session_on_ctrl_not_send(void) /* Maximum Stream ID is reached */ session->next_stream_id = (1u << 31)+1; CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, - NGHTTP2_PRI_DEFAULT, NULL, 0, NULL)); + NULL, NULL, 0, NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(1 == user_data.frame_not_send_cb_called); CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type); @@ -3944,7 +4208,7 @@ void test_nghttp2_session_on_ctrl_not_send(void) CU_ASSERT(0 == nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, NGHTTP2_NO_ERROR, NULL, 0)); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, - NGHTTP2_PRI_DEFAULT, NULL, 0, NULL)); + NULL, NULL, 0, NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(1 == user_data.frame_not_send_cb_called); CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type); @@ -3982,7 +4246,7 @@ void test_nghttp2_session_get_effective_local_window_size(void) CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, + &pri_spec_none, NGHTTP2_STREAM_OPENED, NULL); CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE == @@ -4101,8 +4365,7 @@ void test_nghttp2_session_data_backoff_by_high_pri_frame(void) ud.data_source_length = NGHTTP2_DATA_PAYLOAD_LENGTH * 4; nghttp2_session_client_new(&session, &callbacks, &ud); - nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, NULL, 0, - &data_prd, NULL); + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); ud.block_count = 2; /* Sends request HEADERS + DATA[0] */ @@ -4144,7 +4407,7 @@ static void check_session_recv_data_with_padding(nghttp2_bufs *bufs, nghttp2_session_server_new(&session, &callbacks, &ud); nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_PRI_DEFAULT, NGHTTP2_STREAM_OPENING, + &pri_spec_none, NGHTTP2_STREAM_OPENING, NULL); inlen = nghttp2_bufs_remove(bufs, &in); @@ -4181,8 +4444,7 @@ void test_nghttp2_session_pack_data_with_padding(void) ud.padding_boundary = 512; - nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, NULL, 0, &data_prd, - NULL); + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); ud.block_count = 1; ud.data_source_length = datalen; /* Sends HEADERS */ @@ -4206,8 +4468,7 @@ void test_nghttp2_session_pack_data_with_padding(void) ud.padding_boundary = 64; - nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, NULL, 0, &data_prd, - NULL); + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); ud.block_count = 1; ud.data_source_length = datalen; /* Sends HEADERS */ @@ -4234,6 +4495,7 @@ void test_nghttp2_session_pack_headers_with_padding(void) nghttp2_session_callbacks callbacks; nghttp2_nv nva[4086]; size_t i; + nghttp2_priority_spec pri_spec; for(i = 0; i < ARRLEN(nva); ++i) { nva[i].name = (uint8_t*)":path"; @@ -4258,8 +4520,10 @@ void test_nghttp2_session_pack_headers_with_padding(void) ud.padding_boundary = 16385; + nghttp2_priority_spec_group_init(&pri_spec, 1, 255); + CU_ASSERT(0 == - nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, + nghttp2_submit_request(session, &pri_spec, nva, ARRLEN(nva), NULL, NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); @@ -4320,7 +4584,7 @@ void test_nghttp2_session_pack_headers_with_padding2(void) ud.padding_boundary = 16385; CU_ASSERT(0 == - nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, + nghttp2_submit_request(session, NULL, nva, ARRLEN(nva), NULL, NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); @@ -4369,7 +4633,7 @@ void test_nghttp2_session_pack_headers_with_padding3(void) ud.padding_boundary = 16385; CU_ASSERT(0 == - nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, + nghttp2_submit_request(session, NULL, nva, ARRLEN(nva), NULL, NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); @@ -4413,7 +4677,7 @@ void test_nghttp2_session_pack_headers_with_padding4(void) ud.padding_boundary = 16385; CU_ASSERT(0 == - nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, + nghttp2_submit_request(session, NULL, nva, ARRLEN(nva), NULL, NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); @@ -4456,3 +4720,618 @@ void test_nghttp2_pack_settings_payload(void) len = nghttp2_pack_settings_payload(buf, 9 /* too small */, iv, 2); CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == len); } + +#define check_stream_dep_sib(STREAM, DEP_PREV, DEP_NEXT, SIB_PREV, SIB_NEXT) \ + do { \ + CU_ASSERT(DEP_PREV == STREAM->dep_prev); \ + CU_ASSERT(DEP_NEXT == STREAM->dep_next); \ + CU_ASSERT(SIB_PREV == STREAM->sib_prev); \ + CU_ASSERT(SIB_NEXT == STREAM->sib_next); \ + } while(0) + +/* nghttp2_stream_dep_add() and its families functions should be + tested in nghttp2_stream_test.c, but it is easier to use + nghttp2_session_open_stream(). Therefore, we test them here. */ +void test_nghttp2_session_stream_dep_add(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * b--c + * | + * d + */ + + CU_ASSERT(4 == a->num_substreams); + CU_ASSERT(1 == b->num_substreams); + CU_ASSERT(2 == c->num_substreams); + CU_ASSERT(1 == d->num_substreams); + + check_stream_dep_sib(a, NULL, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, c); + check_stream_dep_sib(c, NULL, d, b, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + e = open_stream_with_dep_excl(session, 9, a); + + /* a + * | + * e + * | + * b--c + * | + * d + */ + + CU_ASSERT(5 == a->num_substreams); + CU_ASSERT(4 == e->num_substreams); + CU_ASSERT(1 == b->num_substreams); + CU_ASSERT(2 == c->num_substreams); + CU_ASSERT(1 == d->num_substreams); + + check_stream_dep_sib(a, NULL, e, NULL, NULL); + check_stream_dep_sib(e, a, b, NULL, NULL); + check_stream_dep_sib(b, e, NULL, NULL, c); + check_stream_dep_sib(c, NULL, d, b, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_dep_remove(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *f; + + memset(&callbacks, 0, sizeof(callbacks)); + + /* Remove root */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * b--c + * | + * d + */ + + nghttp2_stream_dep_remove(a); + + /* becomes: + * b c + * | + * d + */ + + CU_ASSERT(1 == a->num_substreams); + CU_ASSERT(1 == b->num_substreams); + CU_ASSERT(2 == c->num_substreams); + CU_ASSERT(1 == d->num_substreams); + + check_stream_dep_sib(a, NULL, NULL, NULL, NULL); + check_stream_dep_sib(b, NULL, NULL, NULL, NULL); + check_stream_dep_sib(c, NULL, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + nghttp2_session_del(session); + + /* Remove left most stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * b--c + * | + * d + */ + + nghttp2_stream_dep_remove(b); + + /* becomes: + * a + * | + * c + * | + * d + */ + + CU_ASSERT(3 == a->num_substreams); + CU_ASSERT(1 == b->num_substreams); + CU_ASSERT(2 == c->num_substreams); + CU_ASSERT(1 == d->num_substreams); + + check_stream_dep_sib(a, NULL, c, NULL, NULL); + check_stream_dep_sib(b, NULL, NULL, NULL, NULL); + check_stream_dep_sib(c, a, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + nghttp2_session_del(session); + + /* Remove right most stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * b--c + * | + * d + */ + + nghttp2_stream_dep_remove(c); + + /* becomes: + * a + * | + * b--d + */ + + CU_ASSERT(3 == a->num_substreams); + CU_ASSERT(1 == b->num_substreams); + CU_ASSERT(1 == c->num_substreams); + CU_ASSERT(1 == d->num_substreams); + + check_stream_dep_sib(a, NULL, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, d); + check_stream_dep_sib(c, NULL, NULL, NULL, NULL); + check_stream_dep_sib(d, NULL, NULL, b, NULL); + + nghttp2_session_del(session); + + /* Remove middle stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, a); + e = open_stream_with_dep(session, 9, c); + f = open_stream_with_dep(session, 11, c); + + /* a + * | + * b--c--d + * | + * e--f + */ + + CU_ASSERT(6 == a->num_substreams); + CU_ASSERT(1 == b->num_substreams); + CU_ASSERT(3 == c->num_substreams); + CU_ASSERT(1 == d->num_substreams); + CU_ASSERT(1 == e->num_substreams); + CU_ASSERT(1 == f->num_substreams); + + nghttp2_stream_dep_remove(c); + + /* becomes: + * a + * | + * b--e--f--d + */ + + CU_ASSERT(5 == a->num_substreams); + CU_ASSERT(1 == b->num_substreams); + CU_ASSERT(1 == c->num_substreams); + CU_ASSERT(1 == d->num_substreams); + CU_ASSERT(1 == e->num_substreams); + CU_ASSERT(1 == f->num_substreams); + + check_stream_dep_sib(a, NULL, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, e); + check_stream_dep_sib(c, NULL, NULL, NULL, NULL); + check_stream_dep_sib(e, NULL, NULL, b, f); + check_stream_dep_sib(f, NULL, NULL, e, d); + check_stream_dep_sib(d, NULL, NULL, f, NULL); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_dep_add_subtree(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *f; + + memset(&callbacks, 0, sizeof(callbacks)); + + /* dep_stream has dep_next */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + e = open_stream(session, 9); + f = open_stream_with_dep(session, 11, e); + + /* a e + * | | + * b--c f + * | + * d + */ + + nghttp2_stream_dep_add_subtree(a, e, &session->ob_pq); + + /* becomes + * a + * | + * b--c--e + * | | + * d f + */ + + CU_ASSERT(6 == a->num_substreams); + CU_ASSERT(1 == b->num_substreams); + CU_ASSERT(2 == c->num_substreams); + CU_ASSERT(1 == d->num_substreams); + CU_ASSERT(2 == e->num_substreams); + CU_ASSERT(1 == f->num_substreams); + + check_stream_dep_sib(a, NULL, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, c); + check_stream_dep_sib(c, NULL, d, b, e); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + check_stream_dep_sib(e, NULL, f, c, NULL); + check_stream_dep_sib(f, e, NULL, NULL, NULL); + + CU_ASSERT(a->stream_group == e->stream_group); + CU_ASSERT(a->stream_group == f->stream_group); + + nghttp2_session_del(session); + + /* dep_stream has dep_next and now we insert subtree */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + e = open_stream(session, 9); + f = open_stream_with_dep(session, 11, e); + + /* a e + * | | + * b--c f + * | + * d + */ + + nghttp2_stream_dep_insert_subtree(a, e, &session->ob_pq); + + /* becomes + * a + * | + * e + * | + * f--b--c + * | + * d + */ + + CU_ASSERT(6 == a->num_substreams); + CU_ASSERT(1 == b->num_substreams); + CU_ASSERT(2 == c->num_substreams); + CU_ASSERT(1 == d->num_substreams); + CU_ASSERT(5 == e->num_substreams); + CU_ASSERT(1 == f->num_substreams); + + check_stream_dep_sib(a, NULL, e, NULL, NULL); + check_stream_dep_sib(e, a, f, NULL, NULL); + check_stream_dep_sib(f, e, NULL, NULL, b); + check_stream_dep_sib(b, NULL, NULL, f, c); + check_stream_dep_sib(c, NULL, d, b, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + CU_ASSERT(a->stream_group == e->stream_group); + CU_ASSERT(a->stream_group == f->stream_group); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_dep_remove_subtree(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e; + + memset(&callbacks, 0, sizeof(callbacks)); + + /* Remove left most stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * b--c + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(c); + + /* becomes + * a c + * | | + * b d + */ + + CU_ASSERT(2 == a->num_substreams); + CU_ASSERT(1 == b->num_substreams); + CU_ASSERT(2 == c->num_substreams); + CU_ASSERT(1 == d->num_substreams); + + check_stream_dep_sib(a, NULL, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, NULL); + check_stream_dep_sib(c, NULL, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + nghttp2_session_del(session); + + /* Remove right most stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * b--c + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(b); + + /* becomes + * a b + * | + * c + * | + * d + */ + + CU_ASSERT(3 == a->num_substreams); + CU_ASSERT(1 == b->num_substreams); + CU_ASSERT(2 == c->num_substreams); + CU_ASSERT(1 == d->num_substreams); + + check_stream_dep_sib(a, NULL, c, NULL, NULL); + check_stream_dep_sib(c, a, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + check_stream_dep_sib(b, NULL, NULL, NULL, NULL); + + nghttp2_session_del(session); + + /* Remove middle stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + e = open_stream_with_dep(session, 9, a); + + /* a + * | + * b--c--e + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(c); + + /* becomes + * a c + * | | + * b--e d + */ + + CU_ASSERT(3 == a->num_substreams); + CU_ASSERT(1 == b->num_substreams); + CU_ASSERT(1 == e->num_substreams); + CU_ASSERT(2 == c->num_substreams); + CU_ASSERT(1 == d->num_substreams); + + check_stream_dep_sib(a, NULL, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, e); + check_stream_dep_sib(e, NULL, NULL, b, NULL); + check_stream_dep_sib(c, NULL, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + nghttp2_session_del(session); +} + +static nghttp2_outbound_item* create_data_ob_item(void) +{ + nghttp2_outbound_item *item; + + item = malloc(sizeof(nghttp2_outbound_item)); + memset(item, 0, sizeof(nghttp2_outbound_item)); + item->frame_cat = NGHTTP2_CAT_DATA; + + return item; +} + +void test_nghttp2_session_stream_attach_data(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d; + nghttp2_outbound_item *da, *db, *dc, *dd; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * b--c + * | + * d + */ + + db = create_data_ob_item(); + + nghttp2_stream_attach_data(b, db, &session->ob_pq); + + CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_DATA == a->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_DATA == c->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_DATA == d->dpri); + + CU_ASSERT(1 == db->queued); + + dc = create_data_ob_item(); + + nghttp2_stream_attach_data(c, dc, &session->ob_pq); + + CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_DATA == a->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == c->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_DATA == d->dpri); + + CU_ASSERT(1 == dc->queued); + + da = create_data_ob_item(); + + nghttp2_stream_attach_data(a, da, &session->ob_pq); + + CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == a->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == b->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == c->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_DATA == d->dpri); + + CU_ASSERT(1 == da->queued); + + nghttp2_stream_detach_data(a, &session->ob_pq); + + CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_DATA == a->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == c->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_DATA == d->dpri); + + dd = create_data_ob_item(); + + nghttp2_stream_attach_data(d, dd, &session->ob_pq); + + CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_DATA == a->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == c->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == d->dpri); + + CU_ASSERT(0 == dd->queued); + + nghttp2_stream_detach_data(c, &session->ob_pq); + + CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_DATA == a->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_DATA == c->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == d->dpri); + + CU_ASSERT(1 == dd->queued); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_attach_data_subtree(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *f; + nghttp2_outbound_item *db, *de; + + (void)d; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + e = open_stream(session, 9); + f = open_stream_with_dep(session, 11, e); + + /* a e + * | | + * b--c f + * | + * d + */ + + de = create_data_ob_item(); + + nghttp2_stream_attach_data(e, de, &session->ob_pq); + + db = create_data_ob_item(); + + nghttp2_stream_attach_data(b, db, &session->ob_pq); + + nghttp2_stream_dep_insert_subtree(a, e, &session->ob_pq); + + /* a + * | + * e + * | + * f--b--c + * | + * d + */ + + CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == b->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == e->dpri); + CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_DATA == f->dpri); + + nghttp2_stream_dep_remove_subtree(b); + + nghttp2_stream_dep_make_root(b->stream_group, b, &session->ob_pq); + + CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri); + + nghttp2_session_del(session); +} diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index aaa8025b..20425b5c 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -31,6 +31,7 @@ void test_nghttp2_session_recv_invalid_frame(void); void test_nghttp2_session_recv_eof(void); void test_nghttp2_session_recv_data(void); void test_nghttp2_session_recv_continuation(void); +void test_nghttp2_session_recv_headers_with_priority(void); void test_nghttp2_session_recv_premature_headers(void); void test_nghttp2_session_continue(void); void test_nghttp2_session_add_frame(void); @@ -96,5 +97,11 @@ void test_nghttp2_session_pack_headers_with_padding2(void); void test_nghttp2_session_pack_headers_with_padding3(void); void test_nghttp2_session_pack_headers_with_padding4(void); void test_nghttp2_pack_settings_payload(void); +void test_nghttp2_session_stream_dep_add(void); +void test_nghttp2_session_stream_dep_remove(void); +void test_nghttp2_session_stream_dep_add_subtree(void); +void test_nghttp2_session_stream_dep_remove_subtree(void); +void test_nghttp2_session_stream_attach_data(void); +void test_nghttp2_session_stream_attach_data_subtree(void); #endif /* NGHTTP2_SESSION_TEST_H */ diff --git a/tests/nghttp2_test_helper.c b/tests/nghttp2_test_helper.c index fee34ad1..979156c3 100644 --- a/tests/nghttp2_test_helper.c +++ b/tests/nghttp2_test_helper.c @@ -29,6 +29,7 @@ #include #include "nghttp2_helper.h" +#include "nghttp2_priority_spec.h" int unpack_framebuf(nghttp2_frame *frame, nghttp2_bufs *bufs) { @@ -213,3 +214,47 @@ void bufs_large_init(nghttp2_bufs *bufs, size_t chunk_size) /* 2 for PAD_HIGH and PAD_LOW */ nghttp2_bufs_init2(bufs, chunk_size, 16, NGHTTP2_FRAME_HDLEN + 2); } + +nghttp2_stream* open_stream(nghttp2_session *session, int32_t stream_id) +{ + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_group_init(&pri_spec, stream_id, + NGHTTP2_DEFAULT_WEIGHT); + + return nghttp2_session_open_stream(session, stream_id, + NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, + NGHTTP2_STREAM_OPENED, + NULL); +} + +nghttp2_stream* open_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream) +{ + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_dep_init(&pri_spec, dep_stream->stream_id, 0); + + return nghttp2_session_open_stream(session, stream_id, + NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, + NGHTTP2_STREAM_OPENED, + NULL); +} + +nghttp2_stream* open_stream_with_dep_excl(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream) +{ + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_dep_init(&pri_spec, dep_stream->stream_id, 1); + + return nghttp2_session_open_stream(session, stream_id, + NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, + NGHTTP2_STREAM_OPENED, + NULL); +} diff --git a/tests/nghttp2_test_helper.h b/tests/nghttp2_test_helper.h index 6ed49bca..11b96f3f 100644 --- a/tests/nghttp2_test_helper.h +++ b/tests/nghttp2_test_helper.h @@ -31,6 +31,7 @@ #include "nghttp2_frame.h" #include "nghttp2_hd.h" +#include "nghttp2_session.h" #define MAKE_NV(NAME, VALUE) \ { (uint8_t*)NAME, (uint8_t*)VALUE, strlen(NAME), strlen(VALUE) } @@ -81,4 +82,14 @@ void frame_pack_bufs_init(nghttp2_bufs *bufs); void bufs_large_init(nghttp2_bufs *bufs, size_t chunk_size); +nghttp2_stream* open_stream(nghttp2_session *session, int32_t stream_id); + +nghttp2_stream* open_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_stream* open_stream_with_dep_excl(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + #endif /* NGHTTP2_TEST_HELPER_H */